diff --git a/extensions/typescript/src/features/documentSymbolProvider.ts b/extensions/typescript/src/features/documentSymbolProvider.ts index 6d1de81747c..b54143fac74 100644 --- a/extensions/typescript/src/features/documentSymbolProvider.ts +++ b/extensions/typescript/src/features/documentSymbolProvider.ts @@ -5,7 +5,7 @@ 'use strict'; -import { DocumentSymbolProvider, SymbolInformation, SymbolKind, TextDocument, Range, CancellationToken } from 'vscode'; +import { DocumentSymbolProvider, SymbolInformation, SymbolKind, TextDocument, Range, Location, CancellationToken } from 'vscode'; import * as Proto from '../protocol'; import * as PConst from '../protocol.const'; @@ -50,8 +50,8 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP function convert(bucket: SymbolInformation[], item: Proto.NavigationBarItem, containerLabel?: string): void { let result = new SymbolInformation(item.text, outlineTypeTable[item.kind] || SymbolKind.Variable, - textSpan2Range(item.spans[0]), resource.uri, - containerLabel); + containerLabel, + new Location(resource.uri, textSpan2Range(item.spans[0]))); if (item.childItems && item.childItems.length > 0) { for (let child of item.childItems) { diff --git a/extensions/typescript/src/features/workspaceSymbolProvider.ts b/extensions/typescript/src/features/workspaceSymbolProvider.ts index 011edce9df9..67c2b761da0 100644 --- a/extensions/typescript/src/features/workspaceSymbolProvider.ts +++ b/extensions/typescript/src/features/workspaceSymbolProvider.ts @@ -5,7 +5,7 @@ 'use strict'; -import { workspace, Uri, WorkspaceSymbolProvider, SymbolInformation, SymbolKind, Range, CancellationToken } from 'vscode'; +import { workspace, Uri, WorkspaceSymbolProvider, SymbolInformation, SymbolKind, Range, Location, CancellationToken } from 'vscode'; import * as Proto from '../protocol'; import { ITypescriptServiceClient } from '../typescriptService'; @@ -65,8 +65,8 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo if (item.kind === 'method' || item.kind === 'function') { label += '()'; } - result.push(new SymbolInformation(label, _kindMapping[item.kind], range, - this.client.asUrl(item.file), item.containerName)); + result.push(new SymbolInformation(label, _kindMapping[item.kind], item.containerName, + new Location(this.client.asUrl(item.file), range))); } return result; } else { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 023f7ed309b..a4686e55d87 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1738,6 +1738,18 @@ declare namespace vscode { location: Location; /** + * Creates a new symbol information object. + * + * @param name The name of the symbol. + * @param kind The kind of the symbol. + * @param containerName The name of the symbol containing the symbol. + * @param location The the location of the symbol. + */ + constructor(name: string, kind: SymbolKind, containerName: string, location: Location); + + /** + * @deprecated Please use the constructor taking a [location](#Location) object. + * * Creates a new symbol information object. * * @param name The name of the symbol. @@ -1774,7 +1786,9 @@ declare namespace vscode { /** * Project-wide search for a symbol matching the given query string. It is up to the provider - * how to search given the query string, like substring, indexOf etc. + * how to search given the query string, like substring, indexOf etc. To improve performance implementors can + * skip the [location](#SymbolInformation.location) of symbols and implement `resolveWorkspaceSymbol` to do that + * later. * * @param query A non-empty query string. * @param token A cancellation token. @@ -1782,6 +1796,20 @@ declare namespace vscode { * signaled by returning `undefined`, `null`, or an empty array. */ provideWorkspaceSymbols(query: string, token: CancellationToken): SymbolInformation[] | Thenable; + + /** + * Given a symbol fill in its [location](#SymbolInformation.location). This method is called whenever a symbol + * is selected in the UI. Providers can implement this method and return incomplete symbols from + * [`provideWorkspaceSymbols`](#WorkspaceSymbolProvider.provideWorkspaceSymbols) which often helps to improve + * performance. + * + * @param symbol The symbol that is to be resolved. Guaranteed to be an instance of an object returned from an + * earlier call to `provideWorkspaceSymbols`. + * @param token A cancellation token. + * @return The resolved symbol or a thenable that resolves to that. When no result is returned, + * the given `symbol` is used. + */ + resolveWorkspaceSymbol?: (symbol: SymbolInformation, token: CancellationToken) => SymbolInformation | Thenable; } /** @@ -3710,9 +3738,9 @@ declare namespace vscode { /** * Register a workspace symbol provider. * - * Multiple providers can be registered for a language. In that case providers are asked in - * parallel and the results are merged. A failing provider (rejected promise or exception) will - * not cause a failure of the whole operation. + * Multiple providers can be registered. In that case providers are asked in parallel and + * the results are merged. A failing provider (rejected promise or exception) will not cause + * a failure of the whole operation. * * @param provider A workspace symbol provider. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 490e1c38beb..7aceffd2368 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -28,7 +28,7 @@ import * as modes from 'vs/editor/common/modes'; import {IResourceEdit} from 'vs/editor/common/services/bulkEdit'; import {IPickOpenEntry, IPickOptions} from 'vs/workbench/services/quickopen/common/quickOpenService'; -import {ITypeBearing} from 'vs/workbench/parts/search/common/search'; +import {IWorkspaceSymbol} from 'vs/workbench/parts/search/common/search'; import {TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent} from './mainThreadEditorsTracker'; import {EndOfLine} from './extHostTypes'; @@ -263,7 +263,8 @@ export abstract class ExtHostLanguageFeaturesShape { $provideDocumentFormattingEdits(handle: number, resource: URI, options: modes.FormattingOptions): TPromise { throw ni(); } $provideDocumentRangeFormattingEdits(handle: number, resource: URI, range: editorCommon.IRange, options: modes.FormattingOptions): TPromise { throw ni(); } $provideOnTypeFormattingEdits(handle: number, resource: URI, position: editorCommon.IPosition, ch: string, options: modes.FormattingOptions): TPromise { throw ni(); } - $getNavigateToItems(handle: number, search: string): TPromise { throw ni(); } + $provideWorkspaceSymbols(handle: number, search: string): TPromise { throw ni(); } + $resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbol): TPromise { throw ni(); } $provideRenameEdits(handle: number, resource: URI, position: editorCommon.IPosition, newName: string): TPromise { throw ni(); } $provideCompletionItems(handle: number, resource: URI, position: editorCommon.IPosition): TPromise { throw ni(); } $resolveCompletionItem(handle: number, resource: URI, position: editorCommon.IPosition, suggestion: modes.ISuggestion): TPromise { throw ni(); } diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index 4f5c1625e33..3b82ceedcc0 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -16,7 +16,7 @@ import {ICommandHandlerDescription} from 'vs/platform/commands/common/commands'; import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; import {IQuickFix2} from 'vs/editor/contrib/quickFix/common/quickFix'; import {IOutline} from 'vs/editor/contrib/quickOpen/common/quickOpen'; -import {ITypeBearing} from 'vs/workbench/parts/search/common/search'; +import {IWorkspaceSymbolProvider, IWorkspaceSymbol} from 'vs/workbench/parts/search/common/search'; import {ICodeLensData} from 'vs/editor/contrib/codelens/common/codelens'; export function registerApiCommands(commands:ExtHostCommands) { @@ -240,10 +240,14 @@ class ExtHostApiCommands { * @return A promise that resolves to an array of symbol information. */ private _executeWorkspaceSymbolProvider(query: string): Thenable { - return this._commands.executeCommand('_executeWorkspaceSymbolProvider', { query }).then(value => { + return this._commands.executeCommand<[IWorkspaceSymbolProvider, IWorkspaceSymbol[]][]>('_executeWorkspaceSymbolProvider', { query }).then(value => { + const result: types.SymbolInformation[] = []; if (Array.isArray(value)) { - return value.map(typeConverters.toSymbolInformation); + for (const tuple of value) { + result.push(...tuple[1].map(typeConverters.toSymbolInformation)); + } } + return result; }); } diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index d2a45eb6a94..8e6aaed4dfc 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -16,7 +16,7 @@ import * as modes from 'vs/editor/common/modes'; 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 {INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search'; +import {IWorkspaceSymbolProvider, IWorkspaceSymbol} from 'vs/workbench/parts/search/common/search'; import {asWinJsPromise, ShallowCancelThenPromise} from 'vs/base/common/async'; import {MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape} from './extHost.protocol'; import {regExpLeadsToEndlessLoop} from 'vs/base/common/strings'; @@ -394,21 +394,50 @@ class OnTypeFormattingAdapter { } } -class NavigateTypeAdapter implements INavigateTypesSupport { +interface MyWorkspaceSymbol extends IWorkspaceSymbol { + idx: number; +} + +class NavigateTypeAdapter implements IWorkspaceSymbolProvider { private _provider: vscode.WorkspaceSymbolProvider; + private _cache: vscode.SymbolInformation[]; constructor(provider: vscode.WorkspaceSymbolProvider) { this._provider = provider; } - getNavigateToItems(search: string): TPromise { + provideWorkspaceSymbols(search: string): TPromise { + + this._cache = []; + return asWinJsPromise(token => this._provider.provideWorkspaceSymbols(search, token)).then(value => { if (Array.isArray(value)) { - return value.map(TypeConverters.fromSymbolInformation); + this._cache = value; + return value.map((item, idx) => { + const result = TypeConverters.fromSymbolInformation(item); + result.idx = idx; + return result; + }); } }); } + + resolveWorkspaceSymbol(item: IWorkspaceSymbol): TPromise { + + if (typeof this._provider.resolveWorkspaceSymbol !== 'function') { + return; + } + + const idx = (item).idx; + if(typeof idx !== 'number') { + return; + } + + return asWinJsPromise(token => this._provider.resolveWorkspaceSymbol(this._cache[idx], token)).then(value => { + return value && TypeConverters.fromSymbolInformation(value); + }); + } } class RenameAdapter { @@ -795,8 +824,12 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $getNavigateToItems(handle: number, search: string): TPromise { - return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.getNavigateToItems(search)); + $provideWorkspaceSymbols(handle: number, search: string): TPromise { + return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.provideWorkspaceSymbols(search)); + } + + $resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbol): TPromise { + return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.resolveWorkspaceSymbol(symbol)); } // --- rename diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 777667e6842..f3a9aaa3b61 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -12,7 +12,7 @@ import * as modes from 'vs/editor/common/modes'; import * as types from './extHostTypes'; import {Position as EditorPosition} from 'vs/platform/editor/common/editor'; import {IPosition, ISelection, IRange, IDecorationOptions, ISingleEditOperation} from 'vs/editor/common/editorCommon'; -import {ITypeBearing} from 'vs/workbench/parts/search/common/search'; +import {IWorkspaceSymbol} from 'vs/workbench/parts/search/common/search'; import * as vscode from 'vscode'; import URI from 'vs/base/common/uri'; @@ -190,23 +190,22 @@ export namespace SymbolInformation { } } -export function fromSymbolInformation(info: vscode.SymbolInformation): ITypeBearing { - return { +export function fromSymbolInformation(info: vscode.SymbolInformation): IWorkspaceSymbol { + return { name: info.name, type: types.SymbolKind[info.kind || types.SymbolKind.Property].toLowerCase(), - range: fromRange(info.location.range), - resourceUri: info.location.uri, containerName: info.containerName, - parameters: '', + range: info.location && fromRange(info.location.range), + resource: info.location && info.location.uri, }; } -export function toSymbolInformation(bearing: ITypeBearing): types.SymbolInformation { +export function toSymbolInformation(bearing: IWorkspaceSymbol): types.SymbolInformation { return new types.SymbolInformation(bearing.name, types.SymbolKind[bearing.type.charAt(0).toUpperCase() + bearing.type.substr(1)], - toRange(bearing.range), - bearing.resourceUri, - bearing.containerName); + bearing.containerName, + new types.Location(bearing.resource, toRange(bearing.range)) + ); } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index b10990dec48..8966d776c69 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -544,13 +544,15 @@ export class Location { uri: URI; range: Range; - constructor(uri: URI, range: Range | Position) { + constructor(uri: URI, rangeOrPosition: Range | Position) { this.uri = uri; - if (range instanceof Range) { - this.range = range; - } else if (range instanceof Position) { - this.range = new Range(range, range); + if (!rangeOrPosition) { + //that's OK + } else if (rangeOrPosition instanceof Range) { + this.range = rangeOrPosition; + } else if (rangeOrPosition instanceof Position) { + this.range = new Range(rangeOrPosition, rangeOrPosition); } else { throw new Error('Illegal argument'); } @@ -663,11 +665,22 @@ export class SymbolInformation { kind: SymbolKind; containerName: string; - constructor(name: string, kind: SymbolKind, range: Range, uri?: URI, containerName?: string) { + constructor(name: string, kind: SymbolKind, containerName: string, location: Location); + constructor(name: string, kind: SymbolKind, range: Range, uri?: URI, containerName?: string); + constructor(name: string, kind: SymbolKind, rangeOrContainer: string | Range, locationOrUri?: Location | URI, containerName?: string) { this.name = name; this.kind = kind; - this.location = new Location(uri, range); this.containerName = containerName; + + if (typeof rangeOrContainer === 'string') { + this.containerName = rangeOrContainer; + } + + if (locationOrUri instanceof Location) { + this.location = locationOrUri; + } else if (rangeOrContainer instanceof Range) { + this.location = new Location( locationOrUri, rangeOrContainer); + } } toJSON(): any { diff --git a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts index d6c87d52e3c..ae821aab07c 100644 --- a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts @@ -10,7 +10,7 @@ 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 {WorkspaceSymbolProviderRegistry, IWorkspaceSymbolProvider, IWorkspaceSymbol} 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'; @@ -152,9 +152,12 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape // --- navigate type $registerNavigateTypeSupport(handle: number): TPromise { - this._registrations[handle] = NavigateTypesSupportRegistry.register({ - getNavigateToItems: (search: string): TPromise => { - return this._proxy.$getNavigateToItems(handle, search); + this._registrations[handle] = WorkspaceSymbolProviderRegistry.register({ + provideWorkspaceSymbols: (search: string): TPromise => { + return this._proxy.$provideWorkspaceSymbols(handle, search); + }, + resolveWorkspaceSymbol: (item: IWorkspaceSymbol): TPromise => { + return this._proxy.$resolveWorkspaceSymbol(handle, item); } }); return undefined; diff --git a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts b/src/vs/workbench/parts/search/browser/openSymbolHandler.ts index bd48f0edd7c..aaa46d6ae56 100644 --- a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/parts/search/browser/openSymbolHandler.ts @@ -4,100 +4,112 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {TPromise} from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); -import {ThrottledDelayer} from 'vs/base/common/async'; import URI from 'vs/base/common/uri'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {onUnexpectedError} from 'vs/base/common/errors'; +import {ThrottledDelayer} from 'vs/base/common/async'; import {QuickOpenHandler, EditorQuickOpenEntry} from 'vs/workbench/browser/quickopen'; -import {QuickOpenModel, QuickOpenEntry, IHighlight} from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import {IAutoFocus} from 'vs/base/parts/quickopen/common/quickOpen'; +import {QuickOpenModel, QuickOpenEntry} from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import {IAutoFocus, Mode, IEntryRunContext} from 'vs/base/parts/quickopen/common/quickOpen'; import filters = require('vs/base/common/filters'); -import {IRange} from 'vs/editor/common/editorCommon'; +import {Range} from 'vs/editor/common/core/range'; import {EditorInput, IWorkbenchEditorConfiguration} from 'vs/workbench/common/editor'; import labels = require('vs/base/common/labels'); import {IResourceInput} from 'vs/platform/editor/common/editor'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {IModeService} from 'vs/editor/common/services/modeService'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {ITypeBearing, getNavigateToItems} from 'vs/workbench/parts/search/common/search'; +import {IWorkspaceSymbol, IWorkspaceSymbolProvider, getWorkspaceSymbols} from 'vs/workbench/parts/search/common/search'; class SymbolEntry extends EditorQuickOpenEntry { - private name: string; - private parameters: string; - private description: string; - private resource: URI; - private type: string; - private range: IRange; + + private _bearingResolve: TPromise; constructor( - name: string, - parameters: string, - description: string, - resource: URI, - type: string, - range: IRange, - highlights: IHighlight[], - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IConfigurationService private configurationService: IConfigurationService + private _bearing: IWorkspaceSymbol, + private _provider: IWorkspaceSymbolProvider, + @IConfigurationService private _configurationService: IConfigurationService, + @IWorkspaceContextService private _contextService: IWorkspaceContextService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService ) { super(editorService); - - this.name = name; - this.parameters = parameters; - this.description = description; - this.resource = resource; - this.type = type; - this.range = range; - this.setHighlights(highlights); } public getLabel(): string { - return this.name + this.parameters; + return this._bearing.name; } public getAriaLabel(): string { return nls.localize('entryAriaLabel', "{0}, symbols picker", this.getLabel()); } - public getName(): string { - return this.name; - } - - public getParameters(): string { - return this.parameters; - } - public getDescription(): string { - return this.description; - } - - public getType(): string { - return this.type; + let result = this._bearing.containerName; + if (!result && this._bearing.resource) { + result = labels.getPathLabel(this._bearing.resource, this._contextService); + } + return result; } public getIcon(): string { - return this.type; + return this._bearing.type; + } + + public getResource(): URI { + return this._bearing.resource; + } + + public run(mode: Mode, context: IEntryRunContext): boolean { + + // resolve this type bearing if neccessary + if (!this._bearingResolve + && typeof this._provider.resolveWorkspaceSymbol === 'function' + && !this._bearing.range + ) { + + this._bearingResolve = this._provider.resolveWorkspaceSymbol(this._bearing).then(result => { + this._bearing = result || this._bearing; + return this; + }, onUnexpectedError); + } + + TPromise.as(this._bearingResolve) + .then(_ => super.run(mode, context)) + .done(undefined, onUnexpectedError); + + return true; } public getInput(): IResourceInput | EditorInput { let input: IResourceInput = { - resource: this.resource, + resource: this._bearing.resource, options: { - pinned: !this.configurationService.getConfiguration().workbench.editor.enablePreviewFromQuickOpen + pinned: !this._configurationService.getConfiguration().workbench.editor.enablePreviewFromQuickOpen } }; - if (this.range) { - input.options.selection = { - startLineNumber: this.range.startLineNumber, - startColumn: this.range.startColumn - }; + if (this._bearing.range) { + input.options.selection = Range.collapseToStart(this._bearing.range); } return input; } + + public static compare(elementA: SymbolEntry, elementB: SymbolEntry, searchValue: string): number { + + // Sort by Type if name is identical + const elementAName = elementA.getLabel().toLowerCase(); + const elementBName = elementB.getLabel().toLowerCase(); + if (elementAName === elementBName) { + let elementAType = elementA._bearing.type; + let elementBType = elementB._bearing.type; + return elementAType.localeCompare(elementBType); + } + + return QuickOpenEntry.compare(elementA, elementB, searchValue); + } } export interface IOpenSymbolOptions { @@ -113,12 +125,7 @@ export class OpenSymbolHandler extends QuickOpenHandler { private delayer: ThrottledDelayer; private options: IOpenSymbolOptions; - constructor( - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IModeService private modeService: IModeService, - @IInstantiationService private instantiationService: IInstantiationService, - @IWorkspaceContextService private contextService: IWorkspaceContextService - ) { + constructor(@IInstantiationService private instantiationService: IInstantiationService) { super(); this.delayer = new ThrottledDelayer(OpenSymbolHandler.SEARCH_DELAY); @@ -150,75 +157,37 @@ export class OpenSymbolHandler extends QuickOpenHandler { return promise.then(e => new QuickOpenModel(e)); } - private doGetResults(searchValue: string): TPromise { - return getNavigateToItems(searchValue).then(bearings => { - return this.toQuickOpenEntries(bearings, searchValue); + private doGetResults(searchValue: string): TPromise { + return getWorkspaceSymbols(searchValue).then(tuples => { + const result: SymbolEntry[] = []; + for (const tuple of tuples) { + const [provider, bearings] = tuple; + this.fillInSymbolEntries(result, provider, bearings, searchValue); + } + + // Sort (Standalone only) + if (!this.options.skipSorting) { + searchValue = searchValue.toLowerCase(); + return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); + } else { + return result; + } }); } - private toQuickOpenEntries(types: ITypeBearing[], searchValue: string): SymbolEntry[] { - let results: SymbolEntry[] = []; + private fillInSymbolEntries(bucket: SymbolEntry[], provider: IWorkspaceSymbolProvider, types: IWorkspaceSymbol[], searchValue: string): void { // Convert to Entries - types.forEach(element => { + for (const element of types) { + if (this.options.skipLocalSymbols && !!element.containerName) { - return; // ignore local symbols if we are told so + continue; // ignore local symbols if we are told so } - // Find Highlights - let highlights = filters.matchesFuzzy(searchValue, element.name); - if (highlights) { - let resource = element.resourceUri; - if (resource.scheme === 'file') { - let path = labels.getPathLabel(resource, this.contextService); - - let container: string = void (0); - - // Type is top level in module with path spec, use path info then (/folder/file.ts) - if (element.containerName === path) { - container = path; - } - - // Type is top level in module with url spec, find last segment to produce a short description (http://.../file.ts) - else if (element.containerName === resource.toString() && element.containerName.indexOf('/') >= 0) { - container = element.containerName.substr(element.containerName.lastIndexOf('/') + 1); - } - - // Type is inside a module or other type, find last segment to produce a short description (.../folder/file.ts.CompResult) - else if (element.containerName && element.containerName.indexOf('.') >= 0) { - container = element.containerName.substr(element.containerName.lastIndexOf('.') + 1); - } - - // Fallback - else { - container = element.containerName || path; - } - - results.push(this.instantiationService.createInstance(SymbolEntry, element.name, element.parameters, container, resource, element.type, element.range, highlights)); - } - } - }); - - // Sort (Standalone only) - if (!this.options.skipSorting) { - return results.sort(this.sort.bind(this, searchValue.toLowerCase())); + const entry = this.instantiationService.createInstance(SymbolEntry, element, provider); + entry.setHighlights(filters.matchesFuzzy(searchValue, entry.getLabel())); + bucket.push(entry); } - - return results; - } - - private sort(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - - // Sort by Type if name is identical - let elementAName = elementA.getName().toLowerCase(); - let elementBName = elementB.getName().toLowerCase(); - if (elementAName === elementBName) { - let elementAType = elementA.getType(); - let elementBType = elementB.getType(); - return elementAType.localeCompare(elementBType); - } - - return QuickOpenEntry.compare(elementA, elementB, searchValue); } public getGroupLabel(): string { diff --git a/src/vs/workbench/parts/search/common/search.ts b/src/vs/workbench/parts/search/common/search.ts index b315d74e59b..585137639df 100644 --- a/src/vs/workbench/parts/search/common/search.ts +++ b/src/vs/workbench/parts/search/common/search.ts @@ -15,24 +15,24 @@ import URI from 'vs/base/common/uri'; /** * Interface used to navigate to types by value. */ -export interface ITypeBearing { - containerName: string; +export interface IWorkspaceSymbol { name: string; - parameters: string; type: string; + containerName: string; range: IRange; - resourceUri: URI; + resource: URI; } -export interface INavigateTypesSupport { - getNavigateToItems: (search: string) => TPromise; +export interface IWorkspaceSymbolProvider { + provideWorkspaceSymbols(search: string): TPromise; + resolveWorkspaceSymbol?: (item: IWorkspaceSymbol) => TPromise; } -export namespace NavigateTypesSupportRegistry { +export namespace WorkspaceSymbolProviderRegistry { - const _supports: INavigateTypesSupport[] = []; + const _supports: IWorkspaceSymbolProvider[] = []; - export function register(support: INavigateTypesSupport): IDisposable { + export function register(support: IWorkspaceSymbolProvider): IDisposable { if (support) { _supports.push(support); @@ -51,26 +51,24 @@ export namespace NavigateTypesSupportRegistry { }; } - export function all(): INavigateTypesSupport[] { + export function all(): IWorkspaceSymbolProvider[] { return _supports.slice(0); } } -export function getNavigateToItems(query: string): TPromise { +export function getWorkspaceSymbols(query: string): TPromise<[IWorkspaceSymbolProvider, IWorkspaceSymbol[]][]> { - const promises = NavigateTypesSupportRegistry.all().map(support => { - return support.getNavigateToItems(query).then(value => value, onUnexpectedError); - }); + const result: [IWorkspaceSymbolProvider, IWorkspaceSymbol[]][] = []; - return TPromise.join(promises).then(all => { - const result: ITypeBearing[] = []; - for (let bearings of all) { - if (Array.isArray(bearings)) { - result.push(...bearings); + const promises = WorkspaceSymbolProviderRegistry.all().map(support => { + return support.provideWorkspaceSymbols(query).then(value => { + if (Array.isArray(value)) { + result.push([support, value]); } - } - return result; + }, onUnexpectedError); }); + + return TPromise.join(promises).then(_ => result); } CommonEditorRegistry.registerLanguageCommand('_executeWorkspaceSymbolProvider', function (accessor, args: { query: string; }) { @@ -78,5 +76,5 @@ CommonEditorRegistry.registerLanguageCommand('_executeWorkspaceSymbolProvider', if (typeof query !== 'string') { throw illegalArgument(); } - return getNavigateToItems(query); + return getWorkspaceSymbols(query); }); diff --git a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts index e2da57a4192..a926d755727 100644 --- a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts @@ -30,7 +30,7 @@ import {getHover} from 'vs/editor/contrib/hover/common/hover'; import {getOccurrencesAtPosition} from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; import {provideReferences} from 'vs/editor/contrib/referenceSearch/common/referenceSearch'; import {getCodeActions} from 'vs/editor/contrib/quickFix/common/quickFix'; -import {getNavigateToItems} from 'vs/workbench/parts/search/common/search'; +import {getWorkspaceSymbols} from 'vs/workbench/parts/search/common/search'; import {rename} from 'vs/editor/contrib/rename/common/rename'; import {provideSignatureHelp} from 'vs/editor/contrib/parameterHints/common/parameterHints'; import {provideSuggestionItems} from 'vs/editor/contrib/suggest/common/suggest'; @@ -649,8 +649,12 @@ suite('ExtHostLanguageFeatures', function() { return threadService.sync().then(() => { - return getNavigateToItems('').then(value => { + return getWorkspaceSymbols('').then(value => { assert.equal(value.length, 1); + const [first] = value; + const [, symbols] = first; + assert.equal(symbols.length, 1); + assert.equal(symbols[0].name, 'testing'); }); }); }); diff --git a/src/vs/workbench/test/node/api/extHostTypes.test.ts b/src/vs/workbench/test/node/api/extHostTypes.test.ts index 652f5ef1fb4..1c4e8856f0c 100644 --- a/src/vs/workbench/test/node/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/node/api/extHostTypes.test.ts @@ -409,4 +409,11 @@ suite('ExtHostTypes', function () { assertToJSON(item, { label: 'complete', kind: 'Interface' }); }); + + test('SymbolInformation, old ctor', function () { + + let info = new types.SymbolInformation('foo', types.SymbolKind.Array, new types.Range(1, 1, 2, 3)); + assert.ok(info.location instanceof types.Location); + assert.equal(info.location.uri, undefined); + }); }); \ No newline at end of file