UNPKG

@theia/monaco

Version:
235 lines (207 loc) • 9.92 kB
// ***************************************************************************** // Copyright (C) 2023 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 { EditorServiceOverrides, MonacoEditor, MonacoEditorServices } from './monaco-editor'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from '@theia/monaco-editor-core/esm/vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation'; import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices'; import { ServiceCollection } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/serviceCollection'; import { Disposable, DisposableCollection, Emitter, Event, TextDocumentContentChangeDelta, URI } from '@theia/core'; import { MonacoEditorModel } from './monaco-editor-model'; import { Dimension, EditorMouseEvent, MouseTarget, Position, TextDocumentChangeEvent } from '@theia/editor/lib/browser'; import * as monaco from '@theia/monaco-editor-core'; import { ElementExt } from '@theia/core/shared/@lumino/domutils'; import { Selection } from '@theia/editor/lib/browser/editor'; import { SelectionDirection } from '@theia/monaco-editor-core/esm/vs/editor/common/core/selection'; import { ShowLightbulbIconMode } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions'; export class SimpleMonacoEditor extends MonacoEditorServices implements Disposable { protected editor: CodeEditorWidget; protected readonly toDispose = new DisposableCollection(); protected readonly onCursorPositionChangedEmitter = new Emitter<Position>(); protected readonly onFocusChangedEmitter = new Emitter<boolean>(); protected readonly onDocumentContentChangedEmitter = new Emitter<TextDocumentChangeEvent>(); readonly onDocumentContentChanged = this.onDocumentContentChangedEmitter.event; protected readonly onMouseDownEmitter = new Emitter<EditorMouseEvent>(); readonly onDidChangeReadOnly = this.document.onDidChangeReadOnly; protected readonly onLanguageChangedEmitter = new Emitter<string>(); readonly onLanguageChanged = this.onLanguageChangedEmitter.event; protected readonly onScrollChangedEmitter = new Emitter<void>(); readonly onEncodingChanged = this.document.onDidChangeEncoding; protected readonly onResizeEmitter = new Emitter<Dimension | null>(); readonly onDidResize = this.onResizeEmitter.event; get onDispose(): Event<void> { return this.editor.onDidDispose; } constructor( readonly uri: URI, readonly document: MonacoEditorModel, readonly node: HTMLElement, services: MonacoEditorServices, options?: MonacoEditor.IOptions, override?: EditorServiceOverrides, widgetOptions?: ICodeEditorWidgetOptions ) { super(services); this.toDispose.pushAll([ this.onCursorPositionChangedEmitter, this.onFocusChangedEmitter, this.onDocumentContentChangedEmitter, this.onMouseDownEmitter, this.onLanguageChangedEmitter, this.onScrollChangedEmitter ]); this.toDispose.push(this.create({ ...MonacoEditor.createReadOnlyOptions(document.readOnly), ...options, model: undefined, }, override, widgetOptions)); this.addHandlers(this.editor); this.editor.setModel(document.textEditorModel); } getControl(): CodeEditorWidget { return this.editor; } onSelectionChanged(listener: (range: Selection) => void): Disposable { return this.editor.onDidChangeCursorSelection(event => listener({ ...this.m2p.asRange(event.selection), direction: event.selection.getDirection() === SelectionDirection.LTR ? 'ltr' : 'rtl' })); } protected create(options?: MonacoEditor.IOptions, override?: EditorServiceOverrides, widgetOptions?: ICodeEditorWidgetOptions): Disposable { const combinedOptions = { ...options, lightbulb: { enabled: ShowLightbulbIconMode.On }, fixedOverflowWidgets: true, automaticLayout: true, scrollbar: { useShadows: false, verticalHasArrows: false, horizontalHasArrows: false, verticalScrollbarSize: 10, horizontalScrollbarSize: 10, ...options?.scrollbar, } }; const instantiator = this.getInstantiatorWithOverrides(override); return this.editor = instantiator.createInstance(CodeEditorWidget, this.node, { ...combinedOptions, dimension: { width: 0, height: 0 }, }, widgetOptions ?? {}); } protected addHandlers(codeEditor: CodeEditorWidget): void { this.toDispose.push(codeEditor.onDidChangeModelLanguage(e => this.fireLanguageChanged(e.newLanguage) )); this.toDispose.push(codeEditor.onDidChangeConfiguration(() => this.refresh())); this.toDispose.push(codeEditor.onDidChangeModel(() => this.refresh())); this.toDispose.push(codeEditor.onDidChangeModelContent(e => { this.refresh(); this.onDocumentContentChangedEmitter.fire({ document: this.document, contentChanges: e.changes.map(this.mapModelContentChange.bind(this)) }); })); this.toDispose.push(codeEditor.onMouseDown(e => { const { element, position, range } = e.target; this.onMouseDownEmitter.fire({ target: { ...(e.target as unknown as MouseTarget), element: element || undefined, mouseColumn: this.m2p.asPosition(undefined, e.target.mouseColumn).character, range: range && this.m2p.asRange(range) || undefined, position: position && this.m2p.asPosition(position.lineNumber, position.column) || undefined, detail: undefined }, event: e.event.browserEvent }); })); this.toDispose.push(codeEditor.onDidScrollChange(e => { this.onScrollChangedEmitter.fire(undefined); })); this.toDispose.push(this.onDidChangeReadOnly(readOnly => { codeEditor.updateOptions(MonacoEditor.createReadOnlyOptions(readOnly)); })); } setLanguage(languageId: string): void { monaco.editor.setModelLanguage(this.document.textEditorModel, languageId); } protected fireLanguageChanged(languageId: string): void { this.onLanguageChangedEmitter.fire(languageId); } protected getInstantiatorWithOverrides(override?: EditorServiceOverrides): IInstantiationService { const instantiator = StandaloneServices.get(IInstantiationService); if (override) { const overrideServices = new ServiceCollection(...override); const childService = instantiator.createChild(overrideServices); this.toDispose.push(childService); return childService; } return instantiator; } protected mapModelContentChange(change: monaco.editor.IModelContentChange): TextDocumentContentChangeDelta { return { range: this.m2p.asRange(change.range), rangeLength: change.rangeLength, text: change.text }; } focus(): void { this.editor.focus(); } refresh(): void { this.autoresize(); } resizeToFit(): void { this.autoresize(); // eslint-disable-next-line no-null/no-null this.onResizeEmitter.fire(null); } setSize(dimension: Dimension): void { this.resize(dimension); this.onResizeEmitter.fire(dimension); } protected autoresize(): void { this.resize(); } protected resize(dimension?: Dimension): void { if (this.node) { const layoutSize = this.computeLayoutSize(this.node, dimension); this.editor.layout(layoutSize); } } protected computeLayoutSize(hostNode: HTMLElement, dimension: monaco.editor.IDimension | undefined): monaco.editor.IDimension { if (dimension && dimension.width >= 0 && dimension.height >= 0) { return dimension; } const boxSizing = ElementExt.boxSizing(hostNode); const width = (!dimension || dimension.width < 0) ? this.getWidth(hostNode, boxSizing) : dimension.width; const height = (!dimension || dimension.height < 0) ? this.getHeight(hostNode, boxSizing) : dimension.height; return { width, height }; } protected getWidth(hostNode: HTMLElement, boxSizing: ElementExt.IBoxSizing): number { return hostNode.offsetWidth - boxSizing.horizontalSum; } protected getHeight(hostNode: HTMLElement, boxSizing: ElementExt.IBoxSizing): number { return this.editor.getContentHeight(); } dispose(): void { this.toDispose.dispose(); } }