mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-04-01 17:40:30 -04:00
Refresh master with initial release/0.24 snapshot (#332)
* Initial port of release/0.24 source code * Fix additional headers * Fix a typo in launch.json
This commit is contained in:
@@ -207,7 +207,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
|
||||
}
|
||||
|
||||
getChannel<T extends IChannel>(channelName: string): T {
|
||||
const call = (command, arg) => this.request(channelName, command, arg);
|
||||
const call = (command: string, arg: any) => this.request(channelName, command, arg);
|
||||
return { call } as T;
|
||||
}
|
||||
|
||||
@@ -411,8 +411,8 @@ export class IPCServer implements IChannelServer, IRoutingChannelClient, IDispos
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.channels = null;
|
||||
this.channelClients = null;
|
||||
this.channels = Object.create(null);
|
||||
this.channelClients = Object.create(null);
|
||||
this.onClientAdded.dispose();
|
||||
}
|
||||
}
|
||||
@@ -452,14 +452,14 @@ export class IPCClient implements IChannelClient, IChannelServer, IDisposable {
|
||||
}
|
||||
|
||||
export function getDelayedChannel<T extends IChannel>(promise: TPromise<T>): T {
|
||||
const call = (command, arg) => promise.then(c => c.call(command, arg));
|
||||
const call = (command: string, arg: any) => promise.then(c => c.call(command, arg));
|
||||
return { call } as T;
|
||||
}
|
||||
|
||||
export function getNextTickChannel<T extends IChannel>(channel: T): T {
|
||||
let didTick = false;
|
||||
|
||||
const call = (command, arg) => {
|
||||
const call = (command: string, arg: any) => {
|
||||
if (didTick) {
|
||||
return channel.call(command, arg);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Emitter } from 'vs/base/common/event';
|
||||
import { fromEventEmitter } from 'vs/base/node/event';
|
||||
import { createQueuedSender } from 'vs/base/node/processes';
|
||||
import { ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient, IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { isRemoteConsoleLog, log } from 'vs/base/node/console';
|
||||
|
||||
export class Server extends IPCServer {
|
||||
constructor() {
|
||||
@@ -89,7 +90,7 @@ export class Client implements IChannelClient, IDisposable {
|
||||
}
|
||||
|
||||
getChannel<T extends IChannel>(channelName: string): T {
|
||||
const call = (command, arg) => this.request(channelName, command, arg);
|
||||
const call = (command: string, arg: any) => this.request(channelName, command, arg);
|
||||
return { call } as T;
|
||||
}
|
||||
|
||||
@@ -151,24 +152,15 @@ export class Client implements IChannelClient, IDisposable {
|
||||
const onRawMessage = fromEventEmitter(this.child, 'message', msg => msg);
|
||||
|
||||
onRawMessage(msg => {
|
||||
// Handle console logs specially
|
||||
if (msg && msg.type === '__$console') {
|
||||
let args = ['%c[IPC Library: ' + this.options.serverName + ']', 'color: darkgreen'];
|
||||
try {
|
||||
const parsed = JSON.parse(msg.arguments);
|
||||
args = args.concat(Object.getOwnPropertyNames(parsed).map(o => parsed[o]));
|
||||
} catch (error) {
|
||||
args.push(msg.arguments);
|
||||
}
|
||||
|
||||
console[msg.severity].apply(console, args);
|
||||
// Handle remote console logs specially
|
||||
if (isRemoteConsoleLog(msg)) {
|
||||
log(msg, `IPC Library: ${this.options.serverName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Anything else goes to the outside
|
||||
else {
|
||||
onMessageEmitter.fire(msg);
|
||||
}
|
||||
onMessageEmitter.fire(msg);
|
||||
});
|
||||
|
||||
const sender = this.options.useQueue ? createQueuedSender(this.child) : this.child;
|
||||
|
||||
@@ -34,7 +34,7 @@ export class Protocol implements IMessagePassingProtocol {
|
||||
|
||||
constructor(private _socket: Socket) {
|
||||
|
||||
let chunks = [];
|
||||
let chunks: Buffer[] = [];
|
||||
let totalLength = 0;
|
||||
|
||||
const state = {
|
||||
|
||||
@@ -10,13 +10,10 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import types = require('vs/base/common/types');
|
||||
import URI from 'vs/base/common/uri';
|
||||
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 { 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 { compareAnything, compareByScore as doCompareByScore } from 'vs/base/common/comparers';
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
import { ActionBar, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
@@ -24,6 +21,7 @@ import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidge
|
||||
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
|
||||
import { OS } from 'vs/base/common/platform';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { IItemAccessor } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
|
||||
export interface IContext {
|
||||
event: any;
|
||||
@@ -37,18 +35,25 @@ export interface IHighlight {
|
||||
|
||||
let IDS = 0;
|
||||
|
||||
class EntryAccessor {
|
||||
export class QuickOpenItemAccessorClass implements IItemAccessor<QuickOpenEntry> {
|
||||
|
||||
public static getLabel(entry: QuickOpenEntry) {
|
||||
public getItemLabel(entry: QuickOpenEntry): string {
|
||||
return entry.getLabel();
|
||||
}
|
||||
|
||||
public static getResourcePath(entry: QuickOpenEntry) {
|
||||
public getItemDescription(entry: QuickOpenEntry): string {
|
||||
return entry.getDescription();
|
||||
}
|
||||
|
||||
public getItemPath(entry: QuickOpenEntry): string {
|
||||
const resource = entry.getResource();
|
||||
return resource && resource.fsPath;
|
||||
|
||||
return resource ? resource.fsPath : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export const QuickOpenItemAccessor = new QuickOpenItemAccessorClass();
|
||||
|
||||
export class QuickOpenEntry {
|
||||
private id: string;
|
||||
private labelHighlights: IHighlight[];
|
||||
@@ -166,116 +171,6 @@ export class QuickOpenEntry {
|
||||
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);
|
||||
}
|
||||
|
||||
public static compareByScore(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string, lookForNormalizedLower: string, scorerCache?: { [key: string]: number }): number {
|
||||
return doCompareByScore(elementA, elementB, EntryAccessor, lookFor, lookForNormalizedLower, scorerCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
return false; // TODO@Ben debt with editor history merging
|
||||
}
|
||||
@@ -686,3 +581,37 @@ export class QuickOpenModel implements
|
||||
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);
|
||||
}
|
||||
@@ -532,6 +532,13 @@ export class QuickOpenWidget implements IModelProvider {
|
||||
if (this.usageLogger) {
|
||||
const indexOfAcceptedElement = this.model.entries.indexOf(value);
|
||||
const entriesCount = this.model.entries.length;
|
||||
/* __GDPR__
|
||||
"quickOpenWidgetItemAccepted" : {
|
||||
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"count": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"isQuickNavigate": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.usageLogger.publicLog('quickOpenWidgetItemAccepted', { index: indexOfAcceptedElement, count: entriesCount, isQuickNavigate: this.quickNavigateConfiguration ? true : false });
|
||||
}
|
||||
|
||||
@@ -773,6 +780,12 @@ export class QuickOpenWidget implements IModelProvider {
|
||||
if (this.model) {
|
||||
const entriesCount = this.model.entries.filter(e => this.isElementVisible(this.model, e)).length;
|
||||
if (this.usageLogger) {
|
||||
/* __GDPR__
|
||||
"quickOpenWidgetCancelled" : {
|
||||
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"isQuickNavigate": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.usageLogger.publicLog('quickOpenWidgetCancelled', { count: entriesCount, isQuickNavigate: this.quickNavigateConfiguration ? true : false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"IQuickNavigateConfiguration" : {
|
||||
"keybindings" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
export interface IQuickNavigateConfiguration {
|
||||
keybindings: ResolvedKeybinding[];
|
||||
}
|
||||
|
||||
614
src/vs/base/parts/quickopen/common/quickOpenScorer.ts
Normal file
614
src/vs/base/parts/quickopen/common/quickOpenScorer.ts
Normal file
@@ -0,0 +1,614 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
import { matchesPrefix, IMatch, createMatches, matchesCamelCase, isUpper } from 'vs/base/common/filters';
|
||||
import { isEqual, nativeSep } from 'vs/base/common/paths';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { stripWildcards } from 'vs/base/common/strings';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export type Score = [number /* score */, number[] /* match positions */];
|
||||
export type ScorerCache = { [key: string]: IItemScore };
|
||||
|
||||
const NO_MATCH = 0;
|
||||
const NO_SCORE: Score = [NO_MATCH, []];
|
||||
|
||||
// const DEBUG = false;
|
||||
// const DEBUG_MATRIX = false;
|
||||
|
||||
export function score(target: string, query: string, queryLower: string, fuzzy: boolean): Score {
|
||||
if (!target || !query) {
|
||||
return NO_SCORE; // return early if target or query are undefined
|
||||
}
|
||||
|
||||
const targetLength = target.length;
|
||||
const queryLength = query.length;
|
||||
|
||||
if (targetLength < queryLength) {
|
||||
return NO_SCORE; // impossible for query to be contained in target
|
||||
}
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.group(`Target: ${target}, Query: ${query}`);
|
||||
// }
|
||||
|
||||
const targetLower = target.toLowerCase();
|
||||
|
||||
// When not searching fuzzy, we require the query to be contained fully
|
||||
// in the target string contiguously.
|
||||
if (!fuzzy) {
|
||||
const indexOfQueryInTarget = targetLower.indexOf(queryLower);
|
||||
if (indexOfQueryInTarget === -1) {
|
||||
// if (DEBUG) {
|
||||
// console.log(`Characters not matching consecutively ${queryLower} within ${targetLower}`);
|
||||
// }
|
||||
|
||||
return NO_SCORE;
|
||||
}
|
||||
}
|
||||
|
||||
// When searching fuzzy, we require the query to be contained fully
|
||||
// in the target string as separate substrings
|
||||
else {
|
||||
let targetOffset = 0;
|
||||
for (let queryIndex = 0; queryIndex < queryLength; queryIndex++) {
|
||||
targetOffset = targetLower.indexOf(queryLower[queryIndex], targetOffset);
|
||||
if (targetOffset === -1) {
|
||||
return NO_SCORE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res = doScore(query, queryLower, queryLength, target, targetLower, targetLength);
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold');
|
||||
// console.groupEnd();
|
||||
// }
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function doScore(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): [number, number[]] {
|
||||
const scores = [];
|
||||
const matches = [];
|
||||
|
||||
//
|
||||
// Build Scorer Matrix
|
||||
// The matrix is composed of query q and target t. For each index we score
|
||||
// q[i] with t[i] and compare that with the previous score. If the score is
|
||||
// equal or larger, we keep the match. In addition to the score, we also keep
|
||||
// the length of the consecutive matches to use as boost for the score.
|
||||
//
|
||||
// t a r g e t
|
||||
// q
|
||||
// u
|
||||
// e
|
||||
// r
|
||||
// y
|
||||
//
|
||||
for (let queryIndex = 0; queryIndex < queryLength; queryIndex++) {
|
||||
for (let targetIndex = 0; targetIndex < targetLength; targetIndex++) {
|
||||
const currentIndex = queryIndex * targetLength + targetIndex;
|
||||
const leftIndex = currentIndex - 1;
|
||||
const diagIndex = (queryIndex - 1) * targetLength + targetIndex - 1;
|
||||
|
||||
const leftScore = targetIndex > 0 ? scores[leftIndex] : 0;
|
||||
const diagScore = queryIndex > 0 && targetIndex > 0 ? scores[diagIndex] : 0;
|
||||
|
||||
const matchesSequenceLength = queryIndex > 0 && targetIndex > 0 ? matches[diagIndex] : 0;
|
||||
|
||||
const score = computeCharScore(query, queryLower, queryIndex, target, targetLower, targetIndex, matchesSequenceLength);
|
||||
|
||||
// We have a score and its equal or larger than the left score
|
||||
// Match: sequence continues growing from previous diag value
|
||||
// Score: increases by diag score value
|
||||
if (score && diagScore + score >= leftScore) {
|
||||
matches[currentIndex] = matchesSequenceLength + 1;
|
||||
scores[currentIndex] = diagScore + score;
|
||||
}
|
||||
|
||||
// We either have no score or the score is lower than the left score
|
||||
// Match: reset to 0
|
||||
// Score: pick up from left hand side
|
||||
else {
|
||||
matches[currentIndex] = NO_MATCH;
|
||||
scores[currentIndex] = leftScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore Positions (starting from bottom right of matrix)
|
||||
const positions = [];
|
||||
let queryIndex = queryLength - 1;
|
||||
let targetIndex = targetLength - 1;
|
||||
while (queryIndex >= 0 && targetIndex >= 0) {
|
||||
const currentIndex = queryIndex * targetLength + targetIndex;
|
||||
const match = matches[currentIndex];
|
||||
if (match === NO_MATCH) {
|
||||
targetIndex--; // go left
|
||||
} else {
|
||||
positions.push(targetIndex);
|
||||
|
||||
// go up and left
|
||||
queryIndex--;
|
||||
targetIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
// Print matrix
|
||||
// if (DEBUG_MATRIX) {
|
||||
// printMatrix(query, target, matches, scores);
|
||||
// }
|
||||
|
||||
return [scores[queryLength * targetLength - 1], positions.reverse()];
|
||||
}
|
||||
|
||||
function computeCharScore(query: string, queryLower: string, queryIndex: number, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number {
|
||||
let score = 0;
|
||||
|
||||
if (queryLower[queryIndex] !== targetLower[targetIndex]) {
|
||||
return score; // no match of characters
|
||||
}
|
||||
|
||||
// Character match bonus
|
||||
score += 1;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLower[queryIndex]} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal');
|
||||
// }
|
||||
|
||||
// Consecutive match bonus
|
||||
if (matchesSequenceLength > 0) {
|
||||
score += (matchesSequenceLength * 5);
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('Consecutive match bonus: ' + (matchesSequenceLength * 5));
|
||||
// }
|
||||
}
|
||||
|
||||
// Same case bonus
|
||||
if (query[queryIndex] === target[targetIndex]) {
|
||||
score += 1;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('Same case bonus: +1');
|
||||
// }
|
||||
}
|
||||
|
||||
// Start of word bonus
|
||||
if (targetIndex === 0) {
|
||||
score += 8;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('Start of word bonus: +8');
|
||||
// }
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
// After separator bonus
|
||||
const separatorBonus = scoreSeparatorAtPos(target.charCodeAt(targetIndex - 1));
|
||||
if (separatorBonus) {
|
||||
score += separatorBonus;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('After separtor bonus: +4');
|
||||
// }
|
||||
}
|
||||
|
||||
// Inside word upper case bonus (camel case)
|
||||
else if (isUpper(target.charCodeAt(targetIndex))) {
|
||||
score += 1;
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.log('Inside word upper case bonus: +1');
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// if (DEBUG) {
|
||||
// console.groupEnd();
|
||||
// }
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
function scoreSeparatorAtPos(charCode: number): number {
|
||||
switch (charCode) {
|
||||
case CharCode.Slash:
|
||||
case CharCode.Backslash:
|
||||
return 5; // prefer path separators...
|
||||
case CharCode.Underline:
|
||||
case CharCode.Dash:
|
||||
case CharCode.Period:
|
||||
case CharCode.Space:
|
||||
case CharCode.SingleQuote:
|
||||
case CharCode.DoubleQuote:
|
||||
case CharCode.Colon:
|
||||
return 4; // ...over other separators
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// function printMatrix(query: string, target: string, matches: number[], scores: number[]): void {
|
||||
// console.log('\t' + target.split('').join('\t'));
|
||||
// for (let queryIndex = 0; queryIndex < query.length; queryIndex++) {
|
||||
// let line = query[queryIndex] + '\t';
|
||||
// for (let targetIndex = 0; targetIndex < target.length; targetIndex++) {
|
||||
// const currentIndex = queryIndex * target.length + targetIndex;
|
||||
// line = line + 'M' + matches[currentIndex] + '/' + 'S' + scores[currentIndex] + '\t';
|
||||
// }
|
||||
|
||||
// console.log(line);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Scoring on structural items that have a label and optional description.
|
||||
*/
|
||||
export interface IItemScore {
|
||||
|
||||
/**
|
||||
* Overall score.
|
||||
*/
|
||||
score: number;
|
||||
|
||||
/**
|
||||
* Matches within the label.
|
||||
*/
|
||||
labelMatch?: IMatch[];
|
||||
|
||||
/**
|
||||
* Matches within the description.
|
||||
*/
|
||||
descriptionMatch?: IMatch[];
|
||||
}
|
||||
|
||||
const NO_ITEM_SCORE: IItemScore = Object.freeze({ score: 0 });
|
||||
|
||||
export interface IItemAccessor<T> {
|
||||
|
||||
/**
|
||||
* Just the label of the item to score on.
|
||||
*/
|
||||
getItemLabel(item: T): string;
|
||||
|
||||
/**
|
||||
* The optional description of the item to score on. Can be null.
|
||||
*/
|
||||
getItemDescription(item: T): string;
|
||||
|
||||
/**
|
||||
* If the item is a file, the path of the file to score on. Can be null.
|
||||
*/
|
||||
getItemPath(file: T): string;
|
||||
}
|
||||
|
||||
const PATH_IDENTITY_SCORE = 1 << 18;
|
||||
const LABEL_PREFIX_SCORE = 1 << 17;
|
||||
const LABEL_CAMELCASE_SCORE = 1 << 16;
|
||||
const LABEL_SCORE_THRESHOLD = 1 << 15;
|
||||
|
||||
export interface IPreparedQuery {
|
||||
value: string;
|
||||
lowercase: string;
|
||||
containsPathSeparator: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to prepare a search value for scoring in quick open by removing unwanted characters.
|
||||
*/
|
||||
export function prepareQuery(value: string): IPreparedQuery {
|
||||
let lowercase: string;
|
||||
let containsPathSeparator: boolean;
|
||||
|
||||
if (value) {
|
||||
value = stripWildcards(value).replace(/\s/g, ''); // get rid of all wildcards and whitespace
|
||||
if (isWindows) {
|
||||
value = value.replace(/\//g, '\\'); // Help Windows users to search for paths when using slash
|
||||
}
|
||||
|
||||
lowercase = value.toLowerCase();
|
||||
containsPathSeparator = value.indexOf(nativeSep) >= 0;
|
||||
}
|
||||
|
||||
return { value, lowercase, containsPathSeparator };
|
||||
}
|
||||
|
||||
export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): IItemScore {
|
||||
if (!item || !query.value) {
|
||||
return NO_ITEM_SCORE; // we need an item and query to score on at least
|
||||
}
|
||||
|
||||
const label = accessor.getItemLabel(item);
|
||||
if (!label) {
|
||||
return NO_ITEM_SCORE; // we need a label at least
|
||||
}
|
||||
|
||||
const description = accessor.getItemDescription(item);
|
||||
|
||||
let cacheHash: string;
|
||||
if (description) {
|
||||
cacheHash = `${label}${description}${query.value}${fuzzy}`;
|
||||
} else {
|
||||
cacheHash = `${label}${query.value}${fuzzy}`;
|
||||
}
|
||||
|
||||
const cached = cache[cacheHash];
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, fuzzy);
|
||||
cache[cacheHash] = itemScore;
|
||||
|
||||
return itemScore;
|
||||
}
|
||||
|
||||
function doScoreItem<T>(label: string, description: string, path: string, query: IPreparedQuery, fuzzy: boolean): IItemScore {
|
||||
|
||||
// 1.) treat identity matches on full path highest
|
||||
if (path && isEqual(query.value, path, true)) {
|
||||
return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : void 0 };
|
||||
}
|
||||
|
||||
// We only consider label matches if the query is not including file path separators
|
||||
const preferLabelMatches = !path || !query.containsPathSeparator;
|
||||
if (preferLabelMatches) {
|
||||
|
||||
// 2.) treat prefix matches on the label second highest
|
||||
const prefixLabelMatch = matchesPrefix(query.value, label);
|
||||
if (prefixLabelMatch) {
|
||||
return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch };
|
||||
}
|
||||
|
||||
// 3.) treat camelcase matches on the label third highest
|
||||
const camelcaseLabelMatch = matchesCamelCase(query.value, label);
|
||||
if (camelcaseLabelMatch) {
|
||||
return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch };
|
||||
}
|
||||
|
||||
// 4.) prefer scores on the label if any
|
||||
const [labelScore, labelPositions] = score(label, query.value, query.lowercase, fuzzy);
|
||||
if (labelScore) {
|
||||
return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) };
|
||||
}
|
||||
}
|
||||
|
||||
// 5.) finally compute description + label scores if we have a description
|
||||
if (description) {
|
||||
let descriptionPrefix = description;
|
||||
if (!!path) {
|
||||
descriptionPrefix = `${description}${nativeSep}`; // assume this is a file path
|
||||
}
|
||||
|
||||
const descriptionPrefixLength = descriptionPrefix.length;
|
||||
const descriptionAndLabel = `${descriptionPrefix}${label}`;
|
||||
|
||||
const [labelDescriptionScore, labelDescriptionPositions] = score(descriptionAndLabel, query.value, query.lowercase, fuzzy);
|
||||
if (labelDescriptionScore) {
|
||||
const labelDescriptionMatches = createMatches(labelDescriptionPositions);
|
||||
const labelMatch: IMatch[] = [];
|
||||
const descriptionMatch: IMatch[] = [];
|
||||
|
||||
// We have to split the matches back onto the label and description portions
|
||||
labelDescriptionMatches.forEach(h => {
|
||||
|
||||
// Match overlaps label and description part, we need to split it up
|
||||
if (h.start < descriptionPrefixLength && h.end > descriptionPrefixLength) {
|
||||
labelMatch.push({ start: 0, end: h.end - descriptionPrefixLength });
|
||||
descriptionMatch.push({ start: h.start, end: descriptionPrefixLength });
|
||||
}
|
||||
|
||||
// Match on label part
|
||||
else if (h.start >= descriptionPrefixLength) {
|
||||
labelMatch.push({ start: h.start - descriptionPrefixLength, end: h.end - descriptionPrefixLength });
|
||||
}
|
||||
|
||||
// Match on description part
|
||||
else {
|
||||
descriptionMatch.push(h);
|
||||
}
|
||||
});
|
||||
|
||||
return { score: labelDescriptionScore, labelMatch, descriptionMatch };
|
||||
}
|
||||
}
|
||||
|
||||
return NO_ITEM_SCORE;
|
||||
}
|
||||
|
||||
export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache, fallbackComparer = fallbackCompare): number {
|
||||
const itemScoreA = scoreItem(itemA, query, fuzzy, accessor, cache);
|
||||
const itemScoreB = scoreItem(itemB, query, fuzzy, accessor, cache);
|
||||
|
||||
const scoreA = itemScoreA.score;
|
||||
const scoreB = itemScoreB.score;
|
||||
|
||||
// 1.) prefer identity matches
|
||||
if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === PATH_IDENTITY_SCORE ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 2.) prefer label prefix matches
|
||||
if (scoreA === LABEL_PREFIX_SCORE || scoreB === LABEL_PREFIX_SCORE) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === LABEL_PREFIX_SCORE ? -1 : 1;
|
||||
}
|
||||
|
||||
const labelA = accessor.getItemLabel(itemA);
|
||||
const labelB = accessor.getItemLabel(itemB);
|
||||
|
||||
// prefer shorter names when both match on label prefix
|
||||
if (labelA.length !== labelB.length) {
|
||||
return labelA.length - labelB.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 3.) prefer camelcase matches
|
||||
if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1;
|
||||
}
|
||||
|
||||
const labelA = accessor.getItemLabel(itemA);
|
||||
const labelB = accessor.getItemLabel(itemB);
|
||||
|
||||
// prefer more compact camel case matches over longer
|
||||
const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch);
|
||||
if (comparedByMatchLength !== 0) {
|
||||
return comparedByMatchLength;
|
||||
}
|
||||
|
||||
// prefer shorter names when both match on label camelcase
|
||||
if (labelA.length !== labelB.length) {
|
||||
return labelA.length - labelB.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 4.) prefer label scores
|
||||
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
|
||||
if (scoreB < LABEL_SCORE_THRESHOLD) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (scoreA < LABEL_SCORE_THRESHOLD) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 5.) compare by score
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA > scoreB ? -1 : 1;
|
||||
}
|
||||
|
||||
// 6.) scores are identical, prefer more compact matches (label and description)
|
||||
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
|
||||
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
|
||||
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
|
||||
return itemBMatchDistance > itemAMatchDistance ? -1 : 1;
|
||||
}
|
||||
|
||||
// 7.) at this point, scores are identical and match compactness as well
|
||||
// for both items so we start to use the fallback compare
|
||||
return fallbackComparer(itemA, itemB, query, accessor);
|
||||
}
|
||||
|
||||
function computeLabelAndDescriptionMatchDistance<T>(item: T, score: IItemScore, accessor: IItemAccessor<T>): number {
|
||||
const hasLabelMatches = (score.labelMatch && score.labelMatch.length);
|
||||
const hasDescriptionMatches = (score.descriptionMatch && score.descriptionMatch.length);
|
||||
|
||||
let matchStart: number = -1;
|
||||
let matchEnd: number = -1;
|
||||
|
||||
// If we have description matches, the start is first of description match
|
||||
if (hasDescriptionMatches) {
|
||||
matchStart = score.descriptionMatch[0].start;
|
||||
}
|
||||
|
||||
// Otherwise, the start is the first label match
|
||||
else if (hasLabelMatches) {
|
||||
matchStart = score.labelMatch[0].start;
|
||||
}
|
||||
|
||||
// If we have label match, the end is the last label match
|
||||
// If we had a description match, we add the length of the description
|
||||
// as offset to the end to indicate this.
|
||||
if (hasLabelMatches) {
|
||||
matchEnd = score.labelMatch[score.labelMatch.length - 1].end;
|
||||
if (hasDescriptionMatches) {
|
||||
const itemDescription = accessor.getItemDescription(item);
|
||||
if (itemDescription) {
|
||||
matchEnd += itemDescription.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have just a description match, the end is the last description match
|
||||
else if (hasDescriptionMatches) {
|
||||
matchEnd = score.descriptionMatch[score.descriptionMatch.length - 1].end;
|
||||
}
|
||||
|
||||
return matchEnd - matchStart;
|
||||
}
|
||||
|
||||
function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number {
|
||||
if ((!matchesA && !matchesB) || (!matchesA.length && !matchesB.length)) {
|
||||
return 0; // make sure to not cause bad comparing when matches are not provided
|
||||
}
|
||||
|
||||
if (!matchesB || !matchesB.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!matchesA || !matchesA.length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Compute match length of A (first to last match)
|
||||
const matchStartA = matchesA[0].start;
|
||||
const matchEndA = matchesA[matchesA.length - 1].end;
|
||||
const matchLengthA = matchEndA - matchStartA;
|
||||
|
||||
// Compute match length of B (first to last match)
|
||||
const matchStartB = matchesB[0].start;
|
||||
const matchEndB = matchesB[matchesB.length - 1].end;
|
||||
const matchLengthB = matchEndB - matchStartB;
|
||||
|
||||
// Prefer shorter match length
|
||||
return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1;
|
||||
}
|
||||
|
||||
export function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor<T>): number {
|
||||
|
||||
// check for label + description length and prefer shorter
|
||||
const labelA = accessor.getItemLabel(itemA);
|
||||
const labelB = accessor.getItemLabel(itemB);
|
||||
|
||||
const descriptionA = accessor.getItemDescription(itemA);
|
||||
const descriptionB = accessor.getItemDescription(itemB);
|
||||
|
||||
const labelDescriptionALength = labelA.length + (descriptionA ? descriptionA.length : 0);
|
||||
const labelDescriptionBLength = labelB.length + (descriptionB ? descriptionB.length : 0);
|
||||
|
||||
if (labelDescriptionALength !== labelDescriptionBLength) {
|
||||
return labelDescriptionALength - labelDescriptionBLength;
|
||||
}
|
||||
|
||||
// check for path length and prefer shorter
|
||||
const pathA = accessor.getItemPath(itemA);
|
||||
const pathB = accessor.getItemPath(itemB);
|
||||
|
||||
if (pathA && pathB && pathA.length !== pathB.length) {
|
||||
return pathA.length - pathB.length;
|
||||
}
|
||||
|
||||
// 7.) finally we have equal scores and equal length, we fallback to comparer
|
||||
|
||||
// compare by label
|
||||
if (labelA !== labelB) {
|
||||
return compareAnything(labelA, labelB, query.value);
|
||||
}
|
||||
|
||||
// compare by description
|
||||
if (descriptionA && descriptionB && descriptionA !== descriptionB) {
|
||||
return compareAnything(descriptionA, descriptionB, query.value);
|
||||
}
|
||||
|
||||
// compare by path
|
||||
if (pathA && pathB && pathA !== pathB) {
|
||||
return compareAnything(pathA, pathB, query.value);
|
||||
}
|
||||
|
||||
// equal
|
||||
return 0;
|
||||
}
|
||||
778
src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts
Normal file
778
src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts
Normal file
@@ -0,0 +1,778 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as scorer from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { basename, dirname, nativeSep } from 'vs/base/common/paths';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
class ResourceAccessorClass implements scorer.IItemAccessor<URI> {
|
||||
|
||||
getItemLabel(resource: URI): string {
|
||||
return basename(resource.fsPath);
|
||||
}
|
||||
|
||||
getItemDescription(resource: URI): string {
|
||||
return dirname(resource.fsPath);
|
||||
}
|
||||
|
||||
getItemPath(resource: URI): string {
|
||||
return resource.fsPath;
|
||||
}
|
||||
}
|
||||
|
||||
const ResourceAccessor = new ResourceAccessorClass();
|
||||
|
||||
class NullAccessorClass implements scorer.IItemAccessor<URI> {
|
||||
|
||||
getItemLabel(resource: URI): string {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
getItemDescription(resource: URI): string {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
getItemPath(resource: URI): string {
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
function _doScore(target: string, query: string, fuzzy: boolean): scorer.Score {
|
||||
return scorer.score(target, query, query.toLowerCase(), fuzzy);
|
||||
}
|
||||
|
||||
function scoreItem<T>(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache): scorer.IItemScore {
|
||||
return scorer.scoreItem(item, scorer.prepareQuery(query), fuzzy, accessor, cache);
|
||||
}
|
||||
|
||||
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache, fallbackComparer = scorer.fallbackCompare): number {
|
||||
return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer);
|
||||
}
|
||||
|
||||
const NullAccessor = new NullAccessorClass();
|
||||
let cache: scorer.ScorerCache = Object.create(null);
|
||||
|
||||
suite('Quick Open Scorer', () => {
|
||||
|
||||
setup(() => {
|
||||
cache = Object.create(null);
|
||||
});
|
||||
|
||||
test('score (fuzzy)', function () {
|
||||
const target = 'HeLlo-World';
|
||||
|
||||
const scores: scorer.Score[] = [];
|
||||
scores.push(_doScore(target, 'HelLo-World', true)); // direct case match
|
||||
scores.push(_doScore(target, 'hello-world', true)); // direct mix-case match
|
||||
scores.push(_doScore(target, 'HW', true)); // direct case prefix (multiple)
|
||||
scores.push(_doScore(target, 'hw', true)); // direct mix-case prefix (multiple)
|
||||
scores.push(_doScore(target, 'H', true)); // direct case prefix
|
||||
scores.push(_doScore(target, 'h', true)); // direct mix-case prefix
|
||||
scores.push(_doScore(target, 'ld', true)); // in-string mix-case match (consecutive, avoids scattered hit)
|
||||
scores.push(_doScore(target, 'W', true)); // direct case word prefix
|
||||
scores.push(_doScore(target, 'w', true)); // direct mix-case word prefix
|
||||
scores.push(_doScore(target, 'Ld', true)); // in-string case match (multiple)
|
||||
scores.push(_doScore(target, 'L', true)); // in-string case match
|
||||
scores.push(_doScore(target, 'l', true)); // in-string mix-case match
|
||||
scores.push(_doScore(target, '4', true)); // no match
|
||||
|
||||
// Assert scoring order
|
||||
let sortedScores = scores.concat().sort((a, b) => b[0] - a[0]);
|
||||
assert.deepEqual(scores, sortedScores);
|
||||
|
||||
// Assert scoring positions
|
||||
let positions = scores[0][1];
|
||||
assert.equal(positions.length, 'HelLo-World'.length);
|
||||
|
||||
positions = scores[2][1];
|
||||
assert.equal(positions.length, 'HW'.length);
|
||||
assert.equal(positions[0], 0);
|
||||
assert.equal(positions[1], 6);
|
||||
});
|
||||
|
||||
test('score (non fuzzy)', function () {
|
||||
const target = 'HeLlo-World';
|
||||
|
||||
assert.ok(_doScore(target, 'HelLo-World', false)[0] > 0);
|
||||
assert.equal(_doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length);
|
||||
|
||||
assert.ok(_doScore(target, 'hello-world', false)[0] > 0);
|
||||
assert.equal(_doScore(target, 'HW', false)[0], 0);
|
||||
assert.ok(_doScore(target, 'h', false)[0] > 0);
|
||||
assert.ok(_doScore(target, 'ello', false)[0] > 0);
|
||||
assert.ok(_doScore(target, 'ld', false)[0] > 0);
|
||||
assert.equal(_doScore(target, 'eo', false)[0], 0);
|
||||
});
|
||||
|
||||
test('scoreItem - matches are proper', function () {
|
||||
let res = scoreItem(null, 'something', true, ResourceAccessor, cache);
|
||||
assert.ok(!res.score);
|
||||
|
||||
const resource = URI.file('/xyz/some/path/someFile123.txt');
|
||||
|
||||
res = scoreItem(resource, 'something', true, NullAccessor, cache);
|
||||
assert.ok(!res.score);
|
||||
|
||||
// Path Identity
|
||||
const identityRes = scoreItem(resource, ResourceAccessor.getItemPath(resource), true, ResourceAccessor, cache);
|
||||
assert.ok(identityRes.score);
|
||||
assert.equal(identityRes.descriptionMatch.length, 1);
|
||||
assert.equal(identityRes.labelMatch.length, 1);
|
||||
assert.equal(identityRes.descriptionMatch[0].start, 0);
|
||||
assert.equal(identityRes.descriptionMatch[0].end, ResourceAccessor.getItemDescription(resource).length);
|
||||
assert.equal(identityRes.labelMatch[0].start, 0);
|
||||
assert.equal(identityRes.labelMatch[0].end, ResourceAccessor.getItemLabel(resource).length);
|
||||
|
||||
// Basename Prefix
|
||||
const basenamePrefixRes = scoreItem(resource, 'som', true, ResourceAccessor, cache);
|
||||
assert.ok(basenamePrefixRes.score);
|
||||
assert.ok(!basenamePrefixRes.descriptionMatch);
|
||||
assert.equal(basenamePrefixRes.labelMatch.length, 1);
|
||||
assert.equal(basenamePrefixRes.labelMatch[0].start, 0);
|
||||
assert.equal(basenamePrefixRes.labelMatch[0].end, 'som'.length);
|
||||
|
||||
// Basename Camelcase
|
||||
const basenameCamelcaseRes = scoreItem(resource, 'sF', true, ResourceAccessor, cache);
|
||||
assert.ok(basenameCamelcaseRes.score);
|
||||
assert.ok(!basenameCamelcaseRes.descriptionMatch);
|
||||
assert.equal(basenameCamelcaseRes.labelMatch.length, 2);
|
||||
assert.equal(basenameCamelcaseRes.labelMatch[0].start, 0);
|
||||
assert.equal(basenameCamelcaseRes.labelMatch[0].end, 1);
|
||||
assert.equal(basenameCamelcaseRes.labelMatch[1].start, 4);
|
||||
assert.equal(basenameCamelcaseRes.labelMatch[1].end, 5);
|
||||
|
||||
// Basename Match
|
||||
const basenameRes = scoreItem(resource, 'of', true, ResourceAccessor, cache);
|
||||
assert.ok(basenameRes.score);
|
||||
assert.ok(!basenameRes.descriptionMatch);
|
||||
assert.equal(basenameRes.labelMatch.length, 2);
|
||||
assert.equal(basenameRes.labelMatch[0].start, 1);
|
||||
assert.equal(basenameRes.labelMatch[0].end, 2);
|
||||
assert.equal(basenameRes.labelMatch[1].start, 4);
|
||||
assert.equal(basenameRes.labelMatch[1].end, 5);
|
||||
|
||||
// Path Match
|
||||
const pathRes = scoreItem(resource, 'xyz123', true, ResourceAccessor, cache);
|
||||
assert.ok(pathRes.score);
|
||||
assert.ok(pathRes.descriptionMatch);
|
||||
assert.ok(pathRes.labelMatch);
|
||||
assert.equal(pathRes.labelMatch.length, 1);
|
||||
assert.equal(pathRes.labelMatch[0].start, 8);
|
||||
assert.equal(pathRes.labelMatch[0].end, 11);
|
||||
assert.equal(pathRes.descriptionMatch.length, 1);
|
||||
assert.equal(pathRes.descriptionMatch[0].start, 1);
|
||||
assert.equal(pathRes.descriptionMatch[0].end, 4);
|
||||
|
||||
// No Match
|
||||
const noRes = scoreItem(resource, '987', true, ResourceAccessor, cache);
|
||||
assert.ok(!noRes.score);
|
||||
assert.ok(!noRes.labelMatch);
|
||||
assert.ok(!noRes.descriptionMatch);
|
||||
|
||||
// Verify Scores
|
||||
assert.ok(identityRes.score > basenamePrefixRes.score);
|
||||
assert.ok(basenamePrefixRes.score > basenameRes.score);
|
||||
assert.ok(basenameRes.score > pathRes.score);
|
||||
assert.ok(pathRes.score > noRes.score);
|
||||
});
|
||||
|
||||
test('scoreItem - invalid input', function () {
|
||||
|
||||
let res = scoreItem(null, null, true, ResourceAccessor, cache);
|
||||
assert.equal(res.score, 0);
|
||||
|
||||
res = scoreItem(null, 'null', true, ResourceAccessor, cache);
|
||||
assert.equal(res.score, 0);
|
||||
});
|
||||
|
||||
test('scoreItem - optimize for file paths', function () {
|
||||
const resource = URI.file('/xyz/others/spath/some/xsp/file123.txt');
|
||||
|
||||
// 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
|
||||
// end gets returned.
|
||||
const pathRes = scoreItem(resource, 'xspfile123', true, ResourceAccessor, cache);
|
||||
assert.ok(pathRes.score);
|
||||
assert.ok(pathRes.descriptionMatch);
|
||||
assert.ok(pathRes.labelMatch);
|
||||
assert.equal(pathRes.labelMatch.length, 1);
|
||||
assert.equal(pathRes.labelMatch[0].start, 0);
|
||||
assert.equal(pathRes.labelMatch[0].end, 7);
|
||||
assert.equal(pathRes.descriptionMatch.length, 1);
|
||||
assert.equal(pathRes.descriptionMatch[0].start, 23);
|
||||
assert.equal(pathRes.descriptionMatch[0].end, 26);
|
||||
});
|
||||
|
||||
test('scoreItem - avoid match scattering (bug #36119)', function () {
|
||||
const resource = URI.file('projects/ui/cula/ats/target.mk');;
|
||||
|
||||
const pathRes = scoreItem(resource, 'tcltarget.mk', true, ResourceAccessor, cache);
|
||||
assert.ok(pathRes.score);
|
||||
assert.ok(pathRes.descriptionMatch);
|
||||
assert.ok(pathRes.labelMatch);
|
||||
assert.equal(pathRes.labelMatch.length, 1);
|
||||
assert.equal(pathRes.labelMatch[0].start, 0);
|
||||
assert.equal(pathRes.labelMatch[0].end, 9);
|
||||
});
|
||||
|
||||
test('scoreItem - prefers more compact matches', function () {
|
||||
const resource = URI.file('/1a111d1/11a1d1/something.txt');
|
||||
|
||||
// expect "ad" to be matched towards the end of the file because the
|
||||
// match is more compact
|
||||
const res = scoreItem(resource, 'ad', true, ResourceAccessor, cache);
|
||||
assert.ok(res.score);
|
||||
assert.ok(res.descriptionMatch);
|
||||
assert.ok(!res.labelMatch.length);
|
||||
assert.equal(res.descriptionMatch.length, 2);
|
||||
assert.equal(res.descriptionMatch[0].start, 11);
|
||||
assert.equal(res.descriptionMatch[0].end, 12);
|
||||
assert.equal(res.descriptionMatch[1].start, 13);
|
||||
assert.equal(res.descriptionMatch[1].end, 14);
|
||||
});
|
||||
|
||||
test('compareItemsByScore - identity', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileB.txt');
|
||||
const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');
|
||||
|
||||
// Full resource A path
|
||||
let query = ResourceAccessor.getItemPath(resourceA);
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
// Full resource B path
|
||||
query = ResourceAccessor.getItemPath(resourceB);
|
||||
|
||||
res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - basename prefix', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileB.txt');
|
||||
const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');
|
||||
|
||||
// Full resource A basename
|
||||
let query = ResourceAccessor.getItemLabel(resourceA);
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
// Full resource B basename
|
||||
query = ResourceAccessor.getItemLabel(resourceB);
|
||||
|
||||
res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - basename camelcase', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileB.txt');
|
||||
const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');
|
||||
|
||||
// resource A camelcase
|
||||
let query = 'fA';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
// resource B camelcase
|
||||
query = 'fB';
|
||||
|
||||
res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - basename scores', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileB.txt');
|
||||
const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');
|
||||
|
||||
// Resource A part of basename
|
||||
let query = 'fileA';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
// Resource B part of basename
|
||||
query = 'fileB';
|
||||
|
||||
res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - path scores', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileB.txt');
|
||||
const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');
|
||||
|
||||
// Resource A part of path
|
||||
let query = 'pathfileA';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
// Resource B part of path
|
||||
query = 'pathfileB';
|
||||
|
||||
res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter basenames', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileBLonger.txt');
|
||||
const resourceC = URI.file('/unrelated/the/path/other/fileC.txt');
|
||||
|
||||
// Resource A part of path
|
||||
let query = 'somepath';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter basenames (match on basename)', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileBLonger.txt');
|
||||
const resourceC = URI.file('/unrelated/the/path/other/fileC.txt');
|
||||
|
||||
// Resource A part of path
|
||||
let query = 'file';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceC);
|
||||
assert.equal(res[2], resourceB);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceC);
|
||||
assert.equal(res[2], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter paths', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileB.txt');
|
||||
const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');
|
||||
|
||||
// Resource A part of path
|
||||
let query = 'somepath';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter paths (bug #17443)', function () {
|
||||
const resourceA = URI.file('config/test/t1.js');
|
||||
const resourceB = URI.file('config/test.js');
|
||||
const resourceC = URI.file('config/test/t2.js');
|
||||
|
||||
let query = 'co/te';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - allow to provide fallback sorter (bug #31591)', function () {
|
||||
const resourceA = URI.file('virtual/vscode.d.ts');
|
||||
const resourceB = URI.file('vscode/src/vs/vscode.d.ts');
|
||||
|
||||
let query = 'vscode';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => -1));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => -1));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer more compact camel case matches', function () {
|
||||
const resourceA = URI.file('config/test/openthisAnythingHandler.js');
|
||||
const resourceB = URI.file('config/test/openthisisnotsorelevantforthequeryAnyHand.js');
|
||||
|
||||
let query = 'AH';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer more compact matches (label)', function () {
|
||||
const resourceA = URI.file('config/test/examasdaple.js');
|
||||
const resourceB = URI.file('config/test/exampleasdaasd.ts');
|
||||
|
||||
let query = 'xp';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer more compact matches (path)', function () {
|
||||
const resourceA = URI.file('config/test/examasdaple/file.js');
|
||||
const resourceB = URI.file('config/test/exampleasdaasd/file.ts');
|
||||
|
||||
let query = 'xp';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer more compact matches (label and path)', function () {
|
||||
const resourceA = URI.file('config/example/thisfile.ts');
|
||||
const resourceB = URI.file('config/24234243244/example/file.js');
|
||||
|
||||
let query = 'exfile';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #34210)', function () {
|
||||
const resourceA = URI.file('node_modules1/bundle/lib/model/modules/ot1/index.js');
|
||||
const resourceB = URI.file('node_modules1/bundle/lib/model/modules/un1/index.js');
|
||||
const resourceC = URI.file('node_modules1/bundle/lib/model/modules/modu1/index.js');
|
||||
const resourceD = URI.file('node_modules1/bundle/lib/model/modules/oddl1/index.js');
|
||||
|
||||
let query = isWindows ? 'modu1\\index.js' : 'modu1/index.js';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceC);
|
||||
|
||||
query = isWindows ? 'un1\\index.js' : 'un1/index.js';
|
||||
|
||||
res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceC, resourceB, resourceA, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #21019 1.)', function () {
|
||||
const resourceA = URI.file('app/containers/Services/NetworkData/ServiceDetails/ServiceLoad/index.js');
|
||||
const resourceB = URI.file('app/containers/Services/NetworkData/ServiceDetails/ServiceDistribution/index.js');
|
||||
const resourceC = URI.file('app/containers/Services/NetworkData/ServiceDetailTabs/ServiceTabs/StatVideo/index.js');
|
||||
|
||||
let query = 'StatVideoindex';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #21019 2.)', function () {
|
||||
const resourceA = URI.file('src/build-helper/store/redux.ts');
|
||||
const resourceB = URI.file('src/repository/store/redux.ts');
|
||||
|
||||
let query = 'reproreduxts';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #26649)', function () {
|
||||
const resourceA = URI.file('photobook/src/components/AddPagesButton/index.js');
|
||||
const resourceB = URI.file('photobook/src/components/ApprovalPageHeader/index.js');
|
||||
const resourceC = URI.file('photobook/src/canvasComponents/BookPage/index.js');
|
||||
|
||||
let query = 'bookpageIndex';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceC);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #33247)', function () {
|
||||
const resourceA = URI.file('ui/src/utils/constants.js');
|
||||
const resourceB = URI.file('ui/src/ui/Icons/index.js');
|
||||
|
||||
let query = isWindows ? 'ui\\icons' : 'ui/icons';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #33247 comment)', function () {
|
||||
const resourceA = URI.file('ui/src/components/IDInput/index.js');
|
||||
const resourceB = URI.file('ui/src/ui/Input/index.js');
|
||||
|
||||
let query = isWindows ? 'ui\\input\\index' : 'ui/input/index';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #36166)', function () {
|
||||
const resourceA = URI.file('django/contrib/sites/locale/ga/LC_MESSAGES/django.mo');
|
||||
const resourceB = URI.file('django/core/signals.py');
|
||||
|
||||
let query = 'djancosig';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #32918)', function () {
|
||||
const resourceA = URI.file('adsys/protected/config.php');
|
||||
const resourceB = URI.file('adsys/protected/framework/smarty/sysplugins/smarty_internal_config.php');
|
||||
const resourceC = URI.file('duowanVideo/wap/protected/config.php');
|
||||
|
||||
let query = 'protectedconfig.php';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceC);
|
||||
assert.equal(res[2], resourceB);
|
||||
|
||||
res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceC);
|
||||
assert.equal(res[2], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #14879)', function () {
|
||||
const resourceA = URI.file('pkg/search/gradient/testdata/constraint_attrMatchString.yml');
|
||||
const resourceB = URI.file('cmd/gradient/main.go');
|
||||
|
||||
let query = 'gradientmain';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #14727 1)', function () {
|
||||
const resourceA = URI.file('alpha-beta-cappa.txt');
|
||||
const resourceB = URI.file('abc.txt');
|
||||
|
||||
let query = 'abc';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #14727 2)', function () {
|
||||
const resourceA = URI.file('xerxes-yak-zubba/index.js');
|
||||
const resourceB = URI.file('xyz/index.js');
|
||||
|
||||
let query = 'xyz';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #18381)', function () {
|
||||
const resourceA = URI.file('AssymblyInfo.cs');
|
||||
const resourceB = URI.file('IAsynchronousTask.java');
|
||||
|
||||
let query = 'async';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #35572)', function () {
|
||||
const resourceA = URI.file('static/app/source/angluar/-admin/-organization/-settings/layout/layout.js');
|
||||
const resourceB = URI.file('static/app/source/angular/-admin/-project/-settings/_settings/settings.js');
|
||||
|
||||
let query = 'partisettings';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #36810)', function () {
|
||||
const resourceA = URI.file('Trilby.TrilbyTV.Web.Portal/Views/Systems/Index.cshtml');
|
||||
const resourceB = URI.file('Trilby.TrilbyTV.Web.Portal/Areas/Admins/Views/Tips/Index.cshtml');
|
||||
|
||||
let query = 'tipsindex.cshtml';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer shorter hit (bug #20546)', function () {
|
||||
const resourceA = URI.file('editor/core/components/tests/list-view-spec.js');
|
||||
const resourceB = URI.file('editor/core/components/list-view.js');
|
||||
|
||||
let query = 'listview';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - avoid match scattering (bug #12095)', function () {
|
||||
const resourceA = URI.file('src/vs/workbench/parts/files/common/explorerViewModel.ts');
|
||||
const resourceB = URI.file('src/vs/workbench/parts/files/browser/views/explorerView.ts');
|
||||
const resourceC = URI.file('src/vs/workbench/parts/files/browser/views/explorerViewer.ts');
|
||||
|
||||
let query = 'filesexplorerview.ts';
|
||||
|
||||
let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceA, resourceC, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('prepareSearchForScoring', function () {
|
||||
assert.equal(scorer.prepareQuery(' f*a ').value, 'fa');
|
||||
assert.equal(scorer.prepareQuery('model Tester.ts').value, 'modelTester.ts');
|
||||
assert.equal(scorer.prepareQuery('Model Tester.ts').lowercase, 'modeltester.ts');
|
||||
assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false);
|
||||
assert.equal(scorer.prepareQuery('Model' + nativeSep + 'Tester.ts').containsPathSeparator, true);
|
||||
});
|
||||
});
|
||||
@@ -131,7 +131,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
public onClick(tree: _.ITree, element: any, event: mouse.IMouseEvent): boolean {
|
||||
var isMac = platform.isMacintosh;
|
||||
const isMac = platform.isMacintosh;
|
||||
|
||||
// A Ctrl click on the Mac is a context menu event
|
||||
if (isMac && event.ctrlKey) {
|
||||
@@ -152,13 +152,13 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
|
||||
var payload = { origin: origin, originalEvent: eventish };
|
||||
const payload = { origin: origin, originalEvent: eventish };
|
||||
|
||||
if (tree.getInput() === element) {
|
||||
tree.clearFocus(payload);
|
||||
tree.clearSelection(payload);
|
||||
} else {
|
||||
var isMouseDown = eventish && (<mouse.IMouseEvent>eventish).browserEvent && (<mouse.IMouseEvent>eventish).browserEvent.type === 'mousedown';
|
||||
const isMouseDown = eventish && (<mouse.IMouseEvent>eventish).browserEvent && (<mouse.IMouseEvent>eventish).browserEvent.type === 'mousedown';
|
||||
if (!isMouseDown) {
|
||||
eventish.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
||||
}
|
||||
@@ -193,7 +193,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
public onTap(tree: _.ITree, element: any, event: touch.GestureEvent): boolean {
|
||||
var target = <HTMLElement>event.initialTarget;
|
||||
const target = <HTMLElement>event.initialTarget;
|
||||
|
||||
if (target && target.tagName && target.tagName.toLowerCase() === 'input') {
|
||||
return false; // Ignore event if target is a form input field (avoids browser specific issues)
|
||||
@@ -211,7 +211,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
private onKey(bindings: KeybindingDispatcher, tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var handler = bindings.dispatch(event.toKeybinding());
|
||||
const handler = bindings.dispatch(event.toKeybinding());
|
||||
if (handler) {
|
||||
if (handler(tree, event)) {
|
||||
event.preventDefault();
|
||||
@@ -223,7 +223,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onUp(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
@@ -235,7 +235,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onPageUp(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
@@ -247,7 +247,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onDown(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
@@ -259,7 +259,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onPageDown(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
@@ -271,7 +271,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onHome(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
@@ -283,7 +283,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onEnd(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
@@ -295,12 +295,12 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onLeft(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
var focus = tree.getFocus();
|
||||
const focus = tree.getFocus();
|
||||
tree.collapse(focus).then(didCollapse => {
|
||||
if (focus && !didCollapse) {
|
||||
tree.focusParent(payload);
|
||||
@@ -313,12 +313,12 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onRight(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
} else {
|
||||
var focus = tree.getFocus();
|
||||
const focus = tree.getFocus();
|
||||
tree.expand(focus).then(didExpand => {
|
||||
if (focus && !didExpand) {
|
||||
tree.focusFirstChild(payload);
|
||||
@@ -331,12 +331,12 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onEnter(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
return false;
|
||||
}
|
||||
var focus = tree.getFocus();
|
||||
const focus = tree.getFocus();
|
||||
if (focus) {
|
||||
tree.setSelection([focus], payload);
|
||||
}
|
||||
@@ -347,7 +347,7 @@ export class DefaultController implements _.IController {
|
||||
if (tree.getHighlight()) {
|
||||
return false;
|
||||
}
|
||||
var focus = tree.getFocus();
|
||||
const focus = tree.getFocus();
|
||||
if (focus) {
|
||||
tree.toggleExpansion(focus);
|
||||
}
|
||||
@@ -355,7 +355,7 @@ export class DefaultController implements _.IController {
|
||||
}
|
||||
|
||||
protected onEscape(tree: _.ITree, event: IKeyboardEvent): boolean {
|
||||
var payload = { origin: 'keyboard', originalEvent: event };
|
||||
const payload = { origin: 'keyboard', originalEvent: event };
|
||||
|
||||
if (tree.getHighlight()) {
|
||||
tree.clearHighlight(payload);
|
||||
|
||||
@@ -52,12 +52,12 @@ export class TreeContext implements _.ITreeContext {
|
||||
|
||||
const defaultStyles: _.ITreeStyles = {
|
||||
listFocusBackground: Color.fromHex('#073655'),
|
||||
listActiveSelectionBackground: Color.fromHex('#3062D6'),
|
||||
listActiveSelectionBackground: Color.fromHex('#0E639C'),
|
||||
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
|
||||
listFocusAndSelectionBackground: Color.fromHex('#094771'),
|
||||
listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'),
|
||||
listInactiveSelectionBackground: Color.fromHex('#C8C8C8'),
|
||||
listHoverBackground: Color.fromHex('#DCDCDC'),
|
||||
listInactiveSelectionBackground: Color.fromHex('#3F3F46'),
|
||||
listHoverBackground: Color.fromHex('#2A2D2E'),
|
||||
listDropBackground: Color.fromHex('#383B3D')
|
||||
};
|
||||
|
||||
|
||||
@@ -556,7 +556,7 @@ export class Item extends Events.EventEmitter {
|
||||
}
|
||||
|
||||
private mapEachChild<T>(fn: (child: Item) => T): T[] {
|
||||
var result = [];
|
||||
var result: T[] = [];
|
||||
this.forEachChild((child) => {
|
||||
result.push(fn(child));
|
||||
});
|
||||
|
||||
@@ -1550,7 +1550,6 @@ export class TreeView extends HeightMap {
|
||||
// {{SQL CARBON EDIT}}
|
||||
this.context.dnd.dropAbort(this.context.tree, this.currentDragAndDropData);
|
||||
}
|
||||
|
||||
this.cancelDragAndDropScrollInterval();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { IIterator, ArrayIterator } from 'vs/base/common/iterator';
|
||||
import { INextIterator, ArrayIterator } from 'vs/base/common/iterator';
|
||||
import { Item } from './treeModel';
|
||||
|
||||
export interface IViewItem {
|
||||
@@ -30,7 +30,7 @@ export class HeightMap extends EventEmitter {
|
||||
return !last ? 0 : last.top + last.height;
|
||||
}
|
||||
|
||||
public onInsertItems(iterator: IIterator<Item>, afterItemId: string = null): number {
|
||||
public onInsertItems(iterator: INextIterator<Item>, afterItemId: string = null): number {
|
||||
var item: Item;
|
||||
var viewItem: IViewItem;
|
||||
var i: number, j: number;
|
||||
@@ -90,7 +90,7 @@ export class HeightMap extends EventEmitter {
|
||||
}
|
||||
|
||||
// Contiguous items
|
||||
public onRemoveItems(iterator: IIterator<string>): void {
|
||||
public onRemoveItems(iterator: INextIterator<string>): void {
|
||||
var itemId: string;
|
||||
var viewItem: IViewItem;
|
||||
var startIndex: number = null;
|
||||
@@ -139,7 +139,7 @@ export class HeightMap extends EventEmitter {
|
||||
}
|
||||
|
||||
// Ordered, but not necessarily contiguous items
|
||||
public onRefreshItems(iterator: IIterator<Item>): void {
|
||||
public onRefreshItems(iterator: INextIterator<Item>): void {
|
||||
var item: Item;
|
||||
var viewItem: IViewItem;
|
||||
var newHeight: number;
|
||||
@@ -240,4 +240,4 @@ export class HeightMap extends EventEmitter {
|
||||
this.heightMap = null;
|
||||
this.indexes = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import assert = require('assert');
|
||||
import lifecycle = require('vs/base/common/lifecycle');
|
||||
import ee = require('vs/base/common/eventEmitter');
|
||||
import _ = require('vs/base/parts/tree/browser/tree');
|
||||
import WinJS = require('vs/base/common/winjs.base');
|
||||
import Events = require('vs/base/common/eventEmitter');
|
||||
@@ -75,7 +74,7 @@ class EventCounter {
|
||||
this._count = 0;
|
||||
}
|
||||
|
||||
public listen(emitter: ee.IEventEmitter, event: string, fn: (e) => void = null): () => void {
|
||||
public listen(emitter: Events.IEventEmitter, event: string, fn: (e) => void = null): () => void {
|
||||
let r = emitter.addListener(event, (e) => {
|
||||
this._count++;
|
||||
if (fn) {
|
||||
|
||||
Reference in New Issue
Block a user