mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 00:04:14 +08:00
copy vscode sources, add base/common/yaml (#1468)
* copy vscode sources, add base/common/yaml * fix tests (due to change of toStringEdit)
This commit is contained in:
parent
cbb7fcf894
commit
3f4cb59fc5
@ -160,6 +160,7 @@ async function doIt(filepaths: string[]) {
|
||||
'vs/base/common/themables.ts',
|
||||
'vs/base/common/uri.ts',
|
||||
'vs/base/common/uuid.ts',
|
||||
'vs/base/common/yaml.ts',
|
||||
'vs/editor/common/core/ranges/offsetRange.ts',
|
||||
'vs/editor/common/core/wordHelper.ts',
|
||||
'vs/editor/common/model/prefixSumComputer.ts',
|
||||
|
||||
@ -55,20 +55,14 @@ class Point3D {
|
||||
expect(res).toBeTypeOf('object');
|
||||
const result = res as Exclude<typeof res, string | undefined>;
|
||||
expect(result[0].rebasedEditIndex).toBe(1);
|
||||
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`
|
||||
"[68, 76) -> "
|
||||
this.z = z;""
|
||||
`);
|
||||
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`"[68, 76) -> "\\n\\t\\tthis.z = z;""`);
|
||||
}
|
||||
{
|
||||
const res = tryRebase(originalDocument, undefined, decomposeStringEdit(suggestedEdit).edits, [], userEdit, currentDocument, [], 'lenient', tracer);
|
||||
expect(res).toBeTypeOf('object');
|
||||
const result = res as Exclude<typeof res, string | undefined>;
|
||||
expect(result[0].rebasedEditIndex).toBe(1);
|
||||
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`
|
||||
"[68, 76) -> "
|
||||
this.z = z;""
|
||||
`);
|
||||
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`"[68, 76) -> "\\n\\t\\tthis.z = z;""`);
|
||||
}
|
||||
});
|
||||
|
||||
@ -227,12 +221,7 @@ int main()
|
||||
const result = res as Exclude<typeof res, string | undefined>;
|
||||
expect(result[0].rebasedEditIndex).toBe(0);
|
||||
expect(StringEdit.single(result[0].rebasedEdit).apply(currentDocument)).toStrictEqual(final);
|
||||
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`
|
||||
"[87, 164) -> "esult42.empty())
|
||||
return result42.size();
|
||||
result42.clear();
|
||||
return result42""
|
||||
`);
|
||||
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`"[87, 164) -> "esult42.empty())\\n return result42.size();\\n result42.clear();\\n return result42""`);
|
||||
}
|
||||
{
|
||||
const res = tryRebase(originalDocument, undefined, suggestedEdit.replacements, [], userEdit, currentDocument, [], 'lenient', tracer);
|
||||
@ -240,12 +229,7 @@ int main()
|
||||
const result = res as Exclude<typeof res, string | undefined>;
|
||||
expect(result[0].rebasedEditIndex).toBe(0);
|
||||
expect(StringEdit.single(result[0].rebasedEdit).apply(currentDocument)).toStrictEqual(final);
|
||||
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`
|
||||
"[87, 164) -> "esult42.empty())
|
||||
return result42.size();
|
||||
result42.clear();
|
||||
return result42""
|
||||
`);
|
||||
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`"[87, 164) -> "esult42.empty())\\n return result42.size();\\n result42.clear();\\n return result42""`);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -346,16 +330,10 @@ class Point3D {
|
||||
|
||||
const strict = tryRebaseStringEdits(text, edit, base, 'strict')?.removeCommonSuffixAndPrefix(current);
|
||||
expect(strict?.apply(current)).toStrictEqual(final);
|
||||
expect(strict?.replacements.toString()).toMatchInlineSnapshot(`
|
||||
"[69, 69) -> " this.z = z;
|
||||
""
|
||||
`);
|
||||
expect(strict?.replacements.toString()).toMatchInlineSnapshot(`"[69, 69) -> "\\t\\tthis.z = z;\\n""`);
|
||||
const lenient = tryRebaseStringEdits(text, edit, base, 'lenient')?.removeCommonSuffixAndPrefix(current);
|
||||
expect(lenient?.apply(current)).toStrictEqual(final);
|
||||
expect(lenient?.replacements.toString()).toMatchInlineSnapshot(`
|
||||
"[69, 69) -> " this.z = z;
|
||||
""
|
||||
`);
|
||||
expect(lenient?.replacements.toString()).toMatchInlineSnapshot(`"[69, 69) -> "\\t\\tthis.z = z;\\n""`);
|
||||
});
|
||||
test('insert 2 and 2 edits', () => {
|
||||
const text = `
|
||||
|
||||
@ -14,7 +14,7 @@ import { RootedLineEdit } from './rootedLineEdit';
|
||||
export class RootedEdit<TEdit extends BaseStringEdit<BaseStringReplacement<any>, any> = StringEdit> {
|
||||
|
||||
public static toLineEdit(edit: RootedEdit<BaseStringEdit<BaseStringReplacement<any>, any>>): LineEdit {
|
||||
return LineEdit.fromEdit(edit.edit as StringEdit, edit.base);
|
||||
return LineEdit.fromStringEdit(edit.edit as StringEdit, edit.base);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
||||
@ -13,7 +13,7 @@ ensureDependenciesAreSet();
|
||||
|
||||
export class RootedLineEdit {
|
||||
public static fromEdit<TEdit extends BaseStringEdit>(edit: RootedEdit<TEdit>): RootedLineEdit {
|
||||
const lineEdit = LineEdit.fromEdit(edit.edit as BaseStringEdit as StringEdit, edit.base);
|
||||
const lineEdit = LineEdit.fromStringEdit(edit.edit as BaseStringEdit as StringEdit, edit.base);
|
||||
return new RootedLineEdit(edit.base, lineEdit);
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +54,11 @@ export interface IActionChangeEvent {
|
||||
readonly checked?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete implementation of {@link IAction}.
|
||||
*
|
||||
* Note that in most cases you should use the lighter-weight {@linkcode toAction} function instead.
|
||||
*/
|
||||
export class Action extends Disposable implements IAction {
|
||||
|
||||
protected _onDidChange = this._register(new Emitter<IActionChangeEvent>());
|
||||
|
||||
@ -193,6 +193,10 @@ export interface ITask<T> {
|
||||
(): T;
|
||||
}
|
||||
|
||||
export interface ICancellableTask<T> {
|
||||
(token: CancellationToken): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to prevent accumulation of sequential async tasks.
|
||||
*
|
||||
@ -223,18 +227,19 @@ export class Throttler implements IDisposable {
|
||||
|
||||
private activePromise: Promise<any> | null;
|
||||
private queuedPromise: Promise<any> | null;
|
||||
private queuedPromiseFactory: ITask<Promise<any>> | null;
|
||||
|
||||
private isDisposed = false;
|
||||
private queuedPromiseFactory: ICancellableTask<Promise<any>> | null;
|
||||
private cancellationTokenSource: CancellationTokenSource;
|
||||
|
||||
constructor() {
|
||||
this.activePromise = null;
|
||||
this.queuedPromise = null;
|
||||
this.queuedPromiseFactory = null;
|
||||
|
||||
this.cancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
queue<T>(promiseFactory: ITask<Promise<T>>): Promise<T> {
|
||||
if (this.isDisposed) {
|
||||
queue<T>(promiseFactory: ICancellableTask<Promise<T>>): Promise<T> {
|
||||
if (this.cancellationTokenSource.token.isCancellationRequested) {
|
||||
return Promise.reject(new Error('Throttler is disposed'));
|
||||
}
|
||||
|
||||
@ -245,7 +250,7 @@ export class Throttler implements IDisposable {
|
||||
const onComplete = () => {
|
||||
this.queuedPromise = null;
|
||||
|
||||
if (this.isDisposed) {
|
||||
if (this.cancellationTokenSource.token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -265,7 +270,7 @@ export class Throttler implements IDisposable {
|
||||
});
|
||||
}
|
||||
|
||||
this.activePromise = promiseFactory();
|
||||
this.activePromise = promiseFactory(this.cancellationTokenSource.token);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.activePromise!.then((result: T) => {
|
||||
@ -279,7 +284,7 @@ export class Throttler implements IDisposable {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.isDisposed = true;
|
||||
this.cancellationTokenSource.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,7 +465,7 @@ export class ThrottledDelayer<T> {
|
||||
this.throttler = new Throttler();
|
||||
}
|
||||
|
||||
trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<T> {
|
||||
trigger(promiseFactory: ICancellableTask<Promise<T>>, delay?: number): Promise<T> {
|
||||
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise<T>;
|
||||
}
|
||||
|
||||
@ -1717,6 +1722,12 @@ const enum DeferredOutcome {
|
||||
*/
|
||||
export class DeferredPromise<T> {
|
||||
|
||||
public static fromPromise<T>(promise: Promise<T>): DeferredPromise<T> {
|
||||
const deferred = new DeferredPromise<T>();
|
||||
deferred.settleWith(promise);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
private completeCallback!: ValueCallback<T>;
|
||||
private errorCallback!: (err: unknown) => void;
|
||||
private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T };
|
||||
@ -1747,6 +1758,10 @@ export class DeferredPromise<T> {
|
||||
}
|
||||
|
||||
public complete(value: T) {
|
||||
if (this.isSettled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise<void>(resolve => {
|
||||
this.completeCallback(value);
|
||||
this.outcome = { outcome: DeferredOutcome.Resolved, value };
|
||||
@ -1755,6 +1770,10 @@ export class DeferredPromise<T> {
|
||||
}
|
||||
|
||||
public error(err: unknown) {
|
||||
if (this.isSettled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise<void>(resolve => {
|
||||
this.errorCallback(err);
|
||||
this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
|
||||
|
||||
@ -10,7 +10,7 @@ import * as streams from './stream';
|
||||
|
||||
interface NodeBuffer {
|
||||
allocUnsafe(size: number): Uint8Array;
|
||||
isBuffer(obj: any): obj is NodeBuffer;
|
||||
isBuffer(obj: unknown): obj is NodeBuffer;
|
||||
from(arrayBuffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array;
|
||||
from(data: string): Uint8Array;
|
||||
}
|
||||
|
||||
@ -120,3 +120,36 @@ export class CachedFunction<TArg, TComputed> {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses an unbounded cache to memoize the results of the given function.
|
||||
*/
|
||||
export class WeakCachedFunction<TArg, TComputed> {
|
||||
private readonly _map = new WeakMap<WeakKey, TComputed>();
|
||||
|
||||
private readonly _fn: (arg: TArg) => TComputed;
|
||||
private readonly _computeKey: (arg: TArg) => unknown;
|
||||
|
||||
constructor(fn: (arg: TArg) => TComputed);
|
||||
constructor(options: ICacheOptions<TArg>, fn: (arg: TArg) => TComputed);
|
||||
constructor(arg1: ICacheOptions<TArg> | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) {
|
||||
if (typeof arg1 === 'function') {
|
||||
this._fn = arg1;
|
||||
this._computeKey = identity;
|
||||
} else {
|
||||
this._fn = arg2!;
|
||||
this._computeKey = arg1.getCacheKey;
|
||||
}
|
||||
}
|
||||
|
||||
public get(arg: TArg): TComputed {
|
||||
const key = this._computeKey(arg) as WeakKey;
|
||||
if (this._map.has(key)) {
|
||||
return this._map.get(key)!;
|
||||
}
|
||||
|
||||
const value = this._fn(arg);
|
||||
this._map.set(key, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,3 +148,61 @@ export function cancelOnDispose(store: DisposableStore): CancellationToken {
|
||||
store.add({ dispose() { source.cancel(); } });
|
||||
return source.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pool that aggregates multiple cancellation tokens. The pool's own token
|
||||
* (accessible via `pool.token`) is cancelled only after every token added
|
||||
* to the pool has been cancelled. Adding tokens after the pool token has
|
||||
* been cancelled has no effect.
|
||||
*/
|
||||
export class CancellationTokenPool {
|
||||
|
||||
private readonly _source = new CancellationTokenSource();
|
||||
private readonly _listeners = new DisposableStore();
|
||||
|
||||
private _total: number = 0;
|
||||
private _cancelled: number = 0;
|
||||
private _isDone: boolean = false;
|
||||
|
||||
get token(): CancellationToken {
|
||||
return this._source.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a token to the pool. If the token is already cancelled it is counted
|
||||
* immediately. Tokens added after the pool token has been cancelled are ignored.
|
||||
*/
|
||||
add(token: CancellationToken): void {
|
||||
if (this._isDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._total++;
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
this._cancelled++;
|
||||
this._check();
|
||||
return;
|
||||
}
|
||||
|
||||
const d = token.onCancellationRequested(() => {
|
||||
d.dispose();
|
||||
this._cancelled++;
|
||||
this._check();
|
||||
});
|
||||
this._listeners.add(d);
|
||||
}
|
||||
|
||||
private _check(): void {
|
||||
if (!this._isDone && this._total > 0 && this._total === this._cancelled) {
|
||||
this._isDone = true;
|
||||
this._listeners.dispose();
|
||||
this._source.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._listeners.dispose();
|
||||
this._source.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,6 @@ export const codiconsLibrary = {
|
||||
vm: register('vm', 0xea7a),
|
||||
deviceDesktop: register('device-desktop', 0xea7a),
|
||||
file: register('file', 0xea7b),
|
||||
fileText: register('file-text', 0xea7b),
|
||||
more: register('more', 0xea7c),
|
||||
ellipsis: register('ellipsis', 0xea7c),
|
||||
kebabHorizontal: register('kebab-horizontal', 0xea7c),
|
||||
@ -615,4 +614,20 @@ export const codiconsLibrary = {
|
||||
commentDiscussionSparkle: register('comment-discussion-sparkle', 0xec54),
|
||||
chatSparkleWarning: register('chat-sparkle-warning', 0xec55),
|
||||
chatSparkleError: register('chat-sparkle-error', 0xec56),
|
||||
collection: register('collection', 0xec57),
|
||||
newCollection: register('new-collection', 0xec58),
|
||||
thinking: register('thinking', 0xec59),
|
||||
build: register('build', 0xec5a),
|
||||
commentDiscussionQuote: register('comment-discussion-quote', 0xec5b),
|
||||
cursor: register('cursor', 0xec5c),
|
||||
eraser: register('eraser', 0xec5d),
|
||||
fileText: register('file-text', 0xec5e),
|
||||
gitLens: register('git-lens', 0xec5f),
|
||||
quotes: register('quotes', 0xec60),
|
||||
rename: register('rename', 0xec61),
|
||||
runWithDeps: register('run-with-deps', 0xec62),
|
||||
debugConnected: register('debug-connected', 0xec63),
|
||||
strikethrough: register('strikethrough', 0xec64),
|
||||
openInProduct: register('open-in-product', 0xec65),
|
||||
indexZero: register('index-zero', 0xec66),
|
||||
} as const;
|
||||
|
||||
@ -141,6 +141,7 @@ export function transformErrorForSerialization(error: any): any;
|
||||
export function transformErrorForSerialization(error: any): any {
|
||||
if (error instanceof Error) {
|
||||
const { name, message, cause } = error;
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
const stack: string = (<any>error).stacktrace || (<any>error).stack;
|
||||
return {
|
||||
$isError: true,
|
||||
|
||||
@ -361,14 +361,14 @@ export interface IPathWithLineAndColumn {
|
||||
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
|
||||
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
|
||||
|
||||
let path: string | undefined = undefined;
|
||||
let line: number | undefined = undefined;
|
||||
let column: number | undefined = undefined;
|
||||
let path: string | undefined;
|
||||
let line: number | undefined;
|
||||
let column: number | undefined;
|
||||
|
||||
for (const segment of segments) {
|
||||
const segmentAsNumber = Number(segment);
|
||||
if (!isNumber(segmentAsNumber)) {
|
||||
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
|
||||
path = path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
|
||||
} else if (line === undefined) {
|
||||
line = segmentAsNumber;
|
||||
} else if (column === undefined) {
|
||||
|
||||
@ -510,7 +510,7 @@ function toRegExp(pattern: string): ParsedStringPattern {
|
||||
|
||||
return typeof path === 'string' && regExp.test(path) ? pattern : null;
|
||||
};
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,16 +58,16 @@ export function stringHash(s: string, hashVal: number) {
|
||||
return hashVal;
|
||||
}
|
||||
|
||||
function arrayHash(arr: any[], initialHashVal: number): number {
|
||||
function arrayHash(arr: unknown[], initialHashVal: number): number {
|
||||
initialHashVal = numberHash(104579, initialHashVal);
|
||||
return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal);
|
||||
return arr.reduce<number>((hashVal, item) => doHash(item, hashVal), initialHashVal);
|
||||
}
|
||||
|
||||
function objectHash(obj: any, initialHashVal: number): number {
|
||||
function objectHash(obj: object, initialHashVal: number): number {
|
||||
initialHashVal = numberHash(181387, initialHashVal);
|
||||
return Object.keys(obj).sort().reduce((hashVal, key) => {
|
||||
hashVal = stringHash(key, hashVal);
|
||||
return doHash(obj[key], hashVal);
|
||||
return doHash((obj as Record<string, unknown>)[key], hashVal);
|
||||
}, initialHashVal);
|
||||
}
|
||||
|
||||
|
||||
@ -201,12 +201,15 @@ export function parseHrefAndDimensions(href: string): { href: string; dimensions
|
||||
return { href, dimensions };
|
||||
}
|
||||
|
||||
export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[] }, escapeTokens = true): string {
|
||||
const uri = URI.from({
|
||||
scheme: Schemas.command,
|
||||
path: command.id,
|
||||
query: command.arguments?.length ? encodeURIComponent(JSON.stringify(command.arguments)) : undefined,
|
||||
}).toString();
|
||||
|
||||
return `[${escapeTokens ? escapeMarkdownSyntaxTokens(command.title) : command.title}](${uri})`;
|
||||
export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[]; tooltip?: string }, escapeTokens = true): string {
|
||||
const uri = createCommandUri(command.id, ...(command.arguments || [])).toString();
|
||||
return `[${escapeTokens ? escapeMarkdownSyntaxTokens(command.title) : command.title}](${uri}${command.tooltip ? ` "${escapeMarkdownSyntaxTokens(command.tooltip)}"` : ''})`;
|
||||
}
|
||||
|
||||
export function createCommandUri(commandId: string, ...commandArgs: unknown[]): URI {
|
||||
return URI.from({
|
||||
scheme: Schemas.command,
|
||||
path: commandId,
|
||||
query: commandArgs.length ? encodeURIComponent(JSON.stringify(commandArgs)) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@ -208,7 +208,9 @@ export class DisposableTracker implements IDisposableTracker {
|
||||
const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v);
|
||||
delete continuations[stackTracePath[i]];
|
||||
for (const [cont, set] of Object.entries(continuations)) {
|
||||
stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`);
|
||||
if (set) {
|
||||
stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`);
|
||||
}
|
||||
}
|
||||
|
||||
stackTraceFormattedLines.unshift(line);
|
||||
@ -235,6 +237,7 @@ if (TRACK_DISPOSABLES) {
|
||||
trackDisposable(x: IDisposable): void {
|
||||
const stack = new Error('Potentially leaked disposable').stack!;
|
||||
setTimeout(() => {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
if (!(x as any)[__is_disposable_tracked__]) {
|
||||
console.log(stack);
|
||||
}
|
||||
@ -244,6 +247,7 @@ if (TRACK_DISPOSABLES) {
|
||||
setParent(child: IDisposable, parent: IDisposable | null): void {
|
||||
if (child && child !== Disposable.None) {
|
||||
try {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
(child as any)[__is_disposable_tracked__] = true;
|
||||
} catch {
|
||||
// noop
|
||||
@ -254,6 +258,7 @@ if (TRACK_DISPOSABLES) {
|
||||
markAsDisposed(disposable: IDisposable): void {
|
||||
if (disposable && disposable !== Disposable.None) {
|
||||
try {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
(disposable as any)[__is_disposable_tracked__] = true;
|
||||
} catch {
|
||||
// noop
|
||||
@ -313,6 +318,7 @@ export interface IDisposable {
|
||||
* Check if `thing` is {@link IDisposable disposable}.
|
||||
*/
|
||||
export function isDisposable<E extends any>(thing: E): thing is E & IDisposable {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
return typeof thing === 'object' && thing !== null && typeof (<IDisposable><any>thing).dispose === 'function' && (<IDisposable><any>thing).dispose.length === 0;
|
||||
}
|
||||
|
||||
@ -369,19 +375,36 @@ export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
|
||||
return parent;
|
||||
}
|
||||
|
||||
class FunctionDisposable implements IDisposable {
|
||||
private _isDisposed: boolean;
|
||||
private readonly _fn: () => void;
|
||||
|
||||
constructor(fn: () => void) {
|
||||
this._isDisposed = false;
|
||||
this._fn = fn;
|
||||
trackDisposable(this);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
if (!this._fn) {
|
||||
throw new Error(`Unbound disposable context: Need to use an arrow function to preserve the value of this`);
|
||||
}
|
||||
this._isDisposed = true;
|
||||
markAsDisposed(this);
|
||||
this._fn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a function that implements dispose into an {@link IDisposable}.
|
||||
*
|
||||
* @param fn Clean up function, guaranteed to be called only **once**.
|
||||
*/
|
||||
export function toDisposable(fn: () => void): IDisposable {
|
||||
const self = trackDisposable({
|
||||
dispose: createSingleCallFunction(() => {
|
||||
markAsDisposed(self);
|
||||
fn();
|
||||
})
|
||||
});
|
||||
return self;
|
||||
return new FunctionDisposable(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -549,10 +572,25 @@ export class MutableDisposable<T extends IDisposable> implements IDisposable {
|
||||
trackDisposable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed
|
||||
*/
|
||||
get value(): T | undefined {
|
||||
return this._isDisposed ? undefined : this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new disposable value.
|
||||
*
|
||||
* Behaviour:
|
||||
* - If the MutableDisposable has been disposed, the setter is a no-op.
|
||||
* - If the new value is strictly equal to the current value, the setter is a no-op.
|
||||
* - Otherwise the previous value (if any) is disposed and the new value is stored.
|
||||
*
|
||||
* Related helpers:
|
||||
* - clear() resets the value to `undefined` (and disposes the previous value).
|
||||
* - clearAndLeak() returns the old value without disposing it and removes its parent.
|
||||
*/
|
||||
set value(value: T | undefined) {
|
||||
if (this._isDisposed || value === this._value) {
|
||||
return;
|
||||
@ -651,7 +689,7 @@ export abstract class ReferenceCollection<T> {
|
||||
|
||||
private readonly references: Map<string, { readonly object: T; counter: number }> = new Map();
|
||||
|
||||
acquire(key: string, ...args: any[]): IReference<T> {
|
||||
acquire(key: string, ...args: unknown[]): IReference<T> {
|
||||
let reference = this.references.get(key);
|
||||
|
||||
if (!reference) {
|
||||
@ -672,7 +710,7 @@ export abstract class ReferenceCollection<T> {
|
||||
return { object, dispose };
|
||||
}
|
||||
|
||||
protected abstract createReferencedObject(key: string, ...args: any[]): T;
|
||||
protected abstract createReferencedObject(key: string, ...args: unknown[]): T;
|
||||
protected abstract destroyReferencedObject(key: string, object: T): void;
|
||||
}
|
||||
|
||||
|
||||
@ -123,7 +123,7 @@ export class ResourceMap<T> implements Map<URI, T> {
|
||||
clb = clb.bind(thisArg);
|
||||
}
|
||||
for (const [_, entry] of this.map) {
|
||||
clb(entry.value, entry.uri, <any>this);
|
||||
clb(entry.value, entry.uri, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -415,6 +415,7 @@ export namespace COI {
|
||||
* isn't enabled the current context
|
||||
*/
|
||||
export function addSearchParam(urlOrSearch: URLSearchParams | Record<string, string>, coop: boolean, coep: boolean): void {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
if (!(<any>globalThis).crossOriginIsolated) {
|
||||
// depends on the current context being COI
|
||||
return;
|
||||
|
||||
@ -101,65 +101,6 @@ export function isPointWithinTriangle(
|
||||
return u >= 0 && v >= 0 && u + v < 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get a (pseudo)random integer from a provided `max`...[`min`] range.
|
||||
* Both `min` and `max` values are inclusive. The `min` value is optional (defaults to `0`).
|
||||
*
|
||||
* @throws in the next cases:
|
||||
* - if provided `min` or `max` is not a number
|
||||
* - if provided `min` or `max` is not finite
|
||||
* - if provided `min` is larger than `max` value
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* Specifying a `max` value only uses `0` as the `min` value by default:
|
||||
*
|
||||
* ```typescript
|
||||
* // get a random integer between 0 and 10
|
||||
* const randomInt = randomInt(10);
|
||||
*
|
||||
* assert(
|
||||
* randomInt >= 0,
|
||||
* 'Should be greater than or equal to 0.',
|
||||
* );
|
||||
*
|
||||
* assert(
|
||||
* randomInt <= 10,
|
||||
* 'Should be less than or equal to 10.',
|
||||
* );
|
||||
* ```
|
||||
* * Specifying both `max` and `min` values:
|
||||
*
|
||||
* ```typescript
|
||||
* // get a random integer between 5 and 8
|
||||
* const randomInt = randomInt(8, 5);
|
||||
*
|
||||
* assert(
|
||||
* randomInt >= 5,
|
||||
* 'Should be greater than or equal to 5.',
|
||||
* );
|
||||
*
|
||||
* assert(
|
||||
* randomInt <= 8,
|
||||
* 'Should be less than or equal to 8.',
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function randomInt(max: number, min: number = 0): number {
|
||||
assert(!isNaN(min), '"min" param is not a number.');
|
||||
assert(!isNaN(max), '"max" param is not a number.');
|
||||
|
||||
assert(isFinite(max), '"max" param is not finite.');
|
||||
assert(isFinite(min), '"min" param is not finite.');
|
||||
|
||||
assert(max > min, `"max"(${max}) param should be greater than "min"(${min}).`);
|
||||
|
||||
const delta = max - min;
|
||||
const randomFloat = delta * Math.random();
|
||||
|
||||
return Math.round(min + randomFloat);
|
||||
}
|
||||
|
||||
export function randomChance(p: number): boolean {
|
||||
assert(p >= 0 && p <= 1, 'p must be between 0 and 1');
|
||||
return Math.random() < p;
|
||||
|
||||
@ -71,10 +71,10 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set<any>):
|
||||
throw new Error('Cannot clone recursive data-structure');
|
||||
}
|
||||
seen.add(obj);
|
||||
const r2 = {};
|
||||
const r2: Record<string, unknown> = {};
|
||||
for (const i2 in obj) {
|
||||
if (_hasOwnProperty.call(obj, i2)) {
|
||||
(r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen);
|
||||
r2[i2] = _cloneAndChange(obj[i2], changer, seen);
|
||||
}
|
||||
}
|
||||
seen.delete(obj);
|
||||
|
||||
@ -97,6 +97,11 @@ export interface IObservableWithChange<T, TChange = unknown> {
|
||||
*/
|
||||
readonly debugName: string;
|
||||
|
||||
/**
|
||||
* ONLY FOR DEBUGGING!
|
||||
*/
|
||||
debugGetDependencyGraph(): string;
|
||||
|
||||
/**
|
||||
* This property captures the type of the change object. Do not use it at runtime!
|
||||
*/
|
||||
|
||||
@ -33,6 +33,7 @@ export function recordChanges<TObs extends Record<any, IObservableWithChange<any
|
||||
& { changes: readonly ({ [TKey in keyof TObs]: { key: TKey; change: TObs[TKey]['TChange'] } }[keyof TObs])[] }> {
|
||||
return {
|
||||
createChangeSummary: (_previousChangeSummary) => {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
return {
|
||||
changes: [],
|
||||
} as any;
|
||||
@ -40,6 +41,7 @@ export function recordChanges<TObs extends Record<any, IObservableWithChange<any
|
||||
handleChange(ctx, changeSummary) {
|
||||
for (const key in obs) {
|
||||
if (ctx.didChange(obs[key])) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
(changeSummary.changes as any).push({ key, change: ctx.change });
|
||||
}
|
||||
}
|
||||
@ -66,6 +68,7 @@ export function recordChangesLazy<TObs extends Record<any, IObservableWithChange
|
||||
let obs: TObs | undefined = undefined;
|
||||
return {
|
||||
createChangeSummary: (_previousChangeSummary) => {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
return {
|
||||
changes: [],
|
||||
} as any;
|
||||
@ -76,6 +79,7 @@ export function recordChangesLazy<TObs extends Record<any, IObservableWithChange
|
||||
}
|
||||
for (const key in obs) {
|
||||
if (ctx.didChange(obs[key])) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
(changeSummary.changes as any).push({ key, change: ctx.change });
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ export namespace DebugLocation {
|
||||
if (!enabled) {
|
||||
return undefined;
|
||||
}
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
const Err = Error as any as { stackTraceLimit: number }; // For the monaco editor checks, which don't have the nodejs types.
|
||||
|
||||
const l = Err.stackTraceLimit;
|
||||
|
||||
@ -105,6 +105,7 @@ function computeDebugName(self: object, data: DebugNameData): string | undefined
|
||||
|
||||
function findKey(obj: object, value: object): string | undefined {
|
||||
for (const key in obj) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
if ((obj as any)[key] === value) {
|
||||
return key;
|
||||
}
|
||||
|
||||
@ -42,8 +42,10 @@ import { addLogger, setLogObservableFn } from './logging/logging';
|
||||
import { ConsoleObservableLogger, logObservableToConsole } from './logging/consoleObservableLogger';
|
||||
import { DevToolsLogger } from './logging/debugger/devToolsLogger';
|
||||
import { env } from '../process';
|
||||
import { _setDebugGetDependencyGraph } from './observables/baseObservable';
|
||||
import { debugGetDependencyGraph } from './logging/debugGetDependencyGraph';
|
||||
|
||||
|
||||
_setDebugGetDependencyGraph(debugGetDependencyGraph);
|
||||
setLogObservableFn(logObservableToConsole);
|
||||
|
||||
// Remove "//" in the next line to enable logging
|
||||
|
||||
@ -79,6 +79,7 @@ export class ConsoleObservableLogger implements IObservableLogger {
|
||||
const debugTrackUpdating = false;
|
||||
if (debugTrackUpdating) {
|
||||
const updating: IObservable<any>[] = [];
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
(derived as any).__debugUpdating = updating;
|
||||
|
||||
const existingBeginUpdate = derived.beginUpdate;
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IObservable, IObserver } from '../base';
|
||||
import { Derived } from '../observables/derivedImpl';
|
||||
import { FromEventObservable } from '../observables/observableFromEvent';
|
||||
import { ObservableValue } from '../observables/observableValue';
|
||||
import { AutorunObserver } from '../reactions/autorunImpl';
|
||||
import { formatValue } from './consoleObservableLogger';
|
||||
|
||||
export function debugGetDependencyGraph(obs: IObservable<any> | IObserver, options?: { debugNamePostProcessor?: (name: string) => string }): string {
|
||||
const debugNamePostProcessor = options?.debugNamePostProcessor ?? ((str: string) => str);
|
||||
const info = Info.from(obs, debugNamePostProcessor);
|
||||
if (!info) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const alreadyListed = new Set<IObservable<any> | IObserver>();
|
||||
return formatObservableInfo(info, 0, alreadyListed).trim();
|
||||
}
|
||||
|
||||
function formatObservableInfo(info: Info, indentLevel: number, alreadyListed: Set<IObservable<any> | IObserver>): string {
|
||||
const indent = '\t\t'.repeat(indentLevel);
|
||||
const lines: string[] = [];
|
||||
|
||||
const isAlreadyListed = alreadyListed.has(info.sourceObj);
|
||||
if (isAlreadyListed) {
|
||||
lines.push(`${indent}* ${info.type} ${info.name} (already listed)`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
alreadyListed.add(info.sourceObj);
|
||||
|
||||
lines.push(`${indent}* ${info.type} ${info.name}:`);
|
||||
lines.push(`${indent} value: ${formatValue(info.value, 50)}`);
|
||||
lines.push(`${indent} state: ${info.state}`);
|
||||
|
||||
if (info.dependencies.length > 0) {
|
||||
lines.push(`${indent} dependencies:`);
|
||||
for (const dep of info.dependencies) {
|
||||
lines.push(formatObservableInfo(dep, indentLevel + 1, alreadyListed));
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
class Info {
|
||||
public static from(obs: IObservable<any> | IObserver, debugNamePostProcessor: (name: string) => string): Info | undefined {
|
||||
if (obs instanceof AutorunObserver) {
|
||||
const state = obs.debugGetState();
|
||||
return new Info(
|
||||
obs,
|
||||
debugNamePostProcessor(obs.debugName),
|
||||
'autorun',
|
||||
undefined,
|
||||
state.stateStr,
|
||||
Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep))
|
||||
);
|
||||
} else if (obs instanceof Derived) {
|
||||
const state = obs.debugGetState();
|
||||
return new Info(
|
||||
obs,
|
||||
debugNamePostProcessor(obs.debugName),
|
||||
'derived',
|
||||
state.value,
|
||||
state.stateStr,
|
||||
Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep))
|
||||
);
|
||||
} else if (obs instanceof ObservableValue) {
|
||||
const state = obs.debugGetState();
|
||||
return new Info(
|
||||
obs,
|
||||
debugNamePostProcessor(obs.debugName),
|
||||
'observableValue',
|
||||
state.value,
|
||||
'upToDate',
|
||||
[]
|
||||
);
|
||||
} else if (obs instanceof FromEventObservable) {
|
||||
const state = obs.debugGetState();
|
||||
return new Info(
|
||||
obs,
|
||||
debugNamePostProcessor(obs.debugName),
|
||||
'fromEvent',
|
||||
state.value,
|
||||
state.hasValue ? 'upToDate' : 'initial',
|
||||
[]
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static unknown(obs: IObservable<any> | IObserver): Info {
|
||||
return new Info(
|
||||
obs,
|
||||
'(unknown)',
|
||||
'unknown',
|
||||
undefined,
|
||||
'unknown',
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly sourceObj: IObservable<any> | IObserver,
|
||||
public readonly name: string,
|
||||
public readonly type: string,
|
||||
public readonly value: any,
|
||||
public readonly state: string,
|
||||
public readonly dependencies: Info[]
|
||||
) { }
|
||||
}
|
||||
@ -11,6 +11,7 @@ export function registerDebugChannel<T extends { channelId: string } & API>(
|
||||
channelId: T['channelId'],
|
||||
createClient: () => T['client'],
|
||||
): SimpleTypedRpcConnection<MakeSideAsync<T['host']>> {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
const g = globalThis as any as GlobalObj;
|
||||
|
||||
let queuedNotifications: unknown[] = [];
|
||||
|
||||
@ -93,6 +93,7 @@ export class SimpleTypedRpcConnection<T extends Side> {
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
this.api = { notifications: notifications, requests: requests } as any;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { IObservableWithChange, IObserver, IReader, IObservable } from '../base'
|
||||
import { DisposableStore } from '../commonFacade/deps';
|
||||
import { DebugLocation } from '../debugLocation';
|
||||
import { DebugOwner, getFunctionName } from '../debugName';
|
||||
import { debugGetDependencyGraph } from '../logging/debugGetDependencyGraph';
|
||||
import { getLogger, logObservable } from '../logging/logging';
|
||||
import type { keepObserved, recomputeInitiallyAndOnChange } from '../utils/utils';
|
||||
import { derivedOpts } from './derived';
|
||||
@ -32,6 +33,11 @@ export function _setKeepObserved(keepObserved: typeof _keepObserved) {
|
||||
_keepObserved = keepObserved;
|
||||
}
|
||||
|
||||
let _debugGetDependencyGraph: typeof debugGetDependencyGraph;
|
||||
export function _setDebugGetDependencyGraph(debugGetDependencyGraph: typeof _debugGetDependencyGraph) {
|
||||
_debugGetDependencyGraph = debugGetDependencyGraph;
|
||||
}
|
||||
|
||||
export abstract class ConvenientObservable<T, TChange> implements IObservableWithChange<T, TChange> {
|
||||
get TChange(): TChange { return null!; }
|
||||
|
||||
@ -123,6 +129,10 @@ export abstract class ConvenientObservable<T, TChange> implements IObservableWit
|
||||
protected get debugValue() {
|
||||
return this.get();
|
||||
}
|
||||
|
||||
debugGetDependencyGraph(): string {
|
||||
return _debugGetDependencyGraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {
|
||||
|
||||
@ -37,7 +37,9 @@ export function derived<T, TChange = void>(
|
||||
);
|
||||
}
|
||||
return new Derived(
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
new DebugNameData(undefined, undefined, computeFnOrOwner as any),
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
computeFnOrOwner as any,
|
||||
undefined,
|
||||
undefined,
|
||||
@ -121,10 +123,12 @@ export function derivedWithStore<T>(computeFnOrOwner: ((reader: IReader, store:
|
||||
let computeFn: (reader: IReader, store: DisposableStore) => T;
|
||||
let owner: DebugOwner;
|
||||
if (computeFnOrUndefined === undefined) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
computeFn = computeFnOrOwner as any;
|
||||
owner = undefined;
|
||||
} else {
|
||||
owner = computeFnOrOwner;
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
computeFn = computeFnOrUndefined as any;
|
||||
}
|
||||
|
||||
@ -155,10 +159,12 @@ export function derivedDisposable<T extends IDisposable | undefined>(computeFnOr
|
||||
let computeFn: (reader: IReader) => T;
|
||||
let owner: DebugOwner;
|
||||
if (computeFnOrUndefined === undefined) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
computeFn = computeFnOrOwner as any;
|
||||
owner = undefined;
|
||||
} else {
|
||||
owner = computeFnOrOwner;
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
computeFn = computeFnOrUndefined as any;
|
||||
}
|
||||
|
||||
|
||||
@ -42,6 +42,16 @@ export const enum DerivedState {
|
||||
upToDate = 3,
|
||||
}
|
||||
|
||||
function derivedStateToString(state: DerivedState): string {
|
||||
switch (state) {
|
||||
case DerivedState.initial: return 'initial';
|
||||
case DerivedState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged';
|
||||
case DerivedState.stale: return 'stale';
|
||||
case DerivedState.upToDate: return 'upToDate';
|
||||
default: return '<unknown>';
|
||||
}
|
||||
}
|
||||
|
||||
export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObservable<T, TChange> implements IDerivedReader<TChange>, IObserver {
|
||||
private _state = DerivedState.initial;
|
||||
private _value: T | undefined = undefined;
|
||||
@ -302,6 +312,7 @@ export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObserv
|
||||
shouldReact = this._changeTracker ? this._changeTracker.handleChange({
|
||||
changedObservable: observable,
|
||||
change,
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
didChange: (o): this is any => o === observable as any,
|
||||
}, this._changeSummary!) : true;
|
||||
} catch (e) {
|
||||
@ -393,6 +404,7 @@ export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObserv
|
||||
public debugGetState() {
|
||||
return {
|
||||
state: this._state,
|
||||
stateStr: derivedStateToString(this._state),
|
||||
updateCount: this._updateCount,
|
||||
isComputing: this._isComputing,
|
||||
dependencies: this._dependencies,
|
||||
@ -401,6 +413,7 @@ export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObserv
|
||||
}
|
||||
|
||||
public debugSetValue(newValue: unknown) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
this._value = newValue as any;
|
||||
}
|
||||
|
||||
|
||||
@ -149,9 +149,14 @@ export class FromEventObservable<TArgs, T> extends BaseObservable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public debugSetValue(value: unknown) {
|
||||
public debugSetValue(value: unknown): void {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
this._value = value as any;
|
||||
}
|
||||
|
||||
public debugGetState() {
|
||||
return { value: this._value, hasValue: this._hasValue };
|
||||
}
|
||||
}
|
||||
|
||||
export namespace observableFromEvent {
|
||||
|
||||
@ -26,6 +26,15 @@ export const enum AutorunState {
|
||||
upToDate = 3,
|
||||
}
|
||||
|
||||
function autorunStateToString(state: AutorunState): string {
|
||||
switch (state) {
|
||||
case AutorunState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged';
|
||||
case AutorunState.stale: return 'stale';
|
||||
case AutorunState.upToDate: return 'upToDate';
|
||||
default: return '<unknown>';
|
||||
}
|
||||
}
|
||||
|
||||
export class AutorunObserver<TChangeSummary = any> implements IObserver, IReaderWithStore, IDisposable {
|
||||
private _state = AutorunState.stale;
|
||||
private _updateCount = 0;
|
||||
@ -175,6 +184,7 @@ export class AutorunObserver<TChangeSummary = any> implements IObserver, IReader
|
||||
const shouldReact = this._changeTracker ? this._changeTracker.handleChange({
|
||||
changedObservable: observable,
|
||||
change,
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
didChange: (o): this is any => o === observable as any,
|
||||
}, this._changeSummary!) : true;
|
||||
if (shouldReact) {
|
||||
@ -243,6 +253,7 @@ export class AutorunObserver<TChangeSummary = any> implements IObserver, IReader
|
||||
updateCount: this._updateCount,
|
||||
dependencies: this._dependencies,
|
||||
state: this._state,
|
||||
stateStr: autorunStateToString(this._state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@ export class ObservableSet<T> implements Set<T> {
|
||||
|
||||
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
|
||||
this._data.forEach((value, value2, _set) => {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
callbackfn.call(thisArg, value, value2, this as any);
|
||||
});
|
||||
}
|
||||
|
||||
@ -43,6 +43,10 @@ export class ObservablePromise<T> {
|
||||
return new ObservablePromise(fn());
|
||||
}
|
||||
|
||||
public static resolved<T>(value: T): ObservablePromise<T> {
|
||||
return new ObservablePromise(Promise.resolve(value));
|
||||
}
|
||||
|
||||
private readonly _value = observableValue<PromiseResult<T> | undefined>(this, undefined);
|
||||
|
||||
/**
|
||||
|
||||
@ -77,10 +77,12 @@ export function derivedWithCancellationToken<T>(computeFnOrOwner: ((reader: IRea
|
||||
let computeFn: (reader: IReader, store: CancellationToken) => T;
|
||||
let owner: DebugOwner;
|
||||
if (computeFnOrUndefined === undefined) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
computeFn = computeFnOrOwner as any;
|
||||
owner = undefined;
|
||||
} else {
|
||||
owner = computeFnOrOwner;
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
computeFn = computeFnOrUndefined as any;
|
||||
}
|
||||
|
||||
|
||||
@ -280,3 +280,7 @@ export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0);
|
||||
export function isBigSurOrNewer(osVersion: string): boolean {
|
||||
return parseFloat(osVersion) >= 20;
|
||||
}
|
||||
|
||||
export function isTahoeOrNewer(osVersion: string): boolean {
|
||||
return parseFloat(osVersion) >= 25;
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ let safeProcess: Omit<INodeProcess, 'arch'> & { arch: string | undefined };
|
||||
declare const process: INodeProcess;
|
||||
|
||||
// Native sandbox environment
|
||||
const vscodeGlobal = (globalThis as any).vscode;
|
||||
const vscodeGlobal = (globalThis as { vscode?: { process?: INodeProcess } }).vscode;
|
||||
if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') {
|
||||
const sandboxProcess: INodeProcess = vscodeGlobal.process;
|
||||
safeProcess = {
|
||||
|
||||
@ -333,14 +333,14 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
on(event: 'data', callback: (data: T) => void): void;
|
||||
on(event: 'error', callback: (err: Error) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
|
||||
on(event: 'data' | 'error' | 'end', callback: ((data: T) => void) | ((err: Error) => void) | (() => void)): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'data':
|
||||
this.listeners.data.push(callback);
|
||||
this.listeners.data.push(callback as (data: T) => void);
|
||||
|
||||
// switch into flowing mode as soon as the first 'data'
|
||||
// listener is added and we are not yet in flowing mode
|
||||
@ -349,7 +349,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
this.listeners.end.push(callback);
|
||||
this.listeners.end.push(callback as () => void);
|
||||
|
||||
// emit 'end' event directly if we are flowing
|
||||
// and the end has already been reached
|
||||
@ -362,7 +362,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.listeners.error.push(callback);
|
||||
this.listeners.error.push(callback as (err: Error) => void);
|
||||
|
||||
// emit buffered 'error' events unless done already
|
||||
// now that we know that we have at least one listener
|
||||
|
||||
@ -194,10 +194,6 @@ export function convertSimple2RegExpPattern(pattern: string): string {
|
||||
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
|
||||
}
|
||||
|
||||
export function stripWildcards(pattern: string): string {
|
||||
return pattern.replace(/\*/g, '');
|
||||
}
|
||||
|
||||
export interface RegExpOptions {
|
||||
matchCase?: boolean;
|
||||
wholeWord?: boolean;
|
||||
|
||||
@ -103,4 +103,17 @@ export namespace ThemeIcon {
|
||||
return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether specified icon is defined and has 'file' ID.
|
||||
*/
|
||||
export function isFile(icon: ThemeIcon | undefined): boolean {
|
||||
return icon?.id === Codicon.file.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether specified icon is defined and has 'folder' ID.
|
||||
*/
|
||||
export function isFolder(icon: ThemeIcon | undefined): boolean {
|
||||
return icon?.id === Codicon.folder.id;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,14 @@ export function isString(str: unknown): str is string {
|
||||
* @returns whether the provided parameter is a JavaScript Array and each element in the array is a string.
|
||||
*/
|
||||
export function isStringArray(value: unknown): value is string[] {
|
||||
return Array.isArray(value) && (<unknown[]>value).every(elem => isString(elem));
|
||||
return isArrayOf(value, isString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript Array and each element in the array satisfies the provided type guard.
|
||||
*/
|
||||
export function isArrayOf<T>(value: unknown, check: (item: unknown) => item is T): value is T[] {
|
||||
return Array.isArray(value) && value.every(check);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,6 +64,7 @@ export function isNumber(obj: unknown): obj is number {
|
||||
* @returns whether the provided parameter is an Iterable, casting to the given generic
|
||||
*/
|
||||
export function isIterable<T>(obj: unknown): obj is Iterable<T> {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
return !!obj && typeof (obj as any)[Symbol.iterator] === 'function';
|
||||
}
|
||||
|
||||
@ -64,6 +72,7 @@ export function isIterable<T>(obj: unknown): obj is Iterable<T> {
|
||||
* @returns whether the provided parameter is an Iterable, casting to the given generic
|
||||
*/
|
||||
export function isAsyncIterable<T>(obj: unknown): obj is AsyncIterable<T> {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
return !!obj && typeof (obj as any)[Symbol.asyncIterator] === 'function';
|
||||
}
|
||||
|
||||
@ -265,6 +274,7 @@ export function validateConstraint(arg: unknown, constraint: TypeConstraint | un
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) {
|
||||
return;
|
||||
}
|
||||
|
||||
894
extensions/copilot/src/util/vs/base/common/yaml.ts
Normal file
894
extensions/copilot/src/util/vs/base/common/yaml.ts
Normal file
@ -0,0 +1,894 @@
|
||||
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Parses a simplified YAML-like input from a single string.
|
||||
* Supports objects, arrays, primitive types (string, number, boolean, null).
|
||||
* Tracks positions for error reporting and node locations.
|
||||
*
|
||||
* Limitations:
|
||||
* - No multi-line strings or block literals
|
||||
* - No anchors or references
|
||||
* - No complex types (dates, binary)
|
||||
* - No special handling for escape sequences in strings
|
||||
* - Indentation must be consistent (spaces only, no tabs)
|
||||
*
|
||||
* Notes:
|
||||
* - New line separators can be either "\n" or "\r\n". The input string is split into lines internally.
|
||||
*
|
||||
* @param input A string containing the YAML-like input
|
||||
* @param errors Array to collect parsing errors
|
||||
* @param options Parsing options
|
||||
* @returns The parsed representation (ObjectNode, ArrayNode, or primitive node)
|
||||
*/
|
||||
export function parse(input: string, errors: YamlParseError[] = [], options: ParseOptions = {}): YamlNode | undefined {
|
||||
// Normalize both LF and CRLF by splitting on either; CR characters are not retained as part of line text.
|
||||
// This keeps the existing line/character based lexer logic intact.
|
||||
const lines = input.length === 0 ? [] : input.split(/\r\n|\n/);
|
||||
const parser = new YamlParser(lines, errors, options);
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
export interface YamlParseError {
|
||||
readonly message: string;
|
||||
readonly start: Position;
|
||||
readonly end: Position;
|
||||
readonly code: string;
|
||||
}
|
||||
|
||||
export interface ParseOptions {
|
||||
readonly allowDuplicateKeys?: boolean;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
readonly line: number;
|
||||
readonly character: number;
|
||||
}
|
||||
|
||||
export interface YamlStringNode {
|
||||
readonly type: 'string';
|
||||
readonly value: string;
|
||||
readonly start: Position;
|
||||
readonly end: Position;
|
||||
}
|
||||
|
||||
export interface YamlNumberNode {
|
||||
readonly type: 'number';
|
||||
readonly value: number;
|
||||
readonly start: Position;
|
||||
readonly end: Position;
|
||||
}
|
||||
|
||||
export interface YamlBooleanNode {
|
||||
readonly type: 'boolean';
|
||||
readonly value: boolean;
|
||||
readonly start: Position;
|
||||
readonly end: Position;
|
||||
}
|
||||
|
||||
export interface YamlNullNode {
|
||||
readonly type: 'null';
|
||||
readonly value: null;
|
||||
readonly start: Position;
|
||||
readonly end: Position;
|
||||
}
|
||||
|
||||
export interface YamlObjectNode {
|
||||
readonly type: 'object';
|
||||
readonly properties: { key: YamlStringNode; value: YamlNode }[];
|
||||
readonly start: Position;
|
||||
readonly end: Position;
|
||||
}
|
||||
|
||||
export interface YamlArrayNode {
|
||||
readonly type: 'array';
|
||||
readonly items: YamlNode[];
|
||||
readonly start: Position;
|
||||
readonly end: Position;
|
||||
}
|
||||
|
||||
export type YamlNode = YamlStringNode | YamlNumberNode | YamlBooleanNode | YamlNullNode | YamlObjectNode | YamlArrayNode;
|
||||
|
||||
// Helper functions for position and node creation
|
||||
function createPosition(line: number, character: number): Position {
|
||||
return { line, character };
|
||||
}
|
||||
|
||||
// Specialized node creation functions using a more concise approach
|
||||
function createStringNode(value: string, start: Position, end: Position): YamlStringNode {
|
||||
return { type: 'string', value, start, end };
|
||||
}
|
||||
|
||||
function createNumberNode(value: number, start: Position, end: Position): YamlNumberNode {
|
||||
return { type: 'number', value, start, end };
|
||||
}
|
||||
|
||||
function createBooleanNode(value: boolean, start: Position, end: Position): YamlBooleanNode {
|
||||
return { type: 'boolean', value, start, end };
|
||||
}
|
||||
|
||||
function createNullNode(start: Position, end: Position): YamlNullNode {
|
||||
return { type: 'null', value: null, start, end };
|
||||
}
|
||||
|
||||
function createObjectNode(properties: { key: YamlStringNode; value: YamlNode }[], start: Position, end: Position): YamlObjectNode {
|
||||
return { type: 'object', start, end, properties };
|
||||
}
|
||||
|
||||
function createArrayNode(items: YamlNode[], start: Position, end: Position): YamlArrayNode {
|
||||
return { type: 'array', start, end, items };
|
||||
}
|
||||
|
||||
// Utility functions for parsing
|
||||
function isWhitespace(char: string): boolean {
|
||||
return char === ' ' || char === '\t';
|
||||
}
|
||||
|
||||
// Simplified number validation using regex
|
||||
function isValidNumber(value: string): boolean {
|
||||
return /^-?\d*\.?\d+$/.test(value);
|
||||
}
|
||||
|
||||
// Lexer/Tokenizer for YAML content
|
||||
class YamlLexer {
|
||||
private lines: string[];
|
||||
private currentLine: number = 0;
|
||||
private currentChar: number = 0;
|
||||
|
||||
constructor(lines: string[]) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
getCurrentPosition(): Position {
|
||||
return createPosition(this.currentLine, this.currentChar);
|
||||
}
|
||||
|
||||
getCurrentLineNumber(): number {
|
||||
return this.currentLine;
|
||||
}
|
||||
|
||||
getCurrentCharNumber(): number {
|
||||
return this.currentChar;
|
||||
}
|
||||
|
||||
getCurrentLineText(): string {
|
||||
return this.currentLine < this.lines.length ? this.lines[this.currentLine] : '';
|
||||
}
|
||||
|
||||
savePosition(): { line: number; char: number } {
|
||||
return { line: this.currentLine, char: this.currentChar };
|
||||
}
|
||||
|
||||
restorePosition(pos: { line: number; char: number }): void {
|
||||
this.currentLine = pos.line;
|
||||
this.currentChar = pos.char;
|
||||
}
|
||||
|
||||
isAtEnd(): boolean {
|
||||
return this.currentLine >= this.lines.length;
|
||||
}
|
||||
|
||||
getCurrentChar(): string {
|
||||
if (this.isAtEnd() || this.currentChar >= this.lines[this.currentLine].length) {
|
||||
return '';
|
||||
}
|
||||
return this.lines[this.currentLine][this.currentChar];
|
||||
}
|
||||
|
||||
peek(offset: number = 1): string {
|
||||
const newChar = this.currentChar + offset;
|
||||
if (this.currentLine >= this.lines.length || newChar >= this.lines[this.currentLine].length) {
|
||||
return '';
|
||||
}
|
||||
return this.lines[this.currentLine][newChar];
|
||||
}
|
||||
|
||||
advance(): string {
|
||||
const char = this.getCurrentChar();
|
||||
if (this.currentChar >= this.lines[this.currentLine].length && this.currentLine < this.lines.length - 1) {
|
||||
this.currentLine++;
|
||||
this.currentChar = 0;
|
||||
} else {
|
||||
this.currentChar++;
|
||||
}
|
||||
return char;
|
||||
}
|
||||
|
||||
advanceLine(): void {
|
||||
this.currentLine++;
|
||||
this.currentChar = 0;
|
||||
}
|
||||
|
||||
skipWhitespace(): void {
|
||||
while (!this.isAtEnd() && this.currentChar < this.lines[this.currentLine].length && isWhitespace(this.getCurrentChar())) {
|
||||
this.advance();
|
||||
}
|
||||
}
|
||||
|
||||
skipToEndOfLine(): void {
|
||||
this.currentChar = this.lines[this.currentLine].length;
|
||||
}
|
||||
|
||||
getIndentation(): number {
|
||||
if (this.isAtEnd()) {
|
||||
return 0;
|
||||
}
|
||||
let indent = 0;
|
||||
for (let i = 0; i < this.lines[this.currentLine].length; i++) {
|
||||
if (this.lines[this.currentLine][i] === ' ') {
|
||||
indent++;
|
||||
} else if (this.lines[this.currentLine][i] === '\t') {
|
||||
indent += 4; // Treat tab as 4 spaces
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return indent;
|
||||
}
|
||||
|
||||
moveToNextNonEmptyLine(): void {
|
||||
while (this.currentLine < this.lines.length) {
|
||||
// First check current line from current position
|
||||
if (this.currentChar < this.lines[this.currentLine].length) {
|
||||
const remainingLine = this.lines[this.currentLine].substring(this.currentChar).trim();
|
||||
if (remainingLine.length > 0 && !remainingLine.startsWith('#')) {
|
||||
this.skipWhitespace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next line and check from beginning
|
||||
this.currentLine++;
|
||||
this.currentChar = 0;
|
||||
|
||||
if (this.currentLine < this.lines.length) {
|
||||
const line = this.lines[this.currentLine].trim();
|
||||
if (line.length > 0 && !line.startsWith('#')) {
|
||||
this.skipWhitespace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parser class for handling YAML parsing
|
||||
class YamlParser {
|
||||
private lexer: YamlLexer;
|
||||
private errors: YamlParseError[];
|
||||
private options: ParseOptions;
|
||||
// Track nesting level of flow (inline) collections '[' ']' '{' '}'
|
||||
private flowLevel: number = 0;
|
||||
|
||||
constructor(lines: string[], errors: YamlParseError[], options: ParseOptions) {
|
||||
this.lexer = new YamlLexer(lines);
|
||||
this.errors = errors;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
addError(message: string, code: string, start: Position, end: Position): void {
|
||||
this.errors.push({ message, code, start, end });
|
||||
}
|
||||
|
||||
parseValue(expectedIndent?: number): YamlNode {
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
if (this.lexer.isAtEnd()) {
|
||||
const pos = this.lexer.getCurrentPosition();
|
||||
return createStringNode('', pos, pos);
|
||||
}
|
||||
|
||||
const char = this.lexer.getCurrentChar();
|
||||
|
||||
// Handle quoted strings
|
||||
if (char === '"' || char === `'`) {
|
||||
return this.parseQuotedString(char);
|
||||
}
|
||||
|
||||
// Handle inline arrays
|
||||
if (char === '[') {
|
||||
return this.parseInlineArray();
|
||||
}
|
||||
|
||||
// Handle inline objects
|
||||
if (char === '{') {
|
||||
return this.parseInlineObject();
|
||||
}
|
||||
|
||||
// Handle unquoted values
|
||||
return this.parseUnquotedValue();
|
||||
}
|
||||
|
||||
parseQuotedString(quote: string): YamlNode {
|
||||
const start = this.lexer.getCurrentPosition();
|
||||
this.lexer.advance(); // Skip opening quote
|
||||
|
||||
let value = '';
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) {
|
||||
value += this.lexer.advance();
|
||||
}
|
||||
|
||||
if (this.lexer.getCurrentChar() === quote) {
|
||||
this.lexer.advance(); // Skip closing quote
|
||||
}
|
||||
|
||||
const end = this.lexer.getCurrentPosition();
|
||||
return createStringNode(value, start, end);
|
||||
}
|
||||
|
||||
parseUnquotedValue(): YamlNode {
|
||||
const start = this.lexer.getCurrentPosition();
|
||||
let value = '';
|
||||
let endPos = start;
|
||||
|
||||
// Helper function to check for value terminators
|
||||
const isTerminator = (char: string): boolean => {
|
||||
if (char === '#') { return true; }
|
||||
// Comma, ']' and '}' only terminate inside flow collections
|
||||
if (this.flowLevel > 0 && (char === ',' || char === ']' || char === '}')) { return true; }
|
||||
return false;
|
||||
};
|
||||
|
||||
// Handle opening quote that might not be closed
|
||||
const firstChar = this.lexer.getCurrentChar();
|
||||
if (firstChar === '"' || firstChar === `'`) {
|
||||
value += this.lexer.advance();
|
||||
endPos = this.lexer.getCurrentPosition();
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
|
||||
const char = this.lexer.getCurrentChar();
|
||||
if (char === firstChar || isTerminator(char)) {
|
||||
break;
|
||||
}
|
||||
value += this.lexer.advance();
|
||||
endPos = this.lexer.getCurrentPosition();
|
||||
}
|
||||
} else {
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
|
||||
const char = this.lexer.getCurrentChar();
|
||||
if (isTerminator(char)) {
|
||||
break;
|
||||
}
|
||||
value += this.lexer.advance();
|
||||
endPos = this.lexer.getCurrentPosition();
|
||||
}
|
||||
}
|
||||
const trimmed = value.trimEnd();
|
||||
const diff = value.length - trimmed.length;
|
||||
if (diff) {
|
||||
endPos = createPosition(start.line, endPos.character - diff);
|
||||
}
|
||||
const finalValue = (firstChar === '"' || firstChar === `'`) ? trimmed.substring(1) : trimmed;
|
||||
return this.createValueNode(finalValue, start, endPos);
|
||||
}
|
||||
|
||||
private createValueNode(value: string, start: Position, end: Position): YamlNode {
|
||||
if (value === '') {
|
||||
return createStringNode('', start, start);
|
||||
}
|
||||
|
||||
// Boolean values
|
||||
if (value === 'true') {
|
||||
return createBooleanNode(true, start, end);
|
||||
}
|
||||
if (value === 'false') {
|
||||
return createBooleanNode(false, start, end);
|
||||
}
|
||||
|
||||
// Null values
|
||||
if (value === 'null' || value === '~') {
|
||||
return createNullNode(start, end);
|
||||
}
|
||||
|
||||
// Number values
|
||||
const numberValue = Number(value);
|
||||
if (!isNaN(numberValue) && isFinite(numberValue) && isValidNumber(value)) {
|
||||
return createNumberNode(numberValue, start, end);
|
||||
}
|
||||
|
||||
// Default to string
|
||||
return createStringNode(value, start, end);
|
||||
}
|
||||
|
||||
parseInlineArray(): YamlArrayNode {
|
||||
const start = this.lexer.getCurrentPosition();
|
||||
this.lexer.advance(); // Skip '['
|
||||
this.flowLevel++;
|
||||
|
||||
const items: YamlNode[] = [];
|
||||
|
||||
while (!this.lexer.isAtEnd()) {
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Handle end of array
|
||||
if (this.lexer.getCurrentChar() === ']') {
|
||||
this.lexer.advance();
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle end of line - continue to next line for multi-line arrays
|
||||
if (this.lexer.getCurrentChar() === '') {
|
||||
this.lexer.advanceLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle comments - comments should terminate the array parsing
|
||||
if (this.lexer.getCurrentChar() === '#') {
|
||||
// Skip the rest of the line (comment)
|
||||
this.lexer.skipToEndOfLine();
|
||||
this.lexer.advanceLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save position before parsing to detect if we're making progress
|
||||
const positionBefore = this.lexer.savePosition();
|
||||
|
||||
// Parse array item
|
||||
const item = this.parseValue();
|
||||
// Skip implicit empty items that arise from a leading comma at the beginning of a new line
|
||||
// (e.g. a line starting with ",foo" after a comment). A legitimate empty string element
|
||||
// would have quotes and thus a non-zero span. We only filter zero-length spans.
|
||||
if (!(item.type === 'string' && item.value === '' && item.start.line === item.end.line && item.start.character === item.end.character)) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
// Check if we made progress - if not, we're likely stuck
|
||||
const positionAfter = this.lexer.savePosition();
|
||||
if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) {
|
||||
// No progress made, advance at least one character to prevent infinite loop
|
||||
if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
|
||||
this.lexer.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Handle comma separator
|
||||
if (this.lexer.getCurrentChar() === ',') {
|
||||
this.lexer.advance();
|
||||
}
|
||||
}
|
||||
|
||||
const end = this.lexer.getCurrentPosition();
|
||||
this.flowLevel--;
|
||||
return createArrayNode(items, start, end);
|
||||
}
|
||||
|
||||
parseInlineObject(): YamlObjectNode {
|
||||
const start = this.lexer.getCurrentPosition();
|
||||
this.lexer.advance(); // Skip '{'
|
||||
this.flowLevel++;
|
||||
|
||||
const properties: { key: YamlStringNode; value: YamlNode }[] = [];
|
||||
|
||||
while (!this.lexer.isAtEnd()) {
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Handle end of object
|
||||
if (this.lexer.getCurrentChar() === '}') {
|
||||
this.lexer.advance();
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle comments - comments should terminate the object parsing
|
||||
if (this.lexer.getCurrentChar() === '#') {
|
||||
// Skip the rest of the line (comment)
|
||||
this.lexer.skipToEndOfLine();
|
||||
this.lexer.advanceLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save position before parsing to detect if we're making progress
|
||||
const positionBefore = this.lexer.savePosition();
|
||||
|
||||
// Parse key - read until colon
|
||||
const keyStart = this.lexer.getCurrentPosition();
|
||||
let keyValue = '';
|
||||
|
||||
// Handle quoted keys
|
||||
if (this.lexer.getCurrentChar() === '"' || this.lexer.getCurrentChar() === `'`) {
|
||||
const quote = this.lexer.getCurrentChar();
|
||||
this.lexer.advance(); // Skip opening quote
|
||||
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) {
|
||||
keyValue += this.lexer.advance();
|
||||
}
|
||||
|
||||
if (this.lexer.getCurrentChar() === quote) {
|
||||
this.lexer.advance(); // Skip closing quote
|
||||
}
|
||||
} else {
|
||||
// Handle unquoted keys - read until colon
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') {
|
||||
keyValue += this.lexer.advance();
|
||||
}
|
||||
}
|
||||
|
||||
keyValue = keyValue.trim();
|
||||
const keyEnd = this.lexer.getCurrentPosition();
|
||||
const key = createStringNode(keyValue, keyStart, keyEnd);
|
||||
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Expect colon
|
||||
if (this.lexer.getCurrentChar() === ':') {
|
||||
this.lexer.advance();
|
||||
}
|
||||
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Parse value
|
||||
const value = this.parseValue();
|
||||
|
||||
properties.push({ key, value });
|
||||
|
||||
// Check if we made progress - if not, we're likely stuck
|
||||
const positionAfter = this.lexer.savePosition();
|
||||
if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) {
|
||||
// No progress made, advance at least one character to prevent infinite loop
|
||||
if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
|
||||
this.lexer.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Handle comma separator
|
||||
if (this.lexer.getCurrentChar() === ',') {
|
||||
this.lexer.advance();
|
||||
}
|
||||
}
|
||||
|
||||
const end = this.lexer.getCurrentPosition();
|
||||
this.flowLevel--;
|
||||
return createObjectNode(properties, start, end);
|
||||
}
|
||||
|
||||
parseBlockArray(baseIndent: number): YamlArrayNode {
|
||||
const start = this.lexer.getCurrentPosition();
|
||||
const items: YamlNode[] = [];
|
||||
|
||||
while (!this.lexer.isAtEnd()) {
|
||||
this.lexer.moveToNextNonEmptyLine();
|
||||
|
||||
if (this.lexer.isAtEnd()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const currentIndent = this.lexer.getIndentation();
|
||||
|
||||
// If indentation is less than expected, we're done with this array
|
||||
if (currentIndent < baseIndent) {
|
||||
break;
|
||||
}
|
||||
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Check for array item marker
|
||||
if (this.lexer.getCurrentChar() === '-') {
|
||||
this.lexer.advance(); // Skip '-'
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
const itemStart = this.lexer.getCurrentPosition();
|
||||
|
||||
// Check if this is a nested structure
|
||||
if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') {
|
||||
// Empty item - check if next lines form a nested structure
|
||||
this.lexer.advanceLine();
|
||||
|
||||
if (!this.lexer.isAtEnd()) {
|
||||
const nextIndent = this.lexer.getIndentation();
|
||||
|
||||
if (nextIndent > currentIndent) {
|
||||
// Check if the next line starts with a dash (nested array) or has properties (nested object)
|
||||
this.lexer.skipWhitespace();
|
||||
if (this.lexer.getCurrentChar() === '-') {
|
||||
// It's a nested array
|
||||
const nestedArray = this.parseBlockArray(nextIndent);
|
||||
items.push(nestedArray);
|
||||
} else {
|
||||
// Check if it looks like an object property (has a colon)
|
||||
const currentLine = this.lexer.getCurrentLineText();
|
||||
const currentPos = this.lexer.getCurrentCharNumber();
|
||||
const remainingLine = currentLine.substring(currentPos);
|
||||
|
||||
if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) {
|
||||
// It's a nested object
|
||||
const nestedObject = this.parseBlockObject(nextIndent, this.lexer.getCurrentCharNumber());
|
||||
items.push(nestedObject);
|
||||
} else {
|
||||
// Not a nested structure, create empty string
|
||||
items.push(createStringNode('', itemStart, itemStart));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No nested content, empty item
|
||||
items.push(createStringNode('', itemStart, itemStart));
|
||||
}
|
||||
} else {
|
||||
// End of input, empty item
|
||||
items.push(createStringNode('', itemStart, itemStart));
|
||||
}
|
||||
} else {
|
||||
// Parse the item value
|
||||
// Check if this is a multi-line object by looking for a colon and checking next lines
|
||||
const currentLine = this.lexer.getCurrentLineText();
|
||||
const currentPos = this.lexer.getCurrentCharNumber();
|
||||
const remainingLine = currentLine.substring(currentPos);
|
||||
|
||||
// Check if there's a colon on this line (indicating object properties)
|
||||
const hasColon = remainingLine.includes(':');
|
||||
|
||||
if (hasColon) {
|
||||
// Any line with a colon should be treated as an object
|
||||
// Parse as an object with the current item's indentation as the base
|
||||
const item = this.parseBlockObject(itemStart.character, itemStart.character);
|
||||
items.push(item);
|
||||
} else {
|
||||
// No colon, parse as regular value
|
||||
const item = this.parseValue();
|
||||
items.push(item);
|
||||
|
||||
// Skip to end of line
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') {
|
||||
this.lexer.advance();
|
||||
}
|
||||
this.lexer.advanceLine();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No dash found at expected indent level, break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate end position based on the last item
|
||||
let end = start;
|
||||
if (items.length > 0) {
|
||||
const lastItem = items[items.length - 1];
|
||||
end = lastItem.end;
|
||||
} else {
|
||||
// If no items, end is right after the start
|
||||
end = createPosition(start.line, start.character + 1);
|
||||
}
|
||||
|
||||
return createArrayNode(items, start, end);
|
||||
}
|
||||
|
||||
parseBlockObject(baseIndent: number, baseCharPosition?: number): YamlObjectNode {
|
||||
const start = this.lexer.getCurrentPosition();
|
||||
const properties: { key: YamlStringNode; value: YamlNode }[] = [];
|
||||
const localKeysSeen = new Set<string>();
|
||||
|
||||
// For parsing from current position (inline object parsing)
|
||||
const fromCurrentPosition = baseCharPosition !== undefined;
|
||||
let firstIteration = true;
|
||||
|
||||
while (!this.lexer.isAtEnd()) {
|
||||
if (!firstIteration || !fromCurrentPosition) {
|
||||
this.lexer.moveToNextNonEmptyLine();
|
||||
}
|
||||
firstIteration = false;
|
||||
|
||||
if (this.lexer.isAtEnd()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const currentIndent = this.lexer.getIndentation();
|
||||
|
||||
if (fromCurrentPosition) {
|
||||
// For current position parsing, check character position alignment
|
||||
this.lexer.skipWhitespace();
|
||||
const currentCharPosition = this.lexer.getCurrentCharNumber();
|
||||
|
||||
if (currentCharPosition < baseCharPosition) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// For normal block parsing, check indentation level
|
||||
if (currentIndent < baseIndent) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for incorrect indentation
|
||||
if (currentIndent > baseIndent) {
|
||||
const lineStart = createPosition(this.lexer.getCurrentLineNumber(), 0);
|
||||
const lineEnd = createPosition(this.lexer.getCurrentLineNumber(), this.lexer.getCurrentLineText().length);
|
||||
this.addError('Unexpected indentation', 'indentation', lineStart, lineEnd);
|
||||
|
||||
// Try to recover by treating it as a property anyway
|
||||
this.lexer.skipWhitespace();
|
||||
} else {
|
||||
this.lexer.skipWhitespace();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse key
|
||||
const keyStart = this.lexer.getCurrentPosition();
|
||||
let keyValue = '';
|
||||
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') {
|
||||
keyValue += this.lexer.advance();
|
||||
}
|
||||
|
||||
keyValue = keyValue.trim();
|
||||
const keyEnd = this.lexer.getCurrentPosition();
|
||||
const key = createStringNode(keyValue, keyStart, keyEnd);
|
||||
|
||||
// Check for duplicate keys
|
||||
if (!this.options.allowDuplicateKeys && localKeysSeen.has(keyValue)) {
|
||||
this.addError(`Duplicate key '${keyValue}'`, 'duplicateKey', keyStart, keyEnd);
|
||||
}
|
||||
localKeysSeen.add(keyValue);
|
||||
|
||||
// Expect colon
|
||||
if (this.lexer.getCurrentChar() === ':') {
|
||||
this.lexer.advance();
|
||||
}
|
||||
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
// Determine if value is on same line or next line(s)
|
||||
let value: YamlNode;
|
||||
const valueStart = this.lexer.getCurrentPosition();
|
||||
|
||||
if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') {
|
||||
// Value is on next line(s) or empty
|
||||
this.lexer.advanceLine();
|
||||
|
||||
// Check next line for nested content
|
||||
if (!this.lexer.isAtEnd()) {
|
||||
const nextIndent = this.lexer.getIndentation();
|
||||
|
||||
if (nextIndent > currentIndent) {
|
||||
// Nested content - determine if it's an object, array, or just a scalar value
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
if (this.lexer.getCurrentChar() === '-') {
|
||||
value = this.parseBlockArray(nextIndent);
|
||||
} else {
|
||||
// Check if this looks like an object property (has a colon)
|
||||
const currentLine = this.lexer.getCurrentLineText();
|
||||
const currentPos = this.lexer.getCurrentCharNumber();
|
||||
const remainingLine = currentLine.substring(currentPos);
|
||||
|
||||
if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) {
|
||||
// It's a nested object
|
||||
value = this.parseBlockObject(nextIndent);
|
||||
} else {
|
||||
// It's just a scalar value on the next line
|
||||
value = this.parseValue();
|
||||
}
|
||||
}
|
||||
} else if (!fromCurrentPosition && nextIndent === currentIndent) {
|
||||
// Same indentation level - check if it's an array item
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
if (this.lexer.getCurrentChar() === '-') {
|
||||
value = this.parseBlockArray(currentIndent);
|
||||
} else {
|
||||
value = createStringNode('', valueStart, valueStart);
|
||||
}
|
||||
} else {
|
||||
value = createStringNode('', valueStart, valueStart);
|
||||
}
|
||||
} else {
|
||||
value = createStringNode('', valueStart, valueStart);
|
||||
}
|
||||
} else {
|
||||
// Value is on the same line
|
||||
value = this.parseValue();
|
||||
|
||||
// Skip any remaining content on this line (comments, etc.)
|
||||
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') {
|
||||
if (isWhitespace(this.lexer.getCurrentChar())) {
|
||||
this.lexer.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to end of line if we hit a comment
|
||||
if (this.lexer.getCurrentChar() === '#') {
|
||||
this.lexer.skipToEndOfLine();
|
||||
}
|
||||
|
||||
// Move to next line for next iteration
|
||||
if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() === '') {
|
||||
this.lexer.advanceLine();
|
||||
}
|
||||
}
|
||||
|
||||
properties.push({ key, value });
|
||||
}
|
||||
|
||||
// Calculate the end position based on the last property
|
||||
let end = start;
|
||||
if (properties.length > 0) {
|
||||
const lastProperty = properties[properties.length - 1];
|
||||
end = lastProperty.value.end;
|
||||
}
|
||||
|
||||
return createObjectNode(properties, start, end);
|
||||
}
|
||||
|
||||
parse(): YamlNode | undefined {
|
||||
if (this.lexer.isAtEnd()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.lexer.moveToNextNonEmptyLine();
|
||||
|
||||
if (this.lexer.isAtEnd()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Determine the root structure type
|
||||
this.lexer.skipWhitespace();
|
||||
|
||||
if (this.lexer.getCurrentChar() === '-') {
|
||||
// Check if this is an array item or a negative number
|
||||
// Look at the character after the dash
|
||||
const nextChar = this.lexer.peek();
|
||||
if (nextChar === ' ' || nextChar === '\t' || nextChar === '' || nextChar === '#') {
|
||||
// It's an array item (dash followed by whitespace/end/comment)
|
||||
return this.parseBlockArray(0);
|
||||
} else {
|
||||
// It's likely a negative number or other value, treat as single value
|
||||
return this.parseValue();
|
||||
}
|
||||
} else if (this.lexer.getCurrentChar() === '[') {
|
||||
// Root is an inline array
|
||||
return this.parseInlineArray();
|
||||
} else if (this.lexer.getCurrentChar() === '{') {
|
||||
// Root is an inline object
|
||||
return this.parseInlineObject();
|
||||
} else {
|
||||
// Check if this looks like a key-value pair by looking for a colon
|
||||
// For single values, there shouldn't be a colon
|
||||
const currentLine = this.lexer.getCurrentLineText();
|
||||
const currentPos = this.lexer.getCurrentCharNumber();
|
||||
const remainingLine = currentLine.substring(currentPos);
|
||||
|
||||
// Check if there's a colon that's not inside quotes
|
||||
let hasColon = false;
|
||||
let inQuotes = false;
|
||||
let quoteChar = '';
|
||||
|
||||
for (let i = 0; i < remainingLine.length; i++) {
|
||||
const char = remainingLine[i];
|
||||
|
||||
if (!inQuotes && (char === '"' || char === `'`)) {
|
||||
inQuotes = true;
|
||||
quoteChar = char;
|
||||
} else if (inQuotes && char === quoteChar) {
|
||||
inQuotes = false;
|
||||
quoteChar = '';
|
||||
} else if (!inQuotes && char === ':') {
|
||||
hasColon = true;
|
||||
break;
|
||||
} else if (!inQuotes && char === '#') {
|
||||
// Comment starts, stop looking
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasColon) {
|
||||
// Root is an object
|
||||
return this.parseBlockObject(0);
|
||||
} else {
|
||||
// Root is a single value
|
||||
return this.parseValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -153,6 +153,10 @@ export function isPortFree(port: number, timeout: number): Promise<boolean> {
|
||||
return findFreePortFaster(port, 0, timeout).then(port => port !== 0);
|
||||
}
|
||||
|
||||
interface ServerError {
|
||||
code?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener.
|
||||
*/
|
||||
@ -180,8 +184,8 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo
|
||||
server.on('listening', () => {
|
||||
doResolve(startPort, resolve);
|
||||
});
|
||||
server.on('error', err => {
|
||||
if (err && ((<any>err).code === 'EADDRINUSE' || (<any>err).code === 'EACCES') && (countTried < giveUpAfter)) {
|
||||
server.on('error', (err: ServerError) => {
|
||||
if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES') && (countTried < giveUpAfter)) {
|
||||
startPort++;
|
||||
countTried++;
|
||||
server.listen(startPort, hostname);
|
||||
|
||||
@ -9,6 +9,7 @@ import { sumBy } from '../../../../base/common/arrays';
|
||||
import { BugIndicatingError } from '../../../../base/common/errors';
|
||||
import { OffsetRange } from '../ranges/offsetRange';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export abstract class BaseEdit<T extends BaseReplacement<T> = BaseReplacement<any>, TEdit extends BaseEdit<T, TEdit> = BaseEdit<T, any>> {
|
||||
constructor(
|
||||
public readonly replacements: readonly T[],
|
||||
|
||||
@ -22,7 +22,7 @@ export class LineEdit {
|
||||
return new LineEdit(data.map(e => LineReplacement.deserialize(e)));
|
||||
}
|
||||
|
||||
public static fromEdit(edit: BaseStringEdit, initialValue: AbstractText): LineEdit {
|
||||
public static fromStringEdit(edit: BaseStringEdit, initialValue: AbstractText): LineEdit {
|
||||
const textEdit = TextEdit.fromStringEdit(edit, initialValue);
|
||||
return LineEdit.fromTextEdit(textEdit, initialValue);
|
||||
}
|
||||
@ -408,7 +408,7 @@ export namespace SerializedLineReplacement {
|
||||
&& typeof thing[0] === 'number'
|
||||
&& typeof thing[1] === 'number'
|
||||
&& Array.isArray(thing[2])
|
||||
&& thing[2].every((e: any) => typeof e === 'string')
|
||||
&& thing[2].every((e: unknown) => typeof e === 'string')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import { StringText } from '../text/abstractText';
|
||||
import { BaseEdit, BaseReplacement } from './edit';
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseStringReplacement<any>, TEdit extends BaseStringEdit<T, TEdit> = BaseStringEdit<any, any>> extends BaseEdit<T, TEdit> {
|
||||
get TReplacement(): T {
|
||||
throw new Error('TReplacement is not defined for BaseStringEdit');
|
||||
@ -22,6 +23,7 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
|
||||
}
|
||||
let result = edits[0];
|
||||
for (let i = 1; i < edits.length; i++) {
|
||||
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
|
||||
result = result.compose(edits[i]) as any;
|
||||
}
|
||||
return result;
|
||||
@ -171,11 +173,11 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
|
||||
return e.toEdit();
|
||||
}
|
||||
|
||||
removeCommonSuffixAndPrefix(source: string): TEdit {
|
||||
public removeCommonSuffixAndPrefix(source: string): TEdit {
|
||||
return this._createNew(this.replacements.map(e => e.removeCommonSuffixAndPrefix(source))).normalize();
|
||||
}
|
||||
|
||||
applyOnText(docContents: StringText): StringText {
|
||||
public applyOnText(docContents: StringText): StringText {
|
||||
return new StringText(this.apply(docContents.value));
|
||||
}
|
||||
|
||||
@ -190,6 +192,7 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export abstract class BaseStringReplacement<T extends BaseStringReplacement<T> = BaseStringReplacement<any>> extends BaseReplacement<T> {
|
||||
constructor(
|
||||
range: OffsetRange,
|
||||
@ -201,7 +204,7 @@ export abstract class BaseStringReplacement<T extends BaseStringReplacement<T> =
|
||||
getNewLength(): number { return this.newText.length; }
|
||||
|
||||
override toString(): string {
|
||||
return `${this.replaceRange} -> "${this.newText}"`;
|
||||
return `${this.replaceRange} -> ${JSON.stringify(this.newText)}`;
|
||||
}
|
||||
|
||||
replace(str: string): string {
|
||||
@ -523,8 +526,14 @@ export class AnnotatedStringEdit<T extends IEditData<T>> extends BaseStringEdit<
|
||||
return new AnnotatedStringEdit<T>(replacements);
|
||||
}
|
||||
|
||||
toStringEdit(): StringEdit {
|
||||
return new StringEdit(this.replacements.map(e => new StringReplacement(e.replaceRange, e.newText)));
|
||||
public toStringEdit(filter?: (replacement: AnnotatedStringReplacement<T>) => boolean): StringEdit {
|
||||
const newReplacements: StringReplacement[] = [];
|
||||
for (const r of this.replacements) {
|
||||
if (!filter || filter(r)) {
|
||||
newReplacements.push(new StringReplacement(r.replaceRange, r.newText));
|
||||
}
|
||||
}
|
||||
return new StringEdit(newReplacements);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -169,11 +169,11 @@ export class Position {
|
||||
/**
|
||||
* Test if `obj` is an `IPosition`.
|
||||
*/
|
||||
public static isIPosition(obj: any): obj is IPosition {
|
||||
public static isIPosition(obj: unknown): obj is IPosition {
|
||||
return (
|
||||
obj
|
||||
&& (typeof obj.lineNumber === 'number')
|
||||
&& (typeof obj.column === 'number')
|
||||
!!obj
|
||||
&& (typeof (obj as IPosition).lineNumber === 'number')
|
||||
&& (typeof (obj as IPosition).column === 'number')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -392,13 +392,13 @@ export class Range {
|
||||
/**
|
||||
* Test if `obj` is an `IRange`.
|
||||
*/
|
||||
public static isIRange(obj: any): obj is IRange {
|
||||
public static isIRange(obj: unknown): obj is IRange {
|
||||
return (
|
||||
obj
|
||||
&& (typeof obj.startLineNumber === 'number')
|
||||
&& (typeof obj.startColumn === 'number')
|
||||
&& (typeof obj.endLineNumber === 'number')
|
||||
&& (typeof obj.endColumn === 'number')
|
||||
!!obj
|
||||
&& (typeof (obj as IRange).startLineNumber === 'number')
|
||||
&& (typeof (obj as IRange).startColumn === 'number')
|
||||
&& (typeof (obj as IRange).endLineNumber === 'number')
|
||||
&& (typeof (obj as IRange).endColumn === 'number')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
import { BugIndicatingError } from '../../../../base/common/errors';
|
||||
import { OffsetRange } from './offsetRange';
|
||||
import { Range } from '../range';
|
||||
import { IRange, Range } from '../range';
|
||||
import { findFirstIdxMonotonousOrArrLen, findLastIdxMonotonous, findLastMonotonous } from '../../../../base/common/arraysFind';
|
||||
import { Comparator, compareBy, numberComparator } from '../../../../base/common/arrays';
|
||||
|
||||
@ -19,11 +19,11 @@ export class LineRange {
|
||||
return new LineRange(startLineNumber, startLineNumber + length);
|
||||
}
|
||||
|
||||
public static fromRange(range: Range): LineRange {
|
||||
public static fromRange(range: IRange): LineRange {
|
||||
return new LineRange(range.startLineNumber, range.endLineNumber);
|
||||
}
|
||||
|
||||
public static fromRangeInclusive(range: Range): LineRange {
|
||||
public static fromRangeInclusive(range: IRange): LineRange {
|
||||
return new LineRange(range.startLineNumber, range.endLineNumber + 1);
|
||||
}
|
||||
|
||||
|
||||
@ -8,11 +8,11 @@
|
||||
import { assert } from '../../../../base/common/assert';
|
||||
import { splitLines } from '../../../../base/common/strings';
|
||||
import { Position } from '../position';
|
||||
import { PositionOffsetTransformer } from './positionToOffsetImpl';
|
||||
import { Range } from '../range';
|
||||
import { LineRange } from '../ranges/lineRange';
|
||||
import { TextLength } from './textLength';
|
||||
import { OffsetRange } from '../ranges/offsetRange';
|
||||
import { TextLength } from './textLength';
|
||||
import { PositionOffsetTransformer } from './positionToOffsetImpl';
|
||||
|
||||
export abstract class AbstractText {
|
||||
abstract getValueOfRange(range: Range): string;
|
||||
@ -124,4 +124,9 @@ export class StringText extends AbstractText {
|
||||
get length(): TextLength {
|
||||
return this._t.textLength;
|
||||
}
|
||||
|
||||
// Override the getTransformer method to return the cached transformer
|
||||
override getTransformer() {
|
||||
return this._t;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
export class SyncDescriptor<T> {
|
||||
|
||||
readonly ctor: any;
|
||||
readonly staticArguments: any[];
|
||||
readonly staticArguments: unknown[];
|
||||
readonly supportsDelayedInstantiation: boolean;
|
||||
|
||||
constructor(ctor: new (...args: any[]) => T, staticArguments: any[] = [], supportsDelayedInstantiation: boolean = false) {
|
||||
constructor(ctor: new (...args: any[]) => T, staticArguments: unknown[] = [], supportsDelayedInstantiation: boolean = false) {
|
||||
this.ctor = ctor;
|
||||
this.staticArguments = staticArguments;
|
||||
this.supportsDelayedInstantiation = supportsDelayedInstantiation;
|
||||
|
||||
@ -18,9 +18,14 @@ export namespace _util {
|
||||
export const DI_TARGET = '$di$target';
|
||||
export const DI_DEPENDENCIES = '$di$dependencies';
|
||||
|
||||
export function getServiceDependencies(ctor: any): { id: ServiceIdentifier<any>; index: number }[] {
|
||||
export function getServiceDependencies(ctor: DI_TARGET_OBJ): { id: ServiceIdentifier<any>; index: number }[] {
|
||||
return ctor[DI_DEPENDENCIES] || [];
|
||||
}
|
||||
|
||||
export interface DI_TARGET_OBJ extends Function {
|
||||
[DI_TARGET]: Function;
|
||||
[DI_DEPENDENCIES]: { id: ServiceIdentifier<any>; index: number }[];
|
||||
}
|
||||
}
|
||||
|
||||
// --- interfaces ------
|
||||
@ -91,12 +96,13 @@ export interface ServiceIdentifier<T> {
|
||||
type: T;
|
||||
}
|
||||
|
||||
function storeServiceDependency(id: Function, target: Function, index: number): void {
|
||||
if ((target as any)[_util.DI_TARGET] === target) {
|
||||
(target as any)[_util.DI_DEPENDENCIES].push({ id, index });
|
||||
|
||||
function storeServiceDependency(id: ServiceIdentifier<unknown>, target: Function, index: number): void {
|
||||
if ((target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] === target) {
|
||||
(target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES].push({ id, index });
|
||||
} else {
|
||||
(target as any)[_util.DI_DEPENDENCIES] = [{ id, index }];
|
||||
(target as any)[_util.DI_TARGET] = target;
|
||||
(target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES] = [{ id, index }];
|
||||
(target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] = target;
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,12 +115,12 @@ export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
|
||||
return _util.serviceIds.get(serviceId)!;
|
||||
}
|
||||
|
||||
const id = <any>function (target: Function, key: string, index: number) {
|
||||
const id = function (target: Function, key: string, index: number) {
|
||||
if (arguments.length !== 3) {
|
||||
throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
|
||||
}
|
||||
storeServiceDependency(id, target, index);
|
||||
};
|
||||
} as ServiceIdentifier<T>;
|
||||
|
||||
id.toString = () => serviceId;
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ export class InstantiationService implements IInstantiationService {
|
||||
|
||||
createInstance<T>(descriptor: SyncDescriptor0<T>): T;
|
||||
createInstance<Ctor extends new (...args: any[]) => unknown, R extends InstanceType<Ctor>>(ctor: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
|
||||
createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: any[]): unknown {
|
||||
createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: unknown[]): unknown {
|
||||
this._throwIfDisposed();
|
||||
|
||||
let _trace: Trace;
|
||||
@ -140,11 +140,11 @@ export class InstantiationService implements IInstantiationService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private _createInstance<T>(ctor: any, args: any[] = [], _trace: Trace): T {
|
||||
private _createInstance<T>(ctor: any, args: unknown[] = [], _trace: Trace): T {
|
||||
|
||||
// arguments defined by service decorators
|
||||
const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
|
||||
const serviceArgs: any[] = [];
|
||||
const serviceArgs: unknown[] = [];
|
||||
for (const dependency of serviceDependencies) {
|
||||
const service = this._getOrCreateServiceInstance(dependency.id, _trace);
|
||||
if (!service) {
|
||||
@ -288,7 +288,7 @@ export class InstantiationService implements IInstantiationService {
|
||||
return <T>this._getServiceInstanceOrDescriptor(id);
|
||||
}
|
||||
|
||||
private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
|
||||
private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
|
||||
if (this._services.get(id) instanceof SyncDescriptor) {
|
||||
return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace, this._servicesToMaybeDispose);
|
||||
} else if (this._parent) {
|
||||
@ -298,7 +298,7 @@ export class InstantiationService implements IInstantiationService {
|
||||
}
|
||||
}
|
||||
|
||||
private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set<any>): T {
|
||||
private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set<any>): T {
|
||||
if (!supportsDelayedInstantiation) {
|
||||
// eager instantiation
|
||||
const result = this._createInstance<T>(ctor, args, _trace);
|
||||
@ -327,6 +327,7 @@ export class InstantiationService implements IInstantiationService {
|
||||
// early listeners that we kept are now being subscribed to
|
||||
// the real service
|
||||
for (const [key, values] of earlyListeners) {
|
||||
// eslint-disable-next-line local/code-no-any-casts
|
||||
const candidate = <Event<any>>(<any>result)[key];
|
||||
if (typeof candidate === 'function') {
|
||||
for (const value of values) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user