mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-24 18:49:00 +01:00
Split up ext host <-> main thread communication to separate files
This commit is contained in:
@@ -4,656 +4,296 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import EditorCommon = require('vs/editor/common/editorCommon');
|
||||
import Event, {Emitter} from 'vs/base/common/event';
|
||||
import {IEditor} from 'vs/platform/editor/common/editor';
|
||||
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
|
||||
import {IModelService} from 'vs/editor/common/services/modelService';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
|
||||
import {RunOnceScheduler} from 'vs/base/common/async';
|
||||
import {IdGenerator} from 'vs/base/common/idGenerator';
|
||||
import {Range} from 'vs/editor/common/core/range';
|
||||
import {Selection} from 'vs/editor/common/core/selection';
|
||||
import {EndOfLine} from 'vs/workbench/api/node/extHostTypes';
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import {IThreadService} from 'vs/workbench/services/thread/common/threadService';
|
||||
import {EndOfLine} from './extHostTypes';
|
||||
import {ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions} from 'vs/editor/common/editorCommon';
|
||||
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
|
||||
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
|
||||
import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService';
|
||||
import {Position as EditorPosition} from 'vs/platform/editor/common/editor';
|
||||
import {IModelService} from 'vs/editor/common/services/modelService';
|
||||
import {MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, ITextEditorConfigurationUpdate} from 'vs/workbench/api/node/mainThreadEditorsTracker';
|
||||
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
|
||||
import {IEventService} from 'vs/platform/event/common/event';
|
||||
import {equals as arrayEquals} from 'vs/base/common/arrays';
|
||||
import {equals as objectEquals} from 'vs/base/common/objects';
|
||||
import {ExtHostContext} from './extHostProtocol';
|
||||
import {ExtHostEditors, ITextEditorPositionData} from './extHostEditors';
|
||||
|
||||
export interface ITextEditorConfigurationUpdate {
|
||||
tabSize?: number | string;
|
||||
insertSpaces?: boolean | string;
|
||||
cursorStyle?: EditorCommon.TextEditorCursorStyle;
|
||||
}
|
||||
export interface IResolvedTextEditorConfiguration {
|
||||
tabSize: number;
|
||||
insertSpaces: boolean;
|
||||
cursorStyle: EditorCommon.TextEditorCursorStyle;
|
||||
}
|
||||
|
||||
function configurationsEqual(a:IResolvedTextEditorConfiguration, b:IResolvedTextEditorConfiguration) {
|
||||
if (a && !b || !a && b) {
|
||||
return false;
|
||||
}
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
a.tabSize === b.tabSize
|
||||
&& a.insertSpaces === b.insertSpaces
|
||||
);
|
||||
}
|
||||
|
||||
export interface IFocusTracker {
|
||||
onGainedFocus(): void;
|
||||
onLostFocus(): void;
|
||||
}
|
||||
|
||||
export enum TextEditorRevealType {
|
||||
Default,
|
||||
InCenter,
|
||||
InCenterIfOutsideViewport
|
||||
}
|
||||
|
||||
/**
|
||||
* Text Editor that is permanently bound to the same model.
|
||||
* It can be bound or not to a CodeEditor.
|
||||
*/
|
||||
export class MainThreadTextEditor {
|
||||
|
||||
private _id: string;
|
||||
private _model: EditorCommon.IModel;
|
||||
private _modelService: IModelService;
|
||||
private _modelListeners: IDisposable[];
|
||||
private _codeEditor: EditorCommon.ICommonCodeEditor;
|
||||
private _focusTracker: IFocusTracker;
|
||||
private _codeEditorListeners: IDisposable[];
|
||||
|
||||
private _lastSelection: Selection[];
|
||||
private _configuration: IResolvedTextEditorConfiguration;
|
||||
|
||||
private _onSelectionChanged: Emitter<Selection[]>;
|
||||
private _onConfigurationChanged: Emitter<IResolvedTextEditorConfiguration>;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
model:EditorCommon.IModel,
|
||||
codeEditor:EditorCommon.ICommonCodeEditor,
|
||||
focusTracker:IFocusTracker,
|
||||
modelService: IModelService
|
||||
) {
|
||||
this._id = id;
|
||||
this._model = model;
|
||||
this._codeEditor = null;
|
||||
this._focusTracker = focusTracker;
|
||||
this._modelService = modelService;
|
||||
this._codeEditorListeners = [];
|
||||
|
||||
this._onSelectionChanged = new Emitter<Selection[]>();
|
||||
this._onConfigurationChanged = new Emitter<IResolvedTextEditorConfiguration>();
|
||||
|
||||
this._lastSelection = [ new Selection(1,1,1,1) ];
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
this._modelListeners = [];
|
||||
this._modelListeners.push(this._model.onDidChangeOptions((e) => {
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
}));
|
||||
|
||||
this.setCodeEditor(codeEditor);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._model = null;
|
||||
this._modelListeners = dispose(this._modelListeners);
|
||||
this._codeEditor = null;
|
||||
this._codeEditorListeners = dispose(this._codeEditorListeners);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public getModel(): EditorCommon.IModel {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public hasCodeEditor(codeEditor:EditorCommon.ICommonCodeEditor): boolean {
|
||||
return (this._codeEditor === codeEditor);
|
||||
}
|
||||
|
||||
public setCodeEditor(codeEditor:EditorCommon.ICommonCodeEditor): void {
|
||||
if (this.hasCodeEditor(codeEditor)) {
|
||||
// Nothing to do...
|
||||
return;
|
||||
}
|
||||
this._codeEditorListeners = dispose(this._codeEditorListeners);
|
||||
|
||||
this._codeEditor = codeEditor;
|
||||
if (this._codeEditor) {
|
||||
|
||||
// Catch early the case that this code editor gets a different model set and disassociate from this model
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidChangeModel(() => {
|
||||
this.setCodeEditor(null);
|
||||
}));
|
||||
|
||||
let forwardSelection = () => {
|
||||
this._lastSelection = this._codeEditor.getSelections();
|
||||
this._onSelectionChanged.fire(this._lastSelection);
|
||||
};
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidChangeCursorSelection(forwardSelection));
|
||||
if (!Selection.selectionsArrEqual(this._lastSelection, this._codeEditor.getSelections())) {
|
||||
forwardSelection();
|
||||
}
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidFocusEditor(() => {
|
||||
this._focusTracker.onGainedFocus();
|
||||
}));
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidBlurEditor(() => {
|
||||
this._focusTracker.onLostFocus();
|
||||
}));
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidChangeConfiguration(() => {
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
}));
|
||||
this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
|
||||
}
|
||||
}
|
||||
|
||||
public isVisible(): boolean {
|
||||
return !!this._codeEditor;
|
||||
}
|
||||
|
||||
public get onSelectionChanged(): Event<Selection[]> {
|
||||
return this._onSelectionChanged.event;
|
||||
}
|
||||
|
||||
public get onConfigurationChanged(): Event<IResolvedTextEditorConfiguration> {
|
||||
return this._onConfigurationChanged.event;
|
||||
}
|
||||
|
||||
public getSelections(): Selection[] {
|
||||
if (this._codeEditor) {
|
||||
return this._codeEditor.getSelections();
|
||||
}
|
||||
return this._lastSelection;
|
||||
}
|
||||
|
||||
public setSelections(selections:EditorCommon.ISelection[]): void {
|
||||
if (this._codeEditor) {
|
||||
this._codeEditor.setSelections(selections);
|
||||
return;
|
||||
}
|
||||
this._lastSelection = selections.map(Selection.liftSelection);
|
||||
console.warn('setSelections on invisble editor');
|
||||
}
|
||||
|
||||
public getConfiguration(): IResolvedTextEditorConfiguration {
|
||||
return this._configuration;
|
||||
}
|
||||
|
||||
private _setIndentConfiguration(newConfiguration:ITextEditorConfigurationUpdate): void {
|
||||
if (newConfiguration.tabSize === 'auto' || newConfiguration.insertSpaces === 'auto') {
|
||||
// one of the options was set to 'auto' => detect indentation
|
||||
|
||||
let creationOpts = this._modelService.getCreationOptions();
|
||||
let insertSpaces = creationOpts.insertSpaces;
|
||||
let tabSize = creationOpts.tabSize;
|
||||
|
||||
if (newConfiguration.insertSpaces !== 'auto') {
|
||||
if (typeof newConfiguration.insertSpaces !== 'undefined') {
|
||||
insertSpaces = (newConfiguration.insertSpaces === 'false' ? false : Boolean(newConfiguration.insertSpaces));
|
||||
}
|
||||
}
|
||||
if (newConfiguration.tabSize !== 'auto') {
|
||||
if (typeof newConfiguration.tabSize !== 'undefined') {
|
||||
let parsedTabSize = parseInt(<string>newConfiguration.tabSize, 10);
|
||||
if (!isNaN(parsedTabSize)) {
|
||||
tabSize = parsedTabSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._model.detectIndentation(insertSpaces, tabSize);
|
||||
return;
|
||||
}
|
||||
|
||||
let newOpts: EditorCommon.ITextModelUpdateOptions = {};
|
||||
if (typeof newConfiguration.insertSpaces !== 'undefined') {
|
||||
newOpts.insertSpaces = (newConfiguration.insertSpaces === 'false' ? false : Boolean(newConfiguration.insertSpaces));
|
||||
}
|
||||
if (typeof newConfiguration.tabSize !== 'undefined') {
|
||||
let parsedTabSize = parseInt(<string>newConfiguration.tabSize, 10);
|
||||
if (!isNaN(parsedTabSize)) {
|
||||
newOpts.tabSize = parsedTabSize;
|
||||
}
|
||||
}
|
||||
this._model.updateOptions(newOpts);
|
||||
}
|
||||
|
||||
public setConfiguration(newConfiguration:ITextEditorConfigurationUpdate): void {
|
||||
this._setIndentConfiguration(newConfiguration);
|
||||
|
||||
if (newConfiguration.cursorStyle) {
|
||||
let newCursorStyle = EditorCommon.cursorStyleToString(newConfiguration.cursorStyle);
|
||||
|
||||
if (!this._codeEditor) {
|
||||
console.warn('setConfiguration on invisible editor');
|
||||
return;
|
||||
}
|
||||
|
||||
this._codeEditor.updateOptions({
|
||||
cursorStyle: newCursorStyle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setDecorations(key: string, ranges:EditorCommon.IDecorationOptions[]): void {
|
||||
if (!this._codeEditor) {
|
||||
console.warn('setDecorations on invisible editor');
|
||||
return;
|
||||
}
|
||||
this._codeEditor.setDecorations(key, ranges);
|
||||
}
|
||||
|
||||
public revealRange(range:EditorCommon.IRange, revealType:TextEditorRevealType): void {
|
||||
if (!this._codeEditor) {
|
||||
console.warn('revealRange on invisible editor');
|
||||
return;
|
||||
}
|
||||
if (revealType === TextEditorRevealType.Default) {
|
||||
this._codeEditor.revealRange(range);
|
||||
} else if (revealType === TextEditorRevealType.InCenter) {
|
||||
this._codeEditor.revealRangeInCenter(range);
|
||||
} else if (revealType === TextEditorRevealType.InCenterIfOutsideViewport) {
|
||||
this._codeEditor.revealRangeInCenterIfOutsideViewport(range);
|
||||
} else {
|
||||
console.warn('Unknown revealType');
|
||||
}
|
||||
}
|
||||
|
||||
private _readConfiguration(model:EditorCommon.IModel, codeEditor:EditorCommon.ICommonCodeEditor): IResolvedTextEditorConfiguration {
|
||||
if (model.isDisposed()) {
|
||||
// shutdown time
|
||||
return this._configuration;
|
||||
}
|
||||
let cursorStyle = this._configuration ? this._configuration.cursorStyle : EditorCommon.TextEditorCursorStyle.Line;
|
||||
if (codeEditor) {
|
||||
let codeEditorOpts = codeEditor.getConfiguration();
|
||||
cursorStyle = codeEditorOpts.viewInfo.cursorStyle;
|
||||
}
|
||||
|
||||
let indent = model.getOptions();
|
||||
return {
|
||||
insertSpaces: indent.insertSpaces,
|
||||
tabSize: indent.tabSize,
|
||||
cursorStyle: cursorStyle
|
||||
};
|
||||
}
|
||||
|
||||
private _setConfiguration(newConfiguration:IResolvedTextEditorConfiguration): void {
|
||||
if (configurationsEqual(this._configuration, newConfiguration)) {
|
||||
return;
|
||||
}
|
||||
this._configuration = newConfiguration;
|
||||
this._onConfigurationChanged.fire(this._configuration);
|
||||
}
|
||||
|
||||
public isFocused(): boolean {
|
||||
if (this._codeEditor) {
|
||||
return this._codeEditor.isFocused();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public matches(editor: IEditor): boolean {
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
return editor.getControl() === this._codeEditor;
|
||||
}
|
||||
|
||||
public applyEdits(versionIdCheck:number, edits:EditorCommon.ISingleEditOperation[], setEndOfLine:EndOfLine): boolean {
|
||||
if (this._model.getVersionId() !== versionIdCheck) {
|
||||
console.warn('Model has changed in the meantime!');
|
||||
// throw new Error('Model has changed in the meantime!');
|
||||
// model changed in the meantime
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._codeEditor) {
|
||||
if (setEndOfLine === EndOfLine.CRLF) {
|
||||
this._model.setEOL(EditorCommon.EndOfLineSequence.CRLF);
|
||||
} else if (setEndOfLine === EndOfLine.LF) {
|
||||
this._model.setEOL(EditorCommon.EndOfLineSequence.LF);
|
||||
}
|
||||
|
||||
let transformedEdits = edits.map((edit): EditorCommon.IIdentifiedSingleEditOperation => {
|
||||
return {
|
||||
identifier: null,
|
||||
range: Range.lift(edit.range),
|
||||
text: edit.text,
|
||||
forceMoveMarkers: edit.forceMoveMarkers
|
||||
};
|
||||
});
|
||||
return this._codeEditor.executeEdits('MainThreadTextEditor', transformedEdits) || true;
|
||||
}
|
||||
|
||||
console.warn('applyEdits on invisible editor');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of what goes on in the main thread and maps models => text editors
|
||||
*/
|
||||
export class MainThreadEditorsTracker {
|
||||
|
||||
private static _Ids = new IdGenerator('');
|
||||
export class MainThreadEditors {
|
||||
|
||||
private _proxy: ExtHostEditors;
|
||||
private _workbenchEditorService: IWorkbenchEditorService;
|
||||
private _telemetryService: ITelemetryService;
|
||||
private _editorTracker: MainThreadEditorsTracker;
|
||||
private _toDispose: IDisposable[];
|
||||
private _codeEditorService: ICodeEditorService;
|
||||
private _modelService: IModelService;
|
||||
private _updateMapping: RunOnceScheduler;
|
||||
private _editorModelChangeListeners: {[editorId:string]:IDisposable;};
|
||||
|
||||
private _model2TextEditors: {
|
||||
[modelUri:string]: MainThreadTextEditor[];
|
||||
};
|
||||
private _focusedTextEditorId: string;
|
||||
private _visibleTextEditorIds: string[];
|
||||
private _onTextEditorAdd: Emitter<MainThreadTextEditor>;
|
||||
private _onTextEditorRemove: Emitter<MainThreadTextEditor>;
|
||||
private _onDidChangeFocusedTextEditor: Emitter<string>;
|
||||
private _onDidUpdateTextEditors: Emitter<void>;
|
||||
|
||||
private _focusTracker: IFocusTracker;
|
||||
private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; };
|
||||
private _textEditorsMap: { [editorId: string]: MainThreadTextEditor; };
|
||||
private _activeTextEditor: string;
|
||||
private _visibleEditors: string[];
|
||||
private _editorPositionData: ITextEditorPositionData;
|
||||
|
||||
constructor(
|
||||
editorService:ICodeEditorService,
|
||||
modelService:IModelService
|
||||
@IThreadService threadService: IThreadService,
|
||||
@IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ICodeEditorService editorService: ICodeEditorService,
|
||||
@IEventService eventService: IEventService,
|
||||
@IModelService modelService: IModelService
|
||||
) {
|
||||
this._codeEditorService = editorService;
|
||||
this._modelService = modelService;
|
||||
this._proxy = threadService.get(ExtHostContext.ExtHostEditors);
|
||||
this._workbenchEditorService = workbenchEditorService;
|
||||
this._telemetryService = telemetryService;
|
||||
this._toDispose = [];
|
||||
this._focusedTextEditorId = null;
|
||||
this._visibleTextEditorIds = [];
|
||||
this._editorModelChangeListeners = Object.create(null);
|
||||
this._model2TextEditors = Object.create(null);
|
||||
this._onTextEditorAdd = new Emitter<MainThreadTextEditor>();
|
||||
this._onTextEditorRemove = new Emitter<MainThreadTextEditor>();
|
||||
this._onDidUpdateTextEditors = new Emitter<void>();
|
||||
this._onDidChangeFocusedTextEditor = new Emitter<string>();
|
||||
this._focusTracker = {
|
||||
onGainedFocus: () => this._updateFocusedTextEditor(),
|
||||
onLostFocus: () => this._updateFocusedTextEditor()
|
||||
};
|
||||
this._textEditorsListenersMap = Object.create(null);
|
||||
this._textEditorsMap = Object.create(null);
|
||||
this._activeTextEditor = null;
|
||||
this._visibleEditors = [];
|
||||
this._editorPositionData = null;
|
||||
|
||||
this._modelService.onModelAdded(this._onModelAdded, this, this._toDispose);
|
||||
this._modelService.onModelRemoved(this._onModelRemoved, this, this._toDispose);
|
||||
this._editorTracker = new MainThreadEditorsTracker(editorService, modelService);
|
||||
this._toDispose.push(this._editorTracker);
|
||||
|
||||
this._codeEditorService.onCodeEditorAdd(this._onCodeEditorAdd, this, this._toDispose);
|
||||
this._codeEditorService.onCodeEditorRemove(this._onCodeEditorRemove, this, this._toDispose);
|
||||
this._toDispose.push(this._editorTracker.onTextEditorAdd((textEditor) => this._onTextEditorAdd(textEditor)));
|
||||
this._toDispose.push(this._editorTracker.onTextEditorRemove((textEditor) => this._onTextEditorRemove(textEditor)));
|
||||
|
||||
this._updateMapping = new RunOnceScheduler(() => this._doUpdateMapping(), 0);
|
||||
this._toDispose.push(this._updateMapping);
|
||||
this._toDispose.push(this._editorTracker.onDidUpdateTextEditors(() => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(this._editorTracker.onChangedFocusedTextEditor((focusedTextEditorId) => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(editorGroupService.onEditorsChanged(() => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(editorGroupService.onEditorsMoved(() => this._updateActiveAndVisibleTextEditors()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
Object.keys(this._textEditorsListenersMap).forEach((editorId) => {
|
||||
dispose(this._textEditorsListenersMap[editorId]);
|
||||
});
|
||||
this._textEditorsListenersMap = Object.create(null);
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
private _onModelAdded(model: EditorCommon.IModel): void {
|
||||
this._updateMapping.schedule();
|
||||
}
|
||||
|
||||
private _onModelRemoved(model: EditorCommon.IModel): void {
|
||||
this._updateMapping.schedule();
|
||||
}
|
||||
|
||||
private _onCodeEditorAdd(codeEditor: EditorCommon.ICommonCodeEditor): void {
|
||||
this._editorModelChangeListeners[codeEditor.getId()] = codeEditor.onDidChangeModel(_ => this._updateMapping.schedule());
|
||||
this._updateMapping.schedule();
|
||||
}
|
||||
|
||||
private _onCodeEditorRemove(codeEditor: EditorCommon.ICommonCodeEditor): void {
|
||||
this._editorModelChangeListeners[codeEditor.getId()].dispose();
|
||||
delete this._editorModelChangeListeners[codeEditor.getId()];
|
||||
this._updateMapping.schedule();
|
||||
}
|
||||
|
||||
private _doUpdateMapping(): void {
|
||||
let allModels = this._modelService.getModels();
|
||||
// Same filter as in extHostDocuments
|
||||
allModels = allModels.filter((model) => !model.isTooLargeForHavingARichMode());
|
||||
let allModelsMap: { [modelUri:string]: EditorCommon.IModel; } = Object.create(null);
|
||||
allModels.forEach((model) => {
|
||||
allModelsMap[model.uri.toString()] = model;
|
||||
private _onTextEditorAdd(textEditor: MainThreadTextEditor): void {
|
||||
let id = textEditor.getId();
|
||||
let toDispose: IDisposable[] = [];
|
||||
toDispose.push(textEditor.onConfigurationChanged((opts) => {
|
||||
this._proxy._acceptOptionsChanged(id, opts);
|
||||
}));
|
||||
toDispose.push(textEditor.onSelectionChanged((selection) => {
|
||||
this._proxy._acceptSelectionsChanged(id, selection);
|
||||
}));
|
||||
this._proxy._acceptTextEditorAdd({
|
||||
id: id,
|
||||
document: textEditor.getModel().uri,
|
||||
options: textEditor.getConfiguration(),
|
||||
selections: textEditor.getSelections(),
|
||||
editorPosition: this._findEditorPosition(textEditor)
|
||||
});
|
||||
|
||||
// Remove text editors for models that no longer exist
|
||||
Object.keys(this._model2TextEditors).forEach((modelUri) => {
|
||||
if (allModelsMap[modelUri]) {
|
||||
// model still exists, will be updated below
|
||||
return;
|
||||
}
|
||||
|
||||
let textEditorsToRemove = this._model2TextEditors[modelUri];
|
||||
delete this._model2TextEditors[modelUri];
|
||||
|
||||
for (let i = 0; i < textEditorsToRemove.length; i++) {
|
||||
this._onTextEditorRemove.fire(textEditorsToRemove[i]);
|
||||
textEditorsToRemove[i].dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle all visible models
|
||||
let visibleModels = this._getVisibleModels();
|
||||
Object.keys(visibleModels).forEach((modelUri) => {
|
||||
let model = visibleModels[modelUri].model;
|
||||
let codeEditors = visibleModels[modelUri].codeEditors;
|
||||
|
||||
if (!this._model2TextEditors[modelUri]) {
|
||||
this._model2TextEditors[modelUri] = [];
|
||||
}
|
||||
let existingTextEditors = this._model2TextEditors[modelUri];
|
||||
|
||||
// Remove text editors if more exist
|
||||
while (existingTextEditors.length > codeEditors.length) {
|
||||
let removedTextEditor = existingTextEditors.pop();
|
||||
this._onTextEditorRemove.fire(removedTextEditor);
|
||||
removedTextEditor.dispose();
|
||||
}
|
||||
|
||||
// Adjust remaining text editors
|
||||
for (let i = 0; i < existingTextEditors.length; i++) {
|
||||
existingTextEditors[i].setCodeEditor(codeEditors[i]);
|
||||
}
|
||||
|
||||
// Create new editors as needed
|
||||
for (let i = existingTextEditors.length; i < codeEditors.length; i++) {
|
||||
let newTextEditor = new MainThreadTextEditor(MainThreadEditorsTracker._Ids.nextId(), model, codeEditors[i], this._focusTracker, this._modelService);
|
||||
existingTextEditors.push(newTextEditor);
|
||||
this._onTextEditorAdd.fire(newTextEditor);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle all not visible models
|
||||
allModels.forEach((model) => {
|
||||
let modelUri = model.uri.toString();
|
||||
|
||||
if (visibleModels[modelUri]) {
|
||||
// model is visible, already handled above
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._model2TextEditors[modelUri]) {
|
||||
this._model2TextEditors[modelUri] = [];
|
||||
}
|
||||
let existingTextEditors = this._model2TextEditors[modelUri];
|
||||
|
||||
// Remove extra text editors
|
||||
while (existingTextEditors.length > 1) {
|
||||
let removedTextEditor = existingTextEditors.pop();
|
||||
this._onTextEditorRemove.fire(removedTextEditor);
|
||||
removedTextEditor.dispose();
|
||||
}
|
||||
|
||||
// Create new editor if needed or adjust it
|
||||
if (existingTextEditors.length === 0) {
|
||||
let newTextEditor = new MainThreadTextEditor(MainThreadEditorsTracker._Ids.nextId(), model, null, this._focusTracker, this._modelService);
|
||||
existingTextEditors.push(newTextEditor);
|
||||
this._onTextEditorAdd.fire(newTextEditor);
|
||||
} else {
|
||||
existingTextEditors[0].setCodeEditor(null);
|
||||
}
|
||||
});
|
||||
|
||||
this._printState();
|
||||
|
||||
this._visibleTextEditorIds = this._findVisibleTextEditorIds();
|
||||
|
||||
this._updateFocusedTextEditor();
|
||||
|
||||
// this is a sync event
|
||||
this._onDidUpdateTextEditors.fire(undefined);
|
||||
this._textEditorsListenersMap[id] = toDispose;
|
||||
this._textEditorsMap[id] = textEditor;
|
||||
}
|
||||
|
||||
private _updateFocusedTextEditor(): void {
|
||||
this._setFocusedTextEditorId(this._findFocusedTextEditorId());
|
||||
private _onTextEditorRemove(textEditor: MainThreadTextEditor): void {
|
||||
let id = textEditor.getId();
|
||||
dispose(this._textEditorsListenersMap[id]);
|
||||
delete this._textEditorsListenersMap[id];
|
||||
delete this._textEditorsMap[id];
|
||||
this._proxy._acceptTextEditorRemove(id);
|
||||
}
|
||||
|
||||
private _findFocusedTextEditorId(): string {
|
||||
let modelUris = Object.keys(this._model2TextEditors);
|
||||
for (let i = 0, len = modelUris.length; i < len; i++) {
|
||||
let editors = this._model2TextEditors[modelUris[i]];
|
||||
for (let j = 0, lenJ = editors.length; j < lenJ; j++) {
|
||||
if (editors[j].isFocused()) {
|
||||
return editors[j].getId();
|
||||
private _updateActiveAndVisibleTextEditors(): void {
|
||||
|
||||
// active and visible editors
|
||||
let visibleEditors = this._editorTracker.getVisibleTextEditorIds();
|
||||
let activeEditor = this._findActiveTextEditorId();
|
||||
if (activeEditor !== this._activeTextEditor || !arrayEquals(this._visibleEditors, visibleEditors, (a, b) => a === b)) {
|
||||
this._activeTextEditor = activeEditor;
|
||||
this._visibleEditors = visibleEditors;
|
||||
this._proxy._acceptActiveEditorAndVisibleEditors(this._activeTextEditor, this._visibleEditors);
|
||||
}
|
||||
|
||||
// editor columns
|
||||
let editorPositionData = this._getTextEditorPositionData();
|
||||
if (!objectEquals(this._editorPositionData, editorPositionData)) {
|
||||
this._editorPositionData = editorPositionData;
|
||||
this._proxy._acceptEditorPositionData(this._editorPositionData);
|
||||
}
|
||||
}
|
||||
|
||||
private _findActiveTextEditorId(): string {
|
||||
let focusedTextEditorId = this._editorTracker.getFocusedTextEditorId();
|
||||
if (focusedTextEditorId) {
|
||||
return focusedTextEditorId;
|
||||
}
|
||||
|
||||
let activeEditor = this._workbenchEditorService.getActiveEditor();
|
||||
if (!activeEditor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let editor = <IEditor>activeEditor.getControl();
|
||||
// Substitute for (editor instanceof ICodeEditor)
|
||||
if (!editor || typeof editor.getEditorType !== 'function') {
|
||||
// Not a text editor...
|
||||
return null;
|
||||
}
|
||||
|
||||
if (editor.getEditorType() === EditorType.ICodeEditor) {
|
||||
return this._editorTracker.findTextEditorIdFor(<ICommonCodeEditor>editor);
|
||||
}
|
||||
|
||||
// Must be a diff editor => use the modified side
|
||||
return this._editorTracker.findTextEditorIdFor((<ICommonDiffEditor>editor).getModifiedEditor());
|
||||
}
|
||||
|
||||
private _findEditorPosition(editor: MainThreadTextEditor): EditorPosition {
|
||||
for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) {
|
||||
if (editor.matches(workbenchEditor)) {
|
||||
return workbenchEditor.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getTextEditorPositionData(): ITextEditorPositionData {
|
||||
let result: ITextEditorPositionData = Object.create(null);
|
||||
for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) {
|
||||
let editor = <IEditor>workbenchEditor.getControl();
|
||||
// Substitute for (editor instanceof ICodeEditor)
|
||||
if (!editor || typeof editor.getEditorType !== 'function') {
|
||||
// Not a text editor...
|
||||
continue;
|
||||
}
|
||||
if (editor.getEditorType() === EditorType.ICodeEditor) {
|
||||
let id = this._editorTracker.findTextEditorIdFor(<ICommonCodeEditor>editor);
|
||||
if (id) {
|
||||
result[id] = workbenchEditor.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private _findVisibleTextEditorIds(): string[] {
|
||||
let result = [];
|
||||
let modelUris = Object.keys(this._model2TextEditors);
|
||||
for (let i = 0, len = modelUris.length; i < len; i++) {
|
||||
let editors = this._model2TextEditors[modelUris[i]];
|
||||
for (let j = 0, lenJ = editors.length; j < lenJ; j++) {
|
||||
if (editors[j].isVisible()) {
|
||||
result.push(editors[j].getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
result.sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
private _setFocusedTextEditorId(focusedTextEditorId:string): void {
|
||||
if (this._focusedTextEditorId === focusedTextEditorId) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
// --- from extension host process
|
||||
|
||||
this._focusedTextEditorId = focusedTextEditorId;
|
||||
this._printState();
|
||||
this._onDidChangeFocusedTextEditor.fire(this._focusedTextEditorId);
|
||||
}
|
||||
_tryShowTextDocument(resource: URI, position: EditorPosition, preserveFocus: boolean): TPromise<string> {
|
||||
|
||||
const input = {
|
||||
resource,
|
||||
options: { preserveFocus }
|
||||
};
|
||||
|
||||
private _printState(): void {
|
||||
// console.log('----------------------');
|
||||
// Object.keys(this._model2TextEditors).forEach((modelUri) => {
|
||||
// let editors = this._model2TextEditors[modelUri];
|
||||
return this._workbenchEditorService.openEditor(input, position).then(editor => {
|
||||
|
||||
// console.log(editors.map((e) => {
|
||||
// return e.getId() + " (" + (e.getId() === this._focusedTextEditorId ? 'FOCUSED, ': '') + modelUri + ")";
|
||||
// }).join('\n'));
|
||||
// });
|
||||
}
|
||||
|
||||
private _getVisibleModels(): IVisibleModels {
|
||||
let r: IVisibleModels = {};
|
||||
|
||||
let allCodeEditors = this._codeEditorService.listCodeEditors();
|
||||
|
||||
// Maintain a certain sorting such that the mapping doesn't change too much all the time
|
||||
allCodeEditors.sort((a, b) => strcmp(a.getId(), b.getId()));
|
||||
|
||||
allCodeEditors.forEach((codeEditor) => {
|
||||
let model = codeEditor.getModel();
|
||||
if (!model || model.isTooLargeForHavingARichMode()) {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modelUri = model.uri.toString();
|
||||
r[modelUri] = r[modelUri] || {
|
||||
model: model,
|
||||
codeEditors: []
|
||||
};
|
||||
r[modelUri].codeEditors.push(codeEditor);
|
||||
return new TPromise<void>(c => {
|
||||
// not very nice but the way it is: changes to the editor state aren't
|
||||
// send to the ext host as they happen but stuff is delayed a little. in
|
||||
// order to provide the real editor on #openTextEditor we need to sync on
|
||||
// that update
|
||||
let subscription: IDisposable;
|
||||
let handle: number;
|
||||
function contd() {
|
||||
subscription.dispose();
|
||||
clearTimeout(handle);
|
||||
c(undefined);
|
||||
}
|
||||
subscription = this._editorTracker.onDidUpdateTextEditors(() => {
|
||||
contd();
|
||||
});
|
||||
handle = setTimeout(() => {
|
||||
contd();
|
||||
}, 1000);
|
||||
|
||||
}).then(() => {
|
||||
// find the editor we have just opened and return the
|
||||
// id we have assigned to it.
|
||||
for (let id in this._textEditorsMap) {
|
||||
if (this._textEditorsMap[id].matches(editor)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public getFocusedTextEditorId(): string {
|
||||
return this._focusedTextEditorId;
|
||||
_tryShowEditor(id: string, position: EditorPosition): TPromise<void> {
|
||||
// check how often this is used
|
||||
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.show' });
|
||||
|
||||
let mainThreadEditor = this._textEditorsMap[id];
|
||||
if (mainThreadEditor) {
|
||||
let model = mainThreadEditor.getModel();
|
||||
return this._workbenchEditorService.openEditor({
|
||||
resource: model.uri,
|
||||
options: { preserveFocus: false }
|
||||
}, position).then(() => { return; });
|
||||
}
|
||||
}
|
||||
|
||||
public getVisibleTextEditorIds(): string[] {
|
||||
return this._visibleTextEditorIds;
|
||||
}
|
||||
_tryHideEditor(id: string): TPromise<void> {
|
||||
// check how often this is used
|
||||
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.hide' });
|
||||
|
||||
public get onTextEditorAdd(): Event<MainThreadTextEditor> {
|
||||
return this._onTextEditorAdd.event;
|
||||
}
|
||||
|
||||
public get onTextEditorRemove(): Event<MainThreadTextEditor> {
|
||||
return this._onTextEditorRemove.event;
|
||||
}
|
||||
|
||||
public get onDidUpdateTextEditors(): Event<void> {
|
||||
return this._onDidUpdateTextEditors.event;
|
||||
}
|
||||
|
||||
public get onChangedFocusedTextEditor(): Event<string> {
|
||||
return this._onDidChangeFocusedTextEditor.event;
|
||||
}
|
||||
|
||||
public findTextEditorIdFor(codeEditor:EditorCommon.ICommonCodeEditor): string {
|
||||
let modelUris = Object.keys(this._model2TextEditors);
|
||||
for (let i = 0, len = modelUris.length; i < len; i++) {
|
||||
let editors = this._model2TextEditors[modelUris[i]];
|
||||
for (let j = 0, lenJ = editors.length; j < lenJ; j++) {
|
||||
if (editors[j].hasCodeEditor(codeEditor)) {
|
||||
return editors[j].getId();
|
||||
let mainThreadEditor = this._textEditorsMap[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 null;
|
||||
}
|
||||
|
||||
public registerTextEditorDecorationType(key:string, options: EditorCommon.IDecorationRenderOptions): void {
|
||||
this._codeEditorService.registerDecorationType(key, options);
|
||||
_trySetSelections(id: string, selections: ISelection[]): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].setSelections(selections);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public removeTextEditorDecorationType(key:string): void {
|
||||
this._codeEditorService.removeDecorationType(key);
|
||||
_trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].setDecorations(key, ranges);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
_tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].revealRange(range, revealType);
|
||||
}
|
||||
|
||||
_trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].setConfiguration(options);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
_tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], setEndOfLine:EndOfLine): TPromise<boolean> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
return TPromise.as(this._textEditorsMap[id].applyEdits(modelVersionId, edits, setEndOfLine));
|
||||
}
|
||||
|
||||
_registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void {
|
||||
this._editorTracker.registerTextEditorDecorationType(key, options);
|
||||
}
|
||||
|
||||
_removeTextEditorDecorationType(key: string): void {
|
||||
this._editorTracker.removeTextEditorDecorationType(key);
|
||||
}
|
||||
}
|
||||
|
||||
interface IVisibleModels {
|
||||
[modelUri:string]: {
|
||||
model: EditorCommon.IModel;
|
||||
codeEditors: EditorCommon.ICommonCodeEditor[];
|
||||
};
|
||||
}
|
||||
|
||||
function strcmp(a:string, b:string): number {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user