mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 073a24de05773f2261f89172987002dc0ae2f1cd (#9711)
This commit is contained in:
@@ -65,7 +65,7 @@ export interface IIdentityProvider<T> {
|
||||
|
||||
export enum ListAriaRootRole {
|
||||
/** default list structure role */
|
||||
LIST = 'listbox',
|
||||
LIST = 'list',
|
||||
|
||||
/** default tree structure role */
|
||||
TREE = 'tree',
|
||||
|
||||
@@ -182,17 +182,19 @@ class Trait<T> implements ISpliceable<boolean>, IDisposable {
|
||||
|
||||
class SelectionTrait<T> extends Trait<T> {
|
||||
|
||||
constructor() {
|
||||
constructor(private setAriaSelected: boolean) {
|
||||
super('selected');
|
||||
}
|
||||
|
||||
renderIndex(index: number, container: HTMLElement): void {
|
||||
super.renderIndex(index, container);
|
||||
|
||||
if (this.contains(index)) {
|
||||
container.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
container.setAttribute('aria-selected', 'false');
|
||||
if (this.setAriaSelected) {
|
||||
if (this.contains(index)) {
|
||||
container.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
container.setAttribute('aria-selected', 'false');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1198,7 +1200,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
renderers: IListRenderer<any /* TODO@joao */, any>[],
|
||||
private _options: IListOptions<T> = DefaultOptions
|
||||
) {
|
||||
this.selection = new SelectionTrait();
|
||||
this.selection = new SelectionTrait(this._options.ariaRole !== 'listbox');
|
||||
this.focus = new Trait('focused');
|
||||
|
||||
mixin(_options, defaultStyles, false);
|
||||
@@ -1501,9 +1503,13 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
focusFirst(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
this.focusNth(0, browserEvent, filter);
|
||||
}
|
||||
|
||||
focusNth(n: number, browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
if (this.length === 0) { return; }
|
||||
|
||||
const index = this.findNextIndex(0, false, filter);
|
||||
const index = this.findNextIndex(n, false, filter);
|
||||
|
||||
if (index > -1) {
|
||||
this.setFocus([index], browserEvent);
|
||||
|
||||
@@ -357,7 +357,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.selectBackground} !important; }`);
|
||||
}
|
||||
|
||||
// Match quickOpen outline styles - ignore for disabled options
|
||||
// Match quick input outline styles - ignore for disabled options
|
||||
if (this.styles.listFocusOutline) {
|
||||
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { outline: 1.6px dotted ${this.styles.listFocusOutline} !important; outline-offset: -1.6px !important; }`);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper } from 'vs/base/browser/dom';
|
||||
import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper, clearNode } from 'vs/base/browser/dom';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { Color, RGBA } from 'vs/base/common/color';
|
||||
import { SplitView, IView } from './splitview';
|
||||
@@ -52,7 +52,6 @@ export abstract class Pane extends Disposable implements IView {
|
||||
|
||||
protected _expanded: boolean;
|
||||
protected _orientation: Orientation;
|
||||
protected _preventCollapse?: boolean;
|
||||
|
||||
private expandedSize: number | undefined = undefined;
|
||||
private _headerVisible = true;
|
||||
@@ -106,7 +105,7 @@ export abstract class Pane extends Disposable implements IView {
|
||||
get minimumSize(): number {
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
const minimumBodySize = expanded ? this._minimumBodySize : 0;
|
||||
const minimumBodySize = expanded ? this._minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
|
||||
|
||||
return headerSize + minimumBodySize;
|
||||
}
|
||||
@@ -114,7 +113,7 @@ export abstract class Pane extends Disposable implements IView {
|
||||
get maximumSize(): number {
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
const maximumBodySize = expanded ? this._maximumBodySize : 0;
|
||||
const maximumBodySize = expanded ? this._maximumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
|
||||
|
||||
return headerSize + maximumBodySize;
|
||||
}
|
||||
@@ -174,6 +173,18 @@ export abstract class Pane extends Disposable implements IView {
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
get orientation(): Orientation {
|
||||
return this._orientation;
|
||||
}
|
||||
|
||||
set orientation(orientation: Orientation) {
|
||||
if (this._orientation === orientation) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._orientation = orientation;
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.header = $('.pane-header');
|
||||
append(this.element, this.header);
|
||||
@@ -190,22 +201,20 @@ export abstract class Pane extends Disposable implements IView {
|
||||
this.updateHeader();
|
||||
|
||||
|
||||
if (!this._preventCollapse) {
|
||||
const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown'))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown'))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
|
||||
.event(() => this.setExpanded(!this.isExpanded()), null));
|
||||
this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
|
||||
.event(() => this.setExpanded(!this.isExpanded()), null));
|
||||
|
||||
this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow)
|
||||
.event(() => this.setExpanded(false), null));
|
||||
this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow)
|
||||
.event(() => this.setExpanded(false), null));
|
||||
|
||||
this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow)
|
||||
.event(() => this.setExpanded(true), null));
|
||||
this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow)
|
||||
.event(() => this.setExpanded(true), null));
|
||||
|
||||
this._register(domEvent(this.header, 'click')
|
||||
(() => this.setExpanded(!this.isExpanded()), null));
|
||||
}
|
||||
this._register(domEvent(this.header, 'click')
|
||||
(() => this.setExpanded(!this.isExpanded()), null));
|
||||
|
||||
this.body = append(this.element, $('.pane-body'));
|
||||
this.renderBody(this.body);
|
||||
@@ -402,13 +411,14 @@ export class PaneView extends Disposable {
|
||||
private el: HTMLElement;
|
||||
private paneItems: IPaneItem[] = [];
|
||||
private orthogonalSize: number = 0;
|
||||
private size: number = 0;
|
||||
private splitview: SplitView;
|
||||
private orientation: Orientation;
|
||||
private animationTimer: number | undefined = undefined;
|
||||
|
||||
private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
|
||||
readonly onDidDrop: Event<{ from: Pane, to: Pane }> = this._onDidDrop.event;
|
||||
|
||||
orientation: Orientation;
|
||||
readonly onDidSashChange: Event<number>;
|
||||
|
||||
constructor(container: HTMLElement, options: IPaneViewOptions = {}) {
|
||||
@@ -427,6 +437,7 @@ export class PaneView extends Disposable {
|
||||
|
||||
const paneItem = { pane: pane, disposable: disposables };
|
||||
this.paneItems.splice(index, 0, paneItem);
|
||||
pane.orientation = this.orientation;
|
||||
pane.orthogonalSize = this.orthogonalSize;
|
||||
this.splitview.addView(pane, size, index);
|
||||
|
||||
@@ -485,12 +496,39 @@ export class PaneView extends Disposable {
|
||||
|
||||
layout(height: number, width: number): void {
|
||||
this.orthogonalSize = this.orientation === Orientation.VERTICAL ? width : height;
|
||||
this.size = this.orientation === Orientation.HORIZONTAL ? width : height;
|
||||
|
||||
for (const paneItem of this.paneItems) {
|
||||
paneItem.pane.orthogonalSize = this.orthogonalSize;
|
||||
}
|
||||
|
||||
this.splitview.layout(this.orientation === Orientation.HORIZONTAL ? width : height);
|
||||
this.splitview.layout(this.size);
|
||||
}
|
||||
|
||||
flipOrientation(height: number, width: number): void {
|
||||
this.orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||
const paneSizes = this.paneItems.map(pane => this.getPaneSize(pane.pane));
|
||||
|
||||
this.splitview.dispose();
|
||||
clearNode(this.el);
|
||||
|
||||
this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation }));
|
||||
|
||||
const newOrthogonalSize = this.orientation === Orientation.VERTICAL ? width : height;
|
||||
const newSize = this.orientation === Orientation.HORIZONTAL ? width : height;
|
||||
|
||||
this.paneItems.forEach((pane, index) => {
|
||||
pane.pane.orthogonalSize = newOrthogonalSize;
|
||||
pane.pane.orientation = this.orientation;
|
||||
|
||||
const viewSize = this.size === 0 ? 0 : (newSize * paneSizes[index]) / this.size;
|
||||
this.splitview.addView(pane.pane, viewSize, index);
|
||||
});
|
||||
|
||||
this.size = newSize;
|
||||
this.orthogonalSize = newOrthogonalSize;
|
||||
|
||||
this.splitview.layout(this.size);
|
||||
}
|
||||
|
||||
private setupAnimation(): void {
|
||||
|
||||
@@ -115,6 +115,19 @@ export class VSBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
export function readUInt16LE(source: Uint8Array, offset: number): number {
|
||||
return (
|
||||
source[offset]
|
||||
+ source[offset + 1] * 2 ** 8
|
||||
);
|
||||
}
|
||||
|
||||
export function writeUInt16LE(destination: Uint8Array, value: number, offset: number): void {
|
||||
destination[offset] = value;
|
||||
value = value >>> 8;
|
||||
destination[offset + 1] = value;
|
||||
}
|
||||
|
||||
export function readUInt32BE(source: Uint8Array, offset: number): number {
|
||||
return (
|
||||
source[offset] * 2 ** 24
|
||||
@@ -134,11 +147,11 @@ export function writeUInt32BE(destination: Uint8Array, value: number, offset: nu
|
||||
destination[offset] = value;
|
||||
}
|
||||
|
||||
function readUInt8(source: Uint8Array, offset: number): number {
|
||||
export function readUInt8(source: Uint8Array, offset: number): number {
|
||||
return source[offset];
|
||||
}
|
||||
|
||||
function writeUInt8(destination: Uint8Array, value: number, offset: number): void {
|
||||
export function writeUInt8(destination: Uint8Array, value: number, offset: number): void {
|
||||
destination[offset] = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -321,6 +321,8 @@ export function prepareQuery(original: string): IPreparedQuery {
|
||||
let value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace
|
||||
if (isWindows) {
|
||||
value = value.replace(/\//g, sep); // Help Windows users to search for paths when using slash
|
||||
} else {
|
||||
value = value.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash
|
||||
}
|
||||
|
||||
const lowercase = value.toLowerCase();
|
||||
@@ -451,7 +453,7 @@ 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, fallbackComparer = fallbackCompare): number {
|
||||
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);
|
||||
|
||||
@@ -517,7 +519,16 @@ export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery
|
||||
return scoreA > scoreB ? -1 : 1;
|
||||
}
|
||||
|
||||
// 6.) scores are identical, prefer more compact matches (label and description)
|
||||
// 6.) prefer matches in label over non-label matches
|
||||
const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0;
|
||||
const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0;
|
||||
if (itemAHasLabelMatches && !itemBHasLabelMatches) {
|
||||
return -1;
|
||||
} else if (itemBHasLabelMatches && !itemAHasLabelMatches) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 7.) scores are identical, prefer more compact matches (label and description)
|
||||
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
|
||||
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
|
||||
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
|
||||
@@ -526,7 +537,7 @@ export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery
|
||||
|
||||
// 7.) at this point, scores are identical and match compactness as well
|
||||
// for both items so we start to use the fallback compare
|
||||
return fallbackComparer(itemA, itemB, query, accessor);
|
||||
return fallbackCompare(itemA, itemB, query, accessor);
|
||||
}
|
||||
|
||||
function computeLabelAndDescriptionMatchDistance<T>(item: T, score: IItemScore, accessor: IItemAccessor<T>): number {
|
||||
|
||||
@@ -53,6 +53,12 @@ export namespace Schemas {
|
||||
export const vscodeRemoteResource = 'vscode-remote-resource';
|
||||
|
||||
export const userData = 'vscode-userdata';
|
||||
|
||||
export const vscodeCustomEditor = 'vscode-custom-editor';
|
||||
|
||||
export const vscodeSettings = 'vscode-settings';
|
||||
|
||||
export const webviewPanel = 'webview-panel';
|
||||
}
|
||||
|
||||
class RemoteAuthoritiesImpl {
|
||||
|
||||
@@ -209,3 +209,17 @@ export const enum OperatingSystem {
|
||||
Linux = 3
|
||||
}
|
||||
export const OS = (_isMacintosh ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
|
||||
|
||||
let _isLittleEndian = true;
|
||||
let _isLittleEndianComputed = false;
|
||||
export function isLittleEndian(): boolean {
|
||||
if (!_isLittleEndianComputed) {
|
||||
_isLittleEndianComputed = true;
|
||||
const test = new Uint8Array(2);
|
||||
test[0] = 1;
|
||||
test[1] = 2;
|
||||
const view = new Uint16Array(test.buffer);
|
||||
_isLittleEndian = (view[0] === (2 << 8) + 1);
|
||||
}
|
||||
return _isLittleEndian;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,16 @@ import { TernarySearchTree } from 'vs/base/common/map';
|
||||
|
||||
export const originalFSPath = uriOriginalFSPath;
|
||||
|
||||
export function getComparisonKey(resource: URI): string {
|
||||
return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString();
|
||||
/**
|
||||
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
|
||||
* URI queries are included, fragments are ignored.
|
||||
*/
|
||||
export function getComparisonKey(resource: URI, caseInsensitivePath = hasToIgnoreCase(resource)): string {
|
||||
let path = resource.path || '/';
|
||||
if (caseInsensitivePath) {
|
||||
path = path.toLowerCase();
|
||||
}
|
||||
return `${resource.scheme}://${resource.authority.toLowerCase()}/${path}?${resource.query}`;
|
||||
}
|
||||
|
||||
export function hasToIgnoreCase(resource: URI | undefined): boolean {
|
||||
@@ -31,29 +39,33 @@ export function basenameOrAuthority(resource: URI): string {
|
||||
|
||||
/**
|
||||
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
|
||||
* URI queries must match, fragments are ignored.
|
||||
* @param base A uri which is "longer"
|
||||
* @param parentCandidate A uri which is "shorter" then `base`
|
||||
*/
|
||||
export function isEqualOrParent(base: URI, parentCandidate: URI, ignoreCase = hasToIgnoreCase(base)): boolean {
|
||||
if (base.scheme === parentCandidate.scheme) {
|
||||
if (base.scheme === Schemas.file) {
|
||||
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase);
|
||||
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase) && base.query === parentCandidate.query;
|
||||
}
|
||||
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
|
||||
return extpath.isEqualOrParent(base.path, parentCandidate.path, ignoreCase, '/');
|
||||
return extpath.isEqualOrParent(base.path || '/', parentCandidate.path || '/', ignoreCase, '/') && base.query === parentCandidate.query;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests wheter the two authorities are the same
|
||||
* Tests whether the two authorities are the same
|
||||
*/
|
||||
export function isEqualAuthority(a1: string, a2: string) {
|
||||
return a1 === a2 || equalsIgnoreCase(a1, a2);
|
||||
}
|
||||
|
||||
export function isEqual(first: URI | undefined, second: URI | undefined, ignoreCase = hasToIgnoreCase(first)): boolean {
|
||||
/**
|
||||
* Tests whether two resources are the same. URI queries must match, fragments are ignored unless requested.
|
||||
*/
|
||||
export function isEqual(first: URI | undefined, second: URI | undefined, caseInsensitivePath = hasToIgnoreCase(first), ignoreFragment = true): boolean {
|
||||
if (first === second) {
|
||||
return true;
|
||||
}
|
||||
@@ -67,7 +79,7 @@ export function isEqual(first: URI | undefined, second: URI | undefined, ignoreC
|
||||
}
|
||||
|
||||
const p1 = first.path || '/', p2 = second.path || '/';
|
||||
return p1 === p2 || ignoreCase && equalsIgnoreCase(p1 || '/', p2 || '/');
|
||||
return (p1 === p2 || caseInsensitivePath && equalsIgnoreCase(p1, p2)) && first.query === second.query && (ignoreFragment || first.fragment === second.fragment);
|
||||
}
|
||||
|
||||
export function basename(resource: URI): string {
|
||||
@@ -88,13 +100,15 @@ export function dirname(resource: URI): URI {
|
||||
if (resource.path.length === 0) {
|
||||
return resource;
|
||||
}
|
||||
let dirname;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
return URI.file(paths.dirname(originalFSPath(resource)));
|
||||
}
|
||||
let dirname = paths.posix.dirname(resource.path);
|
||||
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
|
||||
console.error(`dirname("${resource.toString})) resulted in a relative path`);
|
||||
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
|
||||
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
|
||||
} else {
|
||||
dirname = paths.posix.dirname(resource.path);
|
||||
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
|
||||
console.error(`dirname("${resource.toString})) resulted in a relative path`);
|
||||
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
|
||||
}
|
||||
}
|
||||
return resource.with({
|
||||
path: dirname
|
||||
@@ -189,7 +203,7 @@ export function addTrailingPathSeparator(resource: URI, sep: string = paths.sep)
|
||||
* Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned.
|
||||
* The returned relative path always uses forward slashes.
|
||||
*/
|
||||
export function relativePath(from: URI, to: URI, ignoreCase = hasToIgnoreCase(from)): string | undefined {
|
||||
export function relativePath(from: URI, to: URI, caseInsensitivePath = hasToIgnoreCase(from)): string | undefined {
|
||||
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -198,7 +212,7 @@ export function relativePath(from: URI, to: URI, ignoreCase = hasToIgnoreCase(fr
|
||||
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
|
||||
}
|
||||
let fromPath = from.path || '/', toPath = to.path || '/';
|
||||
if (ignoreCase) {
|
||||
if (caseInsensitivePath) {
|
||||
// make casing of fromPath match toPath
|
||||
let i = 0;
|
||||
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
|
||||
|
||||
@@ -55,8 +55,8 @@
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.quick-input-widget.quick-navigate-mode .quick-input-header {
|
||||
/* reduce margins and paddings in quick navigate mode */
|
||||
.quick-input-widget.hidden-input .quick-input-header {
|
||||
/* reduce margins and paddings when input box hidden */
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -132,8 +132,8 @@
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.quick-input-widget.quick-navigate-mode .quick-input-list {
|
||||
margin-top: 0; /* reduce margins in quick navigate mode */
|
||||
.quick-input-widget.hidden-input .quick-input-list {
|
||||
margin-top: 0; /* reduce margins when input box hidden */
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-list {
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./media/quickInput';
|
||||
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { QuickInputList } from './quickInputList';
|
||||
import { QuickInputList, QuickInputListFocus } from './quickInputList';
|
||||
import { QuickInputBox } from './quickInputBox';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
@@ -364,7 +364,7 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
|
||||
readonly onDispose = this.onDisposeEmitter.event;
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this.hide();
|
||||
this.onDisposeEmitter.fire();
|
||||
|
||||
@@ -391,6 +391,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private _matchOnLabel = true;
|
||||
private _sortByLabel = true;
|
||||
private _autoFocusOnList = true;
|
||||
private _autoFocusSecondEntry = false;
|
||||
private _activeItems: T[] = [];
|
||||
private activeItemsUpdated = false;
|
||||
private activeItemsToConfirm: T[] | null = [];
|
||||
@@ -408,6 +409,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private _customButtonLabel: string | undefined;
|
||||
private _customButtonHover: string | undefined;
|
||||
private _quickNavigate: IQuickNavigateConfiguration | undefined;
|
||||
private _hideInput: boolean | undefined;
|
||||
|
||||
get quickNavigate() {
|
||||
return this._quickNavigate;
|
||||
@@ -460,10 +462,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
set items(items: Array<T | IQuickPickSeparator>) {
|
||||
this._items = items;
|
||||
this.itemsUpdated = true;
|
||||
if (this._items.length === 0) {
|
||||
// quick-navigate requires at least 1 item
|
||||
this._quickNavigate = undefined;
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
|
||||
@@ -520,7 +518,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.update();
|
||||
}
|
||||
|
||||
|
||||
get autoFocusOnList() {
|
||||
return this._autoFocusOnList;
|
||||
}
|
||||
@@ -530,6 +527,14 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.update();
|
||||
}
|
||||
|
||||
get autoFocusSecondEntry() {
|
||||
return this._autoFocusSecondEntry;
|
||||
}
|
||||
|
||||
set autoFocusSecondEntry(autoFocusSecondEntry: boolean) {
|
||||
this._autoFocusSecondEntry = autoFocusSecondEntry;
|
||||
}
|
||||
|
||||
get activeItems() {
|
||||
return this._activeItems;
|
||||
}
|
||||
@@ -614,14 +619,23 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.update();
|
||||
}
|
||||
|
||||
public inputHasFocus(): boolean {
|
||||
inputHasFocus(): boolean {
|
||||
return this.visible ? this.ui.inputBox.hasFocus() : false;
|
||||
}
|
||||
|
||||
public focusOnInput() {
|
||||
focusOnInput() {
|
||||
this.ui.inputBox.setFocus();
|
||||
}
|
||||
|
||||
get hideInput() {
|
||||
return !!this._hideInput;
|
||||
}
|
||||
|
||||
set hideInput(hideInput: boolean) {
|
||||
this._hideInput = hideInput;
|
||||
this.update();
|
||||
}
|
||||
|
||||
onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
|
||||
|
||||
onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
|
||||
@@ -629,7 +643,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private trySelectFirst() {
|
||||
if (this.autoFocusOnList) {
|
||||
if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) {
|
||||
this.ui.list.focus('First');
|
||||
this.ui.list.focus(QuickInputListFocus.First);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -656,7 +670,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.visibleDisposables.add(this.ui.inputBox.onKeyDown(event => {
|
||||
switch (event.keyCode) {
|
||||
case KeyCode.DownArrow:
|
||||
this.ui.list.focus('Next');
|
||||
this.ui.list.focus(QuickInputListFocus.Next);
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
@@ -664,9 +678,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
break;
|
||||
case KeyCode.UpArrow:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('Previous');
|
||||
this.ui.list.focus(QuickInputListFocus.Previous);
|
||||
} else {
|
||||
this.ui.list.focus('Last');
|
||||
this.ui.list.focus(QuickInputListFocus.Last);
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
@@ -675,9 +689,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
break;
|
||||
case KeyCode.PageDown:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('NextPage');
|
||||
this.ui.list.focus(QuickInputListFocus.NextPage);
|
||||
} else {
|
||||
this.ui.list.focus('First');
|
||||
this.ui.list.focus(QuickInputListFocus.First);
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
@@ -686,9 +700,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
break;
|
||||
case KeyCode.PageUp:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('PreviousPage');
|
||||
this.ui.list.focus(QuickInputListFocus.PreviousPage);
|
||||
} else {
|
||||
this.ui.list.focus('Last');
|
||||
this.ui.list.focus(QuickInputListFocus.Last);
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
@@ -721,7 +735,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.onDidAcceptEmitter.fire({ inBackground: false });
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.onDidCustom(() => {
|
||||
this.onDidCustomEmitter.fire(undefined);
|
||||
this.onDidCustomEmitter.fire();
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => {
|
||||
if (this.activeItemsUpdated) {
|
||||
@@ -768,7 +782,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
|
||||
private registerQuickNavigation() {
|
||||
return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => {
|
||||
if (this.canSelectMany || !this.quickNavigate) {
|
||||
if (this.canSelectMany || !this._quickNavigate) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -776,7 +790,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
const keyCode = keyboardEvent.keyCode;
|
||||
|
||||
// Select element when keys are pressed that signal it
|
||||
const quickNavKeys = this.quickNavigate.keybindings;
|
||||
const quickNavKeys = this._quickNavigate.keybindings;
|
||||
const wasTriggerKeyPressed = quickNavKeys.some(k => {
|
||||
const [firstPart, chordPart] = k.getParts();
|
||||
if (chordPart) {
|
||||
@@ -806,10 +820,16 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
return false;
|
||||
});
|
||||
|
||||
if (wasTriggerKeyPressed && this.activeItems[0]) {
|
||||
this._selectedItems = [this.activeItems[0]];
|
||||
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
||||
this.onDidAcceptEmitter.fire({ inBackground: false });
|
||||
if (wasTriggerKeyPressed) {
|
||||
if (this.activeItems[0]) {
|
||||
this._selectedItems = [this.activeItems[0]];
|
||||
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
||||
this.onDidAcceptEmitter.fire({ inBackground: false });
|
||||
}
|
||||
// Unset quick navigate after press. It is only valid once
|
||||
// and should not result in any behaviour change afterwards
|
||||
// if the picker remains open because there was no active item
|
||||
this._quickNavigate = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -818,11 +838,21 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
dom.toggleClass(this.ui.container, 'quick-navigate-mode', !!this._quickNavigate);
|
||||
const ok = this.ok === 'default' ? this.canSelectMany : this.ok;
|
||||
const visibilities: Visibilities = this.canSelectMany ?
|
||||
{ title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: !this._quickNavigate, progressBar: !this._quickNavigate, visibleCount: true, count: true, ok, list: true, message: !!this.validationMessage, customButton: this.customButton } :
|
||||
{ title: !!this.title || !!this.step, description: !!this.description, inputBox: !this._quickNavigate, progressBar: !this._quickNavigate, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok };
|
||||
const hideInput = !!this._hideInput && this._items.length > 0; // do not allow to hide input without items
|
||||
dom.toggleClass(this.ui.container, 'hidden-input', hideInput);
|
||||
const visibilities: Visibilities = {
|
||||
title: !!this.title || !!this.step,
|
||||
description: !!this.description,
|
||||
checkAll: this.canSelectMany,
|
||||
inputBox: !hideInput,
|
||||
progressBar: !hideInput,
|
||||
visibleCount: true,
|
||||
count: this.canSelectMany,
|
||||
ok: this.ok === 'default' ? this.canSelectMany : this.ok,
|
||||
list: true,
|
||||
message: !!this.validationMessage,
|
||||
customButton: this.customButton
|
||||
};
|
||||
this.ui.setVisibilities(visibilities);
|
||||
super.update();
|
||||
if (this.ui.inputBox.value !== this.value) {
|
||||
@@ -844,17 +874,16 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.ui.list.sortByLabel = this.sortByLabel;
|
||||
if (this.itemsUpdated) {
|
||||
this.itemsUpdated = false;
|
||||
const previousItemCount = this.ui.list.getElementsCount();
|
||||
this.ui.list.setElements(this.items);
|
||||
this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
|
||||
this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
|
||||
this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
|
||||
this.ui.count.setCount(this.ui.list.getCheckedCount());
|
||||
this.trySelectFirst();
|
||||
if (this._quickNavigate && previousItemCount === 0 && this.items.length > 1) {
|
||||
// quick navigate: automatically focus the second entry
|
||||
// so that upon release the item is picked directly
|
||||
this.ui.list.focus('Next');
|
||||
if (this._autoFocusSecondEntry) {
|
||||
this.ui.list.focus(QuickInputListFocus.Second);
|
||||
this._autoFocusSecondEntry = false; // only valid once, then unset
|
||||
} else {
|
||||
this.trySelectFirst();
|
||||
}
|
||||
}
|
||||
if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {
|
||||
@@ -985,7 +1014,7 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
this._value = value;
|
||||
this.onDidValueChangeEmitter.fire(value);
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined)));
|
||||
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire()));
|
||||
this.valueSelectionUpdated = true;
|
||||
}
|
||||
super.show();
|
||||
@@ -1039,10 +1068,12 @@ export class QuickInputController extends Disposable {
|
||||
|
||||
private parentElement: HTMLElement;
|
||||
private styles: IQuickInputStyles;
|
||||
|
||||
private onShowEmitter = new Emitter<void>();
|
||||
readonly onShow = this.onShowEmitter.event;
|
||||
|
||||
private onHideEmitter = new Emitter<void>();
|
||||
public onShow = this.onShowEmitter.event;
|
||||
public onHide = this.onHideEmitter.event;
|
||||
readonly onHide = this.onHideEmitter.event;
|
||||
|
||||
constructor(private options: IQuickInputOptions) {
|
||||
super();
|
||||
@@ -1517,7 +1548,7 @@ export class QuickInputController extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public hide(focusLost?: boolean) {
|
||||
hide(focusLost?: boolean) {
|
||||
const controller = this.controller;
|
||||
if (controller) {
|
||||
this.controller = null;
|
||||
@@ -1544,14 +1575,21 @@ export class QuickInputController extends Disposable {
|
||||
|
||||
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) {
|
||||
if (this.isDisplayed() && this.getUI().list.isDisplayed()) {
|
||||
this.getUI().list.focus(next ? 'Next' : 'Previous');
|
||||
this.getUI().list.focus(next ? QuickInputListFocus.Next : QuickInputListFocus.Previous);
|
||||
if (quickNavigate && this.controller instanceof QuickPick) {
|
||||
this.controller.quickNavigate = quickNavigate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async accept() {
|
||||
async accept(keyMods: IKeyMods = { alt: false, ctrlCmd: false }) {
|
||||
// When accepting the item programmatically, it is important that
|
||||
// we update `keyMods` either from the provided set or unset it
|
||||
// because the accept did not happen from mouse or keyboard
|
||||
// interaction on the list itself
|
||||
this.keyMods.alt = keyMods.alt;
|
||||
this.keyMods.ctrlCmd = keyMods.ctrlCmd;
|
||||
|
||||
this.onDidAcceptEmitter.fire();
|
||||
}
|
||||
|
||||
@@ -1583,7 +1621,7 @@ export class QuickInputController extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public applyStyles(styles: IQuickInputStyles) {
|
||||
applyStyles(styles: IQuickInputStyles) {
|
||||
this.styles = styles;
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
@@ -222,6 +222,16 @@ class ListElementDelegate implements IListVirtualDelegate<ListElement> {
|
||||
}
|
||||
}
|
||||
|
||||
export enum QuickInputListFocus {
|
||||
First = 1,
|
||||
Second,
|
||||
Last,
|
||||
Next,
|
||||
Previous,
|
||||
NextPage,
|
||||
PreviousPage
|
||||
}
|
||||
|
||||
export class QuickInputList {
|
||||
|
||||
readonly id: string;
|
||||
@@ -307,6 +317,18 @@ export class QuickInputList {
|
||||
this._onLeave.fire();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.list.onContextMenu(e => {
|
||||
if (typeof e.index === 'number') {
|
||||
e.browserEvent.preventDefault();
|
||||
|
||||
// we want to treat a context menu event as
|
||||
// a gesture to open the item at the index
|
||||
// since we do not have any context menu
|
||||
// this enables for example macOS to Ctrl-
|
||||
// click on an item to open it.
|
||||
this.list.setSelection([e.index]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@memoize
|
||||
@@ -430,7 +452,10 @@ export class QuickInputList {
|
||||
.filter(item => this.elementsToIndexes.has(item))
|
||||
.map(item => this.elementsToIndexes.get(item)!));
|
||||
if (items.length > 0) {
|
||||
this.list.reveal(this.list.getFocus()[0]);
|
||||
const focused = this.list.getFocus()[0];
|
||||
if (typeof focused === 'number') {
|
||||
this.list.reveal(focused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,19 +499,51 @@ export class QuickInputList {
|
||||
this.list.getHTMLElement().style.pointerEvents = value ? null : 'none';
|
||||
}
|
||||
|
||||
focus(what: 'First' | 'Last' | 'Next' | 'Previous' | 'NextPage' | 'PreviousPage'): void {
|
||||
focus(what: QuickInputListFocus): void {
|
||||
if (!this.list.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((what === 'Next' || what === 'NextPage') && this.list.getFocus()[0] === this.list.length - 1) {
|
||||
what = 'First';
|
||||
if ((what === QuickInputListFocus.Next || what === QuickInputListFocus.NextPage) && this.list.getFocus()[0] === this.list.length - 1) {
|
||||
what = QuickInputListFocus.First;
|
||||
}
|
||||
if ((what === 'Previous' || what === 'PreviousPage') && this.list.getFocus()[0] === 0) {
|
||||
what = 'Last';
|
||||
|
||||
if ((what === QuickInputListFocus.Previous || what === QuickInputListFocus.PreviousPage) && this.list.getFocus()[0] === 0) {
|
||||
what = QuickInputListFocus.Last;
|
||||
}
|
||||
|
||||
if (what === QuickInputListFocus.Second && this.list.length < 2) {
|
||||
what = QuickInputListFocus.First;
|
||||
}
|
||||
|
||||
switch (what) {
|
||||
case QuickInputListFocus.First:
|
||||
this.list.focusFirst();
|
||||
break;
|
||||
case QuickInputListFocus.Second:
|
||||
this.list.focusNth(1);
|
||||
break;
|
||||
case QuickInputListFocus.Last:
|
||||
this.list.focusLast();
|
||||
break;
|
||||
case QuickInputListFocus.Next:
|
||||
this.list.focusNext();
|
||||
break;
|
||||
case QuickInputListFocus.Previous:
|
||||
this.list.focusPrevious();
|
||||
break;
|
||||
case QuickInputListFocus.NextPage:
|
||||
this.list.focusNextPage();
|
||||
break;
|
||||
case QuickInputListFocus.PreviousPage:
|
||||
this.list.focusPreviousPage();
|
||||
break;
|
||||
}
|
||||
|
||||
const focused = this.list.getFocus()[0];
|
||||
if (typeof focused === 'number') {
|
||||
this.list.reveal(focused);
|
||||
}
|
||||
this.list['focus' + what as 'focusFirst' | 'focusLast' | 'focusNext' | 'focusPrevious' | 'focusNextPage' | 'focusPreviousPage']();
|
||||
this.list.reveal(this.list.getFocus()[0]);
|
||||
}
|
||||
|
||||
clearFocus() {
|
||||
|
||||
@@ -237,6 +237,14 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
|
||||
|
||||
autoFocusOnList: boolean;
|
||||
|
||||
/**
|
||||
* If enabled, will try to select the second entry of the picks
|
||||
* once they appear instead of the first one. This is useful
|
||||
* e.g. when `quickNavigate` is enabled to be able to select
|
||||
* a previous entry by just releasing the quick nav keys.
|
||||
*/
|
||||
autoFocusSecondEntry: boolean;
|
||||
|
||||
quickNavigate: IQuickNavigateConfiguration | undefined;
|
||||
|
||||
activeItems: ReadonlyArray<T>;
|
||||
@@ -256,6 +264,13 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
|
||||
inputHasFocus(): boolean;
|
||||
|
||||
focusOnInput(): void;
|
||||
|
||||
/**
|
||||
* Hides the input box from the picker UI. This is typically used
|
||||
* in combination with quick-navigation where no search UI should
|
||||
* be presented.
|
||||
*/
|
||||
hideInput: boolean;
|
||||
}
|
||||
|
||||
export interface IInputBox extends IQuickInput {
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as scorer from 'vs/base/common/fuzzyScorer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { basename, dirname, sep } from 'vs/base/common/path';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
class ResourceAccessorClass implements scorer.IItemAccessor<URI> {
|
||||
|
||||
@@ -49,8 +50,8 @@ function scoreItem<T>(item: T, query: string, fuzzy: boolean, accessor: scorer.I
|
||||
return scorer.scoreItem(item, scorer.prepareQuery(query), fuzzy, accessor, cache);
|
||||
}
|
||||
|
||||
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache, fallbackComparer?: (itemA: T, itemB: T, query: scorer.IPreparedQuery, accessor: scorer.IItemAccessor<T>) => number): number {
|
||||
return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer as any);
|
||||
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);
|
||||
}
|
||||
|
||||
const NullAccessor = new NullAccessorClass();
|
||||
@@ -279,6 +280,19 @@ suite('Fuzzy Scorer', () => {
|
||||
assert.ok(!res.score);
|
||||
});
|
||||
|
||||
test('scoreItem - match if using slash or backslash (local, remote resource)', function () {
|
||||
const localResource = URI.file('abcde/super/duper');
|
||||
const remoteResource = URI.from({ scheme: Schemas.vscodeRemote, path: 'abcde/super/duper' });
|
||||
|
||||
for (const resource of [localResource, remoteResource]) {
|
||||
let res = scoreItem(resource, 'abcde\\super\\duper', true, ResourceAccessor, cache);
|
||||
assert.ok(res.score);
|
||||
|
||||
res = scoreItem(resource, 'abcde/super/duper', true, ResourceAccessor, cache);
|
||||
assert.ok(res.score);
|
||||
}
|
||||
});
|
||||
|
||||
test('compareItemsByScore - identity', function () {
|
||||
const resourceA = URI.file('/some/path/fileA.txt');
|
||||
const resourceB = URI.file('/some/path/other/fileB.txt');
|
||||
@@ -509,33 +523,13 @@ suite('Fuzzy Scorer', () => {
|
||||
assert.equal(res[2], resourceC);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - allow to provide fallback sorter (bug #31591)', function () {
|
||||
const resourceA = URI.file('virtual/vscode.d.ts');
|
||||
const resourceB = URI.file('vscode/src/vs/vscode.d.ts');
|
||||
test('compareFilesByScore - prefer matches in label over description if scores are otherwise equal', function () {
|
||||
const resourceA = URI.file('parts/quick/arrow-left-dark.svg');
|
||||
const resourceB = URI.file('parts/quickopen/quickopen.ts');
|
||||
|
||||
let query = 'vscode';
|
||||
let query = 'partsquick';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => {
|
||||
return compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => {
|
||||
if (r1 as any /* TS fail */ === resourceA) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
});
|
||||
assert.equal(res[0], resourceA);
|
||||
assert.equal(res[1], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => {
|
||||
return compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => {
|
||||
if (r1 as any /* TS fail */ === resourceB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
});
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
|
||||
assert.equal(res[0], resourceB);
|
||||
assert.equal(res[1], resourceA);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator } from 'vs/base/common/resources';
|
||||
import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, getComparisonKey } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { toSlashes } from 'vs/base/common/extpath';
|
||||
@@ -66,6 +66,8 @@ suite('Resources', () => {
|
||||
|
||||
// does not explode (https://github.com/Microsoft/vscode/issues/41987)
|
||||
dirname(URI.from({ scheme: 'file', authority: '/users/someone/portal.h' }));
|
||||
|
||||
assert.equal(dirname(URI.parse('foo://a/b/c?q')).toString(), 'foo://a/b?q');
|
||||
});
|
||||
|
||||
test('basename', () => {
|
||||
@@ -156,6 +158,7 @@ suite('Resources', () => {
|
||||
assert.equal(normalizePath(URI.parse('foo://a/foo/foo/./../some/../bar')).toString(), 'foo://a/foo/bar');
|
||||
assert.equal(normalizePath(URI.parse('foo://a')).toString(), 'foo://a');
|
||||
assert.equal(normalizePath(URI.parse('foo://a/')).toString(), 'foo://a/');
|
||||
assert.equal(normalizePath(URI.parse('foo://a/foo/./bar?q=1')).toString(), URI.parse('foo://a/foo/bar?q%3D1').toString());
|
||||
});
|
||||
|
||||
test('isAbsolute', () => {
|
||||
@@ -233,7 +236,7 @@ suite('Resources', () => {
|
||||
});
|
||||
|
||||
function assertEqualURI(actual: URI, expected: URI, message?: string) {
|
||||
if (!isEqual(expected, actual)) {
|
||||
if (!isEqual(expected, actual, hasToIgnoreCase(expected), false)) {
|
||||
assert.equal(actual.toString(), expected.toString(), message);
|
||||
}
|
||||
}
|
||||
@@ -259,7 +262,7 @@ suite('Resources', () => {
|
||||
assertRelativePath(URI.parse('foo://a'), URI.parse('foo://a'), '');
|
||||
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/'), '');
|
||||
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a'), '');
|
||||
assertRelativePath(URI.parse('foo://a/foo?q'), URI.parse('foo://a/foo/bar#h'), 'bar');
|
||||
assertRelativePath(URI.parse('foo://a/foo?q'), URI.parse('foo://a/foo/bar#h'), 'bar', true);
|
||||
assertRelativePath(URI.parse('foo://'), URI.parse('foo://a/b'), undefined);
|
||||
assertRelativePath(URI.parse('foo://a2/b'), URI.parse('foo://a/b'), undefined);
|
||||
assertRelativePath(URI.parse('goo://a/b'), URI.parse('foo://a/b'), undefined);
|
||||
@@ -343,26 +346,44 @@ suite('Resources', () => {
|
||||
|
||||
});
|
||||
|
||||
function assertIsEqual(u1: URI, u2: URI, ignoreCase: boolean, expected: boolean) {
|
||||
assert.equal(isEqual(u1, u2, ignoreCase), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`);
|
||||
assert.equal(getComparisonKey(u1, ignoreCase) === getComparisonKey(u2, ignoreCase), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`);
|
||||
assert.equal(isEqualOrParent(u1, u2, ignoreCase), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`);
|
||||
}
|
||||
|
||||
|
||||
test('isEqual', () => {
|
||||
let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar');
|
||||
let fileURI2 = isWindows ? URI.file('C:\\foo\\Bar') : URI.file('/foo/Bar');
|
||||
assert.equal(isEqual(fileURI, fileURI, true), true);
|
||||
assert.equal(isEqual(fileURI, fileURI, false), true);
|
||||
assert.equal(isEqual(fileURI, fileURI, hasToIgnoreCase(fileURI)), true);
|
||||
assert.equal(isEqual(fileURI, fileURI2, true), true);
|
||||
assert.equal(isEqual(fileURI, fileURI2, false), false);
|
||||
assertIsEqual(fileURI, fileURI, true, true);
|
||||
assertIsEqual(fileURI, fileURI, false, true);
|
||||
assertIsEqual(fileURI, fileURI, hasToIgnoreCase(fileURI), true);
|
||||
assertIsEqual(fileURI, fileURI2, true, true);
|
||||
assertIsEqual(fileURI, fileURI2, false, false);
|
||||
|
||||
let fileURI3 = URI.parse('foo://server:453/foo/bar');
|
||||
let fileURI4 = URI.parse('foo://server:453/foo/Bar');
|
||||
assert.equal(isEqual(fileURI3, fileURI3, true), true);
|
||||
assert.equal(isEqual(fileURI3, fileURI3, false), true);
|
||||
assert.equal(isEqual(fileURI3, fileURI3, hasToIgnoreCase(fileURI3)), true);
|
||||
assert.equal(isEqual(fileURI3, fileURI4, true), true);
|
||||
assert.equal(isEqual(fileURI3, fileURI4, false), false);
|
||||
assertIsEqual(fileURI3, fileURI3, true, true);
|
||||
assertIsEqual(fileURI3, fileURI3, false, true);
|
||||
assertIsEqual(fileURI3, fileURI3, hasToIgnoreCase(fileURI3), true);
|
||||
assertIsEqual(fileURI3, fileURI4, true, true);
|
||||
assertIsEqual(fileURI3, fileURI4, false, false);
|
||||
|
||||
assert.equal(isEqual(fileURI, fileURI3, true), false);
|
||||
assertIsEqual(fileURI, fileURI3, true, false);
|
||||
|
||||
assert.equal(isEqual(URI.parse('foo://server'), URI.parse('foo://server/')), true);
|
||||
assertIsEqual(URI.parse('foo://server'), URI.parse('foo://server/'), true, true);
|
||||
assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo/'), true, false);
|
||||
assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo?'), true, true);
|
||||
|
||||
let fileURI5 = URI.parse('foo://server:453/foo/bar?q=1');
|
||||
let fileURI6 = URI.parse('foo://server:453/foo/bar#xy');
|
||||
|
||||
assertIsEqual(fileURI5, fileURI5, true, true);
|
||||
assertIsEqual(fileURI5, fileURI3, true, false);
|
||||
assertIsEqual(fileURI6, fileURI6, true, true);
|
||||
assertIsEqual(fileURI6, fileURI5, true, false);
|
||||
assertIsEqual(fileURI6, fileURI3, true, true);
|
||||
});
|
||||
|
||||
test('isEqualOrParent', () => {
|
||||
@@ -388,5 +409,12 @@ suite('Resources', () => {
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI4, false), true, '14');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI, true), false, '15');
|
||||
assert.equal(isEqualOrParent(fileURI5, fileURI5, true), true, '16');
|
||||
|
||||
let fileURI6 = URI.parse('foo://server:453/foo?q=1');
|
||||
let fileURI7 = URI.parse('foo://server:453/foo/bar?q=1');
|
||||
assert.equal(isEqualOrParent(fileURI6, fileURI5, true), false, '17');
|
||||
assert.equal(isEqualOrParent(fileURI6, fileURI6, true), true, '18');
|
||||
assert.equal(isEqualOrParent(fileURI7, fileURI6, true), true, '19');
|
||||
assert.equal(isEqualOrParent(fileURI7, fileURI5, true), false, '20');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user