From 0c3603a554578788aec82c4d972bf796b1914c96 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 20 Nov 2015 11:13:24 +0100 Subject: [PATCH 1/4] Have a support for each provider on the ext host side, starting with outline and code lens --- .../api/browser/pluginHost.api.impl.ts | 6 +- .../api/common/extHostLanguageFeatures.ts | 303 ++++++++++++++++++ src/vs/workbench/electron-browser/shell.ts | 2 + .../api/extHostLanguageFeatures.test.ts | 67 ++++ 4 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/api/common/extHostLanguageFeatures.ts create mode 100644 src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts diff --git a/src/vs/workbench/api/browser/pluginHost.api.impl.ts b/src/vs/workbench/api/browser/pluginHost.api.impl.ts index 3364186808dac..1876f26f47daa 100644 --- a/src/vs/workbench/api/browser/pluginHost.api.impl.ts +++ b/src/vs/workbench/api/browser/pluginHost.api.impl.ts @@ -18,6 +18,7 @@ import {PluginHostStatusBar} from 'vs/workbench/api/browser/pluginHostStatusBar' import {PluginHostCommands} from 'vs/workbench/api/common/pluginHostCommands'; import {ExtHostOutputService} from 'vs/workbench/api/browser/extHostOutputService'; import {LanguageFeatures} from 'vs/workbench/api/common/languageFeatures'; +import {ExtHostLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures'; import {PluginHostMessageService} from 'vs/workbench/api/common/pluginHostMessageService'; import {PluginHostTelemetryService} from 'vs/workbench/api/common/pluginHostTelemetry'; import {PluginHostEditors} from 'vs/workbench/api/common/pluginHostEditors'; @@ -251,6 +252,7 @@ export class PluginHostAPIImplementation { const languages = new ExtHostLanguages(this._threadService); const pluginHostDiagnostics = new PluginHostDiagnostics(this._threadService); const features = LanguageFeatures.createExtensionHostInstances(this._threadService); + const languageFeatures = this._threadService.getRemotable(ExtHostLanguageFeatures); this.languages = { createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { @@ -266,7 +268,7 @@ export class PluginHostAPIImplementation { return features.codeActions.register(selector, provider); }, registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { - return features.codeLens.register(selector, provider); + return languageFeatures.registerCodeLensProvider(selector, provider); }, registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { return features.definition.register(selector, provider); @@ -284,7 +286,7 @@ export class PluginHostAPIImplementation { return features.rename.register(selector, provider); }, registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { - return features.documentSymbols.register(selector, provider); + return languageFeatures.registerDocumentSymbolProvider(selector, provider); }, registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { return features.workspaceSymbols.register(provider); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts new file mode 100644 index 0000000000000..6a7ca262d3b5c --- /dev/null +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -0,0 +1,303 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import URI from 'vs/base/common/uri'; +import Event, {Emitter} from 'vs/base/common/event'; +import Severity from 'vs/base/common/severity'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {sequence} from 'vs/base/common/async'; +import {Range as EditorRange} from 'vs/editor/common/core/range'; +import {IDisposable} from 'vs/base/common/lifecycle'; +import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; +import {Remotable, IThreadService} from 'vs/platform/thread/common/thread'; +import * as vscode from 'vscode'; +import * as TypeConverters from 'vs/workbench/api/common/pluginHostTypeConverters'; +import {Position, Range, SymbolKind, DocumentHighlightKind, Disposable, Diagnostic, DiagnosticSeverity, Location, SignatureHelp, CompletionItemKind} from 'vs/workbench/api/common/pluginHostTypes'; +import {IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; +import * as modes from 'vs/editor/common/modes'; +import {CancellationTokenSource} from 'vs/base/common/cancellation'; +import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments'; +import {IMarkerService, IMarker} from 'vs/platform/markers/common/markers'; +import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands'; +import DeclarationRegistry from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; +import ExtraInfoRegistry from 'vs/editor/contrib/hover/common/hover'; +import DocumentHighlighterRegistry from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; +import ReferenceSearchRegistry from 'vs/editor/contrib/referenceSearch/common/referenceSearch'; +import QuickFixRegistry from 'vs/editor/contrib/quickFix/common/quickFix'; +import QuickOutlineRegistry, {IOutlineEntry, IOutlineSupport} from 'vs/editor/contrib/quickOpen/common/quickOpen'; +import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; +import {NavigateTypesSupportRegistry, INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search' +import {RenameRegistry} from 'vs/editor/contrib/rename/common/rename'; +import {FormatRegistry, FormatOnTypeRegistry} from 'vs/editor/contrib/format/common/format'; +import {CodeLensRegistry} from 'vs/editor/contrib/codelens/common/codelens'; +import {ParameterHintsRegistry} from 'vs/editor/contrib/parameterHints/common/parameterHints'; +import {SuggestRegistry} from 'vs/editor/contrib/suggest/common/suggest'; + + +function isThenable(obj: any): obj is Thenable { + return obj && typeof obj['then'] === 'function'; +} + +function asWinJsPromise(callback: (token: vscode.CancellationToken) => T | Thenable): TPromise { + let source = new CancellationTokenSource(); + return new TPromise((resolve, reject) => { + let item = callback(source.token); + if (isThenable(item)) { + item.then(resolve, reject); + } else { + resolve(item); + } + }, () => { + source.cancel(); + }); +} + +export class AbstractExtHostSupport

{ + + protected _provider: P; + protected _documents: PluginHostModelService; + + constructor(provider: P, documents: PluginHostModelService) { + this._provider = provider; + this._documents = documents; + } + + protected getDocument(resource: URI): vscode.TextDocument { + return this._documents.getDocument(resource); + } +} + +// ---- outline support + +export class ExtHostOutlineSupport extends AbstractExtHostSupport implements IOutlineSupport { + + getOutline(resource: URI): TPromise{ + let doc = this.getDocument(resource); + return asWinJsPromise(token => this._provider.provideDocumentSymbols(doc, token)).then(value => { + if (Array.isArray(value)) { + return value.map(ExtHostOutlineSupport._convertSymbolInfo); + } + }); + } + + private static _convertSymbolInfo(symbol: vscode.SymbolInformation): IOutlineEntry { + return { + type: TypeConverters.fromSymbolKind(symbol.kind), + range: TypeConverters.fromRange(symbol.location.range), + containerLabel: symbol.containerName, + label: symbol.name, + icon: undefined, + }; + } +} + +// ---- code lens suppot + +export class ExtHostCodeLensSupport extends AbstractExtHostSupport implements modes.ICodeLensSupport { + + private _cache: { [resource: string]: [string, vscode.CodeLens][] } = Object.create(null); + private _providerCanResolveLens: boolean; + + constructor(provider: vscode.CodeLensProvider, documents: PluginHostModelService) { + super(provider, documents); + this._providerCanResolveLens = typeof provider.resolveCodeLens === 'function'; + } + + findCodeLensSymbols(resource: URI): TPromise { + + delete this._cache[resource.toString()]; + let doc = this.getDocument(resource); + + return asWinJsPromise(token => this._provider.provideCodeLenses(doc, token)).then(value => { + if (!Array.isArray(value)) { + return; + } + + let result: modes.ICodeLensSymbol[] = []; + for (let i = 0; i < value.length; i++) { + let lens = value[i]; + + if (!this._providerCanResolveLens && !lens.isResolved) { + lens.command = { + title: '<>', + command: 'missing' + }; + } + + let id = 'code_lense_#' + i; + let key = resource.toString(); + if (i === 0) { + this._cache[key] = []; + } + this._cache[key].push([id, lens]); + + result.push({ + id, + range: TypeConverters.fromRange(lens.range) + }); + } + return result; + }); + } + + resolveCodeLensSymbol(resource: URI, symbol: modes.ICodeLensSymbol): TPromise { + + let lens: vscode.CodeLens; + let tuples = this._cache[resource.toString()]; + if (tuples) { + for (let tuple of tuples) { + if (tuple[0] === symbol.id) { + lens = tuple[1]; + } + } + } + + if (!lens) { + return; + } + + let resolve: TPromise; + if (typeof this._provider.resolveCodeLens !== 'function') { + resolve = TPromise.as(lens); + } else { + resolve = asWinJsPromise(token => this._provider.resolveCodeLens(lens, token)); + } + + return resolve.then(newLens => { + lens = newLens || lens; + if (lens.command) { + return { + id: lens.command.command, + title: lens.command.title, + arguments: lens.command.arguments + } + } + }); + } +} + +type LanguageSupport = ExtHostOutlineSupport | ExtHostCodeLensSupport; + +@Remotable.PluginHostContext('ExtHostLanguageFeatures') +export class ExtHostLanguageFeatures { + + private _provider: { [handle: number]: LanguageSupport } = Object.create(null); + private _handlePool = 0; + private _proxy: MainThreadLanguageFeatures; + private _documents: PluginHostModelService; + + constructor( @IThreadService threadService: IThreadService) { + this._proxy = threadService.getRemotable(MainThreadLanguageFeatures); + this._documents = threadService.getRemotable(PluginHostModelService); + } + + // --- outline aka document symbols + + registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._provider[handle] = new ExtHostOutlineSupport(provider, this._documents); + this._proxy.$registerOutlineSupport(handle, selector); + return this._createDisposable(handle); + } + + $getOutline(handle: number, resource: URI): TPromise { + let provider = this._provider[handle]; + if (provider instanceof ExtHostOutlineSupport) { + return provider.getOutline(resource); + } else { + return this._missingProvider(); + } + } + + // --- code lens + + registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._provider[handle] = new ExtHostCodeLensSupport(provider, this._documents); + this._proxy.$registerCodeLensSupport(handle, selector); + return this._createDisposable(handle); + } + + $findCodeLensSymbols(handle: number, resource: URI): TPromise { + let provider = this._provider[handle]; + if (provider instanceof ExtHostCodeLensSupport) { + return provider.findCodeLensSymbols(resource); + } else { + return this._missingProvider(); + } + } + + $resolveCodeLensSymbol(handle: number, resource: URI, symbol: modes.ICodeLensSymbol): TPromise { + let provider = this._provider[handle]; + if (provider instanceof ExtHostCodeLensSupport) { + return provider.resolveCodeLensSymbol(resource, symbol); + } else { + return this._missingProvider(); + } + } + + // ---- utils + + private _nextHandle(): number { + return this._handlePool++; + } + + private _createDisposable(handle: number): vscode.Disposable { + return new Disposable(() => { + delete this._provider[handle]; + this._proxy.$unregister(handle); + }); + } + + private _missingProvider():TPromise { + return TPromise.wrapError('missing provider'); + } +} + +@Remotable.MainContext('MainThreadLanguageFeatures') +export class MainThreadLanguageFeatures { + + private _disposables: { [handle: number]: IDisposable } = Object.create(null); + private _proxy: ExtHostLanguageFeatures; + + constructor( @IThreadService threadService: IThreadService) { + this._proxy = threadService.getRemotable(ExtHostLanguageFeatures); + } + + $unregister(handle: number): TPromise { + if (this._disposables[handle]) { + this._disposables[handle].dispose(); + delete this._disposables[handle]; + } + return undefined; + } + + // --- outline + + $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise{ + let disposable = QuickOutlineRegistry.register(selector, { + getOutline: (resource: URI) => { + return this._proxy.$getOutline(handle, resource); + } + }); + this._disposables[handle] = disposable; + return undefined; + } + + // --- code lens + + $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + let disposable = CodeLensRegistry.register(selector, { + findCodeLensSymbols:(resource: URI) => { + return this._proxy.$findCodeLensSymbols(handle, resource); + }, + resolveCodeLensSymbol:(resource: URI, symbol) => { + return this._proxy.$resolveCodeLensSymbol(handle, resource, symbol); + } + }); + return undefined; + } +} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index cd3566cd7d52a..67d448b965c49 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -68,6 +68,7 @@ import {MainThreadEditors} from 'vs/workbench/api/common/pluginHostEditors'; import {MainThreadWorkspace} from 'vs/workbench/api/browser/pluginHostWorkspace'; import {MainThreadConfiguration} from 'vs/workbench/api/common/pluginHostConfiguration'; import {LanguageFeatures} from 'vs/workbench/api/common/languageFeatures'; +import {MainThreadLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures'; import {EventService} from 'vs/platform/event/common/eventService'; import {IOptions} from 'vs/workbench/common/options'; import themes = require('vs/platform/theme/common/themes'); @@ -337,6 +338,7 @@ export class WorkbenchShell { this.threadServiceInstance.getRemotable(MainThreadWorkspace); this.threadServiceInstance.getRemotable(MainThreadEditors); this.threadServiceInstance.getRemotable(MainThreadStorage); + this.threadServiceInstance.getRemotable(MainThreadLanguageFeatures); LanguageFeatures.createMainThreadInstances(this.threadServiceInstance); } diff --git a/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts new file mode 100644 index 0000000000000..4277933e31ec0 --- /dev/null +++ b/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * 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 assert from 'assert'; +import URI from 'vs/base/common/uri'; +import {MainThreadLanguageFeatures, ExtHostOutlineSupport} from 'vs/workbench/api/common/extHostLanguageFeatures'; +import {PluginHostModelService, BaseTextDocument} from 'vs/workbench/api/common/pluginHostDocuments'; +import {Position} from 'vs/workbench/api/common/pluginHostTypes'; +import {Range as CodeEditorRange} from 'vs/editor/common/core/range'; +import * as EditorCommon from 'vs/editor/common/editorCommon'; +import QuickOutlineRegistry from 'vs/editor/contrib/quickOpen/common/quickOpen'; + +const mockThreadService = { + getRemotable() { + return {}; + } +}; + +class MockDocuments extends PluginHostModelService { + + private _document: BaseTextDocument; + + constructor() { + super(mockThreadService); + + this._document = new BaseTextDocument(URI.parse('fake://tests/a.file'), + [ + 'this is line one', + 'this is line two', + 'this is line three', + ], + '\n', 'testing', 1, false); + } + + public getDocument(resource: vscode.Uri): BaseTextDocument { + return this._document; + } +} + + +suite('ExtHostLanguageFeatures', function() { + + let model = { language: 'testing', uri: URI.parse('fake://tests/a.file') }; + let documents = new MockDocuments(); + let mainThreadFeatures = new MainThreadLanguageFeatures(mockThreadService); + + test('outline, register', function(done) { + + let count = QuickOutlineRegistry.all(model).length; + + mainThreadFeatures.$registerOutlineSupport(0, 'testing') + mainThreadFeatures.$registerOutlineSupport(1, 'testing') + + let count2 = QuickOutlineRegistry.all(model).length; + assert.equal(count2, count + 2); + + mainThreadFeatures.$unregister(8); + assert.equal(QuickOutlineRegistry.all(model).length, count2); + + mainThreadFeatures.$unregister(0); + assert.equal(QuickOutlineRegistry.all(model).length, count2 - 1); + }); +}); \ No newline at end of file From b4c961cfe1c0460ad02b5faf38fa04c64187bb48 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 20 Nov 2015 12:17:11 +0100 Subject: [PATCH 2/4] add tests for outline --- .../api/common/extHostLanguageFeatures.ts | 12 +- .../api/extHostLanguageFeatures.test.ts | 104 +++++++++++++++--- 2 files changed, 94 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 6a7ca262d3b5c..5f36f414d6c20 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -179,15 +179,15 @@ export class ExtHostCodeLensSupport extends AbstractExtHostSupport{ +const threadService = { getRemotable() { return {}; } }; -class MockDocuments extends PluginHostModelService { +class Documents extends PluginHostModelService { private _document: BaseTextDocument; constructor() { - super(mockThreadService); + super(threadService); this._document = new BaseTextDocument(URI.parse('fake://tests/a.file'), [ @@ -41,27 +44,96 @@ class MockDocuments extends PluginHostModelService { } } +const documents = new Documents(); -suite('ExtHostLanguageFeatures', function() { +class ExtHostLF extends LF.ExtHostLanguageFeatures { + constructor() { + super(threadService); + this._documents = documents; + } + set proxy(p:LF.MainThreadLanguageFeatures) { + this._proxy = p; + } +} + + +class MainThreadLF extends LF.MainThreadLanguageFeatures { + constructor() { + super(threadService); + } + set proxy(p: LF.ExtHostLanguageFeatures) { + this._proxy = p; + } +} - let model = { language: 'testing', uri: URI.parse('fake://tests/a.file') }; - let documents = new MockDocuments(); - let mainThreadFeatures = new MainThreadLanguageFeatures(mockThreadService); +const extHostLanguageFeatures = new ExtHostLF(); +const mainThreadLanguageFeatures = new MainThreadLF(); +extHostLanguageFeatures.proxy = mainThreadLanguageFeatures; +mainThreadLanguageFeatures.proxy = extHostLanguageFeatures; - test('outline, register', function(done) { +let model = { language: 'testing', uri: URI.parse('fake://tests/a.file') }; + +suite('ExtHostLanguageFeatures', function() { + + test('outline, register & unregister', function() { let count = QuickOutlineRegistry.all(model).length; - mainThreadFeatures.$registerOutlineSupport(0, 'testing') - mainThreadFeatures.$registerOutlineSupport(1, 'testing') + let sub1 = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { + provideDocumentSymbols(document: vscode.TextDocument) { + let info = new pluginHostTypes.SymbolInformation('test', + pluginHostTypes.SymbolKind.File, + document.lineAt(1).range); + return [info]; + } + }); + + let sub2 = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { + provideDocumentSymbols(document: vscode.TextDocument) { + let info = new pluginHostTypes.SymbolInformation('test', + pluginHostTypes.SymbolKind.File, + document.lineAt(1).range); + return [info]; + } + }); let count2 = QuickOutlineRegistry.all(model).length; assert.equal(count2, count + 2); - mainThreadFeatures.$unregister(8); - assert.equal(QuickOutlineRegistry.all(model).length, count2); - - mainThreadFeatures.$unregister(0); + sub1.dispose(); assert.equal(QuickOutlineRegistry.all(model).length, count2 - 1); + sub2.dispose(); + }); + + test('outline, get data', function(done) { + + let disposable = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { + provideDocumentSymbols(document: vscode.TextDocument) { + let info = new pluginHostTypes.SymbolInformation('test', + pluginHostTypes.SymbolKind.File, + document.lineAt(1).range); + return [info]; + } + }); + + let promise = QuickOutlineRegistry.all(model)[0].getOutline(model.uri); + promise.then(entries => { + assert.equal(1, entries.length); + disposable.dispose(); + done(); + }, done); + }); + + test('outline, bad provider', function(done) { + + let disposable = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { + provideDocumentSymbols():any { + throw new Error('dddd'); + } + }); + + let promise = QuickOutlineRegistry.all(model)[0].getOutline(model.uri); + promise.then(() => done(new Error('should have failed')), err => done()); + }); }); \ No newline at end of file From 75750e3915891f3d97d2978ff9a9e80b27909416 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 20 Nov 2015 14:49:04 +0100 Subject: [PATCH 3/4] Keep the fix small, keep ext host side command --- .../api/browser/pluginHost.api.impl.ts | 6 +- .../api/common/extHostLanguageFeatures.ts | 303 ------------------ .../workbench/api/common/languageFeatures.ts | 36 +-- src/vs/workbench/electron-browser/shell.ts | 2 - .../api/extHostLanguageFeatures.test.ts | 139 -------- 5 files changed, 20 insertions(+), 466 deletions(-) delete mode 100644 src/vs/workbench/api/common/extHostLanguageFeatures.ts delete mode 100644 src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts diff --git a/src/vs/workbench/api/browser/pluginHost.api.impl.ts b/src/vs/workbench/api/browser/pluginHost.api.impl.ts index 1876f26f47daa..3364186808dac 100644 --- a/src/vs/workbench/api/browser/pluginHost.api.impl.ts +++ b/src/vs/workbench/api/browser/pluginHost.api.impl.ts @@ -18,7 +18,6 @@ import {PluginHostStatusBar} from 'vs/workbench/api/browser/pluginHostStatusBar' import {PluginHostCommands} from 'vs/workbench/api/common/pluginHostCommands'; import {ExtHostOutputService} from 'vs/workbench/api/browser/extHostOutputService'; import {LanguageFeatures} from 'vs/workbench/api/common/languageFeatures'; -import {ExtHostLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures'; import {PluginHostMessageService} from 'vs/workbench/api/common/pluginHostMessageService'; import {PluginHostTelemetryService} from 'vs/workbench/api/common/pluginHostTelemetry'; import {PluginHostEditors} from 'vs/workbench/api/common/pluginHostEditors'; @@ -252,7 +251,6 @@ export class PluginHostAPIImplementation { const languages = new ExtHostLanguages(this._threadService); const pluginHostDiagnostics = new PluginHostDiagnostics(this._threadService); const features = LanguageFeatures.createExtensionHostInstances(this._threadService); - const languageFeatures = this._threadService.getRemotable(ExtHostLanguageFeatures); this.languages = { createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { @@ -268,7 +266,7 @@ export class PluginHostAPIImplementation { return features.codeActions.register(selector, provider); }, registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { - return languageFeatures.registerCodeLensProvider(selector, provider); + return features.codeLens.register(selector, provider); }, registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { return features.definition.register(selector, provider); @@ -286,7 +284,7 @@ export class PluginHostAPIImplementation { return features.rename.register(selector, provider); }, registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { - return languageFeatures.registerDocumentSymbolProvider(selector, provider); + return features.documentSymbols.register(selector, provider); }, registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { return features.workspaceSymbols.register(provider); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts deleted file mode 100644 index 5f36f414d6c20..0000000000000 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ /dev/null @@ -1,303 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import URI from 'vs/base/common/uri'; -import Event, {Emitter} from 'vs/base/common/event'; -import Severity from 'vs/base/common/severity'; -import {TPromise} from 'vs/base/common/winjs.base'; -import {sequence} from 'vs/base/common/async'; -import {Range as EditorRange} from 'vs/editor/common/core/range'; -import {IDisposable} from 'vs/base/common/lifecycle'; -import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; -import {Remotable, IThreadService} from 'vs/platform/thread/common/thread'; -import * as vscode from 'vscode'; -import * as TypeConverters from 'vs/workbench/api/common/pluginHostTypeConverters'; -import {Position, Range, SymbolKind, DocumentHighlightKind, Disposable, Diagnostic, DiagnosticSeverity, Location, SignatureHelp, CompletionItemKind} from 'vs/workbench/api/common/pluginHostTypes'; -import {IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; -import * as modes from 'vs/editor/common/modes'; -import {CancellationTokenSource} from 'vs/base/common/cancellation'; -import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments'; -import {IMarkerService, IMarker} from 'vs/platform/markers/common/markers'; -import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands'; -import DeclarationRegistry from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; -import ExtraInfoRegistry from 'vs/editor/contrib/hover/common/hover'; -import DocumentHighlighterRegistry from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; -import ReferenceSearchRegistry from 'vs/editor/contrib/referenceSearch/common/referenceSearch'; -import QuickFixRegistry from 'vs/editor/contrib/quickFix/common/quickFix'; -import QuickOutlineRegistry, {IOutlineEntry, IOutlineSupport} from 'vs/editor/contrib/quickOpen/common/quickOpen'; -import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -import {NavigateTypesSupportRegistry, INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search' -import {RenameRegistry} from 'vs/editor/contrib/rename/common/rename'; -import {FormatRegistry, FormatOnTypeRegistry} from 'vs/editor/contrib/format/common/format'; -import {CodeLensRegistry} from 'vs/editor/contrib/codelens/common/codelens'; -import {ParameterHintsRegistry} from 'vs/editor/contrib/parameterHints/common/parameterHints'; -import {SuggestRegistry} from 'vs/editor/contrib/suggest/common/suggest'; - - -function isThenable(obj: any): obj is Thenable { - return obj && typeof obj['then'] === 'function'; -} - -function asWinJsPromise(callback: (token: vscode.CancellationToken) => T | Thenable): TPromise { - let source = new CancellationTokenSource(); - return new TPromise((resolve, reject) => { - let item = callback(source.token); - if (isThenable(item)) { - item.then(resolve, reject); - } else { - resolve(item); - } - }, () => { - source.cancel(); - }); -} - -export class AbstractExtHostSupport

{ - - protected _provider: P; - protected _documents: PluginHostModelService; - - constructor(provider: P, documents: PluginHostModelService) { - this._provider = provider; - this._documents = documents; - } - - protected getDocument(resource: URI): vscode.TextDocument { - return this._documents.getDocument(resource); - } -} - -// ---- outline support - -export class ExtHostOutlineSupport extends AbstractExtHostSupport implements IOutlineSupport { - - getOutline(resource: URI): TPromise{ - let doc = this.getDocument(resource); - return asWinJsPromise(token => this._provider.provideDocumentSymbols(doc, token)).then(value => { - if (Array.isArray(value)) { - return value.map(ExtHostOutlineSupport._convertSymbolInfo); - } - }); - } - - private static _convertSymbolInfo(symbol: vscode.SymbolInformation): IOutlineEntry { - return { - type: TypeConverters.fromSymbolKind(symbol.kind), - range: TypeConverters.fromRange(symbol.location.range), - containerLabel: symbol.containerName, - label: symbol.name, - icon: undefined, - }; - } -} - -// ---- code lens suppot - -export class ExtHostCodeLensSupport extends AbstractExtHostSupport implements modes.ICodeLensSupport { - - private _cache: { [resource: string]: [string, vscode.CodeLens][] } = Object.create(null); - private _providerCanResolveLens: boolean; - - constructor(provider: vscode.CodeLensProvider, documents: PluginHostModelService) { - super(provider, documents); - this._providerCanResolveLens = typeof provider.resolveCodeLens === 'function'; - } - - findCodeLensSymbols(resource: URI): TPromise { - - delete this._cache[resource.toString()]; - let doc = this.getDocument(resource); - - return asWinJsPromise(token => this._provider.provideCodeLenses(doc, token)).then(value => { - if (!Array.isArray(value)) { - return; - } - - let result: modes.ICodeLensSymbol[] = []; - for (let i = 0; i < value.length; i++) { - let lens = value[i]; - - if (!this._providerCanResolveLens && !lens.isResolved) { - lens.command = { - title: '<>', - command: 'missing' - }; - } - - let id = 'code_lense_#' + i; - let key = resource.toString(); - if (i === 0) { - this._cache[key] = []; - } - this._cache[key].push([id, lens]); - - result.push({ - id, - range: TypeConverters.fromRange(lens.range) - }); - } - return result; - }); - } - - resolveCodeLensSymbol(resource: URI, symbol: modes.ICodeLensSymbol): TPromise { - - let lens: vscode.CodeLens; - let tuples = this._cache[resource.toString()]; - if (tuples) { - for (let tuple of tuples) { - if (tuple[0] === symbol.id) { - lens = tuple[1]; - } - } - } - - if (!lens) { - return; - } - - let resolve: TPromise; - if (typeof this._provider.resolveCodeLens !== 'function') { - resolve = TPromise.as(lens); - } else { - resolve = asWinJsPromise(token => this._provider.resolveCodeLens(lens, token)); - } - - return resolve.then(newLens => { - lens = newLens || lens; - if (lens.command) { - return { - id: lens.command.command, - title: lens.command.title, - arguments: lens.command.arguments - } - } - }); - } -} - -export type LanguageSupport = ExtHostOutlineSupport | ExtHostCodeLensSupport; - -@Remotable.PluginHostContext('ExtHostLanguageFeatures') -export class ExtHostLanguageFeatures { - - protected _provider: { [handle: number]: LanguageSupport } = Object.create(null); - protected _proxy: MainThreadLanguageFeatures; - protected _documents: PluginHostModelService; - private _handlePool = 0; - - constructor( @IThreadService threadService: IThreadService) { - this._proxy = threadService.getRemotable(MainThreadLanguageFeatures); - this._documents = threadService.getRemotable(PluginHostModelService); - } - - // --- outline aka document symbols - - registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._provider[handle] = new ExtHostOutlineSupport(provider, this._documents); - this._proxy.$registerOutlineSupport(handle, selector); - return this._createDisposable(handle); - } - - $getOutline(handle: number, resource: URI): TPromise { - let provider = this._provider[handle]; - if (provider instanceof ExtHostOutlineSupport) { - return provider.getOutline(resource); - } else { - return this._missingProvider(); - } - } - - // --- code lens - - registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._provider[handle] = new ExtHostCodeLensSupport(provider, this._documents); - this._proxy.$registerCodeLensSupport(handle, selector); - return this._createDisposable(handle); - } - - $findCodeLensSymbols(handle: number, resource: URI): TPromise { - let provider = this._provider[handle]; - if (provider instanceof ExtHostCodeLensSupport) { - return provider.findCodeLensSymbols(resource); - } else { - return this._missingProvider(); - } - } - - $resolveCodeLensSymbol(handle: number, resource: URI, symbol: modes.ICodeLensSymbol): TPromise { - let provider = this._provider[handle]; - if (provider instanceof ExtHostCodeLensSupport) { - return provider.resolveCodeLensSymbol(resource, symbol); - } else { - return this._missingProvider(); - } - } - - // ---- utils - - private _nextHandle(): number { - return this._handlePool++; - } - - private _createDisposable(handle: number): vscode.Disposable { - return new Disposable(() => { - delete this._provider[handle]; - this._proxy.$unregister(handle); - }); - } - - private _missingProvider():TPromise { - return TPromise.wrapError('missing provider'); - } -} - -@Remotable.MainContext('MainThreadLanguageFeatures') -export class MainThreadLanguageFeatures { - - protected _disposables: { [handle: number]: IDisposable } = Object.create(null); - protected _proxy: ExtHostLanguageFeatures; - - constructor( @IThreadService threadService: IThreadService) { - this._proxy = threadService.getRemotable(ExtHostLanguageFeatures); - } - - $unregister(handle: number): TPromise { - if (this._disposables[handle]) { - this._disposables[handle].dispose(); - delete this._disposables[handle]; - } - return undefined; - } - - // --- outline - - $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise{ - let disposable = QuickOutlineRegistry.register(selector, { - getOutline: (resource: URI) => { - return this._proxy.$getOutline(handle, resource); - } - }); - this._disposables[handle] = disposable; - return undefined; - } - - // --- code lens - - $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise { - let disposable = CodeLensRegistry.register(selector, { - findCodeLensSymbols:(resource: URI) => { - return this._proxy.$findCodeLensSymbols(handle, resource); - }, - resolveCodeLensSymbol:(resource: URI, symbol) => { - return this._proxy.$resolveCodeLensSymbol(handle, resource, symbol); - } - }); - return undefined; - } -} \ No newline at end of file diff --git a/src/vs/workbench/api/common/languageFeatures.ts b/src/vs/workbench/api/common/languageFeatures.ts index ab86869220a2e..b5caf5ddbd2d5 100644 --- a/src/vs/workbench/api/common/languageFeatures.ts +++ b/src/vs/workbench/api/common/languageFeatures.ts @@ -58,8 +58,8 @@ export abstract class AbstractMainThreadFeature { private _id: string; protected _commands: PluginHostCommands; - protected _handlePool = 0; - protected _disposable: { [handle: number]: IDisposable } = Object.create(null); + protected _refCount = 0; + protected _disposable: IDisposable; protected _registry: LanguageFeatureRegistry; constructor(id: string, registry: LanguageFeatureRegistry, @IThreadService threadService: IThreadService) { @@ -72,16 +72,16 @@ export abstract class AbstractMainThreadFeature { return TPromise.as(this._id); } - _register(selector: vscode.DocumentSelector): TPromise { - const handle = this._handlePool++; - this._disposable[handle] = this._registry.register(selector, this); - return TPromise.as(handle); + _register(selector: vscode.DocumentSelector): TPromise { + if (this._refCount++ === 0) { + this._disposable = this._registry.register(selector, this); + } + return undefined; } - _unregister(handle: number): TPromise { - if (this._disposable[handle]) { - this._disposable[handle].dispose(); - delete this._disposable[handle]; + _unregister(): TPromise { + if (--this._refCount === 0) { + this._disposable.dispose(); } return undefined; } @@ -112,11 +112,11 @@ export abstract class AbstractExtensionHostFeature { disposable.dispose(); // remove locally - handle.then(value => this._proxy._unregister(value)); + registered.then(() => this._proxy._unregister()); }); } @@ -721,11 +721,11 @@ export class ExtHostFormatOnType extends AbstractExtensionHostFeature { disposable.dispose(); - handle.then(value => this._proxy._unregister(value)); + registered.then(() => this._proxy._unregister()); }); } @@ -792,11 +792,11 @@ export class ExtHostSignatureHelp extends AbstractExtensionHostFeature { disposable.dispose(); - handle.then(value => this._proxy._unregister(value)); + registered.then(() => this._proxy._unregister()); }); } @@ -908,10 +908,10 @@ export class ExtHostCompletions extends AbstractExtensionHostFeature { disposable.dispose(); - handle.then(value => this._proxy._unregister(value)); + registered.then(() => this._proxy._unregister()); }); } diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 67d448b965c49..cd3566cd7d52a 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -68,7 +68,6 @@ import {MainThreadEditors} from 'vs/workbench/api/common/pluginHostEditors'; import {MainThreadWorkspace} from 'vs/workbench/api/browser/pluginHostWorkspace'; import {MainThreadConfiguration} from 'vs/workbench/api/common/pluginHostConfiguration'; import {LanguageFeatures} from 'vs/workbench/api/common/languageFeatures'; -import {MainThreadLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures'; import {EventService} from 'vs/platform/event/common/eventService'; import {IOptions} from 'vs/workbench/common/options'; import themes = require('vs/platform/theme/common/themes'); @@ -338,7 +337,6 @@ export class WorkbenchShell { this.threadServiceInstance.getRemotable(MainThreadWorkspace); this.threadServiceInstance.getRemotable(MainThreadEditors); this.threadServiceInstance.getRemotable(MainThreadStorage); - this.threadServiceInstance.getRemotable(MainThreadLanguageFeatures); LanguageFeatures.createMainThreadInstances(this.threadServiceInstance); } diff --git a/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts deleted file mode 100644 index 621427c9a8928..0000000000000 --- a/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 assert from 'assert'; -import {create} from 'vs/base/common/types'; -import URI from 'vs/base/common/uri'; -import * as LF from 'vs/workbench/api/common/extHostLanguageFeatures'; -import {PluginHostModelService, BaseTextDocument} from 'vs/workbench/api/common/pluginHostDocuments'; -import * as pluginHostTypes from 'vs/workbench/api/common/pluginHostTypes'; -import {Range as CodeEditorRange} from 'vs/editor/common/core/range'; -import * as EditorCommon from 'vs/editor/common/editorCommon'; -import QuickOutlineRegistry from 'vs/editor/contrib/quickOpen/common/quickOpen'; -import {AbstractThreadService} from 'vs/platform/thread/common/abstractThreadService' -import {SyncDescriptor, SyncDescriptor0, createSyncDescriptor, AsyncDescriptor0, AsyncDescriptor1, AsyncDescriptor2, AsyncDescriptor3} from 'vs/platform/instantiation/common/descriptors'; - -const threadService = { - getRemotable() { - return {}; - } -}; - -class Documents extends PluginHostModelService { - - private _document: BaseTextDocument; - - constructor() { - super(threadService); - - this._document = new BaseTextDocument(URI.parse('fake://tests/a.file'), - [ - 'this is line one', - 'this is line two', - 'this is line three', - ], - '\n', 'testing', 1, false); - } - - public getDocument(resource: vscode.Uri): BaseTextDocument { - return this._document; - } -} - -const documents = new Documents(); - -class ExtHostLF extends LF.ExtHostLanguageFeatures { - constructor() { - super(threadService); - this._documents = documents; - } - set proxy(p:LF.MainThreadLanguageFeatures) { - this._proxy = p; - } -} - - -class MainThreadLF extends LF.MainThreadLanguageFeatures { - constructor() { - super(threadService); - } - set proxy(p: LF.ExtHostLanguageFeatures) { - this._proxy = p; - } -} - -const extHostLanguageFeatures = new ExtHostLF(); -const mainThreadLanguageFeatures = new MainThreadLF(); -extHostLanguageFeatures.proxy = mainThreadLanguageFeatures; -mainThreadLanguageFeatures.proxy = extHostLanguageFeatures; - -let model = { language: 'testing', uri: URI.parse('fake://tests/a.file') }; - -suite('ExtHostLanguageFeatures', function() { - - test('outline, register & unregister', function() { - - let count = QuickOutlineRegistry.all(model).length; - - let sub1 = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { - provideDocumentSymbols(document: vscode.TextDocument) { - let info = new pluginHostTypes.SymbolInformation('test', - pluginHostTypes.SymbolKind.File, - document.lineAt(1).range); - return [info]; - } - }); - - let sub2 = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { - provideDocumentSymbols(document: vscode.TextDocument) { - let info = new pluginHostTypes.SymbolInformation('test', - pluginHostTypes.SymbolKind.File, - document.lineAt(1).range); - return [info]; - } - }); - - let count2 = QuickOutlineRegistry.all(model).length; - assert.equal(count2, count + 2); - - sub1.dispose(); - assert.equal(QuickOutlineRegistry.all(model).length, count2 - 1); - sub2.dispose(); - }); - - test('outline, get data', function(done) { - - let disposable = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { - provideDocumentSymbols(document: vscode.TextDocument) { - let info = new pluginHostTypes.SymbolInformation('test', - pluginHostTypes.SymbolKind.File, - document.lineAt(1).range); - return [info]; - } - }); - - let promise = QuickOutlineRegistry.all(model)[0].getOutline(model.uri); - promise.then(entries => { - assert.equal(1, entries.length); - disposable.dispose(); - done(); - }, done); - }); - - test('outline, bad provider', function(done) { - - let disposable = extHostLanguageFeatures.registerDocumentSymbolProvider('testing', { - provideDocumentSymbols():any { - throw new Error('dddd'); - } - }); - - let promise = QuickOutlineRegistry.all(model)[0].getOutline(model.uri); - promise.then(() => done(new Error('should have failed')), err => done()); - - }); -}); \ No newline at end of file From 154863b648056b068b9fdce3992029dc7565a448 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 20 Nov 2015 16:26:02 +0100 Subject: [PATCH 4/4] add tests --- .../test/common/api/languageFeatures.test.ts | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/vs/workbench/test/common/api/languageFeatures.test.ts diff --git a/src/vs/workbench/test/common/api/languageFeatures.test.ts b/src/vs/workbench/test/common/api/languageFeatures.test.ts new file mode 100644 index 0000000000000..5ea693041f956 --- /dev/null +++ b/src/vs/workbench/test/common/api/languageFeatures.test.ts @@ -0,0 +1,177 @@ +/*--------------------------------------------------------------------------------------------- + * 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 assert from 'assert'; +import {create} from 'vs/base/common/types'; +import URI from 'vs/base/common/uri'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {PluginHostDocument} from 'vs/workbench/api/common/pluginHostDocuments'; +import * as phTypes from 'vs/workbench/api/common/pluginHostTypes'; +import {Range as CodeEditorRange} from 'vs/editor/common/core/range'; +import * as EditorCommon from 'vs/editor/common/editorCommon'; +import {NullThreadService} from 'vs/platform/test/common/nullThreadService' +import * as LF from 'vs/workbench/api/common/languageFeatures'; +import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands'; +import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments'; +import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors'; +import QuickOutlineRegistry from 'vs/editor/contrib/quickOpen/common/quickOpen'; +import {LanguageSelector, ModelLike} from 'vs/editor/common/modes/languageSelector'; + +class ThreadService extends NullThreadService { + + protected _registerAndInstantiateMainProcessActor(id: string, descriptor: SyncDescriptor0): T { + + let instance: any; + + return this._getOrCreateProxyInstance({ + callOnRemote: (proxyId: string, path: string, args: any[]): TPromise => { + if (!instance) { + instance = create(descriptor.ctor, this); + } + try { + let result = (instance[path]).apply(instance, args); + return TPromise.is(result) ? result : TPromise.as(result); + } catch (err) { + return TPromise.wrapError(err); + } + } + }, id, descriptor) + } + + protected _registerAndInstantiatePluginHostActor(id: string, descriptor: SyncDescriptor0): T { + return this._getOrCreateLocalInstance(id, descriptor); + } +} + +let threadService: ThreadService; +let model: ModelLike = { language: 'far', uri: URI.parse('far://testing/file.a') }; + +let extHost: LF.ExtensionHostDocumentSymbols; +let mainHost: LF.MainThreadDocumentSymbols; + +suite('ExtHostLanguageFeatures', function() { + + suiteSetup(() => { + threadService = new ThreadService(); + let documents = threadService.getRemotable(PluginHostModelService); + documents._acceptModelAdd({ + isDirty: false, + versionId: 1, + modeId: 'far', + url: model.uri, + value: { + EOL: '\n', + lines: [ + 'This is the first line', + 'This is the second line', + 'This is the third line', + ], + BOM: '', + length: -1 + }, + }) + threadService.getRemotable(PluginHostCommands); + threadService.getRemotable(MainThreadCommands); + threadService.getRemotable(LF.MainThreadDocumentSymbols); + extHost = new LF.ExtensionHostDocumentSymbols(threadService); + mainHost = threadService.getRemotable(LF.MainThreadDocumentSymbols); + }); + + test('DocumentSymbols, register/deregister', function() { + + + // register + assert.equal(QuickOutlineRegistry.all(model).length, 0); + let disposable = extHost.register('far', { + provideDocumentSymbols() { + return []; + } + }); + assert.equal(QuickOutlineRegistry.all(model).length, 1); + + // deregister + disposable.dispose(); + assert.equal(QuickOutlineRegistry.all(model).length, 0); + + // all extension host provider appear as one + disposable = extHost.register('far', { + provideDocumentSymbols() { + return []; + } + }); + let disposable2 = extHost.register('far', { + provideDocumentSymbols() { + return []; + } + }); + assert.equal(QuickOutlineRegistry.all(model).length, 1); + + disposable.dispose(); + assert.equal(QuickOutlineRegistry.all(model).length, 1); + disposable2.dispose(); + assert.equal(QuickOutlineRegistry.all(model).length, 0); + }); + + test('DocumentSymbols, evil provider', function(done) { + + + let disposable = extHost.register('far', { + provideDocumentSymbols():any { + throw new Error('ddd'); + } + }); + let disposable2 = extHost.register('far', { + provideDocumentSymbols():any { + return [ + new phTypes.SymbolInformation('boo', phTypes.SymbolKind.Field, new phTypes.Range(0, 0, 0, 0)) + ]; + } + }); + + mainHost.getOutline(model.uri).then(result => { + assert.equal(result.length, 1); + done(); + + disposable.dispose(); + disposable2.dispose(); + + }, err => { + done(err); + }); + }); + + test('DocumentSymbols, data conversion', function(done) { + + let d = extHost.register('far', { + provideDocumentSymbols():any { + return [ + new phTypes.SymbolInformation('boo', + phTypes.SymbolKind.Field, + new phTypes.Range(0, 0, 0, 0), + model.uri, + 'far') + ]; + } + }); + + mainHost.getOutline(model.uri).then(result => { + assert.equal(result.length, 1); + let entry = result[0]; + + assert.equal(entry.label, 'boo'); + assert.equal(entry.containerLabel, 'far'); + assert.equal(entry.children, undefined); + assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); + d.dispose(); + done(); + + }, err => { + done(err); + }); + + }); +}); \ No newline at end of file