mirror of
https://github.com/coder/code-server.git
synced 2026-05-08 13:27:25 +02:00
chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
@@ -14,8 +14,8 @@ export interface ITelemetryData {
|
||||
}
|
||||
|
||||
export type WorkbenchActionExecutedClassification = {
|
||||
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
from: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
|
||||
from: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
|
||||
};
|
||||
|
||||
export type WorkbenchActionExecutedEvent = {
|
||||
@@ -63,7 +63,7 @@ export interface IActionChangeEvent {
|
||||
export class Action extends Disposable implements IAction {
|
||||
|
||||
protected _onDidChange = this._register(new Emitter<IActionChangeEvent>());
|
||||
readonly onDidChange: Event<IActionChangeEvent> = this._onDidChange.event;
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
protected readonly _id: string;
|
||||
protected _label: string;
|
||||
@@ -179,10 +179,10 @@ export interface IRunEvent {
|
||||
export class ActionRunner extends Disposable implements IActionRunner {
|
||||
|
||||
private _onBeforeRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onBeforeRun: Event<IRunEvent> = this._onBeforeRun.event;
|
||||
readonly onBeforeRun = this._onBeforeRun.event;
|
||||
|
||||
private _onDidRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onDidRun: Event<IRunEvent> = this._onDidRun.event;
|
||||
readonly onDidRun = this._onDidRun.event;
|
||||
|
||||
async run(action: IAction, context?: any): Promise<any> {
|
||||
if (!action.enabled) {
|
||||
@@ -246,15 +246,35 @@ export class ActionWithMenuAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class SubmenuAction extends Action {
|
||||
export class SubmenuAction implements IAction {
|
||||
|
||||
get actions(): IAction[] {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly class: string | undefined;
|
||||
readonly tooltip: string = '';
|
||||
readonly enabled: boolean = true;
|
||||
readonly checked: boolean = false;
|
||||
|
||||
private readonly _actions: readonly IAction[];
|
||||
|
||||
constructor(id: string, label: string, actions: readonly IAction[], cssClass?: string) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.class = cssClass;
|
||||
this._actions = actions;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// there is NOTHING to dispose and the SubmenuAction should
|
||||
// never have anything to dispose as it is a convenience type
|
||||
// to bridge into the rendering world.
|
||||
}
|
||||
|
||||
get actions(): readonly IAction[] {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
constructor(id: string, label: string, private _actions: IAction[], cssClass?: string) {
|
||||
super(id, label, cssClass, !!_actions?.length);
|
||||
}
|
||||
async run(): Promise<any> { }
|
||||
}
|
||||
|
||||
export class EmptySubmenuAction extends Action {
|
||||
@@ -263,3 +283,16 @@ export class EmptySubmenuAction extends Action {
|
||||
super(EmptySubmenuAction.ID, nls.localize('submenu.empty', '(empty)'), undefined, false);
|
||||
}
|
||||
}
|
||||
|
||||
export function toAction(props: { id: string, label: string, enabled?: boolean, checked?: boolean, run: Function; }): IAction {
|
||||
return {
|
||||
id: props.id,
|
||||
label: props.label,
|
||||
class: undefined,
|
||||
enabled: props.enabled ?? true,
|
||||
checked: props.checked ?? false,
|
||||
run: async () => props.run(),
|
||||
tooltip: props.label,
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,13 +152,13 @@ export class Throttler {
|
||||
return result;
|
||||
};
|
||||
|
||||
this.queuedPromise = new Promise(c => {
|
||||
this.activePromise!.then(onComplete, onComplete).then(c);
|
||||
this.queuedPromise = new Promise(resolve => {
|
||||
this.activePromise!.then(onComplete, onComplete).then(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((c, e) => {
|
||||
this.queuedPromise!.then(c, e);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queuedPromise!.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ export class Sequencer {
|
||||
private current: Promise<any> = Promise.resolve(null);
|
||||
|
||||
queue<T>(promiseTask: ITask<Promise<T>>): Promise<T> {
|
||||
return this.current = this.current.then(() => promiseTask());
|
||||
return this.current = this.current.then(() => promiseTask(), () => promiseTask());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ export class SequencerByKey<TKey> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to delay execution of a task that is being requested often.
|
||||
* A helper to delay (debounce) execution of a task that is being requested often.
|
||||
*
|
||||
* Following the throttler, now imagine the mail man wants to optimize the number of
|
||||
* trips proactively. The trip itself can be long, so he decides not to make the trip
|
||||
@@ -248,9 +248,9 @@ export class Delayer<T> implements IDisposable {
|
||||
this.cancelTimeout();
|
||||
|
||||
if (!this.completionPromise) {
|
||||
this.completionPromise = new Promise((c, e) => {
|
||||
this.doResolve = c;
|
||||
this.doReject = e;
|
||||
this.completionPromise = new Promise((resolve, reject) => {
|
||||
this.doResolve = resolve;
|
||||
this.doReject = reject;
|
||||
}).then(() => {
|
||||
this.completionPromise = null;
|
||||
this.doResolve = null;
|
||||
@@ -1013,3 +1013,61 @@ export class IntervalCounter {
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export type ValueCallback<T = any> = (value: T | Promise<T>) => void;
|
||||
|
||||
/**
|
||||
* Creates a promise whose resolution or rejection can be controlled imperatively.
|
||||
*/
|
||||
export class DeferredPromise<T> {
|
||||
|
||||
private completeCallback!: ValueCallback<T>;
|
||||
private errorCallback!: (err: any) => void;
|
||||
private rejected = false;
|
||||
private resolved = false;
|
||||
|
||||
public get isRejected() {
|
||||
return this.rejected;
|
||||
}
|
||||
|
||||
public get isResolved() {
|
||||
return this.resolved;
|
||||
}
|
||||
|
||||
public get isSettled() {
|
||||
return this.rejected || this.resolved;
|
||||
}
|
||||
|
||||
public p: Promise<T>;
|
||||
|
||||
constructor() {
|
||||
this.p = new Promise<T>((c, e) => {
|
||||
this.completeCallback = c;
|
||||
this.errorCallback = e;
|
||||
});
|
||||
}
|
||||
|
||||
public complete(value: T) {
|
||||
return new Promise<void>(resolve => {
|
||||
this.completeCallback(value);
|
||||
this.resolved = true;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public error(err: any) {
|
||||
return new Promise<void>(resolve => {
|
||||
this.errorCallback(err);
|
||||
this.rejected = true;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
new Promise<void>(resolve => {
|
||||
this.errorCallback(errors.canceled());
|
||||
this.rejected = true;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,9 @@ export class VSBuffer {
|
||||
return new VSBuffer(actual);
|
||||
}
|
||||
|
||||
static fromString(source: string): VSBuffer {
|
||||
if (hasBuffer) {
|
||||
static fromString(source: string, options?: { dontUseNodeBuffer?: boolean; }): VSBuffer {
|
||||
const dontUseNodeBuffer = options?.dontUseNodeBuffer || false;
|
||||
if (!dontUseNodeBuffer && hasBuffer) {
|
||||
return new VSBuffer(Buffer.from(source));
|
||||
} else if (hasTextEncoder) {
|
||||
if (!textEncoder) {
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { matchesFuzzy, IMatch } from 'vs/base/common/filters';
|
||||
import { ltrim } from 'vs/base/common/strings';
|
||||
|
||||
export const codiconStartMarker = '$(';
|
||||
|
||||
export interface IParsedCodicons {
|
||||
readonly text: string;
|
||||
readonly codiconOffsets?: readonly number[];
|
||||
}
|
||||
|
||||
export function parseCodicons(text: string): IParsedCodicons {
|
||||
const firstCodiconIndex = text.indexOf(codiconStartMarker);
|
||||
if (firstCodiconIndex === -1) {
|
||||
return { text }; // return early if the word does not include an codicon
|
||||
}
|
||||
|
||||
return doParseCodicons(text, firstCodiconIndex);
|
||||
}
|
||||
|
||||
function doParseCodicons(text: string, firstCodiconIndex: number): IParsedCodicons {
|
||||
const codiconOffsets: number[] = [];
|
||||
let textWithoutCodicons: string = '';
|
||||
|
||||
function appendChars(chars: string) {
|
||||
if (chars) {
|
||||
textWithoutCodicons += chars;
|
||||
|
||||
for (const _ of chars) {
|
||||
codiconOffsets.push(codiconsOffset); // make sure to fill in codicon offsets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let currentCodiconStart = -1;
|
||||
let currentCodiconValue: string = '';
|
||||
let codiconsOffset = 0;
|
||||
|
||||
let char: string;
|
||||
let nextChar: string;
|
||||
|
||||
let offset = firstCodiconIndex;
|
||||
const length = text.length;
|
||||
|
||||
// Append all characters until the first codicon
|
||||
appendChars(text.substr(0, firstCodiconIndex));
|
||||
|
||||
// example: $(file-symlink-file) my cool $(other-codicon) entry
|
||||
while (offset < length) {
|
||||
char = text[offset];
|
||||
nextChar = text[offset + 1];
|
||||
|
||||
// beginning of codicon: some value $( <--
|
||||
if (char === codiconStartMarker[0] && nextChar === codiconStartMarker[1]) {
|
||||
currentCodiconStart = offset;
|
||||
|
||||
// if we had a previous potential codicon value without
|
||||
// the closing ')', it was actually not an codicon and
|
||||
// so we have to add it to the actual value
|
||||
appendChars(currentCodiconValue);
|
||||
|
||||
currentCodiconValue = codiconStartMarker;
|
||||
|
||||
offset++; // jump over '('
|
||||
}
|
||||
|
||||
// end of codicon: some value $(some-codicon) <--
|
||||
else if (char === ')' && currentCodiconStart !== -1) {
|
||||
const currentCodiconLength = offset - currentCodiconStart + 1; // +1 to include the closing ')'
|
||||
codiconsOffset += currentCodiconLength;
|
||||
currentCodiconStart = -1;
|
||||
currentCodiconValue = '';
|
||||
}
|
||||
|
||||
// within codicon
|
||||
else if (currentCodiconStart !== -1) {
|
||||
// Make sure this is a real codicon name
|
||||
if (/^[a-z0-9\-]$/i.test(char)) {
|
||||
currentCodiconValue += char;
|
||||
} else {
|
||||
// This is not a real codicon, treat it as text
|
||||
appendChars(currentCodiconValue);
|
||||
|
||||
currentCodiconStart = -1;
|
||||
currentCodiconValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
// any value outside of codicons
|
||||
else {
|
||||
appendChars(char);
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
// if we had a previous potential codicon value without
|
||||
// the closing ')', it was actually not an codicon and
|
||||
// so we have to add it to the actual value
|
||||
appendChars(currentCodiconValue);
|
||||
|
||||
return { text: textWithoutCodicons, codiconOffsets };
|
||||
}
|
||||
|
||||
export function matchesFuzzyCodiconAware(query: string, target: IParsedCodicons, enableSeparateSubstringMatching = false): IMatch[] | null {
|
||||
const { text, codiconOffsets } = target;
|
||||
|
||||
// Return early if there are no codicon markers in the word to match against
|
||||
if (!codiconOffsets || codiconOffsets.length === 0) {
|
||||
return matchesFuzzy(query, text, enableSeparateSubstringMatching);
|
||||
}
|
||||
|
||||
// Trim the word to match against because it could have leading
|
||||
// whitespace now if the word started with an codicon
|
||||
const wordToMatchAgainstWithoutCodiconsTrimmed = ltrim(text, ' ');
|
||||
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutCodiconsTrimmed.length;
|
||||
|
||||
// match on value without codicons
|
||||
const matches = matchesFuzzy(query, wordToMatchAgainstWithoutCodiconsTrimmed, enableSeparateSubstringMatching);
|
||||
|
||||
// Map matches back to offsets with codicons and trimming
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const codiconOffset = codiconOffsets[match.start + leadingWhitespaceOffset] /* codicon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
|
||||
match.start += codiconOffset;
|
||||
match.end += codiconOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
@@ -3,9 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { codiconStartMarker } from 'vs/base/common/codicon';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export interface IIconRegistry {
|
||||
readonly all: IterableIterator<Codicon>;
|
||||
@@ -47,8 +45,8 @@ const _registry = new Registry();
|
||||
|
||||
export const iconRegistry: IIconRegistry = _registry;
|
||||
|
||||
export function registerCodicon(id: string, def: Codicon, description?: string): Codicon {
|
||||
return new Codicon(id, def, description);
|
||||
export function registerCodicon(id: string, def: Codicon): Codicon {
|
||||
return new Codicon(id, def);
|
||||
}
|
||||
|
||||
export class Codicon implements CSSIcon {
|
||||
@@ -61,8 +59,44 @@ export class Codicon implements CSSIcon {
|
||||
public get cssSelector() { return '.codicon.codicon-' + this.id; }
|
||||
}
|
||||
|
||||
export function getClassNamesArray(id: string, modifier?: string) {
|
||||
const classNames = ['codicon', 'codicon-' + id];
|
||||
if (modifier) {
|
||||
classNames.push('codicon-modifier-' + modifier);
|
||||
}
|
||||
return classNames;
|
||||
}
|
||||
|
||||
export interface CSSIcon {
|
||||
readonly classNames: string;
|
||||
readonly id: string;
|
||||
}
|
||||
|
||||
export namespace CSSIcon {
|
||||
export const iconIdRegex = /^(codicon\/)?([a-z\-]+)(?:~([a-z\-]+))?$/i;
|
||||
|
||||
export function asClassNameArray(icon: CSSIcon): string[] {
|
||||
if (icon instanceof Codicon) {
|
||||
return ['codicon', 'codicon-' + icon.id];
|
||||
}
|
||||
const match = iconIdRegex.exec(icon.id);
|
||||
if (!match) {
|
||||
return asClassNameArray(Codicon.error);
|
||||
}
|
||||
let [, , id, modifier] = match;
|
||||
const classNames = ['codicon', 'codicon-' + id];
|
||||
if (modifier) {
|
||||
classNames.push('codicon-modifier-' + modifier);
|
||||
}
|
||||
return classNames;
|
||||
}
|
||||
|
||||
export function asClassName(icon: CSSIcon): string {
|
||||
return asClassNameArray(icon).join(' ');
|
||||
}
|
||||
|
||||
export function asCSSSelector(icon: CSSIcon): string {
|
||||
return '.' + asClassNameArray(icon).join('.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -497,36 +531,15 @@ export namespace Codicon {
|
||||
export const passFilled = new Codicon('pass-filled', { character: '\\ebb3' });
|
||||
export const circleLargeFilled = new Codicon('circle-large-filled', { character: '\\ebb4' });
|
||||
export const circleLargeOutline = new Codicon('circle-large-outline', { character: '\\ebb5' });
|
||||
export const combine = new Codicon('combine', { character: '\\ebb6' });
|
||||
export const gather = new Codicon('gather', { character: '\\ebb6' });
|
||||
export const table = new Codicon('table', { character: '\\ebb7' });
|
||||
export const variableGroup = new Codicon('variable-group', { character: '\\ebb8' });
|
||||
export const typeHierarchy = new Codicon('type-hierarchy', { character: '\\ebb9' });
|
||||
export const typeHierarchySub = new Codicon('type-hierarchy-sub', { character: '\\ebba' });
|
||||
export const typeHierarchySuper = new Codicon('type-hierarchy-super', { character: '\\ebbb' });
|
||||
export const gitPullRequestCreate = new Codicon('git-pull-request-create', { character: '\\ebbc' });
|
||||
|
||||
export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition, localize('dropDownButton', 'Icon for drop down buttons.'));
|
||||
export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition);
|
||||
}
|
||||
|
||||
// common icons
|
||||
|
||||
|
||||
|
||||
|
||||
const escapeCodiconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
export function escapeCodicons(text: string): string {
|
||||
return text.replace(escapeCodiconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownEscapedCodiconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
export function markdownEscapeEscapedCodicons(text: string): string {
|
||||
// Need to add an extra \ for escaping in markdown
|
||||
return text.replace(markdownEscapedCodiconsRegex, match => `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownUnescapeCodiconsRegex = /(\\)?\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi;
|
||||
export function markdownUnescapeCodicons(text: string): string {
|
||||
return text.replace(markdownUnescapeCodiconsRegex, (match, escaped, codicon) => escaped ? match : `$(${codicon})`);
|
||||
}
|
||||
|
||||
const stripCodiconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi;
|
||||
export function stripCodicons(text: string): string {
|
||||
if (text.indexOf(codiconStartMarker) === -1) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.replace(stripCodiconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
|
||||
}
|
||||
|
||||
@@ -142,6 +142,15 @@ export function isPromiseCanceledError(error: any): boolean {
|
||||
return error instanceof Error && error.name === canceledName && error.message === canceledName;
|
||||
}
|
||||
|
||||
// !!!IMPORTANT!!!
|
||||
// Do NOT change this class because it is also used as an API-type.
|
||||
export class CancellationError extends Error {
|
||||
constructor() {
|
||||
super(canceledName);
|
||||
this.name = this.message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error that signals cancellation.
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ import { once as onceFn } from 'vs/base/common/functional';
|
||||
import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
|
||||
/**
|
||||
* To an event a function with one or zero parameters
|
||||
@@ -374,7 +375,7 @@ export namespace Event {
|
||||
}
|
||||
|
||||
export function toPromise<T>(event: Event<T>): Promise<T> {
|
||||
return new Promise(c => once(event)(c));
|
||||
return new Promise(resolve => once(event)(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +387,41 @@ export interface EmitterOptions {
|
||||
onListenerDidAdd?: Function;
|
||||
onLastListenerRemove?: Function;
|
||||
leakWarningThreshold?: number;
|
||||
|
||||
/** ONLY enable this during development */
|
||||
_profName?: string
|
||||
}
|
||||
|
||||
|
||||
class EventProfiling {
|
||||
|
||||
private static _idPool = 0;
|
||||
|
||||
private _name: string;
|
||||
private _stopWatch?: StopWatch;
|
||||
private _listenerCount: number = 0;
|
||||
private _invocationCount = 0;
|
||||
private _elapsedOverall = 0;
|
||||
|
||||
constructor(name: string) {
|
||||
this._name = `${name}_${EventProfiling._idPool++}`;
|
||||
}
|
||||
|
||||
start(listenerCount: number): void {
|
||||
this._stopWatch = new StopWatch(true);
|
||||
this._listenerCount = listenerCount;
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this._stopWatch) {
|
||||
const elapsed = this._stopWatch.elapsed();
|
||||
this._elapsedOverall += elapsed;
|
||||
this._invocationCount += 1;
|
||||
|
||||
console.info(`did FIRE ${this._name}: elapsed_ms: ${elapsed.toFixed(5)}, listener: ${this._listenerCount} (elapsed_overall: ${this._elapsedOverall.toFixed(2)}, invocations: ${this._invocationCount})`);
|
||||
this._stopWatch = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _globalLeakWarningThreshold = -1;
|
||||
@@ -487,6 +523,7 @@ export class Emitter<T> {
|
||||
|
||||
private readonly _options?: EmitterOptions;
|
||||
private readonly _leakageMon?: LeakageMonitor;
|
||||
private readonly _perfMon?: EventProfiling;
|
||||
private _disposed: boolean = false;
|
||||
private _event?: Event<T>;
|
||||
private _deliveryQueue?: LinkedList<[Listener<T>, T]>;
|
||||
@@ -494,9 +531,8 @@ export class Emitter<T> {
|
||||
|
||||
constructor(options?: EmitterOptions) {
|
||||
this._options = options;
|
||||
this._leakageMon = _globalLeakWarningThreshold > 0
|
||||
? new LeakageMonitor(this._options && this._options.leakWarningThreshold)
|
||||
: undefined;
|
||||
this._leakageMon = _globalLeakWarningThreshold > 0 ? new LeakageMonitor(this._options && this._options.leakWarningThreshold) : undefined;
|
||||
this._perfMon = this._options?._profName ? new EventProfiling(this._options._profName) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -527,10 +563,7 @@ export class Emitter<T> {
|
||||
}
|
||||
|
||||
// check and record this emitter for potential leakage
|
||||
let removeMonitor: (() => void) | undefined;
|
||||
if (this._leakageMon) {
|
||||
removeMonitor = this._leakageMon.check(this._listeners.size);
|
||||
}
|
||||
const removeMonitor = this._leakageMon?.check(this._listeners.size);
|
||||
|
||||
let result: IDisposable;
|
||||
result = {
|
||||
@@ -580,6 +613,9 @@ export class Emitter<T> {
|
||||
this._deliveryQueue.push([listener, event]);
|
||||
}
|
||||
|
||||
// start/stop performance insight collection
|
||||
this._perfMon?.start(this._deliveryQueue.size);
|
||||
|
||||
while (this._deliveryQueue.size > 0) {
|
||||
const [listener, event] = this._deliveryQueue.shift()!;
|
||||
try {
|
||||
@@ -592,19 +628,15 @@ export class Emitter<T> {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
|
||||
this._perfMon?.stop();
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._listeners) {
|
||||
this._listeners.clear();
|
||||
}
|
||||
if (this._deliveryQueue) {
|
||||
this._deliveryQueue.clear();
|
||||
}
|
||||
if (this._leakageMon) {
|
||||
this._leakageMon.dispose();
|
||||
}
|
||||
this._listeners?.clear();
|
||||
this._deliveryQueue?.clear();
|
||||
this._leakageMon?.dispose();
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -617,7 +649,7 @@ export class PauseableEmitter<T> extends Emitter<T> {
|
||||
|
||||
constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
|
||||
super(options);
|
||||
this._mergeFn = options && options.merge;
|
||||
this._mergeFn = options?.merge;
|
||||
}
|
||||
|
||||
pause(): void {
|
||||
|
||||
@@ -277,14 +277,25 @@ export function isRootOrDriveLetter(path: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isWindowsDriveLetter(pathNormalized.charCodeAt(0))
|
||||
&& pathNormalized.charCodeAt(1) === CharCode.Colon
|
||||
&& (path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);
|
||||
return hasDriveLetter(pathNormalized) &&
|
||||
(path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);
|
||||
}
|
||||
|
||||
return pathNormalized === posix.sep;
|
||||
}
|
||||
|
||||
export function hasDriveLetter(path: string): boolean {
|
||||
if (isWindows) {
|
||||
return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getDriveLetter(path: string): string | undefined {
|
||||
return hasDriveLetter(path) ? path[0] : undefined;
|
||||
}
|
||||
|
||||
export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
|
||||
if (candidate.length > path.length) {
|
||||
return -1;
|
||||
|
||||
@@ -370,24 +370,23 @@ export function anyScore(pattern: string, lowPattern: string, _patternPos: numbe
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
let matches = 0;
|
||||
let matches: number[] = [];
|
||||
let score = 0;
|
||||
let idx = _wordPos;
|
||||
for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
|
||||
const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
|
||||
if (wordPos >= 0) {
|
||||
score += 1;
|
||||
matches += 2 ** wordPos;
|
||||
matches.unshift(wordPos);
|
||||
idx = wordPos + 1;
|
||||
|
||||
} else if (matches !== 0) {
|
||||
} else if (matches.length > 0) {
|
||||
// once we have started matching things
|
||||
// we need to match the remaining pattern
|
||||
// characters
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [score, matches, _wordPos];
|
||||
return [score, _wordPos, ...matches];
|
||||
}
|
||||
|
||||
//#region --- fuzzyScore ---
|
||||
@@ -396,19 +395,15 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] {
|
||||
if (typeof score === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matches = score[1].toString(2);
|
||||
const wordStart = score[2];
|
||||
const res: IMatch[] = [];
|
||||
|
||||
for (let pos = wordStart; pos < _maxLen; pos++) {
|
||||
if (matches[matches.length - (pos + 1)] === '1') {
|
||||
const last = res[res.length - 1];
|
||||
if (last && last.end === pos) {
|
||||
last.end = pos + 1;
|
||||
} else {
|
||||
res.push({ start: pos, end: pos + 1 });
|
||||
}
|
||||
const wordPos = score[1];
|
||||
for (let i = score.length - 1; i > 1; i--) {
|
||||
const pos = score[i] + wordPos;
|
||||
const last = res[res.length - 1];
|
||||
if (last && last.end === pos) {
|
||||
last.end = pos + 1;
|
||||
} else {
|
||||
res.push({ start: pos, end: pos + 1 });
|
||||
}
|
||||
}
|
||||
return res;
|
||||
@@ -418,20 +413,28 @@ const _maxLen = 128;
|
||||
|
||||
function initTable() {
|
||||
const table: number[][] = [];
|
||||
const row: number[] = [0];
|
||||
for (let i = 1; i <= _maxLen; i++) {
|
||||
row.push(-i);
|
||||
const row: number[] = [];
|
||||
for (let i = 0; i <= _maxLen; i++) {
|
||||
row[i] = 0;
|
||||
}
|
||||
for (let i = 0; i <= _maxLen; i++) {
|
||||
const thisRow = row.slice(0);
|
||||
thisRow[0] = -i;
|
||||
table.push(thisRow);
|
||||
table.push(row.slice(0));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
function initArr(maxLen: number) {
|
||||
const row: number[] = [];
|
||||
for (let i = 0; i <= maxLen; i++) {
|
||||
row[i] = 0;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
const _minWordMatchPos = initArr(2 * _maxLen); // min word position for a certain pattern position
|
||||
const _maxWordMatchPos = initArr(2 * _maxLen); // max word position for a certain pattern position
|
||||
const _diag = initTable(); // the length of a contiguous diagonal match
|
||||
const _table = initTable();
|
||||
const _scores = initTable();
|
||||
const _arrows = <Arrow[][]>initTable();
|
||||
const _debug = false;
|
||||
|
||||
@@ -460,14 +463,14 @@ function printTables(pattern: string, patternStart: number, word: string, wordSt
|
||||
word = word.substr(wordStart);
|
||||
console.log(printTable(_table, pattern, pattern.length, word, word.length));
|
||||
console.log(printTable(_arrows, pattern, pattern.length, word, word.length));
|
||||
console.log(printTable(_scores, pattern, pattern.length, word, word.length));
|
||||
console.log(printTable(_diag, pattern, pattern.length, word, word.length));
|
||||
}
|
||||
|
||||
function isSeparatorAtPos(value: string, index: number): boolean {
|
||||
if (index < 0 || index >= value.length) {
|
||||
return false;
|
||||
}
|
||||
const code = value.charCodeAt(index);
|
||||
const code = value.codePointAt(index);
|
||||
switch (code) {
|
||||
case CharCode.Underline:
|
||||
case CharCode.Dash:
|
||||
@@ -479,8 +482,16 @@ function isSeparatorAtPos(value: string, index: number): boolean {
|
||||
case CharCode.DoubleQuote:
|
||||
case CharCode.Colon:
|
||||
case CharCode.DollarSign:
|
||||
case CharCode.LessThan:
|
||||
case CharCode.OpenParen:
|
||||
case CharCode.OpenSquareBracket:
|
||||
return true;
|
||||
case undefined:
|
||||
return false;
|
||||
default:
|
||||
if (strings.isEmojiImprecise(code)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -503,9 +514,13 @@ function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
|
||||
return word[pos] !== wordLow[pos];
|
||||
}
|
||||
|
||||
export function isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number): boolean {
|
||||
export function isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number, fillMinWordPosArr = false): boolean {
|
||||
while (patternPos < patternLen && wordPos < wordLen) {
|
||||
if (patternLow[patternPos] === wordLow[wordPos]) {
|
||||
if (fillMinWordPosArr) {
|
||||
// Remember the min word position for each pattern position
|
||||
_minWordMatchPos[patternPos] = wordPos;
|
||||
}
|
||||
patternPos += 1;
|
||||
}
|
||||
wordPos += 1;
|
||||
@@ -513,21 +528,24 @@ export function isPatternInWord(patternLow: string, patternPos: number, patternL
|
||||
return patternPos === patternLen; // pattern must be exhausted
|
||||
}
|
||||
|
||||
const enum Arrow { Top = 0b1, Diag = 0b10, Left = 0b100 }
|
||||
const enum Arrow { Diag = 1, Left = 2, LeftLeft = 3 }
|
||||
|
||||
/**
|
||||
* A tuple of three values.
|
||||
* An array representating a fuzzy match.
|
||||
*
|
||||
* 0. the score
|
||||
* 1. the matches encoded as bitmask (2^53)
|
||||
* 2. the offset at which matching started
|
||||
* 1. the offset at which matching started
|
||||
* 2. `<match_pos_N>`
|
||||
* 3. `<match_pos_1>`
|
||||
* 4. `<match_pos_0>` etc
|
||||
*/
|
||||
export type FuzzyScore = [number, number, number];
|
||||
export type FuzzyScore = [score: number, wordStart: number, ...matches: number[]];// [number, number, number];
|
||||
|
||||
export namespace FuzzyScore {
|
||||
/**
|
||||
* No matches and value `-100`
|
||||
*/
|
||||
export const Default: [-100, 0, 0] = <[-100, 0, 0]>Object.freeze([-100, 0, 0]);
|
||||
export const Default: FuzzyScore = ([-100, 0]);
|
||||
|
||||
export function isDefault(score?: FuzzyScore): score is [-100, 0, 0] {
|
||||
return !score || (score[0] === -100 && score[1] === 0 && score[2] === 0);
|
||||
@@ -550,58 +568,71 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
|
||||
// Run a simple check if the characters of pattern occur
|
||||
// (in order) at all in word. If that isn't the case we
|
||||
// stop because no match will be possible
|
||||
if (!isPatternInWord(patternLow, patternStart, patternLen, wordLow, wordStart, wordLen)) {
|
||||
if (!isPatternInWord(patternLow, patternStart, patternLen, wordLow, wordStart, wordLen, true)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Find the max matching word position for each pattern position
|
||||
// NOTE: the min matching word position was filled in above, in the `isPatternInWord` call
|
||||
_fillInMaxWordMatchPos(patternLen, wordLen, patternStart, wordStart, patternLow, wordLow);
|
||||
|
||||
let row: number = 1;
|
||||
let column: number = 1;
|
||||
let patternPos = patternStart;
|
||||
let wordPos = wordStart;
|
||||
|
||||
let hasStrongFirstMatch = false;
|
||||
const hasStrongFirstMatch = [false];
|
||||
|
||||
// There will be a match, fill in tables
|
||||
for (row = 1, patternPos = patternStart; patternPos < patternLen; row++, patternPos++) {
|
||||
|
||||
for (column = 1, wordPos = wordStart; wordPos < wordLen; column++, wordPos++) {
|
||||
// Reduce search space to possible matching word positions and to possible access from next row
|
||||
const minWordMatchPos = _minWordMatchPos[patternPos];
|
||||
const maxWordMatchPos = _maxWordMatchPos[patternPos];
|
||||
const nextMaxWordMatchPos = (patternPos + 1 < patternLen ? _maxWordMatchPos[patternPos + 1] : wordLen);
|
||||
|
||||
const score = _doScore(pattern, patternLow, patternPos, patternStart, word, wordLow, wordPos);
|
||||
for (column = minWordMatchPos - wordStart + 1, wordPos = minWordMatchPos; wordPos < nextMaxWordMatchPos; column++, wordPos++) {
|
||||
|
||||
if (patternPos === patternStart && score > 1) {
|
||||
hasStrongFirstMatch = true;
|
||||
let score = Number.MIN_SAFE_INTEGER;
|
||||
let canComeDiag = false;
|
||||
|
||||
if (wordPos <= maxWordMatchPos) {
|
||||
score = _doScore(
|
||||
pattern, patternLow, patternPos, patternStart,
|
||||
word, wordLow, wordPos, wordLen, wordStart,
|
||||
_diag[row - 1][column - 1] === 0,
|
||||
hasStrongFirstMatch
|
||||
);
|
||||
}
|
||||
|
||||
_scores[row][column] = score;
|
||||
let diagScore = 0;
|
||||
if (score !== Number.MAX_SAFE_INTEGER) {
|
||||
canComeDiag = true;
|
||||
diagScore = score + _table[row - 1][column - 1];
|
||||
}
|
||||
|
||||
const diag = _table[row - 1][column - 1] + (score > 1 ? 1 : score);
|
||||
const top = _table[row - 1][column] + -1;
|
||||
const left = _table[row][column - 1] + -1;
|
||||
const canComeLeft = wordPos > minWordMatchPos;
|
||||
const leftScore = canComeLeft ? _table[row][column - 1] + (_diag[row][column - 1] > 0 ? -5 : 0) : 0; // penalty for a gap start
|
||||
|
||||
if (left >= top) {
|
||||
// left or diag
|
||||
if (left > diag) {
|
||||
_table[row][column] = left;
|
||||
_arrows[row][column] = Arrow.Left;
|
||||
} else if (left === diag) {
|
||||
_table[row][column] = left;
|
||||
_arrows[row][column] = Arrow.Left | Arrow.Diag;
|
||||
} else {
|
||||
_table[row][column] = diag;
|
||||
_arrows[row][column] = Arrow.Diag;
|
||||
}
|
||||
const canComeLeftLeft = wordPos > minWordMatchPos + 1 && _diag[row][column - 1] > 0;
|
||||
const leftLeftScore = canComeLeftLeft ? _table[row][column - 2] + (_diag[row][column - 2] > 0 ? -5 : 0) : 0; // penalty for a gap start
|
||||
|
||||
if (canComeLeftLeft && (!canComeLeft || leftLeftScore >= leftScore) && (!canComeDiag || leftLeftScore >= diagScore)) {
|
||||
// always prefer choosing left left to jump over a diagonal because that means a match is earlier in the word
|
||||
_table[row][column] = leftLeftScore;
|
||||
_arrows[row][column] = Arrow.LeftLeft;
|
||||
_diag[row][column] = 0;
|
||||
} else if (canComeLeft && (!canComeDiag || leftScore >= diagScore)) {
|
||||
// always prefer choosing left since that means a match is earlier in the word
|
||||
_table[row][column] = leftScore;
|
||||
_arrows[row][column] = Arrow.Left;
|
||||
_diag[row][column] = 0;
|
||||
} else if (canComeDiag) {
|
||||
_table[row][column] = diagScore;
|
||||
_arrows[row][column] = Arrow.Diag;
|
||||
_diag[row][column] = _diag[row - 1][column - 1] + 1;
|
||||
} else {
|
||||
// top or diag
|
||||
if (top > diag) {
|
||||
_table[row][column] = top;
|
||||
_arrows[row][column] = Arrow.Top;
|
||||
} else if (top === diag) {
|
||||
_table[row][column] = top;
|
||||
_arrows[row][column] = Arrow.Top | Arrow.Diag;
|
||||
} else {
|
||||
_table[row][column] = diag;
|
||||
_arrows[row][column] = Arrow.Diag;
|
||||
}
|
||||
throw new Error(`not possible`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -610,144 +641,152 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
|
||||
printTables(pattern, patternStart, word, wordStart);
|
||||
}
|
||||
|
||||
if (!hasStrongFirstMatch && !firstMatchCanBeWeak) {
|
||||
if (!hasStrongFirstMatch[0] && !firstMatchCanBeWeak) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
_matchesCount = 0;
|
||||
_topScore = -100;
|
||||
_wordStart = wordStart;
|
||||
_firstMatchCanBeWeak = firstMatchCanBeWeak;
|
||||
row--;
|
||||
column--;
|
||||
|
||||
_findAllMatches2(row - 1, column - 1, patternLen === wordLen ? 1 : 0, 0, false);
|
||||
if (_matchesCount === 0) {
|
||||
return undefined;
|
||||
const result: FuzzyScore = [_table[row][column], wordStart];
|
||||
|
||||
let backwardsDiagLength = 0;
|
||||
let maxMatchColumn = 0;
|
||||
|
||||
while (row >= 1) {
|
||||
// Find the column where we go diagonally up
|
||||
let diagColumn = column;
|
||||
do {
|
||||
const arrow = _arrows[row][diagColumn];
|
||||
if (arrow === Arrow.LeftLeft) {
|
||||
diagColumn = diagColumn - 2;
|
||||
} else if (arrow === Arrow.Left) {
|
||||
diagColumn = diagColumn - 1;
|
||||
} else {
|
||||
// found the diagonal
|
||||
break;
|
||||
}
|
||||
} while (diagColumn >= 1);
|
||||
|
||||
// Overturn the "forwards" decision if keeping the "backwards" diagonal would give a better match
|
||||
if (
|
||||
backwardsDiagLength > 1 // only if we would have a contiguous match of 3 characters
|
||||
&& patternLow[patternStart + row - 1] === wordLow[wordStart + column - 1] // only if we can do a contiguous match diagonally
|
||||
&& !isUpperCaseAtPos(diagColumn + wordStart - 1, word, wordLow) // only if the forwards chose diagonal is not an uppercase
|
||||
&& backwardsDiagLength + 1 > _diag[row][diagColumn] // only if our contiguous match would be longer than the "forwards" contiguous match
|
||||
) {
|
||||
diagColumn = column;
|
||||
}
|
||||
|
||||
if (diagColumn === column) {
|
||||
// this is a contiguous match
|
||||
backwardsDiagLength++;
|
||||
} else {
|
||||
backwardsDiagLength = 1;
|
||||
}
|
||||
|
||||
if (!maxMatchColumn) {
|
||||
// remember the last matched column
|
||||
maxMatchColumn = diagColumn;
|
||||
}
|
||||
|
||||
row--;
|
||||
column = diagColumn - 1;
|
||||
result.push(column);
|
||||
}
|
||||
|
||||
return [_topScore, _topMatch2, wordStart];
|
||||
if (wordLen === patternLen) {
|
||||
// the word matches the pattern with all characters!
|
||||
// giving the score a total match boost (to come up ahead other words)
|
||||
result[0] += 2;
|
||||
}
|
||||
|
||||
// Add 1 penalty for each skipped character in the word
|
||||
const skippedCharsCount = maxMatchColumn - patternLen;
|
||||
result[0] -= skippedCharsCount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function _doScore(pattern: string, patternLow: string, patternPos: number, patternStart: number, word: string, wordLow: string, wordPos: number) {
|
||||
if (patternLow[patternPos] !== wordLow[wordPos]) {
|
||||
return -1;
|
||||
function _fillInMaxWordMatchPos(patternLen: number, wordLen: number, patternStart: number, wordStart: number, patternLow: string, wordLow: string) {
|
||||
let patternPos = patternLen - 1;
|
||||
let wordPos = wordLen - 1;
|
||||
while (patternPos >= patternStart && wordPos >= wordStart) {
|
||||
if (patternLow[patternPos] === wordLow[wordPos]) {
|
||||
_maxWordMatchPos[patternPos] = wordPos;
|
||||
patternPos--;
|
||||
}
|
||||
wordPos--;
|
||||
}
|
||||
}
|
||||
|
||||
function _doScore(
|
||||
pattern: string, patternLow: string, patternPos: number, patternStart: number,
|
||||
word: string, wordLow: string, wordPos: number, wordLen: number, wordStart: number,
|
||||
newMatchStart: boolean,
|
||||
outFirstMatchStrong: boolean[],
|
||||
): number {
|
||||
if (patternLow[patternPos] !== wordLow[wordPos]) {
|
||||
return Number.MIN_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
let score = 1;
|
||||
let isGapLocation = false;
|
||||
if (wordPos === (patternPos - patternStart)) {
|
||||
// common prefix: `foobar <-> foobaz`
|
||||
// ^^^^^
|
||||
if (pattern[patternPos] === word[wordPos]) {
|
||||
return 7;
|
||||
} else {
|
||||
return 5;
|
||||
}
|
||||
score = pattern[patternPos] === word[wordPos] ? 7 : 5;
|
||||
|
||||
} else if (isUpperCaseAtPos(wordPos, word, wordLow) && (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))) {
|
||||
// hitting upper-case: `foo <-> forOthers`
|
||||
// ^^ ^
|
||||
if (pattern[patternPos] === word[wordPos]) {
|
||||
return 7;
|
||||
} else {
|
||||
return 5;
|
||||
}
|
||||
score = pattern[patternPos] === word[wordPos] ? 7 : 5;
|
||||
isGapLocation = true;
|
||||
|
||||
} else if (isSeparatorAtPos(wordLow, wordPos) && (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))) {
|
||||
// hitting a separator: `. <-> foo.bar`
|
||||
// ^
|
||||
return 5;
|
||||
score = 5;
|
||||
|
||||
} else if (isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1)) {
|
||||
// post separator: `foo <-> bar_foo`
|
||||
// ^^^
|
||||
return 5;
|
||||
score = 5;
|
||||
isGapLocation = true;
|
||||
}
|
||||
|
||||
if (score > 1 && patternPos === patternStart) {
|
||||
outFirstMatchStrong[0] = true;
|
||||
}
|
||||
|
||||
if (!isGapLocation) {
|
||||
isGapLocation = isUpperCaseAtPos(wordPos, word, wordLow) || isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1);
|
||||
}
|
||||
|
||||
//
|
||||
if (patternPos === patternStart) { // first character in pattern
|
||||
if (wordPos > wordStart) {
|
||||
// the first pattern character would match a word character that is not at the word start
|
||||
// so introduce a penalty to account for the gap preceding this match
|
||||
score -= isGapLocation ? 3 : 5;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
let _matchesCount: number = 0;
|
||||
let _topMatch2: number = 0;
|
||||
let _topScore: number = 0;
|
||||
let _wordStart: number = 0;
|
||||
let _firstMatchCanBeWeak: boolean = false;
|
||||
|
||||
function _findAllMatches2(row: number, column: number, total: number, matches: number, lastMatched: boolean): void {
|
||||
|
||||
if (_matchesCount >= 10 || total < -25) {
|
||||
// stop when having already 10 results, or
|
||||
// when a potential alignment as already 5 gaps
|
||||
return;
|
||||
}
|
||||
|
||||
let simpleMatchCount = 0;
|
||||
|
||||
while (row > 0 && column > 0) {
|
||||
|
||||
const score = _scores[row][column];
|
||||
const arrow = _arrows[row][column];
|
||||
|
||||
if (arrow === Arrow.Left) {
|
||||
// left -> no match, skip a word character
|
||||
column -= 1;
|
||||
if (lastMatched) {
|
||||
total -= 5; // new gap penalty
|
||||
} else if (matches !== 0) {
|
||||
total -= 1; // gap penalty after first match
|
||||
}
|
||||
lastMatched = false;
|
||||
simpleMatchCount = 0;
|
||||
|
||||
} else if (arrow & Arrow.Diag) {
|
||||
|
||||
if (arrow & Arrow.Left) {
|
||||
// left
|
||||
_findAllMatches2(
|
||||
row,
|
||||
column - 1,
|
||||
matches !== 0 ? total - 1 : total, // gap penalty after first match
|
||||
matches,
|
||||
lastMatched
|
||||
);
|
||||
}
|
||||
|
||||
// diag
|
||||
total += score;
|
||||
row -= 1;
|
||||
column -= 1;
|
||||
lastMatched = true;
|
||||
|
||||
// match -> set a 1 at the word pos
|
||||
matches += 2 ** (column + _wordStart);
|
||||
|
||||
// count simple matches and boost a row of
|
||||
// simple matches when they yield in a
|
||||
// strong match.
|
||||
if (score === 1) {
|
||||
simpleMatchCount += 1;
|
||||
|
||||
if (row === 0 && !_firstMatchCanBeWeak) {
|
||||
// when the first match is a weak
|
||||
// match we discard it
|
||||
return undefined;
|
||||
}
|
||||
|
||||
} else {
|
||||
// boost
|
||||
total += 1 + (simpleMatchCount * (score - 1));
|
||||
simpleMatchCount = 0;
|
||||
}
|
||||
|
||||
if (newMatchStart) {
|
||||
// this would be the beginning of a new match (i.e. there would be a gap before this location)
|
||||
score += isGapLocation ? 2 : 0;
|
||||
} else {
|
||||
return undefined;
|
||||
// this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a prefered gap location
|
||||
score += isGapLocation ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
total -= column >= 3 ? 9 : column * 3; // late start penalty
|
||||
|
||||
// dynamically keep track of the current top score
|
||||
// and insert the current best score at head, the rest at tail
|
||||
_matchesCount += 1;
|
||||
if (total > _topScore) {
|
||||
_topScore = total;
|
||||
_topMatch2 = matches;
|
||||
if (wordPos + 1 === wordLen) {
|
||||
// we always penalize gaps, but this gives unfair advantages to a match that would match the last character in the word
|
||||
// so pretend there is a gap after the last character in the word to normalize things
|
||||
score -= isGapLocation ? 3 : 5;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -389,7 +389,7 @@ export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean
|
||||
// - description (if provided)
|
||||
// - query (normalized)
|
||||
// - number of query pieces (i.e. 'hello world' and 'helloworld' are different)
|
||||
// - wether fuzzy matching is enabled or not
|
||||
// - whether fuzzy matching is enabled or not
|
||||
let cacheHash: string;
|
||||
if (description) {
|
||||
cacheHash = `${label}${description}${query.normalized}${Array.isArray(query.values) ? query.values.length : ''}${fuzzy}`;
|
||||
|
||||
@@ -393,15 +393,24 @@ function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern {
|
||||
}
|
||||
|
||||
// common patterns: **/something/else just need endsWith check, something/else just needs and equals check
|
||||
function trivia4and5(path: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern {
|
||||
const nativePath = paths.sep !== paths.posix.sep ? path.replace(ALL_FORWARD_SLASHES, paths.sep) : path;
|
||||
function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern {
|
||||
const usingPosixSep = paths.sep === paths.posix.sep;
|
||||
const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, paths.sep);
|
||||
const nativePathEnd = paths.sep + nativePath;
|
||||
const parsedPattern: ParsedStringPattern = matchPathEnds ? function (path, basename) {
|
||||
return typeof path === 'string' && (path === nativePath || path.endsWith(nativePathEnd)) ? pattern : null;
|
||||
} : function (path, basename) {
|
||||
return typeof path === 'string' && path === nativePath ? pattern : null;
|
||||
const targetPathEnd = paths.posix.sep + targetPath;
|
||||
|
||||
const parsedPattern: ParsedStringPattern = matchPathEnds ? function (testPath, basename) {
|
||||
return typeof testPath === 'string' &&
|
||||
((testPath === nativePath || testPath.endsWith(nativePathEnd))
|
||||
|| !usingPosixSep && (testPath === targetPath || testPath.endsWith(targetPathEnd)))
|
||||
? pattern : null;
|
||||
} : function (testPath, basename) {
|
||||
return typeof testPath === 'string' &&
|
||||
(testPath === nativePath
|
||||
|| (!usingPosixSep && testPath === targetPath))
|
||||
? pattern : null;
|
||||
};
|
||||
parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + path];
|
||||
parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + targetPath];
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { UriComponents } from 'vs/base/common/uri';
|
||||
import { escapeCodicons } from 'vs/base/common/codicons';
|
||||
import { escapeIcons } from 'vs/base/common/iconLabels';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
|
||||
export interface IMarkdownString {
|
||||
@@ -46,9 +46,7 @@ export class MarkdownString implements IMarkdownString {
|
||||
}
|
||||
|
||||
appendText(value: string, newlineStyle: MarkdownStringTextNewlineStyle = MarkdownStringTextNewlineStyle.Paragraph): MarkdownString {
|
||||
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
|
||||
this.value += (this.supportThemeIcons ? escapeCodicons(value) : value)
|
||||
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
|
||||
this.value += escapeMarkdownSyntaxTokens(this.supportThemeIcons ? escapeIcons(value) : value)
|
||||
.replace(/([ \t]+)/g, (_match, g1) => ' '.repeat(g1.length))
|
||||
.replace(/^>/gm, '\\>')
|
||||
.replace(/\n/g, newlineStyle === MarkdownStringTextNewlineStyle.Break ? '\\\n' : '\n\n');
|
||||
@@ -116,6 +114,11 @@ function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function escapeMarkdownSyntaxTokens(text: string): string {
|
||||
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
|
||||
return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&');
|
||||
}
|
||||
|
||||
export function removeMarkdownEscapes(text: string): string {
|
||||
if (!text) {
|
||||
return text;
|
||||
|
||||
161
lib/vscode/src/vs/base/common/iconLabels.ts
Normal file
161
lib/vscode/src/vs/base/common/iconLabels.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { matchesFuzzy, IMatch } from 'vs/base/common/filters';
|
||||
import { ltrim } from 'vs/base/common/strings';
|
||||
|
||||
export const iconStartMarker = '$(';
|
||||
|
||||
const escapeIconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
export function escapeIcons(text: string): string {
|
||||
return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownEscapedIconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
export function markdownEscapeEscapedIcons(text: string): string {
|
||||
// Need to add an extra \ for escaping in markdown
|
||||
return text.replace(markdownEscapedIconsRegex, match => `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownUnescapeIconsRegex = /(\\)?\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi;
|
||||
export function markdownUnescapeIcons(text: string): string {
|
||||
return text.replace(markdownUnescapeIconsRegex, (match, escaped, iconId) => escaped ? match : `$(${iconId})`);
|
||||
}
|
||||
|
||||
const stripIconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi;
|
||||
export function stripIcons(text: string): string {
|
||||
if (text.indexOf(iconStartMarker) === -1) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.replace(stripIconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
|
||||
}
|
||||
|
||||
|
||||
export interface IParsedLabelWithIcons {
|
||||
readonly text: string;
|
||||
readonly iconOffsets?: readonly number[];
|
||||
}
|
||||
|
||||
export function parseLabelWithIcons(text: string): IParsedLabelWithIcons {
|
||||
const firstIconIndex = text.indexOf(iconStartMarker);
|
||||
if (firstIconIndex === -1) {
|
||||
return { text }; // return early if the word does not include an icon
|
||||
}
|
||||
|
||||
return doParseLabelWithIcons(text, firstIconIndex);
|
||||
}
|
||||
|
||||
function doParseLabelWithIcons(text: string, firstIconIndex: number): IParsedLabelWithIcons {
|
||||
const iconOffsets: number[] = [];
|
||||
let textWithoutIcons: string = '';
|
||||
|
||||
function appendChars(chars: string) {
|
||||
if (chars) {
|
||||
textWithoutIcons += chars;
|
||||
|
||||
for (const _ of chars) {
|
||||
iconOffsets.push(iconsOffset); // make sure to fill in icon offsets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let currentIconStart = -1;
|
||||
let currentIconValue: string = '';
|
||||
let iconsOffset = 0;
|
||||
|
||||
let char: string;
|
||||
let nextChar: string;
|
||||
|
||||
let offset = firstIconIndex;
|
||||
const length = text.length;
|
||||
|
||||
// Append all characters until the first icon
|
||||
appendChars(text.substr(0, firstIconIndex));
|
||||
|
||||
// example: $(file-symlink-file) my cool $(other-icon) entry
|
||||
while (offset < length) {
|
||||
char = text[offset];
|
||||
nextChar = text[offset + 1];
|
||||
|
||||
// beginning of icon: some value $( <--
|
||||
if (char === iconStartMarker[0] && nextChar === iconStartMarker[1]) {
|
||||
currentIconStart = offset;
|
||||
|
||||
// if we had a previous potential icon value without
|
||||
// the closing ')', it was actually not an icon and
|
||||
// so we have to add it to the actual value
|
||||
appendChars(currentIconValue);
|
||||
|
||||
currentIconValue = iconStartMarker;
|
||||
|
||||
offset++; // jump over '('
|
||||
}
|
||||
|
||||
// end of icon: some value $(some-icon) <--
|
||||
else if (char === ')' && currentIconStart !== -1) {
|
||||
const currentIconLength = offset - currentIconStart + 1; // +1 to include the closing ')'
|
||||
iconsOffset += currentIconLength;
|
||||
currentIconStart = -1;
|
||||
currentIconValue = '';
|
||||
}
|
||||
|
||||
// within icon
|
||||
else if (currentIconStart !== -1) {
|
||||
// Make sure this is a real icon name
|
||||
if (/^[a-z0-9\-]$/i.test(char)) {
|
||||
currentIconValue += char;
|
||||
} else {
|
||||
// This is not a real icon, treat it as text
|
||||
appendChars(currentIconValue);
|
||||
|
||||
currentIconStart = -1;
|
||||
currentIconValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
// any value outside of icon
|
||||
else {
|
||||
appendChars(char);
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
// if we had a previous potential icon value without
|
||||
// the closing ')', it was actually not an icon and
|
||||
// so we have to add it to the actual value
|
||||
appendChars(currentIconValue);
|
||||
|
||||
return { text: textWithoutIcons, iconOffsets };
|
||||
}
|
||||
|
||||
export function matchesFuzzyIconAware(query: string, target: IParsedLabelWithIcons, enableSeparateSubstringMatching = false): IMatch[] | null {
|
||||
const { text, iconOffsets } = target;
|
||||
|
||||
// Return early if there are no icon markers in the word to match against
|
||||
if (!iconOffsets || iconOffsets.length === 0) {
|
||||
return matchesFuzzy(query, text, enableSeparateSubstringMatching);
|
||||
}
|
||||
|
||||
// Trim the word to match against because it could have leading
|
||||
// whitespace now if the word started with an icon
|
||||
const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
|
||||
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
|
||||
|
||||
// match on value without icon
|
||||
const matches = matchesFuzzy(query, wordToMatchAgainstWithoutIconsTrimmed, enableSeparateSubstringMatching);
|
||||
|
||||
// Map matches back to offsets with icon and trimming
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
|
||||
match.start += iconOffset;
|
||||
match.end += iconOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
@@ -22,6 +22,10 @@ export namespace Iterable {
|
||||
return iterable || _empty;
|
||||
}
|
||||
|
||||
export function isEmpty<T>(iterable: Iterable<T> | undefined | null): boolean {
|
||||
return !iterable || iterable[Symbol.iterator]().next().done === true;
|
||||
}
|
||||
|
||||
export function first<T>(iterable: Iterable<T>): T | undefined {
|
||||
return iterable[Symbol.iterator]().next().value;
|
||||
}
|
||||
@@ -35,6 +39,8 @@ export namespace Iterable {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function filter<T, R extends T>(iterable: Iterable<T>, predicate: (t: T) => t is R): Iterable<R>;
|
||||
export function filter<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): Iterable<T>;
|
||||
export function* filter<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): Iterable<T> {
|
||||
for (const element of iterable) {
|
||||
if (predicate(element)) {
|
||||
@@ -57,6 +63,33 @@ export namespace Iterable {
|
||||
}
|
||||
}
|
||||
|
||||
export function* concatNested<T>(iterables: Iterable<Iterable<T>>): Iterable<T> {
|
||||
for (const iterable of iterables) {
|
||||
for (const element of iterable) {
|
||||
yield element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterable slice of the array, with the same semantics as `array.slice()`.
|
||||
*/
|
||||
export function* slice<T>(iterable: ReadonlyArray<T>, from: number, to = iterable.length): Iterable<T> {
|
||||
if (from < 0) {
|
||||
from += iterable.length;
|
||||
}
|
||||
|
||||
if (to < 0) {
|
||||
to += iterable.length;
|
||||
} else if (to > iterable.length) {
|
||||
to = iterable.length;
|
||||
}
|
||||
|
||||
for (; from < to; from++) {
|
||||
yield iterable[from];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes `atMost` elements from iterable and returns the consumed elements,
|
||||
* and an iterable for the rest of the elements.
|
||||
|
||||
@@ -9,11 +9,12 @@ import { startsWithIgnoreCase, rtrim } from 'vs/base/common/strings';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { isEqual, basename, relativePath } from 'vs/base/common/resources';
|
||||
import { hasDriveLetter, isRootOrDriveLetter } from 'vs/base/common/extpath';
|
||||
|
||||
export interface IWorkspaceFolderProvider {
|
||||
getWorkspaceFolder(resource: URI): { uri: URI, name?: string } | null;
|
||||
getWorkspaceFolder(resource: URI): { uri: URI, name?: string; } | null;
|
||||
getWorkspace(): {
|
||||
folders: { uri: URI, name?: string }[];
|
||||
folders: { uri: URI, name?: string; }[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,21 +85,13 @@ export function getBaseLabel(resource: URI | string | undefined): string | undef
|
||||
const base = basename(resource) || (resource.scheme === Schemas.file ? resource.fsPath : resource.path) /* can be empty string if '/' is passed in */;
|
||||
|
||||
// convert c: => C:
|
||||
if (hasDriveLetter(base)) {
|
||||
if (isWindows && isRootOrDriveLetter(base)) {
|
||||
return normalizeDriveLetter(base);
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
function hasDriveLetter(path: string): boolean {
|
||||
return !!(isWindows && path && path[1] === ':');
|
||||
}
|
||||
|
||||
export function extractDriveLetter(path: string): string | undefined {
|
||||
return hasDriveLetter(path) ? path[0] : undefined;
|
||||
}
|
||||
|
||||
export function normalizeDriveLetter(path: string): string {
|
||||
if (hasDriveLetter(path)) {
|
||||
return path.charAt(0).toUpperCase() + path.slice(1);
|
||||
@@ -107,7 +100,7 @@ export function normalizeDriveLetter(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
let normalizedUserHomeCached: { original: string; normalized: string } = Object.create(null);
|
||||
let normalizedUserHomeCached: { original: string; normalized: string; } = Object.create(null);
|
||||
export function tildify(path: string, userHome: string): string {
|
||||
if (isWindows || !path || !userHome) {
|
||||
return path; // unsupported
|
||||
@@ -286,7 +279,7 @@ interface ISegment {
|
||||
* @param value string to which templating is applied
|
||||
* @param values the values of the templates to use
|
||||
*/
|
||||
export function template(template: string, values: { [key: string]: string | ISeparator | undefined | null } = Object.create(null)): string {
|
||||
export function template(template: string, values: { [key: string]: string | ISeparator | undefined | null; } = Object.create(null)): string {
|
||||
const segments: ISegment[] = [];
|
||||
|
||||
let inVariable = false;
|
||||
@@ -390,7 +383,7 @@ export function unmnemonicLabel(label: string): string {
|
||||
/**
|
||||
* Splits a path in name and parent path, supporting both '/' and '\'
|
||||
*/
|
||||
export function splitName(fullPath: string): { name: string, parentPath: string } {
|
||||
export function splitName(fullPath: string): { name: string, parentPath: string; } {
|
||||
const p = fullPath.indexOf('/') !== -1 ? posix : win32;
|
||||
const name = p.basename(fullPath);
|
||||
const parentPath = p.dirname(fullPath);
|
||||
|
||||
@@ -14,34 +14,53 @@ import { Iterable } from 'vs/base/common/iterator';
|
||||
* extend Disposable or use a DisposableStore. This means there are a lot of false positives.
|
||||
*/
|
||||
const TRACK_DISPOSABLES = false;
|
||||
let disposableTracker: IDisposableTracker | null = null;
|
||||
|
||||
const __is_disposable_tracked__ = '__is_disposable_tracked__';
|
||||
|
||||
function markTracked<T extends IDisposable>(x: T): void {
|
||||
if (!TRACK_DISPOSABLES) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (x && x !== Disposable.None) {
|
||||
try {
|
||||
(x as any)[__is_disposable_tracked__] = true;
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
export interface IDisposableTracker {
|
||||
trackDisposable(x: IDisposable): void;
|
||||
markTracked(x: IDisposable): void;
|
||||
}
|
||||
|
||||
function trackDisposable<T extends IDisposable>(x: T): T {
|
||||
if (!TRACK_DISPOSABLES) {
|
||||
export function setDisposableTracker(tracker: IDisposableTracker | null): void {
|
||||
disposableTracker = tracker;
|
||||
}
|
||||
|
||||
if (TRACK_DISPOSABLES) {
|
||||
const __is_disposable_tracked__ = '__is_disposable_tracked__';
|
||||
disposableTracker = new class implements IDisposableTracker {
|
||||
trackDisposable(x: IDisposable): void {
|
||||
const stack = new Error('Potentially leaked disposable').stack!;
|
||||
setTimeout(() => {
|
||||
if (!(x as any)[__is_disposable_tracked__]) {
|
||||
console.log(stack);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
markTracked(x: IDisposable): void {
|
||||
if (x && x !== Disposable.None) {
|
||||
try {
|
||||
(x as any)[__is_disposable_tracked__] = true;
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function markTracked<T extends IDisposable>(x: T): void {
|
||||
if (!disposableTracker) {
|
||||
return;
|
||||
}
|
||||
disposableTracker.markTracked(x);
|
||||
}
|
||||
|
||||
export function trackDisposable<T extends IDisposable>(x: T): T {
|
||||
if (!disposableTracker) {
|
||||
return x;
|
||||
}
|
||||
|
||||
const stack = new Error('Potentially leaked disposable').stack!;
|
||||
setTimeout(() => {
|
||||
if (!(x as any)[__is_disposable_tracked__]) {
|
||||
console.log(stack);
|
||||
}
|
||||
}, 3000);
|
||||
disposableTracker.trackDisposable(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -98,7 +117,7 @@ export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | un
|
||||
|
||||
export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
|
||||
disposables.forEach(markTracked);
|
||||
return trackDisposable({ dispose: () => dispose(disposables) });
|
||||
return toDisposable(() => dispose(disposables));
|
||||
}
|
||||
|
||||
export function toDisposable(fn: () => void): IDisposable {
|
||||
|
||||
@@ -60,6 +60,8 @@ export namespace Schemas {
|
||||
|
||||
export const vscodeNotebookCell = 'vscode-notebook-cell';
|
||||
|
||||
export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata';
|
||||
|
||||
export const vscodeSettings = 'vscode-settings';
|
||||
|
||||
export const webviewPanel = 'webview-panel';
|
||||
@@ -148,8 +150,8 @@ class FileAccessImpl {
|
||||
* **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context.
|
||||
*/
|
||||
asBrowserUri(uri: URI): URI;
|
||||
asBrowserUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
|
||||
asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
asBrowserUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }, __forceCodeFileUri?: boolean): URI;
|
||||
asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }, __forceCodeFileUri?: boolean): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
// Handle remote URIs via `RemoteAuthorities`
|
||||
@@ -158,37 +160,23 @@ class FileAccessImpl {
|
||||
}
|
||||
|
||||
// Only convert the URI if we are in a native context and it has `file:` scheme
|
||||
if (platform.isElectronSandboxed && platform.isNative && uri.scheme === Schemas.file) {
|
||||
return this.toCodeFileUri(uri);
|
||||
// and we have explicitly enabled the conversion (sandbox, or ENABLE_VSCODE_BROWSER_CODE_LOADING)
|
||||
if (platform.isNative && (__forceCodeFileUri || platform.isPreferringBrowserCodeLoad) && uri.scheme === Schemas.file) {
|
||||
return uri.with({
|
||||
scheme: Schemas.vscodeFileResource,
|
||||
// We need to provide an authority here so that it can serve
|
||||
// as origin for network and loading matters in chromium.
|
||||
// If the URI is not coming with an authority already, we
|
||||
// add our own
|
||||
authority: uri.authority || this.FALLBACK_AUTHORITY,
|
||||
query: null,
|
||||
fragment: null
|
||||
});
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO@bpasero remove me eventually when vscode-file is adopted everywhere
|
||||
*/
|
||||
_asCodeFileUri(uri: URI): URI;
|
||||
_asCodeFileUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
|
||||
_asCodeFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
return this.toCodeFileUri(uri);
|
||||
}
|
||||
|
||||
private toCodeFileUri(uri: URI): URI {
|
||||
return uri.with({
|
||||
scheme: Schemas.vscodeFileResource,
|
||||
// We need to provide an authority here so that it can serve
|
||||
// as origin for network and loading matters in chromium.
|
||||
// If the URI is not coming with an authority already, we
|
||||
// add our own
|
||||
authority: uri.authority || this.FALLBACK_AUTHORITY,
|
||||
query: null,
|
||||
fragment: null
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `file` URI to use in contexts where node.js
|
||||
* is responsible for loading.
|
||||
|
||||
@@ -43,7 +43,7 @@ const CHAR_QUESTION_MARK = 63; /* ? */
|
||||
|
||||
class ErrorInvalidArgType extends Error {
|
||||
code: 'ERR_INVALID_ARG_TYPE';
|
||||
constructor(name: string, expected: string, actual: any) {
|
||||
constructor(name: string, expected: string, actual: unknown) {
|
||||
// determiner: 'must be' or 'must not be'
|
||||
let determiner;
|
||||
if (typeof expected === 'string' && expected.indexOf('not ') === 0) {
|
||||
@@ -215,7 +215,7 @@ export const win32: IPath = {
|
||||
// absolute path, get cwd for that drive, or the process cwd if
|
||||
// the drive cwd is not available. We're sure the device is not
|
||||
// a UNC path at this points, because UNC paths are always absolute.
|
||||
path = (process.env as any)[`=${resolvedDevice}`] || process.cwd();
|
||||
path = process.env[`=${resolvedDevice}`] || process.cwd();
|
||||
|
||||
// Verify that a cwd was found and that it actually points
|
||||
// to our drive. If not, default to the drive's root.
|
||||
|
||||
12
lib/vscode/src/vs/base/common/performance.d.ts
vendored
12
lib/vscode/src/vs/base/common/performance.d.ts
vendored
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface PerformanceEntry {
|
||||
export interface PerformanceMark {
|
||||
readonly name: string;
|
||||
readonly startTime: number;
|
||||
}
|
||||
@@ -11,12 +11,6 @@ export interface PerformanceEntry {
|
||||
export function mark(name: string): void;
|
||||
|
||||
/**
|
||||
* All entries filtered by type and sorted by `startTime`.
|
||||
* Returns all marks, sorted by `startTime`.
|
||||
*/
|
||||
export function getEntries(): PerformanceEntry[];
|
||||
|
||||
export function getDuration(from: string, to: string): number;
|
||||
|
||||
type ExportData = any[];
|
||||
export function importEntries(data: ExportData): void;
|
||||
export function exportEntries(): ExportData;
|
||||
export function getMarks(): PerformanceMark[];
|
||||
|
||||
@@ -7,66 +7,88 @@
|
||||
|
||||
//@ts-check
|
||||
|
||||
function _factory(sharedObj) {
|
||||
/**
|
||||
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
|
||||
*/
|
||||
function _definePolyfillMarks(timeOrigin) {
|
||||
|
||||
sharedObj.MonacoPerformanceMarks = sharedObj.MonacoPerformanceMarks || [];
|
||||
|
||||
const _dataLen = 2;
|
||||
const _nativeMark = typeof performance === 'object' && typeof performance.mark === 'function' ? performance.mark.bind(performance) : () => { };
|
||||
|
||||
function importEntries(entries) {
|
||||
sharedObj.MonacoPerformanceMarks.splice(0, 0, ...entries);
|
||||
const _data = [];
|
||||
if (typeof timeOrigin === 'number') {
|
||||
_data.push('code/timeOrigin', timeOrigin);
|
||||
}
|
||||
|
||||
function exportEntries() {
|
||||
return sharedObj.MonacoPerformanceMarks.slice(0);
|
||||
function mark(name) {
|
||||
_data.push(name, Date.now());
|
||||
}
|
||||
|
||||
function getEntries() {
|
||||
function getMarks() {
|
||||
const result = [];
|
||||
const entries = sharedObj.MonacoPerformanceMarks;
|
||||
for (let i = 0; i < entries.length; i += _dataLen) {
|
||||
for (let i = 0; i < _data.length; i += 2) {
|
||||
result.push({
|
||||
name: entries[i],
|
||||
startTime: entries[i + 1],
|
||||
name: _data[i],
|
||||
startTime: _data[i + 1],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return { mark, getMarks };
|
||||
}
|
||||
|
||||
function getDuration(from, to) {
|
||||
const entries = sharedObj.MonacoPerformanceMarks;
|
||||
let target = to;
|
||||
let endIndex = 0;
|
||||
for (let i = entries.length - _dataLen; i >= 0; i -= _dataLen) {
|
||||
if (entries[i] === target) {
|
||||
if (target === to) {
|
||||
// found `to` (end of interval)
|
||||
endIndex = i;
|
||||
target = from;
|
||||
} else {
|
||||
// found `from` (start of interval)
|
||||
return entries[endIndex + 1] - entries[i + 1];
|
||||
/**
|
||||
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
|
||||
*/
|
||||
function _define() {
|
||||
|
||||
if (typeof performance === 'object' && typeof performance.mark === 'function') {
|
||||
// in a browser context, reuse performance-util
|
||||
|
||||
if (typeof performance.timeOrigin !== 'number' && !performance.timing) {
|
||||
// safari & webworker: because there is no timeOrigin and no workaround
|
||||
// we use the `Date.now`-based polyfill.
|
||||
return _definePolyfillMarks();
|
||||
|
||||
} else {
|
||||
// use "native" performance for mark and getMarks
|
||||
return {
|
||||
mark(name) {
|
||||
performance.mark(name);
|
||||
},
|
||||
getMarks() {
|
||||
let timeOrigin = performance.timeOrigin;
|
||||
if (typeof timeOrigin !== 'number') {
|
||||
// safari: there is no timerOrigin but in renderers there is the timing-property
|
||||
// see https://bugs.webkit.org/show_bug.cgi?id=174862
|
||||
timeOrigin = performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart;
|
||||
}
|
||||
const result = [{ name: 'code/timeOrigin', startTime: Math.round(timeOrigin) }];
|
||||
for (const entry of performance.getEntriesByType('mark')) {
|
||||
result.push({
|
||||
name: entry.name,
|
||||
startTime: Math.round(timeOrigin + entry.startTime)
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
|
||||
} else if (typeof process === 'object') {
|
||||
// node.js: use the normal polyfill but add the timeOrigin
|
||||
// from the node perf_hooks API as very first mark
|
||||
const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin);
|
||||
return _definePolyfillMarks(timeOrigin);
|
||||
|
||||
} else {
|
||||
// unknown environment
|
||||
console.trace('perf-util loaded in UNKNOWN environment');
|
||||
return _definePolyfillMarks();
|
||||
}
|
||||
}
|
||||
|
||||
function mark(name) {
|
||||
sharedObj.MonacoPerformanceMarks.push(name, Date.now());
|
||||
_nativeMark(name);
|
||||
function _factory(sharedObj) {
|
||||
if (!sharedObj.MonacoPerformanceMarks) {
|
||||
sharedObj.MonacoPerformanceMarks = _define();
|
||||
}
|
||||
|
||||
const exports = {
|
||||
mark: mark,
|
||||
getEntries: getEntries,
|
||||
getDuration: getDuration,
|
||||
importEntries: importEntries,
|
||||
exportEntries: exportEntries
|
||||
};
|
||||
|
||||
return exports;
|
||||
return sharedObj.MonacoPerformanceMarks;
|
||||
}
|
||||
|
||||
// This module can be loaded in an amd and commonjs-context.
|
||||
@@ -92,5 +114,6 @@ if (typeof define === 'function') {
|
||||
// commonjs
|
||||
module.exports = _factory(sharedObj);
|
||||
} else {
|
||||
console.trace('perf-util defined in UNKNOWN context (neither requirejs or commonjs)');
|
||||
sharedObj.perf = _factory(sharedObj);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const LANGUAGE_DEFAULT = 'en';
|
||||
let _isWindows = false;
|
||||
let _isMacintosh = false;
|
||||
let _isLinux = false;
|
||||
let _isLinuxSnap = false;
|
||||
let _isNative = false;
|
||||
let _isWeb = false;
|
||||
let _isIOS = false;
|
||||
@@ -61,6 +62,26 @@ if (typeof process !== 'undefined') {
|
||||
|
||||
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
|
||||
export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed;
|
||||
export const browserCodeLoadingCacheStrategy: 'none' | 'code' | 'bypassHeatCheck' | 'bypassHeatCheckAndEagerCompile' | undefined = (() => {
|
||||
|
||||
// Always enabled when sandbox is enabled
|
||||
if (isElectronSandboxed) {
|
||||
return 'bypassHeatCheck';
|
||||
}
|
||||
|
||||
// Otherwise, only enabled conditionally
|
||||
const env = nodeProcess?.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'];
|
||||
if (typeof env === 'string') {
|
||||
if (env === 'none' || env === 'code' || env === 'bypassHeatCheck' || env === 'bypassHeatCheckAndEagerCompile') {
|
||||
return env;
|
||||
}
|
||||
|
||||
return 'bypassHeatCheck';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})();
|
||||
export const isPreferringBrowserCodeLoad = typeof browserCodeLoadingCacheStrategy === 'string';
|
||||
|
||||
// Web environment
|
||||
if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
@@ -91,6 +112,7 @@ else if (typeof nodeProcess === 'object') {
|
||||
_isWindows = (nodeProcess.platform === 'win32');
|
||||
_isMacintosh = (nodeProcess.platform === 'darwin');
|
||||
_isLinux = (nodeProcess.platform === 'linux');
|
||||
_isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION'];
|
||||
_locale = LANGUAGE_DEFAULT;
|
||||
_language = LANGUAGE_DEFAULT;
|
||||
const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
|
||||
@@ -140,6 +162,7 @@ if (_isMacintosh) {
|
||||
export const isWindows = _isWindows;
|
||||
export const isMacintosh = _isMacintosh;
|
||||
export const isLinux = _isLinux;
|
||||
export const isLinuxSnap = _isLinuxSnap;
|
||||
export const isNative = _isNative;
|
||||
export const isWeb = _isWeb;
|
||||
export const isIOS = _isIOS;
|
||||
|
||||
@@ -108,7 +108,6 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
|
||||
}, {} as Record<string, boolean>);
|
||||
const keysToRemove = [
|
||||
/^ELECTRON_.+$/,
|
||||
/^GOOGLE_API_KEY$/,
|
||||
/^VSCODE_.+$/,
|
||||
/^SNAP(|_.*)$/,
|
||||
/^GDK_PIXBUF_.+$/,
|
||||
|
||||
@@ -13,6 +13,8 @@ export const enum ScrollbarVisibility {
|
||||
}
|
||||
|
||||
export interface ScrollEvent {
|
||||
inSmoothScrolling: boolean;
|
||||
|
||||
oldWidth: number;
|
||||
oldScrollWidth: number;
|
||||
oldScrollLeft: number;
|
||||
@@ -132,7 +134,7 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
|
||||
);
|
||||
}
|
||||
|
||||
public createScrollEvent(previous: ScrollState): ScrollEvent {
|
||||
public createScrollEvent(previous: ScrollState, inSmoothScrolling: boolean): ScrollEvent {
|
||||
const widthChanged = (this.width !== previous.width);
|
||||
const scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth);
|
||||
const scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft);
|
||||
@@ -142,6 +144,7 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
|
||||
const scrollTopChanged = (this.scrollTop !== previous.scrollTop);
|
||||
|
||||
return {
|
||||
inSmoothScrolling: inSmoothScrolling,
|
||||
oldWidth: previous.width,
|
||||
oldScrollWidth: previous.scrollWidth,
|
||||
oldScrollLeft: previous.scrollLeft,
|
||||
@@ -242,7 +245,7 @@ export class Scrollable extends Disposable {
|
||||
|
||||
public setScrollDimensions(dimensions: INewScrollDimensions, useRawScrollPositions: boolean): void {
|
||||
const newState = this._state.withScrollDimensions(dimensions, useRawScrollPositions);
|
||||
this._setState(newState);
|
||||
this._setState(newState, Boolean(this._smoothScrolling));
|
||||
|
||||
// Validate outstanding animated scroll position target
|
||||
if (this._smoothScrolling) {
|
||||
@@ -279,10 +282,10 @@ export class Scrollable extends Disposable {
|
||||
this._smoothScrolling = null;
|
||||
}
|
||||
|
||||
this._setState(newState);
|
||||
this._setState(newState, false);
|
||||
}
|
||||
|
||||
public setScrollPositionSmooth(update: INewScrollPosition): void {
|
||||
public setScrollPositionSmooth(update: INewScrollPosition, reuseAnimation?: boolean): void {
|
||||
if (this._smoothScrollDuration === 0) {
|
||||
// Smooth scrolling not supported.
|
||||
return this.setScrollPositionNow(update);
|
||||
@@ -302,8 +305,12 @@ export class Scrollable extends Disposable {
|
||||
// No need to interrupt or extend the current animation since we're going to the same place
|
||||
return;
|
||||
}
|
||||
|
||||
const newSmoothScrolling = this._smoothScrolling.combine(this._state, validTarget, this._smoothScrollDuration);
|
||||
let newSmoothScrolling: SmoothScrollingOperation;
|
||||
if (reuseAnimation) {
|
||||
newSmoothScrolling = new SmoothScrollingOperation(this._smoothScrolling.from, validTarget, this._smoothScrolling.startTime, this._smoothScrolling.duration);
|
||||
} else {
|
||||
newSmoothScrolling = this._smoothScrolling.combine(this._state, validTarget, this._smoothScrollDuration);
|
||||
}
|
||||
this._smoothScrolling.dispose();
|
||||
this._smoothScrolling = newSmoothScrolling;
|
||||
} else {
|
||||
@@ -330,7 +337,7 @@ export class Scrollable extends Disposable {
|
||||
const update = this._smoothScrolling.tick();
|
||||
const newState = this._state.withScrollPosition(update);
|
||||
|
||||
this._setState(newState);
|
||||
this._setState(newState, true);
|
||||
|
||||
if (!this._smoothScrolling) {
|
||||
// Looks like someone canceled the smooth scrolling
|
||||
@@ -354,14 +361,14 @@ export class Scrollable extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private _setState(newState: ScrollState): void {
|
||||
private _setState(newState: ScrollState, inSmoothScrolling: boolean): void {
|
||||
const oldState = this._state;
|
||||
if (oldState.equals(newState)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
this._state = newState;
|
||||
this._onScroll.fire(this._state.createScrollEvent(oldState));
|
||||
this._onScroll.fire(this._state.createScrollEvent(oldState, inSmoothScrolling));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,17 +411,17 @@ export class SmoothScrollingOperation {
|
||||
public readonly from: ISmoothScrollPosition;
|
||||
public to: ISmoothScrollPosition;
|
||||
public readonly duration: number;
|
||||
private readonly _startTime: number;
|
||||
public readonly startTime: number;
|
||||
public animationFrameDisposable: IDisposable | null;
|
||||
|
||||
private scrollLeft!: IAnimation;
|
||||
private scrollTop!: IAnimation;
|
||||
|
||||
protected constructor(from: ISmoothScrollPosition, to: ISmoothScrollPosition, startTime: number, duration: number) {
|
||||
constructor(from: ISmoothScrollPosition, to: ISmoothScrollPosition, startTime: number, duration: number) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.duration = duration;
|
||||
this._startTime = startTime;
|
||||
this.startTime = startTime;
|
||||
|
||||
this.animationFrameDisposable = null;
|
||||
|
||||
@@ -460,7 +467,7 @@ export class SmoothScrollingOperation {
|
||||
}
|
||||
|
||||
protected _tick(now: number): SmoothScrollingUpdate {
|
||||
const completion = (now - this._startTime) / this.duration;
|
||||
const completion = (now - this.startTime) / this.duration;
|
||||
|
||||
if (completion < 1) {
|
||||
const newScrollLeft = this.scrollLeft(completion);
|
||||
|
||||
@@ -35,6 +35,6 @@ export class StopWatch {
|
||||
}
|
||||
|
||||
private _now(): number {
|
||||
return this._highResolution ? globals.performance.now() : new Date().getTime();
|
||||
return this._highResolution ? globals.performance.now() : Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
/**
|
||||
@@ -229,7 +230,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
|
||||
// flowing: directly send the data to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.data.forEach(listener => listener(data));
|
||||
this.emitData(data);
|
||||
}
|
||||
|
||||
// not yet flowing: buffer data until flowing
|
||||
@@ -250,7 +251,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
|
||||
// flowing: directly send the error to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
this.emitError(error);
|
||||
}
|
||||
|
||||
// not yet flowing: buffer errors until flowing
|
||||
@@ -273,7 +274,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
|
||||
// flowing: send end event to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
this.emitEnd();
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
@@ -284,6 +285,22 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private emitData(data: T): void {
|
||||
this.listeners.data.slice(0).forEach(listener => listener(data)); // slice to avoid listener mutation from delivering event
|
||||
}
|
||||
|
||||
private emitError(error: Error): void {
|
||||
if (this.listeners.error.length === 0) {
|
||||
onUnexpectedError(error); // nobody listened to this error so we log it as unexpected
|
||||
} else {
|
||||
this.listeners.error.slice(0).forEach(listener => listener(error)); // slice to avoid listener mutation from delivering event
|
||||
}
|
||||
}
|
||||
|
||||
private emitEnd(): void {
|
||||
this.listeners.end.slice(0).forEach(listener => listener()); // slice to avoid listener mutation from delivering event
|
||||
}
|
||||
|
||||
on(event: 'data', callback: (data: T) => void): void;
|
||||
on(event: 'error', callback: (err: Error) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
@@ -361,7 +378,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
if (this.buffer.data.length > 0) {
|
||||
const fullDataBuffer = this.reducer(this.buffer.data);
|
||||
|
||||
this.listeners.data.forEach(listener => listener(fullDataBuffer));
|
||||
this.emitData(fullDataBuffer);
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
|
||||
@@ -375,7 +392,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
private flowErrors(): void {
|
||||
if (this.listeners.error.length > 0) {
|
||||
for (const error of this.buffer.error) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
this.emitError(error);
|
||||
}
|
||||
|
||||
this.buffer.error.length = 0;
|
||||
@@ -384,7 +401,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
|
||||
private flowEnd(): boolean {
|
||||
if (this.state.ended) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
this.emitEnd();
|
||||
|
||||
return this.listeners.end.length > 0;
|
||||
}
|
||||
@@ -478,9 +495,13 @@ export function consumeStream<T>(stream: ReadableStreamEvents<T>, reducer: IRedu
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: T[] = [];
|
||||
|
||||
stream.on('data', data => chunks.push(data));
|
||||
stream.on('error', error => reject(error));
|
||||
stream.on('end', () => resolve(reducer(chunks)));
|
||||
|
||||
// Adding the `data` listener will turn the stream
|
||||
// into flowing mode. As such it is important to
|
||||
// add this listener last (DO NOT CHANGE!)
|
||||
stream.on('data', data => chunks.push(data));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -574,3 +595,40 @@ export function transform<Original, Transformed>(stream: ReadableStreamEvents<Or
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export interface IReadableStreamObservable {
|
||||
|
||||
/**
|
||||
* A promise to await the `end` or `error` event
|
||||
* of a stream.
|
||||
*/
|
||||
errorOrEnd: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to observe a stream for certain events through
|
||||
* a promise based API.
|
||||
*/
|
||||
export function observe(stream: ReadableStream<unknown>): IReadableStreamObservable {
|
||||
|
||||
// A stream is closed when it ended or errord
|
||||
// We install this listener right from the
|
||||
// beginning to catch the events early.
|
||||
const errorOrEnd = Promise.race([
|
||||
new Promise<void>(resolve => stream.on('end', () => resolve())),
|
||||
new Promise<void>(resolve => stream.on('error', () => resolve()))
|
||||
]);
|
||||
|
||||
return {
|
||||
errorOrEnd(): Promise<void> {
|
||||
|
||||
// We need to ensure the stream is flowing so that our
|
||||
// listeners are getting triggered. It is possible that
|
||||
// the stream is not flowing because no `data` listener
|
||||
// was attached yet.
|
||||
stream.resume();
|
||||
|
||||
return errorOrEnd;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export class URI implements UriComponents {
|
||||
&& typeof (<URI>thing).path === 'string'
|
||||
&& typeof (<URI>thing).query === 'string'
|
||||
&& typeof (<URI>thing).scheme === 'string'
|
||||
&& typeof (<URI>thing).fsPath === 'function'
|
||||
&& typeof (<URI>thing).fsPath === 'string'
|
||||
&& typeof (<URI>thing).with === 'function'
|
||||
&& typeof (<URI>thing).toString === 'function';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user