UNPKG

@theia/monaco

Version:
494 lines • 25.5 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2018 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** var MonacoEditorProvider_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.MonacoEditorProvider = exports.SAVE_PARTICIPANT_DEFAULT_ORDER = exports.SaveParticipant = exports.MonacoEditorFactory = void 0; const tslib_1 = require("tslib"); /* eslint-disable @typescript-eslint/no-explicit-any */ const uri_1 = require("@theia/core/lib/common/uri"); const diff_uris_1 = require("@theia/core/lib/browser/diff-uris"); const inversify_1 = require("@theia/core/shared/inversify"); const common_1 = require("@theia/core/lib/common"); const monaco_diff_editor_1 = require("./monaco-diff-editor"); const monaco_diff_navigator_factory_1 = require("./monaco-diff-navigator-factory"); const monaco_editor_1 = require("./monaco-editor"); const monaco_editor_model_1 = require("./monaco-editor-model"); const monaco_workspace_1 = require("./monaco-workspace"); const core_1 = require("@theia/core"); const browser_1 = require("@theia/core/lib/browser"); const monaco_resolved_keybinding_1 = require("./monaco-resolved-keybinding"); const monaco_to_protocol_converter_1 = require("./monaco-to-protocol-converter"); const protocol_to_monaco_converter_1 = require("./protocol-to-monaco-converter"); const monaco = require("@theia/monaco-editor-core"); const standaloneServices_1 = require("@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices"); const opener_1 = require("@theia/monaco-editor-core/esm/vs/platform/opener/common/opener"); const keybinding_1 = require("@theia/monaco-editor-core/esm/vs/platform/keybinding/common/keybinding"); const contextView_1 = require("@theia/monaco-editor-core/esm/vs/platform/contextview/browser/contextView"); const event_1 = require("@theia/monaco-editor-core/esm/vs/base/common/event"); const keybindings_1 = require("@theia/monaco-editor-core/esm/vs/base/common/keybindings"); const contextkey_1 = require("@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey"); const resolverService_1 = require("@theia/monaco-editor-core/esm/vs/editor/common/services/resolverService"); const markdown_rendering_1 = require("@theia/core/lib/common/markdown-rendering"); const simple_monaco_editor_1 = require("./simple-monaco-editor"); const promise_util_1 = require("@theia/core/lib/common/promise-util"); const common_2 = require("@theia/filesystem/lib/common"); const monaco_utilities_1 = require("./monaco-utilities"); const editor_preferences_1 = require("@theia/editor/lib/common/editor-preferences"); exports.MonacoEditorFactory = Symbol('MonacoEditorFactory'); exports.SaveParticipant = Symbol('SaveParticipant'); exports.SAVE_PARTICIPANT_DEFAULT_ORDER = 0; let MonacoEditorProvider = class MonacoEditorProvider { static { MonacoEditorProvider_1 = this; } /** * Returns the last focused MonacoEditor. * It takes into account inline editors as well. * If you are interested only in standalone editors then use `MonacoEditor.getCurrent(EditorManager)` */ get current() { return this._current; } constructor(m2p, p2m, workspace, editorPreferences, diffNavigatorFactory) { this.m2p = m2p; this.p2m = p2m; this.workspace = workspace; this.editorPreferences = editorPreferences; this.diffNavigatorFactory = diffNavigatorFactory; } async getModel(uri, toDispose) { const reference = await standaloneServices_1.StandaloneServices.get(resolverService_1.ITextModelService).createModelReference(monaco.Uri.from(uri.toComponents())); // if document is invalid makes sure that all events from underlying resource are processed before throwing invalid model if (!reference.object.valid) { await reference.object.sync(); } if (!reference.object.valid) { reference.dispose(); throw Object.assign(new Error(`'${uri.toString()}' is invalid`), { code: 'MODEL_IS_INVALID' }); } toDispose.push(reference); return reference.object; } async get(uri) { await this.editorPreferences.ready; return this.doCreateEditor(uri, (override, toDispose) => this.createEditor(uri, override, toDispose)); } async doCreateEditor(uri, factory) { const domNode = document.createElement('div'); const contextKeyService = standaloneServices_1.StandaloneServices.get(contextkey_1.IContextKeyService).createScoped(domNode); standaloneServices_1.StandaloneServices.get(opener_1.IOpenerService).registerOpener({ open: (u, options) => this.interceptOpen(u, options) }); const overrides = [ [contextkey_1.IContextKeyService, contextKeyService], ]; const toDispose = new common_1.DisposableCollection(); const editor = await factory(overrides, toDispose); editor.onDispose(() => toDispose.dispose()); if (editor instanceof monaco_editor_1.MonacoEditor) { this.injectKeybindingResolver(editor); toDispose.push(editor.onFocusChanged(focused => { if (focused) { this._current = editor; } })); toDispose.push(common_1.Disposable.create(() => { if (this._current === editor) { this._current = undefined; } })); } return editor; } /** * Intercept internal Monaco open calls and delegate to OpenerService. */ async interceptOpen(monacoUri, monacoOptions) { let options = undefined; if (monacoOptions) { if ('openToSide' in monacoOptions && monacoOptions.openToSide) { options = Object.assign(options || {}, { widgetOptions: { mode: 'split-right' } }); } if ('openExternal' in monacoOptions && monacoOptions.openExternal) { options = Object.assign(options || {}, { openExternal: true }); } } const uri = new uri_1.default(monacoUri.toString()); try { await (0, browser_1.open)(this.openerService, uri, options); return true; } catch (e) { console.error(`Fail to open '${uri.toString()}':`, e); return false; } } injectKeybindingResolver(editor) { const keybindingService = standaloneServices_1.StandaloneServices.get(keybinding_1.IKeybindingService); keybindingService.resolveKeybinding = keybinding => [new monaco_resolved_keybinding_1.MonacoResolvedKeybinding(monaco_resolved_keybinding_1.MonacoResolvedKeybinding.keySequence(keybinding.chords), this.keybindingRegistry)]; keybindingService.resolveKeyboardEvent = keyboardEvent => { const keybinding = new keybindings_1.KeyCodeChord(keyboardEvent.ctrlKey, keyboardEvent.shiftKey, keyboardEvent.altKey, keyboardEvent.metaKey, keyboardEvent.keyCode); return new monaco_resolved_keybinding_1.MonacoResolvedKeybinding(monaco_resolved_keybinding_1.MonacoResolvedKeybinding.keySequence([keybinding]), this.keybindingRegistry); }; } createEditor(uri, override, toDispose) { if (diff_uris_1.DiffUris.isDiffUri(uri)) { return this.createMonacoDiffEditor(uri, override, toDispose); } return this.createMonacoEditor(uri, override, toDispose); } get preferencePrefixes() { return ['editor.']; } async createMonacoEditor(uri, override, toDispose) { const model = await this.getModel(uri, toDispose); const options = this.createMonacoEditorOptions(model); const factory = this.factories.getContributions().find(({ scheme }) => uri.scheme === scheme); const editor = factory ? await factory.create(model, options, override) : await monaco_editor_1.MonacoEditor.create(uri, model, document.createElement('div'), this.services, options, override); toDispose.push(this.editorPreferences.onPreferenceChanged(event => { if (event.affects(uri.toString(), model.languageId)) { this.updateMonacoEditorOptions(editor, event); } })); toDispose.push(editor.onLanguageChanged(() => this.updateMonacoEditorOptions(editor))); toDispose.push(editor.onDidChangeReadOnly(() => this.updateReadOnlyMessage(options, model.readOnly))); toDispose.push(editor.document.onModelWillSaveModel(e => this.runSaveParticipants(editor, e.token, e.options))); return editor; } updateReadOnlyMessage(options, readOnly) { options.readOnlyMessage = markdown_rendering_1.MarkdownString.is(readOnly) ? readOnly : undefined; } createMonacoEditorOptions(model) { const options = this.createOptions(this.preferencePrefixes, model.uri, model.languageId); // eslint-disable-next-line no-null/no-null options.model = null; // explicitly set to null to avoid creating an initial model automatically options.readOnly = model.readOnly; this.updateReadOnlyMessage(options, model.readOnly); options.lineNumbersMinChars = model.lineNumbersMinChars; return options; } updateMonacoEditorOptions(editor, event) { if (event) { const preferenceName = event.preferenceName; const overrideIdentifier = editor.document.languageId; const newValue = this.editorPreferences.get({ preferenceName, overrideIdentifier }, undefined, editor.uri.toString()); editor.getControl().updateOptions(this.setOption(preferenceName, newValue, this.preferencePrefixes)); } else { const options = this.createMonacoEditorOptions(editor.document); delete options.model; editor.getControl().updateOptions(options); } } get diffPreferencePrefixes() { return [...this.preferencePrefixes, 'diffEditor.']; } async createMonacoDiffEditor(uri, override, toDispose) { const [original, modified] = diff_uris_1.DiffUris.decode(uri); const [originalModel, modifiedModel] = await Promise.all([this.getModel(original, toDispose), this.getModel(modified, toDispose)]); const options = this.createMonacoDiffEditorOptions(originalModel, modifiedModel); const editor = new monaco_diff_editor_1.MonacoDiffEditor(uri, document.createElement('div'), originalModel, modifiedModel, this.services, this.diffNavigatorFactory, options, override); toDispose.push(this.editorPreferences.onPreferenceChanged(event => { const originalFileUri = original.withoutQuery().withScheme('file').toString(); if (event.affects(originalFileUri, editor.document.languageId)) { this.updateMonacoDiffEditorOptions(editor, event, originalFileUri); } })); toDispose.push(editor.onLanguageChanged(() => this.updateMonacoDiffEditorOptions(editor))); return editor; } createMonacoDiffEditorOptions(original, modified) { const options = this.createOptions(this.diffPreferencePrefixes, modified.uri, modified.languageId); options.originalEditable = !original.readOnly; options.readOnly = modified.readOnly; options.readOnlyMessage = markdown_rendering_1.MarkdownString.is(modified.readOnly) ? modified.readOnly : undefined; return options; } updateMonacoDiffEditorOptions(editor, event, resourceUri) { if (event) { const preferenceName = event.preferenceName; const overrideIdentifier = editor.document.languageId; const newValue = this.editorPreferences.get({ preferenceName, overrideIdentifier }, undefined, resourceUri); editor.diffEditor.updateOptions(this.setOption(preferenceName, newValue, this.diffPreferencePrefixes)); } else { const options = this.createMonacoDiffEditorOptions(editor.originalModel, editor.modifiedModel); editor.diffEditor.updateOptions(options); } } createOptions(prefixes, uri, overrideIdentifier) { const flat = {}; for (const preferenceName of Object.keys(this.editorPreferences)) { flat[preferenceName] = this.editorPreferences.get({ preferenceName, overrideIdentifier }, undefined, uri); } return Object.entries(flat).reduce((tree, [preferenceName, value]) => this.setOption(preferenceName, (0, common_1.deepClone)(value), prefixes, tree), {}); } setOption(preferenceName, value, prefixes, options = {}) { const optionName = this.toOptionName(preferenceName, prefixes); this.doSetOption(options, value, optionName.split('.')); return options; } toOptionName(preferenceName, prefixes) { for (const prefix of prefixes) { if (preferenceName.startsWith(prefix)) { return preferenceName.substring(prefix.length); } } return preferenceName; } doSetOption(obj, value, names) { for (let i = 0; i < names.length - 1; i++) { const name = names[i]; if (obj[name] === undefined) { obj = obj[name] = {}; } else if (typeof obj[name] !== 'object' || obj[name] === null) { // eslint-disable-line no-null/no-null console.warn(`Preference (diff)editor.${names.join('.')} conflicts with another preference name.`); obj = obj[name] = {}; } else { obj = obj[name]; } } obj[names[names.length - 1]] = value; } getDiffNavigator(editor) { if (editor instanceof monaco_diff_editor_1.MonacoDiffEditor) { return editor.diffNavigator; } return monaco_diff_navigator_factory_1.MonacoDiffNavigatorFactory.nullNavigator; } /** * Creates an instance of the standard MonacoEditor with a StandaloneCodeEditor as its Monaco delegate. * Among other differences, these editors execute basic actions like typing or deletion via commands that may be overridden by extensions. * @deprecated Most use cases for inline editors should be served by `createSimpleInline` instead. */ async createInline(uri, node, options) { return this.doCreateEditor(uri, async (override, toDispose) => { const overrides = override ? Array.from(override) : []; overrides.push([contextView_1.IContextMenuService, { showContextMenu: () => { }, onDidShowContextMenu: event_1.Event.None, onDidHideContextMenu: event_1.Event.None }]); const document = await this.getModel(uri, toDispose); document.suppressOpenEditorWhenDirty = true; const model = (await document.load()).textEditorModel; return await monaco_editor_1.MonacoEditor.create(uri, document, node, this.services, Object.assign({ model, autoSizing: false, minHeight: 1, maxHeight: 1 }, MonacoEditorProvider_1.inlineOptions, options), overrides); }); } /** * Creates an instance of the standard MonacoEditor with a CodeEditorWidget as its Monaco delegate. * In addition to the service customizability of the StandaloneCodeEditor,This editor allows greater customization the editor contributions active in the widget. * See {@link ICodeEditorWidgetOptions.contributions}. */ async createSimpleInline(uri, node, options, widgetOptions) { return this.doCreateEditor(uri, async (override, toDispose) => { const overrides = override ? Array.from(override) : []; overrides.push([contextView_1.IContextMenuService, { showContextMenu: () => { }, onDidShowContextMenu: event_1.Event.None, onDidHideContextMenu: event_1.Event.None }]); const document = await this.getModel(uri, toDispose); document.suppressOpenEditorWhenDirty = true; const model = (await document.load()).textEditorModel; const baseOptions = { model, autoSizing: false, minHeight: 1, maxHeight: 1 }; const editorOptions = { ...baseOptions, ...MonacoEditorProvider_1.inlineOptions, ...options }; return new simple_monaco_editor_1.SimpleMonacoEditor(uri, document, node, this.services, editorOptions, overrides, { isSimpleWidget: true, ...widgetOptions }); }); } static { this.inlineOptions = { wordWrap: 'on', overviewRulerLanes: 0, glyphMargin: false, lineNumbers: 'off', folding: false, selectOnLineNumbers: false, hideCursorInOverviewRuler: true, selectionHighlight: false, scrollbar: { horizontal: 'hidden' }, lineDecorationsWidth: 0, overviewRulerBorder: false, scrollBeyondLastLine: false, renderLineHighlight: 'none', fixedOverflowWidgets: true, acceptSuggestionOnEnter: 'smart', minimap: { enabled: false } }; } async createEmbeddedDiffEditor(parentEditor, node, originalUri, modifiedUri = parentEditor.uri, options) { options = { scrollBeyondLastLine: true, overviewRulerLanes: 2, fixedOverflowWidgets: true, minimap: { enabled: false }, renderSideBySide: false, readOnly: false, renderIndicators: false, diffAlgorithm: 'advanced', stickyScroll: { enabled: false }, ...options, scrollbar: { verticalScrollbarSize: 14, horizontal: 'auto', useShadows: true, verticalHasArrows: false, horizontalHasArrows: false, ...options?.scrollbar } }; const uri = diff_uris_1.DiffUris.encode(originalUri, modifiedUri); return await this.doCreateEditor(uri, async (override, toDispose) => new monaco_diff_editor_1.MonacoDiffEditor(uri, node, await this.getModel(originalUri, toDispose), await this.getModel(modifiedUri, toDispose), this.services, this.diffNavigatorFactory, options, override, parentEditor)); } init() { this.saveParticipants = this.saveProviderContributions.getContributions().slice().sort((left, right) => left.order - right.order); this.registerSaveParticipant({ order: 1000, applyChangesOnSave: (editor, cancellationToken, options) => this.formatOnSave(editor, editor.document, cancellationToken, options) }); } registerSaveParticipant(saveParticipant) { if (this.saveParticipants.find(value => value === saveParticipant)) { throw new Error('Save participant already registered'); } this.saveParticipants.push(saveParticipant); this.saveParticipants.sort((left, right) => left.order - right.order); return common_1.Disposable.create(() => { const index = this.saveParticipants.indexOf(saveParticipant); if (index >= 0) { this.saveParticipants.splice(index, 1); } }); } shouldFormat(model, options) { if (options.saveReason !== monaco_editor_model_1.TextDocumentSaveReason.Manual) { return false; } switch (options.formatType) { case 1 /* FormatType.ON */: return true; case 2 /* FormatType.OFF */: return false; case 3 /* FormatType.DIRTY */: return model.dirty; } return true; } async runSaveParticipants(editor, cancellationToken, options) { const initialState = editor.document.createSnapshot(); for (const participant of this.saveParticipants) { if (cancellationToken.isCancellationRequested) { break; } const snapshot = editor.document.createSnapshot(); try { await participant.applyChangesOnSave(editor, cancellationToken, options); } catch (e) { console.error(e); editor.document.applySnapshot(snapshot); } } if (cancellationToken.isCancellationRequested) { editor.document.applySnapshot(initialState); } } async formatOnSave(editor, model, cancellationToken, options) { if (!this.shouldFormat(model, options)) { return; } const overrideIdentifier = model.languageId; const uri = model.uri.toString(); const formatOnSave = this.editorPreferences.get({ preferenceName: 'editor.formatOnSave', overrideIdentifier }, undefined, uri); if (formatOnSave) { const formatOnSaveTimeout = this.editorPreferences.get({ preferenceName: 'editor.formatOnSaveTimeout', overrideIdentifier }, undefined, uri); await Promise.race([ (0, promise_util_1.timeoutReject)(formatOnSaveTimeout, `Aborted format on save after ${formatOnSaveTimeout}ms`), await editor.runAction('editor.action.formatDocument') ]); } const shouldRemoveWhiteSpace = this.filePreferences.get({ preferenceName: 'files.trimTrailingWhitespace', overrideIdentifier }, undefined, uri); if (shouldRemoveWhiteSpace) { await editor.runAction('editor.action.trimTrailingWhitespace'); } const shouldInsertFinalNewline = this.filePreferences.get({ preferenceName: 'files.insertFinalNewline', overrideIdentifier }, undefined, uri); if (shouldInsertFinalNewline) { this.insertFinalNewline(model); } } insertFinalNewline(editorModel) { (0, monaco_utilities_1.insertFinalNewline)(editorModel); } }; exports.MonacoEditorProvider = MonacoEditorProvider; tslib_1.__decorate([ (0, inversify_1.inject)(core_1.ContributionProvider), (0, inversify_1.named)(exports.MonacoEditorFactory), tslib_1.__metadata("design:type", Object) ], MonacoEditorProvider.prototype, "factories", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(monaco_editor_1.MonacoEditorServices), tslib_1.__metadata("design:type", monaco_editor_1.MonacoEditorServices) ], MonacoEditorProvider.prototype, "services", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(browser_1.KeybindingRegistry), tslib_1.__metadata("design:type", browser_1.KeybindingRegistry) ], MonacoEditorProvider.prototype, "keybindingRegistry", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(browser_1.OpenerService), tslib_1.__metadata("design:type", Object) ], MonacoEditorProvider.prototype, "openerService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(core_1.ContributionProvider), (0, inversify_1.named)(exports.SaveParticipant), tslib_1.__metadata("design:type", Object) ], MonacoEditorProvider.prototype, "saveProviderContributions", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(common_2.FileSystemPreferences), tslib_1.__metadata("design:type", Object) ], MonacoEditorProvider.prototype, "filePreferences", void 0); tslib_1.__decorate([ (0, inversify_1.postConstruct)(), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", []), tslib_1.__metadata("design:returntype", void 0) ], MonacoEditorProvider.prototype, "init", null); exports.MonacoEditorProvider = MonacoEditorProvider = MonacoEditorProvider_1 = tslib_1.__decorate([ (0, inversify_1.injectable)(), tslib_1.__param(0, (0, inversify_1.inject)(monaco_to_protocol_converter_1.MonacoToProtocolConverter)), tslib_1.__param(1, (0, inversify_1.inject)(protocol_to_monaco_converter_1.ProtocolToMonacoConverter)), tslib_1.__param(2, (0, inversify_1.inject)(monaco_workspace_1.MonacoWorkspace)), tslib_1.__param(3, (0, inversify_1.inject)(editor_preferences_1.EditorPreferences)), tslib_1.__param(4, (0, inversify_1.inject)(monaco_diff_navigator_factory_1.MonacoDiffNavigatorFactory)), tslib_1.__metadata("design:paramtypes", [monaco_to_protocol_converter_1.MonacoToProtocolConverter, protocol_to_monaco_converter_1.ProtocolToMonacoConverter, monaco_workspace_1.MonacoWorkspace, Object, monaco_diff_navigator_factory_1.MonacoDiffNavigatorFactory]) ], MonacoEditorProvider); //# sourceMappingURL=monaco-editor-provider.js.map