mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 19:18:59 +01:00
Merge branch 'joh/editors'
This commit is contained in:
@@ -13,6 +13,7 @@ import * as errors from 'vs/base/common/errors';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import { ExtHostFileSystemEventService } from 'vs/workbench/api/node/extHostFileSystemEventService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDocumentSaveParticipant';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
@@ -28,7 +29,7 @@ import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostOutputService } from 'vs/workbench/api/node/extHostOutputService';
|
||||
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
|
||||
import { ExtHostMessageService } from 'vs/workbench/api/node/extHostMessageService';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostEditors';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostTextEditors';
|
||||
import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands';
|
||||
@@ -97,9 +98,10 @@ export function createApiFactory(initData: IInitData, threadService: IThreadServ
|
||||
// Addressable instances
|
||||
const col = new InstanceCollection();
|
||||
const extHostHeapService = col.define(ExtHostContext.ExtHostHeapService).set<ExtHostHeapService>(new ExtHostHeapService());
|
||||
const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set<ExtHostDocuments>(new ExtHostDocuments(threadService));
|
||||
const extHostDocumentsAndEditors = col.define(ExtHostContext.ExtHostDocumentsAndEditors).set<ExtHostDocumentsAndEditors>(new ExtHostDocumentsAndEditors(threadService));
|
||||
const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set<ExtHostDocuments>(new ExtHostDocuments(threadService, extHostDocumentsAndEditors));
|
||||
const extHostDocumentSaveParticipant = col.define(ExtHostContext.ExtHostDocumentSaveParticipant).set<ExtHostDocumentSaveParticipant>(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace)));
|
||||
const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set<ExtHostEditors>(new ExtHostEditors(threadService, extHostDocuments));
|
||||
const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set<ExtHostEditors>(new ExtHostEditors(threadService, extHostDocumentsAndEditors));
|
||||
const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set<ExtHostCommands>(new ExtHostCommands(threadService, extHostEditors, extHostHeapService));
|
||||
const extHostExplorers = col.define(ExtHostContext.ExtHostExplorers).set<ExtHostTreeExplorers>(new ExtHostTreeExplorers(threadService, extHostCommands));
|
||||
const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set<ExtHostConfiguration>(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), initData.configuration));
|
||||
|
||||
@@ -36,6 +36,7 @@ import { MainThreadFileSystemEventService } from './mainThreadFileSystemEventSer
|
||||
import { MainThreadSCM } from './mainThreadSCM';
|
||||
|
||||
// --- other interested parties
|
||||
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
|
||||
import { JSONValidationExtensionPoint } from 'vs/platform/jsonschemas/common/jsonValidationExtensionPoint';
|
||||
import { LanguageConfigurationFileHandler } from 'vs/editor/node/languageConfigurationExtensionPoint';
|
||||
import { SaveParticipant } from './mainThreadSaveParticipant';
|
||||
@@ -62,13 +63,15 @@ export class ExtHostContribution implements IWorkbenchContribution {
|
||||
return this.instantiationService.createInstance(ctor);
|
||||
};
|
||||
|
||||
const documentsAndEditors = this.instantiationService.createInstance(MainThreadDocumentsAndEditors);
|
||||
|
||||
// Addressable instances
|
||||
const col = new InstanceCollection();
|
||||
col.define(MainContext.MainThreadCommands).set(create(MainThreadCommands));
|
||||
col.define(MainContext.MainThreadConfiguration).set(create(MainThreadConfiguration));
|
||||
col.define(MainContext.MainThreadDiagnostics).set(create(MainThreadDiagnostics));
|
||||
col.define(MainContext.MainThreadDocuments).set(create(MainThreadDocuments));
|
||||
col.define(MainContext.MainThreadEditors).set(create(MainThreadEditors));
|
||||
col.define(MainContext.MainThreadDocuments).set(this.instantiationService.createInstance(MainThreadDocuments, documentsAndEditors));
|
||||
col.define(MainContext.MainThreadEditors).set(this.instantiationService.createInstance(MainThreadEditors, documentsAndEditors));
|
||||
col.define(MainContext.MainThreadErrors).set(create(MainThreadErrors));
|
||||
col.define(MainContext.MainThreadExplorers).set(create(MainThreadTreeExplorers));
|
||||
col.define(MainContext.MainThreadLanguageFeatures).set(create(MainThreadLanguageFeatures));
|
||||
|
||||
@@ -35,7 +35,7 @@ import { IWorkspaceConfigurationValues } from 'vs/workbench/services/configurati
|
||||
import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkspaceSymbol } from 'vs/workbench/parts/search/common/search';
|
||||
import { IApplyEditsOptions, IUndoStopOptions, TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent } from './mainThreadEditorsTracker';
|
||||
import { IApplyEditsOptions, IUndoStopOptions, TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent } from './mainThreadEditor';
|
||||
|
||||
import { InternalTreeExplorerNodeContent } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel';
|
||||
|
||||
@@ -290,12 +290,10 @@ export interface IModelAddedData {
|
||||
}
|
||||
export abstract class ExtHostDocumentsShape {
|
||||
$provideTextDocumentContent(handle: number, uri: URI): TPromise<string> { throw ni(); }
|
||||
$acceptModelAdd(initData: IModelAddedData): void { throw ni(); }
|
||||
$acceptModelModeChanged(strURL: string, oldModeId: string, newModeId: string): void { throw ni(); }
|
||||
$acceptModelSaved(strURL: string): void { throw ni(); }
|
||||
$acceptModelDirty(strURL: string): void { throw ni(); }
|
||||
$acceptModelReverted(strURL: string): void { throw ni(); }
|
||||
$acceptModelRemoved(strURL: string): void { throw ni(); }
|
||||
$acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[], isDirty: boolean): void { throw ni(); }
|
||||
}
|
||||
|
||||
@@ -314,14 +312,24 @@ export interface ITextEditorPositionData {
|
||||
[id: string]: EditorPosition;
|
||||
}
|
||||
export abstract class ExtHostEditorsShape {
|
||||
$acceptTextEditorAdd(data: ITextEditorAddData): void { throw ni(); }
|
||||
$acceptOptionsChanged(id: string, opts: IResolvedTextEditorConfiguration): void { throw ni(); }
|
||||
$acceptSelectionsChanged(id: string, event: ISelectionChangeEvent): void { throw ni(); }
|
||||
$acceptActiveEditorAndVisibleEditors(id: string, visibleIds: string[]): void { throw ni(); }
|
||||
$acceptEditorPositionData(data: ITextEditorPositionData): void { throw ni(); }
|
||||
$acceptTextEditorRemove(id: string): void { throw ni(); }
|
||||
}
|
||||
|
||||
export interface IDocumentsAndEditorsDelta {
|
||||
removedDocuments?: string[];
|
||||
addedDocuments?: IModelAddedData[];
|
||||
removedEditors?: string[];
|
||||
addedEditors?: ITextEditorAddData[];
|
||||
newActiveEditor?: string;
|
||||
}
|
||||
|
||||
export abstract class ExtHostDocumentsAndEditorsShape {
|
||||
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void { throw ni(); }
|
||||
}
|
||||
|
||||
|
||||
export abstract class ExtHostTreeExplorersShape {
|
||||
$provideRootNode(providerId: string): TPromise<InternalTreeExplorerNodeContent> { throw ni(); };
|
||||
$resolveChildren(providerId: string, node: InternalTreeExplorerNodeContent): TPromise<InternalTreeExplorerNodeContent[]> { throw ni(); }
|
||||
@@ -432,6 +440,7 @@ export const ExtHostContext = {
|
||||
ExtHostCommands: createExtId<ExtHostCommandsShape>('ExtHostCommands', ExtHostCommandsShape),
|
||||
ExtHostConfiguration: createExtId<ExtHostConfigurationShape>('ExtHostConfiguration', ExtHostConfigurationShape),
|
||||
ExtHostDiagnostics: createExtId<ExtHostDiagnosticsShape>('ExtHostDiagnostics', ExtHostDiagnosticsShape),
|
||||
ExtHostDocumentsAndEditors: createExtId<ExtHostDocumentsAndEditorsShape>('ExtHostDocumentsAndEditors', ExtHostDocumentsAndEditorsShape),
|
||||
ExtHostDocuments: createExtId<ExtHostDocumentsShape>('ExtHostDocuments', ExtHostDocumentsShape),
|
||||
ExtHostDocumentSaveParticipant: createExtId<ExtHostDocumentSaveParticipantShape>('ExtHostDocumentSaveParticipant', ExtHostDocumentSaveParticipantShape),
|
||||
ExtHostEditors: createExtId<ExtHostEditorsShape>('ExtHostEditors', ExtHostEditorsShape),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IThreadService } from 'vs/workbench/services/thread/common/threadServic
|
||||
import { validateConstraint } from 'vs/base/common/types';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostEditors';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostTextEditors';
|
||||
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
@@ -102,7 +102,7 @@ export class ExtHostCommands extends ExtHostCommandsShape {
|
||||
return TPromise.wrapError<T>(`Contributed command '${id}' does not exist.`);
|
||||
}
|
||||
|
||||
let {callback, thisArg, description} = command;
|
||||
let { callback, thisArg, description } = command;
|
||||
|
||||
if (description) {
|
||||
for (let i = 0; i < description.args.length; i++) {
|
||||
@@ -140,7 +140,7 @@ export class ExtHostCommands extends ExtHostCommandsShape {
|
||||
$getContributedCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }> {
|
||||
const result: { [id: string]: string | ICommandHandlerDescription } = Object.create(null);
|
||||
this._commands.forEach((command, id) => {
|
||||
let {description} = command;
|
||||
let { description } = command;
|
||||
if (description) {
|
||||
result[id] = description;
|
||||
}
|
||||
|
||||
244
src/vs/workbench/api/node/extHostDocumentData.ts
Normal file
244
src/vs/workbench/api/node/extHostDocumentData.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
|
||||
import { MirrorModel2 } from 'vs/editor/common/model/mirrorModel2';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Range, Position } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as vscode from 'vscode';
|
||||
import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
|
||||
import { MainThreadDocumentsShape } from './extHost.protocol';
|
||||
import { ITextSource } from 'vs/editor/common/model/textSource';
|
||||
|
||||
const _modeId2WordDefinition = new Map<string, RegExp>();
|
||||
export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
|
||||
_modeId2WordDefinition.set(modeId, wordDefinition);
|
||||
}
|
||||
export function getWordDefinitionFor(modeId: string): RegExp {
|
||||
return _modeId2WordDefinition.get(modeId);
|
||||
}
|
||||
|
||||
export class ExtHostDocumentData extends MirrorModel2 {
|
||||
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
private _languageId: string;
|
||||
private _isDirty: boolean;
|
||||
private _textLines: vscode.TextLine[];
|
||||
private _document: vscode.TextDocument;
|
||||
|
||||
constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string,
|
||||
languageId: string, versionId: number, isDirty: boolean) {
|
||||
|
||||
super(uri, lines, eol, versionId);
|
||||
this._proxy = proxy;
|
||||
this._languageId = languageId;
|
||||
this._isDirty = isDirty;
|
||||
this._textLines = [];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._textLines.length = 0;
|
||||
this._isDirty = false;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
equalLines({ lines }: ITextSource): boolean {
|
||||
const len = lines.length;
|
||||
if (len !== this._lines.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (lines[i] !== this._lines[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get document(): vscode.TextDocument {
|
||||
if (!this._document) {
|
||||
const data = this;
|
||||
this._document = {
|
||||
get uri() { return data._uri; },
|
||||
get fileName() { return data._uri.fsPath; },
|
||||
get isUntitled() { return data._uri.scheme !== 'file'; },
|
||||
get languageId() { return data._languageId; },
|
||||
get version() { return data._versionId; },
|
||||
get isDirty() { return data._isDirty; },
|
||||
save() { return data._proxy.$trySaveDocument(data._uri); },
|
||||
getText(range?) { return range ? data._getTextInRange(range) : data.getText(); },
|
||||
get lineCount() { return data._lines.length; },
|
||||
lineAt(lineOrPos) { return data.lineAt(lineOrPos); },
|
||||
offsetAt(pos) { return data.offsetAt(pos); },
|
||||
positionAt(offset) { return data.positionAt(offset); },
|
||||
validateRange(ran) { return data.validateRange(ran); },
|
||||
validatePosition(pos) { return data.validatePosition(pos); },
|
||||
getWordRangeAtPosition(pos, regexp?) { return data.getWordRangeAtPosition(pos, regexp); }
|
||||
};
|
||||
}
|
||||
return this._document;
|
||||
}
|
||||
|
||||
_acceptLanguageId(newLanguageId: string): void {
|
||||
this._languageId = newLanguageId;
|
||||
}
|
||||
|
||||
_acceptIsDirty(isDirty: boolean): void {
|
||||
this._isDirty = isDirty;
|
||||
}
|
||||
|
||||
private _getTextInRange(_range: vscode.Range): string {
|
||||
let range = this.validateRange(_range);
|
||||
|
||||
if (range.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (range.isSingleLine) {
|
||||
return this._lines[range.start.line].substring(range.start.character, range.end.character);
|
||||
}
|
||||
|
||||
let lineEnding = this._eol,
|
||||
startLineIndex = range.start.line,
|
||||
endLineIndex = range.end.line,
|
||||
resultLines: string[] = [];
|
||||
|
||||
resultLines.push(this._lines[startLineIndex].substring(range.start.character));
|
||||
for (let i = startLineIndex + 1; i < endLineIndex; i++) {
|
||||
resultLines.push(this._lines[i]);
|
||||
}
|
||||
resultLines.push(this._lines[endLineIndex].substring(0, range.end.character));
|
||||
|
||||
return resultLines.join(lineEnding);
|
||||
}
|
||||
|
||||
lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
|
||||
|
||||
let line: number;
|
||||
if (lineOrPosition instanceof Position) {
|
||||
line = lineOrPosition.line;
|
||||
} else if (typeof lineOrPosition === 'number') {
|
||||
line = lineOrPosition;
|
||||
}
|
||||
|
||||
if (line < 0 || line >= this._lines.length) {
|
||||
throw new Error('Illegal value for `line`');
|
||||
}
|
||||
|
||||
let result = this._textLines[line];
|
||||
if (!result || result.lineNumber !== line || result.text !== this._lines[line]) {
|
||||
|
||||
const text = this._lines[line];
|
||||
const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)[1].length;
|
||||
const range = new Range(line, 0, line, text.length);
|
||||
const rangeIncludingLineBreak = line < this._lines.length - 1
|
||||
? new Range(line, 0, line + 1, 0)
|
||||
: range;
|
||||
|
||||
result = Object.freeze({
|
||||
lineNumber: line,
|
||||
range,
|
||||
rangeIncludingLineBreak,
|
||||
text,
|
||||
firstNonWhitespaceCharacterIndex, //TODO@api, rename to 'leadingWhitespaceLength'
|
||||
isEmptyOrWhitespace: firstNonWhitespaceCharacterIndex === text.length
|
||||
});
|
||||
|
||||
this._textLines[line] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
offsetAt(position: vscode.Position): number {
|
||||
position = this.validatePosition(position);
|
||||
this._ensureLineStarts();
|
||||
return this._lineStarts.getAccumulatedValue(position.line - 1) + position.character;
|
||||
}
|
||||
|
||||
positionAt(offset: number): vscode.Position {
|
||||
offset = Math.floor(offset);
|
||||
offset = Math.max(0, offset);
|
||||
|
||||
this._ensureLineStarts();
|
||||
let out = this._lineStarts.getIndexOf(offset);
|
||||
|
||||
let lineLength = this._lines[out.index].length;
|
||||
|
||||
// Ensure we return a valid position
|
||||
return new Position(out.index, Math.min(out.remainder, lineLength));
|
||||
}
|
||||
|
||||
// ---- range math
|
||||
|
||||
validateRange(range: vscode.Range): vscode.Range {
|
||||
if (!(range instanceof Range)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
let start = this.validatePosition(range.start);
|
||||
let end = this.validatePosition(range.end);
|
||||
|
||||
if (start === range.start && end === range.end) {
|
||||
return range;
|
||||
}
|
||||
return new Range(start.line, start.character, end.line, end.character);
|
||||
}
|
||||
|
||||
validatePosition(position: vscode.Position): vscode.Position {
|
||||
if (!(position instanceof Position)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
let { line, character } = position;
|
||||
let hasChanged = false;
|
||||
|
||||
if (line < 0) {
|
||||
line = 0;
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (line >= this._lines.length) {
|
||||
line = this._lines.length - 1;
|
||||
character = this._lines[line].length;
|
||||
hasChanged = true;
|
||||
}
|
||||
else {
|
||||
let maxCharacter = this._lines[line].length;
|
||||
if (character < 0) {
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (character > maxCharacter) {
|
||||
character = maxCharacter;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasChanged) {
|
||||
return position;
|
||||
}
|
||||
return new Position(line, character);
|
||||
}
|
||||
|
||||
getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range {
|
||||
let position = this.validatePosition(_position);
|
||||
if (!regexp || regExpLeadsToEndlessLoop(regexp)) {
|
||||
regexp = getWordDefinitionFor(this._languageId);
|
||||
}
|
||||
let wordAtText = getWordAtText(
|
||||
position.character + 1,
|
||||
ensureValidWordDefinition(regexp),
|
||||
this._lines[position.line],
|
||||
0
|
||||
);
|
||||
|
||||
if (wordAtText) {
|
||||
return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -5,82 +5,74 @@
|
||||
'use strict';
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { MirrorModel2 } from 'vs/editor/common/model/mirrorModel2';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Range, Position, Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as vscode from 'vscode';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
|
||||
import { MainContext, MainThreadDocumentsShape, ExtHostDocumentsShape, IModelAddedData } from './extHost.protocol';
|
||||
import { ITextSource, TextSource } from 'vs/editor/common/model/textSource';
|
||||
|
||||
const _modeId2WordDefinition = new Map<string, RegExp>();
|
||||
|
||||
function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
|
||||
_modeId2WordDefinition.set(modeId, wordDefinition);
|
||||
}
|
||||
|
||||
function getWordDefinitionFor(modeId: string): RegExp {
|
||||
return _modeId2WordDefinition.get(modeId);
|
||||
}
|
||||
import { TextSource } from 'vs/editor/common/model/textSource';
|
||||
import { MainContext, MainThreadDocumentsShape, ExtHostDocumentsShape } from './extHost.protocol';
|
||||
import { ExtHostDocumentData, setWordDefinitionFor } from './extHostDocumentData';
|
||||
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
|
||||
|
||||
export class ExtHostDocuments extends ExtHostDocumentsShape {
|
||||
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private _onDidAddDocumentEventEmitter: Emitter<vscode.TextDocument>;
|
||||
public onDidAddDocument: Event<vscode.TextDocument>;
|
||||
private _onDidAddDocument = new Emitter<vscode.TextDocument>();
|
||||
private _onDidRemoveDocument = new Emitter<vscode.TextDocument>();
|
||||
private _onDidChangeDocument = new Emitter<vscode.TextDocumentChangeEvent>();
|
||||
private _onDidSaveDocument = new Emitter<vscode.TextDocument>();
|
||||
|
||||
private _onDidRemoveDocumentEventEmitter: Emitter<vscode.TextDocument>;
|
||||
public onDidRemoveDocument: Event<vscode.TextDocument>;
|
||||
readonly onDidAddDocument: Event<vscode.TextDocument> = this._onDidAddDocument.event;
|
||||
readonly onDidRemoveDocument: Event<vscode.TextDocument> = this._onDidRemoveDocument.event;
|
||||
readonly onDidChangeDocument: Event<vscode.TextDocumentChangeEvent> = this._onDidChangeDocument.event;
|
||||
readonly onDidSaveDocument: Event<vscode.TextDocument> = this._onDidSaveDocument.event;
|
||||
|
||||
private _onDidChangeDocumentEventEmitter: Emitter<vscode.TextDocumentChangeEvent>;
|
||||
public onDidChangeDocument: Event<vscode.TextDocumentChangeEvent>;
|
||||
|
||||
private _onDidSaveDocumentEventEmitter: Emitter<vscode.TextDocument>;
|
||||
public onDidSaveDocument: Event<vscode.TextDocument>;
|
||||
|
||||
private _documentData = new Map<string, ExtHostDocumentData>();
|
||||
private _toDispose: IDisposable[];
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors;
|
||||
private _documentLoader = new Map<string, TPromise<ExtHostDocumentData>>();
|
||||
private _documentContentProviders = new Map<number, vscode.TextDocumentContentProvider>();
|
||||
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
|
||||
constructor(threadService: IThreadService) {
|
||||
constructor(threadService: IThreadService, documentsAndEditors: ExtHostDocumentsAndEditors) {
|
||||
super();
|
||||
this._proxy = threadService.get(MainContext.MainThreadDocuments);
|
||||
this._documentsAndEditors = documentsAndEditors;
|
||||
|
||||
this._onDidAddDocumentEventEmitter = new Emitter<vscode.TextDocument>();
|
||||
this.onDidAddDocument = this._onDidAddDocumentEventEmitter.event;
|
||||
this._toDispose = [
|
||||
this._documentsAndEditors.onDidRemoveDocuments(documents => {
|
||||
for (const data of documents) {
|
||||
this._onDidRemoveDocument.fire(data.document);
|
||||
}
|
||||
}),
|
||||
this._documentsAndEditors.onDidAddDocuments(documents => {
|
||||
for (const data of documents) {
|
||||
this._onDidAddDocument.fire(data.document);
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
this._onDidRemoveDocumentEventEmitter = new Emitter<vscode.TextDocument>();
|
||||
this.onDidRemoveDocument = this._onDidRemoveDocumentEventEmitter.event;
|
||||
|
||||
this._onDidChangeDocumentEventEmitter = new Emitter<vscode.TextDocumentChangeEvent>();
|
||||
this.onDidChangeDocument = this._onDidChangeDocumentEventEmitter.event;
|
||||
|
||||
this._onDidSaveDocumentEventEmitter = new Emitter<vscode.TextDocument>();
|
||||
this.onDidSaveDocument = this._onDidSaveDocumentEventEmitter.event;
|
||||
public dispose(): void {
|
||||
dispose(this._toDispose);
|
||||
}
|
||||
|
||||
public getAllDocumentData(): ExtHostDocumentData[] {
|
||||
const result: ExtHostDocumentData[] = [];
|
||||
this._documentData.forEach(data => result.push(data));
|
||||
return result;
|
||||
return this._documentsAndEditors.allDocuments();
|
||||
}
|
||||
|
||||
public getDocumentData(resource: vscode.Uri): ExtHostDocumentData {
|
||||
if (!resource) {
|
||||
return undefined;
|
||||
}
|
||||
const data = this._documentData.get(resource.toString());
|
||||
const data = this._documentsAndEditors.getDocument(resource.toString());
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
@@ -89,7 +81,7 @@ export class ExtHostDocuments extends ExtHostDocumentsShape {
|
||||
|
||||
public ensureDocumentData(uri: URI): TPromise<ExtHostDocumentData> {
|
||||
|
||||
let cached = this._documentData.get(uri.toString());
|
||||
let cached = this._documentsAndEditors.getDocument(uri.toString());
|
||||
if (cached) {
|
||||
return TPromise.as(cached);
|
||||
}
|
||||
@@ -98,7 +90,7 @@ export class ExtHostDocuments extends ExtHostDocumentsShape {
|
||||
if (!promise) {
|
||||
promise = this._proxy.$tryOpenDocument(uri).then(() => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return this._documentData.get(uri.toString());
|
||||
return this._documentsAndEditors.getDocument(uri.toString());
|
||||
}, err => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return TPromise.wrapError(err);
|
||||
@@ -126,10 +118,10 @@ export class ExtHostDocuments extends ExtHostDocumentsShape {
|
||||
let subscription: IDisposable;
|
||||
if (typeof provider.onDidChange === 'function') {
|
||||
subscription = provider.onDidChange(uri => {
|
||||
if (this._documentData.has(uri.toString())) {
|
||||
if (this._documentsAndEditors.getDocument(uri.toString())) {
|
||||
this.$provideTextDocumentContent(handle, <URI>uri).then(value => {
|
||||
|
||||
const document = this._documentData.get(uri.toString());
|
||||
const document = this._documentsAndEditors.getDocument(uri.toString());
|
||||
if (!document) {
|
||||
// disposed in the meantime
|
||||
return;
|
||||
@@ -158,7 +150,7 @@ export class ExtHostDocuments extends ExtHostDocumentsShape {
|
||||
});
|
||||
}
|
||||
|
||||
$provideTextDocumentContent(handle: number, uri: URI): TPromise<string> {
|
||||
public $provideTextDocumentContent(handle: number, uri: URI): TPromise<string> {
|
||||
const provider = this._documentContentProviders.get(handle);
|
||||
if (!provider) {
|
||||
return TPromise.wrapError<string>(`unsupported uri-scheme: ${uri.scheme}`);
|
||||
@@ -166,57 +158,37 @@ export class ExtHostDocuments extends ExtHostDocumentsShape {
|
||||
return asWinJsPromise(token => provider.provideTextDocumentContent(uri, token));
|
||||
}
|
||||
|
||||
public $acceptModelAdd(initData: IModelAddedData): void {
|
||||
let data = new ExtHostDocumentData(this._proxy, initData.url, initData.lines, initData.EOL, initData.modeId, initData.versionId, initData.isDirty);
|
||||
let key = data.document.uri.toString();
|
||||
if (this._documentData.has(key)) {
|
||||
throw new Error('Document `' + key + '` already exists.');
|
||||
}
|
||||
this._documentData.set(key, data);
|
||||
this._onDidAddDocumentEventEmitter.fire(data.document);
|
||||
}
|
||||
|
||||
public $acceptModelModeChanged(strURL: string, oldModeId: string, newModeId: string): void {
|
||||
let data = this._documentData.get(strURL);
|
||||
let data = this._documentsAndEditors.getDocument(strURL);
|
||||
|
||||
// Treat a mode change as a remove + add
|
||||
|
||||
this._onDidRemoveDocumentEventEmitter.fire(data.document);
|
||||
this._onDidRemoveDocument.fire(data.document);
|
||||
data._acceptLanguageId(newModeId);
|
||||
this._onDidAddDocumentEventEmitter.fire(data.document);
|
||||
this._onDidAddDocument.fire(data.document);
|
||||
}
|
||||
|
||||
public $acceptModelSaved(strURL: string): void {
|
||||
let data = this._documentData.get(strURL);
|
||||
let data = this._documentsAndEditors.getDocument(strURL);
|
||||
data._acceptIsDirty(false);
|
||||
this._onDidSaveDocumentEventEmitter.fire(data.document);
|
||||
this._onDidSaveDocument.fire(data.document);
|
||||
}
|
||||
|
||||
public $acceptModelDirty(strURL: string): void {
|
||||
let document = this._documentData.get(strURL);
|
||||
let document = this._documentsAndEditors.getDocument(strURL);
|
||||
document._acceptIsDirty(true);
|
||||
}
|
||||
|
||||
public $acceptModelReverted(strURL: string): void {
|
||||
let document = this._documentData.get(strURL);
|
||||
let document = this._documentsAndEditors.getDocument(strURL);
|
||||
document._acceptIsDirty(false);
|
||||
}
|
||||
|
||||
public $acceptModelRemoved(strURL: string): void {
|
||||
if (!this._documentData.has(strURL)) {
|
||||
throw new Error('Document `' + strURL + '` does not exist.');
|
||||
}
|
||||
let data = this._documentData.get(strURL);
|
||||
this._documentData.delete(strURL);
|
||||
this._onDidRemoveDocumentEventEmitter.fire(data.document);
|
||||
data.dispose();
|
||||
}
|
||||
|
||||
public $acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[], isDirty: boolean): void {
|
||||
let data = this._documentData.get(strURL);
|
||||
let data = this._documentsAndEditors.getDocument(strURL);
|
||||
data._acceptIsDirty(isDirty);
|
||||
data.onEvents(events);
|
||||
this._onDidChangeDocumentEventEmitter.fire({
|
||||
this._onDidChangeDocument.fire({
|
||||
document: data.document,
|
||||
contentChanges: events.map((e) => {
|
||||
return {
|
||||
@@ -228,229 +200,7 @@ export class ExtHostDocuments extends ExtHostDocumentsShape {
|
||||
});
|
||||
}
|
||||
|
||||
setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
|
||||
public setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
|
||||
setWordDefinitionFor(modeId, wordDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostDocumentData extends MirrorModel2 {
|
||||
|
||||
private _proxy: MainThreadDocumentsShape;
|
||||
private _languageId: string;
|
||||
private _isDirty: boolean;
|
||||
private _textLines: vscode.TextLine[];
|
||||
private _document: vscode.TextDocument;
|
||||
|
||||
constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string,
|
||||
languageId: string, versionId: number, isDirty: boolean) {
|
||||
|
||||
super(uri, lines, eol, versionId);
|
||||
this._proxy = proxy;
|
||||
this._languageId = languageId;
|
||||
this._isDirty = isDirty;
|
||||
this._textLines = [];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._textLines.length = 0;
|
||||
this._isDirty = false;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
equalLines({lines}: ITextSource): boolean {
|
||||
const len = lines.length;
|
||||
if (len !== this._lines.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (lines[i] !== this._lines[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get document(): vscode.TextDocument {
|
||||
if (!this._document) {
|
||||
const data = this;
|
||||
this._document = {
|
||||
get uri() { return data._uri; },
|
||||
get fileName() { return data._uri.fsPath; },
|
||||
get isUntitled() { return data._uri.scheme !== 'file'; },
|
||||
get languageId() { return data._languageId; },
|
||||
get version() { return data._versionId; },
|
||||
get isDirty() { return data._isDirty; },
|
||||
save() { return data._proxy.$trySaveDocument(data._uri); },
|
||||
getText(range?) { return range ? data._getTextInRange(range) : data.getText(); },
|
||||
get lineCount() { return data._lines.length; },
|
||||
lineAt(lineOrPos) { return data.lineAt(lineOrPos); },
|
||||
offsetAt(pos) { return data.offsetAt(pos); },
|
||||
positionAt(offset) { return data.positionAt(offset); },
|
||||
validateRange(ran) { return data.validateRange(ran); },
|
||||
validatePosition(pos) { return data.validatePosition(pos); },
|
||||
getWordRangeAtPosition(pos, regexp?) { return data.getWordRangeAtPosition(pos, regexp); }
|
||||
};
|
||||
}
|
||||
return this._document;
|
||||
}
|
||||
|
||||
_acceptLanguageId(newLanguageId: string): void {
|
||||
this._languageId = newLanguageId;
|
||||
}
|
||||
|
||||
_acceptIsDirty(isDirty: boolean): void {
|
||||
this._isDirty = isDirty;
|
||||
}
|
||||
|
||||
private _getTextInRange(_range: vscode.Range): string {
|
||||
let range = this.validateRange(_range);
|
||||
|
||||
if (range.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (range.isSingleLine) {
|
||||
return this._lines[range.start.line].substring(range.start.character, range.end.character);
|
||||
}
|
||||
|
||||
let lineEnding = this._eol,
|
||||
startLineIndex = range.start.line,
|
||||
endLineIndex = range.end.line,
|
||||
resultLines: string[] = [];
|
||||
|
||||
resultLines.push(this._lines[startLineIndex].substring(range.start.character));
|
||||
for (let i = startLineIndex + 1; i < endLineIndex; i++) {
|
||||
resultLines.push(this._lines[i]);
|
||||
}
|
||||
resultLines.push(this._lines[endLineIndex].substring(0, range.end.character));
|
||||
|
||||
return resultLines.join(lineEnding);
|
||||
}
|
||||
|
||||
lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
|
||||
|
||||
let line: number;
|
||||
if (lineOrPosition instanceof Position) {
|
||||
line = lineOrPosition.line;
|
||||
} else if (typeof lineOrPosition === 'number') {
|
||||
line = lineOrPosition;
|
||||
}
|
||||
|
||||
if (line < 0 || line >= this._lines.length) {
|
||||
throw new Error('Illegal value for `line`');
|
||||
}
|
||||
|
||||
let result = this._textLines[line];
|
||||
if (!result || result.lineNumber !== line || result.text !== this._lines[line]) {
|
||||
|
||||
const text = this._lines[line];
|
||||
const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)[1].length;
|
||||
const range = new Range(line, 0, line, text.length);
|
||||
const rangeIncludingLineBreak = line < this._lines.length - 1
|
||||
? new Range(line, 0, line + 1, 0)
|
||||
: range;
|
||||
|
||||
result = Object.freeze({
|
||||
lineNumber: line,
|
||||
range,
|
||||
rangeIncludingLineBreak,
|
||||
text,
|
||||
firstNonWhitespaceCharacterIndex, //TODO@api, rename to 'leadingWhitespaceLength'
|
||||
isEmptyOrWhitespace: firstNonWhitespaceCharacterIndex === text.length
|
||||
});
|
||||
|
||||
this._textLines[line] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
offsetAt(position: vscode.Position): number {
|
||||
position = this.validatePosition(position);
|
||||
this._ensureLineStarts();
|
||||
return this._lineStarts.getAccumulatedValue(position.line - 1) + position.character;
|
||||
}
|
||||
|
||||
positionAt(offset: number): vscode.Position {
|
||||
offset = Math.floor(offset);
|
||||
offset = Math.max(0, offset);
|
||||
|
||||
this._ensureLineStarts();
|
||||
let out = this._lineStarts.getIndexOf(offset);
|
||||
|
||||
let lineLength = this._lines[out.index].length;
|
||||
|
||||
// Ensure we return a valid position
|
||||
return new Position(out.index, Math.min(out.remainder, lineLength));
|
||||
}
|
||||
|
||||
// ---- range math
|
||||
|
||||
validateRange(range: vscode.Range): vscode.Range {
|
||||
if (!(range instanceof Range)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
let start = this.validatePosition(range.start);
|
||||
let end = this.validatePosition(range.end);
|
||||
|
||||
if (start === range.start && end === range.end) {
|
||||
return range;
|
||||
}
|
||||
return new Range(start.line, start.character, end.line, end.character);
|
||||
}
|
||||
|
||||
validatePosition(position: vscode.Position): vscode.Position {
|
||||
if (!(position instanceof Position)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
let {line, character} = position;
|
||||
let hasChanged = false;
|
||||
|
||||
if (line < 0) {
|
||||
line = 0;
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (line >= this._lines.length) {
|
||||
line = this._lines.length - 1;
|
||||
character = this._lines[line].length;
|
||||
hasChanged = true;
|
||||
}
|
||||
else {
|
||||
let maxCharacter = this._lines[line].length;
|
||||
if (character < 0) {
|
||||
character = 0;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (character > maxCharacter) {
|
||||
character = maxCharacter;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasChanged) {
|
||||
return position;
|
||||
}
|
||||
return new Position(line, character);
|
||||
}
|
||||
|
||||
getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range {
|
||||
let position = this.validatePosition(_position);
|
||||
if (!regexp || regExpLeadsToEndlessLoop(regexp)) {
|
||||
regexp = getWordDefinitionFor(this._languageId);
|
||||
}
|
||||
let wordAtText = getWordAtText(
|
||||
position.character + 1,
|
||||
ensureValidWordDefinition(regexp),
|
||||
this._lines[position.line],
|
||||
0
|
||||
);
|
||||
|
||||
if (wordAtText) {
|
||||
return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
148
src/vs/workbench/api/node/extHostDocumentsAndEditors.ts
Normal file
148
src/vs/workbench/api/node/extHostDocumentsAndEditors.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 Event, { Emitter } from 'vs/base/common/event';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { MainContext, ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta } from './extHost.protocol';
|
||||
import { ExtHostDocumentData } from './extHostDocumentData';
|
||||
import { ExtHostTextEditor } from './extHostTextEditor';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import * as assert from 'assert';
|
||||
import * as typeConverters from './extHostTypeConverters';
|
||||
|
||||
export class ExtHostDocumentsAndEditors extends ExtHostDocumentsAndEditorsShape {
|
||||
|
||||
private _activeEditorId: string;
|
||||
private readonly _editors = new Map<string, ExtHostTextEditor>();
|
||||
private readonly _documents = new Map<string, ExtHostDocumentData>();
|
||||
|
||||
private readonly _onDidAddDocuments = new Emitter<ExtHostDocumentData[]>();
|
||||
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
|
||||
private readonly _onDidChangeVisibleTextEditors = new Emitter<ExtHostTextEditor[]>();
|
||||
private readonly _onDidChangeActiveTextEditor = new Emitter<ExtHostTextEditor>();
|
||||
|
||||
readonly onDidAddDocuments: Event<ExtHostDocumentData[]> = this._onDidAddDocuments.event;
|
||||
readonly onDidRemoveDocuments: Event<ExtHostDocumentData[]> = this._onDidRemoveDocuments.event;
|
||||
readonly onDidChangeVisibleTextEditors: Event<ExtHostTextEditor[]> = this._onDidChangeVisibleTextEditors.event;
|
||||
readonly onDidChangeActiveTextEditor: Event<ExtHostTextEditor> = this._onDidChangeActiveTextEditor.event;
|
||||
|
||||
constructor(
|
||||
@IThreadService private _threadService: IThreadService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
|
||||
|
||||
const removedDocuments: ExtHostDocumentData[] = [];
|
||||
const addedDocuments: ExtHostDocumentData[] = [];
|
||||
const removedEditors: ExtHostTextEditor[] = [];
|
||||
|
||||
if (delta.removedDocuments) {
|
||||
for (const id of delta.removedDocuments) {
|
||||
const data = this._documents.get(id);
|
||||
this._documents.delete(id);
|
||||
removedDocuments.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.addedDocuments) {
|
||||
for (const data of delta.addedDocuments) {
|
||||
assert.ok(!this._documents.has(data.url.toString()), `document '${data.url} already exists!'`);
|
||||
|
||||
const documentData = new ExtHostDocumentData(
|
||||
this._threadService.get(MainContext.MainThreadDocuments),
|
||||
data.url,
|
||||
data.lines,
|
||||
data.EOL,
|
||||
data.modeId,
|
||||
data.versionId,
|
||||
data.isDirty
|
||||
);
|
||||
this._documents.set(data.url.toString(), documentData);
|
||||
addedDocuments.push(documentData);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.removedEditors) {
|
||||
for (const id of delta.removedEditors) {
|
||||
const editor = this._editors.get(id);
|
||||
this._editors.delete(id);
|
||||
removedEditors.push(editor);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.addedEditors) {
|
||||
for (const data of delta.addedEditors) {
|
||||
assert.ok(this._documents.has(data.document.toString()), `document '${data.document}' does not exist`);
|
||||
assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
|
||||
|
||||
const documentData = this._documents.get(data.document.toString());
|
||||
const editor = new ExtHostTextEditor(
|
||||
this._threadService.get(MainContext.MainThreadEditors),
|
||||
data.id,
|
||||
documentData,
|
||||
data.selections.map(typeConverters.toSelection),
|
||||
data.options,
|
||||
typeConverters.toViewColumn(data.editorPosition)
|
||||
);
|
||||
this._editors.set(data.id, editor);
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
assert.ok(delta.newActiveEditor === null || this._editors.has(delta.newActiveEditor), `active editor '${delta.newActiveEditor}' does not exist`);
|
||||
this._activeEditorId = delta.newActiveEditor;
|
||||
}
|
||||
|
||||
// now that the internal state is complete, fire events
|
||||
if (delta.removedDocuments) {
|
||||
this._onDidRemoveDocuments.fire(removedDocuments);
|
||||
}
|
||||
if (delta.addedDocuments) {
|
||||
this._onDidAddDocuments.fire(addedDocuments);
|
||||
}
|
||||
|
||||
if (delta.removedEditors || delta.addedEditors) {
|
||||
this._onDidChangeVisibleTextEditors.fire(this.allEditors());
|
||||
}
|
||||
if (delta.newActiveEditor) {
|
||||
this._onDidChangeActiveTextEditor.fire(this.activeEditor());
|
||||
}
|
||||
|
||||
// now that the events are out, dispose removed documents and editors
|
||||
dispose(removedDocuments);
|
||||
dispose(removedEditors);
|
||||
}
|
||||
|
||||
getDocument(strUrl: string): ExtHostDocumentData {
|
||||
return this._documents.get(strUrl);
|
||||
}
|
||||
|
||||
allDocuments(): ExtHostDocumentData[] {
|
||||
const result: ExtHostDocumentData[] = [];
|
||||
this._documents.forEach(data => result.push(data));
|
||||
return result;
|
||||
}
|
||||
|
||||
getEditor(id: string): ExtHostTextEditor {
|
||||
return this._editors.get(id);
|
||||
}
|
||||
|
||||
activeEditor(): ExtHostTextEditor | undefined {
|
||||
if (!this._activeEditorId) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._editors.get(this._activeEditorId);
|
||||
}
|
||||
}
|
||||
|
||||
allEditors(): ExtHostTextEditor[] {
|
||||
const result: ExtHostTextEditor[] = [];
|
||||
this._editors.forEach(data => result.push(data));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -4,174 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
import { readonly, illegalArgument } from 'vs/base/common/errors';
|
||||
import { equals as arrayEquals } from 'vs/base/common/arrays';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ExtHostDocuments, ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { Selection, Range, Position, EndOfLine, TextEditorRevealType, TextEditorSelectionChangeKind, TextEditorLineNumbersStyle, SnippetString } from './extHostTypes';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocumentData';
|
||||
import { Selection, Range, Position, EndOfLine, TextEditorRevealType, TextEditorLineNumbersStyle, SnippetString } from './extHostTypes';
|
||||
import { ISingleEditOperation, TextEditorCursorStyle, IRange } from 'vs/editor/common/editorCommon';
|
||||
import { IResolvedTextEditorConfiguration, ISelectionChangeEvent, ITextEditorConfigurationUpdate } from 'vs/workbench/api/node/mainThreadEditorsTracker';
|
||||
import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate } from 'vs/workbench/api/node/mainThreadEditor';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import { MainContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextEditorAddData, ITextEditorPositionData } from './extHost.protocol';
|
||||
import { MainThreadEditorsShape } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostEditors extends ExtHostEditorsShape {
|
||||
|
||||
public onDidChangeTextEditorSelection: Event<vscode.TextEditorSelectionChangeEvent>;
|
||||
private _onDidChangeTextEditorSelection: Emitter<vscode.TextEditorSelectionChangeEvent>;
|
||||
|
||||
public onDidChangeTextEditorOptions: Event<vscode.TextEditorOptionsChangeEvent>;
|
||||
private _onDidChangeTextEditorOptions: Emitter<vscode.TextEditorOptionsChangeEvent>;
|
||||
|
||||
public onDidChangeTextEditorViewColumn: Event<vscode.TextEditorViewColumnChangeEvent>;
|
||||
private _onDidChangeTextEditorViewColumn: Emitter<vscode.TextEditorViewColumnChangeEvent>;
|
||||
|
||||
private _editors: Map<string, ExtHostTextEditor>;
|
||||
private _proxy: MainThreadEditorsShape;
|
||||
private _onDidChangeActiveTextEditor: Emitter<vscode.TextEditor>;
|
||||
private _onDidChangeVisibleTextEditors: Emitter<vscode.TextEditor[]>;
|
||||
private _extHostDocuments: ExtHostDocuments;
|
||||
private _activeEditorId: string;
|
||||
private _visibleEditorIds: string[];
|
||||
|
||||
constructor(
|
||||
threadService: IThreadService,
|
||||
extHostDocuments: ExtHostDocuments
|
||||
) {
|
||||
super();
|
||||
this._onDidChangeTextEditorSelection = new Emitter<vscode.TextEditorSelectionChangeEvent>();
|
||||
this.onDidChangeTextEditorSelection = this._onDidChangeTextEditorSelection.event;
|
||||
|
||||
this._onDidChangeTextEditorOptions = new Emitter<vscode.TextEditorOptionsChangeEvent>();
|
||||
this.onDidChangeTextEditorOptions = this._onDidChangeTextEditorOptions.event;
|
||||
|
||||
this._onDidChangeTextEditorViewColumn = new Emitter<vscode.TextEditorViewColumnChangeEvent>();
|
||||
this.onDidChangeTextEditorViewColumn = this._onDidChangeTextEditorViewColumn.event;
|
||||
|
||||
this._extHostDocuments = extHostDocuments;
|
||||
this._proxy = threadService.get(MainContext.MainThreadEditors);
|
||||
this._onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor>();
|
||||
this._onDidChangeVisibleTextEditors = new Emitter<vscode.TextEditor[]>();
|
||||
this._editors = new Map<string, ExtHostTextEditor>();
|
||||
|
||||
this._visibleEditorIds = [];
|
||||
}
|
||||
|
||||
getActiveTextEditor(): vscode.TextEditor {
|
||||
return this._editors.get(this._activeEditorId);
|
||||
}
|
||||
|
||||
getVisibleTextEditors(): vscode.TextEditor[] {
|
||||
return this._visibleEditorIds.map(id => this._editors.get(id));
|
||||
}
|
||||
|
||||
get onDidChangeActiveTextEditor(): Event<vscode.TextEditor> {
|
||||
return this._onDidChangeActiveTextEditor && this._onDidChangeActiveTextEditor.event;
|
||||
}
|
||||
|
||||
get onDidChangeVisibleTextEditors(): Event<vscode.TextEditor[]> {
|
||||
return this._onDidChangeVisibleTextEditors && this._onDidChangeVisibleTextEditors.event;
|
||||
}
|
||||
|
||||
showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): TPromise<vscode.TextEditor> {
|
||||
return this._proxy.$tryShowTextDocument(<URI>document.uri, TypeConverters.fromViewColumn(column), preserveFocus).then(id => {
|
||||
let editor = this._editors.get(id);
|
||||
if (editor) {
|
||||
return editor;
|
||||
} else {
|
||||
throw new Error(`Failed to show text document ${document.uri.toString()}, should show in editor #${id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {
|
||||
return new TextEditorDecorationType(this._proxy, options);
|
||||
}
|
||||
|
||||
// --- called from main thread
|
||||
|
||||
$acceptTextEditorAdd(data: ITextEditorAddData): void {
|
||||
let document = this._extHostDocuments.getDocumentData(data.document);
|
||||
let newEditor = new ExtHostTextEditor(this._proxy, data.id, document, data.selections.map(TypeConverters.toSelection), data.options, TypeConverters.toViewColumn(data.editorPosition));
|
||||
this._editors.set(data.id, newEditor);
|
||||
}
|
||||
|
||||
$acceptOptionsChanged(id: string, opts: IResolvedTextEditorConfiguration): void {
|
||||
let editor = this._editors.get(id);
|
||||
editor._acceptOptions(opts);
|
||||
this._onDidChangeTextEditorOptions.fire({
|
||||
textEditor: editor,
|
||||
options: opts
|
||||
});
|
||||
}
|
||||
|
||||
$acceptSelectionsChanged(id: string, event: ISelectionChangeEvent): void {
|
||||
const kind = TextEditorSelectionChangeKind.fromValue(event.source);
|
||||
const selections = event.selections.map(TypeConverters.toSelection);
|
||||
const textEditor = this._editors.get(id);
|
||||
textEditor._acceptSelections(selections);
|
||||
this._onDidChangeTextEditorSelection.fire({
|
||||
textEditor,
|
||||
selections,
|
||||
kind
|
||||
});
|
||||
}
|
||||
|
||||
$acceptActiveEditorAndVisibleEditors(id: string, visibleIds: string[]): void {
|
||||
let visibleChanged = false;
|
||||
let activeChanged = false;
|
||||
|
||||
if (!arrayEquals(this._visibleEditorIds, visibleIds)) {
|
||||
this._visibleEditorIds = visibleIds;
|
||||
visibleChanged = true;
|
||||
}
|
||||
|
||||
if (this._activeEditorId !== id) {
|
||||
this._activeEditorId = id;
|
||||
activeChanged = true;
|
||||
}
|
||||
|
||||
if (visibleChanged) {
|
||||
this._onDidChangeVisibleTextEditors.fire(this.getVisibleTextEditors());
|
||||
}
|
||||
if (activeChanged) {
|
||||
this._onDidChangeActiveTextEditor.fire(this.getActiveTextEditor());
|
||||
}
|
||||
}
|
||||
|
||||
$acceptEditorPositionData(data: ITextEditorPositionData): void {
|
||||
for (let id in data) {
|
||||
let textEditor = this._editors.get(id);
|
||||
let viewColumn = TypeConverters.toViewColumn(data[id]);
|
||||
if (textEditor.viewColumn !== viewColumn) {
|
||||
textEditor._acceptViewColumn(viewColumn);
|
||||
this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$acceptTextEditorRemove(id: string): void {
|
||||
// make sure the removed editor is not visible
|
||||
let newVisibleEditors = this._visibleEditorIds.filter(visibleEditorId => visibleEditorId !== id);
|
||||
|
||||
if (this._activeEditorId === id) {
|
||||
// removing the current active editor
|
||||
this.$acceptActiveEditorAndVisibleEditors(undefined, newVisibleEditors);
|
||||
} else {
|
||||
this.$acceptActiveEditorAndVisibleEditors(this._activeEditorId, newVisibleEditors);
|
||||
}
|
||||
|
||||
let editor = this._editors.get(id);
|
||||
editor.dispose();
|
||||
this._editors.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
class TextEditorDecorationType implements vscode.TextEditorDecorationType {
|
||||
export class TextEditorDecorationType implements vscode.TextEditorDecorationType {
|
||||
|
||||
private static _Keys = new IdGenerator('TextEditorDecorationType');
|
||||
|
||||
@@ -467,7 +312,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
|
||||
|
||||
}
|
||||
|
||||
class ExtHostTextEditor implements vscode.TextEditor {
|
||||
export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
|
||||
private _proxy: MainThreadEditorsShape;
|
||||
private _id: string;
|
||||
@@ -630,7 +475,7 @@ class ExtHostTextEditor implements vscode.TextEditor {
|
||||
ranges = this._selections.map(TypeConverters.fromRange);
|
||||
|
||||
} else if (where instanceof Position) {
|
||||
const {lineNumber, column} = TypeConverters.fromPosition(where);
|
||||
const { lineNumber, column } = TypeConverters.fromPosition(where);
|
||||
ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }];
|
||||
|
||||
} else if (where instanceof Range) {
|
||||
@@ -641,7 +486,7 @@ class ExtHostTextEditor implements vscode.TextEditor {
|
||||
if (posOrRange instanceof Range) {
|
||||
ranges.push(TypeConverters.fromRange(posOrRange));
|
||||
} else {
|
||||
const {lineNumber, column} = TypeConverters.fromPosition(posOrRange);
|
||||
const { lineNumber, column } = TypeConverters.fromPosition(posOrRange);
|
||||
ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column });
|
||||
}
|
||||
}
|
||||
105
src/vs/workbench/api/node/extHostTextEditors.ts
Normal file
105
src/vs/workbench/api/node/extHostTextEditors.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { TextEditorSelectionChangeKind } from './extHostTypes';
|
||||
import { IResolvedTextEditorConfiguration, ISelectionChangeEvent } from 'vs/workbench/api/node/mainThreadEditor';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import { TextEditorDecorationType } from './extHostTextEditor';
|
||||
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
|
||||
import { MainContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextEditorPositionData } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostEditors extends ExtHostEditorsShape {
|
||||
|
||||
private readonly _onDidChangeTextEditorSelection = new Emitter<vscode.TextEditorSelectionChangeEvent>();
|
||||
private readonly _onDidChangeTextEditorOptions = new Emitter<vscode.TextEditorOptionsChangeEvent>();
|
||||
private readonly _onDidChangeTextEditorViewColumn = new Emitter<vscode.TextEditorViewColumnChangeEvent>();
|
||||
private readonly _onDidChangeActiveTextEditor = new Emitter<vscode.TextEditor>();
|
||||
private readonly _onDidChangeVisibleTextEditors = new Emitter<vscode.TextEditor[]>();
|
||||
|
||||
readonly onDidChangeTextEditorSelection: Event<vscode.TextEditorSelectionChangeEvent> = this._onDidChangeTextEditorSelection.event;
|
||||
readonly onDidChangeTextEditorOptions: Event<vscode.TextEditorOptionsChangeEvent> = this._onDidChangeTextEditorOptions.event;
|
||||
readonly onDidChangeTextEditorViewColumn: Event<vscode.TextEditorViewColumnChangeEvent> = this._onDidChangeTextEditorViewColumn.event;
|
||||
readonly onDidChangeActiveTextEditor: Event<vscode.TextEditor> = this._onDidChangeActiveTextEditor.event;
|
||||
readonly onDidChangeVisibleTextEditors: Event<vscode.TextEditor[]> = this._onDidChangeVisibleTextEditors.event;
|
||||
|
||||
|
||||
private _proxy: MainThreadEditorsShape;
|
||||
private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
|
||||
|
||||
constructor(
|
||||
threadService: IThreadService,
|
||||
extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
) {
|
||||
super();
|
||||
this._proxy = threadService.get(MainContext.MainThreadEditors);
|
||||
this._extHostDocumentsAndEditors = extHostDocumentsAndEditors;
|
||||
|
||||
this._extHostDocumentsAndEditors.onDidChangeVisibleTextEditors(e => this._onDidChangeVisibleTextEditors.fire(e));
|
||||
this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e));
|
||||
}
|
||||
|
||||
getActiveTextEditor(): vscode.TextEditor {
|
||||
return this._extHostDocumentsAndEditors.activeEditor();
|
||||
}
|
||||
|
||||
getVisibleTextEditors(): vscode.TextEditor[] {
|
||||
return this._extHostDocumentsAndEditors.allEditors();
|
||||
}
|
||||
|
||||
showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): TPromise<vscode.TextEditor> {
|
||||
return this._proxy.$tryShowTextDocument(<URI>document.uri, TypeConverters.fromViewColumn(column), preserveFocus).then(id => {
|
||||
let editor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
if (editor) {
|
||||
return editor;
|
||||
} else {
|
||||
throw new Error(`Failed to show text document ${document.uri.toString()}, should show in editor #${id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType {
|
||||
return new TextEditorDecorationType(this._proxy, options);
|
||||
}
|
||||
|
||||
// --- called from main thread
|
||||
|
||||
$acceptOptionsChanged(id: string, opts: IResolvedTextEditorConfiguration): void {
|
||||
let editor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
editor._acceptOptions(opts);
|
||||
this._onDidChangeTextEditorOptions.fire({
|
||||
textEditor: editor,
|
||||
options: opts
|
||||
});
|
||||
}
|
||||
|
||||
$acceptSelectionsChanged(id: string, event: ISelectionChangeEvent): void {
|
||||
const kind = TextEditorSelectionChangeKind.fromValue(event.source);
|
||||
const selections = event.selections.map(TypeConverters.toSelection);
|
||||
const textEditor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
textEditor._acceptSelections(selections);
|
||||
this._onDidChangeTextEditorSelection.fire({
|
||||
textEditor,
|
||||
selections,
|
||||
kind
|
||||
});
|
||||
}
|
||||
|
||||
$acceptEditorPositionData(data: ITextEditorPositionData): void {
|
||||
for (let id in data) {
|
||||
let textEditor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
let viewColumn = TypeConverters.toViewColumn(data[id]);
|
||||
if (textEditor.viewColumn !== viewColumn) {
|
||||
textEditor._acceptViewColumn(viewColumn);
|
||||
this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,10 @@ import { ExtHostContext, MainThreadDocumentsShape, ExtHostDocumentsShape } from
|
||||
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import { ITextSource } from 'vs/editor/common/model/textSource';
|
||||
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
|
||||
|
||||
export class MainThreadDocuments extends MainThreadDocumentsShape {
|
||||
|
||||
private _modelService: IModelService;
|
||||
private _modeService: IModeService;
|
||||
private _textModelResolverService: ITextModelResolverService;
|
||||
@@ -36,6 +38,7 @@ export class MainThreadDocuments extends MainThreadDocumentsShape {
|
||||
private _resourceContentProvider: { [handle: number]: IDisposable };
|
||||
|
||||
constructor(
|
||||
documentsAndEditors: MainThreadDocumentsAndEditors,
|
||||
@IThreadService threadService: IThreadService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IModeService modeService: IModeService,
|
||||
@@ -57,8 +60,8 @@ export class MainThreadDocuments extends MainThreadDocumentsShape {
|
||||
this._modelIsSynced = {};
|
||||
|
||||
this._toDispose = [];
|
||||
modelService.onModelAdded(this._onModelAdded, this, this._toDispose);
|
||||
modelService.onModelRemoved(this._onModelRemoved, this, this._toDispose);
|
||||
this._toDispose.push(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this)));
|
||||
this._toDispose.push(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this)));
|
||||
modelService.onModelModeChanged(this._onModelModeChanged, this, this._toDispose);
|
||||
|
||||
this._toDispose.push(textFileService.models.onModelSaved(e => {
|
||||
@@ -103,14 +106,6 @@ export class MainThreadDocuments extends MainThreadDocumentsShape {
|
||||
let modelUrl = model.uri;
|
||||
this._modelIsSynced[modelUrl.toString()] = true;
|
||||
this._modelToDisposeMap[modelUrl.toString()] = model.addBulkListener((events) => this._onModelEvents(modelUrl, events));
|
||||
this._proxy.$acceptModelAdd({
|
||||
url: model.uri,
|
||||
versionId: model.getVersionId(),
|
||||
lines: model.getLinesContent(),
|
||||
EOL: model.getEOL(),
|
||||
modeId: model.getLanguageIdentifier().language,
|
||||
isDirty: this._textFileService.isDirty(modelUrl)
|
||||
});
|
||||
}
|
||||
|
||||
private _onModelModeChanged(event: { model: editorCommon.IModel; oldModeId: string; }): void {
|
||||
@@ -122,15 +117,14 @@ export class MainThreadDocuments extends MainThreadDocumentsShape {
|
||||
this._proxy.$acceptModelModeChanged(model.uri.toString(), oldModeId, model.getLanguageIdentifier().language);
|
||||
}
|
||||
|
||||
private _onModelRemoved(model: editorCommon.IModel): void {
|
||||
let modelUrl = model.uri;
|
||||
if (!this._modelIsSynced[modelUrl.toString()]) {
|
||||
private _onModelRemoved(modelUrl: string): void {
|
||||
|
||||
if (!this._modelIsSynced[modelUrl]) {
|
||||
return;
|
||||
}
|
||||
delete this._modelIsSynced[modelUrl.toString()];
|
||||
this._modelToDisposeMap[modelUrl.toString()].dispose();
|
||||
delete this._modelToDisposeMap[modelUrl.toString()];
|
||||
this._proxy.$acceptModelRemoved(modelUrl.toString());
|
||||
delete this._modelIsSynced[modelUrl];
|
||||
this._modelToDisposeMap[modelUrl].dispose();
|
||||
delete this._modelToDisposeMap[modelUrl];
|
||||
}
|
||||
|
||||
private _onModelEvents(modelUrl: URI, events: EmitterEvent[]): void {
|
||||
@@ -246,6 +240,7 @@ export class MainThreadDocuments extends MainThreadDocumentsShape {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const raw: ITextSource = {
|
||||
lines: value.lines,
|
||||
length: value.length,
|
||||
|
||||
351
src/vs/workbench/api/node/mainThreadDocumentsAndEditors.ts
Normal file
351
src/vs/workbench/api/node/mainThreadDocumentsAndEditors.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModel, ICommonCodeEditor, isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { delta } from 'vs/base/common/arrays';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import Event, { Emitter, any } from 'vs/base/common/event';
|
||||
import { ExtHostContext, ExtHostDocumentsAndEditorsShape, IModelAddedData, ITextEditorAddData, IDocumentsAndEditorsDelta } from './extHost.protocol';
|
||||
import { MainThreadTextEditor } from 'vs/workbench/api/node/mainThreadEditor';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Position as EditorPosition, IEditor } from 'vs/platform/editor/common/editor';
|
||||
|
||||
namespace cmp {
|
||||
export function compareModels(a: IModel, b: IModel): number {
|
||||
return compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
export function compareEditors(a: EditorAndModel, b: EditorAndModel): number {
|
||||
let ret = compare(a.editor.getId(), b.editor.getId());
|
||||
if (ret === 0) {
|
||||
ret = compare(a.document.uri.toString(), b.document.uri.toString());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
class EditorAndModel {
|
||||
|
||||
readonly id: string;
|
||||
|
||||
constructor(
|
||||
readonly editor: ICommonCodeEditor,
|
||||
readonly document: IModel,
|
||||
) {
|
||||
this.id = `${editor.getId()},${document.uri.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentAndEditorStateDelta {
|
||||
|
||||
readonly isEmpty: boolean;
|
||||
|
||||
constructor(
|
||||
readonly removedDocuments: IModel[],
|
||||
readonly addedDocuments: IModel[],
|
||||
readonly removedEditors: EditorAndModel[],
|
||||
readonly addedEditors: EditorAndModel[],
|
||||
readonly oldActiveEditor: string,
|
||||
readonly newActiveEditor: string,
|
||||
) {
|
||||
this.isEmpty = this.removedDocuments.length === 0
|
||||
&& this.addedDocuments.length === 0
|
||||
&& this.removedEditors.length === 0
|
||||
&& this.addedEditors.length === 0
|
||||
&& oldActiveEditor === newActiveEditor;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let ret = 'DocumentAndEditorStateDelta\n';
|
||||
ret += `\tRemoved Documents: [${this.removedDocuments.map(d => d.uri.toString(true)).join(', ')}]\n`;
|
||||
ret += `\tAdded Documents: [${this.addedDocuments.map(d => d.uri.toString(true)).join(', ')}]\n`;
|
||||
ret += `\tRemoved Editors: [${this.removedEditors.map(e => e.id).join(', ')}]\n`;
|
||||
ret += `\tAdded Editors: [${this.addedEditors.map(e => e.id).join(', ')}]\n`;
|
||||
ret += `\tNew Active Editor: ${this.newActiveEditor}\n`;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentAndEditorState {
|
||||
|
||||
static compute(before: DocumentAndEditorState, after: DocumentAndEditorState): DocumentAndEditorStateDelta {
|
||||
if (!before) {
|
||||
return new DocumentAndEditorStateDelta([], after.documents, [], after.editors, undefined, after.activeEditor);
|
||||
}
|
||||
const documentDelta = delta(before.documents, after.documents, cmp.compareModels);
|
||||
const editorDelta = delta(before.editors, after.editors, cmp.compareEditors);
|
||||
const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
|
||||
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
|
||||
|
||||
return new DocumentAndEditorStateDelta(
|
||||
documentDelta.removed, documentDelta.added,
|
||||
editorDelta.removed, editorDelta.added,
|
||||
oldActiveEditor, newActiveEditor
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly documents: IModel[],
|
||||
readonly editors: EditorAndModel[],
|
||||
readonly activeEditor: string,
|
||||
) {
|
||||
this.documents = documents.sort(cmp.compareModels);
|
||||
this.editors = editors.sort(cmp.compareEditors);
|
||||
}
|
||||
}
|
||||
|
||||
class MainThreadDocumentAndEditorStateComputer {
|
||||
|
||||
private _toDispose: IDisposable[] = [];
|
||||
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
|
||||
private _onDidChangeState = new Emitter<DocumentAndEditorStateDelta>();
|
||||
private _currentState: DocumentAndEditorState;
|
||||
|
||||
readonly onDidChangeState: Event<DocumentAndEditorStateDelta> = this._onDidChangeState.event;
|
||||
|
||||
constructor(
|
||||
@IModelService private _modelService: IModelService,
|
||||
@ICodeEditorService private _codeEditorService: ICodeEditorService,
|
||||
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService
|
||||
) {
|
||||
this._modelService.onModelAdded(this._updateState, this, this._toDispose);
|
||||
this._modelService.onModelRemoved(this._updateState, this, this._toDispose);
|
||||
this._codeEditorService.onCodeEditorAdd(this._onDidAddEditor, this, this._toDispose);
|
||||
this._codeEditorService.onCodeEditorRemove(this._onDidRemoveEditor, this, this._toDispose);
|
||||
// this._updateState();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
private _onDidAddEditor(e: ICommonCodeEditor): void {
|
||||
const listener = any<any>(
|
||||
e.onDidChangeModel,
|
||||
e.onDidFocusEditor,
|
||||
e.onDidBlurEditor
|
||||
)(this._updateState, this);
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), listener);
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
private _onDidRemoveEditor(e: ICommonCodeEditor): void {
|
||||
const sub = this._toDisposeOnEditorRemove.get(e.getId());
|
||||
if (sub) {
|
||||
this._toDisposeOnEditorRemove.delete(e.getId());
|
||||
sub.dispose();
|
||||
this._updateState();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateState(): void {
|
||||
|
||||
// models: ignore too large models
|
||||
const models = this._modelService.getModels();
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
if (models[i].isTooLargeForHavingARichMode()) {
|
||||
models.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// editor: only take those that have a not too large model
|
||||
const editors: EditorAndModel[] = [];
|
||||
let activeEditor: string = null;
|
||||
|
||||
for (const editor of this._codeEditorService.listCodeEditors()) {
|
||||
const model = editor.getModel();
|
||||
if (model && !model.isTooLargeForHavingARichMode()
|
||||
&& !model.isDisposed() // model disposed
|
||||
&& Boolean(this._modelService.getModel(model.uri)) // model disposing, the flag didn't flip yet but the model service already removed it
|
||||
) {
|
||||
const apiEditor = new EditorAndModel(editor, model);
|
||||
editors.push(apiEditor);
|
||||
if (editor.isFocused()) {
|
||||
activeEditor = apiEditor.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// active editor: if none of the previous editors had focus we try
|
||||
// to match the action workbench editor with one of editor we have
|
||||
// just computed
|
||||
if (!activeEditor) {
|
||||
const workbenchEditor = this._workbenchEditorService.getActiveEditor();
|
||||
if (workbenchEditor) {
|
||||
const workbenchEditorControl = workbenchEditor.getControl();
|
||||
let candidate: ICommonCodeEditor;
|
||||
if (isCommonCodeEditor(workbenchEditorControl)) {
|
||||
candidate = workbenchEditorControl;
|
||||
} else if (isCommonDiffEditor(workbenchEditorControl)) {
|
||||
candidate = workbenchEditorControl.getModifiedEditor();
|
||||
}
|
||||
if (candidate) {
|
||||
for (const { editor, id } of editors) {
|
||||
if (candidate === editor) {
|
||||
activeEditor = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compute new state and compare against old
|
||||
const newState = new DocumentAndEditorState(models, editors, activeEditor);
|
||||
const delta = DocumentAndEditorState.compute(this._currentState, newState);
|
||||
if (!delta.isEmpty) {
|
||||
this._currentState = newState;
|
||||
this._onDidChangeState.fire(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadDocumentsAndEditors {
|
||||
|
||||
private _toDispose: IDisposable[];
|
||||
private _proxy: ExtHostDocumentsAndEditorsShape;
|
||||
private _stateComputer: MainThreadDocumentAndEditorStateComputer;
|
||||
private _editors = <{ [id: string]: MainThreadTextEditor }>Object.create(null);
|
||||
|
||||
private _onTextEditorAdd = new Emitter<MainThreadTextEditor[]>();
|
||||
private _onTextEditorRemove = new Emitter<string[]>();
|
||||
private _onDocumentAdd = new Emitter<IModel[]>();
|
||||
private _onDocumentRemove = new Emitter<string[]>();
|
||||
|
||||
readonly onTextEditorAdd: Event<MainThreadTextEditor[]> = this._onTextEditorAdd.event;
|
||||
readonly onTextEditorRemove: Event<string[]> = this._onTextEditorRemove.event;
|
||||
readonly onDocumentAdd: Event<IModel[]> = this._onDocumentAdd.event;
|
||||
readonly onDocumentRemove: Event<string[]> = this._onDocumentRemove.event;
|
||||
|
||||
constructor(
|
||||
@IModelService private _modelService: IModelService,
|
||||
@ITextFileService private _textFileService: ITextFileService,
|
||||
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService,
|
||||
@IThreadService threadService: IThreadService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
) {
|
||||
this._proxy = threadService.get(ExtHostContext.ExtHostDocumentsAndEditors);
|
||||
this._stateComputer = new MainThreadDocumentAndEditorStateComputer(_modelService, codeEditorService, _workbenchEditorService);
|
||||
this._toDispose = [
|
||||
this._stateComputer,
|
||||
this._stateComputer.onDidChangeState(this._onDelta, this)
|
||||
];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
private _onDelta(delta: DocumentAndEditorStateDelta): void {
|
||||
|
||||
let removedDocuments: string[];
|
||||
let removedEditors: string[] = [];
|
||||
let addedEditors: MainThreadTextEditor[] = [];
|
||||
|
||||
// removed models
|
||||
removedDocuments = delta.removedDocuments.map(m => m.uri.toString());
|
||||
|
||||
// added editors
|
||||
for (const apiEditor of delta.addedEditors) {
|
||||
const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.document,
|
||||
apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this._modelService);
|
||||
|
||||
this._editors[apiEditor.id] = mainThreadEditor;
|
||||
addedEditors.push(mainThreadEditor);
|
||||
}
|
||||
|
||||
// removed editors
|
||||
for (const { id } of delta.removedEditors) {
|
||||
const mainThreadEditor = this._editors[id];
|
||||
if (mainThreadEditor) {
|
||||
mainThreadEditor.dispose();
|
||||
delete this._editors[id];
|
||||
removedEditors.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
let extHostDelta: IDocumentsAndEditorsDelta = Object.create(null);
|
||||
let empty = true;
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
empty = false;
|
||||
extHostDelta.newActiveEditor = delta.newActiveEditor;
|
||||
}
|
||||
if (removedDocuments.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.removedDocuments = removedDocuments;
|
||||
}
|
||||
if (removedEditors.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.removedEditors = removedEditors;
|
||||
}
|
||||
if (delta.addedDocuments.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.addedDocuments = delta.addedDocuments.map(m => this._toModelAddData(m));
|
||||
}
|
||||
if (delta.addedEditors.length > 0) {
|
||||
empty = false;
|
||||
extHostDelta.addedEditors = addedEditors.map(e => this._toTextEditorAddData(e));
|
||||
}
|
||||
|
||||
if (!empty) {
|
||||
// first update ext host
|
||||
this._proxy.$acceptDocumentsAndEditorsDelta(extHostDelta);
|
||||
// second update dependent state listener
|
||||
this._onDocumentRemove.fire(removedDocuments);
|
||||
this._onDocumentAdd.fire(delta.addedDocuments);
|
||||
this._onTextEditorRemove.fire(removedEditors);
|
||||
this._onTextEditorAdd.fire(addedEditors);
|
||||
}
|
||||
}
|
||||
|
||||
private _toModelAddData(model: IModel): IModelAddedData {
|
||||
return {
|
||||
url: model.uri,
|
||||
versionId: model.getVersionId(),
|
||||
lines: model.getLinesContent(),
|
||||
EOL: model.getEOL(),
|
||||
modeId: model.getLanguageIdentifier().language,
|
||||
isDirty: this._textFileService.isDirty(model.uri)
|
||||
};
|
||||
}
|
||||
|
||||
private _toTextEditorAddData(textEditor: MainThreadTextEditor): ITextEditorAddData {
|
||||
return {
|
||||
id: textEditor.getId(),
|
||||
document: textEditor.getModel().uri,
|
||||
options: textEditor.getConfiguration(),
|
||||
selections: textEditor.getSelections(),
|
||||
editorPosition: this._findEditorPosition(textEditor)
|
||||
};
|
||||
}
|
||||
|
||||
private _findEditorPosition(editor: MainThreadTextEditor): EditorPosition {
|
||||
for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) {
|
||||
if (editor.matches(workbenchEditor)) {
|
||||
return workbenchEditor.position;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findTextEditorIdFor(editor: IEditor): string {
|
||||
for (let id in this._editors) {
|
||||
if (this._editors[id].matches(editor)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getEditor(id: string): MainThreadTextEditor {
|
||||
return this._editors[id];
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,8 @@
|
||||
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 { 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 { SnippetController } from 'vs/editor/contrib/snippet/common/snippetController';
|
||||
@@ -131,6 +128,10 @@ export class MainThreadTextEditor {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public getCodeEditor(): EditorCommon.ICommonCodeEditor {
|
||||
return this._codeEditor;
|
||||
}
|
||||
|
||||
public hasCodeEditor(codeEditor: EditorCommon.ICommonCodeEditor): boolean {
|
||||
return (this._codeEditor === codeEditor);
|
||||
}
|
||||
@@ -413,324 +414,3 @@ export class MainThreadTextEditor {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of what goes on in the main thread and maps models => text editors
|
||||
*/
|
||||
export class MainThreadEditorsTracker {
|
||||
|
||||
private static _Ids = new IdGenerator('');
|
||||
|
||||
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;
|
||||
|
||||
constructor(
|
||||
editorService: ICodeEditorService,
|
||||
modelService: IModelService
|
||||
) {
|
||||
this._codeEditorService = editorService;
|
||||
this._modelService = modelService;
|
||||
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._modelService.onModelAdded(this._onModelAdded, this, this._toDispose);
|
||||
this._modelService.onModelRemoved(this._onModelRemoved, this, this._toDispose);
|
||||
|
||||
this._codeEditorService.onCodeEditorAdd(this._onCodeEditorAdd, this, this._toDispose);
|
||||
this._codeEditorService.onCodeEditorRemove(this._onCodeEditorRemove, this, this._toDispose);
|
||||
|
||||
this._updateMapping = new RunOnceScheduler(() => this._doUpdateMapping(), 0);
|
||||
this._toDispose.push(this._updateMapping);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
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;
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private _updateFocusedTextEditor(): void {
|
||||
this._setFocusedTextEditorId(this._findFocusedTextEditorId());
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private _findVisibleTextEditorIds(): string[] {
|
||||
let result: 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].isVisible()) {
|
||||
result.push(editors[j].getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
result.sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
private _setFocusedTextEditorId(focusedTextEditorId: string): void {
|
||||
if (this._focusedTextEditorId === focusedTextEditorId) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusedTextEditorId = focusedTextEditorId;
|
||||
this._printState();
|
||||
this._onDidChangeFocusedTextEditor.fire(this._focusedTextEditorId);
|
||||
}
|
||||
|
||||
|
||||
private _printState(): void {
|
||||
// console.log('----------------------');
|
||||
// Object.keys(this._model2TextEditors).forEach((modelUri) => {
|
||||
// let editors = this._model2TextEditors[modelUri];
|
||||
|
||||
// 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() || !this._modelService.getModel(model.uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modelUri = model.uri.toString();
|
||||
r[modelUri] = r[modelUri] || {
|
||||
model: model,
|
||||
codeEditors: []
|
||||
};
|
||||
r[modelUri].codeEditors.push(codeEditor);
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public getFocusedTextEditorId(): string {
|
||||
return this._focusedTextEditorId;
|
||||
}
|
||||
|
||||
public getVisibleTextEditorIds(): string[] {
|
||||
return this._visibleTextEditorIds;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public registerTextEditorDecorationType(key: string, options: EditorCommon.IDecorationRenderOptions): void {
|
||||
this._codeEditorService.registerDecorationType(key, options);
|
||||
}
|
||||
|
||||
public removeTextEditorDecorationType(key: string): void {
|
||||
this._codeEditorService.removeDecorationType(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;
|
||||
}
|
||||
|
||||
@@ -8,58 +8,47 @@ import URI from 'vs/base/common/uri';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { ISingleEditOperation, ISelection, IRange, 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, IApplyEditsOptions, IUndoStopOptions, ITextEditorConfigurationUpdate } from 'vs/workbench/api/node/mainThreadEditorsTracker';
|
||||
import { TextEditorRevealType, MainThreadTextEditor, IApplyEditsOptions, IUndoStopOptions, ITextEditorConfigurationUpdate } from 'vs/workbench/api/node/mainThreadEditor';
|
||||
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { equals as arrayEquals } from 'vs/base/common/arrays';
|
||||
import { equals as objectEquals } from 'vs/base/common/objects';
|
||||
import { ExtHostContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextEditorPositionData } from './extHost.protocol';
|
||||
|
||||
export class MainThreadEditors extends MainThreadEditorsShape {
|
||||
|
||||
private _proxy: ExtHostEditorsShape;
|
||||
private _documentsAndEditors: MainThreadDocumentsAndEditors;
|
||||
private _workbenchEditorService: IWorkbenchEditorService;
|
||||
private _telemetryService: ITelemetryService;
|
||||
private _editorTracker: MainThreadEditorsTracker;
|
||||
private _toDispose: IDisposable[];
|
||||
private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; };
|
||||
private _textEditorsMap: { [editorId: string]: MainThreadTextEditor; };
|
||||
private _activeTextEditor: string;
|
||||
private _visibleEditors: string[];
|
||||
private _editorPositionData: ITextEditorPositionData;
|
||||
|
||||
constructor(
|
||||
documentsAndEditors: MainThreadDocumentsAndEditors,
|
||||
@ICodeEditorService private _codeEditorService: ICodeEditorService,
|
||||
@IThreadService threadService: IThreadService,
|
||||
@IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ICodeEditorService editorService: ICodeEditorService,
|
||||
@IModelService modelService: IModelService
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
this._proxy = threadService.get(ExtHostContext.ExtHostEditors);
|
||||
this._documentsAndEditors = documentsAndEditors;
|
||||
this._workbenchEditorService = workbenchEditorService;
|
||||
this._telemetryService = telemetryService;
|
||||
this._toDispose = [];
|
||||
this._textEditorsListenersMap = Object.create(null);
|
||||
this._textEditorsMap = Object.create(null);
|
||||
this._activeTextEditor = null;
|
||||
this._visibleEditors = [];
|
||||
this._editorPositionData = null;
|
||||
|
||||
this._editorTracker = new MainThreadEditorsTracker(editorService, modelService);
|
||||
this._toDispose.push(this._editorTracker);
|
||||
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(this._editorTracker.onTextEditorAdd((textEditor) => this._onTextEditorAdd(textEditor)));
|
||||
this._toDispose.push(this._editorTracker.onTextEditorRemove((textEditor) => this._onTextEditorRemove(textEditor)));
|
||||
|
||||
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()));
|
||||
}
|
||||
@@ -81,37 +70,17 @@ export class MainThreadEditors extends MainThreadEditorsShape {
|
||||
toDispose.push(textEditor.onSelectionChanged((event) => {
|
||||
this._proxy.$acceptSelectionsChanged(id, event);
|
||||
}));
|
||||
this._proxy.$acceptTextEditorAdd({
|
||||
id: id,
|
||||
document: textEditor.getModel().uri,
|
||||
options: textEditor.getConfiguration(),
|
||||
selections: textEditor.getSelections(),
|
||||
editorPosition: this._findEditorPosition(textEditor)
|
||||
});
|
||||
|
||||
this._textEditorsListenersMap[id] = toDispose;
|
||||
this._textEditorsMap[id] = textEditor;
|
||||
}
|
||||
|
||||
private _onTextEditorRemove(textEditor: MainThreadTextEditor): void {
|
||||
let id = textEditor.getId();
|
||||
private _onTextEditorRemove(id: string): void {
|
||||
dispose(this._textEditorsListenersMap[id]);
|
||||
delete this._textEditorsListenersMap[id];
|
||||
delete this._textEditorsMap[id];
|
||||
this._proxy.$acceptTextEditorRemove(id);
|
||||
}
|
||||
|
||||
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)) {
|
||||
@@ -120,55 +89,12 @@ export class MainThreadEditors extends MainThreadEditorsShape {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const id = this._documentsAndEditors.findTextEditorIdFor(workbenchEditor);
|
||||
if (id) {
|
||||
result[id] = workbenchEditor.position;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -187,43 +113,7 @@ export class MainThreadEditors extends MainThreadEditorsShape {
|
||||
if (!editor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const findEditor = (): string => {
|
||||
// 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 undefined;
|
||||
};
|
||||
|
||||
const syncEditorId = findEditor();
|
||||
if (syncEditorId) {
|
||||
return TPromise.as(syncEditorId);
|
||||
}
|
||||
|
||||
return new TPromise<void>(resolve => {
|
||||
// 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);
|
||||
resolve(undefined);
|
||||
}
|
||||
subscription = this._editorTracker.onDidUpdateTextEditors(() => {
|
||||
contd();
|
||||
});
|
||||
handle = setTimeout(() => {
|
||||
contd();
|
||||
}, 1000);
|
||||
|
||||
}).then(findEditor);
|
||||
return this._documentsAndEditors.findTextEditorIdFor(editor);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -231,7 +121,7 @@ export class MainThreadEditors extends MainThreadEditorsShape {
|
||||
// check how often this is used
|
||||
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.show' });
|
||||
|
||||
let mainThreadEditor = this._textEditorsMap[id];
|
||||
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
|
||||
if (mainThreadEditor) {
|
||||
let model = mainThreadEditor.getModel();
|
||||
return this._workbenchEditorService.openEditor({
|
||||
@@ -246,7 +136,7 @@ export class MainThreadEditors extends MainThreadEditorsShape {
|
||||
// check how often this is used
|
||||
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.hide' });
|
||||
|
||||
let mainThreadEditor = this._textEditorsMap[id];
|
||||
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
|
||||
if (mainThreadEditor) {
|
||||
let editors = this._workbenchEditorService.getVisibleEditors();
|
||||
for (let editor of editors) {
|
||||
@@ -259,56 +149,57 @@ export class MainThreadEditors extends MainThreadEditorsShape {
|
||||
}
|
||||
|
||||
$trySetSelections(id: string, selections: ISelection[]): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].setSelections(selections);
|
||||
this._documentsAndEditors.getEditor(id).setSelections(selections);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].setDecorations(key, ranges);
|
||||
this._documentsAndEditors.getEditor(id).setDecorations(key, ranges);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].revealRange(range, revealType);
|
||||
this._documentsAndEditors.getEditor(id).revealRange(range, revealType);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
$trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise<any> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
this._textEditorsMap[id].setConfiguration(options);
|
||||
this._documentsAndEditors.getEditor(id).setConfiguration(options);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): TPromise<boolean> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
return TPromise.as(this._textEditorsMap[id].applyEdits(modelVersionId, edits, opts));
|
||||
return TPromise.as(this._documentsAndEditors.getEditor(id).applyEdits(modelVersionId, edits, opts));
|
||||
}
|
||||
|
||||
$tryInsertSnippet(id: string, template: string, ranges: IRange[], opts: IUndoStopOptions): TPromise<boolean> {
|
||||
if (!this._textEditorsMap[id]) {
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError('TextEditor disposed');
|
||||
}
|
||||
return TPromise.as(this._textEditorsMap[id].insertSnippet(template, ranges, opts));
|
||||
return TPromise.as(this._documentsAndEditors.getEditor(id).insertSnippet(template, ranges, opts));
|
||||
}
|
||||
|
||||
$registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void {
|
||||
this._editorTracker.registerTextEditorDecorationType(key, options);
|
||||
this._codeEditorService.registerDecorationType(key, options);
|
||||
}
|
||||
|
||||
$removeTextEditorDecorationType(key: string): void {
|
||||
this._editorTracker.removeTextEditorDecorationType(key);
|
||||
this._codeEditorService.removeDecorationType(key);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user