From 3f4cb59fc540e3c6fa8a49faa0ab23df85265cc0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 21 Oct 2025 19:22:20 +0200 Subject: [PATCH] copy vscode sources, add base/common/yaml (#1468) * copy vscode sources, add base/common/yaml * fix tests (due to change of toStringEdit) --- .../copilot/script/setup/copySources.ts | 1 + .../test/common/editRebase.spec.ts | 34 +- .../inlineEdits/common/dataTypes/edit.ts | 2 +- .../common/dataTypes/rootedLineEdit.ts | 2 +- .../src/util/vs/base/common/actions.ts | 5 + .../copilot/src/util/vs/base/common/async.ts | 37 +- .../copilot/src/util/vs/base/common/buffer.ts | 2 +- .../copilot/src/util/vs/base/common/cache.ts | 33 + .../src/util/vs/base/common/cancellation.ts | 58 ++ .../util/vs/base/common/codiconsLibrary.ts | 17 +- .../copilot/src/util/vs/base/common/errors.ts | 1 + .../src/util/vs/base/common/extpath.ts | 8 +- .../copilot/src/util/vs/base/common/glob.ts | 2 +- .../copilot/src/util/vs/base/common/hash.ts | 8 +- .../src/util/vs/base/common/htmlContent.ts | 19 +- .../src/util/vs/base/common/lifecycle.ts | 58 +- .../copilot/src/util/vs/base/common/map.ts | 2 +- .../src/util/vs/base/common/network.ts | 1 + .../src/util/vs/base/common/numbers.ts | 59 -- .../src/util/vs/base/common/objects.ts | 4 +- .../vs/base/common/observableInternal/base.ts | 5 + .../observableInternal/changeTracker.ts | 4 + .../observableInternal/debugLocation.ts | 1 + .../common/observableInternal/debugName.ts | 1 + .../base/common/observableInternal/index.ts | 4 +- .../logging/consoleObservableLogger.ts | 1 + .../logging/debugGetDependencyGraph.ts | 117 +++ .../logging/debugger/debuggerRpc.ts | 1 + .../logging/debugger/rpc.ts | 1 + .../observables/baseObservable.ts | 10 + .../observableInternal/observables/derived.ts | 6 + .../observables/derivedImpl.ts | 13 + .../observables/observableFromEvent.ts | 7 +- .../reactions/autorunImpl.ts | 11 + .../vs/base/common/observableInternal/set.ts | 1 + .../observableInternal/utils/promise.ts | 4 + .../utils/utilsCancellation.ts | 2 + .../src/util/vs/base/common/platform.ts | 4 + .../src/util/vs/base/common/process.ts | 2 +- .../copilot/src/util/vs/base/common/stream.ts | 8 +- .../src/util/vs/base/common/strings.ts | 4 - .../src/util/vs/base/common/themables.ts | 13 + .../copilot/src/util/vs/base/common/types.ts | 12 +- .../copilot/src/util/vs/base/common/yaml.ts | 894 ++++++++++++++++++ .../copilot/src/util/vs/base/node/ports.ts | 8 +- .../util/vs/editor/common/core/edits/edit.ts | 1 + .../vs/editor/common/core/edits/lineEdit.ts | 4 +- .../vs/editor/common/core/edits/stringEdit.ts | 19 +- .../util/vs/editor/common/core/position.ts | 8 +- .../src/util/vs/editor/common/core/range.ts | 12 +- .../vs/editor/common/core/ranges/lineRange.ts | 6 +- .../editor/common/core/text/abstractText.ts | 9 +- .../instantiation/common/descriptors.ts | 4 +- .../instantiation/common/instantiation.ts | 22 +- .../common/instantiationService.ts | 11 +- 55 files changed, 1402 insertions(+), 181 deletions(-) create mode 100644 extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts create mode 100644 extensions/copilot/src/util/vs/base/common/yaml.ts diff --git a/extensions/copilot/script/setup/copySources.ts b/extensions/copilot/script/setup/copySources.ts index ac3c3b7679b..3d025f971aa 100644 --- a/extensions/copilot/script/setup/copySources.ts +++ b/extensions/copilot/script/setup/copySources.ts @@ -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', diff --git a/extensions/copilot/src/extension/inlineEdits/test/common/editRebase.spec.ts b/extensions/copilot/src/extension/inlineEdits/test/common/editRebase.spec.ts index adeb145c184..02a96c28bf6 100644 --- a/extensions/copilot/src/extension/inlineEdits/test/common/editRebase.spec.ts +++ b/extensions/copilot/src/extension/inlineEdits/test/common/editRebase.spec.ts @@ -55,20 +55,14 @@ class Point3D { expect(res).toBeTypeOf('object'); const result = res as Exclude; 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; 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; 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; 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 = ` diff --git a/extensions/copilot/src/platform/inlineEdits/common/dataTypes/edit.ts b/extensions/copilot/src/platform/inlineEdits/common/dataTypes/edit.ts index 86ec05ce5e4..3b321550474 100644 --- a/extensions/copilot/src/platform/inlineEdits/common/dataTypes/edit.ts +++ b/extensions/copilot/src/platform/inlineEdits/common/dataTypes/edit.ts @@ -14,7 +14,7 @@ import { RootedLineEdit } from './rootedLineEdit'; export class RootedEdit, any> = StringEdit> { public static toLineEdit(edit: RootedEdit, any>>): LineEdit { - return LineEdit.fromEdit(edit.edit as StringEdit, edit.base); + return LineEdit.fromStringEdit(edit.edit as StringEdit, edit.base); } constructor( diff --git a/extensions/copilot/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts b/extensions/copilot/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts index a1b17e24417..8426050a084 100644 --- a/extensions/copilot/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts +++ b/extensions/copilot/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts @@ -13,7 +13,7 @@ ensureDependenciesAreSet(); export class RootedLineEdit { public static fromEdit(edit: RootedEdit): 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); } diff --git a/extensions/copilot/src/util/vs/base/common/actions.ts b/extensions/copilot/src/util/vs/base/common/actions.ts index c469f9e9330..7e5bbe0794e 100644 --- a/extensions/copilot/src/util/vs/base/common/actions.ts +++ b/extensions/copilot/src/util/vs/base/common/actions.ts @@ -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()); diff --git a/extensions/copilot/src/util/vs/base/common/async.ts b/extensions/copilot/src/util/vs/base/common/async.ts index ca93c1f73f8..f4c371a7d22 100644 --- a/extensions/copilot/src/util/vs/base/common/async.ts +++ b/extensions/copilot/src/util/vs/base/common/async.ts @@ -193,6 +193,10 @@ export interface ITask { (): T; } +export interface ICancellableTask { + (token: CancellationToken): T; +} + /** * A helper to prevent accumulation of sequential async tasks. * @@ -223,18 +227,19 @@ export class Throttler implements IDisposable { private activePromise: Promise | null; private queuedPromise: Promise | null; - private queuedPromiseFactory: ITask> | null; - - private isDisposed = false; + private queuedPromiseFactory: ICancellableTask> | null; + private cancellationTokenSource: CancellationTokenSource; constructor() { this.activePromise = null; this.queuedPromise = null; this.queuedPromiseFactory = null; + + this.cancellationTokenSource = new CancellationTokenSource(); } - queue(promiseFactory: ITask>): Promise { - if (this.isDisposed) { + queue(promiseFactory: ICancellableTask>): Promise { + 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 { this.throttler = new Throttler(); } - trigger(promiseFactory: ITask>, delay?: number): Promise { + trigger(promiseFactory: ICancellableTask>, delay?: number): Promise { return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise; } @@ -1717,6 +1722,12 @@ const enum DeferredOutcome { */ export class DeferredPromise { + public static fromPromise(promise: Promise): DeferredPromise { + const deferred = new DeferredPromise(); + deferred.settleWith(promise); + return deferred; + } + private completeCallback!: ValueCallback; private errorCallback!: (err: unknown) => void; private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T }; @@ -1747,6 +1758,10 @@ export class DeferredPromise { } public complete(value: T) { + if (this.isSettled) { + return Promise.resolve(); + } + return new Promise(resolve => { this.completeCallback(value); this.outcome = { outcome: DeferredOutcome.Resolved, value }; @@ -1755,6 +1770,10 @@ export class DeferredPromise { } public error(err: unknown) { + if (this.isSettled) { + return Promise.resolve(); + } + return new Promise(resolve => { this.errorCallback(err); this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; diff --git a/extensions/copilot/src/util/vs/base/common/buffer.ts b/extensions/copilot/src/util/vs/base/common/buffer.ts index 366401632ea..e63a51a8faf 100644 --- a/extensions/copilot/src/util/vs/base/common/buffer.ts +++ b/extensions/copilot/src/util/vs/base/common/buffer.ts @@ -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; } diff --git a/extensions/copilot/src/util/vs/base/common/cache.ts b/extensions/copilot/src/util/vs/base/common/cache.ts index 1835913aa11..038919107e4 100644 --- a/extensions/copilot/src/util/vs/base/common/cache.ts +++ b/extensions/copilot/src/util/vs/base/common/cache.ts @@ -120,3 +120,36 @@ export class CachedFunction { return value; } } + +/** + * Uses an unbounded cache to memoize the results of the given function. +*/ +export class WeakCachedFunction { + private readonly _map = new WeakMap(); + + private readonly _fn: (arg: TArg) => TComputed; + private readonly _computeKey: (arg: TArg) => unknown; + + constructor(fn: (arg: TArg) => TComputed); + constructor(options: ICacheOptions, fn: (arg: TArg) => TComputed); + constructor(arg1: ICacheOptions | ((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; + } +} diff --git a/extensions/copilot/src/util/vs/base/common/cancellation.ts b/extensions/copilot/src/util/vs/base/common/cancellation.ts index b0471b27742..dc6a1eababb 100644 --- a/extensions/copilot/src/util/vs/base/common/cancellation.ts +++ b/extensions/copilot/src/util/vs/base/common/cancellation.ts @@ -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(); + } +} diff --git a/extensions/copilot/src/util/vs/base/common/codiconsLibrary.ts b/extensions/copilot/src/util/vs/base/common/codiconsLibrary.ts index f2270e038df..97047a19773 100644 --- a/extensions/copilot/src/util/vs/base/common/codiconsLibrary.ts +++ b/extensions/copilot/src/util/vs/base/common/codiconsLibrary.ts @@ -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; diff --git a/extensions/copilot/src/util/vs/base/common/errors.ts b/extensions/copilot/src/util/vs/base/common/errors.ts index a2c1f2dbe27..add094e7f3a 100644 --- a/extensions/copilot/src/util/vs/base/common/errors.ts +++ b/extensions/copilot/src/util/vs/base/common/errors.ts @@ -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 = (error).stacktrace || (error).stack; return { $isError: true, diff --git a/extensions/copilot/src/util/vs/base/common/extpath.ts b/extensions/copilot/src/util/vs/base/common/extpath.ts index 0a83d0cee1c..d4c77c3182d 100644 --- a/extensions/copilot/src/util/vs/base/common/extpath.ts +++ b/extensions/copilot/src/util/vs/base/common/extpath.ts @@ -361,14 +361,14 @@ export interface IPathWithLineAndColumn { export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn { const segments = rawPath.split(':'); // C:\file.txt:: - 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) { diff --git a/extensions/copilot/src/util/vs/base/common/glob.ts b/extensions/copilot/src/util/vs/base/common/glob.ts index c408fa09bc9..42b19c0ad96 100644 --- a/extensions/copilot/src/util/vs/base/common/glob.ts +++ b/extensions/copilot/src/util/vs/base/common/glob.ts @@ -510,7 +510,7 @@ function toRegExp(pattern: string): ParsedStringPattern { return typeof path === 'string' && regExp.test(path) ? pattern : null; }; - } catch (error) { + } catch { return NULL; } } diff --git a/extensions/copilot/src/util/vs/base/common/hash.ts b/extensions/copilot/src/util/vs/base/common/hash.ts index 75b30da9db3..208d37f384c 100644 --- a/extensions/copilot/src/util/vs/base/common/hash.ts +++ b/extensions/copilot/src/util/vs/base/common/hash.ts @@ -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((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)[key], hashVal); }, initialHashVal); } diff --git a/extensions/copilot/src/util/vs/base/common/htmlContent.ts b/extensions/copilot/src/util/vs/base/common/htmlContent.ts index 38202f89388..7ba6689e6ff 100644 --- a/extensions/copilot/src/util/vs/base/common/htmlContent.ts +++ b/extensions/copilot/src/util/vs/base/common/htmlContent.ts @@ -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, + }); } diff --git a/extensions/copilot/src/util/vs/base/common/lifecycle.ts b/extensions/copilot/src/util/vs/base/common/lifecycle.ts index c4bf1c2a55a..bece9fe4b96 100644 --- a/extensions/copilot/src/util/vs/base/common/lifecycle.ts +++ b/extensions/copilot/src/util/vs/base/common/lifecycle.ts @@ -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(thing: E): thing is E & IDisposable { + // eslint-disable-next-line local/code-no-any-casts return typeof thing === 'object' && thing !== null && typeof (thing).dispose === 'function' && (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 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 { private readonly references: Map = new Map(); - acquire(key: string, ...args: any[]): IReference { + acquire(key: string, ...args: unknown[]): IReference { let reference = this.references.get(key); if (!reference) { @@ -672,7 +710,7 @@ export abstract class ReferenceCollection { 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; } diff --git a/extensions/copilot/src/util/vs/base/common/map.ts b/extensions/copilot/src/util/vs/base/common/map.ts index 73602346db1..d88a8a53e9f 100644 --- a/extensions/copilot/src/util/vs/base/common/map.ts +++ b/extensions/copilot/src/util/vs/base/common/map.ts @@ -123,7 +123,7 @@ export class ResourceMap implements Map { clb = clb.bind(thisArg); } for (const [_, entry] of this.map) { - clb(entry.value, entry.uri, this); + clb(entry.value, entry.uri, this); } } diff --git a/extensions/copilot/src/util/vs/base/common/network.ts b/extensions/copilot/src/util/vs/base/common/network.ts index 1c92507efbb..f630e358e67 100644 --- a/extensions/copilot/src/util/vs/base/common/network.ts +++ b/extensions/copilot/src/util/vs/base/common/network.ts @@ -415,6 +415,7 @@ export namespace COI { * isn't enabled the current context */ export function addSearchParam(urlOrSearch: URLSearchParams | Record, coop: boolean, coep: boolean): void { + // eslint-disable-next-line local/code-no-any-casts if (!(globalThis).crossOriginIsolated) { // depends on the current context being COI return; diff --git a/extensions/copilot/src/util/vs/base/common/numbers.ts b/extensions/copilot/src/util/vs/base/common/numbers.ts index 702dac06a03..9d77d9aeb7f 100644 --- a/extensions/copilot/src/util/vs/base/common/numbers.ts +++ b/extensions/copilot/src/util/vs/base/common/numbers.ts @@ -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; diff --git a/extensions/copilot/src/util/vs/base/common/objects.ts b/extensions/copilot/src/util/vs/base/common/objects.ts index 35e9c092945..80b9ba03b22 100644 --- a/extensions/copilot/src/util/vs/base/common/objects.ts +++ b/extensions/copilot/src/util/vs/base/common/objects.ts @@ -71,10 +71,10 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set): throw new Error('Cannot clone recursive data-structure'); } seen.add(obj); - const r2 = {}; + const r2: Record = {}; 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); diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/base.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/base.ts index c38beae3a90..dfee51e3deb 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/base.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/base.ts @@ -97,6 +97,11 @@ export interface IObservableWithChange { */ readonly debugName: string; + /** + * ONLY FOR DEBUGGING! + */ + debugGetDependencyGraph(): string; + /** * This property captures the type of the change object. Do not use it at runtime! */ diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/changeTracker.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/changeTracker.ts index 2ab6d76704f..03f8d8d1874 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/changeTracker.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/changeTracker.ts @@ -33,6 +33,7 @@ export function recordChanges { return { createChangeSummary: (_previousChangeSummary) => { + // eslint-disable-next-line local/code-no-any-casts return { changes: [], } as any; @@ -40,6 +41,7 @@ export function recordChanges { + // eslint-disable-next-line local/code-no-any-casts return { changes: [], } as any; @@ -76,6 +79,7 @@ export function recordChangesLazy[] = []; + // eslint-disable-next-line local/code-no-any-casts (derived as any).__debugUpdating = updating; const existingBeginUpdate = derived.beginUpdate; diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts new file mode 100644 index 00000000000..a92ec9e41b9 --- /dev/null +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts @@ -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 | 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 | IObserver>(); + return formatObservableInfo(info, 0, alreadyListed).trim(); +} + +function formatObservableInfo(info: Info, indentLevel: number, alreadyListed: Set | 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 | 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 | IObserver): Info { + return new Info( + obs, + '(unknown)', + 'unknown', + undefined, + 'unknown', + [] + ); + } + + constructor( + public readonly sourceObj: IObservable | IObserver, + public readonly name: string, + public readonly type: string, + public readonly value: any, + public readonly state: string, + public readonly dependencies: Info[] + ) { } +} diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts index 35067fdc328..89b3bc3adc4 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts @@ -11,6 +11,7 @@ export function registerDebugChannel( channelId: T['channelId'], createClient: () => T['client'], ): SimpleTypedRpcConnection> { + // eslint-disable-next-line local/code-no-any-casts const g = globalThis as any as GlobalObj; let queuedNotifications: unknown[] = []; diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/rpc.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/rpc.ts index e9b02353738..a549e7c0b09 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/rpc.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/logging/debugger/rpc.ts @@ -93,6 +93,7 @@ export class SimpleTypedRpcConnection { } }); + // eslint-disable-next-line local/code-no-any-casts this.api = { notifications: notifications, requests: requests } as any; } } diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/baseObservable.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/baseObservable.ts index 7749b4b5008..ddfad66e4bb 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/baseObservable.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/baseObservable.ts @@ -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 implements IObservableWithChange { get TChange(): TChange { return null!; } @@ -123,6 +129,10 @@ export abstract class ConvenientObservable implements IObservableWit protected get debugValue() { return this.get(); } + + debugGetDependencyGraph(): string { + return _debugGetDependencyGraph(this); + } } export abstract class BaseObservable extends ConvenientObservable { diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derived.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derived.ts index 7414b19e82c..29415125e75 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derived.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derived.ts @@ -37,7 +37,9 @@ export function derived( ); } 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(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(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; } diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts index a70e94f65a6..2952f49e944 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts @@ -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 ''; + } +} + export class Derived extends BaseObservable implements IDerivedReader, IObserver { private _state = DerivedState.initial; private _value: T | undefined = undefined; @@ -302,6 +312,7 @@ export class Derived 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 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 extends BaseObserv } public debugSetValue(newValue: unknown) { + // eslint-disable-next-line local/code-no-any-casts this._value = newValue as any; } diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts index d8af1bb2c05..b168bf6ef18 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts @@ -149,9 +149,14 @@ export class FromEventObservable extends BaseObservable { } } - 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 { diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts index a6d02351e85..aaeb212463f 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts @@ -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 ''; + } +} + export class AutorunObserver implements IObserver, IReaderWithStore, IDisposable { private _state = AutorunState.stale; private _updateCount = 0; @@ -175,6 +184,7 @@ export class AutorunObserver 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 implements IObserver, IReader updateCount: this._updateCount, dependencies: this._dependencies, state: this._state, + stateStr: autorunStateToString(this._state), }; } diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/set.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/set.ts index 7457ccc56b4..dc6b431e09a 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/set.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/set.ts @@ -50,6 +50,7 @@ export class ObservableSet implements Set { forEach(callbackfn: (value: T, value2: T, set: Set) => 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); }); } diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/utils/promise.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/utils/promise.ts index 73a23d50d94..430272f2455 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/utils/promise.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/utils/promise.ts @@ -43,6 +43,10 @@ export class ObservablePromise { return new ObservablePromise(fn()); } + public static resolved(value: T): ObservablePromise { + return new ObservablePromise(Promise.resolve(value)); + } + private readonly _value = observableValue | undefined>(this, undefined); /** diff --git a/extensions/copilot/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts b/extensions/copilot/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts index f6628206998..8b4b408ea30 100644 --- a/extensions/copilot/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts +++ b/extensions/copilot/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts @@ -77,10 +77,12 @@ export function derivedWithCancellationToken(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; } diff --git a/extensions/copilot/src/util/vs/base/common/platform.ts b/extensions/copilot/src/util/vs/base/common/platform.ts index 438ece013ed..488a41450cd 100644 --- a/extensions/copilot/src/util/vs/base/common/platform.ts +++ b/extensions/copilot/src/util/vs/base/common/platform.ts @@ -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; +} diff --git a/extensions/copilot/src/util/vs/base/common/process.ts b/extensions/copilot/src/util/vs/base/common/process.ts index 190b8659ca1..0e0c3cb4b42 100644 --- a/extensions/copilot/src/util/vs/base/common/process.ts +++ b/extensions/copilot/src/util/vs/base/common/process.ts @@ -11,7 +11,7 @@ let safeProcess: Omit & { 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 = { diff --git a/extensions/copilot/src/util/vs/base/common/stream.ts b/extensions/copilot/src/util/vs/base/common/stream.ts index 9f29739b1fa..d07ec731695 100644 --- a/extensions/copilot/src/util/vs/base/common/stream.ts +++ b/extensions/copilot/src/util/vs/base/common/stream.ts @@ -333,14 +333,14 @@ class WriteableStreamImpl implements WriteableStream { 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 implements WriteableStream { 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 implements WriteableStream { 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 diff --git a/extensions/copilot/src/util/vs/base/common/strings.ts b/extensions/copilot/src/util/vs/base/common/strings.ts index 6bf18572472..578c5b2be63 100644 --- a/extensions/copilot/src/util/vs/base/common/strings.ts +++ b/extensions/copilot/src/util/vs/base/common/strings.ts @@ -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; diff --git a/extensions/copilot/src/util/vs/base/common/themables.ts b/extensions/copilot/src/util/vs/base/common/themables.ts index 442aeede0ec..61f711bf75f 100644 --- a/extensions/copilot/src/util/vs/base/common/themables.ts +++ b/extensions/copilot/src/util/vs/base/common/themables.ts @@ -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; + } } diff --git a/extensions/copilot/src/util/vs/base/common/types.ts b/extensions/copilot/src/util/vs/base/common/types.ts index dc8802bbd34..eaeaf03ccb2 100644 --- a/extensions/copilot/src/util/vs/base/common/types.ts +++ b/extensions/copilot/src/util/vs/base/common/types.ts @@ -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) && (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(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(obj: unknown): obj is Iterable { + // 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(obj: unknown): obj is Iterable { * @returns whether the provided parameter is an Iterable, casting to the given generic */ export function isAsyncIterable(obj: unknown): obj is AsyncIterable { + // 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; } diff --git a/extensions/copilot/src/util/vs/base/common/yaml.ts b/extensions/copilot/src/util/vs/base/common/yaml.ts new file mode 100644 index 00000000000..c5ea1c52339 --- /dev/null +++ b/extensions/copilot/src/util/vs/base/common/yaml.ts @@ -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(); + + // 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(); + } + } + } +} + + diff --git a/extensions/copilot/src/util/vs/base/node/ports.ts b/extensions/copilot/src/util/vs/base/node/ports.ts index 3336aeef402..5661640407a 100644 --- a/extensions/copilot/src/util/vs/base/node/ports.ts +++ b/extensions/copilot/src/util/vs/base/node/ports.ts @@ -153,6 +153,10 @@ export function isPortFree(port: number, timeout: number): Promise { 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 && ((err).code === 'EADDRINUSE' || (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); diff --git a/extensions/copilot/src/util/vs/editor/common/core/edits/edit.ts b/extensions/copilot/src/util/vs/editor/common/core/edits/edit.ts index b262d2985d3..73cc883e709 100644 --- a/extensions/copilot/src/util/vs/editor/common/core/edits/edit.ts +++ b/extensions/copilot/src/util/vs/editor/common/core/edits/edit.ts @@ -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 = BaseReplacement, TEdit extends BaseEdit = BaseEdit> { constructor( public readonly replacements: readonly T[], diff --git a/extensions/copilot/src/util/vs/editor/common/core/edits/lineEdit.ts b/extensions/copilot/src/util/vs/editor/common/core/edits/lineEdit.ts index 28cadcf170e..f6e3eb120a2 100644 --- a/extensions/copilot/src/util/vs/editor/common/core/edits/lineEdit.ts +++ b/extensions/copilot/src/util/vs/editor/common/core/edits/lineEdit.ts @@ -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') ); } } diff --git a/extensions/copilot/src/util/vs/editor/common/core/edits/stringEdit.ts b/extensions/copilot/src/util/vs/editor/common/core/edits/stringEdit.ts index 8a99701542a..103e65e00bf 100644 --- a/extensions/copilot/src/util/vs/editor/common/core/edits/stringEdit.ts +++ b/extensions/copilot/src/util/vs/editor/common/core/edits/stringEdit.ts @@ -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 = BaseStringReplacement, TEdit extends BaseStringEdit = BaseStringEdit> extends BaseEdit { get TReplacement(): T { throw new Error('TReplacement is not defined for BaseStringEdit'); @@ -22,6 +23,7 @@ export abstract class BaseStringEdit = 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 = 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 = BaseSt } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export abstract class BaseStringReplacement = BaseStringReplacement> extends BaseReplacement { constructor( range: OffsetRange, @@ -201,7 +204,7 @@ export abstract class BaseStringReplacement = 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> extends BaseStringEdit< return new AnnotatedStringEdit(replacements); } - toStringEdit(): StringEdit { - return new StringEdit(this.replacements.map(e => new StringReplacement(e.replaceRange, e.newText))); + public toStringEdit(filter?: (replacement: AnnotatedStringReplacement) => 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); } } diff --git a/extensions/copilot/src/util/vs/editor/common/core/position.ts b/extensions/copilot/src/util/vs/editor/common/core/position.ts index 4d1e1592132..8e0c83e621f 100644 --- a/extensions/copilot/src/util/vs/editor/common/core/position.ts +++ b/extensions/copilot/src/util/vs/editor/common/core/position.ts @@ -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') ); } diff --git a/extensions/copilot/src/util/vs/editor/common/core/range.ts b/extensions/copilot/src/util/vs/editor/common/core/range.ts index 3058aacbc93..c82380fab2e 100644 --- a/extensions/copilot/src/util/vs/editor/common/core/range.ts +++ b/extensions/copilot/src/util/vs/editor/common/core/range.ts @@ -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') ); } diff --git a/extensions/copilot/src/util/vs/editor/common/core/ranges/lineRange.ts b/extensions/copilot/src/util/vs/editor/common/core/ranges/lineRange.ts index 85191773d84..29065b7655a 100644 --- a/extensions/copilot/src/util/vs/editor/common/core/ranges/lineRange.ts +++ b/extensions/copilot/src/util/vs/editor/common/core/ranges/lineRange.ts @@ -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); } diff --git a/extensions/copilot/src/util/vs/editor/common/core/text/abstractText.ts b/extensions/copilot/src/util/vs/editor/common/core/text/abstractText.ts index 948eec8f5bb..fbf39f061cc 100644 --- a/extensions/copilot/src/util/vs/editor/common/core/text/abstractText.ts +++ b/extensions/copilot/src/util/vs/editor/common/core/text/abstractText.ts @@ -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; + } } diff --git a/extensions/copilot/src/util/vs/platform/instantiation/common/descriptors.ts b/extensions/copilot/src/util/vs/platform/instantiation/common/descriptors.ts index 2e92237e6fc..776288feba0 100644 --- a/extensions/copilot/src/util/vs/platform/instantiation/common/descriptors.ts +++ b/extensions/copilot/src/util/vs/platform/instantiation/common/descriptors.ts @@ -8,10 +8,10 @@ export class SyncDescriptor { 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; diff --git a/extensions/copilot/src/util/vs/platform/instantiation/common/instantiation.ts b/extensions/copilot/src/util/vs/platform/instantiation/common/instantiation.ts index 99c386ccce9..2efe0839a98 100644 --- a/extensions/copilot/src/util/vs/platform/instantiation/common/instantiation.ts +++ b/extensions/copilot/src/util/vs/platform/instantiation/common/instantiation.ts @@ -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; index: number }[] { + export function getServiceDependencies(ctor: DI_TARGET_OBJ): { id: ServiceIdentifier; index: number }[] { return ctor[DI_DEPENDENCIES] || []; } + + export interface DI_TARGET_OBJ extends Function { + [DI_TARGET]: Function; + [DI_DEPENDENCIES]: { id: ServiceIdentifier; index: number }[]; + } } // --- interfaces ------ @@ -91,12 +96,13 @@ export interface ServiceIdentifier { 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, 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(serviceId: string): ServiceIdentifier { return _util.serviceIds.get(serviceId)!; } - const id = 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; id.toString = () => serviceId; diff --git a/extensions/copilot/src/util/vs/platform/instantiation/common/instantiationService.ts b/extensions/copilot/src/util/vs/platform/instantiation/common/instantiationService.ts index b6c980a2361..78511c8f0b2 100644 --- a/extensions/copilot/src/util/vs/platform/instantiation/common/instantiationService.ts +++ b/extensions/copilot/src/util/vs/platform/instantiation/common/instantiationService.ts @@ -124,7 +124,7 @@ export class InstantiationService implements IInstantiationService { createInstance(descriptor: SyncDescriptor0): T; createInstance unknown, R extends InstanceType>(ctor: Ctor, ...args: GetLeadingNonServiceArgs>): R; - createInstance(ctorOrDescriptor: any | SyncDescriptor, ...rest: any[]): unknown { + createInstance(ctorOrDescriptor: any | SyncDescriptor, ...rest: unknown[]): unknown { this._throwIfDisposed(); let _trace: Trace; @@ -140,11 +140,11 @@ export class InstantiationService implements IInstantiationService { return result; } - private _createInstance(ctor: any, args: any[] = [], _trace: Trace): T { + private _createInstance(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 this._getServiceInstanceOrDescriptor(id); } - private _createServiceInstanceWithOwner(id: ServiceIdentifier, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T { + private _createServiceInstanceWithOwner(id: ServiceIdentifier, 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(id: ServiceIdentifier, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set): T { + private _createServiceInstance(id: ServiceIdentifier, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set): T { if (!supportsDelayedInstantiation) { // eager instantiation const result = this._createInstance(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 = >(result)[key]; if (typeof candidate === 'function') { for (const value of values) {