UNPKG

@theia/monaco

Version:
170 lines (147 loc) 8.26 kB
// ***************************************************************************** // Copyright (C) 2017 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 // ***************************************************************************** import { injectable, inject, decorate, named } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { OpenerService, open, WidgetOpenMode, ApplicationShell, PreferenceService } from '@theia/core/lib/browser'; import { EditorWidget, EditorOpenerOptions, EditorManager, CustomEditorWidget } from '@theia/editor/lib/browser'; import { MonacoEditor } from './monaco-editor'; import { MonacoToProtocolConverter } from './monaco-to-protocol-converter'; import { MonacoEditorModel } from './monaco-editor-model'; import { IResourceEditorInput, ITextResourceEditorInput } from '@theia/monaco-editor-core/esm/vs/platform/editor/common/editor'; import { StandaloneCodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditorService'; import { StandaloneCodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor'; import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser'; import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey'; import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService'; import { ContributionProvider } from '@theia/core'; decorate(injectable(), StandaloneCodeEditorService); export const VSCodeContextKeyService = Symbol('VSCodeContextKeyService'); export const VSCodeThemeService = Symbol('VSCodeThemeService'); export const MonacoEditorServiceFactory = Symbol('MonacoEditorServiceFactory'); export type MonacoEditorServiceFactoryType = (contextKeyService: IContextKeyService, themeService: IThemeService) => MonacoEditorService; /** * contribution provider to extend the active editor handling to other editor types than just standalone editor widgets. */ export const ActiveMonacoEditorContribution = Symbol('ActiveMonacoEditorContribution'); export interface ActiveMonacoEditorContribution { getActiveEditor(): ICodeEditor | undefined; } @injectable() export class MonacoEditorService extends StandaloneCodeEditorService { public static readonly ENABLE_PREVIEW_PREFERENCE: string = 'editor.enablePreview'; @inject(OpenerService) protected readonly openerService: OpenerService; @inject(MonacoToProtocolConverter) protected readonly m2p: MonacoToProtocolConverter; @inject(ApplicationShell) protected readonly shell: ApplicationShell; @inject(EditorManager) protected readonly editors: EditorManager; @inject(PreferenceService) protected readonly preferencesService: PreferenceService; @inject(ContributionProvider) @named(ActiveMonacoEditorContribution) protected readonly activeMonacoEditorContribution: ContributionProvider<ActiveMonacoEditorContribution>; constructor(@inject(VSCodeContextKeyService) contextKeyService: IContextKeyService, @inject(VSCodeThemeService) themeService: IThemeService) { super(contextKeyService, themeService); } /** * Monaco active editor is either focused or last focused editor. */ override getActiveCodeEditor(): ICodeEditor | null { let editor = MonacoEditor.getCurrent(this.editors); if (!editor && CustomEditorWidget.is(this.shell.activeWidget)) { const model = this.shell.activeWidget.modelRef.object; if (model?.editorTextModel instanceof MonacoEditorModel) { editor = MonacoEditor.findByDocument(this.editors, model.editorTextModel)[0]; } } const candidate = editor?.getControl(); // Since we extend a private super class, we have to check that the thing that matches the public interface also matches the private expectations the superclass. if (candidate instanceof StandaloneCodeEditor) { return candidate; } for (const activeEditorProvider of this.activeMonacoEditorContribution.getContributions()) { const activeEditor = activeEditorProvider.getActiveEditor(); if (activeEditor) { return activeEditor; } } /* eslint-disable-next-line no-null/no-null */ return null; } override async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> { const uri = new URI(input.resource.toString()); const openerOptions = this.createEditorOpenerOptions(input, source, sideBySide); const widget = await open(this.openerService, uri, openerOptions); const editorWidget = await this.findEditorWidgetByUri(widget, uri.toString()); const candidate = MonacoEditor.get(editorWidget)?.getControl(); // Since we extend a private super class, we have to check that the thing that matches the public interface also matches the private expectations the superclass. // eslint-disable-next-line no-null/no-null return candidate instanceof StandaloneCodeEditor ? candidate : null; } protected async findEditorWidgetByUri(widget: object | undefined, uriAsString: string): Promise<EditorWidget | undefined> { if (widget instanceof EditorWidget) { if (widget.editor.uri.toString() === uriAsString) { return widget; } return undefined; } if (ApplicationShell.TrackableWidgetProvider.is(widget)) { for (const childWidget of widget.getTrackableWidgets()) { const editorWidget = await this.findEditorWidgetByUri(childWidget, uriAsString); if (editorWidget) { return editorWidget; } } } return undefined; } protected createEditorOpenerOptions(input: IResourceEditorInput | ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): EditorOpenerOptions { const mode = this.getEditorOpenMode(input); const widgetOptions = this.getWidgetOptions(source, sideBySide); const selection = this.getSelection(input); const preview = !!this.preferencesService.get<boolean>(MonacoEditorService.ENABLE_PREVIEW_PREFERENCE, false); return { mode, widgetOptions, preview, selection }; } protected getSelection(input: IResourceEditorInput | ITextResourceEditorInput): EditorOpenerOptions['selection'] { if ('options' in input && input.options && 'selection' in input.options) { return this.m2p.asRange(input.options.selection); } } protected getEditorOpenMode(input: IResourceEditorInput): WidgetOpenMode { const options = { preserveFocus: false, revealIfVisible: true, ...input.options }; if (options.preserveFocus) { return 'reveal'; } return options.revealIfVisible ? 'activate' : 'open'; } protected getWidgetOptions(source: ICodeEditor | null, sideBySide?: boolean): ApplicationShell.WidgetOptions | undefined { const ref = MonacoEditor.getWidgetFor(this.editors, source); if (!ref) { return undefined; } const area = (ref && this.shell.getAreaFor(ref)) || 'main'; const mode = ref && sideBySide ? 'split-right' : undefined; if (area === 'secondaryWindow') { return { area: 'main', mode }; } return { area, mode, ref }; } }