/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import { disposed } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { equals as objectEquals } from 'vs/base/common/objects'; import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; import { IDecorationOptions, IDecorationRenderOptions, ILineChange } from 'vs/editor/common/editorCommon'; import { ISingleEditOperation } from 'vs/editor/common/model'; import { ITextEditorOptions, Position as EditorPosition } from 'vs/platform/editor/common/editor'; import { IApplyEditsOptions, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ExtHostContext, ExtHostEditorsShape, IExtHostContext, ITextDocumentShowOptions, ITextEditorPositionData, MainThreadTextEditorsShape } from '../node/extHost.protocol'; import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors'; import { MainThreadTextEditor } from './mainThreadEditor'; export class MainThreadTextEditors implements MainThreadTextEditorsShape { private _proxy: ExtHostEditorsShape; private _documentsAndEditors: MainThreadDocumentsAndEditors; private _workbenchEditorService: IWorkbenchEditorService; private _toDispose: IDisposable[]; private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; }; private _editorPositionData: ITextEditorPositionData; private _registeredDecorationTypes: { [decorationType: string]: boolean; }; constructor( documentsAndEditors: MainThreadDocumentsAndEditors, extHostContext: IExtHostContext, @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditors); this._documentsAndEditors = documentsAndEditors; this._workbenchEditorService = workbenchEditorService; this._toDispose = []; this._textEditorsListenersMap = Object.create(null); this._editorPositionData = null; this._toDispose.push(documentsAndEditors.onTextEditorAdd(editors => editors.forEach(this._onTextEditorAdd, this))); this._toDispose.push(documentsAndEditors.onTextEditorRemove(editors => editors.forEach(this._onTextEditorRemove, this))); this._toDispose.push(editorGroupService.onEditorsChanged(() => this._updateActiveAndVisibleTextEditors())); this._toDispose.push(editorGroupService.onEditorGroupMoved(() => this._updateActiveAndVisibleTextEditors())); this._registeredDecorationTypes = Object.create(null); } public dispose(): void { Object.keys(this._textEditorsListenersMap).forEach((editorId) => { dispose(this._textEditorsListenersMap[editorId]); }); this._textEditorsListenersMap = Object.create(null); this._toDispose = dispose(this._toDispose); for (let decorationType in this._registeredDecorationTypes) { this._codeEditorService.removeDecorationType(decorationType); } this._registeredDecorationTypes = Object.create(null); } private _onTextEditorAdd(textEditor: MainThreadTextEditor): void { let id = textEditor.getId(); let toDispose: IDisposable[] = []; toDispose.push(textEditor.onPropertiesChanged((data) => { this._proxy.$acceptEditorPropertiesChanged(id, data); })); this._textEditorsListenersMap[id] = toDispose; } private _onTextEditorRemove(id: string): void { dispose(this._textEditorsListenersMap[id]); delete this._textEditorsListenersMap[id]; } private _updateActiveAndVisibleTextEditors(): void { // editor columns let editorPositionData = this._getTextEditorPositionData(); if (!objectEquals(this._editorPositionData, editorPositionData)) { this._editorPositionData = editorPositionData; this._proxy.$acceptEditorPositionData(this._editorPositionData); } } private _getTextEditorPositionData(): ITextEditorPositionData { let result: ITextEditorPositionData = Object.create(null); for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) { const id = this._documentsAndEditors.findTextEditorIdFor(workbenchEditor); if (id) { result[id] = workbenchEditor.position; } } return result; } // --- from extension host process $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): TPromise { const uri = URI.revive(resource); const editorOptions: ITextEditorOptions = { preserveFocus: options.preserveFocus, pinned: options.pinned, selection: options.selection }; const input = { resource: uri, options: editorOptions }; return this._workbenchEditorService.openEditor(input, options.position).then(editor => { if (!editor) { return undefined; } return this._documentsAndEditors.findTextEditorIdFor(editor); }); } $tryShowEditor(id: string, position: EditorPosition): TPromise { let mainThreadEditor = this._documentsAndEditors.getEditor(id); if (mainThreadEditor) { let model = mainThreadEditor.getModel(); return this._workbenchEditorService.openEditor({ resource: model.uri, options: { preserveFocus: false } }, position).then(() => { return; }); } return undefined; } $tryHideEditor(id: string): TPromise { let mainThreadEditor = this._documentsAndEditors.getEditor(id); if (mainThreadEditor) { let editors = this._workbenchEditorService.getVisibleEditors(); for (let editor of editors) { if (mainThreadEditor.matches(editor)) { return this._workbenchEditorService.closeEditor(editor.position, editor.input).then(() => { return; }); } } } return undefined; } $trySetSelections(id: string, selections: ISelection[]): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } this._documentsAndEditors.getEditor(id).setSelections(selections); return TPromise.as(null); } $trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } this._documentsAndEditors.getEditor(id).setDecorations(key, ranges); return TPromise.as(null); } $trySetDecorationsFast(id: string, key: string, ranges: number[]): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } this._documentsAndEditors.getEditor(id).setDecorationsFast(key, ranges); return TPromise.as(null); } $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } this._documentsAndEditors.getEditor(id).revealRange(range, revealType); return undefined; } $trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } this._documentsAndEditors.getEditor(id).setConfiguration(options); return TPromise.as(null); } $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } return TPromise.as(this._documentsAndEditors.getEditor(id).applyEdits(modelVersionId, edits, opts)); } $tryApplyWorkspaceEdit(dto: WorkspaceEditDto): TPromise { const { edits } = reviveWorkspaceEditDto(dto); return this._bulkEditService.apply({ edits }, undefined).then(() => true, err => false); } $tryInsertSnippet(id: string, template: string, ranges: IRange[], opts: IUndoStopOptions): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } return TPromise.as(this._documentsAndEditors.getEditor(id).insertSnippet(template, ranges, opts)); } $registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void { this._registeredDecorationTypes[key] = true; this._codeEditorService.registerDecorationType(key, options); } $removeTextEditorDecorationType(key: string): void { delete this._registeredDecorationTypes[key]; this._codeEditorService.removeDecorationType(key); } $getDiffInformation(id: string): TPromise { const editor = this._documentsAndEditors.getEditor(id); if (!editor) { return TPromise.wrapError(new Error('No such TextEditor')); } const codeEditor = editor.getCodeEditor(); const codeEditorId = codeEditor.getId(); const diffEditors = this._codeEditorService.listDiffEditors(); const [diffEditor] = diffEditors.filter(d => d.getOriginalEditor().getId() === codeEditorId || d.getModifiedEditor().getId() === codeEditorId); if (!diffEditor) { return TPromise.as([]); } return TPromise.as(diffEditor.getLineChanges()); } }