// ***************************************************************************** // Copyright (C) 2018 Red Hat, Inc. 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 { TextEditorsExt, EditorChangedPropertiesData, TextEditorPositionData, TextEditorsMain, PLUGIN_RPC_CONTEXT, TextEditorDiffInformationDto } from '../common/plugin-api-rpc'; import { RPCProtocol } from '../common/rpc-protocol'; import * as theia from '@theia/plugin'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { EditorsAndDocumentsExtImpl } from './editors-and-documents'; import { TextEditorExt } from './text-editor'; import * as Converters from './type-converters'; import { TextEditorChangeKind, TextEditorSelectionChangeKind, URI } from './types-impl'; import { IdGenerator } from '../common/id-generator'; export class TextEditorsExtImpl implements TextEditorsExt { private readonly _onDidChangeTextEditorSelection = new Emitter(); private readonly _onDidChangeTextEditorOptions = new Emitter(); private readonly _onDidChangeTextEditorVisibleRanges = new Emitter(); private readonly _onDidChangeTextEditorViewColumn = new Emitter(); private readonly _onDidChangeTextEditorDiffInformation = new Emitter(); private readonly _onDidChangeActiveTextEditor = new Emitter(); private readonly _onDidChangeVisibleTextEditors = new Emitter(); readonly onDidChangeTextEditorSelection: Event = this._onDidChangeTextEditorSelection.event; readonly onDidChangeTextEditorOptions = this._onDidChangeTextEditorOptions.event; readonly onDidChangeTextEditorVisibleRanges = this._onDidChangeTextEditorVisibleRanges.event; readonly onDidChangeTextEditorViewColumn = this._onDidChangeTextEditorViewColumn.event; readonly onDidChangeTextEditorDiffInformation = this._onDidChangeTextEditorDiffInformation.event; readonly onDidChangeActiveTextEditor = this._onDidChangeActiveTextEditor.event; readonly onDidChangeVisibleTextEditors = this._onDidChangeVisibleTextEditors.event; private proxy: TextEditorsMain; constructor(rpc: RPCProtocol, private editorsAndDocuments: EditorsAndDocumentsExtImpl) { this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN); this.editorsAndDocuments.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e)); this.editorsAndDocuments.onDidChangeVisibleTextEditors(e => this._onDidChangeVisibleTextEditors.fire(e)); } $acceptEditorPropertiesChanged(id: string, props: EditorChangedPropertiesData): void { const textEditor = this.editorsAndDocuments.getEditor(id); if (!textEditor) { return; } if (props.options) { textEditor.acceptOptions(props.options); } if (props.selections) { const selections = props.selections.selections.map(Converters.toSelection); textEditor.acceptSelections(selections); } if (props.visibleRanges) { const visibleRanges = props.visibleRanges.map(Converters.toRange); textEditor.acceptVisibleRanges(visibleRanges); } if (props.options) { this._onDidChangeTextEditorOptions.fire({ textEditor, options: props.options }); } if (props.selections) { const kind = TextEditorSelectionChangeKind.fromValue(props.selections.source); const selections = props.selections.selections.map(Converters.toSelection); this._onDidChangeTextEditorSelection.fire({ textEditor, selections, kind }); } if (props.visibleRanges) { const visibleRanges = props.visibleRanges.map(Converters.toRange); this._onDidChangeTextEditorVisibleRanges.fire({ textEditor, visibleRanges }); } } $acceptEditorPositionData(data: TextEditorPositionData): void { for (const id in data) { if (data.hasOwnProperty(id)) { const textEditor = this.editorsAndDocuments.getEditor(id); const viewColumn = Converters.toViewColumn(data[id]); if (textEditor && viewColumn) { if (textEditor.viewColumn !== viewColumn) { textEditor.acceptViewColumn(viewColumn); this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn }); } } } } } getActiveEditor(): TextEditorExt | undefined { return this.editorsAndDocuments.activeEditor(); } async getDiffInformation(): Promise { const activeEditor = this.getActiveEditor(); if (!activeEditor) { return []; } return activeEditor.getDiffInformation(); } $acceptEditorDiffInformation(id: string, diffInformation: TextEditorDiffInformationDto[] | undefined): void { const textEditor = this.editorsAndDocuments.getEditor(id); if (!textEditor) { throw new Error('unknown text editor'); } if (!diffInformation) { textEditor._acceptDiffInformation(undefined); this._onDidChangeTextEditorDiffInformation.fire({ textEditor: textEditor, diffInformation: undefined }); return; } const that = this; const result = diffInformation.map(diff => { const original = URI.revive(diff.original); const modified = URI.revive(diff.modified); const changes = diff.changes.map(change => { const originalStartLineNumber = change.original.startLineNumber; const originalEndLineNumberExclusive = change.original.endLineNumberExclusive; const modifiedStartLineNumber = change.modified.startLineNumber; const modifiedEndLineNumberExclusive = change.modified.endLineNumberExclusive; let kind: TextEditorChangeKind; if (change.original.startLineNumber === originalEndLineNumberExclusive) { kind = TextEditorChangeKind.Addition; } else if (modifiedStartLineNumber === modifiedEndLineNumberExclusive) { kind = TextEditorChangeKind.Deletion; } else { kind = TextEditorChangeKind.Modification; } return { original: { startLineNumber: originalStartLineNumber, endLineNumberExclusive: originalEndLineNumberExclusive }, modified: { startLineNumber: modifiedStartLineNumber, endLineNumberExclusive: modifiedEndLineNumberExclusive }, kind } satisfies theia.TextEditorChange; }); return Object.freeze({ documentVersion: diff.documentVersion, original, modified, changes, get isStale(): boolean { const document = that.editorsAndDocuments.getDocument(modified.toString()); return document?.document.version !== diff.documentVersion; } }); }); textEditor._acceptDiffInformation(result); this._onDidChangeTextEditorDiffInformation.fire({ textEditor: textEditor, diffInformation: result }); } getVisibleTextEditors(): theia.TextEditor[] { return this.editorsAndDocuments.allEditors(); } createTextEditorDecorationType(options: theia.DecorationRenderOptions): theia.TextEditorDecorationType { return new TextEditorDecorationType(this.proxy, options); } applyWorkspaceEdit(edit: theia.WorkspaceEdit, metadata?: theia.WorkspaceEditMetadata): Promise { const dto = Converters.fromWorkspaceEdit(edit, this.editorsAndDocuments); return this.proxy.$tryApplyWorkspaceEdit(dto, metadata); } save(uri: theia.Uri): PromiseLike { return this.proxy.$save(uri).then(components => URI.revive(components)); } saveAs(uri: theia.Uri): PromiseLike { return this.proxy.$saveAs(uri).then(components => URI.revive(components)); } saveAll(includeUntitled?: boolean): PromiseLike { return this.proxy.$saveAll(includeUntitled); } } export class TextEditorDecorationType implements theia.TextEditorDecorationType { private static readonly Keys = new IdGenerator('TextEditorDecorationType'); private proxy: TextEditorsMain; public key: string; constructor(proxy: TextEditorsMain, options: theia.DecorationRenderOptions) { this.key = TextEditorDecorationType.Keys.nextId(); this.proxy = proxy; // eslint-disable-next-line @typescript-eslint/no-explicit-any this.proxy.$registerTextEditorDecorationType(this.key, Converters.DecorationRenderOptions.from(options)); } dispose(): void { this.proxy.$removeTextEditorDecorationType(this.key); } }