Merge from vscode 6fded8a497cd0142de3a1c607649a5423a091a25

This commit is contained in:
ADS Merger
2020-04-04 04:30:52 +00:00
parent 00cc0074f7
commit 35f1a014d5
184 changed files with 3043 additions and 2285 deletions

View File

@@ -159,6 +159,11 @@ export let addStandardDisposableGenericMouseDownListner = function addStandardDi
return addDisposableGenericMouseDownListner(node, wrapHandler, useCapture);
};
export let addStandardDisposableGenericMouseUpListner = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
let wrapHandler = _wrapAsStandardMouseEvent(handler);
return addDisposableGenericMouseUpListner(node, wrapHandler, useCapture);
};
export function addDisposableGenericMouseDownListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture);
}

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.7146 12.3573L6.33332 12.976L11 8.30935V7.69064L6.33332 3.02397L5.7146 3.64269L10.0719 7.99999Z" fill="#C5C5C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.7146 12.3573L6.33332 12.976L11 8.30935V7.69063L6.33332 3.02396L5.7146 3.64268L10.0719 7.99999Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 282 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.7146 12.3573L6.33332 12.976L11 8.30935V7.69063L6.33332 3.02396L5.7146 3.64268L10.0719 7.99999Z" fill="#424242"/>
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View File

@@ -36,7 +36,7 @@ export class HorizontalScrollbar extends AbstractScrollbar {
let scrollbarDelta = (options.horizontalScrollbarSize - ARROW_IMG_SIZE) / 2;
this._createArrow({
className: 'left-arrow',
className: 'scra codicon codicon-triangle-left',
top: scrollbarDelta,
left: arrowDelta,
bottom: undefined,
@@ -47,7 +47,7 @@ export class HorizontalScrollbar extends AbstractScrollbar {
});
this._createArrow({
className: 'right-arrow',
className: 'scra codicon codicon-triangle-right',
top: scrollbarDelta,
left: undefined,
bottom: undefined,

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11 11"><path transform="rotate(-180 5.49045991897583,5.811500072479248)" fill="#E8E8E8" d="m9.48046,8.9615l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z"/></svg>

Before

Width:  |  Height:  |  Size: 233 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11 11"><path transform="rotate(-180 5.49045991897583,5.811500072479248)" fill="#424242" d="m9.48046,8.9615l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z"/></svg>

Before

Width:  |  Height:  |  Size: 233 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11 11"><path transform="rotate(-90 5.490459918975831,5.431382179260254)" fill="#E8E8E8" d="m9.48046,8.58138l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z"/></svg>

Before

Width:  |  Height:  |  Size: 234 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11 11"><path transform="rotate(-90 5.490459918975831,5.431382179260254)" fill="#424242" d="m9.48046,8.58138l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z"/></svg>

Before

Width:  |  Height:  |  Size: 234 B

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg"><path transform="rotate(90 5.6171650886535645,5.55808973312378) " fill="#E8E8E8" d="m9.60717,8.70809l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z"/></svg>

Before

Width:  |  Height:  |  Size: 234 B

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg"><path transform="rotate(90 5.6171650886535645,5.55808973312378) " fill="#424242" d="m9.60717,8.70809l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z"/></svg>

Before

Width:  |  Height:  |  Size: 234 B

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg"><path d="m9.48046,8.9615l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z" fill="#E8E8E8"/></svg>

Before

Width:  |  Height:  |  Size: 173 B

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg"><path d="m9.48046,8.9615l1.26,-1.26l-5.04,-5.04l-5.46,5.04l1.26,1.26l4.2,-3.78l3.78,3.78z" fill="#424242"/></svg>

Before

Width:  |  Height:  |  Size: 173 B

View File

@@ -4,38 +4,9 @@
*--------------------------------------------------------------------------------------------*/
/* Arrows */
.monaco-scrollable-element > .scrollbar > .up-arrow {
background: url('arrow-up.svg');
.monaco-scrollable-element > .scrollbar > .scra {
cursor: pointer;
}
.monaco-scrollable-element > .scrollbar > .down-arrow {
background: url('arrow-down.svg');
cursor: pointer;
}
.monaco-scrollable-element > .scrollbar > .left-arrow {
background: url('arrow-left.svg');
cursor: pointer;
}
.monaco-scrollable-element > .scrollbar > .right-arrow {
background: url('arrow-right.svg');
cursor: pointer;
}
.hc-black .monaco-scrollable-element > .scrollbar > .up-arrow,
.vs-dark .monaco-scrollable-element > .scrollbar > .up-arrow {
background: url('arrow-up-dark.svg');
}
.hc-black .monaco-scrollable-element > .scrollbar > .down-arrow,
.vs-dark .monaco-scrollable-element > .scrollbar > .down-arrow {
background: url('arrow-down-dark.svg');
}
.hc-black .monaco-scrollable-element > .scrollbar > .left-arrow,
.vs-dark .monaco-scrollable-element > .scrollbar > .left-arrow {
background: url('arrow-left-dark.svg');
}
.hc-black .monaco-scrollable-element > .scrollbar > .right-arrow,
.vs-dark .monaco-scrollable-element > .scrollbar > .right-arrow {
background: url('arrow-right-dark.svg');
font-size: 11px !important;
}
.monaco-scrollable-element > .visible {
@@ -137,4 +108,4 @@
.hc-black .monaco-scrollable-element .shadow.top.left {
box-shadow: none;
}
}

View File

@@ -37,7 +37,7 @@ export class VerticalScrollbar extends AbstractScrollbar {
let scrollbarDelta = (options.verticalScrollbarSize - ARROW_IMG_SIZE) / 2;
this._createArrow({
className: 'up-arrow',
className: 'scra codicon codicon-triangle-up',
top: arrowDelta,
left: scrollbarDelta,
bottom: undefined,
@@ -48,7 +48,7 @@ export class VerticalScrollbar extends AbstractScrollbar {
});
this._createArrow({
className: 'down-arrow',
className: 'scra codicon codicon-triangle-down',
top: undefined,
left: scrollbarDelta,
bottom: arrowDelta,

View File

@@ -52,6 +52,10 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
}));
});
this._register(dom.addStandardDisposableListener(this.selectElement, 'click', (e) => {
dom.EventHelper.stop(e, true);
}));
this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
this.selectElement.title = e.target.value;
this._onDidSelect.fire({

View File

@@ -68,6 +68,24 @@
color: inherit;
}
.monaco-pane-view .pane > .pane-header .monaco-action-bar .action-item.select-container {
cursor: default;
}
.monaco-pane-view .pane > .pane-header .action-item .monaco-select-box {
cursor: pointer;
min-width: 110px;
min-height: 18px;
padding: 2px 23px 2px 8px;
background-color: inherit !important;
color: inherit !important;
}
.linux .monaco-pane-view .pane > .pane-header .action-item .monaco-select-box,
.windows .monaco-pane-view .pane > .pane-header .action-item .monaco-select-box {
padding: 0px 23px 2px 8px;
}
/* Bold font style does not go well with CJK fonts */
.monaco-pane-view:lang(zh-Hans) .pane > .pane-header,
.monaco-pane-view:lang(zh-Hant) .pane > .pane-header,

View File

@@ -31,6 +31,7 @@ export interface IPaneStyles {
headerForeground?: Color;
headerBackground?: Color;
headerBorder?: Color;
leftBorder?: Color;
}
/**
@@ -243,6 +244,8 @@ export abstract class Pane extends Disposable implements IView {
style(styles: IPaneStyles): void {
this.styles = styles;
this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : '';
if (!this.header) {
return;
}
@@ -261,7 +264,7 @@ export abstract class Pane extends Disposable implements IView {
this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : '';
this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : '';
this.header.style.borderTop = this.styles.headerBorder ? `1px solid ${this.styles.headerBorder}` : '';
this.header.style.borderTop = this.styles.headerBorder && this.orientation === Orientation.VERTICAL ? `1px solid ${this.styles.headerBorder}` : '';
this._dropBackground = this.styles.dropBackground;
}

View File

@@ -1,31 +0,0 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g style="fill:grey;">
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,31 +0,0 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g style="fill:white;">
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,31 +0,0 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g>
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69064L6.33333 3.02397L5.71461 3.64269L10.0719 7.99999Z" fill="#C5C5C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 286 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69063L6.33333 3.02396L5.71461 3.64268L10.0719 7.99999Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69063L6.33333 3.02396L5.71461 3.64268L10.0719 7.99999Z" fill="#424242"/>
</svg>

Before

Width:  |  Height:  |  Size: 286 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97603 10.0719L12.3333 5.71461L12.9521 6.33333L8.28539 11L7.66667 11L3 6.33333L3.61872 5.71461L7.97603 10.0719Z" fill="#C5C5C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97603 10.0719L12.3333 5.71461L12.9521 6.33333L8.28539 11L7.66667 11L3 6.33333L3.61872 5.71461L7.97603 10.0719Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 282 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97603 10.0719L12.3333 5.71461L12.9521 6.33333L8.28539 11L7.66667 11L3 6.33333L3.61872 5.71461L7.97603 10.0719Z" fill="#424242"/>
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View File

@@ -4,56 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import { compareAnything } from 'vs/base/common/comparers';
import { matchesPrefix, IMatch, matchesCamelCase, isUpper } from 'vs/base/common/filters';
import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches } from 'vs/base/common/filters';
import { sep } from 'vs/base/common/path';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import { distinctES6 } from 'vs/base/common/arrays';
export type Score = [number /* score */, number[] /* match positions */];
export type ScorerCache = { [key: string]: IItemScore };
//#region Fuzzy scorer
export type FuzzyScore = [number /* score */, number[] /* match positions */];
export type FuzzyScorerCache = { [key: string]: IItemScore };
const NO_MATCH = 0;
const NO_SCORE: Score = [NO_MATCH, []];
const NO_SCORE: FuzzyScore = [NO_MATCH, []];
// const DEBUG = false;
// const DEBUG_MATRIX = false;
export function score(target: string, query: IPreparedQuery, fuzzy: boolean): Score {
if (query.values && query.values.length > 1) {
return scoreMultiple(target, query.values, fuzzy);
}
return scoreSingle(target, query.normalized, query.normalizedLowercase, fuzzy);
}
function scoreMultiple(target: string, query: IPreparedQueryPiece[], fuzzy: boolean): Score {
let totalScore = NO_MATCH;
const totalPositions: number[] = [];
for (const { normalized, normalizedLowercase } of query) {
const [scoreValue, positions] = scoreSingle(target, normalized, normalizedLowercase, fuzzy);
if (scoreValue === NO_MATCH) {
// if a single query value does not match, return with
// no score entirely, we require all queries to match
return NO_SCORE;
}
totalScore += scoreValue;
totalPositions.push(...positions);
}
if (totalScore === NO_MATCH) {
return NO_SCORE;
}
// if we have a score, ensure that the positions are
// sorted in ascending order and distinct
return [totalScore, distinctES6(totalPositions).sort((a, b) => a - b)];
}
function scoreSingle(target: string, query: string, queryLower: string, fuzzy: boolean): Score {
export function scoreFuzzy(target: string, query: string, queryLower: string, fuzzy: boolean): FuzzyScore {
if (!target || !query) {
return NO_SCORE; // return early if target or query are undefined
}
@@ -84,7 +52,7 @@ function scoreSingle(target: string, query: string, queryLower: string, fuzzy: b
}
}
const res = doScore(query, queryLower, queryLength, target, targetLower, targetLength);
const res = doScoreFuzzy(query, queryLower, queryLength, target, targetLower, targetLength);
// if (DEBUG) {
// console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold');
@@ -94,7 +62,7 @@ function scoreSingle(target: string, query: string, queryLower: string, fuzzy: b
return res;
}
function doScore(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): Score {
function doScoreFuzzy(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): FuzzyScore {
const scores: number[] = [];
const matches: number[] = [];
@@ -291,6 +259,61 @@ function scoreSeparatorAtPos(charCode: number): number {
// }
// }
//#endregion
//#region Alternate fuzzy scorer implementation that is e.g. used for symbols
export type FuzzyScore2 = [number /* score*/, IMatch[]];
const NO_SCORE2: FuzzyScore2 = [NO_MATCH, []];
export function scoreFuzzy2(target: string, query: IPreparedQuery, patternStart = 0, matchOffset = 0): FuzzyScore2 {
// Score: multiple inputs
if (query.values && query.values.length > 1) {
return doScoreFuzzy2Multiple(target, query.values, patternStart, matchOffset);
}
// Score: single input
return doScoreFuzzy2Single(target, query, patternStart, matchOffset);
}
function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, matchOffset: number): FuzzyScore2 {
let totalScore = 0;
const totalMatches: IMatch[] = [];
for (const queryPiece of query) {
const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, matchOffset);
if (!score) {
// if a single query value does not match, return with
// no score entirely, we require all queries to match
return NO_SCORE2;
}
totalScore += score;
totalMatches.push(...matches);
}
// if we have a score, ensure that the positions are
// sorted in ascending order and distinct
return [totalScore, normalizeMatches(totalMatches)];
}
function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, matchOffset: number): FuzzyScore2 {
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), 0, true);
if (!score) {
return NO_SCORE2;
}
return [score[0], createFuzzyMatches(score, matchOffset)];
}
//#endregion
//#region Item (label, description, path) scorer
/**
* Scoring on structural items that have a label and optional description.
*/
@@ -337,99 +360,7 @@ const LABEL_PREFIX_SCORE = 1 << 17;
const LABEL_CAMELCASE_SCORE = 1 << 16;
const LABEL_SCORE_THRESHOLD = 1 << 15;
export interface IPreparedQueryPiece {
/**
* The original query as provided as input.
*/
original: string;
originalLowercase: string;
/**
* Original normalized to platform separators:
* - Windows: \
* - Posix: /
*/
pathNormalized: string;
/**
* In addition to the normalized path, will have
* whitespace and wildcards removed.
*/
normalized: string;
normalizedLowercase: string;
}
export interface IPreparedQuery extends IPreparedQueryPiece {
// Split by spaces
values: IPreparedQueryPiece[] | undefined;
containsPathSeparator: boolean;
}
/**
* Helper function to prepare a search value for scoring by removing unwanted characters
* and allowing to score on multiple pieces separated by whitespace character.
*/
const MULTIPL_QUERY_VALUES_SEPARATOR = ' ';
export function prepareQuery(original: string): IPreparedQuery {
if (typeof original !== 'string') {
original = '';
}
const originalLowercase = original.toLowerCase();
const { pathNormalized, normalized, normalizedLowercase } = normalizeQuery(original);
const containsPathSeparator = pathNormalized.indexOf(sep) >= 0;
let values: IPreparedQueryPiece[] | undefined = undefined;
const originalSplit = original.split(MULTIPL_QUERY_VALUES_SEPARATOR);
if (originalSplit.length > 1) {
for (const originalPiece of originalSplit) {
const {
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
} = normalizeQuery(originalPiece);
if (normalizedPiece) {
if (!values) {
values = [];
}
values.push({
original: originalPiece,
originalLowercase: originalPiece.toLowerCase(),
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
});
}
}
}
return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator };
}
function normalizeQuery(original: string): { pathNormalized: string, normalized: string, normalizedLowercase: string } {
let pathNormalized: string;
if (isWindows) {
pathNormalized = original.replace(/\//g, sep); // Help Windows users to search for paths when using slash
} else {
pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash
}
const normalized = stripWildcards(pathNormalized).replace(/\s/g, '');
return {
pathNormalized,
normalized,
normalizedLowercase: normalized.toLowerCase()
};
}
export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): IItemScore {
export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): IItemScore {
if (!item || !query.normalized) {
return NO_ITEM_SCORE; // we need an item and query to score on at least
}
@@ -453,62 +384,86 @@ export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, acc
return cached;
}
const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, fuzzy);
const itemScore = doScoreItemFuzzy(label, description, accessor.getItemPath(item), query, fuzzy);
cache[cacheHash] = itemScore;
return itemScore;
}
function createMatches(offsets: undefined | number[]): IMatch[] {
let ret: IMatch[] = [];
if (!offsets) {
return ret;
}
function doScoreItemFuzzy(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore {
const preferLabelMatches = !path || !query.containsPathSeparator;
let last: IMatch | undefined;
for (const pos of offsets) {
if (last && last.end === pos) {
last.end += 1;
} else {
last = { start: pos, end: pos + 1 };
ret.push(last);
}
}
return ret;
}
function doScoreItem(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore {
// 1.) treat identity matches on full path highest
// Treat identity matches on full path highest
if (path && (isLinux ? query.pathNormalized === path : equalsIgnoreCase(query.pathNormalized, path))) {
return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : undefined };
}
// We only consider label matches if the query is not including file path separators
const preferLabelMatches = !path || !query.containsPathSeparator;
// Score: multiple inputs
if (query.values && query.values.length > 1) {
return doScoreItemFuzzyMultiple(label, description, path, query.values, preferLabelMatches, fuzzy);
}
// Score: single input
return doScoreItemFuzzySingle(label, description, path, query, preferLabelMatches, fuzzy);
}
function doScoreItemFuzzyMultiple(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece[], preferLabelMatches: boolean, fuzzy: boolean): IItemScore {
let totalScore = 0;
const totalLabelMatches: IMatch[] = [];
const totalDescriptionMatches: IMatch[] = [];
for (const queryPiece of query) {
const { score, labelMatch, descriptionMatch } = doScoreItemFuzzySingle(label, description, path, queryPiece, preferLabelMatches, fuzzy);
if (score === NO_MATCH) {
// if a single query value does not match, return with
// no score entirely, we require all queries to match
return NO_ITEM_SCORE;
}
totalScore += score;
if (labelMatch) {
totalLabelMatches.push(...labelMatch);
}
if (descriptionMatch) {
totalDescriptionMatches.push(...descriptionMatch);
}
}
// if we have a score, ensure that the positions are
// sorted in ascending order and distinct
return {
score: totalScore,
labelMatch: normalizeMatches(totalLabelMatches),
descriptionMatch: normalizeMatches(totalDescriptionMatches)
};
}
function doScoreItemFuzzySingle(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece, preferLabelMatches: boolean, fuzzy: boolean): IItemScore {
// Prefer label matches if told so
if (preferLabelMatches) {
// 2.) treat prefix matches on the label second highest
// Treat prefix matches on the label second highest
const prefixLabelMatch = matchesPrefix(query.normalized, label);
if (prefixLabelMatch) {
return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch };
}
// 3.) treat camelcase matches on the label third highest
// Treat camelcase matches on the label third highest
const camelcaseLabelMatch = matchesCamelCase(query.normalized, label);
if (camelcaseLabelMatch) {
return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch };
}
// 4.) prefer scores on the label if any
const [labelScore, labelPositions] = score(label, query, fuzzy);
// Prefer scores on the label if any
const [labelScore, labelPositions] = scoreFuzzy(label, query.normalized, query.normalizedLowercase, fuzzy);
if (labelScore) {
return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) };
}
}
// 5.) finally compute description + label scores if we have a description
// Finally compute description + label scores if we have a description
if (description) {
let descriptionPrefix = description;
if (!!path) {
@@ -518,7 +473,7 @@ function doScoreItem(label: string, description: string | undefined, path: strin
const descriptionPrefixLength = descriptionPrefix.length;
const descriptionAndLabel = `${descriptionPrefix}${label}`;
const [labelDescriptionScore, labelDescriptionPositions] = score(descriptionAndLabel, query, fuzzy);
const [labelDescriptionScore, labelDescriptionPositions] = scoreFuzzy(descriptionAndLabel, query.normalized, query.normalizedLowercase, fuzzy);
if (labelDescriptionScore) {
const labelDescriptionMatches = createMatches(labelDescriptionPositions);
const labelMatch: IMatch[] = [];
@@ -551,9 +506,45 @@ function doScoreItem(label: string, description: string | undefined, path: strin
return NO_ITEM_SCORE;
}
export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): number {
const itemScoreA = scoreItem(itemA, query, fuzzy, accessor, cache);
const itemScoreB = scoreItem(itemB, query, fuzzy, accessor, cache);
function createMatches(offsets: number[] | undefined): IMatch[] {
const ret: IMatch[] = [];
if (!offsets) {
return ret;
}
let last: IMatch | undefined;
for (const pos of offsets) {
if (last && last.end === pos) {
last.end += 1;
} else {
last = { start: pos, end: pos + 1 };
ret.push(last);
}
}
return ret;
}
function normalizeMatches(matches: IMatch[]): IMatch[] {
const positions = new Set<number>();
for (const match of matches) {
for (let i = match.start; i < match.end; i++) {
positions.add(i);
}
}
return createMatches(Array.from(positions.values()).sort((a, b) => a - b));
}
//#endregion
//#region Comparers
export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): number {
const itemScoreA = scoreItemFuzzy(itemA, query, fuzzy, accessor, cache);
const itemScoreB = scoreItemFuzzy(itemB, query, fuzzy, accessor, cache);
const scoreA = itemScoreA.score;
const scoreB = itemScoreB.score;
@@ -744,3 +735,112 @@ function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor:
// equal
return 0;
}
//#endregion
//#region Query Normalizer
export interface IPreparedQueryPiece {
/**
* The original query as provided as input.
*/
original: string;
originalLowercase: string;
/**
* Original normalized to platform separators:
* - Windows: \
* - Posix: /
*/
pathNormalized: string;
/**
* In addition to the normalized path, will have
* whitespace and wildcards removed.
*/
normalized: string;
normalizedLowercase: string;
}
export interface IPreparedQuery extends IPreparedQueryPiece {
// Split by spaces
values: IPreparedQueryPiece[] | undefined;
containsPathSeparator: boolean;
}
/**
* Helper function to prepare a search value for scoring by removing unwanted characters
* and allowing to score on multiple pieces separated by whitespace character.
*/
const MULTIPLE_QUERY_VALUES_SEPARATOR = ' ';
export function prepareQuery(original: string): IPreparedQuery {
if (typeof original !== 'string') {
original = '';
}
const originalLowercase = original.toLowerCase();
const { pathNormalized, normalized, normalizedLowercase } = normalizeQuery(original);
const containsPathSeparator = pathNormalized.indexOf(sep) >= 0;
let values: IPreparedQueryPiece[] | undefined = undefined;
const originalSplit = original.split(MULTIPLE_QUERY_VALUES_SEPARATOR);
if (originalSplit.length > 1) {
for (const originalPiece of originalSplit) {
const {
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
} = normalizeQuery(originalPiece);
if (normalizedPiece) {
if (!values) {
values = [];
}
values.push({
original: originalPiece,
originalLowercase: originalPiece.toLowerCase(),
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
});
}
}
}
return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator };
}
function normalizeQuery(original: string): { pathNormalized: string, normalized: string, normalizedLowercase: string } {
let pathNormalized: string;
if (isWindows) {
pathNormalized = original.replace(/\//g, sep); // Help Windows users to search for paths when using slash
} else {
pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash
}
const normalized = stripWildcards(pathNormalized).replace(/\s/g, '');
return {
pathNormalized,
normalized,
normalizedLowercase: normalized.toLowerCase()
};
}
export function pieceToQuery(piece: IPreparedQueryPiece): IPreparedQuery;
export function pieceToQuery(pieces: IPreparedQueryPiece[]): IPreparedQuery;
export function pieceToQuery(arg1: IPreparedQueryPiece | IPreparedQueryPiece[]): IPreparedQuery {
if (Array.isArray(arg1)) {
return prepareQuery(arg1.map(piece => piece.original).join(MULTIPLE_QUERY_VALUES_SEPARATOR));
}
return prepareQuery(arg1.original);
}
//#endregion

View File

@@ -113,6 +113,9 @@ export function mixin(destination: any, source: any, overwrite: boolean = true):
return destination;
}
/**
* @deprecated ES6
*/
export function assign<T>(destination: T): T;
export function assign<T, U>(destination: T, u: U): T & U;
export function assign<T, U, V>(destination: T, u: U, v: V): T & U & V;

View File

@@ -33,6 +33,9 @@ export interface ScrollEvent {
export class ScrollState implements IScrollDimensions, IScrollPosition {
_scrollStateBrand: void;
public readonly rawScrollLeft: number;
public readonly rawScrollTop: number;
public readonly width: number;
public readonly scrollWidth: number;
public readonly scrollLeft: number;
@@ -55,6 +58,9 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
scrollHeight = scrollHeight | 0;
scrollTop = scrollTop | 0;
this.rawScrollLeft = scrollLeft; // before validation
this.rawScrollTop = scrollTop; // before validation
if (width < 0) {
width = 0;
}
@@ -85,7 +91,9 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
public equals(other: ScrollState): boolean {
return (
this.width === other.width
this.rawScrollLeft === other.rawScrollLeft
&& this.rawScrollTop === other.rawScrollTop
&& this.width === other.width
&& this.scrollWidth === other.scrollWidth
&& this.scrollLeft === other.scrollLeft
&& this.height === other.height
@@ -98,10 +106,10 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
return new ScrollState(
(typeof update.width !== 'undefined' ? update.width : this.width),
(typeof update.scrollWidth !== 'undefined' ? update.scrollWidth : this.scrollWidth),
this.scrollLeft,
this.rawScrollLeft,
(typeof update.height !== 'undefined' ? update.height : this.height),
(typeof update.scrollHeight !== 'undefined' ? update.scrollHeight : this.scrollHeight),
this.scrollTop
this.rawScrollTop
);
}
@@ -109,10 +117,10 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
return new ScrollState(
this.width,
this.scrollWidth,
(typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.scrollLeft),
(typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.rawScrollLeft),
this.height,
this.scrollHeight,
(typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.scrollTop)
(typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.rawScrollTop)
);
}

View File

@@ -5,45 +5,25 @@
import { URI, UriComponents } from 'vs/base/common/uri';
const _typeof = {
number: 'number',
string: 'string',
undefined: 'undefined',
object: 'object',
function: 'function'
};
/**
* @returns whether the provided parameter is a JavaScript Array or not.
*/
export function isArray(array: any): array is any[] {
if (Array.isArray) {
return Array.isArray(array);
}
if (array && typeof (array.length) === _typeof.number && array.constructor === Array) {
return true;
}
return false;
return Array.isArray(array);
}
/**
* @returns whether the provided parameter is a JavaScript String or not.
*/
export function isString(str: any): str is string {
if (typeof (str) === _typeof.string || str instanceof String) {
return true;
}
return false;
return (typeof str === 'string');
}
/**
* @returns whether the provided parameter is a JavaScript Array and each element in the array is a string.
*/
export function isStringArray(value: any): value is string[] {
return isArray(value) && (<any[]>value).every(elem => isString(elem));
return Array.isArray(value) && (<any[]>value).every(elem => isString(elem));
}
/**
@@ -55,7 +35,7 @@ export function isObject(obj: any): obj is Object {
// The method can't do a type cast since there are type (like strings) which
// are subclasses of any put not positvely matched by the function. Hence type
// narrowing results in wrong results.
return typeof obj === _typeof.object
return typeof obj === 'object'
&& obj !== null
&& !Array.isArray(obj)
&& !(obj instanceof RegExp)
@@ -67,32 +47,28 @@ export function isObject(obj: any): obj is Object {
* @returns whether the provided parameter is a JavaScript Number or not.
*/
export function isNumber(obj: any): obj is number {
if ((typeof (obj) === _typeof.number || obj instanceof Number) && !isNaN(obj)) {
return true;
}
return false;
return (typeof obj === 'number' && !isNaN(obj));
}
/**
* @returns whether the provided parameter is a JavaScript Boolean or not.
*/
export function isBoolean(obj: any): obj is boolean {
return obj === true || obj === false;
return (obj === true || obj === false);
}
/**
* @returns whether the provided parameter is undefined.
*/
export function isUndefined(obj: any): obj is undefined {
return typeof (obj) === _typeof.undefined;
return (typeof obj === 'undefined');
}
/**
* @returns whether the provided parameter is undefined or null.
*/
export function isUndefinedOrNull(obj: any): obj is undefined | null {
return isUndefined(obj) || obj === null;
return (isUndefined(obj) || obj === null);
}
@@ -158,7 +134,7 @@ export function isEmptyObject(obj: any): obj is any {
* @returns whether the provided parameter is a JavaScript Function or not.
*/
export function isFunction(obj: any): obj is Function {
return typeof obj === _typeof.function;
return (typeof obj === 'function');
}
/**

View File

@@ -237,11 +237,9 @@
.quick-input-list .quick-input-list-entry-action-bar .action-label {
/*
* By default, actions in the quick input action bar are hidden
* until hovered over them or selected. We do not use display:none
* so that the amount of visual flickering is little by reserving the
* space the button needs still.
* until hovered over them or selected.
*/
visibility: hidden;
display: none;
}
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
@@ -266,5 +264,5 @@
.quick-input-list .quick-input-list-entry .quick-input-list-entry-action-bar .action-label.always-visible,
.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar .action-label,
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar .action-label {
visibility: visible;
display: flex;
}

View File

@@ -386,7 +386,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _matchOnLabel = true;
private _sortByLabel = true;
private _autoFocusOnList = true;
private _itemActivation = ItemActivation.FIRST;
private _itemActivation = this.ui.isScreenReaderOptimized() ? ItemActivation.NONE /* https://github.com/microsoft/vscode/issues/57501 */ : ItemActivation.FIRST;
private _activeItems: T[] = [];
private activeItemsUpdated = false;
private activeItemsToConfirm: T[] | null = [];
@@ -637,7 +637,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private trySelectFirst() {
if (this.autoFocusOnList) {
if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) {
if (!this.canSelectMany) {
this.ui.list.focus(QuickInputListFocus.First);
}
}
@@ -683,22 +683,14 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
event.preventDefault();
break;
case KeyCode.PageDown:
if (this.ui.list.getFocusedElements().length) {
this.ui.list.focus(QuickInputListFocus.NextPage);
} else {
this.ui.list.focus(QuickInputListFocus.First);
}
this.ui.list.focus(QuickInputListFocus.NextPage);
if (this.canSelectMany) {
this.ui.list.domFocus();
}
event.preventDefault();
break;
case KeyCode.PageUp:
if (this.ui.list.getFocusedElements().length) {
this.ui.list.focus(QuickInputListFocus.PreviousPage);
} else {
this.ui.list.focus(QuickInputListFocus.Last);
}
this.ui.list.focus(QuickInputListFocus.PreviousPage);
if (this.canSelectMany) {
this.ui.list.domFocus();
}
@@ -875,6 +867,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
this.ui.count.setCount(this.ui.list.getCheckedCount());
switch (this._itemActivation) {
case ItemActivation.NONE:
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
break;
case ItemActivation.SECOND:
this.ui.list.focus(QuickInputListFocus.Second);
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
@@ -1086,30 +1081,13 @@ export class QuickInputController extends Disposable {
}
private registerKeyModsListeners() {
this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
switch (event.keyCode) {
case KeyCode.Ctrl:
case KeyCode.Meta:
this.keyMods.ctrlCmd = true;
break;
case KeyCode.Alt:
this.keyMods.alt = true;
break;
}
}));
this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
switch (event.keyCode) {
case KeyCode.Ctrl:
case KeyCode.Meta:
this.keyMods.ctrlCmd = false;
break;
case KeyCode.Alt:
this.keyMods.alt = false;
break;
}
}));
const listener = (e: KeyboardEvent | MouseEvent) => {
this.keyMods.ctrlCmd = e.ctrlKey || e.metaKey;
this.keyMods.alt = e.altKey;
};
this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, listener, true));
this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, listener, true));
this._register(dom.addDisposableListener(window, dom.EventType.MOUSE_DOWN, listener, true));
}
private getUI() {
@@ -1362,6 +1340,9 @@ export class QuickInputController extends Disposable {
];
input.canSelectMany = !!options.canPickMany;
input.placeholder = options.placeHolder;
if (options.placeHolder) {
input.ariaLabel = options.placeHolder;
}
input.ignoreFocusOut = !!options.ignoreFocusLost;
input.matchOnDescription = !!options.matchOnDescription;
input.matchOnDetail = !!options.matchOnDetail;

View File

@@ -302,14 +302,12 @@ export class QuickInputList {
}
break;
case KeyCode.UpArrow:
case KeyCode.PageUp:
const focus1 = this.list.getFocus();
if (focus1.length === 1 && focus1[0] === 0) {
this._onLeave.fire();
}
break;
case KeyCode.DownArrow:
case KeyCode.PageDown:
const focus2 = this.list.getFocus();
if (focus2.length === 1 && focus2[0] === this.list.length - 1) {
this._onLeave.fire();
@@ -518,11 +516,11 @@ export class QuickInputList {
return;
}
if ((what === QuickInputListFocus.Next || what === QuickInputListFocus.NextPage) && this.list.getFocus()[0] === this.list.length - 1) {
if (what === QuickInputListFocus.Next && this.list.getFocus()[0] === this.list.length - 1) {
what = QuickInputListFocus.First;
}
if ((what === QuickInputListFocus.Previous || what === QuickInputListFocus.PreviousPage) && this.list.getFocus()[0] === 0) {
if (what === QuickInputListFocus.Previous && this.list.getFocus()[0] === 0) {
what = QuickInputListFocus.Last;
}

View File

@@ -183,7 +183,8 @@ export interface IQuickPickAcceptEvent {
}
export enum ItemActivation {
FIRST = 1,
NONE,
FIRST,
SECOND,
LAST
}
@@ -326,7 +327,7 @@ export type QuickPickInput<T = IQuickPickItem> = T | IQuickPickSeparator;
//region Fuzzy Scorer Support
export type IQuickPickItemWithResource = IQuickPickItem & { resource: URI | undefined };
export type IQuickPickItemWithResource = IQuickPickItem & { resource?: URI };
export class QuickPickItemScorerAccessor implements IItemAccessor<IQuickPickItemWithResource> {

View File

@@ -42,20 +42,28 @@ class NullAccessorClass implements scorer.IItemAccessor<URI> {
}
}
function _doScore(target: string, query: string, fuzzy: boolean): scorer.Score {
return scorer.score(target, scorer.prepareQuery(query), fuzzy);
function _doScore(target: string, query: string, fuzzy: boolean): scorer.FuzzyScore {
const preparedQuery = scorer.prepareQuery(query);
return scorer.scoreFuzzy(target, preparedQuery.normalized, preparedQuery.normalizedLowercase, 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 _doScore2(target: string, query: string): scorer.FuzzyScore2 {
const preparedQuery = scorer.prepareQuery(query);
return scorer.scoreFuzzy2(target, preparedQuery);
}
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache): number {
return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache);
function scoreItem<T>(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.FuzzyScorerCache): scorer.IItemScore {
return scorer.scoreItemFuzzy(item, scorer.prepareQuery(query), fuzzy, accessor, cache);
}
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.FuzzyScorerCache): number {
return scorer.compareItemsByFuzzyScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache);
}
const NullAccessor = new NullAccessorClass();
let cache: scorer.ScorerCache = Object.create(null);
let cache: scorer.FuzzyScorerCache = Object.create(null);
suite('Fuzzy Scorer', () => {
@@ -66,7 +74,7 @@ suite('Fuzzy Scorer', () => {
test('score (fuzzy)', function () {
const target = 'HeLlo-World';
const scores: scorer.Score[] = [];
const scores: scorer.FuzzyScore[] = [];
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)
@@ -109,42 +117,6 @@ suite('Fuzzy Scorer', () => {
assert.equal(_doScore(target, 'eo', false)[0], 0);
});
test('score (fuzzy, multiple)', function () {
const target = 'HeLlo-World';
const [firstSingleScore, firstSinglePositions] = _doScore(target, 'HelLo', true);
const [secondSingleScore, secondSinglePositions] = _doScore(target, 'World', true);
const firstAndSecondSinglePositions = [...firstSinglePositions, ...secondSinglePositions];
let [multiScore, multiPositions] = _doScore(target, 'HelLo World', true);
function assertScore() {
assert.ok(multiScore >= firstSingleScore + secondSingleScore);
for (let i = 0; i < multiPositions.length; i++) {
assert.equal(multiPositions[i], firstAndSecondSinglePositions[i]);
}
}
function assertNoScore() {
assert.equal(multiScore, 0);
assert.equal(multiPositions.length, 0);
}
assertScore();
[multiScore, multiPositions] = _doScore(target, 'World HelLo', true);
assertScore();
[multiScore, multiPositions] = _doScore(target, 'World HelLo World', true);
assertScore();
[multiScore, multiPositions] = _doScore(target, 'World HelLo Nothing', true);
assertNoScore();
[multiScore, multiPositions] = _doScore(target, 'More Nothing', true);
assertNoScore();
});
test('scoreItem - matches are proper', function () {
let res = scoreItem(null, 'something', true, ResourceAccessor, cache);
assert.ok(!res.score);
@@ -217,6 +189,49 @@ suite('Fuzzy Scorer', () => {
assert.ok(pathRes.score > noRes.score);
});
test('scoreItem - multiple', function () {
const resource = URI.file('/xyz/some/path/someFile123.txt');
let res1 = scoreItem(resource, 'xyz some', true, ResourceAccessor, cache);
assert.ok(res1.score);
assert.equal(res1.labelMatch?.length, 1);
assert.equal(res1.labelMatch![0].start, 0);
assert.equal(res1.labelMatch![0].end, 4);
assert.equal(res1.descriptionMatch?.length, 1);
assert.equal(res1.descriptionMatch![0].start, 1);
assert.equal(res1.descriptionMatch![0].end, 4);
let res2 = scoreItem(resource, 'some xyz', true, ResourceAccessor, cache);
assert.ok(res2.score);
assert.equal(res1.score, res2.score);
assert.equal(res2.labelMatch?.length, 1);
assert.equal(res2.labelMatch![0].start, 0);
assert.equal(res2.labelMatch![0].end, 4);
assert.equal(res2.descriptionMatch?.length, 1);
assert.equal(res2.descriptionMatch![0].start, 1);
assert.equal(res2.descriptionMatch![0].end, 4);
let res3 = scoreItem(resource, 'some xyz file file123', true, ResourceAccessor, cache);
assert.ok(res3.score);
assert.ok(res3.score > res2.score);
assert.equal(res3.labelMatch?.length, 1);
assert.equal(res3.labelMatch![0].start, 0);
assert.equal(res3.labelMatch![0].end, 11);
assert.equal(res3.descriptionMatch?.length, 1);
assert.equal(res3.descriptionMatch![0].start, 1);
assert.equal(res3.descriptionMatch![0].end, 4);
let res4 = scoreItem(resource, 'path z y', true, ResourceAccessor, cache);
assert.ok(res4.score);
assert.ok(res4.score < res2.score);
assert.equal(res4.labelMatch?.length, 0);
assert.equal(res4.descriptionMatch?.length, 2);
assert.equal(res4.descriptionMatch![0].start, 2);
assert.equal(res4.descriptionMatch![0].end, 4);
assert.equal(res4.descriptionMatch![1].start, 10);
assert.equal(res4.descriptionMatch![1].end, 14);
});
test('scoreItem - invalid input', function () {
let res = scoreItem(null, null!, true, ResourceAccessor, cache);
@@ -878,6 +893,11 @@ suite('Fuzzy Scorer', () => {
assert.equal(query.values?.[1].normalized, 'World');
assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase());
let restoredQuery = scorer.pieceToQuery(query.values!);
assert.equal(restoredQuery.original, query.original);
assert.equal(restoredQuery.values?.length, query.values?.length);
assert.equal(restoredQuery.containsPathSeparator, query.containsPathSeparator);
// with spaces that are empty
query = scorer.prepareQuery(' Hello World ');
assert.equal(query.original, ' Hello World ');
@@ -911,4 +931,48 @@ suite('Fuzzy Scorer', () => {
assert.equal(scorer.prepareQuery('\\some\\path').containsPathSeparator, true);
}
});
test('fuzzyScore2 (multiple queries)', function () {
const target = 'HeLlo-World';
const [firstSingleScore, firstSingleMatches] = _doScore2(target, 'HelLo');
const [secondSingleScore, secondSingleMatches] = _doScore2(target, 'World');
const firstAndSecondSingleMatches = [...firstSingleMatches || [], ...secondSingleMatches || []];
let [multiScore, multiMatches] = _doScore2(target, 'HelLo World');
function assertScore() {
assert.ok(multiScore ?? 0 >= ((firstSingleScore ?? 0) + (secondSingleScore ?? 0)));
for (let i = 0; multiMatches && i < multiMatches.length; i++) {
const multiMatch = multiMatches[i];
const firstAndSecondSingleMatch = firstAndSecondSingleMatches[i];
if (multiMatch && firstAndSecondSingleMatch) {
assert.equal(multiMatch.start, firstAndSecondSingleMatch.start);
assert.equal(multiMatch.end, firstAndSecondSingleMatch.end);
} else {
assert.fail();
}
}
}
function assertNoScore() {
assert.equal(multiScore, 0);
assert.equal(multiMatches.length, 0);
}
assertScore();
[multiScore, multiMatches] = _doScore2(target, 'World HelLo');
assertScore();
[multiScore, multiMatches] = _doScore2(target, 'World HelLo World');
assertScore();
[multiScore, multiMatches] = _doScore2(target, 'World HelLo Nothing');
assertNoScore();
[multiScore, multiMatches] = _doScore2(target, 'More Nothing');
assertNoScore();
});
});