提交 7a2b6fd7 编写于 作者: B Benjamin Pasero

more predictable recent files sorting (#10690, #20546)

上级 d869040c
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"version": "0.1.20", "version": "0.1.20",
"license": "MIT", "license": "MIT",
"repositoryURL": "https://github.com/joshaven/string_score", "repositoryURL": "https://github.com/joshaven/string_score",
"description": "The file scorer.ts was inspired by the string_score algorithm from Joshaven Potter.", "description": "The file quickOpenScorer.ts was inspired by the string_score algorithm from Joshaven Potter.",
"licenseDetail": [ "licenseDetail": [
"This software is released under the MIT license:", "This software is released under the MIT license:",
"", "",
......
...@@ -122,7 +122,7 @@ function isLower(code: number): boolean { ...@@ -122,7 +122,7 @@ function isLower(code: number): boolean {
return CharCode.a <= code && code <= CharCode.z; return CharCode.a <= code && code <= CharCode.z;
} }
function isUpper(code: number): boolean { export function isUpper(code: number): boolean {
return CharCode.A <= code && code <= CharCode.Z; return CharCode.A <= code && code <= CharCode.Z;
} }
...@@ -421,7 +421,7 @@ function printTable(table: number[][], pattern: string, patternLen: number, word ...@@ -421,7 +421,7 @@ function printTable(table: number[][], pattern: string, patternLen: number, word
return ret; return ret;
} }
function isSeparatorAtPos(value: string, index: number): boolean { export function isSeparatorAtPos(value: string, index: number): boolean {
if (index < 0 || index >= value.length) { if (index < 0 || index >= value.length) {
return false; return false;
} }
......
...@@ -10,9 +10,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; ...@@ -10,9 +10,6 @@ import { TPromise } from 'vs/base/common/winjs.base';
import types = require('vs/base/common/types'); import types = require('vs/base/common/types');
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
import filters = require('vs/base/common/filters');
import strings = require('vs/base/common/strings');
import paths = require('vs/base/common/paths');
import { IconLabel, IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IconLabel, IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode } from 'vs/base/parts/quickopen/common/quickOpen'; import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode } from 'vs/base/parts/quickopen/common/quickOpen';
import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
...@@ -24,7 +21,7 @@ import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidge ...@@ -24,7 +21,7 @@ import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidge
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
import { OS } from 'vs/base/common/platform'; import { OS } from 'vs/base/common/platform';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IItemAccessor } from 'vs/base/common/scorer'; import { IItemAccessor } from 'vs/base/parts/quickopen/common/quickOpenScorer';
export interface IContext { export interface IContext {
event: any; event: any;
...@@ -174,112 +171,6 @@ export class QuickOpenEntry { ...@@ -174,112 +171,6 @@ export class QuickOpenEntry {
return false; return false;
} }
/**
* A good default sort implementation for quick open entries respecting highlight information
* as well as associated resources.
*/
public static compare(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string): number {
// Give matches with label highlights higher priority over
// those with only description highlights
const labelHighlightsA = elementA.getHighlights()[0] || [];
const labelHighlightsB = elementB.getHighlights()[0] || [];
if (labelHighlightsA.length && !labelHighlightsB.length) {
return -1;
} else if (!labelHighlightsA.length && labelHighlightsB.length) {
return 1;
}
// Fallback to the full path if labels are identical and we have associated resources
let nameA = elementA.getLabel();
let nameB = elementB.getLabel();
if (nameA === nameB) {
const resourceA = elementA.getResource();
const resourceB = elementB.getResource();
if (resourceA && resourceB) {
nameA = resourceA.fsPath;
nameB = resourceB.fsPath;
}
}
return compareAnything(nameA, nameB, lookFor);
}
/**
* A good default highlight implementation for an entry with label and description.
*/
public static highlight(entry: QuickOpenEntry, lookFor: string, fuzzyHighlight = false): { labelHighlights: IHighlight[], descriptionHighlights: IHighlight[] } {
let labelHighlights: IHighlight[] = [];
const descriptionHighlights: IHighlight[] = [];
const normalizedLookFor = strings.stripWildcards(lookFor);
const label = entry.getLabel();
const description = entry.getDescription();
// Highlight file aware
if (entry.getResource()) {
// Highlight entire label and description if searching for full absolute path
const fsPath = entry.getResource().fsPath;
if (lookFor.length === fsPath.length && lookFor.toLowerCase() === fsPath.toLowerCase()) {
labelHighlights.push({ start: 0, end: label.length });
descriptionHighlights.push({ start: 0, end: description.length });
}
// Fuzzy/Full-Path: Highlight is special
else if (fuzzyHighlight || lookFor.indexOf(paths.nativeSep) >= 0) {
const candidateLabelHighlights = filters.matchesFuzzy(lookFor, label, fuzzyHighlight);
if (!candidateLabelHighlights) {
const pathPrefix = description ? (description + paths.nativeSep) : '';
const pathPrefixLength = pathPrefix.length;
// If there are no highlights in the label, build a path out of description and highlight and match on both,
// then extract the individual label and description highlights back to the original positions
let pathHighlights = filters.matchesFuzzy(lookFor, pathPrefix + label, fuzzyHighlight);
if (!pathHighlights && lookFor !== normalizedLookFor) {
pathHighlights = filters.matchesFuzzy(normalizedLookFor, pathPrefix + label, fuzzyHighlight);
}
if (pathHighlights) {
pathHighlights.forEach(h => {
// Match overlaps label and description part, we need to split it up
if (h.start < pathPrefixLength && h.end > pathPrefixLength) {
labelHighlights.push({ start: 0, end: h.end - pathPrefixLength });
descriptionHighlights.push({ start: h.start, end: pathPrefixLength });
}
// Match on label part
else if (h.start >= pathPrefixLength) {
labelHighlights.push({ start: h.start - pathPrefixLength, end: h.end - pathPrefixLength });
}
// Match on description part
else {
descriptionHighlights.push(h);
}
});
}
} else {
labelHighlights = candidateLabelHighlights;
}
}
// Highlight only inside label
else {
labelHighlights = filters.matchesFuzzy(lookFor, label);
}
}
// Highlight by label otherwise
else {
labelHighlights = filters.matchesFuzzy(lookFor, label);
}
return { labelHighlights, descriptionHighlights };
}
public isFile(): boolean { public isFile(): boolean {
return false; // TODO@Ben debt with editor history merging return false; // TODO@Ben debt with editor history merging
} }
...@@ -690,3 +581,37 @@ export class QuickOpenModel implements ...@@ -690,3 +581,37 @@ export class QuickOpenModel implements
return entry.run(mode, context); return entry.run(mode, context);
} }
} }
/**
* A good default sort implementation for quick open entries respecting highlight information
* as well as associated resources.
*/
export function compareEntries(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string): number {
// Give matches with label highlights higher priority over
// those with only description highlights
const labelHighlightsA = elementA.getHighlights()[0] || [];
const labelHighlightsB = elementB.getHighlights()[0] || [];
if (labelHighlightsA.length && !labelHighlightsB.length) {
return -1;
}
if (!labelHighlightsA.length && labelHighlightsB.length) {
return 1;
}
// Fallback to the full path if labels are identical and we have associated resources
let nameA = elementA.getLabel();
let nameB = elementB.getLabel();
if (nameA === nameB) {
const resourceA = elementA.getResource();
const resourceB = elementB.getResource();
if (resourceA && resourceB) {
nameA = resourceA.fsPath;
nameB = resourceB.fsPath;
}
}
return compareAnything(nameA, nameB, lookFor);
}
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
'use strict'; 'use strict';
import { compareAnything } from 'vs/base/common/comparers'; import { compareAnything } from 'vs/base/common/comparers';
import { matchesPrefix, IMatch, createMatches, matchesCamelCase } from 'vs/base/common/filters'; import { matchesPrefix, IMatch, createMatches, matchesCamelCase, isSeparatorAtPos, isUpper } from 'vs/base/common/filters';
import { isEqual, nativeSep } from 'vs/base/common/paths'; import { isEqual, nativeSep } from 'vs/base/common/paths';
export type Score = [number /* score */, number[] /* match positions */]; export type Score = [number /* score */, number[] /* match positions */];
...@@ -14,8 +14,6 @@ export type ScorerCache = { [key: string]: IItemScore }; ...@@ -14,8 +14,6 @@ export type ScorerCache = { [key: string]: IItemScore };
const NO_SCORE: Score = [0, []]; const NO_SCORE: Score = [0, []];
const wordPathBoundary = ['-', '_', ' ', '/', '\\', '.'];
// Based on material from: // Based on material from:
/*! /*!
BEGIN THIRD PARTY BEGIN THIRD PARTY
...@@ -45,7 +43,7 @@ BEGIN THIRD PARTY ...@@ -45,7 +43,7 @@ BEGIN THIRD PARTY
* Start of word/path bonus: 7 * Start of word/path bonus: 7
* Start of string bonus: 8 * Start of string bonus: 8
*/ */
export function _doScore(target: string, query: string, inverse?: boolean): Score { export function _doScore(target: string, query: string, fuzzy: boolean, inverse?: boolean): Score {
if (!target || !query) { if (!target || !query) {
return NO_SCORE; // return early if target or query are undefined return NO_SCORE; // return early if target or query are undefined
} }
...@@ -72,8 +70,34 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor ...@@ -72,8 +70,34 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor
startAt = target.length - 1; // inverse: from end of target to beginning startAt = target.length - 1; // inverse: from end of target to beginning
} }
// When not searching fuzzy, we require the query to be contained fully
// in the target string.
if (!fuzzy) {
let indexOfQueryInTarget: number;
if (!inverse) {
indexOfQueryInTarget = targetLower.indexOf(queryLower);
} else {
indexOfQueryInTarget = targetLower.lastIndexOf(queryLower);
}
if (indexOfQueryInTarget === -1) {
// console.log(`Characters not matching consecutively ${queryLower} within ${targetLower}`);
return NO_SCORE;
}
// Adjust the start position with the offset of the query
if (!inverse) {
startAt = indexOfQueryInTarget;
} else {
startAt = indexOfQueryInTarget + query.length;
}
}
let score = 0; let score = 0;
while (inverse ? index >= 0 : index < queryLen) { while (inverse ? index >= 0 : index < queryLen) {
// Check for query character being contained in target
let indexOf: number; let indexOf: number;
if (!inverse) { if (!inverse) {
indexOf = targetLower.indexOf(queryLower[index], startAt); indexOf = targetLower.indexOf(queryLower[index], startAt);
...@@ -82,10 +106,9 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor ...@@ -82,10 +106,9 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor
} }
if (indexOf < 0) { if (indexOf < 0) {
// console.log(`Character not part of target ${query[index]}`); // console.log(`Character not part of target ${query[index]}`);
score = 0; // This makes sure that the query is contained in the target score = 0;
break; break;
} }
...@@ -119,7 +142,7 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor ...@@ -119,7 +142,7 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor
} }
// After separator bonus // After separator bonus
else if (wordPathBoundary.some(w => w === target[indexOf - 1])) { else if (isSeparatorAtPos(target, indexOf - 1)) {
score += 7; score += 7;
// console.log('After separtor bonus: +7'); // console.log('After separtor bonus: +7');
...@@ -156,9 +179,6 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor ...@@ -156,9 +179,6 @@ export function _doScore(target: string, query: string, inverse?: boolean): Scor
return res; return res;
} }
function isUpper(code: number): boolean {
return 65 <= code && code <= 90;
}
/*! /*!
END THIRD PARTY END THIRD PARTY
*/ */
...@@ -209,7 +229,7 @@ const LABEL_PREFIX_SCORE = 1 << 17; ...@@ -209,7 +229,7 @@ const LABEL_PREFIX_SCORE = 1 << 17;
const LABEL_CAMELCASE_SCORE = 1 << 16; const LABEL_CAMELCASE_SCORE = 1 << 16;
const LABEL_SCORE_THRESHOLD = 1 << 15; const LABEL_SCORE_THRESHOLD = 1 << 15;
export function scoreItem<T>(item: T, query: string, accessor: IItemAccessor<T>, cache: ScorerCache): IItemScore { export function scoreItem<T>(item: T, query: string, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): IItemScore {
if (!item || !query) { if (!item || !query) {
return NO_ITEM_SCORE; // we need an item and query to score on at least return NO_ITEM_SCORE; // we need an item and query to score on at least
} }
...@@ -221,9 +241,11 @@ export function scoreItem<T>(item: T, query: string, accessor: IItemAccessor<T>, ...@@ -221,9 +241,11 @@ export function scoreItem<T>(item: T, query: string, accessor: IItemAccessor<T>,
const description = accessor.getItemDescription(item); const description = accessor.getItemDescription(item);
let cacheHash = label + query; let cacheHash: string;
if (description) { if (description) {
cacheHash += description; cacheHash = `${label}${description}${query}${fuzzy}`;
} else {
cacheHash = `${label}${query}${fuzzy}`;
} }
const cached = cache[cacheHash]; const cached = cache[cacheHash];
...@@ -231,13 +253,13 @@ export function scoreItem<T>(item: T, query: string, accessor: IItemAccessor<T>, ...@@ -231,13 +253,13 @@ export function scoreItem<T>(item: T, query: string, accessor: IItemAccessor<T>,
return cached; return cached;
} }
const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, accessor); const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, fuzzy, accessor);
cache[cacheHash] = itemScore; cache[cacheHash] = itemScore;
return itemScore; return itemScore;
} }
function doScoreItem<T>(label: string, description: string, path: string, query: string, accessor: IItemAccessor<T>): IItemScore { function doScoreItem<T>(label: string, description: string, path: string, query: string, fuzzy: boolean, accessor: IItemAccessor<T>): IItemScore {
// 1.) treat identity matches on full path highest // 1.) treat identity matches on full path highest
if (path && isEqual(query, path, true)) { if (path && isEqual(query, path, true)) {
...@@ -257,7 +279,7 @@ function doScoreItem<T>(label: string, description: string, path: string, query: ...@@ -257,7 +279,7 @@ function doScoreItem<T>(label: string, description: string, path: string, query:
} }
// 4.) prefer scores on the label if any // 4.) prefer scores on the label if any
const [labelScore, labelPositions] = _doScore(label, query); const [labelScore, labelPositions] = _doScore(label, query, fuzzy);
if (labelScore) { if (labelScore) {
return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) }; return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) };
} }
...@@ -272,12 +294,12 @@ function doScoreItem<T>(label: string, description: string, path: string, query: ...@@ -272,12 +294,12 @@ function doScoreItem<T>(label: string, description: string, path: string, query:
const descriptionPrefixLength = descriptionPrefix.length; const descriptionPrefixLength = descriptionPrefix.length;
const descriptionAndLabel = `${descriptionPrefix}${label}`; const descriptionAndLabel = `${descriptionPrefix}${label}`;
let [labelDescriptionScore, labelDescriptionPositions] = _doScore(descriptionAndLabel, query); let [labelDescriptionScore, labelDescriptionPositions] = _doScore(descriptionAndLabel, query, fuzzy);
// Optimize for file paths: score from the back to the beginning to catch more specific folder // Optimize for file paths: score from the back to the beginning to catch more specific folder
// names that match on the end of the file. This yields better results in most cases. // names that match on the end of the file. This yields better results in most cases.
if (!!path) { if (!!path) {
const [labelDescriptionScoreInverse, labelDescriptionPositionsInverse] = _doScore(descriptionAndLabel, query, true /* inverse */); const [labelDescriptionScoreInverse, labelDescriptionPositionsInverse] = _doScore(descriptionAndLabel, query, fuzzy, true /* inverse */);
if (labelDescriptionScoreInverse && labelDescriptionScoreInverse > labelDescriptionScore) { if (labelDescriptionScoreInverse && labelDescriptionScoreInverse > labelDescriptionScore) {
labelDescriptionScore = labelDescriptionScoreInverse; labelDescriptionScore = labelDescriptionScoreInverse;
labelDescriptionPositions = labelDescriptionPositionsInverse; labelDescriptionPositions = labelDescriptionPositionsInverse;
...@@ -316,9 +338,9 @@ function doScoreItem<T>(label: string, description: string, path: string, query: ...@@ -316,9 +338,9 @@ function doScoreItem<T>(label: string, description: string, path: string, query:
return NO_ITEM_SCORE; return NO_ITEM_SCORE;
} }
export function compareItemsByScore<T>(itemA: T, itemB: T, query: string, accessor: IItemAccessor<T>, cache: ScorerCache): number { export function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): number {
const scoreA = scoreItem(itemA, query, accessor, cache).score; const scoreA = scoreItem(itemA, query, fuzzy, accessor, cache).score;
const scoreB = scoreItem(itemB, query, accessor, cache).score; const scoreB = scoreItem(itemB, query, fuzzy, accessor, cache).score;
// 1.) check for identity matches // 1.) check for identity matches
if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) { if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
'use strict'; 'use strict';
import * as assert from 'assert'; import * as assert from 'assert';
import * as scorer from 'vs/base/common/scorer'; import * as scorer from 'vs/base/parts/quickopen/common/quickOpenScorer';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { basename, dirname } from 'vs/base/common/paths'; import { basename, dirname } from 'vs/base/common/paths';
...@@ -45,25 +45,25 @@ class NullAccessorClass implements scorer.IItemAccessor<URI> { ...@@ -45,25 +45,25 @@ class NullAccessorClass implements scorer.IItemAccessor<URI> {
const NullAccessor = new NullAccessorClass(); const NullAccessor = new NullAccessorClass();
const cache: scorer.ScorerCache = Object.create(null); const cache: scorer.ScorerCache = Object.create(null);
suite('Scorer', () => { suite('Quick Open Scorer', () => {
test('score', function () { test('score (fuzzy)', function () {
const target = 'HeLlo-World'; const target = 'HeLlo-World';
const scores: scorer.Score[] = []; const scores: scorer.Score[] = [];
scores.push(scorer._doScore(target, 'HelLo-World')); // direct case match scores.push(scorer._doScore(target, 'HelLo-World', true)); // direct case match
scores.push(scorer._doScore(target, 'hello-world')); // direct mix-case match scores.push(scorer._doScore(target, 'hello-world', true)); // direct mix-case match
scores.push(scorer._doScore(target, 'HW')); // direct case prefix (multiple) scores.push(scorer._doScore(target, 'HW', true)); // direct case prefix (multiple)
scores.push(scorer._doScore(target, 'hw')); // direct mix-case prefix (multiple) scores.push(scorer._doScore(target, 'hw', true)); // direct mix-case prefix (multiple)
scores.push(scorer._doScore(target, 'H')); // direct case prefix scores.push(scorer._doScore(target, 'H', true)); // direct case prefix
scores.push(scorer._doScore(target, 'h')); // direct mix-case prefix scores.push(scorer._doScore(target, 'h', true)); // direct mix-case prefix
scores.push(scorer._doScore(target, 'W')); // direct case word prefix scores.push(scorer._doScore(target, 'W', true)); // direct case word prefix
scores.push(scorer._doScore(target, 'w')); // direct mix-case word prefix scores.push(scorer._doScore(target, 'w', true)); // direct mix-case word prefix
scores.push(scorer._doScore(target, 'Ld')); // in-string case match (multiple) scores.push(scorer._doScore(target, 'Ld', true)); // in-string case match (multiple)
scores.push(scorer._doScore(target, 'ld')); // in-string mix-case match scores.push(scorer._doScore(target, 'ld', true)); // in-string mix-case match
scores.push(scorer._doScore(target, 'L')); // in-string case match scores.push(scorer._doScore(target, 'L', true)); // in-string case match
scores.push(scorer._doScore(target, 'l')); // in-string mix-case match scores.push(scorer._doScore(target, 'l', true)); // in-string mix-case match
scores.push(scorer._doScore(target, '4')); // no match scores.push(scorer._doScore(target, '4', true)); // no match
// Assert scoring order // Assert scoring order
let sortedScores = scores.concat().sort((a, b) => b[0] - a[0]); let sortedScores = scores.concat().sort((a, b) => b[0] - a[0]);
...@@ -79,17 +79,45 @@ suite('Scorer', () => { ...@@ -79,17 +79,45 @@ suite('Scorer', () => {
assert.equal(positions[1], 6); assert.equal(positions[1], 6);
}); });
test('score (non fuzzy)', function () {
const target = 'HeLlo-World';
assert.ok(scorer._doScore(target, 'HelLo-World', false)[0] > 0);
assert.equal(scorer._doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length);
assert.ok(scorer._doScore(target, 'hello-world', false)[0] > 0);
assert.equal(scorer._doScore(target, 'HW', false)[0], 0);
assert.ok(scorer._doScore(target, 'h', false)[0] > 0);
assert.ok(scorer._doScore(target, 'ello', false)[0] > 0);
assert.ok(scorer._doScore(target, 'ld', false)[0] > 0);
assert.equal(scorer._doScore(target, 'eo', false)[0], 0);
});
test('score (non fuzzy, inverse)', function () {
const target = 'HeLlo-World';
assert.ok(scorer._doScore(target, 'HelLo-World', false, true)[0] > 0);
assert.equal(scorer._doScore(target, 'HelLo-World', false, true)[1].length, 'HelLo-World'.length);
assert.ok(scorer._doScore(target, 'hello-world', false, true)[0] > 0);
assert.equal(scorer._doScore(target, 'HW', false, true)[0], 0);
assert.ok(scorer._doScore(target, 'h', false, true)[0] > 0);
assert.ok(scorer._doScore(target, 'ello', false, true)[0] > 0);
assert.ok(scorer._doScore(target, 'ld', false, true)[0] > 0);
assert.equal(scorer._doScore(target, 'eo', false, true)[0], 0);
});
test('scoreItem - matches are proper', function () { test('scoreItem - matches are proper', function () {
let res = scorer.scoreItem(null, 'something', ResourceAccessor, cache); let res = scorer.scoreItem(null, 'something', true, ResourceAccessor, cache);
assert.ok(!res.score); assert.ok(!res.score);
const resource = URI.file('/xyz/some/path/someFile123.txt'); const resource = URI.file('/xyz/some/path/someFile123.txt');
res = scorer.scoreItem(resource, 'something', NullAccessor, cache); res = scorer.scoreItem(resource, 'something', true, NullAccessor, cache);
assert.ok(!res.score); assert.ok(!res.score);
// Path Identity // Path Identity
const identityRes = scorer.scoreItem(resource, ResourceAccessor.getItemPath(resource), ResourceAccessor, cache); const identityRes = scorer.scoreItem(resource, ResourceAccessor.getItemPath(resource), true, ResourceAccessor, cache);
assert.ok(identityRes.score); assert.ok(identityRes.score);
assert.equal(identityRes.descriptionMatch.length, 1); assert.equal(identityRes.descriptionMatch.length, 1);
assert.equal(identityRes.labelMatch.length, 1); assert.equal(identityRes.labelMatch.length, 1);
...@@ -99,7 +127,7 @@ suite('Scorer', () => { ...@@ -99,7 +127,7 @@ suite('Scorer', () => {
assert.equal(identityRes.labelMatch[0].end, ResourceAccessor.getItemLabel(resource).length); assert.equal(identityRes.labelMatch[0].end, ResourceAccessor.getItemLabel(resource).length);
// Basename Prefix // Basename Prefix
const basenamePrefixRes = scorer.scoreItem(resource, 'som', ResourceAccessor, cache); const basenamePrefixRes = scorer.scoreItem(resource, 'som', true, ResourceAccessor, cache);
assert.ok(basenamePrefixRes.score); assert.ok(basenamePrefixRes.score);
assert.ok(!basenamePrefixRes.descriptionMatch); assert.ok(!basenamePrefixRes.descriptionMatch);
assert.equal(basenamePrefixRes.labelMatch.length, 1); assert.equal(basenamePrefixRes.labelMatch.length, 1);
...@@ -107,7 +135,7 @@ suite('Scorer', () => { ...@@ -107,7 +135,7 @@ suite('Scorer', () => {
assert.equal(basenamePrefixRes.labelMatch[0].end, 'som'.length); assert.equal(basenamePrefixRes.labelMatch[0].end, 'som'.length);
// Basename Camelcase // Basename Camelcase
const basenameCamelcaseRes = scorer.scoreItem(resource, 'sF', ResourceAccessor, cache); const basenameCamelcaseRes = scorer.scoreItem(resource, 'sF', true, ResourceAccessor, cache);
assert.ok(basenameCamelcaseRes.score); assert.ok(basenameCamelcaseRes.score);
assert.ok(!basenameCamelcaseRes.descriptionMatch); assert.ok(!basenameCamelcaseRes.descriptionMatch);
assert.equal(basenameCamelcaseRes.labelMatch.length, 2); assert.equal(basenameCamelcaseRes.labelMatch.length, 2);
...@@ -117,7 +145,7 @@ suite('Scorer', () => { ...@@ -117,7 +145,7 @@ suite('Scorer', () => {
assert.equal(basenameCamelcaseRes.labelMatch[1].end, 5); assert.equal(basenameCamelcaseRes.labelMatch[1].end, 5);
// Basename Match // Basename Match
const basenameRes = scorer.scoreItem(resource, 'of', ResourceAccessor, cache); const basenameRes = scorer.scoreItem(resource, 'of', true, ResourceAccessor, cache);
assert.ok(basenameRes.score); assert.ok(basenameRes.score);
assert.ok(!basenameRes.descriptionMatch); assert.ok(!basenameRes.descriptionMatch);
assert.equal(basenameRes.labelMatch.length, 2); assert.equal(basenameRes.labelMatch.length, 2);
...@@ -127,7 +155,7 @@ suite('Scorer', () => { ...@@ -127,7 +155,7 @@ suite('Scorer', () => {
assert.equal(basenameRes.labelMatch[1].end, 5); assert.equal(basenameRes.labelMatch[1].end, 5);
// Path Match // Path Match
const pathRes = scorer.scoreItem(resource, 'xyz123', ResourceAccessor, cache); const pathRes = scorer.scoreItem(resource, 'xyz123', true, ResourceAccessor, cache);
assert.ok(pathRes.score); assert.ok(pathRes.score);
assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.descriptionMatch);
assert.ok(pathRes.labelMatch); assert.ok(pathRes.labelMatch);
...@@ -139,7 +167,7 @@ suite('Scorer', () => { ...@@ -139,7 +167,7 @@ suite('Scorer', () => {
assert.equal(pathRes.descriptionMatch[0].end, 4); assert.equal(pathRes.descriptionMatch[0].end, 4);
// No Match // No Match
const noRes = scorer.scoreItem(resource, '987', ResourceAccessor, cache); const noRes = scorer.scoreItem(resource, '987', true, ResourceAccessor, cache);
assert.ok(!noRes.score); assert.ok(!noRes.score);
assert.ok(!noRes.labelMatch); assert.ok(!noRes.labelMatch);
assert.ok(!noRes.descriptionMatch); assert.ok(!noRes.descriptionMatch);
...@@ -157,7 +185,7 @@ suite('Scorer', () => { ...@@ -157,7 +185,7 @@ suite('Scorer', () => {
// xsp is more relevant to the end of the file path even though it matches // xsp is more relevant to the end of the file path even though it matches
// fuzzy also in the beginning. we verify the more relevant match at the // fuzzy also in the beginning. we verify the more relevant match at the
// end gets returned. // end gets returned.
const pathRes = scorer.scoreItem(resource, 'xspfile123', ResourceAccessor, cache); const pathRes = scorer.scoreItem(resource, 'xspfile123', true, ResourceAccessor, cache);
assert.ok(pathRes.score); assert.ok(pathRes.score);
assert.ok(pathRes.descriptionMatch); assert.ok(pathRes.descriptionMatch);
assert.ok(pathRes.labelMatch); assert.ok(pathRes.labelMatch);
...@@ -177,12 +205,12 @@ suite('Scorer', () => { ...@@ -177,12 +205,12 @@ suite('Scorer', () => {
// Full resource A path // Full resource A path
let query = ResourceAccessor.getItemPath(resourceA); let query = ResourceAccessor.getItemPath(resourceA);
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -190,12 +218,12 @@ suite('Scorer', () => { ...@@ -190,12 +218,12 @@ suite('Scorer', () => {
// Full resource B path // Full resource B path
query = ResourceAccessor.getItemPath(resourceB); query = ResourceAccessor.getItemPath(resourceB);
res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -209,12 +237,12 @@ suite('Scorer', () => { ...@@ -209,12 +237,12 @@ suite('Scorer', () => {
// Full resource A basename // Full resource A basename
let query = ResourceAccessor.getItemLabel(resourceA); let query = ResourceAccessor.getItemLabel(resourceA);
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -222,12 +250,12 @@ suite('Scorer', () => { ...@@ -222,12 +250,12 @@ suite('Scorer', () => {
// Full resource B basename // Full resource B basename
query = ResourceAccessor.getItemLabel(resourceB); query = ResourceAccessor.getItemLabel(resourceB);
res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -241,12 +269,12 @@ suite('Scorer', () => { ...@@ -241,12 +269,12 @@ suite('Scorer', () => {
// resource A camelcase // resource A camelcase
let query = 'fA'; let query = 'fA';
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -254,12 +282,12 @@ suite('Scorer', () => { ...@@ -254,12 +282,12 @@ suite('Scorer', () => {
// resource B camelcase // resource B camelcase
query = 'fB'; query = 'fB';
res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -273,12 +301,12 @@ suite('Scorer', () => { ...@@ -273,12 +301,12 @@ suite('Scorer', () => {
// Resource A part of basename // Resource A part of basename
let query = 'fileA'; let query = 'fileA';
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -286,12 +314,12 @@ suite('Scorer', () => { ...@@ -286,12 +314,12 @@ suite('Scorer', () => {
// Resource B part of basename // Resource B part of basename
query = 'fileB'; query = 'fileB';
res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -305,12 +333,12 @@ suite('Scorer', () => { ...@@ -305,12 +333,12 @@ suite('Scorer', () => {
// Resource A part of path // Resource A part of path
let query = 'pathfileA'; let query = 'pathfileA';
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -318,12 +346,12 @@ suite('Scorer', () => { ...@@ -318,12 +346,12 @@ suite('Scorer', () => {
// Resource B part of path // Resource B part of path
query = 'pathfileB'; query = 'pathfileB';
res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -337,12 +365,12 @@ suite('Scorer', () => { ...@@ -337,12 +365,12 @@ suite('Scorer', () => {
// Resource A part of path // Resource A part of path
let query = 'somepath'; let query = 'somepath';
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -356,12 +384,12 @@ suite('Scorer', () => { ...@@ -356,12 +384,12 @@ suite('Scorer', () => {
// Resource A part of path // Resource A part of path
let query = 'somepath'; let query = 'somepath';
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); res = [resourceC, resourceB, resourceA].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceA); assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB); assert.equal(res[1], resourceB);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
...@@ -374,7 +402,7 @@ suite('Scorer', () => { ...@@ -374,7 +402,7 @@ suite('Scorer', () => {
let query = 'co/te'; let query = 'co/te';
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, ResourceAccessor, cache)); let res = [resourceA, resourceB, resourceC].sort((r1, r2) => scorer.compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB); assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA); assert.equal(res[1], resourceA);
assert.equal(res[2], resourceC); assert.equal(res[2], resourceC);
......
...@@ -23,7 +23,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti ...@@ -23,7 +23,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor'; import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor';
import { stripWildcards } from 'vs/base/common/strings'; import { stripWildcards } from 'vs/base/common/strings';
import { compareItemsByScore, scoreItem, ScorerCache } from 'vs/base/common/scorer'; import { compareItemsByScore, scoreItem, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer';
export class EditorPickerEntry extends QuickOpenEntryGroup { export class EditorPickerEntry extends QuickOpenEntryGroup {
private stacks: IEditorStacksModel; private stacks: IEditorStacksModel;
...@@ -116,7 +116,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler { ...@@ -116,7 +116,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
return true; return true;
} }
const itemScore = scoreItem(e, searchValue, QuickOpenItemAccessor, this.scorerCache); const itemScore = scoreItem(e, searchValue, true, QuickOpenItemAccessor, this.scorerCache);
if (!itemScore.score) { if (!itemScore.score) {
return false; return false;
} }
...@@ -133,7 +133,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler { ...@@ -133,7 +133,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group); return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group);
} }
return compareItemsByScore(e1, e2, searchValue, QuickOpenItemAccessor, this.scorerCache); return compareItemsByScore(e1, e2, searchValue, true, QuickOpenItemAccessor, this.scorerCache);
}); });
} }
......
...@@ -20,7 +20,7 @@ import { Action, IAction } from 'vs/base/common/actions'; ...@@ -20,7 +20,7 @@ import { Action, IAction } from 'vs/base/common/actions';
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, compareEntries, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget';
import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { ContributableActionProvider } from 'vs/workbench/browser/actions';
import labels = require('vs/base/common/labels'); import labels = require('vs/base/common/labels');
...@@ -55,6 +55,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' ...@@ -55,6 +55,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { FileKind, IFileService } from 'vs/platform/files/common/files'; import { FileKind, IFileService } from 'vs/platform/files/common/files';
import { scoreItem, ScorerCache, compareItemsByScore } from 'vs/base/parts/quickopen/common/quickOpenScorer';
const HELP_PREFIX = '?'; const HELP_PREFIX = '?';
...@@ -458,7 +459,7 @@ export class QuickOpenController extends Component implements IQuickOpenService ...@@ -458,7 +459,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
return pickA.index - pickB.index; // restore natural order return pickA.index - pickB.index; // restore natural order
} }
return QuickOpenEntry.compare(pickA, pickB, normalizedSearchValue); return compareEntries(pickA, pickB, normalizedSearchValue);
}); });
this.pickOpenWidget.refresh(model, value ? { autoFocusFirstEntry: true } : autoFocus); this.pickOpenWidget.refresh(model, value ? { autoFocusFirstEntry: true } : autoFocus);
...@@ -1164,6 +1165,7 @@ class PickOpenActionProvider implements IActionProvider { ...@@ -1164,6 +1165,7 @@ class PickOpenActionProvider implements IActionProvider {
} }
class EditorHistoryHandler { class EditorHistoryHandler {
private scorerCache: ScorerCache;
constructor( constructor(
@IHistoryService private historyService: IHistoryService, @IHistoryService private historyService: IHistoryService,
...@@ -1171,11 +1173,12 @@ class EditorHistoryHandler { ...@@ -1171,11 +1173,12 @@ class EditorHistoryHandler {
@IWorkspaceContextService private contextService: IWorkspaceContextService, @IWorkspaceContextService private contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService @IFileService private fileService: IFileService
) { ) {
this.scorerCache = Object.create(null);
} }
public getResults(searchValue?: string): QuickOpenEntry[] { public getResults(searchValue?: string): QuickOpenEntry[] {
if (searchValue) { if (searchValue) {
searchValue = searchValue.replace(/ /g, ''); // get rid of all whitespace searchValue = strings.stripWildcards(searchValue.replace(/ /g, '')); // get rid of all whitespace and wildcards
} }
// Just return all if we are not searching // Just return all if we are not searching
...@@ -1184,10 +1187,13 @@ class EditorHistoryHandler { ...@@ -1184,10 +1187,13 @@ class EditorHistoryHandler {
return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)); return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input));
} }
const searchInPath = searchValue.indexOf(paths.nativeSep) >= 0; // Otherwise filter by search value and sort by score. Include matches on description
// in case the user is explicitly including path separators.
const accessor = searchValue.indexOf(paths.nativeSep) >= 0 ? MatchOnDescription : DoNotMatchOnDescription;
return history
const results: QuickOpenEntry[] = []; // For now, only support to match on inputs that provide resource information
history.forEach(input => { .filter(input => {
let resource: URI; let resource: URI;
if (input instanceof EditorInput) { if (input instanceof EditorInput) {
resource = resourceForEditorHistory(input, this.fileService); resource = resourceForEditorHistory(input, this.fileService);
...@@ -1195,38 +1201,43 @@ class EditorHistoryHandler { ...@@ -1195,38 +1201,43 @@ class EditorHistoryHandler {
resource = (input as IResourceInput).resource; resource = (input as IResourceInput).resource;
} }
if (!resource) { return !!resource;
return; //For now, only support to match on inputs that provide resource information })
}
let searchTargetToMatch: string; // Conver to quick open entries
if (searchInPath) { .map(input => this.instantiationService.createInstance(EditorHistoryEntry, input))
searchTargetToMatch = labels.getPathLabel(resource, this.contextService);
} else if (input instanceof EditorInput) {
searchTargetToMatch = input.getName();
} else {
searchTargetToMatch = paths.basename((input as IResourceInput).resource.fsPath);
}
// Check if this entry is a match for the search value // Make sure the search value is matching
if (!filters.matchesFuzzy(searchValue, searchTargetToMatch)) { .filter(e => {
return; const itemScore = scoreItem(e, searchValue, false, accessor, this.scorerCache);
if (!itemScore.score) {
return false;
} }
const entry = this.instantiationService.createInstance(EditorHistoryEntry, input); e.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch);
const { labelHighlights, descriptionHighlights } = QuickOpenEntry.highlight(entry, searchValue); return true;
entry.setHighlights(labelHighlights, descriptionHighlights); })
results.push(entry); // Sort by score
}); .sort((e1, e2) => compareItemsByScore(e1, e2, searchValue, false, accessor, this.scorerCache));
}
}
// Sort class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass {
const normalizedSearchValue = strings.stripWildcards(searchValue.toLowerCase());
return results.sort((elementA: EditorHistoryEntry, elementB: EditorHistoryEntry) => QuickOpenEntry.compare(elementA, elementB, normalizedSearchValue)); constructor(private allowMatchOnDescription: boolean) {
super();
}
public getItemDescription(entry: QuickOpenEntry): string {
return this.allowMatchOnDescription ? entry.getDescription() : void 0;
} }
} }
const MatchOnDescription = new EditorHistoryItemAccessorClass(true);
const DoNotMatchOnDescription = new EditorHistoryItemAccessorClass(false);
export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { export class EditorHistoryEntryGroup extends QuickOpenEntryGroup {
// Marker class // Marker class
} }
......
...@@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; ...@@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchSearchConfiguration } from 'vs/workbench/parts/search/common/search'; import { IWorkbenchSearchConfiguration } from 'vs/workbench/parts/search/common/search';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
import { compareItemsByScore, scoreItem, ScorerCache } from 'vs/base/common/scorer'; import { compareItemsByScore, scoreItem, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer';
export import OpenSymbolHandler = openSymbolHandler.OpenSymbolHandler; // OpenSymbolHandler is used from an extension and must be in the main bundle file so it can load export import OpenSymbolHandler = openSymbolHandler.OpenSymbolHandler; // OpenSymbolHandler is used from an extension and must be in the main bundle file so it can load
...@@ -218,7 +218,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { ...@@ -218,7 +218,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
// Sort // Sort
const unsortedResultTime = Date.now(); const unsortedResultTime = Date.now();
const normalizedSearchValue = strings.stripWildcards(searchValue); const normalizedSearchValue = strings.stripWildcards(searchValue);
const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => compareItemsByScore(elementA, elementB, normalizedSearchValue, QuickOpenItemAccessor, this.scorerCache); const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => compareItemsByScore(elementA, elementB, normalizedSearchValue, true, QuickOpenItemAccessor, this.scorerCache);
const viewResults = arrays.top(mergedResults, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); const viewResults = arrays.top(mergedResults, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS);
const sortedResultTime = Date.now(); const sortedResultTime = Date.now();
...@@ -227,7 +227,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { ...@@ -227,7 +227,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
if (entry instanceof FileEntry) { if (entry instanceof FileEntry) {
entry.setRange(searchWithRange ? searchWithRange.range : null); entry.setRange(searchWithRange ? searchWithRange.range : null);
const itemScore = scoreItem(entry, normalizedSearchValue, QuickOpenItemAccessor, this.scorerCache); const itemScore = scoreItem(entry, normalizedSearchValue, true, QuickOpenItemAccessor, this.scorerCache);
entry.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch); entry.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch);
} }
}); });
......
...@@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; ...@@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { onUnexpectedError } from 'vs/base/common/errors'; import { onUnexpectedError } from 'vs/base/common/errors';
import { ThrottledDelayer } from 'vs/base/common/async'; import { ThrottledDelayer } from 'vs/base/common/async';
import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen'; import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen';
import { QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenModel, QuickOpenEntry, compareEntries } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
import filters = require('vs/base/common/filters'); import filters = require('vs/base/common/filters');
import strings = require('vs/base/common/strings'); import strings = require('vs/base/common/strings');
...@@ -118,7 +118,7 @@ class SymbolEntry extends EditorQuickOpenEntry { ...@@ -118,7 +118,7 @@ class SymbolEntry extends EditorQuickOpenEntry {
return elementAType.localeCompare(elementBType); return elementAType.localeCompare(elementBType);
} }
return QuickOpenEntry.compare(elementA, elementB, searchValue); return compareEntries(elementA, elementB, searchValue);
} }
} }
......
...@@ -23,7 +23,7 @@ import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/text ...@@ -23,7 +23,7 @@ import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/text
import { IRawSearchService, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchProgressItem, ISerializedSearchComplete, ISearchEngine, IFileSearchProgressItem, ITelemetryEvent } from './search'; import { IRawSearchService, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchProgressItem, ISerializedSearchComplete, ISearchEngine, IFileSearchProgressItem, ITelemetryEvent } from './search';
import { ICachedSearchStats, IProgress } from 'vs/platform/search/common/search'; import { ICachedSearchStats, IProgress } from 'vs/platform/search/common/search';
import { fuzzyContains } from 'vs/base/common/strings'; import { fuzzyContains } from 'vs/base/common/strings';
import { compareItemsByScore, IItemAccessor, ScorerCache } from 'vs/base/common/scorer'; import { compareItemsByScore, IItemAccessor, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer';
export class SearchService implements IRawSearchService { export class SearchService implements IRawSearchService {
...@@ -254,7 +254,7 @@ export class SearchService implements IRawSearchService { ...@@ -254,7 +254,7 @@ export class SearchService implements IRawSearchService {
// this is very important because we are also limiting the number of results by config.maxResults // this is very important because we are also limiting the number of results by config.maxResults
// and as such we want the top items to be included in this result set if the number of items // and as such we want the top items to be included in this result set if the number of items
// exceeds config.maxResults. // exceeds config.maxResults.
const compare = (matchA: IRawFileMatch, matchB: IRawFileMatch) => compareItemsByScore(matchA, matchB, strings.stripWildcards(config.filePattern), FileMatchItemAccessor, scorerCache); const compare = (matchA: IRawFileMatch, matchB: IRawFileMatch) => compareItemsByScore(matchA, matchB, strings.stripWildcards(config.filePattern), true, FileMatchItemAccessor, scorerCache);
return arrays.topAsync(results, compare, config.maxResults, 10000); return arrays.topAsync(results, compare, config.maxResults, 10000);
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册