Merge from vscode 6fded8a497cd0142de3a1c607649a5423a091a25
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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,
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||