diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 68a4fa14348..26597d3c47b 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -136,31 +136,12 @@ export class QuickOpenEntry { /** * A good default sort implementation for quick open entries respecting highlight information * as well as associated resources. - * - * Supports fuzzy scoring when the option is enabled. */ - public static compare(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string, enableFuzzyScoring = false): number { + public static compare(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string): number { // Normalize if (lookFor) { - lookFor = Strings.stripWildcards(lookFor); - } - - // Fuzzy scoring is special - if (enableFuzzyScoring) { - const labelAScore = Strings.score(elementA.getLabel(), lookFor); - const labelBScore = Strings.score(elementB.getLabel(), lookFor); - - if (labelAScore !== labelBScore) { - return labelAScore > labelBScore ? -1 : 1; - } - - const descriptionAScore = Strings.score(elementA.getDescription(), lookFor); - const descriptionBScore = Strings.score(elementB.getDescription(), lookFor); - - if (descriptionAScore !== descriptionBScore) { - return descriptionAScore > descriptionBScore ? -1 : 1; - } + lookFor = Strings.stripWildcards(lookFor).toLowerCase(); } // Give matches with label highlights higher priority over @@ -186,15 +167,13 @@ export class QuickOpenEntry { } } - return compareAnything(nameA, nameB, lookFor ? lookFor.toLowerCase() : lookFor); + return compareAnything(nameA, nameB, lookFor); } /** * A good default highlight implementation for an entry with label and description. - * - * Supports fuzzy matching when the option is enabled. */ - public static highlight(entry: QuickOpenEntry, lookFor: string, enableFuzzyMatching = false): { labelHighlights: IHighlight[], descriptionHighlights: IHighlight[] } { + public static highlight(entry: QuickOpenEntry, lookFor: string, enableSeparateSubstringMatching = false): { labelHighlights: IHighlight[], descriptionHighlights: IHighlight[] } { let labelHighlights: IHighlight[] = []; let descriptionHighlights: IHighlight[] = []; @@ -203,24 +182,24 @@ export class QuickOpenEntry { // Highlight only inside label if (lookFor.indexOf(Paths.nativeSep) < 0) { - labelHighlights = Filters.matchesFuzzy(lookFor, entry.getLabel(), enableFuzzyMatching); + labelHighlights = Filters.matchesFuzzy(lookFor, entry.getLabel(), enableSeparateSubstringMatching); } // Highlight in label and description else { - descriptionHighlights = Filters.matchesFuzzy(Strings.trim(lookFor, Paths.nativeSep), entry.getDescription(), enableFuzzyMatching); + descriptionHighlights = Filters.matchesFuzzy(Strings.trim(lookFor, Paths.nativeSep), entry.getDescription(), enableSeparateSubstringMatching); // If we have no highlights, assume that the match is split among name and parent folder if (!descriptionHighlights || !descriptionHighlights.length) { - labelHighlights = Filters.matchesFuzzy(Paths.basename(lookFor), entry.getLabel(), enableFuzzyMatching); - descriptionHighlights = Filters.matchesFuzzy(Strings.trim(Paths.dirname(lookFor), Paths.nativeSep), entry.getDescription(), enableFuzzyMatching); + labelHighlights = Filters.matchesFuzzy(Paths.basename(lookFor), entry.getLabel(), enableSeparateSubstringMatching); + descriptionHighlights = Filters.matchesFuzzy(Strings.trim(Paths.dirname(lookFor), Paths.nativeSep), entry.getDescription(), enableSeparateSubstringMatching); } } } // Highlight by label otherwise else { - labelHighlights = Filters.matchesFuzzy(lookFor, entry.getLabel(), enableFuzzyMatching); + labelHighlights = Filters.matchesFuzzy(lookFor, entry.getLabel(), enableSeparateSubstringMatching); } return { labelHighlights, descriptionHighlights }; diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index e4841054fc7..f4005e8a4ca 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -92,5 +92,6 @@ export class LineMatch implements ILineMatch { export interface ISearchConfiguration extends IFilesConfiguration { search: { exclude: glob.IExpression; + fuzzyFilePicker: boolean; }; } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 6cffabc6760..e97ff5de865 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -13,7 +13,6 @@ import strings = require('vs/base/common/strings'); import filters = require('vs/base/common/filters'); import uuid = require('vs/base/common/uuid'); import types = require('vs/base/common/types'); -import {ListenerUnbind} from 'vs/base/common/eventEmitter'; import {Mode, IContext, IAutoFocus, IQuickNavigateConfiguration, IModel} from 'vs/base/parts/quickopen/browser/quickOpen'; import {QuickOpenEntryItem, QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup} from 'vs/base/parts/quickopen/browser/quickOpenModel'; import {QuickOpenWidget} from 'vs/base/parts/quickopen/browser/quickOpenWidget'; @@ -79,8 +78,6 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe private actionProvider = new ContributableActionProvider(); private previousValue = ''; private visibilityChangeTimeoutHandle: number; - private fuzzyMatchingEnabled: boolean; - private configurationListenerUnbind: ListenerUnbind; constructor( private eventService: IEventService, @@ -102,22 +99,8 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe this.inQuickOpenMode = keybindingService.createKey(QUICK_OPEN_MODE, false); - this.updateFuzzyMatching(contextService.getOptions().globalSettings.settings); - this._onShow = new EventSource<() => void>(); this._onHide = new EventSource<() => void>(); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Listen to configuration changes - this.configurationListenerUnbind = this.configurationService.addListener(ConfigurationServiceEventTypes.UPDATED, (e: IConfigurationServiceEvent) => this.updateFuzzyMatching(e.config)); - } - - private updateFuzzyMatching(configuration: any): void { - this.fuzzyMatchingEnabled = configuration.picker && configuration.picker.enableFuzzy; } public get onShow(): EventProvider<() => void> { @@ -136,10 +119,6 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe return this.editorHistoryModel; } - public isFuzzyMatchingEnabled(): boolean { - return this.fuzzyMatchingEnabled; - } - public create(): void { // Listen on Editor Input Changes to show in MRU List @@ -852,10 +831,6 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe this.pickOpenWidget.dispose(); } - if (this.configurationListenerUnbind) { - this.configurationListenerUnbind(); - } - super.dispose(); } } diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 57f23a42a93..6e03a888c50 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -73,19 +73,4 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('updateChannel', "Configure the update channel to receive updates from. Requires a restart after change.") } } -}); - -// Picker Configuration -configurationRegistry.registerConfiguration({ - 'id': 'picker', - 'order': 11, - 'title': nls.localize('filterConfigurationTitle', "Picker configuration"), - 'type': 'object', - 'properties': { - 'picker.enableFuzzy': { - 'type': 'boolean', - 'default': false, - 'description': nls.localize('enableFuzzy', "Enable or disable fuzzy matching and sorting in the picker.") - } - } }); \ No newline at end of file diff --git a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts index c63b6b46b95..d404ec26a34 100644 --- a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts +++ b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts @@ -14,6 +14,7 @@ import paths = require('vs/base/common/paths'); import filters = require('vs/base/common/filters'); import labels = require('vs/base/common/labels'); import {IRange} from 'vs/editor/common/editorCommon'; +import {ListenerUnbind} from 'vs/base/common/eventEmitter'; import {IAutoFocus} from 'vs/base/parts/quickopen/browser/quickOpen'; import {QuickOpenEntry, QuickOpenModel} from 'vs/base/parts/quickopen/browser/quickOpenModel'; import {QuickOpenHandler} from 'vs/workbench/browser/quickopen'; @@ -21,8 +22,10 @@ import {FileEntry, OpenFileHandler} from 'vs/workbench/parts/search/browser/open import {OpenSymbolHandler as _OpenSymbolHandler} from 'vs/workbench/parts/search/browser/openSymbolHandler'; import {IMessageService, Severity} from 'vs/platform/message/common/message'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {IWorkspaceContextService} from 'vs/workbench/services/workspace/common/contextService'; import {IQuickOpenService} from 'vs/workbench/services/quickopen/browser/quickOpenService'; +import {ISearchConfiguration} from 'vs/platform/search/common/search'; +import {IConfigurationService, IConfigurationServiceEvent, ConfigurationServiceEventTypes} from 'vs/platform/configuration/common/configuration'; // OpenSymbolHandler is used from an extension and must be in the main bundle file so it can load export const OpenSymbolHandler = _OpenSymbolHandler @@ -42,12 +45,15 @@ export class OpenAnythingHandler extends QuickOpenHandler { private delayer: ThrottledDelayer; private pendingSearch: TPromise; private isClosed: boolean; + private fuzzyMatchingEnabled: boolean; + private configurationListenerUnbind: ListenerUnbind; constructor( @IMessageService private messageService: IMessageService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IInstantiationService instantiationService: IInstantiationService, - @IQuickOpenService private quickOpenService: IQuickOpenService + @IQuickOpenService private quickOpenService: IQuickOpenService, + @IConfigurationService private configurationService: IConfigurationService ) { super(); @@ -60,6 +66,19 @@ export class OpenAnythingHandler extends QuickOpenHandler { this.resultsToSearchCache = Object.create(null); this.delayer = new ThrottledDelayer(OpenAnythingHandler.SEARCH_DELAY); + + this.updateFuzzyMatching(contextService.getOptions().globalSettings.settings); + + this.registerListeners(); + } + + private registerListeners(): void { + this.configurationListenerUnbind = this.configurationService.addListener(ConfigurationServiceEventTypes.UPDATED, (e: IConfigurationServiceEvent) => this.updateFuzzyMatching(e.config)); + } + + private updateFuzzyMatching(configuration: ISearchConfiguration): void { + this.fuzzyMatchingEnabled = configuration.search && configuration.search.fuzzyFilePicker; + this.openFileHandler.setFuzzyMatchingEnabled(this.fuzzyMatchingEnabled); } public getResults(searchValue: string): TPromise { @@ -144,7 +163,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { let result = [...results[0].entries, ...results[1].entries]; // Sort - result.sort((elementA, elementB) => QuickOpenEntry.compare(elementA, elementB, searchValue, this.quickOpenService.isFuzzyMatchingEnabled())); + result.sort((elementA, elementB) => this.sort(elementA, elementB, searchValue, this.fuzzyMatchingEnabled)); // Apply Range result.forEach((element) => { @@ -237,7 +256,6 @@ export class OpenAnythingHandler extends QuickOpenHandler { // Pattern match on results and adjust highlights let results: QuickOpenEntry[] = []; const searchInPath = searchValue.indexOf(paths.nativeSep) >= 0; - const enableFuzzy = this.quickOpenService.isFuzzyMatchingEnabled(); for (let i = 0; i < cachedEntries.length; i++) { let entry = cachedEntries[i]; @@ -248,19 +266,19 @@ export class OpenAnythingHandler extends QuickOpenHandler { // Check if this entry is a match for the search value let targetToMatch = searchInPath ? labels.getPathLabel(entry.getResource(), this.contextService) : entry.getLabel(); - if (!filters.matchesFuzzy(searchValue, targetToMatch, enableFuzzy)) { + if (!filters.matchesFuzzy(searchValue, targetToMatch, this.fuzzyMatchingEnabled)) { continue; } // Apply highlights - const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue, enableFuzzy); + const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue, this.fuzzyMatchingEnabled); entry.setHighlights(labelHighlights, descriptionHighlights); results.push(entry); } // Sort - results.sort((elementA, elementB) => QuickOpenEntry.compare(elementA, elementB, searchValue, enableFuzzy)); + results.sort((elementA, elementB) => this.sort(elementA, elementB, searchValue, this.fuzzyMatchingEnabled)); // Apply Range results.forEach((element) => { @@ -272,6 +290,28 @@ export class OpenAnythingHandler extends QuickOpenHandler { return results; } + private sort(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string, enableFuzzyScoring): number { + + // Fuzzy scoring is special + if (enableFuzzyScoring) { + const labelAScore = strings.score(elementA.getLabel(), lookFor); + const labelBScore = strings.score(elementB.getLabel(), lookFor); + + if (labelAScore !== labelBScore) { + return labelAScore > labelBScore ? -1 : 1; + } + + const descriptionAScore = strings.score(elementA.getDescription(), lookFor); + const descriptionBScore = strings.score(elementB.getDescription(), lookFor); + + if (descriptionAScore !== descriptionBScore) { + return descriptionAScore > descriptionBScore ? -1 : 1; + } + } + + return QuickOpenEntry.compare(elementA, elementB, lookFor); + } + public getGroupLabel(): string { return nls.localize('fileAndTypeResults', "file and symbol results"); } diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/parts/search/browser/openFileHandler.ts index e5acd140ad4..bc108d99a4b 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/parts/search/browser/openFileHandler.ts @@ -90,6 +90,7 @@ export class OpenFileHandler extends QuickOpenHandler { private queryBuilder: QueryBuilder; private delayer: ThrottledDelayer; private isStandalone: boolean; + private fuzzyMatchingEnabled: boolean; constructor( @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -113,6 +114,10 @@ export class OpenFileHandler extends QuickOpenHandler { this.isStandalone = standalone; } + public setFuzzyMatchingEnabled(enabled: boolean): void { + this.fuzzyMatchingEnabled = enabled; + } + public getResults(searchValue: string): TPromise { searchValue = searchValue.trim(); let promise: TPromise; @@ -135,7 +140,7 @@ export class OpenFileHandler extends QuickOpenHandler { rootResources.push(this.contextService.getWorkspace().resource); } - let query: IQueryOptions = { filePattern: searchValue, matchFuzzy: this.quickOpenService.isFuzzyMatchingEnabled(), rootResources: rootResources }; + let query: IQueryOptions = { filePattern: searchValue, matchFuzzy: this.fuzzyMatchingEnabled, rootResources: rootResources }; return this.queryBuilder.file(query).then((query) => this.searchService.search(query)).then((complete) => { @@ -150,7 +155,7 @@ export class OpenFileHandler extends QuickOpenHandler { let entry = this.instantiationService.createInstance(FileEntry, label, description, fileMatch.resource); // Apply highlights - let {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue, this.quickOpenService.isFuzzyMatchingEnabled()); + let {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue, this.fuzzyMatchingEnabled); entry.setHighlights(labelHighlights, descriptionHighlights); results.push(entry); @@ -158,7 +163,7 @@ export class OpenFileHandler extends QuickOpenHandler { // Sort (standalone only) if (this.isStandalone) { - results = results.sort((elementA, elementB) => QuickOpenEntry.compare(elementA, elementB, searchValue, this.quickOpenService.isFuzzyMatchingEnabled())); + results = results.sort((elementA, elementB) => QuickOpenEntry.compare(elementA, elementB, searchValue)); } return results; diff --git a/src/vs/workbench/parts/search/browser/search.contribution.ts b/src/vs/workbench/parts/search/browser/search.contribution.ts index 8050b9ac646..a30775cfa61 100644 --- a/src/vs/workbench/parts/search/browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/browser/search.contribution.ts @@ -197,6 +197,11 @@ configurationRegistry.registerConfiguration({ } ] } + }, + 'search.fuzzyFilePicker': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('enableFuzzy', "Enable or disable fuzzy matching and sorting in the file picker.") } } }); \ No newline at end of file diff --git a/src/vs/workbench/services/quickopen/browser/quickOpenService.ts b/src/vs/workbench/services/quickopen/browser/quickOpenService.ts index 50f260bccb6..ece782330ab 100644 --- a/src/vs/workbench/services/quickopen/browser/quickOpenService.ts +++ b/src/vs/workbench/services/quickopen/browser/quickOpenService.ts @@ -125,9 +125,4 @@ export interface IQuickOpenService { * Allows to register on the event that quick open is hiding */ onHide: EventProvider<() => void>; - - /** - * A boolean to indicate if fuzzy matching is enabled or not. - */ - isFuzzyMatchingEnabled(): boolean; } \ No newline at end of file diff --git a/src/vs/workbench/test/browser/servicesTestUtils.ts b/src/vs/workbench/test/browser/servicesTestUtils.ts index bc9b85cc92b..75c6e8324ec 100644 --- a/src/vs/workbench/test/browser/servicesTestUtils.ts +++ b/src/vs/workbench/test/browser/servicesTestUtils.ts @@ -505,10 +505,6 @@ export class TestQuickOpenService implements QuickOpenService.IQuickOpenService return null; } - public isFuzzyMatchingEnabled(): boolean { - return false; - } - public removeEditorHistoryEntry(input: WorkbenchEditorCommon.EditorInput): void {} public dispose() {} public quickNavigate(): void {}