From d285fe05e1b73ff15a706c4898e14fbe65928c25 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 27 Jun 2016 14:58:03 +0200 Subject: [PATCH] Split up ext host <-> main thread communication to separate files --- src/vs/workbench/api/node/extHost.api.impl.ts | 91 +- .../api/node/extHost.contribution.ts | 44 +- src/vs/workbench/api/node/extHostCommands.ts | 98 +-- .../api/node/extHostConfiguration.ts | 25 - .../workbench/api/node/extHostDiagnostics.ts | 25 +- src/vs/workbench/api/node/extHostDocuments.ts | 273 +----- src/vs/workbench/api/node/extHostEditors.ts | 305 +------ ...nService.ts => extHostExtensionService.ts} | 136 +-- .../api/node/extHostFileSystemEventService.ts | 42 - .../api/node/extHostLanguageFeatures.ts | 267 ++---- src/vs/workbench/api/node/extHostLanguages.ts | 16 +- .../api/node/extHostMessageService.ts | 53 +- .../api/node/extHostOutputService.ts | 54 +- src/vs/workbench/api/node/extHostProtocol.ts | 47 +- src/vs/workbench/api/node/extHostQuickOpen.ts | 85 +- src/vs/workbench/api/node/extHostStatusBar.ts | 33 +- src/vs/workbench/api/node/extHostStorage.ts | 35 +- src/vs/workbench/api/node/extHostTelemetry.ts | 21 +- src/vs/workbench/api/node/extHostTypes.ts | 5 + src/vs/workbench/api/node/extHostWorkspace.ts | 91 +- .../workbench/api/node/mainThreadCommands.ts | 103 +++ .../api/node/mainThreadConfiguration.ts | 32 + .../api/node/mainThreadDiagnostics.ts | 31 + .../workbench/api/node/mainThreadDocuments.ts | 262 ++++++ .../workbench/api/node/mainThreadEditors.ts | 794 +++++------------- .../api/node/mainThreadEditorsTracker.ts | 659 +++++++++++++++ src/vs/workbench/api/node/mainThreadErrors.ts | 15 + .../api/node/mainThreadExtensionService.ts | 143 ++++ .../node/mainThreadFileSystemEventService.ts | 49 ++ .../api/node/mainThreadLanguageFeatures.ts | 212 +++++ .../workbench/api/node/mainThreadLanguages.ts | 23 + .../api/node/mainThreadMessageService.ts | 59 ++ .../api/node/mainThreadOutputService.ts | 59 ++ .../workbench/api/node/mainThreadQuickOpen.ts | 92 ++ .../workbench/api/node/mainThreadStatusBar.ts | 37 + .../workbench/api/node/mainThreadStorage.ts | 41 + .../workbench/api/node/mainThreadTelemetry.ts | 28 + .../workbench/api/node/mainThreadWorkspace.ts | 97 +++ src/vs/workbench/electron-browser/shell.ts | 2 +- src/vs/workbench/node/extensionHostMain.ts | 2 +- .../test/node/api/extHostApiCommands.test.ts | 14 +- .../node/api/extHostLanguageFeatures.test.ts | 14 +- .../node/api/extHostMessagerService.test.ts | 2 +- 43 files changed, 2345 insertions(+), 2171 deletions(-) rename src/vs/workbench/api/node/{nativeExtensionService.ts => extHostExtensionService.ts} (72%) create mode 100644 src/vs/workbench/api/node/mainThreadCommands.ts create mode 100644 src/vs/workbench/api/node/mainThreadConfiguration.ts create mode 100644 src/vs/workbench/api/node/mainThreadDiagnostics.ts create mode 100644 src/vs/workbench/api/node/mainThreadDocuments.ts create mode 100644 src/vs/workbench/api/node/mainThreadEditorsTracker.ts create mode 100644 src/vs/workbench/api/node/mainThreadErrors.ts create mode 100644 src/vs/workbench/api/node/mainThreadExtensionService.ts create mode 100644 src/vs/workbench/api/node/mainThreadFileSystemEventService.ts create mode 100644 src/vs/workbench/api/node/mainThreadLanguageFeatures.ts create mode 100644 src/vs/workbench/api/node/mainThreadLanguages.ts create mode 100644 src/vs/workbench/api/node/mainThreadMessageService.ts create mode 100644 src/vs/workbench/api/node/mainThreadOutputService.ts create mode 100644 src/vs/workbench/api/node/mainThreadQuickOpen.ts create mode 100644 src/vs/workbench/api/node/mainThreadStatusBar.ts create mode 100644 src/vs/workbench/api/node/mainThreadStorage.ts create mode 100644 src/vs/workbench/api/node/mainThreadTelemetry.ts create mode 100644 src/vs/workbench/api/node/mainThreadWorkspace.ts diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index ba52db6cfdb..152ced1b0fe 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -7,11 +7,10 @@ import {Emitter} from 'vs/base/common/event'; import {score} from 'vs/editor/common/modes/languageSelector'; import * as Platform from 'vs/base/common/platform'; -import {regExpLeadsToEndlessLoop} from 'vs/base/common/strings'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import * as errors from 'vs/base/common/errors'; import {ExtHostFileSystemEventService} from 'vs/workbench/api/node/extHostFileSystemEventService'; -import {ExtHostModelService, setWordDefinitionFor} from 'vs/workbench/api/node/extHostDocuments'; +import {ExtHostDocuments} from 'vs/workbench/api/node/extHostDocuments'; import {ExtHostConfiguration} from 'vs/workbench/api/node/extHostConfiguration'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; import {ExtHostWorkspace} from 'vs/workbench/api/node/extHostWorkspace'; @@ -27,22 +26,18 @@ import * as ExtHostTypeConverters from 'vs/workbench/api/node/extHostTypeConvert import {registerApiCommands} from 'vs/workbench/api/node/extHostApiCommands'; import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; import Modes = require('vs/editor/common/modes'); -import {IModeService} from 'vs/editor/common/services/modeService'; import URI from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; -import {IDisposable} from 'vs/base/common/lifecycle'; import EditorCommon = require('vs/editor/common/editorCommon'); import {IExtensionDescription} from 'vs/platform/extensions/common/extensions'; -import {ExtHostExtensionService} from 'vs/workbench/api/node/nativeExtensionService'; +import {ExtHostExtensionService} from 'vs/workbench/api/node/extHostExtensionService'; import {ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; import {TPromise} from 'vs/base/common/winjs.base'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {CancellationTokenSource} from 'vs/base/common/cancellation'; import vscode = require('vscode'); -import {TextEditorRevealType} from 'vs/workbench/api/node/mainThreadEditors'; import * as paths from 'vs/base/common/paths'; import {ITelemetryService, ITelemetryInfo} from 'vs/platform/telemetry/common/telemetry'; -import {LanguageConfigurationRegistry} from 'vs/editor/common/modes/languageConfigurationRegistry'; import {MainContext, ExtHostContext, InstanceCollection} from './extHostProtocol'; /** @@ -51,13 +46,6 @@ import {MainContext, ExtHostContext, InstanceCollection} from './extHostProtocol */ export class ExtHostAPIImplementation { - private static _LAST_REGISTER_TOKEN = 0; - private static generateDisposeToken(): string { - return String(++ExtHostAPIImplementation._LAST_REGISTER_TOKEN); - } - - private _proxy: MainProcessVSCodeAPIHelper; - version: typeof vscode.version; env: typeof vscode.env; Uri: typeof vscode.Uri; @@ -106,7 +94,7 @@ export class ExtHostAPIImplementation { // Addressable instances const col = new InstanceCollection(); - const extHostDocuments = col.define(ExtHostContext.ExtHostModelService).set(new ExtHostModelService(threadService)); + const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set(new ExtHostDocuments(threadService)); const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set(new ExtHostEditors(threadService, extHostDocuments)); const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostEditors)); const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set(new ExtHostConfiguration()); @@ -118,11 +106,10 @@ export class ExtHostAPIImplementation { col.finish(false, threadService); - // Others - this._proxy = threadService.get(MainContext.MainProcessVSCodeAPIHelper); + const mainThreadErrors = threadService.get(MainContext.MainThreadErrors); errors.setUnexpectedErrorHandler((err) => { - this._proxy.onUnexpectedExtHostError(errors.transformErrorForSerialization(err)); + mainThreadErrors.onUnexpectedExtHostError(errors.transformErrorForSerialization(err)); }); const extHostMessageService = new ExtHostMessageService(threadService); @@ -167,7 +154,7 @@ export class ExtHostAPIImplementation { this.StatusBarAlignment = extHostTypes.StatusBarAlignment; this.IndentAction = Modes.IndentAction; this.OverviewRulerLane = EditorCommon.OverviewRulerLane; - this.TextEditorRevealType = TextEditorRevealType; + this.TextEditorRevealType = extHostTypes.TextEditorRevealType; this.EndOfLine = extHostTypes.EndOfLine; this.TextEditorCursorStyle = EditorCommon.TextEditorCursorStyle; @@ -383,7 +370,7 @@ export class ExtHostAPIImplementation { return languageFeatures.registerCompletionItemProvider(selector, provider, triggerCharacters); }, setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration):vscode.Disposable => { - return this._setLanguageConfiguration(language, configuration); + return languageFeatures.setLanguageConfiguration(language, configuration); } }; @@ -399,41 +386,6 @@ export class ExtHostAPIImplementation { } }; } - - private _disposableFromToken(disposeToken:string): IDisposable { - return new extHostTypes.Disposable(() => this._proxy.disposeByToken(disposeToken)); - } - - private _setLanguageConfiguration(modeId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable { - - let {wordPattern} = configuration; - - // check for a valid word pattern - if (wordPattern && regExpLeadsToEndlessLoop(wordPattern)) { - throw new Error(`Invalid language configuration: wordPattern '${wordPattern}' is not allowed to match the empty string.`); - } - - // word definition - if (wordPattern) { - setWordDefinitionFor(modeId, wordPattern); - } else { - setWordDefinitionFor(modeId, null); - } - - // backward compatibility, migrate deprecated setting - if (configuration.__characterPairSupport && !configuration.autoClosingPairs) { - configuration.autoClosingPairs = configuration.__characterPairSupport.autoClosingPairs; - delete configuration.__characterPairSupport; - } - - return this.Modes_RichEditSupport_register(modeId, configuration); - } - - private Modes_RichEditSupport_register(modeId: string, configuration:vscode.LanguageConfiguration): IDisposable { - let disposeToken = ExtHostAPIImplementation.generateDisposeToken(); - this._proxy.Modes_RichEditSupport_register(disposeToken, modeId, configuration); - return this._disposableFromToken(disposeToken); - } } class Extension implements vscode.Extension { @@ -475,32 +427,3 @@ export function defineAPI(impl: typeof vscode) { }; define('vscode', [], impl); } - -export class MainProcessVSCodeAPIHelper { - protected _modeService: IModeService; - private _token2Dispose: { - [token:string]: IDisposable; - }; - - constructor( - @IModeService modeService: IModeService - ) { - this._modeService = modeService; - this._token2Dispose = {}; - } - - public onUnexpectedExtHostError(err: any): void { - errors.onUnexpectedError(err); - } - - public disposeByToken(disposeToken:string): void { - if (this._token2Dispose[disposeToken]) { - this._token2Dispose[disposeToken].dispose(); - delete this._token2Dispose[disposeToken]; - } - } - - public Modes_RichEditSupport_register(disposeToken:string, modeId: string, configuration:vscode.LanguageConfiguration): void { - this._token2Dispose[disposeToken] = LanguageConfigurationRegistry.register(modeId, configuration); - } -} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.contribution.ts b/src/vs/workbench/api/node/extHost.contribution.ts index 7b419114efd..205b45c3994 100644 --- a/src/vs/workbench/api/node/extHost.contribution.ts +++ b/src/vs/workbench/api/node/extHost.contribution.ts @@ -10,29 +10,33 @@ import {Registry} from 'vs/platform/platform'; import {IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions} from 'vs/workbench/common/contributions'; import {IInstantiationService, IConstructorSignature0} from 'vs/platform/instantiation/common/instantiation'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {MainThreadDocuments} from 'vs/workbench/api/node/extHostDocuments'; +import {MainContext, InstanceCollection} from './extHostProtocol'; +import {IExtensionService} from 'vs/platform/extensions/common/extensions'; + +// --- addressable +import {MainThreadCommands} from './mainThreadCommands'; +import {MainThreadConfiguration} from './mainThreadConfiguration'; +import {MainThreadDiagnostics} from './mainThreadDiagnostics'; +import {MainThreadDocuments} from './mainThreadDocuments'; +import {MainThreadEditors} from './mainThreadEditors'; +import {MainThreadErrors} from './mainThreadErrors'; +import {MainThreadLanguageFeatures} from './mainThreadLanguageFeatures'; +import {MainThreadLanguages} from './mainThreadLanguages'; +import {MainThreadMessageService} from './mainThreadMessageService'; +import {MainThreadOutputService} from './mainThreadOutputService'; +import {MainThreadQuickOpen} from './mainThreadQuickOpen'; +import {MainThreadStatusBar} from './mainThreadStatusBar'; +import {MainThreadStorage} from './mainThreadStorage'; +import {MainThreadTelemetry} from './mainThreadTelemetry'; +import {MainThreadWorkspace} from './mainThreadWorkspace'; +import {MainProcessExtensionService} from './mainThreadExtensionService'; +import {MainThreadFileSystemEventService} from './mainThreadFileSystemEventService'; + +// --- other interested parties import {MainProcessTextMateSyntax} from 'vs/editor/node/textMate/TMSyntax'; import {MainProcessTextMateSnippet} from 'vs/editor/node/textMate/TMSnippets'; import {JSONValidationExtensionPoint} from 'vs/platform/jsonschemas/common/jsonValidationExtensionPoint'; import {LanguageConfigurationFileHandler} from 'vs/editor/node/languageConfiguration'; -import {MainThreadFileSystemEventService} from 'vs/workbench/api/node/extHostFileSystemEventService'; -import {MainThreadQuickOpen} from 'vs/workbench/api/node/extHostQuickOpen'; -import {MainThreadStatusBar} from 'vs/workbench/api/node/extHostStatusBar'; -import {MainThreadCommands} from 'vs/workbench/api/node/extHostCommands'; -import {MainThreadTelemetry} from 'vs/workbench/api/node/extHostTelemetry'; -import {MainThreadDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; -import {MainThreadOutputService} from 'vs/workbench/api/node/extHostOutputService'; -import {MainThreadMessageService} from 'vs/workbench/api/node/extHostMessageService'; -import {MainThreadLanguages} from 'vs/workbench/api/node/extHostLanguages'; -import {MainThreadEditors} from 'vs/workbench/api/node/extHostEditors'; -import {MainThreadWorkspace} from 'vs/workbench/api/node/extHostWorkspace'; -import {MainThreadConfiguration} from 'vs/workbench/api/node/extHostConfiguration'; -import {MainThreadLanguageFeatures} from 'vs/workbench/api/node/extHostLanguageFeatures'; -import {MainThreadStorage} from 'vs/workbench/api/node/extHostStorage'; -import {MainProcessVSCodeAPIHelper} from 'vs/workbench/api/node/extHost.api.impl'; -import {MainContext, InstanceCollection} from './extHostProtocol'; -import {IExtensionService} from 'vs/platform/extensions/common/extensions'; -import {MainProcessExtensionService} from './nativeExtensionService'; export class ExtHostContribution implements IWorkbenchContribution { @@ -55,12 +59,12 @@ export class ExtHostContribution implements IWorkbenchContribution { // Addressable instances const col = new InstanceCollection(); - col.define(MainContext.MainProcessVSCodeAPIHelper).set(create(MainProcessVSCodeAPIHelper)); 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.MainThreadErrors).set(create(MainThreadErrors)); col.define(MainContext.MainThreadLanguageFeatures).set(create(MainThreadLanguageFeatures)); col.define(MainContext.MainThreadLanguages).set(create(MainThreadLanguages)); col.define(MainContext.MainThreadMessageService).set(create(MainThreadMessageService)); diff --git a/src/vs/workbench/api/node/extHostCommands.ts b/src/vs/workbench/api/node/extHostCommands.ts index abbabf0236b..5b51f294779 100644 --- a/src/vs/workbench/api/node/extHostCommands.ts +++ b/src/vs/workbench/api/node/extHostCommands.ts @@ -6,14 +6,14 @@ import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import {validateConstraint} from 'vs/base/common/types'; -import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; -import {IKeybindingService, ICommandHandlerDescription} from 'vs/platform/keybinding/common/keybindingService'; +import {ICommandHandlerDescription} from 'vs/platform/keybinding/common/keybindingService'; import {TPromise} from 'vs/base/common/winjs.base'; import {ExtHostEditors} from 'vs/workbench/api/node/extHostEditors'; 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'; -import {MainContext, ExtHostContext} from './extHostProtocol'; +import {MainContext} from './extHostProtocol'; +import {MainThreadCommands} from './mainThreadCommands'; interface CommandHandler { callback: Function; @@ -126,95 +126,3 @@ export class ExtHostCommands { return TPromise.as(result); } } - -export class MainThreadCommands { - - private _threadService: IThreadService; - private _keybindingService: IKeybindingService; - private _proxy: ExtHostCommands; - - constructor( - @IThreadService threadService: IThreadService, - @IKeybindingService keybindingService: IKeybindingService - ) { - this._threadService = threadService; - this._keybindingService = keybindingService; - this._proxy = this._threadService.get(ExtHostContext.ExtHostCommands); - } - - $registerCommand(id: string): TPromise { - - KeybindingsRegistry.registerCommandDesc({ - id, - handler: (serviceAccessor, ...args: any[]) => { - return this._proxy.$executeContributedCommand(id, ...args); - }, - weight: undefined, - when: undefined, - win: undefined, - mac: undefined, - linux: undefined, - primary: undefined, - secondary: undefined - }); - - return undefined; - } - - $executeCommand(id: string, args: any[]): Thenable { - return this._keybindingService.executeCommand(id, ...args); - } - - $getCommands(): Thenable { - return TPromise.as(Object.keys(KeybindingsRegistry.getCommands())); - } -} - - -// --- command doc - -KeybindingsRegistry.registerCommandDesc({ - id: '_generateCommandsDocumentation', - handler: function(accessor) { - return accessor.get(IThreadService).get(ExtHostContext.ExtHostCommands).$getContributedCommandHandlerDescriptions().then(result => { - - // add local commands - const commands = KeybindingsRegistry.getCommands(); - for (let id in commands) { - let {description} = commands[id]; - if (description) { - result[id] = description; - } - } - - // print all as markdown - const all: string[] = []; - for (let id in result) { - all.push('`' + id + '` - ' + _generateMarkdown(result[id])); - } - console.log(all.join('\n')); - }); - }, - when: undefined, - weight: KeybindingsRegistry.WEIGHT.builtinExtension(0), - primary: undefined -}); - -function _generateMarkdown(description: string | ICommandHandlerDescription): string { - if (typeof description === 'string') { - return description; - } else { - let parts = [description.description]; - parts.push('\n\n'); - if (description.args) { - for (let arg of description.args) { - parts.push(`* _${arg.name}_ ${arg.description || ''}\n`); - } - } - if (description.returns) { - parts.push(`* _(returns)_ ${description.returns}`); - } - parts.push('\n\n'); - return parts.join(''); - } -} diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 1f2629d8a99..2f79028fc41 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -6,12 +6,8 @@ import {clone} from 'vs/base/common/objects'; import {illegalState} from 'vs/base/common/errors'; -import {IDisposable, dispose} from 'vs/base/common/lifecycle'; -import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import Event, {Emitter} from 'vs/base/common/event'; import {WorkspaceConfiguration} from 'vscode'; -import {ExtHostContext} from './extHostProtocol'; export class ExtHostConfiguration { @@ -76,24 +72,3 @@ export class ExtHostConfiguration { return node; } } - -export class MainThreadConfiguration { - - private _configurationService: IConfigurationService; - private _toDispose: IDisposable; - private _proxy: ExtHostConfiguration; - - constructor(@IConfigurationService configurationService: IConfigurationService, - @IThreadService threadService: IThreadService) { - - this._configurationService = configurationService; - this._proxy = threadService.get(ExtHostContext.ExtHostConfiguration); - - this._toDispose = this._configurationService.onDidUpdateConfiguration(event => this._proxy.$acceptConfigurationChanged(event.config)); - this._proxy.$acceptConfigurationChanged(this._configurationService.getConfiguration()); - } - - public dispose(): void { - this._toDispose = dispose(this._toDispose); - } -} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostDiagnostics.ts b/src/vs/workbench/api/node/extHostDiagnostics.ts index e6031f24c41..70d74e02124 100644 --- a/src/vs/workbench/api/node/extHostDiagnostics.ts +++ b/src/vs/workbench/api/node/extHostDiagnostics.ts @@ -5,12 +5,12 @@ 'use strict'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IMarkerService, IMarkerData} from 'vs/platform/markers/common/markers'; +import {IMarkerData} from 'vs/platform/markers/common/markers'; import URI from 'vs/base/common/uri'; -import {TPromise} from 'vs/base/common/winjs.base'; import Severity from 'vs/base/common/severity'; import * as vscode from 'vscode'; import {MainContext} from './extHostProtocol'; +import {MainThreadDiagnostics} from './mainThreadDiagnostics'; export class DiagnosticCollection implements vscode.DiagnosticCollection { @@ -216,24 +216,3 @@ export class ExtHostDiagnostics { } } -export class MainThreadDiagnostics { - - private _markerService: IMarkerService; - - constructor(@IMarkerService markerService: IMarkerService) { - this._markerService = markerService; - } - - $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise { - for (let entry of entries) { - let [uri, markers] = entry; - this._markerService.changeOne(owner, uri, markers); - } - return undefined; - } - - $clear(owner: string): TPromise { - this._markerService.changeAll(owner, undefined); - return undefined; - } -} diff --git a/src/vs/workbench/api/node/extHostDocuments.ts b/src/vs/workbench/api/node/extHostDocuments.ts index 928774239cb..94614d20b1f 100644 --- a/src/vs/workbench/api/node/extHostDocuments.ts +++ b/src/vs/workbench/api/node/extHostDocuments.ts @@ -4,34 +4,26 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {toErrorMessage, onUnexpectedError} from 'vs/base/common/errors'; -import {EmitterEvent} from 'vs/base/common/eventEmitter'; -import {IModelService} from 'vs/editor/common/services/modelService'; -import * as EditorCommon from 'vs/editor/common/editorCommon'; +import {onUnexpectedError} from 'vs/base/common/errors'; +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, dispose} from 'vs/base/common/lifecycle'; +import {IDisposable} from 'vs/base/common/lifecycle'; import {Range, Position, Disposable} from 'vs/workbench/api/node/extHostTypes'; -import {IEventService} from 'vs/platform/event/common/event'; -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {EventType as FileEventType, TextFileChangeEvent, ITextFileService} from 'vs/workbench/parts/files/common/files'; import * as TypeConverters from './extHostTypeConverters'; import {TPromise} from 'vs/base/common/winjs.base'; import * as vscode from 'vscode'; -import {IFileService} from 'vs/platform/files/common/files'; -import {IModeService} from 'vs/editor/common/services/modeService'; -import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; -import {ResourceEditorInput} from 'vs/workbench/common/editor/resourceEditorInput'; import {asWinJsPromise} from 'vs/base/common/async'; import {getWordAtText, ensureValidWordDefinition} from 'vs/editor/common/model/wordHelper'; -import {MainContext, ExtHostContext} from './extHostProtocol'; +import {MainContext} from './extHostProtocol'; +import {MainThreadDocuments} from './mainThreadDocuments'; export interface IModelAddedData { url: URI; versionId: number; - value: EditorCommon.IRawText; + value: editorCommon.IRawText; modeId: string; isDirty: boolean; } @@ -40,15 +32,15 @@ const _modeId2WordDefinition: { [modeId: string]: RegExp; } = Object.create(null); -export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { +function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { _modeId2WordDefinition[modeId] = wordDefinition; } -export function getWordDefinitionFor(modeId: string): RegExp { +function getWordDefinitionFor(modeId: string): RegExp { return _modeId2WordDefinition[modeId]; } -export class ExtHostModelService { +export class ExtHostDocuments { private static _handlePool: number = 0; @@ -135,7 +127,7 @@ export class ExtHostModelService { throw new Error(`scheme '${scheme}' already registered`); } - const handle = ExtHostModelService._handlePool++; + const handle = ExtHostDocuments._handlePool++; this._documentContentProviders[handle] = provider; this._proxy.$registerTextContentProvider(handle, scheme); @@ -215,7 +207,7 @@ export class ExtHostModelService { data.dispose(); } - public _acceptModelChanged(strURL: string, events: EditorCommon.IModelContentChangedEvent2[]): void { + public _acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[]): void { let data = this._documentData[strURL]; data.onEvents(events); this._onDidChangeDocumentEventEmitter.fire({ @@ -229,6 +221,10 @@ export class ExtHostModelService { }) }); } + + setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { + setWordDefinitionFor(modeId, wordDefinition); + } } export class ExtHostDocumentData extends MirrorModel2 { @@ -436,242 +432,3 @@ export class ExtHostDocumentData extends MirrorModel2 { } } } - -export class MainThreadDocuments { - private _modelService: IModelService; - private _modeService: IModeService; - private _textFileService: ITextFileService; - private _editorService: IWorkbenchEditorService; - private _fileService: IFileService; - private _untitledEditorService: IUntitledEditorService; - private _toDispose: IDisposable[]; - private _modelToDisposeMap: { [modelUrl: string]: IDisposable; }; - private _proxy: ExtHostModelService; - private _modelIsSynced: { [modelId: string]: boolean; }; - private _resourceContentProvider: { [handle: number]: IDisposable }; - private _virtualDocumentSet: { [resource: string]: boolean }; - - constructor( - @IThreadService threadService: IThreadService, - @IModelService modelService: IModelService, - @IModeService modeService: IModeService, - @IEventService eventService: IEventService, - @ITextFileService textFileService: ITextFileService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IFileService fileService: IFileService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService - ) { - this._modelService = modelService; - this._modeService = modeService; - this._textFileService = textFileService; - this._editorService = editorService; - this._fileService = fileService; - this._untitledEditorService = untitledEditorService; - this._proxy = threadService.get(ExtHostContext.ExtHostModelService); - this._modelIsSynced = {}; - - this._toDispose = []; - modelService.onModelAdded(this._onModelAdded, this, this._toDispose); - modelService.onModelRemoved(this._onModelRemoved, this, this._toDispose); - modelService.onModelModeChanged(this._onModelModeChanged, this, this._toDispose); - - this._toDispose.push(eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => { - if (this._shouldHandleFileEvent(e)) { - this._proxy._acceptModelSaved(e.resource.toString()); - } - })); - this._toDispose.push(eventService.addListener2(FileEventType.FILE_REVERTED, (e: TextFileChangeEvent) => { - if (this._shouldHandleFileEvent(e)) { - this._proxy._acceptModelReverted(e.resource.toString()); - } - })); - this._toDispose.push(eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => { - if (this._shouldHandleFileEvent(e)) { - this._proxy._acceptModelDirty(e.resource.toString()); - } - })); - - const handle = setInterval(() => this._runDocumentCleanup(), 1000 * 60 * 3); - this._toDispose.push({ dispose() { clearInterval(handle); } }); - - this._modelToDisposeMap = Object.create(null); - this._resourceContentProvider = Object.create(null); - this._virtualDocumentSet = Object.create(null); - } - - public dispose(): void { - Object.keys(this._modelToDisposeMap).forEach((modelUrl) => { - this._modelToDisposeMap[modelUrl].dispose(); - }); - this._modelToDisposeMap = Object.create(null); - this._toDispose = dispose(this._toDispose); - } - - private _shouldHandleFileEvent(e: TextFileChangeEvent): boolean { - const model = this._modelService.getModel(e.resource); - return model && !model.isTooLargeForHavingARichMode(); - } - - private _onModelAdded(model: EditorCommon.IModel): void { - // Same filter as in mainThreadEditors - if (model.isTooLargeForHavingARichMode()) { - // don't synchronize too large models - return null; - } - 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(), - value: model.toRawText(), - modeId: model.getMode().getId(), - isDirty: this._textFileService.isDirty(modelUrl) - }); - } - - private _onModelModeChanged(event: { model: EditorCommon.IModel; oldModeId: string; }): void { - let {model, oldModeId} = event; - let modelUrl = model.uri; - if (!this._modelIsSynced[modelUrl.toString()]) { - return; - } - this._proxy._acceptModelModeChanged(model.uri.toString(), oldModeId, model.getMode().getId()); - } - - private _onModelRemoved(model: EditorCommon.IModel): void { - let modelUrl = model.uri; - if (!this._modelIsSynced[modelUrl.toString()]) { - return; - } - delete this._modelIsSynced[modelUrl.toString()]; - this._modelToDisposeMap[modelUrl.toString()].dispose(); - delete this._modelToDisposeMap[modelUrl.toString()]; - this._proxy._acceptModelRemoved(modelUrl.toString()); - } - - private _onModelEvents(modelUrl: URI, events: EmitterEvent[]): void { - let changedEvents: EditorCommon.IModelContentChangedEvent2[] = []; - for (let i = 0, len = events.length; i < len; i++) { - let e = events[i]; - switch (e.getType()) { - case EditorCommon.EventType.ModelContentChanged2: - changedEvents.push(e.getData()); - break; - } - } - if (changedEvents.length > 0) { - this._proxy._acceptModelChanged(modelUrl.toString(), changedEvents); - } - } - - // --- from extension host process - - _trySaveDocument(uri: URI): TPromise { - return this._textFileService.save(uri); - } - - _tryOpenDocument(uri: URI): TPromise { - - if (!uri.scheme || !(uri.fsPath || uri.authority)) { - return TPromise.wrapError(`Invalid uri. Scheme and authority or path must be set.`); - } - - let promise: TPromise; - switch (uri.scheme) { - case 'untitled': - promise = this._handleUnititledScheme(uri); - break; - case 'file': - default: - promise = this._handleAsResourceInput(uri); - break; - } - - return promise.then(success => { - if (!success) { - return TPromise.wrapError('cannot open ' + uri.toString()); - } - }, err => { - return TPromise.wrapError('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err)); - }); - } - - private _handleAsResourceInput(uri: URI): TPromise { - return this._editorService.resolveEditorModel({ resource: uri }).then(model => { - return !!model; - }); - } - - private _handleUnititledScheme(uri: URI): TPromise { - let asFileUri = URI.file(uri.fsPath); - return this._fileService.resolveFile(asFileUri).then(stats => { - // don't create a new file ontop of an existing file - return TPromise.wrapError('file already exists on disk'); - }, err => { - let input = this._untitledEditorService.createOrGet(asFileUri); // using file-uri makes it show in 'Working Files' section - return input.resolve(true).then(model => { - if (input.getResource().toString() !== uri.toString()) { - throw new Error(`expected URI ${uri.toString() } BUT GOT ${input.getResource().toString() }`); - } - if (!this._modelIsSynced[uri.toString()]) { - throw new Error(`expected URI ${uri.toString()} to have come to LIFE`); - } - return this._proxy._acceptModelDirty(uri.toString()); // mark as dirty - }).then(() => { - return true; - }); - }); - } - - // --- virtual document logic - - $registerTextContentProvider(handle:number, scheme: string): void { - this._resourceContentProvider[handle] = ResourceEditorInput.registerResourceContentProvider(scheme, { - provideTextContent: (uri: URI): TPromise => { - return this._proxy.$provideTextDocumentContent(handle, uri).then(value => { - if (typeof value === 'string') { - this._virtualDocumentSet[uri.toString()] = true; - const firstLineText = value.substr(0, 1 + value.search(/\r?\n/)); - const mode = this._modeService.getOrCreateModeByFilenameOrFirstLine(uri.fsPath, firstLineText); - return this._modelService.createModel(value, mode, uri); - } - }); - } - }); - } - - $unregisterTextContentProvider(handle: number): void { - const registration = this._resourceContentProvider[handle]; - if (registration) { - registration.dispose(); - delete this._resourceContentProvider[handle]; - } - } - - $onVirtualDocumentChange(uri: URI, value: string): void { - const model = this._modelService.getModel(uri); - if (model) { - model.setValue(value); - } - } - - private _runDocumentCleanup(): void { - - const toBeDisposed: URI[] = []; - - TPromise.join(Object.keys(this._virtualDocumentSet).map(key => { - let resource = URI.parse(key); - return this._editorService.createInput({ resource }).then(input => { - if (!this._editorService.isVisible(input, true)) { - toBeDisposed.push(resource); - } - }); - })).then(() => { - for (let resource of toBeDisposed) { - this._modelService.destroyModel(resource); - delete this._virtualDocumentSet[resource.toString()]; - } - }, onUnexpectedError); - } -} diff --git a/src/vs/workbench/api/node/extHostEditors.ts b/src/vs/workbench/api/node/extHostEditors.ts index c4fc5b0a5d2..a0327f99e98 100644 --- a/src/vs/workbench/api/node/extHostEditors.ts +++ b/src/vs/workbench/api/node/extHostEditors.ts @@ -8,25 +8,17 @@ import URI from 'vs/base/common/uri'; import {readonly, illegalArgument} from 'vs/base/common/errors'; import {IdGenerator} from 'vs/base/common/idGenerator'; import Event, {Emitter} from 'vs/base/common/event'; -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 {ExtHostModelService, ExtHostDocumentData} from 'vs/workbench/api/node/extHostDocuments'; -import {Selection, Range, Position, EditorOptions, EndOfLine} from './extHostTypes'; -import {ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions} from 'vs/editor/common/editorCommon'; -import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {ExtHostDocuments, ExtHostDocumentData} from 'vs/workbench/api/node/extHostDocuments'; +import {Selection, Range, Position, EditorOptions, EndOfLine, TextEditorRevealType} from './extHostTypes'; +import {ISingleEditOperation, ISelection} from 'vs/editor/common/editorCommon'; import {Position as EditorPosition} from 'vs/platform/editor/common/editor'; -import {IModelService} from 'vs/editor/common/services/modelService'; -import {MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration} from 'vs/workbench/api/node/mainThreadEditors'; +import {IResolvedTextEditorConfiguration} from 'vs/workbench/api/node/mainThreadEditorsTracker'; import * as TypeConverters from './extHostTypeConverters'; import {TextDocument, TextEditorSelectionChangeEvent, TextEditorOptionsChangeEvent, TextEditorOptions, TextEditorViewColumnChangeEvent, ViewColumn} from 'vscode'; -import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; -import {IEventService} from 'vs/platform/event/common/event'; -import {equals as arrayEquals} from 'vs/base/common/arrays'; -import {equals as objectEquals} from 'vs/base/common/objects'; -import {MainContext, ExtHostContext} from './extHostProtocol'; +import {MainContext} from './extHostProtocol'; +import {MainThreadEditors} from './mainThreadEditors'; export interface ITextEditorAddData { id: string; @@ -54,13 +46,13 @@ export class ExtHostEditors { private _editors: { [id: string]: ExtHostTextEditor }; private _proxy: MainThreadEditors; private _onDidChangeActiveTextEditor: Emitter; - private _modelService: ExtHostModelService; + private _extHostDocuments: ExtHostDocuments; private _activeEditorId: string; private _visibleEditorIds: string[]; constructor( threadService: IThreadService, - modelService: ExtHostModelService + extHostDocuments: ExtHostDocuments ) { this._onDidChangeTextEditorSelection = new Emitter(); this.onDidChangeTextEditorSelection = this._onDidChangeTextEditorSelection.event; @@ -71,7 +63,7 @@ export class ExtHostEditors { this._onDidChangeTextEditorViewColumn = new Emitter(); this.onDidChangeTextEditorViewColumn = this._onDidChangeTextEditorViewColumn.event; - this._modelService = modelService; + this._extHostDocuments = extHostDocuments; this._proxy = threadService.get(MainContext.MainThreadEditors); this._onDidChangeActiveTextEditor = new Emitter(); this._editors = Object.create(null); @@ -109,7 +101,7 @@ export class ExtHostEditors { // --- called from main thread _acceptTextEditorAdd(data: ITextEditorAddData): void { - let document = this._modelService.getDocumentData(data.document); + 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[data.id] = newEditor; } @@ -404,7 +396,7 @@ class ExtHostTextEditor implements vscode.TextEditor { () => this._proxy._tryRevealRange( this._id, TypeConverters.fromRange(range), - (revealType) || TextEditorRevealType.Default + revealType || TextEditorRevealType.Default ), true ); @@ -453,278 +445,3 @@ class ExtHostTextEditor implements vscode.TextEditor { }); } } - -export class MainThreadEditors { - - private _proxy: ExtHostEditors; - 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( - @IThreadService threadService: IThreadService, - @IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService, - @IEditorGroupService editorGroupService: IEditorGroupService, - @ITelemetryService telemetryService: ITelemetryService, - @ICodeEditorService editorService: ICodeEditorService, - @IEventService eventService: IEventService, - @IModelService modelService: IModelService - ) { - this._proxy = threadService.get(ExtHostContext.ExtHostEditors); - 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(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())); - } - - public dispose(): void { - Object.keys(this._textEditorsListenersMap).forEach((editorId) => { - dispose(this._textEditorsListenersMap[editorId]); - }); - this._textEditorsListenersMap = Object.create(null); - this._toDispose = dispose(this._toDispose); - } - - private _onTextEditorAdd(textEditor: MainThreadTextEditor): void { - let id = textEditor.getId(); - let toDispose: IDisposable[] = []; - toDispose.push(textEditor.onConfigurationChanged((opts) => { - this._proxy._acceptOptionsChanged(id, opts); - })); - toDispose.push(textEditor.onSelectionChanged((selection) => { - this._proxy._acceptSelectionsChanged(id, selection); - })); - this._proxy._acceptTextEditorAdd({ - id: id, - document: textEditor.getModel().uri, - options: textEditor.getConfiguration(), - selections: textEditor.getSelections(), - editorPosition: this._findEditorPosition(textEditor) - }); - - this._textEditorsListenersMap[id] = toDispose; - this._textEditorsMap[id] = textEditor; - } - - private _onTextEditorRemove(textEditor: MainThreadTextEditor): void { - let id = textEditor.getId(); - dispose(this._textEditorsListenersMap[id]); - delete this._textEditorsListenersMap[id]; - delete this._textEditorsMap[id]; - this._proxy._acceptTextEditorRemove(id); - } - - private _updateActiveAndVisibleTextEditors(): void { - - // active and visible editors - let visibleEditors = this._editorTracker.getVisibleTextEditorIds(); - let activeEditor = this._findActiveTextEditorId(); - if (activeEditor !== this._activeTextEditor || !arrayEquals(this._visibleEditors, visibleEditors, (a, b) => a === b)) { - this._activeTextEditor = activeEditor; - this._visibleEditors = visibleEditors; - this._proxy._acceptActiveEditorAndVisibleEditors(this._activeTextEditor, this._visibleEditors); - } - - // editor columns - let editorPositionData = this._getTextEditorPositionData(); - if (!objectEquals(this._editorPositionData, editorPositionData)) { - this._editorPositionData = editorPositionData; - this._proxy._acceptEditorPositionData(this._editorPositionData); - } - } - - private _findActiveTextEditorId(): string { - let focusedTextEditorId = this._editorTracker.getFocusedTextEditorId(); - if (focusedTextEditorId) { - return focusedTextEditorId; - } - - let activeEditor = this._workbenchEditorService.getActiveEditor(); - if (!activeEditor) { - return null; - } - - let editor = 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(editor); - } - - // Must be a diff editor => use the modified side - return this._editorTracker.findTextEditorIdFor((editor).getModifiedEditor()); - } - - private _findEditorPosition(editor: MainThreadTextEditor): EditorPosition { - for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) { - if (editor.matches(workbenchEditor)) { - return workbenchEditor.position; - } - } - } - - private _getTextEditorPositionData(): ITextEditorPositionData { - let result: ITextEditorPositionData = Object.create(null); - for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) { - let editor = 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(editor); - if (id) { - result[id] = workbenchEditor.position; - } - } - } - return result; - } - - // --- from extension host process - - _tryShowTextDocument(resource: URI, position: EditorPosition, preserveFocus: boolean): TPromise { - - const input = { - resource, - options: { preserveFocus } - }; - - return this._workbenchEditorService.openEditor(input, position).then(editor => { - - if (!editor) { - return; - } - - return new TPromise(c => { - // not very nice but the way it is: changes to the editor state aren't - // send to the ext host as they happen but stuff is delayed a little. in - // order to provide the real editor on #openTextEditor we need to sync on - // that update - let subscription: IDisposable; - let handle: number; - function contd() { - subscription.dispose(); - clearTimeout(handle); - c(undefined); - } - subscription = this._editorTracker.onDidUpdateTextEditors(() => { - contd(); - }); - handle = setTimeout(() => { - contd(); - }, 1000); - - }).then(() => { - // find the editor we have just opened and return the - // id we have assigned to it. - for (let id in this._textEditorsMap) { - if (this._textEditorsMap[id].matches(editor)) { - return id; - } - } - }); - }); - } - - _tryShowEditor(id: string, position: EditorPosition): TPromise { - // check how often this is used - this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.show' }); - - let mainThreadEditor = this._textEditorsMap[id]; - if (mainThreadEditor) { - let model = mainThreadEditor.getModel(); - return this._workbenchEditorService.openEditor({ - resource: model.uri, - options: { preserveFocus: false } - }, position).then(() => { return; }); - } - } - - _tryHideEditor(id: string): TPromise { - // check how often this is used - this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.hide' }); - - let mainThreadEditor = this._textEditorsMap[id]; - if (mainThreadEditor) { - let editors = this._workbenchEditorService.getVisibleEditors(); - for (let editor of editors) { - if (mainThreadEditor.matches(editor)) { - return this._workbenchEditorService.closeEditor(editor.position, editor.input).then(() => { return; }); - } - } - } - } - - _trySetSelections(id: string, selections: ISelection[]): TPromise { - if (!this._textEditorsMap[id]) { - return TPromise.wrapError('TextEditor disposed'); - } - this._textEditorsMap[id].setSelections(selections); - return TPromise.as(null); - } - - _trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise { - if (!this._textEditorsMap[id]) { - return TPromise.wrapError('TextEditor disposed'); - } - this._textEditorsMap[id].setDecorations(key, ranges); - return TPromise.as(null); - } - - _tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise { - if (!this._textEditorsMap[id]) { - return TPromise.wrapError('TextEditor disposed'); - } - this._textEditorsMap[id].revealRange(range, revealType); - } - - _trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise { - if (!this._textEditorsMap[id]) { - return TPromise.wrapError('TextEditor disposed'); - } - this._textEditorsMap[id].setConfiguration(options); - return TPromise.as(null); - } - - _tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], setEndOfLine:EndOfLine): TPromise { - if (!this._textEditorsMap[id]) { - return TPromise.wrapError('TextEditor disposed'); - } - return TPromise.as(this._textEditorsMap[id].applyEdits(modelVersionId, edits, setEndOfLine)); - } - - _registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void { - this._editorTracker.registerTextEditorDecorationType(key, options); - } - - _removeTextEditorDecorationType(key: string): void { - this._editorTracker.removeTextEditorDecorationType(key); - } -} diff --git a/src/vs/workbench/api/node/nativeExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts similarity index 72% rename from src/vs/workbench/api/node/nativeExtensionService.ts rename to src/vs/workbench/api/node/extHostExtensionService.ts index ef6fc086fa3..c2a2855386f 100644 --- a/src/vs/workbench/api/node/nativeExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -10,144 +10,16 @@ import * as paths from 'vs/base/common/paths'; import Severity from 'vs/base/common/severity'; import {TPromise} from 'vs/base/common/winjs.base'; import {AbstractExtensionService, ActivatedExtension} from 'vs/platform/extensions/common/abstractExtensionService'; -import {IMessage, IExtensionDescription, IExtensionsStatus} from 'vs/platform/extensions/common/extensions'; +import {IMessage, IExtensionDescription} from 'vs/platform/extensions/common/extensions'; import {ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; -import {IMessageService} from 'vs/platform/message/common/message'; import {ExtHostStorage} from 'vs/workbench/api/node/extHostStorage'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {MainContext, ExtHostContext} from './extHostProtocol'; +import {MainContext} from './extHostProtocol'; +import {MainProcessExtensionService} from './mainThreadExtensionService'; const hasOwnProperty = Object.hasOwnProperty; -/** - * Represents a failed extension in the ext host. - */ -class MainProcessFailedExtension extends ActivatedExtension { - constructor() { - super(true); - } -} - -/** - * Represents an extension that was successfully loaded or an - * empty extension in the ext host. - */ -class MainProcessSuccessExtension extends ActivatedExtension { - constructor() { - super(false); - } -} - -function messageWithSource(msg:IMessage): string { - return (msg.source ? '[' + msg.source + ']: ' : '') + msg.message; -} - -export class MainProcessExtensionService extends AbstractExtensionService { - - private _threadService: IThreadService; - private _messageService: IMessageService; - private _proxy: ExtHostExtensionService; - private _isDev: boolean; - private _extensionsStatus: { [id: string]: IExtensionsStatus }; - - /** - * This class is constructed manually because it is a service, so it doesn't use any ctor injection - */ - constructor( - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IThreadService threadService: IThreadService, - @IMessageService messageService: IMessageService - ) { - super(false); - let config = contextService.getConfiguration(); - this._isDev = !config.env.isBuilt || !!config.env.extensionDevelopmentPath; - - this._messageService = messageService; - this._threadService = threadService; - this._proxy = this._threadService.get(ExtHostContext.ExtHostExtensionService); - this._extensionsStatus = {}; - - ExtensionsRegistry.handleExtensionPoints((msg) => this._handleMessage(msg)); - } - - private _handleMessage(msg: IMessage) { - this._showMessage(msg.type, messageWithSource(msg)); - - if (!this._extensionsStatus[msg.source]) { - this._extensionsStatus[msg.source] = { messages: [] }; - } - this._extensionsStatus[msg.source].messages.push(msg); - } - - public $localShowMessage(severity: Severity, msg: string): void { - let messageShown = false; - if (severity === Severity.Error || severity === Severity.Warning) { - if (this._isDev) { - // Only show nasty intrusive messages if doing extension development. - this._messageService.show(severity, msg); - messageShown = true; - } - } - - if (!messageShown) { - switch (severity) { - case Severity.Error: - console.error(msg); - break; - case Severity.Warning: - console.warn(msg); - break; - default: - console.log(msg); - } - } - } - - // -- overwriting AbstractExtensionService - - public getExtensionsStatus(): { [id: string]: IExtensionsStatus } { - return this._extensionsStatus; - } - - protected _showMessage(severity: Severity, msg: string): void { - this._proxy.$localShowMessage(severity, msg); - this.$localShowMessage(severity, msg); - } - - protected _createFailedExtension(): ActivatedExtension { - return new MainProcessFailedExtension(); - } - - protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise { - - // redirect extension activation to the extension host - return this._proxy.$activateExtension(extensionDescription).then(_ => { - - // the extension host calls $onExtensionActivated, where we write to `_activatedExtensions` - return this._activatedExtensions[extensionDescription.id]; - }); - } - - // -- called by extension host - - public $onExtensionHostReady(extensionDescriptions: IExtensionDescription[], messages: IMessage[]): TPromise { - ExtensionsRegistry.registerExtensions(extensionDescriptions); - messages.forEach((entry) => this._handleMessage(entry)); - this._triggerOnReady(); - return; - } - - public $onExtensionActivated(extensionId: string): void { - this._activatedExtensions[extensionId] = new MainProcessSuccessExtension(); - } - - public $onExtensionActivationFailed(extensionId: string): void { - this._activatedExtensions[extensionId] = new MainProcessFailedExtension(); - } -} - /** * Represents the source code (module) of an extension. */ @@ -444,4 +316,4 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription } return event; -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostFileSystemEventService.ts b/src/vs/workbench/api/node/extHostFileSystemEventService.ts index b4d015ce004..45e60ec17bb 100644 --- a/src/vs/workbench/api/node/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/node/extHostFileSystemEventService.ts @@ -4,16 +4,11 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {FileChangesEvent, FileChangeType} from 'vs/platform/files/common/files'; -import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import Event, {Emitter} from 'vs/base/common/event'; import {Disposable} from './extHostTypes'; -import {IEventService} from 'vs/platform/event/common/event'; -import {RunOnceScheduler} from 'vs/base/common/async'; import URI from 'vs/base/common/uri'; import {match} from 'vs/base/common/glob'; import {Uri, FileSystemWatcher as _FileSystemWatcher} from 'vscode'; -import {ExtHostContext} from './extHostProtocol'; export interface FileSystemEvents { created: URI[]; @@ -113,40 +108,3 @@ export class ExtHostFileSystemEventService { this._emitter.fire(events); } } - -export class MainThreadFileSystemEventService { - - constructor( @IEventService eventService: IEventService, @IThreadService threadService: IThreadService) { - - const proxy = threadService.get(ExtHostContext.ExtHostFileSystemEventService); - const events: FileSystemEvents = { - created: [], - changed: [], - deleted: [] - }; - - const scheduler = new RunOnceScheduler(() => { - proxy._onFileEvent(events); - events.created.length = 0; - events.changed.length = 0; - events.deleted.length = 0; - }, 100); - - eventService.addListener2('files:fileChanges', (event: FileChangesEvent) => { - for (let change of event.changes) { - switch (change.type) { - case FileChangeType.ADDED: - events.created.push(change.resource); - break; - case FileChangeType.UPDATED: - events.changed.push(change.resource); - break; - case FileChangeType.DELETED: - events.deleted.push(change.resource); - break; - } - } - scheduler.schedule(); - }); - } -} diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index bd59ec40f68..143679b7e86 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -11,26 +11,25 @@ import {IThreadService} from 'vs/workbench/services/thread/common/threadService' import * as vscode from 'vscode'; import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import {Range, Disposable, SignatureHelp, CompletionList} from 'vs/workbench/api/node/extHostTypes'; -import {IReadOnlyModel, IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; +import {IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; import * as modes from 'vs/editor/common/modes'; -import {ExtHostModelService} from 'vs/workbench/api/node/extHostDocuments'; +import {ExtHostDocuments} from 'vs/workbench/api/node/extHostDocuments'; import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; -import {NavigateTypesSupportRegistry, INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search'; -import {asWinJsPromise, ShallowCancelThenPromise, wireCancellationToken} from 'vs/base/common/async'; -import {CancellationToken} from 'vs/base/common/cancellation'; -import {Position as EditorPosition} from 'vs/editor/common/core/position'; -import {Range as EditorRange} from 'vs/editor/common/core/range'; -import {MainContext, ExtHostContext} from './extHostProtocol'; +import {INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search'; +import {asWinJsPromise, ShallowCancelThenPromise} from 'vs/base/common/async'; +import {MainContext} from './extHostProtocol'; +import {MainThreadLanguageFeatures} from './mainThreadLanguageFeatures'; +import {regExpLeadsToEndlessLoop} from 'vs/base/common/strings'; // --- adapter class OutlineAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.DocumentSymbolProvider; - constructor(documents: ExtHostModelService, provider: vscode.DocumentSymbolProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.DocumentSymbolProvider) { this._documents = documents; this._provider = provider; } @@ -53,13 +52,13 @@ interface CachedCodeLens { class CodeLensAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _commands: ExtHostCommands; private _provider: vscode.CodeLensProvider; private _cache: { [uri: string]: { version: number; data: TPromise; } } = Object.create(null); - constructor(documents: ExtHostModelService, commands: ExtHostCommands, provider: vscode.CodeLensProvider) { + constructor(documents: ExtHostDocuments, commands: ExtHostCommands, provider: vscode.CodeLensProvider) { this._documents = documents; this._commands = commands; this._provider = provider; @@ -157,10 +156,10 @@ class CodeLensAdapter { class DefinitionAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.DefinitionProvider; - constructor(documents: ExtHostModelService, provider: vscode.DefinitionProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.DefinitionProvider) { this._documents = documents; this._provider = provider; } @@ -190,10 +189,10 @@ class DefinitionAdapter { class HoverAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.HoverProvider; - constructor(documents: ExtHostModelService, provider: vscode.HoverProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.HoverProvider) { this._documents = documents; this._provider = provider; } @@ -221,10 +220,10 @@ class HoverAdapter { class DocumentHighlightAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.DocumentHighlightProvider; - constructor(documents: ExtHostModelService, provider: vscode.DocumentHighlightProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.DocumentHighlightProvider) { this._documents = documents; this._provider = provider; } @@ -251,10 +250,10 @@ class DocumentHighlightAdapter { class ReferenceAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.ReferenceProvider; - constructor(documents: ExtHostModelService, provider: vscode.ReferenceProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.ReferenceProvider) { this._documents = documents; this._provider = provider; } @@ -280,14 +279,14 @@ class ReferenceAdapter { class QuickFixAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _commands: ExtHostCommands; private _diagnostics: ExtHostDiagnostics; private _provider: vscode.CodeActionProvider; private _cachedCommands: IDisposable[] = []; - constructor(documents: ExtHostModelService, commands: ExtHostCommands, diagnostics: ExtHostDiagnostics, provider: vscode.CodeActionProvider) { + constructor(documents: ExtHostDocuments, commands: ExtHostCommands, diagnostics: ExtHostDiagnostics, provider: vscode.CodeActionProvider) { this._documents = documents; this._commands = commands; this._diagnostics = diagnostics; @@ -328,10 +327,10 @@ class QuickFixAdapter { class DocumentFormattingAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.DocumentFormattingEditProvider; - constructor(documents: ExtHostModelService, provider: vscode.DocumentFormattingEditProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.DocumentFormattingEditProvider) { this._documents = documents; this._provider = provider; } @@ -350,10 +349,10 @@ class DocumentFormattingAdapter { class RangeFormattingAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.DocumentRangeFormattingEditProvider; - constructor(documents: ExtHostModelService, provider: vscode.DocumentRangeFormattingEditProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.DocumentRangeFormattingEditProvider) { this._documents = documents; this._provider = provider; } @@ -373,10 +372,10 @@ class RangeFormattingAdapter { class OnTypeFormattingAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.OnTypeFormattingEditProvider; - constructor(documents: ExtHostModelService, provider: vscode.OnTypeFormattingEditProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.OnTypeFormattingEditProvider) { this._documents = documents; this._provider = provider; } @@ -415,10 +414,10 @@ class NavigateTypeAdapter implements INavigateTypesSupport { class RenameAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.RenameProvider; - constructor(documents: ExtHostModelService, provider: vscode.RenameProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.RenameProvider) { this._documents = documents; this._provider = provider; } @@ -467,11 +466,11 @@ interface ISuggestion2 extends modes.ISuggestion { class SuggestAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.CompletionItemProvider; private _cache: { [key: string]: CompletionList } = Object.create(null); - constructor(documents: ExtHostModelService, provider: vscode.CompletionItemProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.CompletionItemProvider) { this._documents = documents; this._provider = provider; } @@ -569,10 +568,10 @@ class SuggestAdapter { class SignatureHelpAdapter { - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _provider: vscode.SignatureHelpProvider; - constructor(documents: ExtHostModelService, provider: vscode.SignatureHelpProvider) { + constructor(documents: ExtHostDocuments, provider: vscode.SignatureHelpProvider) { this._documents = documents; this._provider = provider; } @@ -600,14 +599,14 @@ export class ExtHostLanguageFeatures { private static _handlePool: number = 0; private _proxy: MainThreadLanguageFeatures; - private _documents: ExtHostModelService; + private _documents: ExtHostDocuments; private _commands: ExtHostCommands; private _diagnostics: ExtHostDiagnostics; private _adapter: { [handle: number]: Adapter } = Object.create(null); constructor( threadService: IThreadService, - documents: ExtHostModelService, + documents: ExtHostDocuments, commands: ExtHostCommands, diagnostics: ExtHostDiagnostics ) { @@ -821,188 +820,32 @@ export class ExtHostLanguageFeatures { $provideSignatureHelp(handle: number, resource: URI, position: IPosition): TPromise { return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(resource, position)); } -} - -export class MainThreadLanguageFeatures { - private _proxy: ExtHostLanguageFeatures; - private _registrations: { [handle: number]: IDisposable; } = Object.create(null); + // --- configuration - constructor( @IThreadService threadService: IThreadService) { - this._proxy = threadService.get(ExtHostContext.ExtHostLanguageFeatures); - } + setLanguageConfiguration(languageId:string, configuration: vscode.LanguageConfiguration): vscode.Disposable { + let {wordPattern} = configuration; - $unregister(handle: number): TPromise { - let registration = this._registrations[handle]; - if (registration) { - registration.dispose(); - delete this._registrations[handle]; + // check for a valid word pattern + if (wordPattern && regExpLeadsToEndlessLoop(wordPattern)) { + throw new Error(`Invalid language configuration: wordPattern '${wordPattern}' is not allowed to match the empty string.`); } - return undefined; - } - - // --- outline - - $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(selector, { - provideDocumentSymbols: (model:IReadOnlyModel, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDocumentSymbols(handle, model.uri)); - } - }); - return undefined; - } - - // --- code lens - - $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.CodeLensProviderRegistry.register(selector, { - provideCodeLenses: (model:IReadOnlyModel, token: CancellationToken): modes.ICodeLensSymbol[] | Thenable => { - return wireCancellationToken(token, this._proxy.$provideCodeLenses(handle, model.uri)); - }, - resolveCodeLens: (model:IReadOnlyModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): modes.ICodeLensSymbol | Thenable => { - return wireCancellationToken(token, this._proxy.$resolveCodeLens(handle, model.uri, codeLens)); - } - }); - return undefined; - } - - // --- declaration - - $registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.DefinitionProviderRegistry.register(selector, { - provideDefinition: (model, position, token): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)); - } - }); - return undefined; - } - // --- extra info - - $registerHoverProvider(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.HoverProviderRegistry.register(selector, { - provideHover: (model:IReadOnlyModel, position:EditorPosition, token:CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideHover(handle, model.uri, position)); - } - }); - return undefined; - } - - // --- occurrences - - $registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(selector, { - provideDocumentHighlights: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDocumentHighlights(handle, model.uri, position)); - } - }); - return undefined; - } - - // --- references - - $registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.ReferenceProviderRegistry.register(selector, { - provideReferences: (model:IReadOnlyModel, position:EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideReferences(handle, model.uri, position, context)); - } - }); - return undefined; - } - - // --- quick fix - - $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, { - provideCodeActions: (model:IReadOnlyModel, range:EditorRange, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range)); - } - }); - return undefined; - } - - // --- formatting - - $registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(selector, { - provideDocumentFormattingEdits: (model: IReadOnlyModel, options: modes.FormattingOptions, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options)); - } - }); - return undefined; - } - - $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(selector, { - provideDocumentRangeFormattingEdits: (model: IReadOnlyModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options)); - } - }); - return undefined; - } - - $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): TPromise { - this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(selector, { - - autoFormatTriggerCharacters, - - provideOnTypeFormattingEdits: (model: IReadOnlyModel, position: EditorPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideOnTypeFormattingEdits(handle, model.uri, position, ch, options)); - } - }); - return undefined; - } - - // --- navigate type - - $registerNavigateTypeSupport(handle: number): TPromise { - this._registrations[handle] = NavigateTypesSupportRegistry.register({ - getNavigateToItems: (search: string): TPromise => { - return this._proxy.$getNavigateToItems(handle, search); - } - }); - return undefined; - } - - // --- rename - - $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - this._registrations[handle] = modes.RenameProviderRegistry.register(selector, { - provideRenameEdits: (model:IReadOnlyModel, position:EditorPosition, newName: string, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)); - } - }); - return undefined; - } - - // --- suggest - - $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[]): TPromise { - this._registrations[handle] = modes.SuggestRegistry.register(selector, { - triggerCharacters: triggerCharacters, - shouldAutotriggerSuggest: true, - provideCompletionItems: (model:IReadOnlyModel, position:EditorPosition, token:CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)); - }, - resolveCompletionItem: (model:IReadOnlyModel, position:EditorPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion)); - } - }); - return undefined; - } - - // --- parameter hints - - $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise { - this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(selector, { - - signatureHelpTriggerCharacters: triggerCharacter, + // word definition + if (wordPattern) { + this._documents.setWordDefinitionFor(languageId, wordPattern); + } else { + this._documents.setWordDefinitionFor(languageId, null); + } - provideSignatureHelp: (model:IReadOnlyModel, position:EditorPosition, token:CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideSignatureHelp(handle, model.uri, position)); - } + // backward compatibility, migrate deprecated setting + if (configuration.__characterPairSupport && !configuration.autoClosingPairs) { + configuration.autoClosingPairs = configuration.__characterPairSupport.autoClosingPairs; + delete configuration.__characterPairSupport; + } - }); - return undefined; + const handle = this._nextHandle(); + this._proxy.$setLanguageConfiguration(handle, languageId, configuration); + return this._createDisposable(handle); } } diff --git a/src/vs/workbench/api/node/extHostLanguages.ts b/src/vs/workbench/api/node/extHostLanguages.ts index c4572e80261..aaa6d580b40 100644 --- a/src/vs/workbench/api/node/extHostLanguages.ts +++ b/src/vs/workbench/api/node/extHostLanguages.ts @@ -6,8 +6,8 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IModeService} from 'vs/editor/common/services/modeService'; import {MainContext} from './extHostProtocol'; +import {MainThreadLanguages} from './mainThreadLanguages'; export class ExtHostLanguages { @@ -24,17 +24,3 @@ export class ExtHostLanguages { } } -export class MainThreadLanguages { - - private _modeService: IModeService; - - constructor( - @IModeService modeService: IModeService - ) { - this._modeService = modeService; - } - - _getLanguages(): TPromise { - return TPromise.as(this._modeService.getRegisteredModes()); - } -} diff --git a/src/vs/workbench/api/node/extHostMessageService.ts b/src/vs/workbench/api/node/extHostMessageService.ts index f665d23655e..66ea27f1929 100644 --- a/src/vs/workbench/api/node/extHostMessageService.ts +++ b/src/vs/workbench/api/node/extHostMessageService.ts @@ -4,14 +4,11 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import nls = require('vs/nls'); import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IMessageService} from 'vs/platform/message/common/message'; import Severity from 'vs/base/common/severity'; -import {Action} from 'vs/base/common/actions'; -import {TPromise as Promise} from 'vs/base/common/winjs.base'; import vscode = require('vscode'); import {MainContext} from './extHostProtocol'; +import {MainThreadMessageService} from './mainThreadMessageService'; export class ExtHostMessageService { @@ -44,51 +41,3 @@ export class ExtHostMessageService { }); } } - -export class MainThreadMessageService { - - private _messageService: IMessageService; - - constructor(@IMessageService messageService:IMessageService) { - this._messageService = messageService; - } - - $showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Thenable { - - return new Promise(resolve => { - - let messageHide: Function; - let actions: MessageItemAction[] = []; - let hasCloseAffordance = false; - - class MessageItemAction extends Action { - constructor(id: string, label: string, handle: number) { - super(id, label, undefined, true, () => { - resolve(handle); - messageHide(); // triggers dispose! make sure promise is already resolved - return undefined; - }); - } - dispose(): void { - resolve(undefined); - } - } - - commands.forEach(command => { - if (command.isCloseAffordance === true) { - hasCloseAffordance = true; - } - actions.push(new MessageItemAction('_extension_message_handle_' + command.handle, command.title, command.handle)); - }); - - if (!hasCloseAffordance) { - actions.unshift(new MessageItemAction('__close', nls.localize('close', "Close"), undefined)); - } - - messageHide = this._messageService.show(severity, { - message, - actions - }); - }); - } -} diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index e4ef85e9ef0..1037c520e24 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -4,13 +4,9 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {TPromise} from 'vs/base/common/winjs.base'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {Registry} from 'vs/platform/platform'; -import {IOutputService, IOutputChannel, OUTPUT_PANEL_ID, Extensions, IOutputChannelRegistry} from 'vs/workbench/parts/output/common/output'; -import {IPartService} from 'vs/workbench/services/part/common/partService'; -import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; import {MainContext} from './extHostProtocol'; +import {MainThreadOutputService} from './mainThreadOutputService'; export class ExtHostOutputChannel implements vscode.OutputChannel { @@ -81,51 +77,3 @@ export class ExtHostOutputService { } } } - -export class MainThreadOutputService { - - private _outputService: IOutputService; - private _partService: IPartService; - private _panelService: IPanelService; - - constructor(@IOutputService outputService: IOutputService, - @IPartService partService: IPartService, - @IPanelService panelService: IPanelService - ) { - this._outputService = outputService; - this._partService = partService; - this._panelService = panelService; - } - - public append(channelId: string, label: string, value: string): TPromise { - this._getChannel(channelId, label).append(value); - return undefined; - } - - public clear(channelId: string, label: string): TPromise { - this._getChannel(channelId, label).clear(); - return undefined; - } - - public reveal(channelId: string, label: string, preserveFocus: boolean): TPromise { - this._getChannel(channelId, label).show(preserveFocus); - return undefined; - } - - private _getChannel(channelId: string, label: string): IOutputChannel { - if (Registry.as(Extensions.OutputChannels).getChannels().every(channel => channel.id !== channelId)) { - Registry.as(Extensions.OutputChannels).registerChannel(channelId, label); - } - - return this._outputService.getChannel(channelId); - } - - public close(channelId: string): TPromise { - const panel = this._panelService.getActivePanel(); - if (panel && panel.getId() === OUTPUT_PANEL_ID && channelId === this._outputService.getActiveChannel().id ) { - this._partService.setPanelHidden(true); - } - - return undefined; - } -} diff --git a/src/vs/workbench/api/node/extHostProtocol.ts b/src/vs/workbench/api/node/extHostProtocol.ts index af0f1d7a64f..86845e03c85 100644 --- a/src/vs/workbench/api/node/extHostProtocol.ts +++ b/src/vs/workbench/api/node/extHostProtocol.ts @@ -9,32 +9,43 @@ import { createExtHostContextProxyIdentifier as createExtId, ProxyIdentifier, IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {MainProcessVSCodeAPIHelper} from './extHost.api.impl'; -import {ExtHostCommands, MainThreadCommands} from './extHostCommands'; -import {ExtHostConfiguration, MainThreadConfiguration} from './extHostConfiguration'; -import {ExtHostDiagnostics, MainThreadDiagnostics} from './extHostDiagnostics'; -import {ExtHostModelService, MainThreadDocuments} from './extHostDocuments'; -import {ExtHostEditors, MainThreadEditors} from './extHostEditors'; +// --- main thread addressable +import {MainThreadCommands} from './mainThreadCommands'; +import {MainThreadConfiguration} from './mainThreadConfiguration'; +import {MainThreadDiagnostics} from './mainThreadDiagnostics'; +import {MainThreadDocuments} from './mainThreadDocuments'; +import {MainThreadEditors} from './mainThreadEditors'; +import {MainThreadErrors} from './mainThreadErrors'; +import {MainThreadLanguageFeatures} from './mainThreadLanguageFeatures'; +import {MainThreadLanguages} from './mainThreadLanguages'; +import {MainThreadMessageService} from './mainThreadMessageService'; +import {MainThreadOutputService} from './mainThreadOutputService'; +import {MainThreadQuickOpen} from './mainThreadQuickOpen'; +import {MainThreadStatusBar} from './mainThreadStatusBar'; +import {MainThreadStorage} from './mainThreadStorage'; +import {MainThreadTelemetry} from './mainThreadTelemetry'; +import {MainThreadWorkspace} from './mainThreadWorkspace'; +import {MainProcessExtensionService} from './mainThreadExtensionService'; + +// --- ext host addressable +import {ExtHostCommands} from './extHostCommands'; +import {ExtHostConfiguration} from './extHostConfiguration'; +import {ExtHostDiagnostics} from './extHostDiagnostics'; +import {ExtHostDocuments} from './extHostDocuments'; +import {ExtHostEditors} from './extHostEditors'; import {ExtHostFileSystemEventService} from './extHostFileSystemEventService'; -import {ExtHostLanguageFeatures, MainThreadLanguageFeatures} from './extHostLanguageFeatures'; -import {MainThreadLanguages} from './extHostLanguages'; -import {MainThreadMessageService} from './extHostMessageService'; -import {MainThreadOutputService} from './extHostOutputService'; -import {ExtHostQuickOpen, MainThreadQuickOpen} from './extHostQuickOpen'; -import {MainThreadStatusBar} from './extHostStatusBar'; -import {MainThreadStorage} from './extHostStorage'; -import {MainThreadTelemetry} from './extHostTelemetry'; -import {MainThreadWorkspace} from './extHostWorkspace'; -import {ExtHostExtensionService, MainProcessExtensionService} from './nativeExtensionService'; +import {ExtHostLanguageFeatures} from './extHostLanguageFeatures'; +import {ExtHostQuickOpen} from './extHostQuickOpen'; +import {ExtHostExtensionService} from './extHostExtensionService'; let mainCounter = 0; export const MainContext = { - MainProcessVSCodeAPIHelper: createMainId(++mainCounter), MainThreadCommands: createMainId(++mainCounter), MainThreadConfiguration: createMainId(++mainCounter), MainThreadDiagnostics: createMainId(++mainCounter), MainThreadDocuments: createMainId(++mainCounter), MainThreadEditors: createMainId(++mainCounter), + MainThreadErrors: createMainId(++mainCounter), MainThreadLanguageFeatures: createMainId(++mainCounter), MainThreadLanguages: createMainId(++mainCounter), MainThreadMessageService: createMainId(++mainCounter), @@ -52,7 +63,7 @@ export const ExtHostContext = { ExtHostCommands: createExtId(++extCounter), ExtHostConfiguration: createExtId(++extCounter), ExtHostDiagnostics: createExtId(++extCounter), - ExtHostModelService: createExtId(++extCounter), + ExtHostDocuments: createExtId(++extCounter), ExtHostEditors: createExtId(++extCounter), ExtHostFileSystemEventService: createExtId(++extCounter), ExtHostLanguageFeatures: createExtId(++extCounter), diff --git a/src/vs/workbench/api/node/extHostQuickOpen.ts b/src/vs/workbench/api/node/extHostQuickOpen.ts index af032170866..bb2c1c80a79 100644 --- a/src/vs/workbench/api/node/extHostQuickOpen.ts +++ b/src/vs/workbench/api/node/extHostQuickOpen.ts @@ -6,9 +6,10 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IQuickOpenService, IPickOpenEntry, IPickOptions, IInputOptions} from 'vs/workbench/services/quickopen/common/quickOpenService'; +import {IPickOpenEntry} from 'vs/workbench/services/quickopen/common/quickOpenService'; import {QuickPickOptions, QuickPickItem, InputBoxOptions} from 'vscode'; -import {MainContext, ExtHostContext} from './extHostProtocol'; +import {MainContext} from './extHostProtocol'; +import {MainThreadQuickOpen} from './mainThreadQuickOpen'; export interface MyQuickPickItems extends IPickOpenEntry { handle: number; @@ -111,83 +112,3 @@ export class ExtHostQuickOpen { } } } - -export class MainThreadQuickOpen { - - private _proxy: ExtHostQuickOpen; - private _quickOpenService: IQuickOpenService; - private _doSetItems: (items: MyQuickPickItems[]) => any; - private _doSetError: (error: Error) => any; - private _contents: TPromise; - private _token: number = 0; - - constructor( @IThreadService threadService: IThreadService, @IQuickOpenService quickOpenService: IQuickOpenService) { - this._proxy = threadService.get(ExtHostContext.ExtHostQuickOpen); - this._quickOpenService = quickOpenService; - } - - $show(options: IPickOptions): Thenable { - - const myToken = ++this._token; - - this._contents = new TPromise((c, e) => { - this._doSetItems = (items) => { - if (myToken === this._token) { - c(items); - } - }; - - this._doSetError = (error) => { - if (myToken === this._token) { - e(error); - } - }; - }); - - return this._quickOpenService.pick(this._contents, options).then(item => { - if (item) { - return item.handle; - } - }, undefined, progress => { - if (progress) { - this._proxy.$onItemSelected((progress).handle); - } - }); - } - - $setItems(items: MyQuickPickItems[]): Thenable { - if (this._doSetItems) { - this._doSetItems(items); - return; - } - } - - $setError(error: Error): Thenable { - if (this._doSetError) { - this._doSetError(error); - return; - } - } - - // ---- input - - $input(options: InputBoxOptions, validateInput: boolean): Thenable { - - const inputOptions: IInputOptions = Object.create(null); - - if (options) { - inputOptions.password = options.password; - inputOptions.placeHolder = options.placeHolder; - inputOptions.prompt = options.prompt; - inputOptions.value = options.value; - } - - if (validateInput) { - inputOptions.validateInput = (value) => { - return this._proxy.$validateInput(value); - }; - } - - return this._quickOpenService.input(inputOptions); - } -} diff --git a/src/vs/workbench/api/node/extHostStatusBar.ts b/src/vs/workbench/api/node/extHostStatusBar.ts index eb7ec9bb3dc..0e4a0b05c38 100644 --- a/src/vs/workbench/api/node/extHostStatusBar.ts +++ b/src/vs/workbench/api/node/extHostStatusBar.ts @@ -5,11 +5,11 @@ 'use strict'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment} from 'vs/platform/statusbar/common/statusbar'; -import {IDisposable} from 'vs/base/common/lifecycle'; +import {StatusbarAlignment as MainThreadStatusBarAlignment} from 'vs/platform/statusbar/common/statusbar'; import {StatusBarAlignment as ExtHostStatusBarAlignment, Disposable} from './extHostTypes'; import {StatusBarItem, StatusBarAlignment} from 'vscode'; import {MainContext} from './extHostProtocol'; +import {MainThreadStatusBar} from './mainThreadStatusBar'; export class ExtHostStatusBarEntry implements StatusBarItem { private static ID_GEN = 0; @@ -188,32 +188,3 @@ export class ExtHostStatusBar { }); } } - -export class MainThreadStatusBar { - private mapIdToDisposable: { [id: number]: IDisposable }; - - constructor( - @IStatusbarService private statusbarService: IStatusbarService - ) { - this.mapIdToDisposable = Object.create(null); - } - - setEntry(id: number, text: string, tooltip: string, command: string, color: string, alignment: MainThreadStatusBarAlignment, priority: number): void { - - // Dispose any old - this.dispose(id); - - // Add new - let disposeable = this.statusbarService.addEntry({ text, tooltip, command, color }, alignment, priority); - this.mapIdToDisposable[id] = disposeable; - } - - dispose(id: number) { - let disposeable = this.mapIdToDisposable[id]; - if (disposeable) { - disposeable.dispose(); - } - - delete this.mapIdToDisposable[id]; - } -} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostStorage.ts b/src/vs/workbench/api/node/extHostStorage.ts index 910e534283b..05ed11b6b7e 100644 --- a/src/vs/workbench/api/node/extHostStorage.ts +++ b/src/vs/workbench/api/node/extHostStorage.ts @@ -6,41 +6,8 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; import {MainContext} from './extHostProtocol'; - -export class MainThreadStorage { - - private _storageService: IStorageService; - - constructor( @IStorageService storageService: IStorageService) { - this._storageService = storageService; - } - - getValue(shared: boolean, key: string): TPromise { - let jsonValue = this._storageService.get(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); - if (!jsonValue) { - return TPromise.as(undefined); - } - let value: T; - try { - value = JSON.parse(jsonValue); - return TPromise.as(value); - } catch (err) { - return TPromise.wrapError(err); - } - } - - setValue(shared: boolean, key: string, value: any): TPromise { - let jsonValue: any; - try { - jsonValue = JSON.stringify(value); - this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); - } catch (err) { - return TPromise.wrapError(err); - } - } -} +import {MainThreadStorage} from './mainThreadStorage'; export class ExtHostStorage { diff --git a/src/vs/workbench/api/node/extHostTelemetry.ts b/src/vs/workbench/api/node/extHostTelemetry.ts index a29720478a1..1cc345c1117 100644 --- a/src/vs/workbench/api/node/extHostTelemetry.ts +++ b/src/vs/workbench/api/node/extHostTelemetry.ts @@ -9,26 +9,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {ITelemetryService, ITelemetryInfo} from 'vs/platform/telemetry/common/telemetry'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import {MainContext} from './extHostProtocol'; - -/** - * Helper always instantiated in the main process to receive telemetry events from remote telemetry services - */ -export class MainThreadTelemetry { - - private _telemetryService: ITelemetryService; - - constructor( @ITelemetryService telemetryService: ITelemetryService) { - this._telemetryService = telemetryService; - } - - public $publicLog(eventName: string, data?: any): void { - this._telemetryService.publicLog(eventName, data); - } - - public $getTelemetryInfo(): TPromise { - return this._telemetryService.getTelemetryInfo(); - } -} +import {MainThreadTelemetry} from './mainThreadTelemetry'; export class RemoteTelemetryService implements ITelemetryService { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index d375205386a..e06cf82c77a 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -764,3 +764,8 @@ export enum EndOfLine { CRLF = 2 } +export enum TextEditorRevealType { + Default = 0, + InCenter = 1, + InCenterIfOutsideViewport = 2 +} diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 7b53e1700f3..c7846877fe7 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -4,20 +4,14 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {isPromiseCanceledError} from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; -import {ISearchService, QueryType} from 'vs/platform/search/common/search'; -import {IWorkspaceContextService, IWorkspace} from 'vs/platform/workspace/common/workspace'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IEventService} from 'vs/platform/event/common/event'; -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {ITextFileService} from 'vs/workbench/parts/files/common/files'; -import {ICommonCodeEditor} from 'vs/editor/common/editorCommon'; -import {bulkEdit, IResourceEdit} from 'vs/editor/common/services/bulkEdit'; +import {IResourceEdit} from 'vs/editor/common/services/bulkEdit'; import {TPromise} from 'vs/base/common/winjs.base'; import {fromRange} from 'vs/workbench/api/node/extHostTypeConverters'; import {Uri, CancellationToken} from 'vscode'; import {MainContext} from './extHostProtocol'; +import {MainThreadWorkspace} from './mainThreadWorkspace'; export class ExtHostWorkspace { @@ -85,84 +79,3 @@ export class ExtHostWorkspace { return this._proxy.$applyWorkspaceEdit(resourceEdits); } } - -export class MainThreadWorkspace { - - private _activeSearches: { [id: number]: TPromise } = Object.create(null); - private _searchService: ISearchService; - private _workspace: IWorkspace; - private _textFileService: ITextFileService; - private _editorService:IWorkbenchEditorService; - private _eventService:IEventService; - - constructor( @ISearchService searchService: ISearchService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @ITextFileService textFileService, - @IWorkbenchEditorService editorService, - @IEventService eventService) { - - this._searchService = searchService; - this._workspace = contextService.getWorkspace(); - this._textFileService = textFileService; - this._editorService = editorService; - this._eventService = eventService; - } - - $startSearch(include: string, exclude: string, maxResults: number, requestId: number): Thenable { - - if (!this._workspace) { - return; - } - - const search = this._searchService.search({ - folderResources: [this._workspace.resource], - type: QueryType.File, - maxResults, - includePattern: { [include]: true }, - excludePattern: { [exclude]: true }, - }).then(result => { - return result.results.map(m => m.resource); - }, err => { - if (!isPromiseCanceledError(err)) { - return TPromise.wrapError(err); - } - }); - - this._activeSearches[requestId] = search; - const onDone = () => delete this._activeSearches[requestId]; - search.done(onDone, onDone); - - return search; - } - - $cancelSearch(requestId: number): Thenable { - const search = this._activeSearches[requestId]; - if (search) { - delete this._activeSearches[requestId]; - search.cancel(); - return TPromise.as(true); - } - } - - $saveAll(includeUntitled?: boolean): Thenable { - return this._textFileService.saveAll(includeUntitled).then(result => { - return result.results.every(each => each.success === true); - }); - } - - $applyWorkspaceEdit(edits: IResourceEdit[]): TPromise { - - let codeEditor: ICommonCodeEditor; - let editor = this._editorService.getActiveEditor(); - if (editor) { - let candidate = editor.getControl(); - if (typeof candidate.getEditorType === 'function') { - // enough proof - codeEditor = candidate; - } - } - - return bulkEdit(this._eventService, this._editorService, codeEditor, edits) - .then(() => true); - } -} \ No newline at end of file diff --git a/src/vs/workbench/api/node/mainThreadCommands.ts b/src/vs/workbench/api/node/mainThreadCommands.ts new file mode 100644 index 00000000000..2775bc19ef1 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadCommands.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; +import {IKeybindingService, ICommandHandlerDescription} from 'vs/platform/keybinding/common/keybindingService'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {ExtHostContext} from './extHostProtocol'; +import {ExtHostCommands} from './extHostCommands'; + +export class MainThreadCommands { + + private _threadService: IThreadService; + private _keybindingService: IKeybindingService; + private _proxy: ExtHostCommands; + + constructor( + @IThreadService threadService: IThreadService, + @IKeybindingService keybindingService: IKeybindingService + ) { + this._threadService = threadService; + this._keybindingService = keybindingService; + this._proxy = this._threadService.get(ExtHostContext.ExtHostCommands); + } + + $registerCommand(id: string): TPromise { + + KeybindingsRegistry.registerCommandDesc({ + id, + handler: (serviceAccessor, ...args: any[]) => { + return this._proxy.$executeContributedCommand(id, ...args); + }, + weight: undefined, + when: undefined, + win: undefined, + mac: undefined, + linux: undefined, + primary: undefined, + secondary: undefined + }); + + return undefined; + } + + $executeCommand(id: string, args: any[]): Thenable { + return this._keybindingService.executeCommand(id, ...args); + } + + $getCommands(): Thenable { + return TPromise.as(Object.keys(KeybindingsRegistry.getCommands())); + } +} + +// --- command doc + +KeybindingsRegistry.registerCommandDesc({ + id: '_generateCommandsDocumentation', + handler: function(accessor) { + return accessor.get(IThreadService).get(ExtHostContext.ExtHostCommands).$getContributedCommandHandlerDescriptions().then(result => { + + // add local commands + const commands = KeybindingsRegistry.getCommands(); + for (let id in commands) { + let {description} = commands[id]; + if (description) { + result[id] = description; + } + } + + // print all as markdown + const all: string[] = []; + for (let id in result) { + all.push('`' + id + '` - ' + _generateMarkdown(result[id])); + } + console.log(all.join('\n')); + }); + }, + when: undefined, + weight: KeybindingsRegistry.WEIGHT.builtinExtension(0), + primary: undefined +}); + +function _generateMarkdown(description: string | ICommandHandlerDescription): string { + if (typeof description === 'string') { + return description; + } else { + let parts = [description.description]; + parts.push('\n\n'); + if (description.args) { + for (let arg of description.args) { + parts.push(`* _${arg.name}_ ${arg.description || ''}\n`); + } + } + if (description.returns) { + parts.push(`* _(returns)_ ${description.returns}`); + } + parts.push('\n\n'); + return parts.join(''); + } +} diff --git a/src/vs/workbench/api/node/mainThreadConfiguration.ts b/src/vs/workbench/api/node/mainThreadConfiguration.ts new file mode 100644 index 00000000000..6bbf391fa99 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadConfiguration.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {IDisposable, dispose} from 'vs/base/common/lifecycle'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; +import {ExtHostContext} from './extHostProtocol'; +import {ExtHostConfiguration} from './extHostConfiguration'; + +export class MainThreadConfiguration { + + private _configurationService: IConfigurationService; + private _toDispose: IDisposable; + private _proxy: ExtHostConfiguration; + + constructor(@IConfigurationService configurationService: IConfigurationService, + @IThreadService threadService: IThreadService) { + + this._configurationService = configurationService; + this._proxy = threadService.get(ExtHostContext.ExtHostConfiguration); + + this._toDispose = this._configurationService.onDidUpdateConfiguration(event => this._proxy.$acceptConfigurationChanged(event.config)); + this._proxy.$acceptConfigurationChanged(this._configurationService.getConfiguration()); + } + + public dispose(): void { + this._toDispose = dispose(this._toDispose); + } +} diff --git a/src/vs/workbench/api/node/mainThreadDiagnostics.ts b/src/vs/workbench/api/node/mainThreadDiagnostics.ts new file mode 100644 index 00000000000..beb0a0f2b24 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadDiagnostics.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {IMarkerService, IMarkerData} from 'vs/platform/markers/common/markers'; +import URI from 'vs/base/common/uri'; +import {TPromise} from 'vs/base/common/winjs.base'; + +export class MainThreadDiagnostics { + + private _markerService: IMarkerService; + + constructor(@IMarkerService markerService: IMarkerService) { + this._markerService = markerService; + } + + $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise { + for (let entry of entries) { + let [uri, markers] = entry; + this._markerService.changeOne(owner, uri, markers); + } + return undefined; + } + + $clear(owner: string): TPromise { + this._markerService.changeAll(owner, undefined); + return undefined; + } +} diff --git a/src/vs/workbench/api/node/mainThreadDocuments.ts b/src/vs/workbench/api/node/mainThreadDocuments.ts new file mode 100644 index 00000000000..bbe839b1f6a --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadDocuments.ts @@ -0,0 +1,262 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {toErrorMessage, onUnexpectedError} from 'vs/base/common/errors'; +import {EmitterEvent} from 'vs/base/common/eventEmitter'; +import {IModelService} from 'vs/editor/common/services/modelService'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import URI from 'vs/base/common/uri'; +import {IDisposable, dispose} from 'vs/base/common/lifecycle'; +import {IEventService} from 'vs/platform/event/common/event'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {EventType as FileEventType, TextFileChangeEvent, ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {IFileService} from 'vs/platform/files/common/files'; +import {IModeService} from 'vs/editor/common/services/modeService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import {ResourceEditorInput} from 'vs/workbench/common/editor/resourceEditorInput'; +import {ExtHostContext} from './extHostProtocol'; +import {ExtHostDocuments} from './extHostDocuments'; + +export class MainThreadDocuments { + private _modelService: IModelService; + private _modeService: IModeService; + private _textFileService: ITextFileService; + private _editorService: IWorkbenchEditorService; + private _fileService: IFileService; + private _untitledEditorService: IUntitledEditorService; + private _toDispose: IDisposable[]; + private _modelToDisposeMap: { [modelUrl: string]: IDisposable; }; + private _proxy: ExtHostDocuments; + private _modelIsSynced: { [modelId: string]: boolean; }; + private _resourceContentProvider: { [handle: number]: IDisposable }; + private _virtualDocumentSet: { [resource: string]: boolean }; + + constructor( + @IThreadService threadService: IThreadService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, + @IEventService eventService: IEventService, + @ITextFileService textFileService: ITextFileService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IFileService fileService: IFileService, + @IUntitledEditorService untitledEditorService: IUntitledEditorService + ) { + this._modelService = modelService; + this._modeService = modeService; + this._textFileService = textFileService; + this._editorService = editorService; + this._fileService = fileService; + this._untitledEditorService = untitledEditorService; + this._proxy = threadService.get(ExtHostContext.ExtHostDocuments); + this._modelIsSynced = {}; + + this._toDispose = []; + modelService.onModelAdded(this._onModelAdded, this, this._toDispose); + modelService.onModelRemoved(this._onModelRemoved, this, this._toDispose); + modelService.onModelModeChanged(this._onModelModeChanged, this, this._toDispose); + + this._toDispose.push(eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => { + if (this._shouldHandleFileEvent(e)) { + this._proxy._acceptModelSaved(e.resource.toString()); + } + })); + this._toDispose.push(eventService.addListener2(FileEventType.FILE_REVERTED, (e: TextFileChangeEvent) => { + if (this._shouldHandleFileEvent(e)) { + this._proxy._acceptModelReverted(e.resource.toString()); + } + })); + this._toDispose.push(eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => { + if (this._shouldHandleFileEvent(e)) { + this._proxy._acceptModelDirty(e.resource.toString()); + } + })); + + const handle = setInterval(() => this._runDocumentCleanup(), 1000 * 60 * 3); + this._toDispose.push({ dispose() { clearInterval(handle); } }); + + this._modelToDisposeMap = Object.create(null); + this._resourceContentProvider = Object.create(null); + this._virtualDocumentSet = Object.create(null); + } + + public dispose(): void { + Object.keys(this._modelToDisposeMap).forEach((modelUrl) => { + this._modelToDisposeMap[modelUrl].dispose(); + }); + this._modelToDisposeMap = Object.create(null); + this._toDispose = dispose(this._toDispose); + } + + private _shouldHandleFileEvent(e: TextFileChangeEvent): boolean { + const model = this._modelService.getModel(e.resource); + return model && !model.isTooLargeForHavingARichMode(); + } + + private _onModelAdded(model: editorCommon.IModel): void { + // Same filter as in mainThreadEditorsTracker + if (model.isTooLargeForHavingARichMode()) { + // don't synchronize too large models + return null; + } + 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(), + value: model.toRawText(), + modeId: model.getMode().getId(), + isDirty: this._textFileService.isDirty(modelUrl) + }); + } + + private _onModelModeChanged(event: { model: editorCommon.IModel; oldModeId: string; }): void { + let {model, oldModeId} = event; + let modelUrl = model.uri; + if (!this._modelIsSynced[modelUrl.toString()]) { + return; + } + this._proxy._acceptModelModeChanged(model.uri.toString(), oldModeId, model.getMode().getId()); + } + + private _onModelRemoved(model: editorCommon.IModel): void { + let modelUrl = model.uri; + if (!this._modelIsSynced[modelUrl.toString()]) { + return; + } + delete this._modelIsSynced[modelUrl.toString()]; + this._modelToDisposeMap[modelUrl.toString()].dispose(); + delete this._modelToDisposeMap[modelUrl.toString()]; + this._proxy._acceptModelRemoved(modelUrl.toString()); + } + + private _onModelEvents(modelUrl: URI, events: EmitterEvent[]): void { + let changedEvents: editorCommon.IModelContentChangedEvent2[] = []; + for (let i = 0, len = events.length; i < len; i++) { + let e = events[i]; + switch (e.getType()) { + case editorCommon.EventType.ModelContentChanged2: + changedEvents.push(e.getData()); + break; + } + } + if (changedEvents.length > 0) { + this._proxy._acceptModelChanged(modelUrl.toString(), changedEvents); + } + } + + // --- from extension host process + + _trySaveDocument(uri: URI): TPromise { + return this._textFileService.save(uri); + } + + _tryOpenDocument(uri: URI): TPromise { + + if (!uri.scheme || !(uri.fsPath || uri.authority)) { + return TPromise.wrapError(`Invalid uri. Scheme and authority or path must be set.`); + } + + let promise: TPromise; + switch (uri.scheme) { + case 'untitled': + promise = this._handleUnititledScheme(uri); + break; + case 'file': + default: + promise = this._handleAsResourceInput(uri); + break; + } + + return promise.then(success => { + if (!success) { + return TPromise.wrapError('cannot open ' + uri.toString()); + } + }, err => { + return TPromise.wrapError('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err)); + }); + } + + private _handleAsResourceInput(uri: URI): TPromise { + return this._editorService.resolveEditorModel({ resource: uri }).then(model => { + return !!model; + }); + } + + private _handleUnititledScheme(uri: URI): TPromise { + let asFileUri = URI.file(uri.fsPath); + return this._fileService.resolveFile(asFileUri).then(stats => { + // don't create a new file ontop of an existing file + return TPromise.wrapError('file already exists on disk'); + }, err => { + let input = this._untitledEditorService.createOrGet(asFileUri); // using file-uri makes it show in 'Working Files' section + return input.resolve(true).then(model => { + if (input.getResource().toString() !== uri.toString()) { + throw new Error(`expected URI ${uri.toString() } BUT GOT ${input.getResource().toString() }`); + } + if (!this._modelIsSynced[uri.toString()]) { + throw new Error(`expected URI ${uri.toString()} to have come to LIFE`); + } + return this._proxy._acceptModelDirty(uri.toString()); // mark as dirty + }).then(() => { + return true; + }); + }); + } + + // --- virtual document logic + + $registerTextContentProvider(handle:number, scheme: string): void { + this._resourceContentProvider[handle] = ResourceEditorInput.registerResourceContentProvider(scheme, { + provideTextContent: (uri: URI): TPromise => { + return this._proxy.$provideTextDocumentContent(handle, uri).then(value => { + if (typeof value === 'string') { + this._virtualDocumentSet[uri.toString()] = true; + const firstLineText = value.substr(0, 1 + value.search(/\r?\n/)); + const mode = this._modeService.getOrCreateModeByFilenameOrFirstLine(uri.fsPath, firstLineText); + return this._modelService.createModel(value, mode, uri); + } + }); + } + }); + } + + $unregisterTextContentProvider(handle: number): void { + const registration = this._resourceContentProvider[handle]; + if (registration) { + registration.dispose(); + delete this._resourceContentProvider[handle]; + } + } + + $onVirtualDocumentChange(uri: URI, value: string): void { + const model = this._modelService.getModel(uri); + if (model) { + model.setValue(value); + } + } + + private _runDocumentCleanup(): void { + + const toBeDisposed: URI[] = []; + + TPromise.join(Object.keys(this._virtualDocumentSet).map(key => { + let resource = URI.parse(key); + return this._editorService.createInput({ resource }).then(input => { + if (!this._editorService.isVisible(input, true)) { + toBeDisposed.push(resource); + } + }); + })).then(() => { + for (let resource of toBeDisposed) { + this._modelService.destroyModel(resource); + delete this._virtualDocumentSet[resource.toString()]; + } + }, onUnexpectedError); + } +} diff --git a/src/vs/workbench/api/node/mainThreadEditors.ts b/src/vs/workbench/api/node/mainThreadEditors.ts index 32f317a4832..0c721adf95e 100644 --- a/src/vs/workbench/api/node/mainThreadEditors.ts +++ b/src/vs/workbench/api/node/mainThreadEditors.ts @@ -4,656 +4,296 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import EditorCommon = require('vs/editor/common/editorCommon'); -import Event, {Emitter} from 'vs/base/common/event'; -import {IEditor} from 'vs/platform/editor/common/editor'; +import 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 {EndOfLine} from './extHostTypes'; +import {ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions} from 'vs/editor/common/editorCommon'; import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {Position as EditorPosition} from 'vs/platform/editor/common/editor'; import {IModelService} from 'vs/editor/common/services/modelService'; -import {IDisposable, dispose} from 'vs/base/common/lifecycle'; -import {RunOnceScheduler} from 'vs/base/common/async'; -import {IdGenerator} from 'vs/base/common/idGenerator'; -import {Range} from 'vs/editor/common/core/range'; -import {Selection} from 'vs/editor/common/core/selection'; -import {EndOfLine} from 'vs/workbench/api/node/extHostTypes'; - -export interface ITextEditorConfigurationUpdate { - tabSize?: number | string; - insertSpaces?: boolean | string; - cursorStyle?: EditorCommon.TextEditorCursorStyle; -} -export interface IResolvedTextEditorConfiguration { - tabSize: number; - insertSpaces: boolean; - cursorStyle: EditorCommon.TextEditorCursorStyle; -} - -function configurationsEqual(a:IResolvedTextEditorConfiguration, b:IResolvedTextEditorConfiguration) { - if (a && !b || !a && b) { - return false; - } - if (!a && !b) { - return true; - } - return ( - a.tabSize === b.tabSize - && a.insertSpaces === b.insertSpaces - ); -} - -export interface IFocusTracker { - onGainedFocus(): void; - onLostFocus(): void; -} - -export enum TextEditorRevealType { - Default, - InCenter, - InCenterIfOutsideViewport -} - -/** - * Text Editor that is permanently bound to the same model. - * It can be bound or not to a CodeEditor. - */ -export class MainThreadTextEditor { - - private _id: string; - private _model: EditorCommon.IModel; - private _modelService: IModelService; - private _modelListeners: IDisposable[]; - private _codeEditor: EditorCommon.ICommonCodeEditor; - private _focusTracker: IFocusTracker; - private _codeEditorListeners: IDisposable[]; - - private _lastSelection: Selection[]; - private _configuration: IResolvedTextEditorConfiguration; - - private _onSelectionChanged: Emitter; - private _onConfigurationChanged: Emitter; +import {MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, ITextEditorConfigurationUpdate} from 'vs/workbench/api/node/mainThreadEditorsTracker'; +import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import {IEventService} from 'vs/platform/event/common/event'; +import {equals as arrayEquals} from 'vs/base/common/arrays'; +import {equals as objectEquals} from 'vs/base/common/objects'; +import {ExtHostContext} from './extHostProtocol'; +import {ExtHostEditors, ITextEditorPositionData} from './extHostEditors'; + +export class MainThreadEditors { + + private _proxy: ExtHostEditors; + 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( - id: string, - model:EditorCommon.IModel, - codeEditor:EditorCommon.ICommonCodeEditor, - focusTracker:IFocusTracker, - modelService: IModelService + @IThreadService threadService: IThreadService, + @IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService, + @IEditorGroupService editorGroupService: IEditorGroupService, + @ITelemetryService telemetryService: ITelemetryService, + @ICodeEditorService editorService: ICodeEditorService, + @IEventService eventService: IEventService, + @IModelService modelService: IModelService ) { - this._id = id; - this._model = model; - this._codeEditor = null; - this._focusTracker = focusTracker; - this._modelService = modelService; - this._codeEditorListeners = []; - - this._onSelectionChanged = new Emitter(); - this._onConfigurationChanged = new Emitter(); - - this._lastSelection = [ new Selection(1,1,1,1) ]; - this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); - this._modelListeners = []; - this._modelListeners.push(this._model.onDidChangeOptions((e) => { - this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); - })); - - this.setCodeEditor(codeEditor); - } + this._proxy = threadService.get(ExtHostContext.ExtHostEditors); + 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; - public dispose(): void { - this._model = null; - this._modelListeners = dispose(this._modelListeners); - this._codeEditor = null; - this._codeEditorListeners = dispose(this._codeEditorListeners); - } + this._editorTracker = new MainThreadEditorsTracker(editorService, modelService); + this._toDispose.push(this._editorTracker); - public getId(): string { - return this._id; - } + this._toDispose.push(this._editorTracker.onTextEditorAdd((textEditor) => this._onTextEditorAdd(textEditor))); + this._toDispose.push(this._editorTracker.onTextEditorRemove((textEditor) => this._onTextEditorRemove(textEditor))); - public getModel(): EditorCommon.IModel { - return this._model; + this._toDispose.push(this._editorTracker.onDidUpdateTextEditors(() => this._updateActiveAndVisibleTextEditors())); + this._toDispose.push(this._editorTracker.onChangedFocusedTextEditor((focusedTextEditorId) => this._updateActiveAndVisibleTextEditors())); + this._toDispose.push(editorGroupService.onEditorsChanged(() => this._updateActiveAndVisibleTextEditors())); + this._toDispose.push(editorGroupService.onEditorsMoved(() => this._updateActiveAndVisibleTextEditors())); } - public hasCodeEditor(codeEditor:EditorCommon.ICommonCodeEditor): boolean { - return (this._codeEditor === codeEditor); + public dispose(): void { + Object.keys(this._textEditorsListenersMap).forEach((editorId) => { + dispose(this._textEditorsListenersMap[editorId]); + }); + this._textEditorsListenersMap = Object.create(null); + this._toDispose = dispose(this._toDispose); } - public setCodeEditor(codeEditor:EditorCommon.ICommonCodeEditor): void { - if (this.hasCodeEditor(codeEditor)) { - // Nothing to do... - return; - } - this._codeEditorListeners = dispose(this._codeEditorListeners); - - this._codeEditor = codeEditor; - if (this._codeEditor) { - - // Catch early the case that this code editor gets a different model set and disassociate from this model - this._codeEditorListeners.push(this._codeEditor.onDidChangeModel(() => { - this.setCodeEditor(null); - })); - - let forwardSelection = () => { - this._lastSelection = this._codeEditor.getSelections(); - this._onSelectionChanged.fire(this._lastSelection); - }; - this._codeEditorListeners.push(this._codeEditor.onDidChangeCursorSelection(forwardSelection)); - if (!Selection.selectionsArrEqual(this._lastSelection, this._codeEditor.getSelections())) { - forwardSelection(); - } - this._codeEditorListeners.push(this._codeEditor.onDidFocusEditor(() => { - this._focusTracker.onGainedFocus(); - })); - this._codeEditorListeners.push(this._codeEditor.onDidBlurEditor(() => { - this._focusTracker.onLostFocus(); - })); - this._codeEditorListeners.push(this._codeEditor.onDidChangeConfiguration(() => { - this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); - })); - this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); - } - } + private _onTextEditorAdd(textEditor: MainThreadTextEditor): void { + let id = textEditor.getId(); + let toDispose: IDisposable[] = []; + toDispose.push(textEditor.onConfigurationChanged((opts) => { + this._proxy._acceptOptionsChanged(id, opts); + })); + toDispose.push(textEditor.onSelectionChanged((selection) => { + this._proxy._acceptSelectionsChanged(id, selection); + })); + this._proxy._acceptTextEditorAdd({ + id: id, + document: textEditor.getModel().uri, + options: textEditor.getConfiguration(), + selections: textEditor.getSelections(), + editorPosition: this._findEditorPosition(textEditor) + }); - public isVisible(): boolean { - return !!this._codeEditor; + this._textEditorsListenersMap[id] = toDispose; + this._textEditorsMap[id] = textEditor; } - public get onSelectionChanged(): Event { - return this._onSelectionChanged.event; + private _onTextEditorRemove(textEditor: MainThreadTextEditor): void { + let id = textEditor.getId(); + dispose(this._textEditorsListenersMap[id]); + delete this._textEditorsListenersMap[id]; + delete this._textEditorsMap[id]; + this._proxy._acceptTextEditorRemove(id); } - public get onConfigurationChanged(): Event { - return this._onConfigurationChanged.event; - } + private _updateActiveAndVisibleTextEditors(): void { - public getSelections(): Selection[] { - if (this._codeEditor) { - return this._codeEditor.getSelections(); + // 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); } - return this._lastSelection; - } - public setSelections(selections:EditorCommon.ISelection[]): void { - if (this._codeEditor) { - this._codeEditor.setSelections(selections); - return; + // editor columns + let editorPositionData = this._getTextEditorPositionData(); + if (!objectEquals(this._editorPositionData, editorPositionData)) { + this._editorPositionData = editorPositionData; + this._proxy._acceptEditorPositionData(this._editorPositionData); } - this._lastSelection = selections.map(Selection.liftSelection); - console.warn('setSelections on invisble editor'); } - public getConfiguration(): IResolvedTextEditorConfiguration { - return this._configuration; - } - - private _setIndentConfiguration(newConfiguration:ITextEditorConfigurationUpdate): void { - if (newConfiguration.tabSize === 'auto' || newConfiguration.insertSpaces === 'auto') { - // one of the options was set to 'auto' => detect indentation - - let creationOpts = this._modelService.getCreationOptions(); - let insertSpaces = creationOpts.insertSpaces; - let tabSize = creationOpts.tabSize; - - if (newConfiguration.insertSpaces !== 'auto') { - if (typeof newConfiguration.insertSpaces !== 'undefined') { - insertSpaces = (newConfiguration.insertSpaces === 'false' ? false : Boolean(newConfiguration.insertSpaces)); - } - } - if (newConfiguration.tabSize !== 'auto') { - if (typeof newConfiguration.tabSize !== 'undefined') { - let parsedTabSize = parseInt(newConfiguration.tabSize, 10); - if (!isNaN(parsedTabSize)) { - tabSize = parsedTabSize; - } - } - } - - this._model.detectIndentation(insertSpaces, tabSize); - return; - } - - let newOpts: EditorCommon.ITextModelUpdateOptions = {}; - if (typeof newConfiguration.insertSpaces !== 'undefined') { - newOpts.insertSpaces = (newConfiguration.insertSpaces === 'false' ? false : Boolean(newConfiguration.insertSpaces)); - } - if (typeof newConfiguration.tabSize !== 'undefined') { - let parsedTabSize = parseInt(newConfiguration.tabSize, 10); - if (!isNaN(parsedTabSize)) { - newOpts.tabSize = parsedTabSize; - } - } - this._model.updateOptions(newOpts); - } - - public setConfiguration(newConfiguration:ITextEditorConfigurationUpdate): void { - this._setIndentConfiguration(newConfiguration); - - if (newConfiguration.cursorStyle) { - let newCursorStyle = EditorCommon.cursorStyleToString(newConfiguration.cursorStyle); - - if (!this._codeEditor) { - console.warn('setConfiguration on invisible editor'); - return; - } - - this._codeEditor.updateOptions({ - cursorStyle: newCursorStyle - }); + private _findActiveTextEditorId(): string { + let focusedTextEditorId = this._editorTracker.getFocusedTextEditorId(); + if (focusedTextEditorId) { + return focusedTextEditorId; } - } - public setDecorations(key: string, ranges:EditorCommon.IDecorationOptions[]): void { - if (!this._codeEditor) { - console.warn('setDecorations on invisible editor'); - return; + let activeEditor = this._workbenchEditorService.getActiveEditor(); + if (!activeEditor) { + return null; } - this._codeEditor.setDecorations(key, ranges); - } - public revealRange(range:EditorCommon.IRange, revealType:TextEditorRevealType): void { - if (!this._codeEditor) { - console.warn('revealRange on invisible editor'); - return; + let editor = activeEditor.getControl(); + // Substitute for (editor instanceof ICodeEditor) + if (!editor || typeof editor.getEditorType !== 'function') { + // Not a text editor... + return null; } - if (revealType === TextEditorRevealType.Default) { - this._codeEditor.revealRange(range); - } else if (revealType === TextEditorRevealType.InCenter) { - this._codeEditor.revealRangeInCenter(range); - } else if (revealType === TextEditorRevealType.InCenterIfOutsideViewport) { - this._codeEditor.revealRangeInCenterIfOutsideViewport(range); - } else { - console.warn('Unknown revealType'); - } - } - private _readConfiguration(model:EditorCommon.IModel, codeEditor:EditorCommon.ICommonCodeEditor): IResolvedTextEditorConfiguration { - if (model.isDisposed()) { - // shutdown time - return this._configuration; - } - let cursorStyle = this._configuration ? this._configuration.cursorStyle : EditorCommon.TextEditorCursorStyle.Line; - if (codeEditor) { - let codeEditorOpts = codeEditor.getConfiguration(); - cursorStyle = codeEditorOpts.viewInfo.cursorStyle; + if (editor.getEditorType() === EditorType.ICodeEditor) { + return this._editorTracker.findTextEditorIdFor(editor); } - let indent = model.getOptions(); - return { - insertSpaces: indent.insertSpaces, - tabSize: indent.tabSize, - cursorStyle: cursorStyle - }; + // Must be a diff editor => use the modified side + return this._editorTracker.findTextEditorIdFor((editor).getModifiedEditor()); } - private _setConfiguration(newConfiguration:IResolvedTextEditorConfiguration): void { - if (configurationsEqual(this._configuration, newConfiguration)) { - return; - } - this._configuration = newConfiguration; - this._onConfigurationChanged.fire(this._configuration); - } - - public isFocused(): boolean { - if (this._codeEditor) { - return this._codeEditor.isFocused(); - } - return false; - } - - public matches(editor: IEditor): boolean { - if (!editor) { - return false; + private _findEditorPosition(editor: MainThreadTextEditor): EditorPosition { + for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) { + if (editor.matches(workbenchEditor)) { + return workbenchEditor.position; + } } - return editor.getControl() === this._codeEditor; } - public applyEdits(versionIdCheck:number, edits:EditorCommon.ISingleEditOperation[], setEndOfLine:EndOfLine): boolean { - if (this._model.getVersionId() !== versionIdCheck) { - console.warn('Model has changed in the meantime!'); - // throw new Error('Model has changed in the meantime!'); - // model changed in the meantime - return false; - } - - if (this._codeEditor) { - if (setEndOfLine === EndOfLine.CRLF) { - this._model.setEOL(EditorCommon.EndOfLineSequence.CRLF); - } else if (setEndOfLine === EndOfLine.LF) { - this._model.setEOL(EditorCommon.EndOfLineSequence.LF); + private _getTextEditorPositionData(): ITextEditorPositionData { + let result: ITextEditorPositionData = Object.create(null); + for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) { + let editor = 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(editor); + if (id) { + result[id] = workbenchEditor.position; + } } - - let transformedEdits = edits.map((edit): EditorCommon.IIdentifiedSingleEditOperation => { - return { - identifier: null, - range: Range.lift(edit.range), - text: edit.text, - forceMoveMarkers: edit.forceMoveMarkers - }; - }); - return this._codeEditor.executeEdits('MainThreadTextEditor', transformedEdits) || true; } - - console.warn('applyEdits on invisible editor'); - return false; + return result; } -} - -/** - * Keeps track of what goes on in the main thread and maps models => text editors - */ -export class MainThreadEditorsTracker { - private static _Ids = new IdGenerator(''); + // --- from extension host process - 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; - private _onTextEditorRemove: Emitter; - private _onDidChangeFocusedTextEditor: Emitter; - private _onDidUpdateTextEditors: Emitter; - - private _focusTracker: IFocusTracker; + _tryShowTextDocument(resource: URI, position: EditorPosition, preserveFocus: boolean): TPromise { - 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(); - this._onTextEditorRemove = new Emitter(); - this._onDidUpdateTextEditors = new Emitter(); - this._onDidChangeFocusedTextEditor = new Emitter(); - this._focusTracker = { - onGainedFocus: () => this._updateFocusedTextEditor(), - onLostFocus: () => this._updateFocusedTextEditor() + const input = { + resource, + options: { preserveFocus } }; - 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); + return this._workbenchEditorService.openEditor(input, position).then(editor => { - 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 + if (!editor) { 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); - } + return new TPromise(c => { + // not very nice but the way it is: changes to the editor state aren't + // send to the ext host as they happen but stuff is delayed a little. in + // order to provide the real editor on #openTextEditor we need to sync on + // that update + let subscription: IDisposable; + let handle: number; + function contd() { + subscription.dispose(); + clearTimeout(handle); + c(undefined); + } + subscription = this._editorTracker.onDidUpdateTextEditors(() => { + contd(); + }); + handle = setTimeout(() => { + contd(); + }, 1000); + + }).then(() => { + // find the editor we have just opened and return the + // id we have assigned to it. + for (let id in this._textEditorsMap) { + if (this._textEditorsMap[id].matches(editor)) { + return id; + } + } + }); }); - - this._printState(); - - this._visibleTextEditorIds = this._findVisibleTextEditorIds(); - - this._updateFocusedTextEditor(); - - // this is a sync event - this._onDidUpdateTextEditors.fire(undefined); } - private _updateFocusedTextEditor(): void { - this._setFocusedTextEditorId(this._findFocusedTextEditorId()); - } + _tryShowEditor(id: string, position: EditorPosition): TPromise { + // check how often this is used + this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.show' }); - 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(); - } - } + let mainThreadEditor = this._textEditorsMap[id]; + if (mainThreadEditor) { + let model = mainThreadEditor.getModel(); + return this._workbenchEditorService.openEditor({ + resource: model.uri, + options: { preserveFocus: false } + }, position).then(() => { return; }); } - - return null; } - private _findVisibleTextEditorIds(): string[] { - let result = []; - let modelUris = Object.keys(this._model2TextEditors); - for (let i = 0, len = modelUris.length; i < len; i++) { - let editors = this._model2TextEditors[modelUris[i]]; - for (let j = 0, lenJ = editors.length; j < lenJ; j++) { - if (editors[j].isVisible()) { - result.push(editors[j].getId()); + _tryHideEditor(id: string): TPromise { + // check how often this is used + this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.hide' }); + + let mainThreadEditor = this._textEditorsMap[id]; + if (mainThreadEditor) { + let editors = this._workbenchEditorService.getVisibleEditors(); + for (let editor of editors) { + if (mainThreadEditor.matches(editor)) { + return this._workbenchEditorService.closeEditor(editor.position, editor.input).then(() => { return; }); } } } - result.sort(); - return result; } - private _setFocusedTextEditorId(focusedTextEditorId:string): void { - if (this._focusedTextEditorId === focusedTextEditorId) { - // no change - return; + _trySetSelections(id: string, selections: ISelection[]): TPromise { + if (!this._textEditorsMap[id]) { + return TPromise.wrapError('TextEditor disposed'); } - - 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')); - // }); + this._textEditorsMap[id].setSelections(selections); + return TPromise.as(null); } - 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()) { - 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 { - return this._onTextEditorAdd.event; - } - - public get onTextEditorRemove(): Event { - return this._onTextEditorRemove.event; - } - - public get onDidUpdateTextEditors(): Event { - return this._onDidUpdateTextEditors.event; + _trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise { + if (!this._textEditorsMap[id]) { + return TPromise.wrapError('TextEditor disposed'); + } + this._textEditorsMap[id].setDecorations(key, ranges); + return TPromise.as(null); } - public get onChangedFocusedTextEditor(): Event { - return this._onDidChangeFocusedTextEditor.event; + _tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise { + if (!this._textEditorsMap[id]) { + return TPromise.wrapError('TextEditor disposed'); + } + this._textEditorsMap[id].revealRange(range, revealType); } - 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(); - } - } + _trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise { + if (!this._textEditorsMap[id]) { + return TPromise.wrapError('TextEditor disposed'); } - - return null; + this._textEditorsMap[id].setConfiguration(options); + return TPromise.as(null); } - public registerTextEditorDecorationType(key:string, options: EditorCommon.IDecorationRenderOptions): void { - this._codeEditorService.registerDecorationType(key, options); + _tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], setEndOfLine:EndOfLine): TPromise { + if (!this._textEditorsMap[id]) { + return TPromise.wrapError('TextEditor disposed'); + } + return TPromise.as(this._textEditorsMap[id].applyEdits(modelVersionId, edits, setEndOfLine)); } - public removeTextEditorDecorationType(key:string): void { - this._codeEditorService.removeDecorationType(key); + _registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void { + this._editorTracker.registerTextEditorDecorationType(key, options); } -} - -interface IVisibleModels { - [modelUri:string]: { - model: EditorCommon.IModel; - codeEditors: EditorCommon.ICommonCodeEditor[]; - }; -} -function strcmp(a:string, b:string): number { - if (a < b) { - return -1; + _removeTextEditorDecorationType(key: string): void { + this._editorTracker.removeTextEditorDecorationType(key); } - if (a > b) { - return 1; - } - return 0; } - diff --git a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts new file mode 100644 index 00000000000..1e938fe542e --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts @@ -0,0 +1,659 @@ +/*--------------------------------------------------------------------------------------------- + * 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 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 {EndOfLine} from 'vs/workbench/api/node/extHostTypes'; + +export interface ITextEditorConfigurationUpdate { + tabSize?: number | string; + insertSpaces?: boolean | string; + cursorStyle?: EditorCommon.TextEditorCursorStyle; +} +export interface IResolvedTextEditorConfiguration { + tabSize: number; + insertSpaces: boolean; + cursorStyle: EditorCommon.TextEditorCursorStyle; +} + +function configurationsEqual(a:IResolvedTextEditorConfiguration, b:IResolvedTextEditorConfiguration) { + if (a && !b || !a && b) { + return false; + } + if (!a && !b) { + return true; + } + return ( + a.tabSize === b.tabSize + && a.insertSpaces === b.insertSpaces + ); +} + +export interface IFocusTracker { + onGainedFocus(): void; + onLostFocus(): void; +} + +export enum TextEditorRevealType { + Default = 0, + InCenter = 1, + InCenterIfOutsideViewport = 2 +} + +/** + * Text Editor that is permanently bound to the same model. + * It can be bound or not to a CodeEditor. + */ +export class MainThreadTextEditor { + + private _id: string; + private _model: EditorCommon.IModel; + private _modelService: IModelService; + private _modelListeners: IDisposable[]; + private _codeEditor: EditorCommon.ICommonCodeEditor; + private _focusTracker: IFocusTracker; + private _codeEditorListeners: IDisposable[]; + + private _lastSelection: Selection[]; + private _configuration: IResolvedTextEditorConfiguration; + + private _onSelectionChanged: Emitter; + private _onConfigurationChanged: Emitter; + + constructor( + id: string, + model:EditorCommon.IModel, + codeEditor:EditorCommon.ICommonCodeEditor, + focusTracker:IFocusTracker, + modelService: IModelService + ) { + this._id = id; + this._model = model; + this._codeEditor = null; + this._focusTracker = focusTracker; + this._modelService = modelService; + this._codeEditorListeners = []; + + this._onSelectionChanged = new Emitter(); + this._onConfigurationChanged = new Emitter(); + + this._lastSelection = [ new Selection(1,1,1,1) ]; + this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); + this._modelListeners = []; + this._modelListeners.push(this._model.onDidChangeOptions((e) => { + this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); + })); + + this.setCodeEditor(codeEditor); + } + + public dispose(): void { + this._model = null; + this._modelListeners = dispose(this._modelListeners); + this._codeEditor = null; + this._codeEditorListeners = dispose(this._codeEditorListeners); + } + + public getId(): string { + return this._id; + } + + public getModel(): EditorCommon.IModel { + return this._model; + } + + public hasCodeEditor(codeEditor:EditorCommon.ICommonCodeEditor): boolean { + return (this._codeEditor === codeEditor); + } + + public setCodeEditor(codeEditor:EditorCommon.ICommonCodeEditor): void { + if (this.hasCodeEditor(codeEditor)) { + // Nothing to do... + return; + } + this._codeEditorListeners = dispose(this._codeEditorListeners); + + this._codeEditor = codeEditor; + if (this._codeEditor) { + + // Catch early the case that this code editor gets a different model set and disassociate from this model + this._codeEditorListeners.push(this._codeEditor.onDidChangeModel(() => { + this.setCodeEditor(null); + })); + + let forwardSelection = () => { + this._lastSelection = this._codeEditor.getSelections(); + this._onSelectionChanged.fire(this._lastSelection); + }; + this._codeEditorListeners.push(this._codeEditor.onDidChangeCursorSelection(forwardSelection)); + if (!Selection.selectionsArrEqual(this._lastSelection, this._codeEditor.getSelections())) { + forwardSelection(); + } + this._codeEditorListeners.push(this._codeEditor.onDidFocusEditor(() => { + this._focusTracker.onGainedFocus(); + })); + this._codeEditorListeners.push(this._codeEditor.onDidBlurEditor(() => { + this._focusTracker.onLostFocus(); + })); + this._codeEditorListeners.push(this._codeEditor.onDidChangeConfiguration(() => { + this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); + })); + this._setConfiguration(this._readConfiguration(this._model, this._codeEditor)); + } + } + + public isVisible(): boolean { + return !!this._codeEditor; + } + + public get onSelectionChanged(): Event { + return this._onSelectionChanged.event; + } + + public get onConfigurationChanged(): Event { + return this._onConfigurationChanged.event; + } + + public getSelections(): Selection[] { + if (this._codeEditor) { + return this._codeEditor.getSelections(); + } + return this._lastSelection; + } + + public setSelections(selections:EditorCommon.ISelection[]): void { + if (this._codeEditor) { + this._codeEditor.setSelections(selections); + return; + } + this._lastSelection = selections.map(Selection.liftSelection); + console.warn('setSelections on invisble editor'); + } + + public getConfiguration(): IResolvedTextEditorConfiguration { + return this._configuration; + } + + private _setIndentConfiguration(newConfiguration:ITextEditorConfigurationUpdate): void { + if (newConfiguration.tabSize === 'auto' || newConfiguration.insertSpaces === 'auto') { + // one of the options was set to 'auto' => detect indentation + + let creationOpts = this._modelService.getCreationOptions(); + let insertSpaces = creationOpts.insertSpaces; + let tabSize = creationOpts.tabSize; + + if (newConfiguration.insertSpaces !== 'auto') { + if (typeof newConfiguration.insertSpaces !== 'undefined') { + insertSpaces = (newConfiguration.insertSpaces === 'false' ? false : Boolean(newConfiguration.insertSpaces)); + } + } + if (newConfiguration.tabSize !== 'auto') { + if (typeof newConfiguration.tabSize !== 'undefined') { + let parsedTabSize = parseInt(newConfiguration.tabSize, 10); + if (!isNaN(parsedTabSize)) { + tabSize = parsedTabSize; + } + } + } + + this._model.detectIndentation(insertSpaces, tabSize); + return; + } + + let newOpts: EditorCommon.ITextModelUpdateOptions = {}; + if (typeof newConfiguration.insertSpaces !== 'undefined') { + newOpts.insertSpaces = (newConfiguration.insertSpaces === 'false' ? false : Boolean(newConfiguration.insertSpaces)); + } + if (typeof newConfiguration.tabSize !== 'undefined') { + let parsedTabSize = parseInt(newConfiguration.tabSize, 10); + if (!isNaN(parsedTabSize)) { + newOpts.tabSize = parsedTabSize; + } + } + this._model.updateOptions(newOpts); + } + + public setConfiguration(newConfiguration:ITextEditorConfigurationUpdate): void { + this._setIndentConfiguration(newConfiguration); + + if (newConfiguration.cursorStyle) { + let newCursorStyle = EditorCommon.cursorStyleToString(newConfiguration.cursorStyle); + + if (!this._codeEditor) { + console.warn('setConfiguration on invisible editor'); + return; + } + + this._codeEditor.updateOptions({ + cursorStyle: newCursorStyle + }); + } + } + + public setDecorations(key: string, ranges:EditorCommon.IDecorationOptions[]): void { + if (!this._codeEditor) { + console.warn('setDecorations on invisible editor'); + return; + } + this._codeEditor.setDecorations(key, ranges); + } + + public revealRange(range:EditorCommon.IRange, revealType:TextEditorRevealType): void { + if (!this._codeEditor) { + console.warn('revealRange on invisible editor'); + return; + } + if (revealType === TextEditorRevealType.Default) { + this._codeEditor.revealRange(range); + } else if (revealType === TextEditorRevealType.InCenter) { + this._codeEditor.revealRangeInCenter(range); + } else if (revealType === TextEditorRevealType.InCenterIfOutsideViewport) { + this._codeEditor.revealRangeInCenterIfOutsideViewport(range); + } else { + console.warn('Unknown revealType'); + } + } + + private _readConfiguration(model:EditorCommon.IModel, codeEditor:EditorCommon.ICommonCodeEditor): IResolvedTextEditorConfiguration { + if (model.isDisposed()) { + // shutdown time + return this._configuration; + } + let cursorStyle = this._configuration ? this._configuration.cursorStyle : EditorCommon.TextEditorCursorStyle.Line; + if (codeEditor) { + let codeEditorOpts = codeEditor.getConfiguration(); + cursorStyle = codeEditorOpts.viewInfo.cursorStyle; + } + + let indent = model.getOptions(); + return { + insertSpaces: indent.insertSpaces, + tabSize: indent.tabSize, + cursorStyle: cursorStyle + }; + } + + private _setConfiguration(newConfiguration:IResolvedTextEditorConfiguration): void { + if (configurationsEqual(this._configuration, newConfiguration)) { + return; + } + this._configuration = newConfiguration; + this._onConfigurationChanged.fire(this._configuration); + } + + public isFocused(): boolean { + if (this._codeEditor) { + return this._codeEditor.isFocused(); + } + return false; + } + + public matches(editor: IEditor): boolean { + if (!editor) { + return false; + } + return editor.getControl() === this._codeEditor; + } + + public applyEdits(versionIdCheck:number, edits:EditorCommon.ISingleEditOperation[], setEndOfLine:EndOfLine): boolean { + if (this._model.getVersionId() !== versionIdCheck) { + console.warn('Model has changed in the meantime!'); + // throw new Error('Model has changed in the meantime!'); + // model changed in the meantime + return false; + } + + if (this._codeEditor) { + if (setEndOfLine === EndOfLine.CRLF) { + this._model.setEOL(EditorCommon.EndOfLineSequence.CRLF); + } else if (setEndOfLine === EndOfLine.LF) { + this._model.setEOL(EditorCommon.EndOfLineSequence.LF); + } + + let transformedEdits = edits.map((edit): EditorCommon.IIdentifiedSingleEditOperation => { + return { + identifier: null, + range: Range.lift(edit.range), + text: edit.text, + forceMoveMarkers: edit.forceMoveMarkers + }; + }); + return this._codeEditor.executeEdits('MainThreadTextEditor', transformedEdits) || true; + } + + console.warn('applyEdits on invisible editor'); + return false; + } +} + +/** + * Keeps track of what goes on in the main thread and maps models => text editors + */ +export class MainThreadEditorsTracker { + + private static _Ids = new IdGenerator(''); + + 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; + private _onTextEditorRemove: Emitter; + private _onDidChangeFocusedTextEditor: Emitter; + private _onDidUpdateTextEditors: Emitter; + + 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(); + this._onTextEditorRemove = new Emitter(); + this._onDidUpdateTextEditors = new Emitter(); + this._onDidChangeFocusedTextEditor = new Emitter(); + 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 = []; + 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()) { + 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 { + return this._onTextEditorAdd.event; + } + + public get onTextEditorRemove(): Event { + return this._onTextEditorRemove.event; + } + + public get onDidUpdateTextEditors(): Event { + return this._onDidUpdateTextEditors.event; + } + + public get onChangedFocusedTextEditor(): Event { + 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; +} + diff --git a/src/vs/workbench/api/node/mainThreadErrors.ts b/src/vs/workbench/api/node/mainThreadErrors.ts new file mode 100644 index 00000000000..33d32470601 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadErrors.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as errors from 'vs/base/common/errors'; + +export class MainThreadErrors { + + public onUnexpectedExtHostError(err: any): void { + errors.onUnexpectedError(err); + } + +} diff --git a/src/vs/workbench/api/node/mainThreadExtensionService.ts b/src/vs/workbench/api/node/mainThreadExtensionService.ts new file mode 100644 index 00000000000..8689c471c76 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadExtensionService.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * 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 Severity from 'vs/base/common/severity'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {AbstractExtensionService, ActivatedExtension} from 'vs/platform/extensions/common/abstractExtensionService'; +import {IMessage, IExtensionDescription, IExtensionsStatus} from 'vs/platform/extensions/common/extensions'; +import {ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; +import {IMessageService} from 'vs/platform/message/common/message'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {ExtHostContext} from './extHostProtocol'; +import {ExtHostExtensionService} from './extHostExtensionService'; + +/** + * Represents a failed extension in the ext host. + */ +class MainProcessFailedExtension extends ActivatedExtension { + constructor() { + super(true); + } +} + +/** + * Represents an extension that was successfully loaded or an + * empty extension in the ext host. + */ +class MainProcessSuccessExtension extends ActivatedExtension { + constructor() { + super(false); + } +} + +function messageWithSource(msg:IMessage): string { + return (msg.source ? '[' + msg.source + ']: ' : '') + msg.message; +} + +export class MainProcessExtensionService extends AbstractExtensionService { + + private _threadService: IThreadService; + private _messageService: IMessageService; + private _proxy: ExtHostExtensionService; + private _isDev: boolean; + private _extensionsStatus: { [id: string]: IExtensionsStatus }; + + /** + * This class is constructed manually because it is a service, so it doesn't use any ctor injection + */ + constructor( + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IThreadService threadService: IThreadService, + @IMessageService messageService: IMessageService + ) { + super(false); + let config = contextService.getConfiguration(); + this._isDev = !config.env.isBuilt || !!config.env.extensionDevelopmentPath; + + this._messageService = messageService; + this._threadService = threadService; + this._proxy = this._threadService.get(ExtHostContext.ExtHostExtensionService); + this._extensionsStatus = {}; + + ExtensionsRegistry.handleExtensionPoints((msg) => this._handleMessage(msg)); + } + + private _handleMessage(msg: IMessage) { + this._showMessage(msg.type, messageWithSource(msg)); + + if (!this._extensionsStatus[msg.source]) { + this._extensionsStatus[msg.source] = { messages: [] }; + } + this._extensionsStatus[msg.source].messages.push(msg); + } + + public $localShowMessage(severity: Severity, msg: string): void { + let messageShown = false; + if (severity === Severity.Error || severity === Severity.Warning) { + if (this._isDev) { + // Only show nasty intrusive messages if doing extension development. + this._messageService.show(severity, msg); + messageShown = true; + } + } + + if (!messageShown) { + switch (severity) { + case Severity.Error: + console.error(msg); + break; + case Severity.Warning: + console.warn(msg); + break; + default: + console.log(msg); + } + } + } + + // -- overwriting AbstractExtensionService + + public getExtensionsStatus(): { [id: string]: IExtensionsStatus } { + return this._extensionsStatus; + } + + protected _showMessage(severity: Severity, msg: string): void { + this._proxy.$localShowMessage(severity, msg); + this.$localShowMessage(severity, msg); + } + + protected _createFailedExtension(): ActivatedExtension { + return new MainProcessFailedExtension(); + } + + protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise { + + // redirect extension activation to the extension host + return this._proxy.$activateExtension(extensionDescription).then(_ => { + + // the extension host calls $onExtensionActivated, where we write to `_activatedExtensions` + return this._activatedExtensions[extensionDescription.id]; + }); + } + + // -- called by extension host + + public $onExtensionHostReady(extensionDescriptions: IExtensionDescription[], messages: IMessage[]): TPromise { + ExtensionsRegistry.registerExtensions(extensionDescriptions); + messages.forEach((entry) => this._handleMessage(entry)); + this._triggerOnReady(); + return; + } + + public $onExtensionActivated(extensionId: string): void { + this._activatedExtensions[extensionId] = new MainProcessSuccessExtension(); + } + + public $onExtensionActivationFailed(extensionId: string): void { + this._activatedExtensions[extensionId] = new MainProcessFailedExtension(); + } +} diff --git a/src/vs/workbench/api/node/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/node/mainThreadFileSystemEventService.ts new file mode 100644 index 00000000000..3374330b174 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadFileSystemEventService.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {FileChangesEvent, FileChangeType} from 'vs/platform/files/common/files'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import {IEventService} from 'vs/platform/event/common/event'; +import {RunOnceScheduler} from 'vs/base/common/async'; +import {ExtHostContext} from './extHostProtocol'; +import {FileSystemEvents} from './extHostFileSystemEventService'; + +export class MainThreadFileSystemEventService { + + constructor( @IEventService eventService: IEventService, @IThreadService threadService: IThreadService) { + + const proxy = threadService.get(ExtHostContext.ExtHostFileSystemEventService); + const events: FileSystemEvents = { + created: [], + changed: [], + deleted: [] + }; + + const scheduler = new RunOnceScheduler(() => { + proxy._onFileEvent(events); + events.created.length = 0; + events.changed.length = 0; + events.deleted.length = 0; + }, 100); + + eventService.addListener2('files:fileChanges', (event: FileChangesEvent) => { + for (let change of event.changes) { + switch (change.type) { + case FileChangeType.ADDED: + events.created.push(change.resource); + break; + case FileChangeType.UPDATED: + events.changed.push(change.resource); + break; + case FileChangeType.DELETED: + events.deleted.push(change.resource); + break; + } + } + scheduler.schedule(); + }); + } +} diff --git a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts new file mode 100644 index 00000000000..61b9521d0f6 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts @@ -0,0 +1,212 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {TPromise} from 'vs/base/common/winjs.base'; +import {IDisposable} from 'vs/base/common/lifecycle'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import * as vscode from 'vscode'; +import {IReadOnlyModel, ISingleEditOperation} from 'vs/editor/common/editorCommon'; +import * as modes from 'vs/editor/common/modes'; +import {NavigateTypesSupportRegistry, INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search'; +import {wireCancellationToken} from 'vs/base/common/async'; +import {CancellationToken} from 'vs/base/common/cancellation'; +import {Position as EditorPosition} from 'vs/editor/common/core/position'; +import {Range as EditorRange} from 'vs/editor/common/core/range'; +import {ExtHostContext} from './extHostProtocol'; +import {ExtHostLanguageFeatures} from './extHostLanguageFeatures'; +import {LanguageConfigurationRegistry} from 'vs/editor/common/modes/languageConfigurationRegistry'; + +export class MainThreadLanguageFeatures { + + private _proxy: ExtHostLanguageFeatures; + private _registrations: { [handle: number]: IDisposable; } = Object.create(null); + + constructor( @IThreadService threadService: IThreadService) { + this._proxy = threadService.get(ExtHostContext.ExtHostLanguageFeatures); + } + + $unregister(handle: number): TPromise { + let registration = this._registrations[handle]; + if (registration) { + registration.dispose(); + delete this._registrations[handle]; + } + return undefined; + } + + // --- outline + + $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(selector, { + provideDocumentSymbols: (model:IReadOnlyModel, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideDocumentSymbols(handle, model.uri)); + } + }); + return undefined; + } + + // --- code lens + + $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.CodeLensProviderRegistry.register(selector, { + provideCodeLenses: (model:IReadOnlyModel, token: CancellationToken): modes.ICodeLensSymbol[] | Thenable => { + return wireCancellationToken(token, this._proxy.$provideCodeLenses(handle, model.uri)); + }, + resolveCodeLens: (model:IReadOnlyModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): modes.ICodeLensSymbol | Thenable => { + return wireCancellationToken(token, this._proxy.$resolveCodeLens(handle, model.uri, codeLens)); + } + }); + return undefined; + } + + // --- declaration + + $registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.DefinitionProviderRegistry.register(selector, { + provideDefinition: (model, position, token): Thenable => { + return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)); + } + }); + return undefined; + } + + // --- extra info + + $registerHoverProvider(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.HoverProviderRegistry.register(selector, { + provideHover: (model:IReadOnlyModel, position:EditorPosition, token:CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideHover(handle, model.uri, position)); + } + }); + return undefined; + } + + // --- occurrences + + $registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(selector, { + provideDocumentHighlights: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideDocumentHighlights(handle, model.uri, position)); + } + }); + return undefined; + } + + // --- references + + $registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.ReferenceProviderRegistry.register(selector, { + provideReferences: (model:IReadOnlyModel, position:EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideReferences(handle, model.uri, position, context)); + } + }); + return undefined; + } + + // --- quick fix + + $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, { + provideCodeActions: (model:IReadOnlyModel, range:EditorRange, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range)); + } + }); + return undefined; + } + + // --- formatting + + $registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(selector, { + provideDocumentFormattingEdits: (model: IReadOnlyModel, options: modes.FormattingOptions, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options)); + } + }); + return undefined; + } + + $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(selector, { + provideDocumentRangeFormattingEdits: (model: IReadOnlyModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options)); + } + }); + return undefined; + } + + $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): TPromise { + this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(selector, { + + autoFormatTriggerCharacters, + + provideOnTypeFormattingEdits: (model: IReadOnlyModel, position: EditorPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideOnTypeFormattingEdits(handle, model.uri, position, ch, options)); + } + }); + return undefined; + } + + // --- navigate type + + $registerNavigateTypeSupport(handle: number): TPromise { + this._registrations[handle] = NavigateTypesSupportRegistry.register({ + getNavigateToItems: (search: string): TPromise => { + return this._proxy.$getNavigateToItems(handle, search); + } + }); + return undefined; + } + + // --- rename + + $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = modes.RenameProviderRegistry.register(selector, { + provideRenameEdits: (model:IReadOnlyModel, position:EditorPosition, newName: string, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)); + } + }); + return undefined; + } + + // --- suggest + + $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[]): TPromise { + this._registrations[handle] = modes.SuggestRegistry.register(selector, { + triggerCharacters: triggerCharacters, + shouldAutotriggerSuggest: true, + provideCompletionItems: (model:IReadOnlyModel, position:EditorPosition, token:CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)); + }, + resolveCompletionItem: (model:IReadOnlyModel, position:EditorPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion)); + } + }); + return undefined; + } + + // --- parameter hints + + $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise { + this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(selector, { + + signatureHelpTriggerCharacters: triggerCharacter, + + provideSignatureHelp: (model:IReadOnlyModel, position:EditorPosition, token:CancellationToken): Thenable => { + return wireCancellationToken(token, this._proxy.$provideSignatureHelp(handle, model.uri, position)); + } + + }); + return undefined; + } + + // --- configuration + + $setLanguageConfiguration(handle: number, languageId:string, configuration: vscode.LanguageConfiguration): TPromise { + this._registrations[handle] = LanguageConfigurationRegistry.register(languageId, configuration); + return undefined; + } + +} diff --git a/src/vs/workbench/api/node/mainThreadLanguages.ts b/src/vs/workbench/api/node/mainThreadLanguages.ts new file mode 100644 index 00000000000..7f5df0fbd6b --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadLanguages.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {TPromise} from 'vs/base/common/winjs.base'; +import {IModeService} from 'vs/editor/common/services/modeService'; + +export class MainThreadLanguages { + + private _modeService: IModeService; + + constructor( + @IModeService modeService: IModeService + ) { + this._modeService = modeService; + } + + _getLanguages(): TPromise { + return TPromise.as(this._modeService.getRegisteredModes()); + } +} diff --git a/src/vs/workbench/api/node/mainThreadMessageService.ts b/src/vs/workbench/api/node/mainThreadMessageService.ts new file mode 100644 index 00000000000..fbb006ea93f --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadMessageService.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * 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 nls = require('vs/nls'); +import {IMessageService} from 'vs/platform/message/common/message'; +import Severity from 'vs/base/common/severity'; +import {Action} from 'vs/base/common/actions'; +import {TPromise as Promise} from 'vs/base/common/winjs.base'; + +export class MainThreadMessageService { + + private _messageService: IMessageService; + + constructor(@IMessageService messageService:IMessageService) { + this._messageService = messageService; + } + + $showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Thenable { + + return new Promise(resolve => { + + let messageHide: Function; + let actions: MessageItemAction[] = []; + let hasCloseAffordance = false; + + class MessageItemAction extends Action { + constructor(id: string, label: string, handle: number) { + super(id, label, undefined, true, () => { + resolve(handle); + messageHide(); // triggers dispose! make sure promise is already resolved + return undefined; + }); + } + dispose(): void { + resolve(undefined); + } + } + + commands.forEach(command => { + if (command.isCloseAffordance === true) { + hasCloseAffordance = true; + } + actions.push(new MessageItemAction('_extension_message_handle_' + command.handle, command.title, command.handle)); + }); + + if (!hasCloseAffordance) { + actions.unshift(new MessageItemAction('__close', nls.localize('close', "Close"), undefined)); + } + + messageHide = this._messageService.show(severity, { + message, + actions + }); + }); + } +} diff --git a/src/vs/workbench/api/node/mainThreadOutputService.ts b/src/vs/workbench/api/node/mainThreadOutputService.ts new file mode 100644 index 00000000000..acf65d094ef --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadOutputService.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {TPromise} from 'vs/base/common/winjs.base'; +import {Registry} from 'vs/platform/platform'; +import {IOutputService, IOutputChannel, OUTPUT_PANEL_ID, Extensions, IOutputChannelRegistry} from 'vs/workbench/parts/output/common/output'; +import {IPartService} from 'vs/workbench/services/part/common/partService'; +import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; + +export class MainThreadOutputService { + + private _outputService: IOutputService; + private _partService: IPartService; + private _panelService: IPanelService; + + constructor(@IOutputService outputService: IOutputService, + @IPartService partService: IPartService, + @IPanelService panelService: IPanelService + ) { + this._outputService = outputService; + this._partService = partService; + this._panelService = panelService; + } + + public append(channelId: string, label: string, value: string): TPromise { + this._getChannel(channelId, label).append(value); + return undefined; + } + + public clear(channelId: string, label: string): TPromise { + this._getChannel(channelId, label).clear(); + return undefined; + } + + public reveal(channelId: string, label: string, preserveFocus: boolean): TPromise { + this._getChannel(channelId, label).show(preserveFocus); + return undefined; + } + + private _getChannel(channelId: string, label: string): IOutputChannel { + if (Registry.as(Extensions.OutputChannels).getChannels().every(channel => channel.id !== channelId)) { + Registry.as(Extensions.OutputChannels).registerChannel(channelId, label); + } + + return this._outputService.getChannel(channelId); + } + + public close(channelId: string): TPromise { + const panel = this._panelService.getActivePanel(); + if (panel && panel.getId() === OUTPUT_PANEL_ID && channelId === this._outputService.getActiveChannel().id ) { + this._partService.setPanelHidden(true); + } + + return undefined; + } +} diff --git a/src/vs/workbench/api/node/mainThreadQuickOpen.ts b/src/vs/workbench/api/node/mainThreadQuickOpen.ts new file mode 100644 index 00000000000..96d7864593d --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadQuickOpen.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {TPromise} from 'vs/base/common/winjs.base'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import {IQuickOpenService, IPickOptions, IInputOptions} from 'vs/workbench/services/quickopen/common/quickOpenService'; +import {InputBoxOptions} from 'vscode'; +import {ExtHostContext} from './extHostProtocol'; +import {ExtHostQuickOpen, MyQuickPickItems} from './extHostQuickOpen'; + +export class MainThreadQuickOpen { + + private _proxy: ExtHostQuickOpen; + private _quickOpenService: IQuickOpenService; + private _doSetItems: (items: MyQuickPickItems[]) => any; + private _doSetError: (error: Error) => any; + private _contents: TPromise; + private _token: number = 0; + + constructor( @IThreadService threadService: IThreadService, @IQuickOpenService quickOpenService: IQuickOpenService) { + this._proxy = threadService.get(ExtHostContext.ExtHostQuickOpen); + this._quickOpenService = quickOpenService; + } + + $show(options: IPickOptions): Thenable { + + const myToken = ++this._token; + + this._contents = new TPromise((c, e) => { + this._doSetItems = (items) => { + if (myToken === this._token) { + c(items); + } + }; + + this._doSetError = (error) => { + if (myToken === this._token) { + e(error); + } + }; + }); + + return this._quickOpenService.pick(this._contents, options).then(item => { + if (item) { + return item.handle; + } + }, undefined, progress => { + if (progress) { + this._proxy.$onItemSelected((progress).handle); + } + }); + } + + $setItems(items: MyQuickPickItems[]): Thenable { + if (this._doSetItems) { + this._doSetItems(items); + return; + } + } + + $setError(error: Error): Thenable { + if (this._doSetError) { + this._doSetError(error); + return; + } + } + + // ---- input + + $input(options: InputBoxOptions, validateInput: boolean): Thenable { + + const inputOptions: IInputOptions = Object.create(null); + + if (options) { + inputOptions.password = options.password; + inputOptions.placeHolder = options.placeHolder; + inputOptions.prompt = options.prompt; + inputOptions.value = options.value; + } + + if (validateInput) { + inputOptions.validateInput = (value) => { + return this._proxy.$validateInput(value); + }; + } + + return this._quickOpenService.input(inputOptions); + } +} diff --git a/src/vs/workbench/api/node/mainThreadStatusBar.ts b/src/vs/workbench/api/node/mainThreadStatusBar.ts new file mode 100644 index 00000000000..1de60997c89 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadStatusBar.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment} from 'vs/platform/statusbar/common/statusbar'; +import {IDisposable} from 'vs/base/common/lifecycle'; + +export class MainThreadStatusBar { + private mapIdToDisposable: { [id: number]: IDisposable }; + + constructor( + @IStatusbarService private statusbarService: IStatusbarService + ) { + this.mapIdToDisposable = Object.create(null); + } + + setEntry(id: number, text: string, tooltip: string, command: string, color: string, alignment: MainThreadStatusBarAlignment, priority: number): void { + + // Dispose any old + this.dispose(id); + + // Add new + let disposeable = this.statusbarService.addEntry({ text, tooltip, command, color }, alignment, priority); + this.mapIdToDisposable[id] = disposeable; + } + + dispose(id: number) { + let disposeable = this.mapIdToDisposable[id]; + if (disposeable) { + disposeable.dispose(); + } + + delete this.mapIdToDisposable[id]; + } +} diff --git a/src/vs/workbench/api/node/mainThreadStorage.ts b/src/vs/workbench/api/node/mainThreadStorage.ts new file mode 100644 index 00000000000..52bf5ab3c36 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadStorage.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {TPromise} from 'vs/base/common/winjs.base'; +import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; + +export class MainThreadStorage { + + private _storageService: IStorageService; + + constructor( @IStorageService storageService: IStorageService) { + this._storageService = storageService; + } + + getValue(shared: boolean, key: string): TPromise { + let jsonValue = this._storageService.get(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + if (!jsonValue) { + return TPromise.as(undefined); + } + let value: T; + try { + value = JSON.parse(jsonValue); + return TPromise.as(value); + } catch (err) { + return TPromise.wrapError(err); + } + } + + setValue(shared: boolean, key: string, value: any): TPromise { + let jsonValue: any; + try { + jsonValue = JSON.stringify(value); + this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + } catch (err) { + return TPromise.wrapError(err); + } + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/mainThreadTelemetry.ts b/src/vs/workbench/api/node/mainThreadTelemetry.ts new file mode 100644 index 00000000000..ee8de9b4c96 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadTelemetry.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {TPromise} from 'vs/base/common/winjs.base'; +import {ITelemetryService, ITelemetryInfo} from 'vs/platform/telemetry/common/telemetry'; + +/** + * Helper always instantiated in the main process to receive telemetry events from remote telemetry services + */ +export class MainThreadTelemetry { + + private _telemetryService: ITelemetryService; + + constructor( @ITelemetryService telemetryService: ITelemetryService) { + this._telemetryService = telemetryService; + } + + public $publicLog(eventName: string, data?: any): void { + this._telemetryService.publicLog(eventName, data); + } + + public $getTelemetryInfo(): TPromise { + return this._telemetryService.getTelemetryInfo(); + } +} diff --git a/src/vs/workbench/api/node/mainThreadWorkspace.ts b/src/vs/workbench/api/node/mainThreadWorkspace.ts new file mode 100644 index 00000000000..ce5caf37c4c --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadWorkspace.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {isPromiseCanceledError} from 'vs/base/common/errors'; +import {ISearchService, QueryType} from 'vs/platform/search/common/search'; +import {IWorkspaceContextService, IWorkspace} from 'vs/platform/workspace/common/workspace'; +import {IEventService} from 'vs/platform/event/common/event'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {ICommonCodeEditor} from 'vs/editor/common/editorCommon'; +import {bulkEdit, IResourceEdit} from 'vs/editor/common/services/bulkEdit'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {Uri} from 'vscode'; + +export class MainThreadWorkspace { + + private _activeSearches: { [id: number]: TPromise } = Object.create(null); + private _searchService: ISearchService; + private _workspace: IWorkspace; + private _textFileService: ITextFileService; + private _editorService:IWorkbenchEditorService; + private _eventService:IEventService; + + constructor( @ISearchService searchService: ISearchService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @ITextFileService textFileService, + @IWorkbenchEditorService editorService, + @IEventService eventService) { + + this._searchService = searchService; + this._workspace = contextService.getWorkspace(); + this._textFileService = textFileService; + this._editorService = editorService; + this._eventService = eventService; + } + + $startSearch(include: string, exclude: string, maxResults: number, requestId: number): Thenable { + + if (!this._workspace) { + return; + } + + const search = this._searchService.search({ + folderResources: [this._workspace.resource], + type: QueryType.File, + maxResults, + includePattern: { [include]: true }, + excludePattern: { [exclude]: true }, + }).then(result => { + return result.results.map(m => m.resource); + }, err => { + if (!isPromiseCanceledError(err)) { + return TPromise.wrapError(err); + } + }); + + this._activeSearches[requestId] = search; + const onDone = () => delete this._activeSearches[requestId]; + search.done(onDone, onDone); + + return search; + } + + $cancelSearch(requestId: number): Thenable { + const search = this._activeSearches[requestId]; + if (search) { + delete this._activeSearches[requestId]; + search.cancel(); + return TPromise.as(true); + } + } + + $saveAll(includeUntitled?: boolean): Thenable { + return this._textFileService.saveAll(includeUntitled).then(result => { + return result.results.every(each => each.success === true); + }); + } + + $applyWorkspaceEdit(edits: IResourceEdit[]): TPromise { + + let codeEditor: ICommonCodeEditor; + let editor = this._editorService.getActiveEditor(); + if (editor) { + let candidate = editor.getControl(); + if (typeof candidate.getEditorType === 'function') { + // enough proof + codeEditor = candidate; + } + } + + return bulkEdit(this._eventService, this._editorService, codeEditor, edits) + .then(() => true); + } +} diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index f498c640ac8..a426aa9e640 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -45,7 +45,7 @@ import {CodeEditorServiceImpl} from 'vs/editor/browser/services/codeEditorServic import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; import {EditorWorkerServiceImpl} from 'vs/editor/common/services/editorWorkerServiceImpl'; import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService'; -import {MainProcessExtensionService} from 'vs/workbench/api/node/nativeExtensionService'; +import {MainProcessExtensionService} from 'vs/workbench/api/node/mainThreadExtensionService'; import {IOptions} from 'vs/workbench/common/options'; import {IStorageService} from 'vs/platform/storage/common/storage'; import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 4e12cfb8d82..b07ce51f5f4 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -17,7 +17,7 @@ import {IExtensionDescription} from 'vs/platform/extensions/common/extensions'; import {ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; import {ExtHostAPIImplementation, defineAPI} from 'vs/workbench/api/node/extHost.api.impl'; import {IMainProcessExtHostIPC} from 'vs/platform/extensions/common/ipcRemoteCom'; -import {ExtHostExtensionService} from 'vs/workbench/api/node/nativeExtensionService'; +import {ExtHostExtensionService} from 'vs/workbench/api/node/extHostExtensionService'; import {ExtHostThreadService} from 'vs/workbench/services/thread/common/extHostThreadService'; import {RemoteTelemetryService} from 'vs/workbench/api/node/extHostTelemetry'; import {BaseWorkspaceContextService} from 'vs/platform/workspace/common/baseWorkspaceContextService'; diff --git a/src/vs/workbench/test/node/api/extHostApiCommands.test.ts b/src/vs/workbench/test/node/api/extHostApiCommands.test.ts index 938bd3d794d..caeacc935cb 100644 --- a/src/vs/workbench/test/node/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/node/api/extHostApiCommands.test.ts @@ -21,10 +21,12 @@ import {IThreadService} from 'vs/workbench/services/thread/common/threadService' import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; import {IModelService} from 'vs/editor/common/services/modelService'; -import {ExtHostLanguageFeatures, MainThreadLanguageFeatures} from 'vs/workbench/api/node/extHostLanguageFeatures'; +import {ExtHostLanguageFeatures} from 'vs/workbench/api/node/extHostLanguageFeatures'; +import {MainThreadLanguageFeatures} from 'vs/workbench/api/node/mainThreadLanguageFeatures'; import {registerApiCommands} from 'vs/workbench/api/node/extHostApiCommands'; -import {ExtHostCommands, MainThreadCommands} from 'vs/workbench/api/node/extHostCommands'; -import {ExtHostModelService} from 'vs/workbench/api/node/extHostDocuments'; +import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; +import {MainThreadCommands} from 'vs/workbench/api/node/mainThreadCommands'; +import {ExtHostDocuments} from 'vs/workbench/api/node/extHostDocuments'; import * as ExtHostTypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import {MainContext, ExtHostContext} from 'vs/workbench/api/node/extHostProtocol'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; @@ -78,8 +80,8 @@ suite('ExtHostLanguageFeatureCommands', function() { getCreationOptions(): any { throw new Error(); } }); - const extHostModelService = threadService.set(ExtHostContext.ExtHostModelService, new ExtHostModelService(threadService)); - extHostModelService._acceptModelAdd({ + const extHostDocuments = threadService.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(threadService)); + extHostDocuments._acceptModelAdd({ isDirty: false, versionId: model.getVersionId(), modeId: model.getModeId(), @@ -105,7 +107,7 @@ suite('ExtHostLanguageFeatureCommands', function() { const diagnostics = threadService.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(threadService)); - extHost = threadService.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(threadService, extHostModelService, commands, diagnostics)); + extHost = threadService.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, diagnostics)); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures)); diff --git a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts index b6df2c1fe34..1473290214e 100644 --- a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts @@ -19,9 +19,11 @@ import {InstantiationService} from 'vs/platform/instantiation/common/instantiati import {MarkerService} from 'vs/platform/markers/common/markerService'; import {IMarkerService} from 'vs/platform/markers/common/markers'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {ExtHostLanguageFeatures, MainThreadLanguageFeatures} from 'vs/workbench/api/node/extHostLanguageFeatures'; -import {ExtHostCommands, MainThreadCommands} from 'vs/workbench/api/node/extHostCommands'; -import {ExtHostModelService} from 'vs/workbench/api/node/extHostDocuments'; +import {ExtHostLanguageFeatures} from 'vs/workbench/api/node/extHostLanguageFeatures'; +import {MainThreadLanguageFeatures} from 'vs/workbench/api/node/mainThreadLanguageFeatures'; +import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; +import {MainThreadCommands} from 'vs/workbench/api/node/mainThreadCommands'; +import {ExtHostDocuments} from 'vs/workbench/api/node/extHostDocuments'; import {getDocumentSymbols} from 'vs/editor/contrib/quickOpen/common/quickOpen'; import {DocumentSymbolProviderRegistry, DocumentHighlightKind} from 'vs/editor/common/modes'; import {getCodeLensData} from 'vs/editor/contrib/codelens/common/codelens'; @@ -69,8 +71,8 @@ suite('ExtHostLanguageFeatures', function() { originalErrorHandler = errorHandler.getUnexpectedErrorHandler(); setUnexpectedErrorHandler(() => { }); - const extHostModelService = threadService.set(ExtHostContext.ExtHostModelService, new ExtHostModelService(threadService)); - extHostModelService._acceptModelAdd({ + const extHostDocuments = threadService.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(threadService)); + extHostDocuments._acceptModelAdd({ isDirty: false, versionId: model.getVersionId(), modeId: model.getModeId(), @@ -94,7 +96,7 @@ suite('ExtHostLanguageFeatures', function() { const diagnostics = threadService.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(threadService)); - extHost = threadService.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(threadService, extHostModelService, commands, diagnostics)); + extHost = threadService.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, diagnostics)); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures)); }); diff --git a/src/vs/workbench/test/node/api/extHostMessagerService.test.ts b/src/vs/workbench/test/node/api/extHostMessagerService.test.ts index 50ff62358ae..3cb6c67bf0f 100644 --- a/src/vs/workbench/test/node/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/node/api/extHostMessagerService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import {Action} from 'vs/base/common/actions'; -import {MainThreadMessageService} from 'vs/workbench/api/node/extHostMessageService'; +import {MainThreadMessageService} from 'vs/workbench/api/node/mainThreadMessageService'; suite('ExtHostMessageService', function () { -- GitLab