diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index bf47f5db4f91096c95608efadbe5189ac854c717..65187b136fd825506107d3ae10348fb73a0c8b0b 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -79,6 +79,29 @@ export function findFirst(array: T[], p: (x: T) => boolean): number { return low; } +/** + * Returns the top N elements from the array. + * + * Faster than sorting the entire array when the array is a lot larger than N. + * + * @param array The unsorted array. + * @param compare A sort function for the elements. + * @param n The number of elements to return. + * @return The first n elemnts from array when sorted with compare. + */ +export function top(array: T[], compare: (a: T, b: T) => number, n: number) { + const result = array.slice(0, n).sort(compare); + for (let i = n, m = array.length; i < m; i++) { + const element = array[i]; + if (compare(element, result[n - 1]) < 0) { + result.pop(); + const j = findFirst(result, e => compare(element, e) < 0); + result.splice(j, 0, element); + } + } + return result; +} + export function merge(arrays: T[][], hashFn?: (element: T) => string): T[] { const result = new Array(); if (!hashFn) { diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index c3b13d111f583375a95c7195027e723e25d17ddf..2ad22ee4ca0dbf61857d4cd596b6dd8f0788f903 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -57,8 +57,8 @@ export function compareByPrefix(one: string, other: string, lookFor: string): nu let elementBName = other.toLowerCase(); // Sort prefix matches over non prefix matches - let elementAPrefixMatch = elementAName.indexOf(lookFor) === 0; - let elementBPrefixMatch = elementBName.indexOf(lookFor) === 0; + let elementAPrefixMatch = strings.startsWith(elementAName, lookFor); + let elementBPrefixMatch = strings.startsWith(elementBName, lookFor); if (elementAPrefixMatch !== elementBPrefixMatch) { return elementAPrefixMatch ? -1 : 1; } diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index 1031c680f2c54526f8ef206b9d807baf64f596ad..865177da2bda449ae4df558c6a0f2b4967899be7 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -60,5 +60,17 @@ suite('Arrays', () => { assert.deepEqual(arrays.distinct(['32', 'constructor', 'proto', 'proto', 'constructor'], compare), ['32', 'constructor', 'proto']); assert.deepEqual(arrays.distinct(['32', '4', '5', '32', '4', '5', '32', '4', '5', '5'], compare), ['32', '4', '5']); }); + + test('top', function () { + const cmp = (a, b) => a - b; + + assert.deepEqual(arrays.top([], cmp, 1), []); + assert.deepEqual(arrays.top([1], cmp, 0), []); + assert.deepEqual(arrays.top([1, 2], cmp, 1), [1]); + assert.deepEqual(arrays.top([2, 1], cmp, 1), [1]); + assert.deepEqual(arrays.top([1, 3, 2], cmp, 2), [1, 2]); + assert.deepEqual(arrays.top([3, 2, 1], cmp, 3), [1, 2, 3]); + assert.deepEqual(arrays.top([4, 6, 2, 7, 8, 3, 5, 1], cmp, 3), [1, 2, 3]); + }); }); diff --git a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts index f9ba32e26fddd552698d015e7f4fc10c843843f3..606e11232d79df4d9eb4b845d10a1193d2a0d037 100644 --- a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts +++ b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts @@ -5,6 +5,7 @@ 'use strict'; +import * as arrays from 'vs/base/common/arrays'; import {TPromise} from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); import {ThrottledDelayer} from 'vs/base/common/async'; @@ -182,27 +183,19 @@ export class OpenAnythingHandler extends QuickOpenHandler { // Combine symbol results and file results let result = [...results[0].entries, ...results[1].entries]; - // Sort - const normalizedSearchValue = strings.stripWildcards(searchValue).toLowerCase(); - result.sort((elementA, elementB) => QuickOpenEntry.compareByScore(elementA, elementB, searchValue, normalizedSearchValue, this.scorerCache)); - const sortedResultTime = Date.now(); - - // Apply Range - result.forEach((element) => { - if (element instanceof FileEntry) { - (element).setRange(searchWithRange ? searchWithRange.range : null); - } - }); - // Cache for fast lookup this.resultsToSearchCache[searchValue] = result; - // Cap the number of results to make the view snappy - const viewResults = result.length > OpenAnythingHandler.MAX_DISPLAYED_RESULTS ? result.slice(0, OpenAnythingHandler.MAX_DISPLAYED_RESULTS) : result; + // Sort + const normalizedSearchValue = strings.stripWildcards(searchValue).toLowerCase(); + const compare = (elementA, elementB) => QuickOpenEntry.compareByScore(elementA, elementB, searchValue, normalizedSearchValue, this.scorerCache); + const viewResults = arrays.top(result, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); + const sortedResultTime = Date.now(); - // Apply highlights to file entries + // Apply range and highlights to file entries viewResults.forEach(entry => { if (entry instanceof FileEntry) { + entry.setRange(searchWithRange ? searchWithRange.range : null); const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue, true /* fuzzy highlight */); entry.setHighlights(labelHighlights, descriptionHighlights); } @@ -324,21 +317,15 @@ export class OpenAnythingHandler extends QuickOpenHandler { const unsortedResultTime = Date.now(); // Sort - results.sort((elementA, elementB) => QuickOpenEntry.compareByScore(elementA, elementB, searchValue, normalizedSearchValueLowercase, this.scorerCache)); + const compare = (elementA, elementB) => QuickOpenEntry.compareByScore(elementA, elementB, searchValue, normalizedSearchValueLowercase, this.scorerCache); + const viewResults = arrays.top(results, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); const sortedResultTime = Date.now(); - // Apply Range - results.forEach((element) => { - if (element instanceof FileEntry) { - (element).setRange(range); - } - }); - - // Cap the number of results to make the view snappy - const viewResults = results.length > OpenAnythingHandler.MAX_DISPLAYED_RESULTS ? results.slice(0, OpenAnythingHandler.MAX_DISPLAYED_RESULTS) : results; - - // Apply highlights + // Apply range and highlights viewResults.forEach(entry => { + if (entry instanceof FileEntry) { + entry.setRange(range); + } const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue, true /* fuzzy highlight */); entry.setHighlights(labelHighlights, descriptionHighlights); });