diff --git a/script/setup/copySources.ts b/script/setup/copySources.ts index e1cafd3e7..74f1385c8 100644 --- a/script/setup/copySources.ts +++ b/script/setup/copySources.ts @@ -101,9 +101,6 @@ async function doIt(filepaths: string[]) { newSource = newSource.slice(0, edit.pos + 1) + edit.newText + newSource.slice(edit.end + 1); } - if (filepath.endsWith('src/vs/nls.ts')) { - newSource = 'declare var document: any;\n\n' + newSource; - } newSource = '//!!! DO NOT modify, this file was COPIED from \'microsoft/vscode\'\n\n' + newSource; seen.set(filepath, { @@ -137,6 +134,7 @@ async function doIt(filepaths: string[]) { 'vs/base/common/cancellation.ts', 'vs/base/common/charCode.ts', 'vs/base/common/errors.ts', + 'vs/base/common/errorMessage.ts', 'vs/base/common/event.ts', 'vs/base/common/functional.ts', 'vs/base/common/glob.ts', @@ -149,6 +147,7 @@ async function doIt(filepaths: string[]) { 'vs/base/common/numbers.ts', 'vs/base/common/objects.ts', 'vs/base/common/resources.ts', + 'vs/base/common/sseParser.ts', 'vs/base/common/strings.ts', 'vs/base/common/ternarySearchTree.ts', 'vs/base/common/themables.ts', @@ -164,6 +163,9 @@ async function doIt(filepaths: string[]) { 'vs/platform/instantiation/common/instantiationService.ts', + 'vs/editor/common/core/edits/lineEdit.ts', + 'vs/editor/common/core/text/positionToOffset.ts', + // SPECIAL IMPLICIT DEPENDENCIES 'typings/vscode-globals-nls.d.ts', 'typings/vscode-globals-product.d.ts', diff --git a/src/extension/contextKeys/vscode-node/contextKeys.contribution.ts b/src/extension/contextKeys/vscode-node/contextKeys.contribution.ts index aa4dca412..1b1c56bbe 100644 --- a/src/extension/contextKeys/vscode-node/contextKeys.contribution.ts +++ b/src/extension/contextKeys/vscode-node/contextKeys.contribution.ts @@ -40,7 +40,7 @@ const debugContextKey = 'github.copilot.chat.debug'; export class ContextKeysContribution extends Disposable { private _needsOfflineCheck = false; - private _scheduledOfflineCheck: NodeJS.Timeout | undefined; + private _scheduledOfflineCheck: TimeoutHandle | undefined; private _showLogView = false; constructor( diff --git a/src/extension/inlineEdits/common/common.ts b/src/extension/inlineEdits/common/common.ts index df2c355e6..55d91c6e8 100644 --- a/src/extension/inlineEdits/common/common.ts +++ b/src/extension/inlineEdits/common/common.ts @@ -4,8 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable } from '../../../util/vs/base/common/lifecycle'; +import { ensureDependenciesAreSet } from '../../../util/vs/editor/common/core/text/positionToOffset'; export function createTimeout(ms: number, cb: () => void): IDisposable { const t = setTimeout(cb, ms); return toDisposable(() => clearTimeout(t)); } + +ensureDependenciesAreSet(); \ No newline at end of file diff --git a/src/extension/inlineEdits/node/nextEditProviderTelemetry.ts b/src/extension/inlineEdits/node/nextEditProviderTelemetry.ts index e3b712119..f977dff7f 100644 --- a/src/extension/inlineEdits/node/nextEditProviderTelemetry.ts +++ b/src/extension/inlineEdits/node/nextEditProviderTelemetry.ts @@ -553,7 +553,7 @@ export class NextEditProviderTelemetryBuilder extends Disposable { export class TelemetrySender implements IDisposable { - private readonly _map = new Map(); + private readonly _map = new Map(); constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, diff --git a/src/extension/inlineEdits/node/serverPoweredInlineEditProvider.ts b/src/extension/inlineEdits/node/serverPoweredInlineEditProvider.ts index 89b15332f..a9d05d8d0 100644 --- a/src/extension/inlineEdits/node/serverPoweredInlineEditProvider.ts +++ b/src/extension/inlineEdits/node/serverPoweredInlineEditProvider.ts @@ -93,7 +93,7 @@ export class ServerPoweredInlineEditProvider implements IStatelessNextEditProvid const edits = response.edits.map(e => LineReplacement.deserialize(e)); const sortingPermutation = Permutation.createSortPermutation(edits, (a, b) => a.lineRange.startLineNumber - b.lineRange.startLineNumber); const lineEdit = new LineEdit(sortingPermutation.apply(edits)); - lineEdit.edits.forEach(edit => pushEdit(Result.ok({ edit }))); + lineEdit.replacements.forEach(edit => pushEdit(Result.ok({ edit }))); pushEdit(Result.error(new NoNextEditReason.NoSuggestions(request.documentBeforeEdits, undefined))); return StatelessNextEditResult.streaming(telemetryBuilder); } else { diff --git a/src/extension/inlineEdits/test/node/ignoreImportChanges.spec.ts b/src/extension/inlineEdits/test/node/ignoreImportChanges.spec.ts index fa3791f85..2ef553d70 100644 --- a/src/extension/inlineEdits/test/node/ignoreImportChanges.spec.ts +++ b/src/extension/inlineEdits/test/node/ignoreImportChanges.spec.ts @@ -48,7 +48,7 @@ class FooBar { `); const lineEdit = RootedEdit.toLineEdit(await computeDiff(doc1, doc2)); - expect(IgnoreImportChangesAspect.isImportChange(lineEdit.edits[0], 'typescript', doc1.getLines())).toBe(true); + expect(IgnoreImportChangesAspect.isImportChange(lineEdit.replacements[0], 'typescript', doc1.getLines())).toBe(true); }); @@ -69,7 +69,7 @@ class FooBar { `); const lineEdit = RootedEdit.toLineEdit(await computeDiff(doc1, doc2)); - expect(IgnoreImportChangesAspect.isImportChange(lineEdit.edits[0], 'typescript', doc1.getLines())).toBe(true); + expect(IgnoreImportChangesAspect.isImportChange(lineEdit.replacements[0], 'typescript', doc1.getLines())).toBe(true); }); test('ImportChange', async () => { @@ -88,7 +88,7 @@ class FooBar { `); const lineEdit = RootedEdit.toLineEdit(await computeDiff(doc1, doc2)); - expect(IgnoreImportChangesAspect.isImportChange(lineEdit.edits[0], 'typescript', doc1.getLines())).toBe(true); + expect(IgnoreImportChangesAspect.isImportChange(lineEdit.replacements[0], 'typescript', doc1.getLines())).toBe(true); }); @@ -109,6 +109,6 @@ class FooBar { `); const lineEdit = RootedEdit.toLineEdit(await computeDiff(doc1, doc2)); - expect(IgnoreImportChangesAspect.isImportChange(lineEdit.edits[0], 'typescript', doc1.getLines())).toBe(false); + expect(IgnoreImportChangesAspect.isImportChange(lineEdit.replacements[0], 'typescript', doc1.getLines())).toBe(false); }); }); diff --git a/src/extension/inlineEdits/test/node/nextEditProviderCaching.spec.ts b/src/extension/inlineEdits/test/node/nextEditProviderCaching.spec.ts index b459cd557..8fe116504 100644 --- a/src/extension/inlineEdits/test/node/nextEditProviderCaching.spec.ts +++ b/src/extension/inlineEdits/test/node/nextEditProviderCaching.spec.ts @@ -76,7 +76,7 @@ describe('NextEditProvider Caching', () => { ) ] ); - lineEdit.edits.forEach(edit => pushEdit(Result.ok({ edit }))); + lineEdit.replacements.forEach(edit => pushEdit(Result.ok({ edit }))); pushEdit(Result.error(new NoNextEditReason.NoSuggestions(request.documentBeforeEdits, undefined))); return StatelessNextEditResult.streaming(telemetryBuilder); } diff --git a/src/extension/inlineEdits/vscode-node/components/logContextRecorder.ts b/src/extension/inlineEdits/vscode-node/components/logContextRecorder.ts index 4cfa33a3c..d31812124 100644 --- a/src/extension/inlineEdits/vscode-node/components/logContextRecorder.ts +++ b/src/extension/inlineEdits/vscode-node/components/logContextRecorder.ts @@ -20,7 +20,7 @@ export class LogContextRecorder extends Disposable { public readonly logFilePath: string; private readonly _impl: Promise; - private readonly _shownSuggestions: DisposableMap void }>; + private readonly _shownSuggestions: DisposableMap void }>; constructor( public readonly recordingDirPath: string, diff --git a/src/extension/inlineEdits/vscode-node/inlineEditModel.ts b/src/extension/inlineEdits/vscode-node/inlineEditModel.ts index 3c8260bda..0f1dd682a 100644 --- a/src/extension/inlineEdits/vscode-node/inlineEditModel.ts +++ b/src/extension/inlineEdits/vscode-node/inlineEditModel.ts @@ -65,15 +65,15 @@ class LastChange extends Disposable { public lastEditedTimestamp: number; public lineNumberTriggers: Map; - private _timeout: NodeJS.Timeout | undefined; - public set timeout(value: NodeJS.Timeout | undefined) { + private _timeout: Timeout | undefined; + public set timeout(value: Timeout | undefined) { if (value !== undefined) { // TODO: we can end up collecting multiple timeouts, but also they could be cleared as debouncing happens this._register(toDisposable(() => clearTimeout(value))); } this._timeout = value; } - public get timeout(): NodeJS.Timeout | undefined { + public get timeout(): Timeout | undefined { return this._timeout; } diff --git a/src/extension/inlineEdits/vscode-node/parts/vscodeWorkspace.ts b/src/extension/inlineEdits/vscode-node/parts/vscodeWorkspace.ts index d1618627e..9ef9639e5 100644 --- a/src/extension/inlineEdits/vscode-node/parts/vscodeWorkspace.ts +++ b/src/extension/inlineEdits/vscode-node/parts/vscodeWorkspace.ts @@ -34,6 +34,7 @@ import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offse import { StringText } from '../../../../util/vs/editor/common/core/text/abstractText'; import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation'; import { toInternalTextEdit } from '../utils/translations'; +import { assertStoreNotDisposed } from '../../../../util/common/lifecycle'; export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable { private readonly _openDocuments = observableValue(this, []); @@ -344,7 +345,7 @@ export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable * Returns undefined for documents that are not tracked (e.g. filtered out). */ public getDocumentByTextDocument(doc: TextDocument, reader?: IReader): IVSCodeObservableDocument | undefined { - this._store.assertNotDisposed(); + assertStoreNotDisposed(this._store); const internalDoc = this._getInternalDocument(doc.uri, reader); if (!internalDoc) { diff --git a/src/extension/prompts/node/inline/summarizedDocument/fragments.ts b/src/extension/prompts/node/inline/summarizedDocument/fragments.ts index 5abc161f5..bc65b0b98 100644 --- a/src/extension/prompts/node/inline/summarizedDocument/fragments.ts +++ b/src/extension/prompts/node/inline/summarizedDocument/fragments.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { TextLengthOfSubstr, TextLengthSum } from '../../../../../util/common/textLength'; import { Lazy } from '../../../../../util/vs/base/common/lazy'; import { StringEdit, StringReplacement } from '../../../../../util/vs/editor/common/core/edits/stringEdit'; import { OffsetRange } from '../../../../../util/vs/editor/common/core/ranges/offsetRange'; @@ -99,7 +100,7 @@ export class OriginalStringFragment extends StringFragment { return null; } - private readonly _textLength = new Lazy(() => TextLength.ofSubstr(this.originalText, this.range)); + private readonly _textLength = new Lazy(() => TextLengthOfSubstr(this.originalText, this.range)); get textLength() { return this._textLength.value; } } @@ -127,7 +128,7 @@ export class ConcatenatedStringFragment extends StringFragment { return this.fragments.map(f => f.text).join(''); } - private readonly _textLength = new Lazy(() => TextLength.sum(this.fragments, f => f.textLength)); + private readonly _textLength = new Lazy(() => TextLengthSum(this.fragments, f => f.textLength)); get textLength() { return this._textLength.value; } } diff --git a/src/extension/prompts/node/inline/summarizedDocument/implementation.ts b/src/extension/prompts/node/inline/summarizedDocument/implementation.ts index 0ee78bac0..cd2cfd702 100644 --- a/src/extension/prompts/node/inline/summarizedDocument/implementation.ts +++ b/src/extension/prompts/node/inline/summarizedDocument/implementation.ts @@ -5,13 +5,13 @@ import { AbstractDocument } from '../../../../../platform/editing/common/abstractText'; import { OverlayNode } from '../../../../../platform/parser/node/nodes'; import { min } from '../../../../../util/common/arrays'; +import { TextLengthSum } from '../../../../../util/common/textLength'; import { compareBy, groupAdjacentBy, numberComparator, sumBy } from '../../../../../util/vs/base/common/arrays'; import { CachedFunction } from '../../../../../util/vs/base/common/cache'; import { StringEdit, StringReplacement } from '../../../../../util/vs/editor/common/core/edits/stringEdit'; import { Position } from '../../../../../util/vs/editor/common/core/position'; import { OffsetRange } from '../../../../../util/vs/editor/common/core/ranges/offsetRange'; import { PositionOffsetTransformer } from '../../../../../util/vs/editor/common/core/text/positionToOffset'; -import { TextLength } from '../../../../../util/vs/editor/common/core/text/textLength'; import { Range } from '../../../../../vscodeTypes'; import { IAstVisualization, subtractRange, toAstNode } from '../visualization'; import { ConcatenatedStringFragment, LiteralStringFragment, OriginalStringFragment, pushFragment, StringFragment } from './fragments'; @@ -213,7 +213,7 @@ export function summarizeDocumentsSyncImpl( // total length of all nodes let totalLength = sumBy(rootMarkedNodes, c => c.getTextFragment().length); if (settings.lineNumberStyle === SummarizedDocumentLineNumberStyle.Full) { - const textLen = TextLength.sum(rootMarkedNodes, c => c.getTextFragment().textLength); + const textLen = TextLengthSum(rootMarkedNodes, c => c.getTextFragment().textLength); const maxLineNumber = docs[docIdx].document.getLineCount(); const totalLineNumberChars = textLen.lineCount * getLineNumberText(maxLineNumber).length; // This is an upper bound approximation. totalLength += totalLineNumberChars; diff --git a/src/extension/prompts/node/inline/summarizedDocument/projectedText.ts b/src/extension/prompts/node/inline/summarizedDocument/projectedText.ts index 04990718d..001f26a40 100644 --- a/src/extension/prompts/node/inline/summarizedDocument/projectedText.ts +++ b/src/extension/prompts/node/inline/summarizedDocument/projectedText.ts @@ -43,7 +43,7 @@ export class ProjectedText { } public projectOffsetEdit(edit: StringEdit): StringEdit { - return edit.tryRebase(this.edits, false); + return edit.rebaseSkipConflicting(this.edits); } public tryRebase(originalEdit: StringEdit): { edit: StringEdit; text: ProjectedText } | undefined { @@ -66,7 +66,7 @@ export class ProjectedText { } public projectBackOffsetEdit(edit: StringEdit): StringEdit { - return edit.tryRebase(this.edits.inverse(this.originalText), false); + return edit.rebaseSkipConflicting(this.edits.inverse(this.originalText)); } public projectBackTextEdit(edits: readonly vscode.TextEdit[]): vscode.TextEdit[] { diff --git a/src/extension/prompts/node/inline/workingCopies.ts b/src/extension/prompts/node/inline/workingCopies.ts index 110fbf38b..c57c74c83 100644 --- a/src/extension/prompts/node/inline/workingCopies.ts +++ b/src/extension/prompts/node/inline/workingCopies.ts @@ -86,8 +86,8 @@ export class WorkingCopyDerivedDocument { const s0 = this._derivedDocument; const e_sum = s0.edits; const e_ai = toOffsetEdits(s0.positionOffsetTransformer, value.edits); - const e_ai_r = e_ai.tryRebase(e_sum.inverse(d0.text), false); - const e_sum_r = e_sum.tryRebase(e_ai_r, false); + const e_ai_r = e_ai.rebaseSkipConflicting(e_sum.inverse(d0.text)); + const e_sum_r = e_sum.rebaseSkipConflicting(e_ai_r); const transformedProgressItem = new ChatResponseTextEditPart(value.uri, fromOffsetEdits(d0.transformer, e_ai_r)); @@ -103,7 +103,7 @@ export class WorkingCopyDerivedDocument { const s0 = this._derivedDocument; const e_sum = s0.edits; const e_ai = toOffsetEdits(s0.positionOffsetTransformer, edits); - const e_ai_r = e_ai.tryRebase(e_sum.inverse(d0.text), false); + const e_ai_r = e_ai.rebaseSkipConflicting(e_sum.inverse(d0.text)); return fromOffsetEdits(d0.transformer, e_ai_r); } diff --git a/src/extension/typescriptContext/vscode-node/throttledDebounce.ts b/src/extension/typescriptContext/vscode-node/throttledDebounce.ts index cd7a7819c..8caf6be25 100644 --- a/src/extension/typescriptContext/vscode-node/throttledDebounce.ts +++ b/src/extension/typescriptContext/vscode-node/throttledDebounce.ts @@ -15,7 +15,7 @@ export class ThrottledDebouncer implements Disposable { private static readonly DELAY_INCREMENT = 10; private static readonly MAX_DELAY = 500; - private timeoutId: NodeJS.Timeout | undefined; + private timeoutId: Timeout | undefined; private currentDelay: number; private readonly initialDelay: number; private readonly increment: number; diff --git a/src/extension/xtab/common/promptCrafting.ts b/src/extension/xtab/common/promptCrafting.ts index c5b954006..51397e7fe 100644 --- a/src/extension/xtab/common/promptCrafting.ts +++ b/src/extension/xtab/common/promptCrafting.ts @@ -262,7 +262,7 @@ function generateDocDiff(entry: IXtabHistoryEditEntry, workspacePath: string | u const lineEdit = RootedEdit.toLineEdit(entry.edit); - for (const singleLineEdit of lineEdit.edits) { + for (const singleLineEdit of lineEdit.replacements) { const oldLines = entry.edit.base.getLines().slice(singleLineEdit.lineRange.startLineNumber - 1, singleLineEdit.lineRange.endLineNumberExclusive - 1); const newLines = singleLineEdit.newLines; diff --git a/src/extension/xtab/node/xtabProvider.ts b/src/extension/xtab/node/xtabProvider.ts index 04250ec07..fd3dd3452 100644 --- a/src/extension/xtab/node/xtabProvider.ts +++ b/src/extension/xtab/node/xtabProvider.ts @@ -30,7 +30,7 @@ import { IWorkspaceService } from '../../../platform/workspace/common/workspaceS import * as errors from '../../../util/common/errors'; import { Result } from '../../../util/common/result'; import { createTracer, ITracer } from '../../../util/common/tracing'; -import { AsyncIterableObject, DeferredPromise, raceFilter, raceTimeout, timeout } from '../../../util/vs/base/common/async'; +import { AsyncIterableObject, DeferredPromise, raceTimeout, timeout } from '../../../util/vs/base/common/async'; import { CancellationToken } from '../../../util/vs/base/common/cancellation'; import { StopWatch } from '../../../util/vs/base/common/stopwatch'; import { LineEdit, LineReplacement } from '../../../util/vs/editor/common/core/edits/lineEdit'; @@ -48,6 +48,7 @@ import { IgnoreImportChangesAspect } from '../../inlineEdits/node/importFilterin import { AREA_AROUND_END_TAG, AREA_AROUND_START_TAG, CODE_TO_EDIT_END_TAG, CODE_TO_EDIT_START_TAG, createTaggedCurrentFileContentUsingPagedClipping, CURSOR_TAG, getUserPrompt, N_LINES_ABOVE, N_LINES_AS_CONTEXT, N_LINES_BELOW, nes41Miniv3SystemPrompt, simplifiedPrompt, systemPromptTemplate, unifiedModelSystemPrompt, xtab275SystemPrompt } from '../common/promptCrafting'; import { XtabEndpoint } from './xtabEndpoint'; import { linesWithBackticksRemoved, toLines } from './xtabUtils'; +import { raceFilter } from '../../../util/common/async'; export const IGNORE_TEXT_BEFORE = /```[^\n]*\n/; diff --git a/src/platform/heatmap/test/vscode-node/heatmapServiceImpl.test.ts b/src/platform/heatmap/test/vscode-node/heatmapServiceImpl.test.ts index a34499278..4e0d7dd61 100644 --- a/src/platform/heatmap/test/vscode-node/heatmapServiceImpl.test.ts +++ b/src/platform/heatmap/test/vscode-node/heatmapServiceImpl.test.ts @@ -383,7 +383,7 @@ class MemFS implements vscode.FileSystemProvider { private _emitter = new vscode.EventEmitter(); private _bufferedEvents: vscode.FileChangeEvent[] = []; - private _fireSoonHandle?: NodeJS.Timeout; + private _fireSoonHandle?: Timeout; readonly onDidChangeFile: vscode.Event = this._emitter.event; diff --git a/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts b/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts index 49899f195..9779de3d4 100644 --- a/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts +++ b/src/platform/inlineEdits/common/dataTypes/rootedLineEdit.ts @@ -6,8 +6,11 @@ import { LineEdit, LineReplacement } from '../../../../util/vs/editor/common/core/edits/lineEdit'; import { BaseStringEdit, StringEdit } from '../../../../util/vs/editor/common/core/edits/stringEdit'; import { StringText } from '../../../../util/vs/editor/common/core/text/abstractText'; +import { ensureDependenciesAreSet } from '../../../../util/vs/editor/common/core/text/positionToOffset'; import { RootedEdit } from './edit'; +ensureDependenciesAreSet(); + export class RootedLineEdit { public static fromEdit(edit: RootedEdit): RootedLineEdit { const lineEdit = LineEdit.fromEdit(edit.edit, edit.base); @@ -40,7 +43,7 @@ export class RootedLineEdit { public removeCommonSuffixPrefixLines(): RootedLineEdit { const isNotEmptyEdit = (edit: LineReplacement) => !edit.lineRange.isEmpty || edit.newLines.length > 0; - const newEdit = this.edit.edits.map(e => e.removeCommonSuffixPrefixLines(this.base)).filter(e => isNotEmptyEdit(e)); + const newEdit = this.edit.replacements.map(e => e.removeCommonSuffixPrefixLines(this.base)).filter(e => isNotEmptyEdit(e)); return new RootedLineEdit(this.base, new LineEdit(newEdit)); } } diff --git a/src/platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider.ts b/src/platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider.ts index 48a9e5835..3e8d27461 100644 --- a/src/platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider.ts +++ b/src/platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider.ts @@ -272,7 +272,7 @@ class DocumentState { const potentialRecentEdit = e.edit.compose(recentEdit); const potentialLineEdit = RootedEdit.toLineEdit(new RootedEdit(lastValue, potentialRecentEdit)); const rootedLineEdit = new RootedLineEdit(lastValue, potentialLineEdit).removeCommonSuffixPrefixLines(); // do not take into account no-op edits - const editLineCount = rootedLineEdit.edit.edits.length; + const editLineCount = rootedLineEdit.edit.replacements.length; if (editLineCount > maxEditCount) { break; } diff --git a/src/platform/inlineEdits/common/workspaceEditTracker/nesXtabHistoryTracker.ts b/src/platform/inlineEdits/common/workspaceEditTracker/nesXtabHistoryTracker.ts index e0628fe20..0ce26b5a0 100644 --- a/src/platform/inlineEdits/common/workspaceEditTracker/nesXtabHistoryTracker.ts +++ b/src/platform/inlineEdits/common/workspaceEditTracker/nesXtabHistoryTracker.ts @@ -142,7 +142,7 @@ export class NesXtabHistoryTracker extends Disposable { const currentLineEdit = RootedEdit.toLineEdit(currentRootedEdit); - if (!currentLineEdit.isEmpty() && !lastLineEdit.isEmpty() && lastLineEdit.edits[0].lineRange.startLineNumber === currentLineEdit.edits[0].lineRange.startLineNumber) { + if (!currentLineEdit.isEmpty() && !lastLineEdit.isEmpty() && lastLineEdit.replacements[0].lineRange.startLineNumber === currentLineEdit.replacements[0].lineRange.startLineNumber) { // merge edits previousRecord.removeFromHistory(); const composedEdit = lastRootedEdit.edit.compose(currentEdit); diff --git a/src/platform/test/node/services.ts b/src/platform/test/node/services.ts index 8ec0245f9..9ab8601f4 100644 --- a/src/platform/test/node/services.ts +++ b/src/platform/test/node/services.ts @@ -171,7 +171,11 @@ export class TestingServicesAccessor implements ITestingServicesAccessor { } getIfExists(id: ServiceIdentifier): T | undefined { - return this._instaService.invokeFunction(accessor => accessor.getIfExists(id)); + try { + return this._instaService.invokeFunction(accessor => accessor.get(id)); + } catch { + return undefined; + } } } diff --git a/src/platform/tokenizer/node/tokenizer.ts b/src/platform/tokenizer/node/tokenizer.ts index 06d9f8fd8..f1b0467cd 100644 --- a/src/platform/tokenizer/node/tokenizer.ts +++ b/src/platform/tokenizer/node/tokenizer.ts @@ -281,7 +281,7 @@ class BPETokenizer extends Disposable implements ITokenizer { this._tokenizer = undefined; }); - let timeout: NodeJS.Timeout; + let timeout: Timeout; return { encode: (text, allowedSpecial) => { diff --git a/src/util/common/async.ts b/src/util/common/async.ts index 0fdbab90b..215a3fd8b 100644 --- a/src/util/common/async.ts +++ b/src/util/common/async.ts @@ -127,3 +127,29 @@ export class BatchedProcessor { } } } + +export function raceFilter(promises: Promise[], filter: (result: T) => boolean): Promise { + return new Promise((resolve, reject) => { + if (promises.length === 0) { + resolve(undefined); + return; + } + + let resolved = false; + let unresolvedCount = promises.length; + for (const promise of promises) { + promise.then(result => { + unresolvedCount--; + if (!resolved) { + if (filter(result)) { + resolved = true; + resolve(result); + } else if (unresolvedCount === 0) { + // Last one has to resolve the promise + resolve(undefined); + } + } + }).catch(reject); + } + }); +} \ No newline at end of file diff --git a/src/util/common/lifecycle.ts b/src/util/common/lifecycle.ts new file mode 100644 index 000000000..7b9376777 --- /dev/null +++ b/src/util/common/lifecycle.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BugIndicatingError, onUnexpectedError } from '../vs/base/common/errors'; +import { DisposableStore } from '../vs/base/common/lifecycle'; + +export function assertStoreNotDisposed(store: DisposableStore): void { + if (store.isDisposed) { + onUnexpectedError(new BugIndicatingError('Object disposed')); + } +} \ No newline at end of file diff --git a/src/util/common/textLength.ts b/src/util/common/textLength.ts new file mode 100644 index 000000000..5a0b36fbe --- /dev/null +++ b/src/util/common/textLength.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { OffsetRange } from '../vs/editor/common/core/ranges/offsetRange'; +import { TextLength } from '../vs/editor/common/core/text/textLength'; + +export function TextLengthOfSubstr(str: string, range: OffsetRange): TextLength { + return TextLength.ofText(range.substring(str)); +} + +export function TextLengthSum(fragments: readonly T[], getLength: (f: T) => TextLength): TextLength { + return fragments.reduce((acc, f) => acc.add(getLength(f)), TextLength.zero); +} \ No newline at end of file diff --git a/src/util/vs/base-common.d.ts b/src/util/vs/base-common.d.ts index 995cd7a0c..d3e1d1abf 100644 --- a/src/util/vs/base-common.d.ts +++ b/src/util/vs/base-common.d.ts @@ -9,6 +9,8 @@ declare global { + // --- idle callbacks + interface IdleDeadline { readonly didTimeout: boolean; timeRemaining(): number; @@ -17,6 +19,24 @@ declare global { function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number; function cancelIdleCallback(handle: number): void; + + // --- timeout / interval (available in all contexts, but different signatures in node.js vs web) + + interface TimeoutHandle { readonly _: never; /* this is a trick that seems needed to prevent direct number assignment */ } + type Timeout = TimeoutHandle; + function setTimeout(handler: string | Function, timeout?: number, ...arguments: any[]): Timeout; + function clearTimeout(timeout: Timeout | undefined): void; + + function setInterval(callback: (...args: any[]) => void, delay?: number, ...args: any[]): Timeout; + function clearInterval(timeout: Timeout | undefined): void; + + + // --- error + + interface ErrorConstructor { + captureStackTrace(targetObject: object, constructorOpt?: Function): void; + stackTraceLimit: number; + } } -export { } +export { }; diff --git a/src/util/vs/base/common/actions.ts b/src/util/vs/base/common/actions.ts index 126f5cea1..c469f9e93 100644 --- a/src/util/vs/base/common/actions.ts +++ b/src/util/vs/base/common/actions.ts @@ -57,7 +57,7 @@ export interface IActionChangeEvent { export class Action extends Disposable implements IAction { protected _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; + get onDidChange() { return this._onDidChange.event; } protected readonly _id: string; protected _label: string; @@ -170,10 +170,10 @@ export interface IRunEvent { export class ActionRunner extends Disposable implements IActionRunner { private readonly _onWillRun = this._register(new Emitter()); - readonly onWillRun = this._onWillRun.event; + get onWillRun() { return this._onWillRun.event; } private readonly _onDidRun = this._register(new Emitter()); - readonly onDidRun = this._onDidRun.event; + get onDidRun() { return this._onDidRun.event; } async run(action: IAction, context?: unknown): Promise { if (!action.enabled) { diff --git a/src/util/vs/base/common/arraysFind.ts b/src/util/vs/base/common/arraysFind.ts index 40865e35f..a888b3f00 100644 --- a/src/util/vs/base/common/arraysFind.ts +++ b/src/util/vs/base/common/arraysFind.ts @@ -7,6 +7,8 @@ import { Comparator } from './arrays'; +export function findLast(array: readonly T[], predicate: (item: T) => item is R, fromIndex?: number): R | undefined; +export function findLast(array: readonly T[], predicate: (item: T) => unknown, fromIndex?: number): T | undefined; export function findLast(array: readonly T[], predicate: (item: T) => unknown, fromIndex = array.length - 1): T | undefined { const idx = findLastIdx(array, predicate, fromIndex); if (idx === -1) { diff --git a/src/util/vs/base/common/async.ts b/src/util/vs/base/common/async.ts index d9624d0a7..0bc1ff8ef 100644 --- a/src/util/vs/base/common/async.ts +++ b/src/util/vs/base/common/async.ts @@ -8,7 +8,7 @@ import { CancellationToken, CancellationTokenSource } from './cancellation'; import { BugIndicatingError, CancellationError } from './errors'; import { Emitter, Event } from './event'; -import { Disposable, DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from './lifecycle'; +import { Disposable, DisposableMap, DisposableStore, IDisposable, isDisposable, MutableDisposable, toDisposable } from './lifecycle'; import { extUri as defaultExtUri, IExtUri } from './resources'; import { URI } from './uri'; import { setTimeout0 } from './platform'; @@ -23,19 +23,41 @@ export interface CancelablePromise extends Promise { cancel(): void; } +/** + * Returns a promise that can be cancelled using the provided cancellation token. + * + * @remarks When cancellation is requested, the promise will be rejected with a {@link CancellationError}. + * If the promise resolves to a disposable object, it will be automatically disposed when cancellation + * is requested. + * + * @param callback A function that accepts a cancellation token and returns a promise + * @returns A promise that can be cancelled + */ export function createCancelablePromise(callback: (token: CancellationToken) => Promise): CancelablePromise { const source = new CancellationTokenSource(); const thenable = callback(source.token); + + let isCancelled = false; + const promise = new Promise((resolve, reject) => { const subscription = source.token.onCancellationRequested(() => { + isCancelled = true; subscription.dispose(); reject(new CancellationError()); }); Promise.resolve(thenable).then(value => { subscription.dispose(); source.dispose(); - resolve(value); + + if (!isCancelled) { + resolve(value); + + } else if (isDisposable(value)) { + // promise has been cancelled, result is disposable and will + // be cleaned up + value.dispose(); + } }, err => { subscription.dispose(); source.dispose(); @@ -48,10 +70,10 @@ export function createCancelablePromise(callback: (token: CancellationToken) source.cancel(); source.dispose(); } - then(resolve?: ((value: T) => TResult1 | Promise) | undefined | null, reject?: ((reason: any) => TResult2 | Promise) | undefined | null): Promise { + then(resolve?: ((value: T) => TResult1 | Promise) | undefined | null, reject?: ((reason: unknown) => TResult2 | Promise) | undefined | null): Promise { return promise.then(resolve, reject); } - catch(reject?: ((reason: any) => TResult | Promise) | undefined | null): Promise { + catch(reject?: ((reason: unknown) => TResult | Promise) | undefined | null): Promise { return this.then(undefined, reject); } finally(onfinally?: (() => void) | undefined | null): Promise { @@ -96,22 +118,35 @@ export function raceCancellationError(promise: Promise, token: Cancellatio }); } +/** + * Wraps a cancellable promise such that it is no cancellable. Can be used to + * avoid issues with shared promises that would normally be returned as + * cancellable to consumers. + */ +export function notCancellablePromise(promise: CancelablePromise): Promise { + return new Promise((resolve, reject) => { + promise.then(resolve, reject); + }); +} + /** * Returns as soon as one of the promises resolves or rejects and cancels remaining promises */ -export async function raceCancellablePromises(cancellablePromises: CancelablePromise[]): Promise { +export function raceCancellablePromises(cancellablePromises: (CancelablePromise | Promise)[]): CancelablePromise { let resolvedPromiseIndex = -1; const promises = cancellablePromises.map((promise, index) => promise.then(result => { resolvedPromiseIndex = index; return result; })); - try { - const result = await Promise.race(promises); - return result; - } finally { + const promise = Promise.race(promises) as CancelablePromise; + promise.cancel = () => { cancellablePromises.forEach((cancellablePromise, index) => { - if (index !== resolvedPromiseIndex) { - cancellablePromise.cancel(); + if (index !== resolvedPromiseIndex && (cancellablePromise as CancelablePromise).cancel) { + (cancellablePromise as CancelablePromise).cancel(); } }); - } + }; + promise.finally(() => { + promise.cancel(); + }); + return promise; } export function raceTimeout(promise: Promise, timeout: number, onTimeout?: () => void): Promise { @@ -128,32 +163,6 @@ export function raceTimeout(promise: Promise, timeout: number, onTimeout?: ]); } -export function raceFilter(promises: Promise[], filter: (result: T) => boolean): Promise { - return new Promise((resolve, reject) => { - if (promises.length === 0) { - resolve(undefined); - return; - } - - let resolved = false; - let unresolvedCount = promises.length; - for (const promise of promises) { - promise.then(result => { - unresolvedCount--; - if (!resolved) { - if (filter(result)) { - resolved = true; - resolve(result); - } else if (unresolvedCount === 0) { - // Last one has to resolve the promise - resolve(undefined); - } - } - }).catch(reject); - } - }); -} - export function asPromise(callback: () => T | Thenable): Promise { return new Promise((resolve, reject) => { const item = callback(); @@ -368,7 +377,7 @@ export class Delayer implements IDisposable { private deferred: IScheduledLater | null; private completionPromise: Promise | null; private doResolve: ((value?: any | Promise) => void) | null; - private doReject: ((err: any) => void) | null; + private doReject: ((err: unknown) => void) | null; private task: ITask> | null; constructor(public defaultDelay: number | typeof MicrotaskDelay) { @@ -504,11 +513,11 @@ export class Barrier { */ export class AutoOpenBarrier extends Barrier { - private readonly _timeout: any; + private readonly _timeout: Timeout; constructor(autoOpenTimeMs: number) { super(); - this._timeout = setTimeout(() => this.open(), autoOpenTimeMs); + this._timeout = setTimeout(() => this.open(), autoOpenTimeMs) as any; } override open(): void { @@ -583,9 +592,9 @@ export function sequence(promiseFactories: ITask>[]): Promise return index < len ? promiseFactories[index++]() : null; } - function thenHandler(result: any): Promise { + function thenHandler(result: unknown): Promise { if (result !== undefined && result !== null) { - results.push(result); + results.push(result as T); } const n = next(); @@ -910,14 +919,94 @@ export class ResourceQueue implements IDisposable { } } +export type Task = () => (Promise | T); + +/** + * Processes tasks in the order they were scheduled. +*/ +export class TaskQueue { + private _runningTask: Task | undefined = undefined; + private _pendingTasks: { task: Task; deferred: DeferredPromise; setUndefinedWhenCleared: boolean }[] = []; + + /** + * Waits for the current and pending tasks to finish, then runs and awaits the given task. + * If the task is skipped because of clearPending, the promise is rejected with a CancellationError. + */ + public schedule(task: Task): Promise { + const deferred = new DeferredPromise(); + this._pendingTasks.push({ task, deferred, setUndefinedWhenCleared: false }); + this._runIfNotRunning(); + return deferred.p; + } + + /** + * Waits for the current and pending tasks to finish, then runs and awaits the given task. + * If the task is skipped because of clearPending, the promise is resolved with undefined. + */ + public scheduleSkipIfCleared(task: Task): Promise { + const deferred = new DeferredPromise(); + this._pendingTasks.push({ task, deferred, setUndefinedWhenCleared: true }); + this._runIfNotRunning(); + return deferred.p; + } + + private _runIfNotRunning(): void { + if (this._runningTask === undefined) { + this._processQueue(); + } + } + + private async _processQueue(): Promise { + if (this._pendingTasks.length === 0) { + return; + } + + const next = this._pendingTasks.shift(); + if (!next) { + return; + } + + if (this._runningTask) { + throw new BugIndicatingError(); + } + + this._runningTask = next.task; + + try { + const result = await next.task(); + next.deferred.complete(result); + } catch (e) { + next.deferred.error(e); + } finally { + this._runningTask = undefined; + this._processQueue(); + } + } + + /** + * Clears all pending tasks. Does not cancel the currently running task. + */ + public clearPending(): void { + const tasks = this._pendingTasks; + this._pendingTasks = []; + for (const task of tasks) { + if (task.setUndefinedWhenCleared) { + task.deferred.complete(undefined); + } else { + task.deferred.error(new CancellationError()); + } + } + } +} + export class TimeoutTimer implements IDisposable { - private _token: any; + private _token: Timeout | undefined; private _isDisposed = false; constructor(); constructor(runner: () => void, timeout: number); constructor(runner?: () => void, timeout?: number) { - this._token = -1; + this._token = undefined; if (typeof runner === 'function' && typeof timeout === 'number') { this.setIfNotSet(runner, timeout); @@ -930,9 +1019,9 @@ export class TimeoutTimer implements IDisposable { } cancel(): void { - if (this._token !== -1) { + if (this._token !== undefined) { clearTimeout(this._token); - this._token = -1; + this._token = undefined; } } @@ -943,9 +1032,9 @@ export class TimeoutTimer implements IDisposable { this.cancel(); this._token = setTimeout(() => { - this._token = -1; + this._token = undefined; runner(); - }, timeout); + }, timeout) as any; } setIfNotSet(runner: () => void, timeout: number): void { @@ -953,14 +1042,14 @@ export class TimeoutTimer implements IDisposable { throw new BugIndicatingError(`Calling 'setIfNotSet' on a disposed TimeoutTimer`); } - if (this._token !== -1) { + if (this._token !== undefined) { // timer is already set return; } this._token = setTimeout(() => { - this._token = -1; + this._token = undefined; runner(); - }, timeout); + }, timeout) as any; } } @@ -1000,12 +1089,12 @@ export class RunOnceScheduler implements IDisposable { protected runner: ((...args: unknown[]) => void) | null; - private timeoutToken: any; + private timeoutToken: Timeout | undefined; private timeout: number; private timeoutHandler: () => void; constructor(runner: (...args: any[]) => void, delay: number) { - this.timeoutToken = -1; + this.timeoutToken = undefined; this.runner = runner; this.timeout = delay; this.timeoutHandler = this.onTimeout.bind(this); @@ -1025,7 +1114,7 @@ export class RunOnceScheduler implements IDisposable { cancel(): void { if (this.isScheduled()) { clearTimeout(this.timeoutToken); - this.timeoutToken = -1; + this.timeoutToken = undefined; } } @@ -1034,7 +1123,7 @@ export class RunOnceScheduler implements IDisposable { */ schedule(delay = this.timeout): void { this.cancel(); - this.timeoutToken = setTimeout(this.timeoutHandler, delay); + this.timeoutToken = setTimeout(this.timeoutHandler, delay) as any; } get delay(): number { @@ -1049,7 +1138,7 @@ export class RunOnceScheduler implements IDisposable { * Returns true if scheduled. */ isScheduled(): boolean { - return this.timeoutToken !== -1; + return this.timeoutToken !== undefined; } flush(): void { @@ -1060,7 +1149,7 @@ export class RunOnceScheduler implements IDisposable { } private onTimeout() { - this.timeoutToken = -1; + this.timeoutToken = undefined; if (this.runner) { this.doRun(); } @@ -1085,7 +1174,7 @@ export class ProcessTimeRunOnceScheduler { private timeout: number; private counter: number; - private intervalToken: any; + private intervalToken: Timeout | undefined; private intervalHandler: () => void; constructor(runner: () => void, delay: number) { @@ -1095,7 +1184,7 @@ export class ProcessTimeRunOnceScheduler { this.runner = runner; this.timeout = delay; this.counter = 0; - this.intervalToken = -1; + this.intervalToken = undefined; this.intervalHandler = this.onInterval.bind(this); } @@ -1107,7 +1196,7 @@ export class ProcessTimeRunOnceScheduler { cancel(): void { if (this.isScheduled()) { clearInterval(this.intervalToken); - this.intervalToken = -1; + this.intervalToken = undefined; } } @@ -1120,14 +1209,14 @@ export class ProcessTimeRunOnceScheduler { } this.cancel(); this.counter = Math.ceil(delay / 1000); - this.intervalToken = setInterval(this.intervalHandler, 1000); + this.intervalToken = setInterval(this.intervalHandler, 1000) as any; } /** * Returns true if scheduled. */ isScheduled(): boolean { - return this.intervalToken !== -1; + return this.intervalToken !== undefined; } private onInterval() { @@ -1139,7 +1228,7 @@ export class ProcessTimeRunOnceScheduler { // time elapsed clearInterval(this.intervalToken); - this.intervalToken = -1; + this.intervalToken = undefined; this.runner?.(); } } @@ -1305,6 +1394,7 @@ export class ThrottledWorker extends Disposable { override dispose(): void { super.dispose(); + this.pendingWork.length = 0; this.disposed = true; } } @@ -1345,7 +1435,8 @@ export let runWhenGlobalIdle: (callback: (idle: IdleDeadline) => void, timeout?: export let _runWhenIdle: (targetWindow: IdleApi, callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable; (function () { - if (typeof globalThis.requestIdleCallback !== 'function' || typeof globalThis.cancelIdleCallback !== 'function') { + const safeGlobal: any = globalThis; + if (typeof safeGlobal.requestIdleCallback !== 'function' || typeof safeGlobal.cancelIdleCallback !== 'function') { _runWhenIdle = (_targetWindow, runner, timeout?) => { setTimeout0(() => { if (disposed) { @@ -1371,7 +1462,7 @@ export let _runWhenIdle: (targetWindow: IdleApi, callback: (idle: IdleDeadline) }; }; } else { - _runWhenIdle = (targetWindow: IdleApi, runner, timeout?) => { + _runWhenIdle = (targetWindow: typeof safeGlobal, runner, timeout?) => { const handle: number = targetWindow.requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined); let disposed = false; return { @@ -1628,7 +1719,7 @@ export class DeferredPromise { private completeCallback!: ValueCallback; private errorCallback!: (err: unknown) => void; - private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T }; + private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T }; public get isRejected() { return this.outcome?.outcome === DeferredOutcome.Rejected; @@ -1671,6 +1762,13 @@ export class DeferredPromise { }); } + public settleWith(promise: Promise): Promise { + return promise.then( + value => this.complete(value), + error => this.error(error) + ); + } + public cancel() { return this.error(new CancellationError()); } @@ -1844,7 +1942,7 @@ export interface AsyncIterableExecutor { /** * @param emitter An object that allows to emit async values valid only for the duration of the executor. */ - (emitter: AsyncIterableEmitter): void | Promise; + (emitter: AsyncIterableEmitter): unknown | Promise; } /** @@ -1960,6 +2058,8 @@ export class AsyncIterableObject implements AsyncIterable { }); } + public filter(filterFn: (item: T) => item is T2): AsyncIterableObject; + public filter(filterFn: (item: T) => boolean): AsyncIterableObject; public filter(filterFn: (item: T) => boolean): AsyncIterableObject { return AsyncIterableObject.filter(this, filterFn); } @@ -2044,24 +2144,12 @@ export class AsyncIterableObject implements AsyncIterable { } } -export class CancelableAsyncIterableObject extends AsyncIterableObject { - constructor( - private readonly _source: CancellationTokenSource, - executor: AsyncIterableExecutor - ) { - super(executor); - } - cancel(): void { - this._source.cancel(); - } -} - -export function createCancelableAsyncIterable(callback: (token: CancellationToken) => AsyncIterable): CancelableAsyncIterableObject { +export function createCancelableAsyncIterableProducer(callback: (token: CancellationToken) => AsyncIterable): CancelableAsyncIterableProducer { const source = new CancellationTokenSource(); const innerIterable = callback(source.token); - return new CancelableAsyncIterableObject(source, async (emitter) => { + return new CancelableAsyncIterableProducer(source, async (emitter) => { const subscription = source.token.onCancellationRequested(() => { subscription.dispose(); source.dispose(); @@ -2091,7 +2179,8 @@ export class AsyncIterableSource { private readonly _asyncIterable: AsyncIterableObject; private _errorFn: (error: Error) => void; - private _emitFn: (item: T) => void; + private _emitOneFn: (item: T) => void; + private _emitManyFn: (item: T[]) => void; /** * @@ -2110,22 +2199,31 @@ export class AsyncIterableSource { emitter.emitMany(earlyItems); } this._errorFn = (error: Error) => emitter.reject(error); - this._emitFn = (item: T) => emitter.emitOne(item); + this._emitOneFn = (item: T) => emitter.emitOne(item); + this._emitManyFn = (items: T[]) => emitter.emitMany(items); return this._deferred.p; }, onReturn); let earlyError: Error | undefined; let earlyItems: T[] | undefined; - this._emitFn = (item: T) => { + + this._errorFn = (error: Error) => { + if (!earlyError) { + earlyError = error; + } + }; + this._emitOneFn = (item: T) => { if (!earlyItems) { earlyItems = []; } earlyItems.push(item); }; - this._errorFn = (error: Error) => { - if (!earlyError) { - earlyError = error; + this._emitManyFn = (items: T[]) => { + if (!earlyItems) { + earlyItems = items.slice(); + } else { + items.forEach(item => earlyItems!.push(item)); } }; } @@ -2144,8 +2242,336 @@ export class AsyncIterableSource { } emitOne(item: T): void { - this._emitFn(item); + this._emitOneFn(item); + } + + emitMany(items: T[]) { + this._emitManyFn(items); + } +} + +export function cancellableIterable(iterableOrIterator: AsyncIterator | AsyncIterable, token: CancellationToken): AsyncIterableIterator { + const iterator = Symbol.asyncIterator in iterableOrIterator ? iterableOrIterator[Symbol.asyncIterator]() : iterableOrIterator; + + return { + async next(): Promise> { + if (token.isCancellationRequested) { + return { done: true, value: undefined }; + } + const result = await raceCancellation(iterator.next(), token); + return result || { done: true, value: undefined }; + }, + throw: iterator.throw?.bind(iterator), + return: iterator.return?.bind(iterator), + [Symbol.asyncIterator]() { + return this; + } + }; +} + +type ProducerConsumerValue = { + ok: true; + value: T; +} | { + ok: false; + error: Error; +}; + +class ProducerConsumer { + private readonly _unsatisfiedConsumers: DeferredPromise[] = []; + private readonly _unconsumedValues: ProducerConsumerValue[] = []; + private _finalValue: ProducerConsumerValue | undefined; + + public get hasFinalValue(): boolean { + return !!this._finalValue; + } + + produce(value: ProducerConsumerValue): void { + this._ensureNoFinalValue(); + if (this._unsatisfiedConsumers.length > 0) { + const deferred = this._unsatisfiedConsumers.shift()!; + this._resolveOrRejectDeferred(deferred, value); + } else { + this._unconsumedValues.push(value); + } + } + + produceFinal(value: ProducerConsumerValue): void { + this._ensureNoFinalValue(); + this._finalValue = value; + for (const deferred of this._unsatisfiedConsumers) { + this._resolveOrRejectDeferred(deferred, value); + } + this._unsatisfiedConsumers.length = 0; + } + + private _ensureNoFinalValue(): void { + if (this._finalValue) { + throw new BugIndicatingError('ProducerConsumer: cannot produce after final value has been set'); + } + } + + private _resolveOrRejectDeferred(deferred: DeferredPromise, value: ProducerConsumerValue): void { + if (value.ok) { + deferred.complete(value.value); + } else { + deferred.error(value.error); + } + } + + consume(): Promise { + if (this._unconsumedValues.length > 0 || this._finalValue) { + const value = this._unconsumedValues.length > 0 ? this._unconsumedValues.shift()! : this._finalValue!; + if (value.ok) { + return Promise.resolve(value.value); + } else { + return Promise.reject(value.error); + } + } else { + const deferred = new DeferredPromise(); + this._unsatisfiedConsumers.push(deferred); + return deferred.p; + } + } +} + +/** + * Important difference to AsyncIterableObject: + * If it is iterated two times, the second iterator will not see the values emitted by the first iterator. + */ +export class AsyncIterableProducer implements AsyncIterable { + private readonly _producerConsumer = new ProducerConsumer>(); + + constructor(executor: AsyncIterableExecutor, private readonly _onReturn?: () => void) { + queueMicrotask(async () => { + const p = executor({ + emitOne: value => this._producerConsumer.produce({ ok: true, value: { done: false, value: value } }), + emitMany: values => { + for (const value of values) { + this._producerConsumer.produce({ ok: true, value: { done: false, value: value } }); + } + }, + reject: error => this._finishError(error), + }); + + if (!this._producerConsumer.hasFinalValue) { + try { + await p; + this._finishOk(); + } catch (error) { + this._finishError(error); + } + } + }); + } + + public static fromArray(items: T[]): AsyncIterableProducer { + return new AsyncIterableProducer((writer) => { + writer.emitMany(items); + }); + } + + public static fromPromise(promise: Promise): AsyncIterableProducer { + return new AsyncIterableProducer(async (emitter) => { + emitter.emitMany(await promise); + }); + } + + public static fromPromisesResolveOrder(promises: Promise[]): AsyncIterableProducer { + return new AsyncIterableProducer(async (emitter) => { + await Promise.all(promises.map(async (p) => emitter.emitOne(await p))); + }); + } + + public static merge(iterables: AsyncIterable[]): AsyncIterableProducer { + return new AsyncIterableProducer(async (emitter) => { + await Promise.all(iterables.map(async (iterable) => { + for await (const item of iterable) { + emitter.emitOne(item); + } + })); + }); + } + + public static EMPTY = AsyncIterableProducer.fromArray([]); + + public static map(iterable: AsyncIterable, mapFn: (item: T) => R): AsyncIterableProducer { + return new AsyncIterableProducer(async (emitter) => { + for await (const item of iterable) { + emitter.emitOne(mapFn(item)); + } + }); + } + + public map(mapFn: (item: T) => R): AsyncIterableProducer { + return AsyncIterableProducer.map(this, mapFn); + } + + public static coalesce(iterable: AsyncIterable): AsyncIterableProducer { + return >AsyncIterableProducer.filter(iterable, item => !!item); + } + + public coalesce(): AsyncIterableProducer> { + return AsyncIterableProducer.coalesce(this) as AsyncIterableProducer>; + } + + public static filter(iterable: AsyncIterable, filterFn: (item: T) => boolean): AsyncIterableProducer { + return new AsyncIterableProducer(async (emitter) => { + for await (const item of iterable) { + if (filterFn(item)) { + emitter.emitOne(item); + } + } + }); + } + + public filter(filterFn: (item: T) => item is T2): AsyncIterableProducer; + public filter(filterFn: (item: T) => boolean): AsyncIterableProducer; + public filter(filterFn: (item: T) => boolean): AsyncIterableProducer { + return AsyncIterableProducer.filter(this, filterFn); + } + + private _finishOk(): void { + this._producerConsumer.produceFinal({ ok: true, value: { done: true, value: undefined } }); + } + + private _finishError(error: Error): void { + this._producerConsumer.produceFinal({ ok: false, error: error }); + } + + private readonly _iterator: AsyncIterator = { + next: () => this._producerConsumer.consume(), + return: () => { + this._onReturn?.(); + return Promise.resolve({ done: true, value: undefined }); + }, + throw: async (e) => { + this._finishError(e); + return { done: true, value: undefined }; + }, + }; + + [Symbol.asyncIterator](): AsyncIterator { + return this._iterator; + } +} + +export class CancelableAsyncIterableProducer extends AsyncIterableProducer { + constructor( + private readonly _source: CancellationTokenSource, + executor: AsyncIterableExecutor + ) { + super(executor); + } + + cancel(): void { + this._source.cancel(); } } //#endregion + +export const AsyncReaderEndOfStream = Symbol('AsyncReaderEndOfStream'); + +export class AsyncReader { + private _buffer: T[] = []; + private _atEnd = false; + + public get endOfStream(): boolean { return this._buffer.length === 0 && this._atEnd; } + private _extendBufferPromise: Promise | undefined; + + constructor( + private readonly _source: AsyncIterator + ) { + } + + public async read(): Promise { + if (this._buffer.length === 0 && !this._atEnd) { + await this._extendBuffer(); + } + if (this._buffer.length === 0) { + return AsyncReaderEndOfStream; + } + return this._buffer.shift()!; + } + + public async readWhile(predicate: (value: T) => boolean, callback: (element: T) => unknown): Promise { + do { + const piece = await this.peek(); + if (piece === AsyncReaderEndOfStream) { + break; + } + if (!predicate(piece)) { + break; + } + await this.read(); // consume + await callback(piece); + } while (true); + } + + public readBufferedOrThrow(): T | typeof AsyncReaderEndOfStream { + const value = this.peekBufferedOrThrow(); + this._buffer.shift(); + return value; + } + + public async consumeToEnd(): Promise { + while (!this.endOfStream) { + await this.read(); + } + } + + public async peek(): Promise { + if (this._buffer.length === 0 && !this._atEnd) { + await this._extendBuffer(); + } + if (this._buffer.length === 0) { + return AsyncReaderEndOfStream; + } + return this._buffer[0]; + } + + public peekBufferedOrThrow(): T | typeof AsyncReaderEndOfStream { + if (this._buffer.length === 0) { + if (this._atEnd) { + return AsyncReaderEndOfStream; + } + throw new BugIndicatingError('No buffered elements'); + } + + return this._buffer[0]; + } + + public async peekTimeout(timeoutMs: number): Promise { + if (this._buffer.length === 0 && !this._atEnd) { + await raceTimeout(this._extendBuffer(), timeoutMs); + } + if (this._atEnd) { + return AsyncReaderEndOfStream; + } + if (this._buffer.length === 0) { + return undefined; + } + return this._buffer[0]; + } + + private _extendBuffer(): Promise { + if (this._atEnd) { + return Promise.resolve(); + } + + if (!this._extendBufferPromise) { + this._extendBufferPromise = (async () => { + const { value, done } = await this._source.next(); + this._extendBufferPromise = undefined; + if (done) { + this._atEnd = true; + } else { + this._buffer.push(value); + } + })(); + } + + return this._extendBufferPromise; + } +} diff --git a/src/util/vs/base/common/buffer.ts b/src/util/vs/base/common/buffer.ts index 1c6eacfe4..366401632 100644 --- a/src/util/vs/base/common/buffer.ts +++ b/src/util/vs/base/common/buffer.ts @@ -8,13 +8,20 @@ import { Lazy } from './lazy'; import * as streams from './stream'; -declare const Buffer: any; +interface NodeBuffer { + allocUnsafe(size: number): Uint8Array; + isBuffer(obj: any): obj is NodeBuffer; + from(arrayBuffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array; + from(data: string): Uint8Array; +} + +declare const Buffer: NodeBuffer; const hasBuffer = (typeof Buffer !== 'undefined'); const indexOfTable = new Lazy(() => new Uint8Array(256)); -let textEncoder: TextEncoder | null; -let textDecoder: TextDecoder | null; +let textEncoder: { encode: (input: string) => Uint8Array } | null; +let textDecoder: { decode: (input: Uint8Array) => string } | null; export class VSBuffer { @@ -95,6 +102,10 @@ export class VSBuffer { return ret; } + static isNativeBuffer(buffer: unknown): boolean { + return hasBuffer && Buffer.isBuffer(buffer); + } + readonly buffer: Uint8Array; readonly byteLength: number; @@ -453,3 +464,38 @@ export function encodeBase64({ buffer }: VSBuffer, padded = true, urlSafe = fals return output; } + +const hexChars = '0123456789abcdef'; +export function encodeHex({ buffer }: VSBuffer): string { + let result = ''; + for (let i = 0; i < buffer.length; i++) { + const byte = buffer[i]; + result += hexChars[byte >>> 4]; + result += hexChars[byte & 0x0f]; + } + return result; +} + +export function decodeHex(hex: string): VSBuffer { + if (hex.length % 2 !== 0) { + throw new SyntaxError('Hex string must have an even length'); + } + const out = new Uint8Array(hex.length >> 1); + for (let i = 0; i < hex.length;) { + out[i >> 1] = (decodeHexChar(hex, i++) << 4) | decodeHexChar(hex, i++); + } + return VSBuffer.wrap(out); +} + +function decodeHexChar(str: string, position: number) { + const s = str.charCodeAt(position); + if (s >= 48 && s <= 57) { // '0'-'9' + return s - 48; + } else if (s >= 97 && s <= 102) { // 'a'-'f' + return s - 87; + } else if (s >= 65 && s <= 70) { // 'A'-'F' + return s - 55; + } else { + throw new SyntaxError(`Invalid hex character at position ${position}`); + } +} diff --git a/src/util/vs/base/common/codiconsLibrary.ts b/src/util/vs/base/common/codiconsLibrary.ts index ca0635648..e272d8678 100644 --- a/src/util/vs/base/common/codiconsLibrary.ts +++ b/src/util/vs/base/common/codiconsLibrary.ts @@ -594,4 +594,22 @@ export const codiconsLibrary = { flag: register('flag', 0xec3f), lightbulbEmpty: register('lightbulb-empty', 0xec40), symbolMethodArrow: register('symbol-method-arrow', 0xec41), + copilotUnavailable: register('copilot-unavailable', 0xec42), + repoPinned: register('repo-pinned', 0xec43), + keyboardTabAbove: register('keyboard-tab-above', 0xec44), + keyboardTabBelow: register('keyboard-tab-below', 0xec45), + gitPullRequestDone: register('git-pull-request-done', 0xec46), + mcp: register('mcp', 0xec47), + extensionsLarge: register('extensions-large', 0xec48), + layoutPanelDock: register('layout-panel-dock', 0xec49), + layoutSidebarLeftDock: register('layout-sidebar-left-dock', 0xec4a), + layoutSidebarRightDock: register('layout-sidebar-right-dock', 0xec4b), + copilotInProgress: register('copilot-in-progress', 0xec4c), + copilotError: register('copilot-error', 0xec4d), + copilotSuccess: register('copilot-success', 0xec4e), + chatSparkle: register('chat-sparkle', 0xec4f), + searchSparkle: register('search-sparkle', 0xec50), + editSparkle: register('edit-sparkle', 0xec51), + copilotSnooze: register('copilot-snooze', 0xec52), + sendToRemoteAgent: register('send-to-remote-agent', 0xec53), } as const; diff --git a/src/util/vs/base/common/collections.ts b/src/util/vs/base/common/collections.ts index 302a296b4..59a776e0e 100644 --- a/src/util/vs/base/common/collections.ts +++ b/src/util/vs/base/common/collections.ts @@ -34,6 +34,20 @@ export function groupBy(data: V[], groupF return result; } +export function groupByMap(data: V[], groupFn: (element: V) => K): Map { + const result = new Map(); + for (const element of data) { + const key = groupFn(element); + let target = result.get(key); + if (!target) { + target = []; + result.set(key, target); + } + target.push(element); + } + return result; +} + export function diffSets(before: ReadonlySet, after: ReadonlySet): { removed: T[]; added: T[] } { const removed: T[] = []; const added: T[] = []; diff --git a/src/util/vs/base/common/diff/diff.ts b/src/util/vs/base/common/diff/diff.ts index 07d0a7a72..7c9f90b36 100644 --- a/src/util/vs/base/common/diff/diff.ts +++ b/src/util/vs/base/common/diff/diff.ts @@ -96,7 +96,7 @@ class MyArray { * length: * A 64-bit integer that represents the number of elements to copy. */ - public static Copy(sourceArray: any[], sourceIndex: number, destinationArray: any[], destinationIndex: number, length: number) { + public static Copy(sourceArray: unknown[], sourceIndex: number, destinationArray: unknown[], destinationIndex: number, length: number) { for (let i = 0; i < length; i++) { destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i]; } diff --git a/src/util/vs/base/common/errors.ts b/src/util/vs/base/common/errors.ts index 5a8c1e3d4..a2c1f2dbe 100644 --- a/src/util/vs/base/common/errors.ts +++ b/src/util/vs/base/common/errors.ts @@ -194,7 +194,7 @@ export interface V8CallSite { toString(): string; } -const canceledName = 'Canceled'; +export const canceledName = 'Canceled'; /** * Checks if the given error is a promise in canceled state @@ -215,6 +215,20 @@ export class CancellationError extends Error { } } +export class PendingMigrationError extends Error { + + private static readonly _name = 'PendingMigrationError'; + + static is(error: unknown): error is PendingMigrationError { + return error instanceof PendingMigrationError || (error instanceof Error && error.name === PendingMigrationError._name); + } + + constructor(message: string) { + super(message); + this.name = PendingMigrationError._name; + } +} + /** * @deprecated use {@link CancellationError `new CancellationError()`} instead */ diff --git a/src/util/vs/base/common/event.ts b/src/util/vs/base/common/event.ts index a86d2f02d..7e76cff20 100644 --- a/src/util/vs/base/common/event.ts +++ b/src/util/vs/base/common/event.ts @@ -5,6 +5,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancelablePromise } from './async'; import { CancellationToken } from './cancellation'; import { diffSets } from './collections'; import { onUnexpectedError } from './errors'; @@ -258,7 +259,7 @@ export namespace Event { export function debounce(event: Event, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold?: number, disposable?: DisposableStore): Event { let subscription: IDisposable; let output: O | undefined = undefined; - let handle: any = undefined; + let handle: Timeout | undefined | null = undefined; let numDebouncedCalls = 0; let doFire: (() => void) | undefined; @@ -285,11 +286,13 @@ export namespace Event { }; if (typeof delay === 'number') { - clearTimeout(handle); - handle = setTimeout(doFire, delay); + if (handle) { + clearTimeout(handle); + } + handle = setTimeout(doFire, delay) as any; } else { if (handle === undefined) { - handle = 0; + handle = null; queueMicrotask(doFire); } } @@ -324,7 +327,7 @@ export namespace Event { * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. */ - export function accumulate(event: Event, delay: number = 0, disposable?: DisposableStore): Event { + export function accumulate(event: Event, delay: number | typeof MicrotaskDelay = 0, disposable?: DisposableStore): Event { return Event.debounce(event, (last, e) => { if (!last) { return [e]; @@ -598,26 +601,16 @@ export namespace Event { /** * Creates a promise out of an event, using the {@link Event.once} helper. */ - export function toPromise(event: Event, disposables?: IDisposable[] | DisposableStore): Promise { - return new Promise(resolve => once(event)(resolve, null, disposables)); - } + export function toPromise(event: Event, disposables?: IDisposable[] | DisposableStore): CancelablePromise { + let cancelRef: () => void; + const promise = new Promise((resolve, reject) => { + const listener = once(event)(resolve, null, disposables); + // not resolved, matching the behavior of a normal disposal + cancelRef = () => listener.dispose(); + }) as CancelablePromise; + promise.cancel = cancelRef!; - /** - * Creates an event out of a promise that fires once when the promise is - * resolved with the result of the promise or `undefined`. - */ - export function fromPromise(promise: Promise): Event { - const result = new Emitter(); - - promise.then(res => { - result.fire(res); - }, () => { - result.fire(undefined); - }).finally(() => { - result.dispose(); - }); - - return result.event; + return promise; } /** @@ -1414,7 +1407,7 @@ export class PauseableEmitter extends Emitter { export class DebounceEmitter extends PauseableEmitter { private readonly _delay: number; - private _handle: any | undefined; + private _handle: Timeout | undefined; constructor(options: EmitterOptions & { merge: (input: T[]) => T; delay?: number }) { super(options); @@ -1427,7 +1420,7 @@ export class DebounceEmitter extends PauseableEmitter { this._handle = setTimeout(() => { this._handle = undefined; this.resume(); - }, this._delay); + }, this._delay) as any; } super.fire(event); } diff --git a/src/util/vs/base/common/glob.ts b/src/util/vs/base/common/glob.ts index 569805e0a..c408fa09b 100644 --- a/src/util/vs/base/common/glob.ts +++ b/src/util/vs/base/common/glob.ts @@ -307,6 +307,24 @@ const NULL = function (): string | null { return null; }; +/** + * Check if a provided parsed pattern or expression + * is empty - hence it won't ever match anything. + * + * See {@link FALSE} and {@link NULL}. + */ +export function isEmptyPattern(pattern: ParsedPattern | ParsedExpression): pattern is (typeof FALSE | typeof NULL) { + if (pattern === FALSE) { + return true; + } + + if (pattern === NULL) { + return true; + } + + return false; +} + function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): ParsedStringPattern { if (!arg1) { return NULL; diff --git a/src/util/vs/base/common/hash.ts b/src/util/vs/base/common/hash.ts index b4b39ef2f..75b30da9d 100644 --- a/src/util/vs/base/common/hash.ts +++ b/src/util/vs/base/common/hash.ts @@ -5,7 +5,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { VSBuffer } from './buffer'; +import { encodeHex, VSBuffer } from './buffer'; import * as strings from './strings'; type NotSyncHashable = ArrayBufferLike | ArrayBufferView; @@ -20,7 +20,7 @@ export function hash(obj: T extends NotSyncHashable ? never : T): number { return doHash(obj, 0); } -export function doHash(obj: any, hashVal: number): number { +export function doHash(obj: unknown, hashVal: number): number { switch (typeof obj) { case 'object': if (obj === null) { @@ -95,7 +95,7 @@ export const hashAsync = (input: string | ArrayBufferView | VSBuffer) => { buff = input; } - return crypto.subtle.digest('sha-1', buff).then(toHexString); // CodeQL [SM04514] we use sha1 here for validating old stored client state, not for security + return crypto.subtle.digest('sha-1', buff as ArrayBufferView).then(toHexString); // CodeQL [SM04514] we use sha1 here for validating old stored client state, not for security }; const enum SHA1Constant { @@ -118,7 +118,7 @@ function toHexString(buffer: ArrayBuffer): string; function toHexString(value: number, bitsize?: number): string; function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string { if (bufferOrValue instanceof ArrayBuffer) { - return Array.from(new Uint8Array(bufferOrValue)).map(b => b.toString(16).padStart(2, '0')).join(''); + return encodeHex(VSBuffer.wrap(new Uint8Array(bufferOrValue))); } return (bufferOrValue >>> 0).toString(16).padStart(bitsize / 4, '0'); diff --git a/src/util/vs/base/common/htmlContent.ts b/src/util/vs/base/common/htmlContent.ts index cd8fbd6d2..ed4befbf3 100644 --- a/src/util/vs/base/common/htmlContent.ts +++ b/src/util/vs/base/common/htmlContent.ts @@ -7,6 +7,7 @@ import { illegalArgument } from './errors'; import { escapeIcons } from './iconLabels'; +import { Schemas } from './network'; import { isEqual } from './resources'; import { escapeRegExpCharacters } from './strings'; import { URI, UriComponents } from './uri'; @@ -39,10 +40,6 @@ export class MarkdownString implements IMarkdownString { public uris?: { [href: string]: UriComponents } | undefined; public static lift(dto: IMarkdownString): MarkdownString { - if (dto instanceof MarkdownString) { - return dto; - } - const markdownString = new MarkdownString(dto.value, dto); markdownString.uris = dto.uris; markdownString.baseUri = dto.baseUri ? URI.revive(dto.baseUri) : undefined; @@ -123,7 +120,7 @@ export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownStri } } -export function isMarkdownString(thing: any): thing is IMarkdownString { +export function isMarkdownString(thing: unknown): thing is IMarkdownString { if (thing instanceof MarkdownString) { return true; } else if (thing && typeof thing === 'object') { @@ -203,3 +200,13 @@ export function parseHrefAndDimensions(href: string): { href: string; dimensions } return { href, dimensions }; } + +export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[] }): string { + const uri = URI.from({ + scheme: Schemas.command, + path: command.id, + query: command.arguments?.length ? encodeURIComponent(JSON.stringify(command.arguments)) : undefined, + }).toString(); + + return `[${escapeMarkdownSyntaxTokens(command.title)}](${uri})`; +} diff --git a/src/util/vs/base/common/iterator.ts b/src/util/vs/base/common/iterator.ts index 3e542483f..9766f42eb 100644 --- a/src/util/vs/base/common/iterator.ts +++ b/src/util/vs/base/common/iterator.ts @@ -34,7 +34,7 @@ export namespace Iterable { return iterable || _empty; } - export function* reverse(array: Array): Iterable { + export function* reverse(array: ReadonlyArray): Iterable { for (let i = array.length - 1; i >= 0; i--) { yield array[i]; } diff --git a/src/util/vs/base/common/lifecycle.ts b/src/util/vs/base/common/lifecycle.ts index e5a283ded..b5135a216 100644 --- a/src/util/vs/base/common/lifecycle.ts +++ b/src/util/vs/base/common/lifecycle.ts @@ -7,10 +7,10 @@ import { compareBy, numberComparator } from './arrays'; import { groupBy } from './collections'; -import { BugIndicatingError, onUnexpectedError } from './errors'; +import { SetMap } from './map'; import { createSingleCallFunction } from './functional'; import { Iterable } from './iterator'; -import { SetMap } from './map'; +import { BugIndicatingError, onUnexpectedError } from './errors'; // #region Disposable Tracking @@ -489,12 +489,6 @@ export class DisposableStore implements IDisposable { setParentOfDisposable(o, null); } } - - public assertNotDisposed(): void { - if (this._isDisposed) { - onUnexpectedError(new BugIndicatingError('Object disposed')); - } - } } /** @@ -643,35 +637,6 @@ export class RefCountedDisposable { } } -/** - * A safe disposable can be `unset` so that a leaked reference (listener) - * can be cut-off. - */ -export class SafeDisposable implements IDisposable { - - dispose: () => void = () => { }; - unset: () => void = () => { }; - isset: () => boolean = () => false; - - constructor() { - trackDisposable(this); - } - - set(fn: Function) { - let callback: Function | undefined = fn; - this.unset = () => callback = undefined; - this.isset = () => callback !== undefined; - this.dispose = () => { - if (callback) { - callback(); - callback = undefined; - markAsDisposed(this); - } - }; - return this; - } -} - export interface IReference extends IDisposable { readonly object: T; } @@ -804,6 +769,7 @@ export class DisposableMap implements ID } this._store.set(key, value); + setParentOfDisposable(value, this); } /** @@ -820,10 +786,19 @@ export class DisposableMap implements ID */ deleteAndLeak(key: K): V | undefined { const value = this._store.get(key); + if (value) { + setParentOfDisposable(value, null); + } this._store.delete(key); return value; } + public assertNotDisposed(): void { + if (this._isDisposed) { + onUnexpectedError(new BugIndicatingError('Object disposed')); + } + } + keys(): IterableIterator { return this._store.keys(); } diff --git a/src/util/vs/base/common/marshallingIds.ts b/src/util/vs/base/common/marshallingIds.ts index 825efe461..82d06b1da 100644 --- a/src/util/vs/base/common/marshallingIds.ts +++ b/src/util/vs/base/common/marshallingIds.ts @@ -27,6 +27,9 @@ export const enum MarshalledId { ChatViewContext, LanguageModelToolResult, LanguageModelTextPart, + LanguageModelThinkingPart, LanguageModelPromptTsxPart, LanguageModelDataPart, + ChatSessionContext, + ChatResponsePullRequestPart, } diff --git a/src/util/vs/base/common/network.ts b/src/util/vs/base/common/network.ts index d22193349..1c92507ef 100644 --- a/src/util/vs/base/common/network.ts +++ b/src/util/vs/base/common/network.ts @@ -84,7 +84,13 @@ export namespace Schemas { export const vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block'; /** Scheme used for the chat input editor. */ - export const vscodeChatSesssion = 'vscode-chat-editor'; + export const vscodeChatEditor = 'vscode-chat-editor'; + + /** Scheme used for the chat input part */ + export const vscodeChatInput = 'chatSessionInput'; + + /** Scheme for chat session content */ + export const vscodeChatSession = 'vscode-chat-session'; /** * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) @@ -141,6 +147,12 @@ export namespace Schemas { * Scheme used for the accessible view */ export const accessibleView = 'accessible-view'; + + /** + * Used for snapshots of chat edits + */ + export const chatEditingSnapshotScheme = 'chat-editing-snapshot-text-model'; + export const chatEditingModel = 'chat-editing-text-model'; } export function matchesScheme(target: URI | string, scheme: string): boolean { @@ -335,7 +347,7 @@ class FileAccessImpl { return uri; } - private toUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { + private toUri(uriOrModule: URI | string): URI { if (URI.isUri(uriOrModule)) { return uriOrModule; } @@ -353,7 +365,7 @@ class FileAccessImpl { return URI.file(modulePath); } - return URI.parse(moduleIdToUrl!.toUrl(uriOrModule)); + throw new Error('Cannot determine URI for module id!'); } } diff --git a/src/util/vs/base/common/observableInternal/changeTracker.ts b/src/util/vs/base/common/observableInternal/changeTracker.ts index 519a183be..2ab6d7670 100644 --- a/src/util/vs/base/common/observableInternal/changeTracker.ts +++ b/src/util/vs/base/common/observableInternal/changeTracker.ts @@ -55,3 +55,42 @@ export function recordChanges>>(getObs: () => TObs): + IChangeTracker<{ [TKey in keyof TObs]: ReturnType } + & { changes: readonly ({ [TKey in keyof TObs]: { key: TKey; change: TObs[TKey]['TChange'] } }[keyof TObs])[] }> { + let obs: TObs | undefined = undefined; + return { + createChangeSummary: (_previousChangeSummary) => { + return { + changes: [], + } as any; + }, + handleChange(ctx, changeSummary) { + if (!obs) { + obs = getObs(); + } + for (const key in obs) { + if (ctx.didChange(obs[key])) { + (changeSummary.changes as any).push({ key, change: ctx.change }); + } + } + return true; + }, + beforeUpdate(reader, changeSummary) { + if (!obs) { + obs = getObs(); + } + for (const key in obs) { + if (key === 'changes') { + throw new BugIndicatingError('property name "changes" is reserved for change tracking'); + } + changeSummary[key] = obs[key].read(reader); + } + } + }; +} diff --git a/src/util/vs/base/common/observableInternal/debugLocation.ts b/src/util/vs/base/common/observableInternal/debugLocation.ts new file mode 100644 index 000000000..d46322019 --- /dev/null +++ b/src/util/vs/base/common/observableInternal/debugLocation.ts @@ -0,0 +1,88 @@ +//!!! 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. + *--------------------------------------------------------------------------------------------*/ + +export type DebugLocation = DebugLocationImpl | undefined; + +export namespace DebugLocation { + let enabled = false; + + export function enable(): void { + enabled = true; + } + + export function ofCaller(): DebugLocation { + if (!enabled) { + return undefined; + } + const Err = Error as any as { stackTraceLimit: number }; // For the monaco editor checks, which don't have the nodejs types. + + const l = Err.stackTraceLimit; + Err.stackTraceLimit = 3; + const stack = new Error().stack!; + Err.stackTraceLimit = l; + + return DebugLocationImpl.fromStack(stack, 2); + } +} + +class DebugLocationImpl implements ILocation { + public static fromStack(stack: string, parentIdx: number): DebugLocationImpl | undefined { + const lines = stack.split('\n'); + const location = parseLine(lines[parentIdx + 1]); + if (location) { + return new DebugLocationImpl( + location.fileName, + location.line, + location.column, + location.id + ); + } else { + return undefined; + } + } + + constructor( + public readonly fileName: string, + public readonly line: number, + public readonly column: number, + public readonly id: string, + ) { + } +} + + +export interface ILocation { + fileName: string; + line: number; + column: number; + id: string; +} + +function parseLine(stackLine: string): ILocation | undefined { + const match = stackLine.match(/\((.*):(\d+):(\d+)\)/); + if (match) { + return { + fileName: match[1], + line: parseInt(match[2]), + column: parseInt(match[3]), + id: stackLine, + }; + } + + const match2 = stackLine.match(/at ([^\(\)]*):(\d+):(\d+)/); + + if (match2) { + return { + fileName: match2[1], + line: parseInt(match2[2]), + column: parseInt(match2[3]), + id: stackLine, + }; + } + + return undefined; +} diff --git a/src/util/vs/base/common/observableInternal/experimental/utils.ts b/src/util/vs/base/common/observableInternal/experimental/utils.ts index 733c57a81..5da4e4e15 100644 --- a/src/util/vs/base/common/observableInternal/experimental/utils.ts +++ b/src/util/vs/base/common/observableInternal/experimental/utils.ts @@ -24,7 +24,7 @@ export function latestChangedValue[]>(owner: DebugOwn } let hasLastChangedValue = false; - let lastChangedValue: any = undefined; + let lastChangedValue: unknown = undefined; const result = observableFromEvent(owner, cb => { const store = new DisposableStore(); diff --git a/src/util/vs/base/common/observableInternal/index.ts b/src/util/vs/base/common/observableInternal/index.ts index 5b9be1e5f..b0daa2156 100644 --- a/src/util/vs/base/common/observableInternal/index.ts +++ b/src/util/vs/base/common/observableInternal/index.ts @@ -7,38 +7,41 @@ // This is a facade for the observable implementation. Only import from here! +export { observableValueOpts } from './observables/observableValueOpts'; +export { autorun, autorunDelta, autorunHandleChanges, autorunOpts, autorunWithStore, autorunWithStoreHandleChanges, autorunIterableDelta, autorunSelfDisposable } from './reactions/autorun'; export { type IObservable, type IObservableWithChange, type IObserver, type IReader, type ISettable, type ISettableObservable, type ITransaction } from './base'; -export { recordChanges, type IChangeContext, type IChangeTracker } from './changeTracker'; -export { type DebugOwner } from './debugName'; -export { derivedConstOnceDefined, latestChangedValue } from './experimental/utils'; -export { constObservable } from './observables/constObservable'; +export { disposableObservableValue } from './observables/observableValue'; export { derived, derivedDisposable, derivedHandleChanges, derivedOpts, derivedWithSetter, derivedWithStore } from './observables/derived'; export { type IDerivedReader } from './observables/derivedImpl'; -export { observableFromEvent, observableFromEventOpts } from './observables/observableFromEvent'; -export { observableSignal, type IObservableSignal } from './observables/observableSignal'; -export { observableSignalFromEvent } from './observables/observableSignalFromEvent'; -export { disposableObservableValue, observableValue } from './observables/observableValue'; -export { observableValueOpts } from './observables/observableValueOpts'; -export { autorun, autorunDelta, autorunHandleChanges, autorunIterableDelta, autorunOpts, autorunWithStore, autorunWithStoreHandleChanges } from './reactions/autorun'; -export { asyncTransaction, globalTransaction, subtransaction, transaction, TransactionImpl } from './transaction'; -export { ObservableLazy, ObservableLazyPromise, ObservablePromise, PromiseResult } from './utils/promise'; -export { RemoveUndefined, runOnChange, runOnChangeWithCancellationToken, runOnChangeWithStore } from './utils/runOnChange'; +export { ObservableLazy, ObservableLazyPromise, ObservablePromise, PromiseResult, } from './utils/promise'; +export { derivedWithCancellationToken, waitForState } from './utils/utilsCancellation'; export { - debouncedObservable, debouncedObservableDeprecated, derivedObservableWithCache, + debouncedObservableDeprecated, debouncedObservable, derivedObservableWithCache, derivedObservableWithWritableCache, keepObserved, mapObservableArrayCached, observableFromPromise, recomputeInitiallyAndOnChange, - signalFromObservable, wasEventTriggeredRecently + signalFromObservable, wasEventTriggeredRecently, } from './utils/utils'; -export { derivedWithCancellationToken, waitForState } from './utils/utilsCancellation'; +export { type DebugOwner } from './debugName'; +export { type IChangeContext, type IChangeTracker, recordChanges, recordChangesLazy } from './changeTracker'; +export { constObservable } from './observables/constObservable'; +export { type IObservableSignal, observableSignal } from './observables/observableSignal'; +export { observableFromEventOpts } from './observables/observableFromEvent'; +export { observableSignalFromEvent } from './observables/observableSignalFromEvent'; +export { asyncTransaction, globalTransaction, subtransaction, transaction, TransactionImpl } from './transaction'; export { observableFromValueWithChangeEvent, ValueWithChangeEventFromObservable } from './utils/valueWithChangeEvent'; +export { runOnChange, runOnChangeWithCancellationToken, runOnChangeWithStore, type RemoveUndefined } from './utils/runOnChange'; +export { derivedConstOnceDefined, latestChangedValue } from './experimental/utils'; +export { observableFromEvent } from './observables/observableFromEvent'; +export { observableValue } from './observables/observableValue'; -export { ObservableMap } from './map'; export { ObservableSet } from './set'; +export { ObservableMap } from './map'; +export { DebugLocation } from './debugLocation'; -import { env } from '../process'; +import { addLogger, setLogObservableFn } from './logging/logging'; import { ConsoleObservableLogger, logObservableToConsole } from './logging/consoleObservableLogger'; import { DevToolsLogger } from './logging/debugger/devToolsLogger'; -import { addLogger, setLogObservableFn } from './logging/logging'; +import { env } from '../process'; setLogObservableFn(logObservableToConsole); diff --git a/src/util/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts b/src/util/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts index a00dd50d9..219fcedaa 100644 --- a/src/util/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts +++ b/src/util/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts @@ -26,9 +26,15 @@ export type ObsDebuggerApi = { getDerivedInfo(instanceId: ObsInstanceId): IDerivedObservableDetailedInfo; getAutorunInfo(instanceId: ObsInstanceId): IAutorunDetailedInfo; getObservableValueInfo(instanceId: ObsInstanceId): IObservableValueInfo; + setValue(instanceId: ObsInstanceId, jsonValue: unknown): void; getValue(instanceId: ObsInstanceId): unknown; + // For autorun and deriveds + rerun(instanceId: ObsInstanceId): void; + + logValue(instanceId: ObsInstanceId): void; + getTransactionState(): ITransactionState | undefined; } }; diff --git a/src/util/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts b/src/util/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts index 6b1f98bbb..bf2b62e8a 100644 --- a/src/util/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts +++ b/src/util/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts @@ -11,7 +11,7 @@ import { IChangeInformation, IObservableLogger } from '../logging'; import { formatValue } from '../consoleObservableLogger'; import { ObsDebuggerApi, IObsDeclaration, ObsInstanceId, ObsStateUpdate, ITransactionState, ObserverInstanceState } from './debuggerApi'; import { registerDebugChannel } from './debuggerRpc'; -import { deepAssign, deepAssignDeleteNulls, getFirstStackFrameOutsideOf, ILocation, Throttler } from './utils'; +import { deepAssign, deepAssignDeleteNulls, Throttler } from './utils'; import { isDefined } from '../../../types'; import { FromEventObservable } from '../../observables/observableFromEvent'; import { BugIndicatingError, onUnexpectedError } from '../../../errors'; @@ -19,6 +19,7 @@ import { IObservable, IObserver } from '../../base'; import { BaseObservable } from '../../observables/baseObservable'; import { Derived, DerivedState } from '../../observables/derivedImpl'; import { ObservableValue } from '../../observables/observableValue'; +import { DebugLocation } from '../../debugLocation'; interface IInstanceInfo { declarationId: number; @@ -137,7 +138,25 @@ export class DevToolsLogger implements IObservableLogger { } return undefined; - } + }, + logValue: (instanceId) => { + const obs = this._aliveInstances.get(instanceId); + if (obs && 'get' in obs) { + console.log('Logged Value:', obs.get()); + } else { + throw new BugIndicatingError('Observable is not supported'); + } + }, + rerun: (instanceId) => { + const obs = this._aliveInstances.get(instanceId); + if (obs instanceof Derived) { + obs.debugRecompute(); + } else if (obs instanceof AutorunObserver) { + obs.debugRerun(); + } else { + throw new BugIndicatingError('Observable is not supported'); + } + }, } }; }); @@ -256,7 +275,9 @@ export class DevToolsLogger implements IObservableLogger { return undefined; } - private constructor() { } + private constructor() { + DebugLocation.enable(); + } private _pendingChanges: ObsStateUpdate | null = null; private readonly _changeThrottler = new Throttler(); @@ -282,54 +303,29 @@ export class DevToolsLogger implements IObservableLogger { } }; - private _getDeclarationId(type: IObsDeclaration['type']): number { - - let shallow = true; - let loc!: ILocation; - - const Err = Error as any as { stackTraceLimit: number }; // For the monaco editor checks, which don't have the nodejs types. - - while (true) { - const l = Err.stackTraceLimit; - Err.stackTraceLimit = shallow ? 6 : 20; - const stack = new Error().stack!; - Err.stackTraceLimit = l; - - let result = getFirstStackFrameOutsideOf(stack, /[/\\]observableInternal[/\\]|\.observe|[/\\]util(s)?\./); - - if (!shallow && !result) { - result = getFirstStackFrameOutsideOf(stack, /[/\\]observableInternal[/\\]|\.observe/)!; - } - if (result) { - loc = result; - break; - } - if (!shallow) { - console.error('Could not find location for declaration', new Error().stack); - loc = { fileName: 'unknown', line: 0, column: 0, id: 'unknown' }; - break; - } - shallow = false; + private _getDeclarationId(type: IObsDeclaration['type'], location: DebugLocation): number { + if (!location) { + return -1; } - let decInfo = this._declarations.get(loc.id); + let decInfo = this._declarations.get(location.id); if (decInfo === undefined) { decInfo = { id: this._declarationId++, type, - url: loc.fileName, - line: loc.line, - column: loc.column, + url: location.fileName, + line: location.line, + column: location.column, }; - this._declarations.set(loc.id, decInfo); + this._declarations.set(location.id, decInfo); this._handleChange({ decls: { [decInfo.id]: decInfo } }); } return decInfo.id; } - handleObservableCreated(observable: IObservable): void { - const declarationId = this._getDeclarationId('observable/value'); + handleObservableCreated(observable: IObservable, location: DebugLocation): void { + const declarationId = this._getDeclarationId('observable/value', location); const info: IObservableInfo = { declarationId, @@ -389,8 +385,8 @@ export class DevToolsLogger implements IObservableLogger { } } - handleAutorunCreated(autorun: AutorunObserver): void { - const declarationId = this._getDeclarationId('autorun'); + handleAutorunCreated(autorun: AutorunObserver, location: DebugLocation): void { + const declarationId = this._getDeclarationId('autorun', location); const info: IAutorunInfo = { declarationId, instanceId: this._instanceId++, diff --git a/src/util/vs/base/common/observableInternal/logging/debugger/utils.ts b/src/util/vs/base/common/observableInternal/logging/debugger/utils.ts index 1eca83f73..db5dd6ee3 100644 --- a/src/util/vs/base/common/observableInternal/logging/debugger/utils.ts +++ b/src/util/vs/base/common/observableInternal/logging/debugger/utils.ts @@ -7,66 +7,8 @@ import { IDisposable } from '../../../lifecycle'; -export function getFirstStackFrameOutsideOf(stack: string, pattern?: RegExp): ILocation | undefined { - const lines = stack.split('\n'); - - for (let i = 1; i < lines.length; i++) { - const line = lines[i]; - - if (pattern && pattern.test(line)) { - continue; - } - - const showFramesUpMatch = line.match(/\$show(\d+)FramesUp/); - if (showFramesUpMatch) { - const n = parseInt(showFramesUpMatch[1], 10); - i += (n - 1); - continue; - } - - const result = parseLine(line); - if (result) { - return result; - } - } - - return undefined; -} - -export interface ILocation { - fileName: string; - line: number; - column: number; - id: string; -} - -function parseLine(stackLine: string): ILocation | undefined { - const match = stackLine.match(/\((.*):(\d+):(\d+)\)/); - if (match) { - return { - fileName: match[1], - line: parseInt(match[2]), - column: parseInt(match[3]), - id: stackLine, - }; - } - - const match2 = stackLine.match(/at ([^\(\)]*):(\d+):(\d+)/); - - if (match2) { - return { - fileName: match2[1], - line: parseInt(match2[2]), - column: parseInt(match2[3]), - id: stackLine, - }; - } - - return undefined; -} - export class Debouncer implements IDisposable { - private _timeout: any | undefined = undefined; + private _timeout: Timeout | undefined = undefined; public debounce(fn: () => void, timeoutMs: number): void { if (this._timeout !== undefined) { @@ -75,7 +17,7 @@ export class Debouncer implements IDisposable { this._timeout = setTimeout(() => { this._timeout = undefined; fn(); - }, timeoutMs); + }, timeoutMs) as any; } dispose(): void { @@ -86,14 +28,14 @@ export class Debouncer implements IDisposable { } export class Throttler implements IDisposable { - private _timeout: any | undefined = undefined; + private _timeout: Timeout | undefined = undefined; public throttle(fn: () => void, timeoutMs: number): void { if (this._timeout === undefined) { this._timeout = setTimeout(() => { this._timeout = undefined; fn(); - }, timeoutMs); + }, timeoutMs) as any; } } diff --git a/src/util/vs/base/common/observableInternal/logging/logging.ts b/src/util/vs/base/common/observableInternal/logging/logging.ts index 1bf2ff6a6..403d89522 100644 --- a/src/util/vs/base/common/observableInternal/logging/logging.ts +++ b/src/util/vs/base/common/observableInternal/logging/logging.ts @@ -9,6 +9,7 @@ import { AutorunObserver } from '../reactions/autorunImpl'; import { IObservable } from '../base'; import { TransactionImpl } from '../transaction'; import type { Derived } from '../observables/derivedImpl'; +import { DebugLocation } from '../debugLocation'; let globalObservableLogger: IObservableLogger | undefined; @@ -46,12 +47,12 @@ export interface IChangeInformation { } export interface IObservableLogger { - handleObservableCreated(observable: IObservable): void; + handleObservableCreated(observable: IObservable, location: DebugLocation): void; handleOnListenerCountChanged(observable: IObservable, newCount: number): void; handleObservableUpdated(observable: IObservable, info: IChangeInformation): void; - handleAutorunCreated(autorun: AutorunObserver): void; + handleAutorunCreated(autorun: AutorunObserver, location: DebugLocation): void; handleAutorunDisposed(autorun: AutorunObserver): void; handleAutorunDependencyChanged(autorun: AutorunObserver, observable: IObservable, change: unknown): void; handleAutorunStarted(autorun: AutorunObserver): void; @@ -69,9 +70,9 @@ class ComposedLogger implements IObservableLogger { public readonly loggers: IObservableLogger[], ) { } - handleObservableCreated(observable: IObservable): void { + handleObservableCreated(observable: IObservable, location: DebugLocation): void { for (const logger of this.loggers) { - logger.handleObservableCreated(observable); + logger.handleObservableCreated(observable, location); } } handleOnListenerCountChanged(observable: IObservable, newCount: number): void { @@ -84,9 +85,9 @@ class ComposedLogger implements IObservableLogger { logger.handleObservableUpdated(observable, info); } } - handleAutorunCreated(autorun: AutorunObserver): void { + handleAutorunCreated(autorun: AutorunObserver, location: DebugLocation): void { for (const logger of this.loggers) { - logger.handleAutorunCreated(autorun); + logger.handleAutorunCreated(autorun, location); } } handleAutorunDisposed(autorun: AutorunObserver): void { diff --git a/src/util/vs/base/common/observableInternal/map.ts b/src/util/vs/base/common/observableInternal/map.ts index 484039b56..44180ca69 100644 --- a/src/util/vs/base/common/observableInternal/map.ts +++ b/src/util/vs/base/common/observableInternal/map.ts @@ -5,7 +5,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValueOpts, IObservable, ITransaction } from '../observable'; +import { IObservable, ITransaction } from '../observable'; +import { observableValueOpts } from './observables/observableValueOpts'; + export class ObservableMap implements Map { private readonly _data = new Map(); diff --git a/src/util/vs/base/common/observableInternal/observables/baseObservable.ts b/src/util/vs/base/common/observableInternal/observables/baseObservable.ts index 559fde3c0..cbd5fea3a 100644 --- a/src/util/vs/base/common/observableInternal/observables/baseObservable.ts +++ b/src/util/vs/base/common/observableInternal/observables/baseObservable.ts @@ -7,6 +7,7 @@ import { IObservableWithChange, IObserver, IReader, IObservable } from '../base'; import { DisposableStore } from '../commonFacade/deps'; +import { DebugLocation } from '../debugLocation'; import { DebugOwner, getFunctionName } from '../debugName'; import { getLogger, logObservable } from '../logging/logging'; import type { keepObserved, recomputeInitiallyAndOnChange } from '../utils/utils'; @@ -126,9 +127,9 @@ export abstract class ConvenientObservable implements IObservableWit export abstract class BaseObservable extends ConvenientObservable { protected readonly _observers = new Set(); - constructor() { + constructor(debugLocation: DebugLocation) { super(); - getLogger()?.handleObservableCreated(this); + getLogger()?.handleObservableCreated(this, debugLocation); } public addObserver(observer: IObserver): void { @@ -159,7 +160,7 @@ export abstract class BaseObservable extends ConvenientObserv const hadLogger = !!getLogger(); logObservable(this); if (!hadLogger) { - getLogger()?.handleObservableCreated(this); + getLogger()?.handleObservableCreated(this, DebugLocation.ofCaller()); } return this; } diff --git a/src/util/vs/base/common/observableInternal/observables/derived.ts b/src/util/vs/base/common/observableInternal/observables/derived.ts index 65167c5b3..7414b19e8 100644 --- a/src/util/vs/base/common/observableInternal/observables/derived.ts +++ b/src/util/vs/base/common/observableInternal/observables/derived.ts @@ -8,6 +8,7 @@ import { IObservable, IReader, ITransaction, ISettableObservable, IObservableWithChange } from '../base'; import { IChangeTracker } from '../changeTracker'; import { DisposableStore, EqualityComparer, IDisposable, strictEquals } from '../commonFacade/deps'; +import { DebugLocation } from '../debugLocation'; import { DebugOwner, DebugNameData, IDebugNameData } from '../debugName'; import { _setDerivedOpts } from './baseObservable'; import { IDerivedReader, Derived, DerivedWithSetter } from './derivedImpl'; @@ -20,14 +21,19 @@ import { IDerivedReader, Derived, DerivedWithSetter } from './derivedImpl'; */ export function derived(computeFn: (reader: IDerivedReader) => T): IObservableWithChange; export function derived(owner: DebugOwner, computeFn: (reader: IDerivedReader) => T): IObservableWithChange; -export function derived(computeFnOrOwner: ((reader: IDerivedReader) => T) | DebugOwner, computeFn?: ((reader: IDerivedReader) => T) | undefined): IObservable { +export function derived( + computeFnOrOwner: ((reader: IDerivedReader) => T) | DebugOwner, + computeFn?: ((reader: IDerivedReader) => T) | undefined, + debugLocation = DebugLocation.ofCaller() +): IObservable { if (computeFn !== undefined) { return new Derived( new DebugNameData(computeFnOrOwner, undefined, computeFn), computeFn, undefined, undefined, - strictEquals + strictEquals, + debugLocation, ); } return new Derived( @@ -35,18 +41,20 @@ export function derived(computeFnOrOwner: ((reader: IDerivedR computeFnOrOwner as any, undefined, undefined, - strictEquals + strictEquals, + debugLocation, ); } -export function derivedWithSetter(owner: DebugOwner | undefined, computeFn: (reader: IReader) => T, setter: (value: T, transaction: ITransaction | undefined) => void): ISettableObservable { +export function derivedWithSetter(owner: DebugOwner | undefined, computeFn: (reader: IReader) => T, setter: (value: T, transaction: ITransaction | undefined) => void, debugLocation = DebugLocation.ofCaller()): ISettableObservable { return new DerivedWithSetter( new DebugNameData(owner, undefined, computeFn), computeFn, undefined, undefined, strictEquals, - setter + setter, + debugLocation ); } @@ -55,14 +63,16 @@ export function derivedOpts( equalsFn?: EqualityComparer; onLastObserverRemoved?: (() => void); }, - computeFn: (reader: IReader) => T + computeFn: (reader: IReader) => T, + debugLocation = DebugLocation.ofCaller() ): IObservable { return new Derived( new DebugNameData(options.owner, options.debugName, options.debugReferenceFn), computeFn, undefined, options.onLastObserverRemoved, - options.equalsFn ?? strictEquals + options.equalsFn ?? strictEquals, + debugLocation ); } _setDerivedOpts(derivedOpts); @@ -85,14 +95,16 @@ export function derivedHandleChanges( changeTracker: IChangeTracker; equalityComparer?: EqualityComparer; }, - computeFn: (reader: IDerivedReader, changeSummary: TChangeSummary) => T + computeFn: (reader: IDerivedReader, changeSummary: TChangeSummary) => T, + debugLocation = DebugLocation.ofCaller() ): IObservableWithChange { return new Derived( new DebugNameData(options.owner, options.debugName, undefined), computeFn, options.changeTracker, undefined, - options.equalityComparer ?? strictEquals + options.equalityComparer ?? strictEquals, + debugLocation ); } @@ -105,7 +117,7 @@ export function derivedWithStore(computeFn: (reader: IReader, store: Disposab * @deprecated Use `derived(reader => { reader.store.add(...) })` instead! */ export function derivedWithStore(owner: DebugOwner, computeFn: (reader: IReader, store: DisposableStore) => T): IObservable; -export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: DisposableStore) => T) | DebugOwner, computeFnOrUndefined?: ((reader: IReader, store: DisposableStore) => T)): IObservable { +export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: DisposableStore) => T) | DebugOwner, computeFnOrUndefined?: ((reader: IReader, store: DisposableStore) => T), debugLocation = DebugLocation.ofCaller()): IObservable { let computeFn: (reader: IReader, store: DisposableStore) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { @@ -132,13 +144,14 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: }, undefined, () => store.dispose(), - strictEquals + strictEquals, + debugLocation ); } export function derivedDisposable(computeFn: (reader: IReader) => T): IObservable; export function derivedDisposable(owner: DebugOwner, computeFn: (reader: IReader) => T): IObservable; -export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | DebugOwner, computeFnOrUndefined?: ((reader: IReader) => T)): IObservable { +export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | DebugOwner, computeFnOrUndefined?: ((reader: IReader) => T), debugLocation = DebugLocation.ofCaller()): IObservable { let computeFn: (reader: IReader) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { @@ -171,6 +184,7 @@ export function derivedDisposable(computeFnOr store = undefined; } }, - strictEquals + strictEquals, + debugLocation ); } diff --git a/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts b/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts index e44a42862..a70e94f65 100644 --- a/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts +++ b/src/util/vs/base/common/observableInternal/observables/derivedImpl.ts @@ -11,6 +11,7 @@ import { DebugNameData } from '../debugName'; import { BugIndicatingError, DisposableStore, EqualityComparer, assertFn, onBugIndicatingError } from '../commonFacade/deps'; import { getLogger } from '../logging/logging'; import { IChangeTracker } from '../changeTracker'; +import { DebugLocation } from '../debugLocation'; export interface IDerivedReader extends IReaderWithStore { /** @@ -67,8 +68,9 @@ export class Derived extends BaseObserv private readonly _changeTracker: IChangeTracker | undefined, private readonly _handleLastObserverRemoved: (() => void) | undefined = undefined, private readonly _equalityComparator: EqualityComparer, + debugLocation: DebugLocation, ) { - super(); + super(debugLocation); this._changeSummary = this._changeTracker?.createChangeSummary(undefined); } @@ -402,6 +404,14 @@ export class Derived extends BaseObserv this._value = newValue as any; } + public debugRecompute(): void { + if (!this._isComputing) { + this._recompute(); + } else { + this._state = DerivedState.stale; + } + } + public setValue(newValue: T, tx: ITransaction, change: TChange): void { this._value = newValue; const observers = this._observers; @@ -421,6 +431,7 @@ export class DerivedWithSetter exten handleLastObserverRemoved: (() => void) | undefined = undefined, equalityComparator: EqualityComparer, public readonly set: (value: T, tx: ITransaction | undefined, change: TOutChanges) => void, + debugLocation: DebugLocation, ) { super( debugNameData, @@ -428,6 +439,7 @@ export class DerivedWithSetter exten changeTracker, handleLastObserverRemoved, equalityComparator, + debugLocation, ); } } diff --git a/src/util/vs/base/common/observableInternal/observables/lazyObservableValue.ts b/src/util/vs/base/common/observableInternal/observables/lazyObservableValue.ts index e323020f3..8aa1a8660 100644 --- a/src/util/vs/base/common/observableInternal/observables/lazyObservableValue.ts +++ b/src/util/vs/base/common/observableInternal/observables/lazyObservableValue.ts @@ -11,6 +11,7 @@ import { TransactionImpl } from '../transaction'; import { DebugNameData } from '../debugName'; import { getLogger } from '../logging/logging'; import { BaseObservable } from './baseObservable'; +import { DebugLocation } from '../debugLocation'; /** * Holds off updating observers until the value is actually read. @@ -30,8 +31,9 @@ export class LazyObservableValue private readonly _debugNameData: DebugNameData, initialValue: T, private readonly _equalityComparator: EqualityComparer, + debugLocation: DebugLocation ) { - super(); + super(debugLocation); this._value = initialValue; } diff --git a/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts b/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts index 9cd372ded..d8af1bb2c 100644 --- a/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts +++ b/src/util/vs/base/common/observableInternal/observables/observableFromEvent.ts @@ -11,35 +11,39 @@ import { EqualityComparer, Event, IDisposable, strictEquals } from '../commonFac import { DebugOwner, DebugNameData, IDebugNameData } from '../debugName'; import { getLogger } from '../logging/logging'; import { BaseObservable } from './baseObservable'; +import { DebugLocation } from '../debugLocation'; export function observableFromEvent( owner: DebugOwner, event: Event, - getValue: (args: TArgs | undefined) => T + getValue: (args: TArgs | undefined) => T, + debugLocation?: DebugLocation, ): IObservable; export function observableFromEvent( event: Event, - getValue: (args: TArgs | undefined) => T + getValue: (args: TArgs | undefined) => T, ): IObservable; export function observableFromEvent(...args: - [owner: DebugOwner, event: Event, getValue: (args: any | undefined) => any] | + [owner: DebugOwner, event: Event, getValue: (args: any | undefined) => any, debugLocation?: DebugLocation] | [event: Event, getValue: (args: any | undefined) => any] ): IObservable { let owner; let event; let getValue; - if (args.length === 3) { - [owner, event, getValue] = args; - } else { + let debugLocation; + if (args.length === 2) { [event, getValue] = args; + } else { + [owner, event, getValue, debugLocation] = args; } return new FromEventObservable( new DebugNameData(owner, undefined, getValue), event, getValue, () => FromEventObservable.globalTransaction, - strictEquals + strictEquals, + debugLocation ?? DebugLocation.ofCaller() ); } @@ -48,12 +52,13 @@ export function observableFromEventOpts( equalsFn?: EqualityComparer; }, event: Event, - getValue: (args: TArgs | undefined) => T + getValue: (args: TArgs | undefined) => T, + debugLocation = DebugLocation.ofCaller() ): IObservable { return new FromEventObservable( new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? getValue), event, - getValue, () => FromEventObservable.globalTransaction, options.equalsFn ?? strictEquals + getValue, () => FromEventObservable.globalTransaction, options.equalsFn ?? strictEquals, debugLocation ); } @@ -69,9 +74,10 @@ export class FromEventObservable extends BaseObservable { private readonly event: Event, public readonly _getValue: (args: TArgs | undefined) => T, private readonly _getTransaction: () => ITransaction | undefined, - private readonly _equalityComparator: EqualityComparer + private readonly _equalityComparator: EqualityComparer, + debugLocation: DebugLocation ) { - super(); + super(debugLocation); } private getDebugName(): string | undefined { diff --git a/src/util/vs/base/common/observableInternal/observables/observableSignal.ts b/src/util/vs/base/common/observableInternal/observables/observableSignal.ts index 1e6077bfa..4314a4367 100644 --- a/src/util/vs/base/common/observableInternal/observables/observableSignal.ts +++ b/src/util/vs/base/common/observableInternal/observables/observableSignal.ts @@ -9,6 +9,7 @@ import { IObservableWithChange, ITransaction } from '../base'; import { transaction } from '../transaction'; import { DebugNameData } from '../debugName'; import { BaseObservable } from './baseObservable'; +import { DebugLocation } from '../debugLocation'; /** * Creates a signal that can be triggered to invalidate observers. @@ -17,11 +18,11 @@ import { BaseObservable } from './baseObservable'; */ export function observableSignal(debugName: string): IObservableSignal; export function observableSignal(owner: object): IObservableSignal; -export function observableSignal(debugNameOrOwner: string | object): IObservableSignal { +export function observableSignal(debugNameOrOwner: string | object, debugLocation = DebugLocation.ofCaller()): IObservableSignal { if (typeof debugNameOrOwner === 'string') { - return new ObservableSignal(debugNameOrOwner); + return new ObservableSignal(debugNameOrOwner, undefined, debugLocation); } else { - return new ObservableSignal(undefined, debugNameOrOwner); + return new ObservableSignal(undefined, debugNameOrOwner, debugLocation); } } @@ -40,9 +41,10 @@ class ObservableSignal extends BaseObservable implements constructor( private readonly _debugName: string | undefined, - private readonly _owner?: object + private readonly _owner: object | undefined, + debugLocation: DebugLocation ) { - super(); + super(debugLocation); } public trigger(tx: ITransaction | undefined, change: TChange): void { diff --git a/src/util/vs/base/common/observableInternal/observables/observableSignalFromEvent.ts b/src/util/vs/base/common/observableInternal/observables/observableSignalFromEvent.ts index b9d6952ef..1290e1385 100644 --- a/src/util/vs/base/common/observableInternal/observables/observableSignalFromEvent.ts +++ b/src/util/vs/base/common/observableInternal/observables/observableSignalFromEvent.ts @@ -10,12 +10,14 @@ import { transaction } from '../transaction'; import { Event, IDisposable } from '../commonFacade/deps'; import { DebugOwner, DebugNameData } from '../debugName'; import { BaseObservable } from './baseObservable'; +import { DebugLocation } from '../debugLocation'; export function observableSignalFromEvent( owner: DebugOwner | string, - event: Event + event: Event, + debugLocation = DebugLocation.ofCaller() ): IObservable { - return new FromEventObservableSignal(typeof owner === 'string' ? owner : new DebugNameData(owner, undefined, undefined), event); + return new FromEventObservableSignal(typeof owner === 'string' ? owner : new DebugNameData(owner, undefined, undefined), event, debugLocation); } class FromEventObservableSignal extends BaseObservable { @@ -24,9 +26,10 @@ class FromEventObservableSignal extends BaseObservable { public readonly debugName: string; constructor( debugNameDataOrName: DebugNameData | string, - private readonly event: Event + private readonly event: Event, + debugLocation: DebugLocation ) { - super(); + super(debugLocation); this.debugName = typeof debugNameDataOrName === 'string' ? debugNameDataOrName : debugNameDataOrName.getDebugName(this) ?? 'Observable Signal From Event'; diff --git a/src/util/vs/base/common/observableInternal/observables/observableValue.ts b/src/util/vs/base/common/observableInternal/observables/observableValue.ts index 7757effa1..6d66d622e 100644 --- a/src/util/vs/base/common/observableInternal/observables/observableValue.ts +++ b/src/util/vs/base/common/observableInternal/observables/observableValue.ts @@ -11,6 +11,7 @@ import { BaseObservable } from './baseObservable'; import { EqualityComparer, IDisposable, strictEquals } from '../commonFacade/deps'; import { DebugNameData } from '../debugName'; import { getLogger } from '../logging/logging'; +import { DebugLocation } from '../debugLocation'; /** * Creates an observable value. @@ -21,14 +22,14 @@ import { getLogger } from '../logging/logging'; export function observableValue(name: string, initialValue: T): ISettableObservable; export function observableValue(owner: object, initialValue: T): ISettableObservable; -export function observableValue(nameOrOwner: string | object, initialValue: T): ISettableObservable { +export function observableValue(nameOrOwner: string | object, initialValue: T, debugLocation = DebugLocation.ofCaller()): ISettableObservable { let debugNameData: DebugNameData; if (typeof nameOrOwner === 'string') { debugNameData = new DebugNameData(undefined, nameOrOwner, undefined); } else { debugNameData = new DebugNameData(nameOrOwner, undefined, undefined); } - return new ObservableValue(debugNameData, initialValue, strictEquals); + return new ObservableValue(debugNameData, initialValue, strictEquals, debugLocation); } export class ObservableValue @@ -43,9 +44,10 @@ export class ObservableValue constructor( private readonly _debugNameData: DebugNameData, initialValue: T, - private readonly _equalityComparator: EqualityComparer + private readonly _equalityComparator: EqualityComparer, + debugLocation: DebugLocation ) { - super(); + super(debugLocation); this._value = initialValue; getLogger()?.handleObservableUpdated(this, { hadValue: false, newValue: initialValue, change: undefined, didChange: true, oldValue: undefined }); @@ -102,14 +104,14 @@ export class ObservableValue * When a new value is set, the previous value is disposed. */ -export function disposableObservableValue(nameOrOwner: string | object, initialValue: T): ISettableObservable & IDisposable { +export function disposableObservableValue(nameOrOwner: string | object, initialValue: T, debugLocation = DebugLocation.ofCaller()): ISettableObservable & IDisposable { let debugNameData: DebugNameData; if (typeof nameOrOwner === 'string') { debugNameData = new DebugNameData(undefined, nameOrOwner, undefined); } else { debugNameData = new DebugNameData(nameOrOwner, undefined, undefined); } - return new DisposableObservableValue(debugNameData, initialValue, strictEquals); + return new DisposableObservableValue(debugNameData, initialValue, strictEquals, debugLocation); } export class DisposableObservableValue extends ObservableValue implements IDisposable { diff --git a/src/util/vs/base/common/observableInternal/observables/observableValueOpts.ts b/src/util/vs/base/common/observableInternal/observables/observableValueOpts.ts index d93b5b2d0..1fe82f33d 100644 --- a/src/util/vs/base/common/observableInternal/observables/observableValueOpts.ts +++ b/src/util/vs/base/common/observableInternal/observables/observableValueOpts.ts @@ -10,24 +10,28 @@ import { DebugNameData, IDebugNameData } from '../debugName'; import { EqualityComparer, strictEquals } from '../commonFacade/deps'; import { ObservableValue } from './observableValue'; import { LazyObservableValue } from './lazyObservableValue'; +import { DebugLocation } from '../debugLocation'; export function observableValueOpts( options: IDebugNameData & { equalsFn?: EqualityComparer; lazy?: boolean; }, - initialValue: T + initialValue: T, + debugLocation = DebugLocation.ofCaller(), ): ISettableObservable { if (options.lazy) { return new LazyObservableValue( new DebugNameData(options.owner, options.debugName, undefined), initialValue, options.equalsFn ?? strictEquals, + debugLocation ); } return new ObservableValue( new DebugNameData(options.owner, options.debugName, undefined), initialValue, options.equalsFn ?? strictEquals, + debugLocation ); } diff --git a/src/util/vs/base/common/observableInternal/reactions/autorun.ts b/src/util/vs/base/common/observableInternal/reactions/autorun.ts index f7c3f2680..83a30752a 100644 --- a/src/util/vs/base/common/observableInternal/reactions/autorun.ts +++ b/src/util/vs/base/common/observableInternal/reactions/autorun.ts @@ -10,16 +10,18 @@ import { IChangeTracker } from '../changeTracker'; import { DisposableStore, IDisposable, toDisposable } from '../commonFacade/deps'; import { DebugNameData, IDebugNameData } from '../debugName'; import { AutorunObserver } from './autorunImpl'; +import { DebugLocation } from '../debugLocation'; /** * Runs immediately and whenever a transaction ends and an observed observable changed. * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ -export function autorun(fn: (reader: IReaderWithStore) => void): IDisposable { +export function autorun(fn: (reader: IReaderWithStore) => void, debugLocation = DebugLocation.ofCaller()): IDisposable { return new AutorunObserver( new DebugNameData(undefined, undefined, fn), fn, - undefined + undefined, + debugLocation ); } @@ -27,11 +29,12 @@ export function autorun(fn: (reader: IReaderWithStore) => void): IDisposable { * Runs immediately and whenever a transaction ends and an observed observable changed. * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ -export function autorunOpts(options: IDebugNameData & {}, fn: (reader: IReaderWithStore) => void): IDisposable { +export function autorunOpts(options: IDebugNameData & {}, fn: (reader: IReaderWithStore) => void, debugLocation = DebugLocation.ofCaller()): IDisposable { return new AutorunObserver( new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, - undefined + undefined, + debugLocation ); } @@ -50,12 +53,14 @@ export function autorunHandleChanges( options: IDebugNameData & { changeTracker: IChangeTracker; }, - fn: (reader: IReader, changeSummary: TChangeSummary) => void + fn: (reader: IReader, changeSummary: TChangeSummary) => void, + debugLocation = DebugLocation.ofCaller() ): IDisposable { return new AutorunObserver( new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, - options.changeTracker + options.changeTracker, + debugLocation ); } @@ -151,3 +156,33 @@ export function autorunIterableDelta( } }); } + +export interface IReaderWithDispose extends IReaderWithStore, IDisposable { } + +/** + * An autorun with a `dispose()` method on its `reader` which cancels the autorun. + * It it safe to call `dispose()` synchronously. + */ +export function autorunSelfDisposable(fn: (reader: IReaderWithDispose) => void, debugLocation = DebugLocation.ofCaller()): IDisposable { + let ar: IDisposable | undefined; + let disposed = false; + + // eslint-disable-next-line prefer-const + ar = autorun(reader => { + fn({ + delayedStore: reader.delayedStore, + store: reader.store, + readObservable: reader.readObservable.bind(reader), + dispose: () => { + ar?.dispose(); + disposed = true; + } + }); + }, debugLocation); + + if (disposed) { + ar.dispose(); + } + + return ar; +} diff --git a/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts b/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts index 81a5f44ed..a6d02351e 100644 --- a/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts +++ b/src/util/vs/base/common/observableInternal/reactions/autorunImpl.ts @@ -10,6 +10,7 @@ import { DebugNameData } from '../debugName'; import { assertFn, BugIndicatingError, DisposableStore, IDisposable, markAsDisposed, onBugIndicatingError, trackDisposable } from '../commonFacade/deps'; import { getLogger } from '../logging/logging'; import { IChangeTracker } from '../changeTracker'; +import { DebugLocation } from '../debugLocation'; export const enum AutorunState { /** @@ -42,9 +43,10 @@ export class AutorunObserver implements IObserver, IReader public readonly _debugNameData: DebugNameData, public readonly _runFn: (reader: IReaderWithStore, changeSummary: TChangeSummary) => void, private readonly _changeTracker: IChangeTracker | undefined, + debugLocation: DebugLocation ) { this._changeSummary = this._changeTracker?.createChangeSummary(undefined); - getLogger()?.handleAutorunCreated(this); + getLogger()?.handleAutorunCreated(this, debugLocation); this._run(); trackDisposable(this); diff --git a/src/util/vs/base/common/observableInternal/set.ts b/src/util/vs/base/common/observableInternal/set.ts index d9992e0b5..7457ccc56 100644 --- a/src/util/vs/base/common/observableInternal/set.ts +++ b/src/util/vs/base/common/observableInternal/set.ts @@ -5,8 +5,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValueOpts, IObservable, ITransaction } from '../observable'; - +import { IObservable, ITransaction } from '../observable'; +import { observableValueOpts } from './observables/observableValueOpts'; export class ObservableSet implements Set { diff --git a/src/util/vs/base/common/observableInternal/utils/utils.ts b/src/util/vs/base/common/observableInternal/utils/utils.ts index 66cd8dab7..909d73b23 100644 --- a/src/util/vs/base/common/observableInternal/utils/utils.ts +++ b/src/util/vs/base/common/observableInternal/utils/utils.ts @@ -5,16 +5,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { autorun } from '../reactions/autorun'; import { IObservable, IObservableWithChange, IObserver, IReader, ITransaction } from '../base'; -import { DisposableStore, Event, IDisposable, toDisposable } from '../commonFacade/deps'; +import { transaction } from '../transaction'; +import { observableValue } from '../observables/observableValue'; import { DebugOwner } from '../debugName'; -import { _setKeepObserved, _setRecomputeInitiallyAndOnChange } from '../observables/baseObservable'; +import { DisposableStore, Event, IDisposable, toDisposable } from '../commonFacade/deps'; import { derived, derivedOpts } from '../observables/derived'; import { observableFromEvent } from '../observables/observableFromEvent'; import { observableSignal } from '../observables/observableSignal'; -import { observableValue } from '../observables/observableValue'; -import { autorun } from '../reactions/autorun'; -import { transaction } from '../transaction'; +import { _setKeepObserved, _setRecomputeInitiallyAndOnChange } from '../observables/baseObservable'; export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { const observable = observableValue<{ value?: T }>('promiseValue', {}); @@ -39,7 +39,7 @@ export function signalFromObservable(owner: DebugOwner | undefined, observabl export function debouncedObservableDeprecated(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { const debouncedObservable = observableValue('debounced', undefined); - let timeout: any | undefined = undefined; + let timeout: Timeout | undefined = undefined; disposableStore.add(autorun(reader => { /** @description debounce */ @@ -52,7 +52,7 @@ export function debouncedObservableDeprecated(observable: IObservable, deb transaction(tx => { debouncedObservable.set(value, tx); }); - }, debounceMs); + }, debounceMs) as any; })); @@ -66,7 +66,7 @@ export function debouncedObservable(observable: IObservable, debounceMs: n let hasValue = false; let lastValue: T | undefined; - let timeout: any | undefined = undefined; + let timeout: Timeout | undefined = undefined; return observableFromEvent(cb => { const d = autorun(reader => { @@ -82,7 +82,7 @@ export function debouncedObservable(observable: IObservable, debounceMs: n timeout = setTimeout(() => { lastValue = value; cb(); - }, debounceMs); + }, debounceMs) as any; } }); return { @@ -104,7 +104,7 @@ export function debouncedObservable(observable: IObservable, debounceMs: n export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { const observable = observableValue('triggeredRecently', false); - let timeout: any | undefined = undefined; + let timeout: Timeout | undefined = undefined; disposableStore.add(event(() => { observable.set(true, undefined); @@ -114,7 +114,7 @@ export function wasEventTriggeredRecently(event: Event, timeoutMs: number, } timeout = setTimeout(() => { observable.set(false, undefined); - }, timeoutMs); + }, timeoutMs) as any; })); return observable; diff --git a/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts b/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts index 082a5e00f..f66282069 100644 --- a/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts +++ b/src/util/vs/base/common/observableInternal/utils/utilsCancellation.ts @@ -11,6 +11,7 @@ import { CancellationError, CancellationToken, CancellationTokenSource } from '. import { strictEquals } from '../commonFacade/deps'; import { autorun } from '../reactions/autorun'; import { Derived } from '../observables/derivedImpl'; +import { DebugLocation } from '../debugLocation'; /** * Resolves the promise when the observables state matches the predicate. @@ -94,6 +95,7 @@ export function derivedWithCancellationToken(computeFnOrOwner: ((reader: IRea return computeFn(r, cancellationTokenSource.token); }, undefined, () => cancellationTokenSource?.dispose(), - strictEquals + strictEquals, + DebugLocation.ofCaller() ); } diff --git a/src/util/vs/base/common/platform.ts b/src/util/vs/base/common/platform.ts index 590615cdb..438ece013 100644 --- a/src/util/vs/base/common/platform.ts +++ b/src/util/vs/base/common/platform.ts @@ -79,7 +79,7 @@ if (typeof nodeProcess === 'object') { _isLinux = (nodeProcess.platform === 'linux'); _isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION']; _isElectron = isElectronProcess; - _isCI = !!nodeProcess.env['CI'] || !!nodeProcess.env['BUILD_ARTIFACTSTAGINGDIRECTORY']; + _isCI = !!nodeProcess.env['CI'] || !!nodeProcess.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] || !!nodeProcess.env['GITHUB_WORKSPACE']; _locale = LANGUAGE_DEFAULT; _language = LANGUAGE_DEFAULT; const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG']; diff --git a/src/util/vs/base/common/sseParser.ts b/src/util/vs/base/common/sseParser.ts index 0990e0247..545970772 100644 --- a/src/util/vs/base/common/sseParser.ts +++ b/src/util/vs/base/common/sseParser.ts @@ -1,3 +1,5 @@ +//!!! 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. diff --git a/src/util/vs/base/common/stopwatch.ts b/src/util/vs/base/common/stopwatch.ts index a8ef26dc5..b4b9d7c28 100644 --- a/src/util/vs/base/common/stopwatch.ts +++ b/src/util/vs/base/common/stopwatch.ts @@ -5,10 +5,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// fake definition so that the valid layers check won't trip on this -declare const globalThis: { performance?: { now(): number } }; - -const hasPerformanceNow = (globalThis.performance && typeof globalThis.performance.now === 'function'); +declare const globalThis: { performance: { now(): number } }; +const performanceNow = globalThis.performance.now.bind(globalThis.performance); export class StopWatch { @@ -22,7 +20,7 @@ export class StopWatch { } constructor(highResolution?: boolean) { - this._now = hasPerformanceNow && highResolution === false ? Date.now : globalThis.performance!.now.bind(globalThis.performance); + this._now = highResolution === false ? Date.now : performanceNow; this._startTime = this._now(); this._stopTime = -1; } diff --git a/src/util/vs/base/common/strings.ts b/src/util/vs/base/common/strings.ts index 765254f70..6bf185724 100644 --- a/src/util/vs/base/common/strings.ts +++ b/src/util/vs/base/common/strings.ts @@ -784,34 +784,6 @@ export function lcut(text: string, n: number, prefix = ''): string { return prefix + trimmed.substring(i).trimStart(); } -/** - * Given a string and a max length returns a shorted version. Shorting - * happens at favorable positions - such as whitespace or punctuation characters. - * The return value can be longer than the given value of `n`. Trailing whitespace is always trimmed. - */ -export function rcut(text: string, n: number, suffix = ''): string { - const trimmed = text.trimEnd(); - - if (trimmed.length < n) { - return trimmed; - } - - const parts = text.split(/\b/); - let result = ''; - for (const part of parts) { - if (result.length > 0 && result.length + part.length > n) { - break; - } - result += part; - } - - if (result === trimmed) { - return result; - } - - return result.trim().replace(/b$/, '') + suffix; -} - // Defacto standard: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html const CSI_SEQUENCE = /(?:\x1b\[|\x9b)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/; const OSC_SEQUENCE = /(?:\x1b\]|\x9d).*?(?:\x1b\\|\x07|\x9c)/; @@ -1363,3 +1335,30 @@ export class InvisibleCharacters { } export const Ellipsis = '\u2026'; + +/** + * Convert a Unicode string to a string in which each 16-bit unit occupies only one byte + * + * From https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa + */ +function toBinary(str: string): string { + const codeUnits = new Uint16Array(str.length); + for (let i = 0; i < codeUnits.length; i++) { + codeUnits[i] = str.charCodeAt(i); + } + let binary = ''; + const uint8array = new Uint8Array(codeUnits.buffer); + for (let i = 0; i < uint8array.length; i++) { + binary += String.fromCharCode(uint8array[i]); + } + return binary; +} + +/** + * Version of the global `btoa` function that handles multi-byte characters instead + * of throwing an exception. + */ + +export function multibyteAwareBtoa(str: string): string { + return btoa(toBinary(str)); +} diff --git a/src/util/vs/base/common/ternarySearchTree.ts b/src/util/vs/base/common/ternarySearchTree.ts index 0ebf9d9fc..ba8252d52 100644 --- a/src/util/vs/base/common/ternarySearchTree.ts +++ b/src/util/vs/base/common/ternarySearchTree.ts @@ -6,6 +6,7 @@ *--------------------------------------------------------------------------------------------*/ import { shuffle } from './arrays'; +import { assert } from './assert'; import { CharCode } from './charCode'; import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from './strings'; import { URI } from './uri'; @@ -266,11 +267,11 @@ abstract class Undef { class TernarySearchTreeNode { height: number = 1; segment!: string; - value: V | typeof Undef.Val | undefined; - key: K | undefined; - left: TernarySearchTreeNode | undefined; - mid: TernarySearchTreeNode | undefined; - right: TernarySearchTreeNode | undefined; + value: V | typeof Undef.Val | undefined = undefined; + key: K | undefined = undefined; + left: TernarySearchTreeNode | undefined = undefined; + mid: TernarySearchTreeNode | undefined = undefined; + right: TernarySearchTreeNode | undefined = undefined; isEmpty(): boolean { return !this.left && !this.mid && !this.right && this.value === undefined; @@ -565,13 +566,40 @@ export class TernarySearchTree { // full node // replace deleted-node with the min-node of the right branch. // If there is no true min-node leave things as they are - const min = this._min(node.right); + const stack2: typeof stack = [[Dir.Right, node]]; + const min = this._min(node.right, stack2); + if (min.key) { - const { key, value, segment } = min; - this._delete(min.key, false); - node.key = key; - node.value = value; - node.segment = segment; + + node.key = min.key; + node.value = min.value; + node.segment = min.segment; + + // remove NODE (inorder successor can only have right child) + const newChild = min.right; + if (stack2.length > 1) { + const [dir, parent] = stack2[stack2.length - 1]; + switch (dir) { + case Dir.Left: parent.left = newChild; break; + case Dir.Mid: assert(false); + case Dir.Right: assert(false); + } + } else { + node.right = newChild; + } + + // balance right branch and UPDATE parent pointer for stack + const newChild2 = this._balanceByStack(stack2)!; + if (stack.length > 0) { + const [dir, parent] = stack[stack.length - 1]; + switch (dir) { + case Dir.Left: parent.left = newChild2; break; + case Dir.Mid: parent.mid = newChild2; break; + case Dir.Right: parent.right = newChild2; break; + } + } else { + this._root = newChild2; + } } } else { @@ -591,6 +619,19 @@ export class TernarySearchTree { } // AVL balance + this._root = this._balanceByStack(stack) ?? this._root; + } + + private _min(node: TernarySearchTreeNode, stack: [Dir, TernarySearchTreeNode][]): TernarySearchTreeNode { + while (node.left) { + stack.push([Dir.Left, node]); + node = node.left; + } + return node; + } + + private _balanceByStack(stack: [Dir, TernarySearchTreeNode][]) { + for (let i = stack.length - 1; i >= 0; i--) { const node = stack[i][1]; @@ -633,16 +674,11 @@ export class TernarySearchTree { break; } } else { - this._root = stack[0][1]; + return stack[0][1]; } } - } - private _min(node: TernarySearchTreeNode): TernarySearchTreeNode { - while (node.left) { - node = node.left; - } - return node; + return undefined; } findSubstr(key: K): V | undefined { diff --git a/src/util/vs/base/node/ports.ts b/src/util/vs/base/node/ports.ts index dddff8328..1be0092bc 100644 --- a/src/util/vs/base/node/ports.ts +++ b/src/util/vs/base/node/ports.ts @@ -66,7 +66,7 @@ function doFindFreePort(startPort: number, giveUpAfter: number, stride: number, } // Reference: https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/port_util.cc#56 -export const BROWSER_RESTRICTED_PORTS: any = { +export const BROWSER_RESTRICTED_PORTS: Record = { 1: true, // tcpmux 7: true, // echo 9: true, // discard @@ -149,12 +149,16 @@ export const BROWSER_RESTRICTED_PORTS: any = { 10080: true // Amanda }; +export function isPortFree(port: number, timeout: number): Promise { + return findFreePortFaster(port, 0, timeout).then(port => port !== 0); +} + /** * 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. */ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeout: number, hostname: string = '127.0.0.1'): Promise { let resolved: boolean = false; - let timeoutHandle: NodeJS.Timeout | undefined = undefined; + let timeoutHandle: Timeout | undefined = undefined; let countTried: number = 1; const server = net.createServer({ pauseOnConnect: true }); function doResolve(port: number, resolve: (port: number) => void) { @@ -171,7 +175,7 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo return new Promise(resolve => { timeoutHandle = setTimeout(() => { doResolve(0, resolve); - }, timeout); + }, timeout) as any; server.on('listening', () => { doResolve(startPort, resolve); diff --git a/src/util/vs/editor/common/core/edits/arrayEdit.ts b/src/util/vs/editor/common/core/edits/arrayEdit.ts deleted file mode 100644 index fc9830307..000000000 --- a/src/util/vs/editor/common/core/edits/arrayEdit.ts +++ /dev/null @@ -1,92 +0,0 @@ -//!!! 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 { OffsetRange } from '../ranges/offsetRange'; -import { BaseEdit, BaseReplacement } from './edit'; - -/** - * Represents a set of replacements to an array. - * All these replacements are applied at once. -*/ -export class ArrayEdit extends BaseEdit, ArrayEdit> { - public static readonly empty = new ArrayEdit([]); - - public static create(replacements: readonly ArrayReplacement[]): ArrayEdit { - return new ArrayEdit(replacements); - } - - public static single(replacement: ArrayReplacement): ArrayEdit { - return new ArrayEdit([replacement]); - } - - public static replace(range: OffsetRange, replacement: readonly T[]): ArrayEdit { - return new ArrayEdit([new ArrayReplacement(range, replacement)]); - } - - public static insert(offset: number, replacement: readonly T[]): ArrayEdit { - return new ArrayEdit([new ArrayReplacement(OffsetRange.emptyAt(offset), replacement)]); - } - - public static delete(range: OffsetRange): ArrayEdit { - return new ArrayEdit([new ArrayReplacement(range, [])]); - } - - protected override _createNew(replacements: readonly ArrayReplacement[]): ArrayEdit { - return new ArrayEdit(replacements); - } - - public apply(data: readonly T[]): readonly T[] { - const resultData: T[] = []; - let pos = 0; - for (const edit of this.replacements) { - resultData.push(...data.slice(pos, edit.replaceRange.start)); - resultData.push(...edit.newValue); - pos = edit.replaceRange.endExclusive; - } - resultData.push(...data.slice(pos)); - return resultData; - } - - /** - * Creates an edit that reverts this edit. - */ - public inverse(baseVal: readonly T[]): ArrayEdit { - const edits: ArrayReplacement[] = []; - let offset = 0; - for (const e of this.replacements) { - edits.push(new ArrayReplacement( - OffsetRange.ofStartAndLength(e.replaceRange.start + offset, e.newValue.length), - baseVal.slice(e.replaceRange.start, e.replaceRange.endExclusive), - )); - offset += e.newValue.length - e.replaceRange.length; - } - return new ArrayEdit(edits); - } -} - -export class ArrayReplacement extends BaseReplacement> { - constructor( - range: OffsetRange, - public readonly newValue: readonly T[] - ) { - super(range); - } - - override equals(other: ArrayReplacement): boolean { - return this.replaceRange.equals(other.replaceRange) && this.newValue.length === other.newValue.length && this.newValue.every((v, i) => v === other.newValue[i]); - } - - getNewLength(): number { return this.newValue.length; } - - tryJoinTouching(other: ArrayReplacement): ArrayReplacement | undefined { - return new ArrayReplacement(this.replaceRange.joinRightTouching(other.replaceRange), this.newValue.concat(other.newValue)); - } - - slice(range: OffsetRange, rangeInReplacement?: OffsetRange): ArrayReplacement { - return new ArrayReplacement(range, rangeInReplacement ? rangeInReplacement.slice(this.newValue) : this.newValue); - } -} diff --git a/src/util/vs/editor/common/core/edits/edit.ts b/src/util/vs/editor/common/core/edits/edit.ts index 79b9da422..b262d2985 100644 --- a/src/util/vs/editor/common/core/edits/edit.ts +++ b/src/util/vs/editor/common/core/edits/edit.ts @@ -185,7 +185,6 @@ export abstract class BaseEdit = BaseReplacement boolean): { e1: TEdit; e2: TEdit } { const e1: T[] = []; const e2: T[] = []; diff --git a/src/util/vs/editor/common/core/edits/lengthEdit.ts b/src/util/vs/editor/common/core/edits/lengthEdit.ts deleted file mode 100644 index f06092e62..000000000 --- a/src/util/vs/editor/common/core/edits/lengthEdit.ts +++ /dev/null @@ -1,132 +0,0 @@ -//!!! 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 { OffsetRange } from '../ranges/offsetRange'; -import { AnyEdit, BaseEdit, BaseReplacement } from './edit'; - -/** - * Like a normal edit, but only captures the length information. -*/ -export class LengthEdit extends BaseEdit { - public static readonly empty = new LengthEdit([]); - - public static fromEdit(edit: AnyEdit): LengthEdit { - return new LengthEdit(edit.replacements.map(r => new LengthReplacement(r.replaceRange, r.getNewLength()))); - } - - public static create(replacements: readonly LengthReplacement[]): LengthEdit { - return new LengthEdit(replacements); - } - - public static single(replacement: LengthReplacement): LengthEdit { - return new LengthEdit([replacement]); - } - - public static replace(range: OffsetRange, newLength: number): LengthEdit { - return new LengthEdit([new LengthReplacement(range, newLength)]); - } - - public static insert(offset: number, newLength: number): LengthEdit { - return new LengthEdit([new LengthReplacement(OffsetRange.emptyAt(offset), newLength)]); - } - - public static delete(range: OffsetRange): LengthEdit { - return new LengthEdit([new LengthReplacement(range, 0)]); - } - - public static compose(edits: readonly LengthEdit[]): LengthEdit { - let e = LengthEdit.empty; - for (const edit of edits) { - e = e.compose(edit); - } - return e; - } - - /** - * Creates an edit that reverts this edit. - */ - public inverse(): LengthEdit { - const edits: LengthReplacement[] = []; - let offset = 0; - for (const e of this.replacements) { - edits.push(new LengthReplacement( - OffsetRange.ofStartAndLength(e.replaceRange.start + offset, e.newLength), - e.replaceRange.length, - )); - offset += e.newLength - e.replaceRange.length; - } - return new LengthEdit(edits); - } - - protected override _createNew(replacements: readonly LengthReplacement[]): LengthEdit { - return new LengthEdit(replacements); - } - - public applyArray(arr: readonly T[], fillItem: T): T[] { - const newArr = new Array(this.getNewDataLength(arr.length)); - - let srcPos = 0; - let dstPos = 0; - - for (const replacement of this.replacements) { - // Copy items before the current replacement - for (let i = srcPos; i < replacement.replaceRange.start; i++) { - newArr[dstPos++] = arr[i]; - } - - // Skip the replaced items in the source array - srcPos = replacement.replaceRange.endExclusive; - - // Fill with the provided fillItem for insertions - for (let i = 0; i < replacement.newLength; i++) { - newArr[dstPos++] = fillItem; - } - } - - // Copy any remaining items from the original array - while (srcPos < arr.length) { - newArr[dstPos++] = arr[srcPos++]; - } - - return newArr; - } -} - -export class LengthReplacement extends BaseReplacement { - public static create( - startOffset: number, - endOffsetExclusive: number, - newLength: number, - ): LengthReplacement { - return new LengthReplacement(new OffsetRange(startOffset, endOffsetExclusive), newLength); - } - - constructor( - range: OffsetRange, - public readonly newLength: number, - ) { - super(range); - } - - override equals(other: LengthReplacement): boolean { - return this.replaceRange.equals(other.replaceRange) && this.newLength === other.newLength; - } - - getNewLength(): number { return this.newLength; } - - tryJoinTouching(other: LengthReplacement): LengthReplacement | undefined { - return new LengthReplacement(this.replaceRange.joinRightTouching(other.replaceRange), this.newLength + other.newLength); - } - - slice(range: OffsetRange, rangeInReplacement: OffsetRange): LengthReplacement { - return new LengthReplacement(range, rangeInReplacement.length); - } - - override toString() { - return `[${this.replaceRange.start}, +${this.replaceRange.length}) -> +${this.newLength}}`; - } -} diff --git a/src/util/vs/editor/common/core/edits/lineEdit.ts b/src/util/vs/editor/common/core/edits/lineEdit.ts index 90011d1cf..6c40f0d48 100644 --- a/src/util/vs/editor/common/core/edits/lineEdit.ts +++ b/src/util/vs/editor/common/core/edits/lineEdit.ts @@ -61,18 +61,18 @@ export class LineEdit { /** * Have to be sorted by start line number and non-intersecting. */ - public readonly edits: readonly LineReplacement[] + public readonly replacements: readonly LineReplacement[] ) { - assert(checkAdjacentItems(edits, (i1, i2) => i1.lineRange.endLineNumberExclusive <= i2.lineRange.startLineNumber)); + assert(checkAdjacentItems(replacements, (i1, i2) => i1.lineRange.endLineNumberExclusive <= i2.lineRange.startLineNumber)); } public isEmpty(): boolean { - return this.edits.length === 0; + return this.replacements.length === 0; } public toEdit(initialValue: AbstractText): StringEdit { const edits: StringReplacement[] = []; - for (const edit of this.edits) { + for (const edit of this.replacements) { const singleEdit = edit.toSingleEdit(initialValue); edits.push(singleEdit); } @@ -80,17 +80,17 @@ export class LineEdit { } public toString(): string { - return this.edits.map(e => e.toString()).join(','); + return this.replacements.map(e => e.toString()).join(','); } public serialize(): SerializedLineEdit { - return this.edits.map(e => e.serialize()); + return this.replacements.map(e => e.serialize()); } public getNewLineRanges(): LineRange[] { const ranges: LineRange[] = []; let offset = 0; - for (const e of this.edits) { + for (const e of this.replacements) { ranges.push(LineRange.ofLength(e.lineRange.startLineNumber + offset, e.newLines.length),); offset += e.newLines.length - e.lineRange.length; } @@ -99,7 +99,7 @@ export class LineEdit { public mapLineNumber(lineNumber: number): number { let lineDelta = 0; - for (const e of this.edits) { + for (const e of this.replacements) { if (e.lineRange.endLineNumberExclusive > lineNumber) { break; } @@ -124,12 +124,12 @@ export class LineEdit { } public touches(other: LineEdit): boolean { - return this.edits.some(e1 => other.edits.some(e2 => e1.lineRange.intersect(e2.lineRange))); + return this.replacements.some(e1 => other.replacements.some(e2 => e1.lineRange.intersect(e2.lineRange))); } public rebase(base: LineEdit): LineEdit { return new LineEdit( - this.edits.map(e => new LineReplacement(base.mapLineRange(e.lineRange), e.newLines)), + this.replacements.map(e => new LineReplacement(base.mapLineRange(e.lineRange), e.newLines)), ); } @@ -156,7 +156,7 @@ export class LineEdit { let lineDelta = 0; let first = true; - for (const edits of groupAdjacentBy(this.edits, (e1, e2) => e1.lineRange.distanceToRange(e2.lineRange) <= 5)) { + for (const edits of groupAdjacentBy(this.replacements, (e1, e2) => e1.lineRange.distanceToRange(e2.lineRange) <= 5)) { if (!first) { pushSeperator(); } else { @@ -199,7 +199,7 @@ export class LineEdit { let currentLineIndex = 0; - for (const edit of this.edits) { + for (const edit of this.replacements) { while (currentLineIndex < edit.lineRange.startLineNumber - 1) { result.push(lines[currentLineIndex]); currentLineIndex++; @@ -222,7 +222,7 @@ export class LineEdit { public inverse(originalLines: string[]): LineEdit { const newRanges = this.getNewLineRanges(); - return new LineEdit(this.edits.map((e, idx) => new LineReplacement( + return new LineEdit(this.replacements.map((e, idx) => new LineReplacement( newRanges[idx], originalLines.slice(e.lineRange.startLineNumber - 1, e.lineRange.endLineNumberExclusive - 1), ))); diff --git a/src/util/vs/editor/common/core/edits/stringEdit.ts b/src/util/vs/editor/common/core/edits/stringEdit.ts index bf56fc53c..8a9970154 100644 --- a/src/util/vs/editor/common/core/edits/stringEdit.ts +++ b/src/util/vs/editor/common/core/edits/stringEdit.ts @@ -10,6 +10,7 @@ import { OffsetRange } from '../ranges/offsetRange'; import { StringText } from '../text/abstractText'; import { BaseEdit, BaseReplacement } from './edit'; + export abstract class BaseStringEdit = BaseStringReplacement, TEdit extends BaseStringEdit = BaseStringEdit> extends BaseEdit { get TReplacement(): T { throw new Error('TReplacement is not defined for BaseStringEdit'); @@ -30,9 +31,9 @@ export abstract class BaseStringEdit = BaseSt * r := trySwap(e1, e2); * e1.compose(e2) === r.e1.compose(r.e2) */ - public static trySwap(e1: BaseStringEdit, e2: BaseStringEdit): { e1: StringEdit; e2: StringEdit; } | undefined { + public static trySwap(e1: BaseStringEdit, e2: BaseStringEdit): { e1: StringEdit; e2: StringEdit } | undefined { // TODO make this more efficient - const e1Inv = e1.inverseOnSlice((start, endEx) => " ".repeat(endEx - start)); + const e1Inv = e1.inverseOnSlice((start, endEx) => ' '.repeat(endEx - start)); const e1_ = e2.tryRebase(e1Inv); if (!e1_) { @@ -82,16 +83,15 @@ export abstract class BaseStringEdit = BaseSt return this.inverseOnSlice((start, endEx) => original.substring(start, endEx)); } - /** - * Consider `t1 := text o base` and `t2 := text o this`. - * We are interested in `tm := tryMerge(t1, t2, base: text)`. - * For that, we compute `tm' := t1 o base o this.rebase(base)` - * such that `tm' === tm`. - */ - public tryRebase(base: StringEdit): StringEdit | undefined; - /** @deprecated avoid */ - public tryRebase(base: StringEdit, noOverlap: false): StringEdit; - public tryRebase(base: StringEdit, noOverlap: boolean = true): StringEdit | undefined { + public rebaseSkipConflicting(base: StringEdit): StringEdit { + return this._tryRebase(base, false)!; + } + + public tryRebase(base: StringEdit): StringEdit | undefined { + return this._tryRebase(base, true); + } + + private _tryRebase(base: StringEdit, noOverlap: boolean): StringEdit | undefined { const newEdits: StringReplacement[] = []; let baseIdx = 0; @@ -135,11 +135,7 @@ export abstract class BaseStringEdit = BaseSt } public toJson(): ISerializedStringEdit { - return this.replacements.map(e => ({ - txt: e.newText, - pos: e.replaceRange.start, - len: e.replaceRange.length, - })); + return this.replacements.map(e => e.toJson()); } public isNeutralOn(text: string): boolean { @@ -205,7 +201,7 @@ export abstract class BaseStringReplacement = getNewLength(): number { return this.newText.length; } override toString(): string { - return `${this.replaceRange} -> ${JSON.stringify(this.newText)}`; + return `${this.replaceRange} -> "${this.newText}"`; } replace(str: string): string { @@ -231,7 +227,7 @@ export abstract class BaseStringReplacement = const replaceRange = new OffsetRange( this.replaceRange.start + prefixLen, - this.replaceRange.endExclusive - suffixLen + this.replaceRange.endExclusive - suffixLen, ); const newText = this.newText.substring(prefixLen, this.newText.length - suffixLen); @@ -271,6 +267,14 @@ export abstract class BaseStringReplacement = public toEdit(): StringEdit { return new StringEdit([this]); } + + public toJson(): ISerializedStringReplacement { + return ({ + txt: this.newText, + pos: this.replaceRange.start, + len: this.replaceRange.length, + }); + } } @@ -458,11 +462,11 @@ export function applyEditsToRanges(sortedRanges: OffsetRange[], edit: StringEdit } return result; -}/** +} + +/** * Represents data associated to a single edit, which survives certain edit operations. */ - - export interface IEditData { join(other: T): T | undefined; } @@ -472,11 +476,11 @@ export class VoidEditData implements IEditData { return this; } } + /** * Represents a set of replacements to a string. * All these replacements are applied at once. */ - export class AnnotatedStringEdit> extends BaseStringEdit, AnnotatedStringEdit> { public static readonly empty = new AnnotatedStringEdit([]); diff --git a/src/util/vs/editor/common/core/edits/textEdit.ts b/src/util/vs/editor/common/core/edits/textEdit.ts index 40b0c88ce..f054d6831 100644 --- a/src/util/vs/editor/common/core/edits/textEdit.ts +++ b/src/util/vs/editor/common/core/edits/textEdit.ts @@ -5,7 +5,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { equals } from '../../../../base/common/arrays'; +import { compareBy, equals } from '../../../../base/common/arrays'; import { assertFn, checkAdjacentItems } from '../../../../base/common/assert'; import { BugIndicatingError } from '../../../../base/common/errors'; import { commonPrefixLength, commonSuffixLength } from '../../../../base/common/strings'; @@ -26,10 +26,19 @@ export class TextEdit { return new TextEdit([new TextReplacement(originalRange, newText)]); } + public static delete(range: Range): TextEdit { + return new TextEdit([new TextReplacement(range, '')]); + } + public static insert(position: Position, newText: string): TextEdit { return new TextEdit([new TextReplacement(Range.fromPositions(position, position), newText)]); } + public static fromParallelReplacementsUnsorted(replacements: readonly TextReplacement[]): TextEdit { + const r = replacements.slice().sort(compareBy(i => i.range, Range.compareRangesUsingStarts)); + return new TextEdit(r); + } + constructor( public readonly replacements: readonly TextReplacement[] ) { @@ -287,6 +296,10 @@ export class TextReplacement { return new TextReplacement(initialState.getTransformer().getRange(replacement.replaceRange), replacement.newText); } + public static delete(range: Range): TextReplacement { + return new TextReplacement(range, ''); + } + constructor( public readonly range: Range, public readonly text: string, @@ -336,6 +349,12 @@ export class TextReplacement { return this.extendToCoverRange(newRange, initialValue); } + public removeCommonPrefixAndSuffix(text: AbstractText): TextReplacement { + const prefix = this.removeCommonPrefix(text); + const suffix = prefix.removeCommonSuffix(text); + return suffix; + } + public removeCommonPrefix(text: AbstractText): TextReplacement { const normalizedOriginalText = text.getValueOfRange(this.range).replaceAll('\r\n', '\n'); const normalizedModifiedText = this.text.replaceAll('\r\n', '\n'); @@ -349,6 +368,19 @@ export class TextReplacement { return new TextReplacement(range, newText); } + public removeCommonSuffix(text: AbstractText): TextReplacement { + const normalizedOriginalText = text.getValueOfRange(this.range).replaceAll('\r\n', '\n'); + const normalizedModifiedText = this.text.replaceAll('\r\n', '\n'); + + const commonSuffixLen = commonSuffixLength(normalizedOriginalText, normalizedModifiedText); + const end = TextLength.ofText(normalizedOriginalText.substring(0, normalizedOriginalText.length - commonSuffixLen)) + .addToPosition(this.range.getStartPosition()); + + const newText = normalizedModifiedText.substring(0, normalizedModifiedText.length - commonSuffixLen); + const range = Range.fromPositions(this.range.getStartPosition(), end); + return new TextReplacement(range, newText); + } + public isEffectiveDeletion(text: AbstractText): boolean { let newText = this.text.replaceAll('\r\n', '\n'); let existingText = text.getValueOfRange(this.range).replaceAll('\r\n', '\n'); @@ -361,6 +393,12 @@ export class TextReplacement { return newText === ''; } + + public toString(): string { + const start = this.range.getStartPosition(); + const end = this.range.getEndPosition(); + return `(${start.lineNumber},${start.column} -> ${end.lineNumber},${end.column}): "${this.text}"`; + } } function rangeFromPositions(start: Position, end: Position): Range { diff --git a/src/util/vs/editor/common/core/text/abstractText.ts b/src/util/vs/editor/common/core/text/abstractText.ts index c8f61cbe3..948eec8f5 100644 --- a/src/util/vs/editor/common/core/text/abstractText.ts +++ b/src/util/vs/editor/common/core/text/abstractText.ts @@ -8,10 +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 { PositionOffsetTransformer } from './positionToOffset'; import { TextLength } from './textLength'; +import { OffsetRange } from '../ranges/offsetRange'; export abstract class AbstractText { abstract getValueOfRange(range: Range): string; @@ -29,6 +30,10 @@ export abstract class AbstractText { return this.getValueOfRange(this.length.toRange()); } + getValueOfOffsetRange(range: OffsetRange): string { + return this.getValueOfRange(this.getTransformer().getRange(range)); + } + getLineLength(lineNumber: number): number { return this.getValueOfRange(new Range(lineNumber, 1, lineNumber, Number.MAX_SAFE_INTEGER)).length; } @@ -51,6 +56,10 @@ export abstract class AbstractText { return splitLines(value); } + getLinesOfRange(range: LineRange): string[] { + return range.mapToLineArray(lineNumber => this.getLineAt(lineNumber)); + } + equals(other: AbstractText): boolean { if (this === other) { return true; diff --git a/src/util/vs/editor/common/core/text/positionToOffset.ts b/src/util/vs/editor/common/core/text/positionToOffset.ts index 4f97538ae..2fb6960f0 100644 --- a/src/util/vs/editor/common/core/text/positionToOffset.ts +++ b/src/util/vs/editor/common/core/text/positionToOffset.ts @@ -5,117 +5,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { findLastIdxMonotonous } from '../../../../base/common/arraysFind'; import { StringEdit, StringReplacement } from '../edits/stringEdit'; -import { OffsetRange } from '../ranges/offsetRange'; -import { Position } from '../position'; -import { Range } from '../range'; -import { TextReplacement, TextEdit } from '../edits/textEdit'; +import { TextEdit, TextReplacement } from '../edits/textEdit'; +import { _setPositionOffsetTransformerDependencies } from './positionToOffsetImpl'; import { TextLength } from './textLength'; -export abstract class PositionOffsetTransformerBase { - abstract getOffset(position: Position): number; +export { PositionOffsetTransformerBase, PositionOffsetTransformer } from './positionToOffsetImpl'; - getOffsetRange(range: Range): OffsetRange { - return new OffsetRange( - this.getOffset(range.getStartPosition()), - this.getOffset(range.getEndPosition()) - ); - } +_setPositionOffsetTransformerDependencies({ + StringEdit: StringEdit, + StringReplacement: StringReplacement, + TextReplacement: TextReplacement, + TextEdit: TextEdit, + TextLength: TextLength, +}); - abstract getPosition(offset: number): Position; - - getRange(offsetRange: OffsetRange): Range { - return Range.fromPositions( - this.getPosition(offsetRange.start), - this.getPosition(offsetRange.endExclusive) - ); - } - - getStringEdit(edit: TextEdit): StringEdit { - const edits = edit.replacements.map(e => this.getStringReplacement(e)); - return new StringEdit(edits); - } - - getStringReplacement(edit: TextReplacement): StringReplacement { - return new StringReplacement(this.getOffsetRange(edit.range), edit.text); - } - - getSingleTextEdit(edit: StringReplacement): TextReplacement { - return new TextReplacement(this.getRange(edit.replaceRange), edit.newText); - } - - getTextEdit(edit: StringEdit): TextEdit { - const edits = edit.replacements.map(e => this.getSingleTextEdit(e)); - return new TextEdit(edits); - } -} - -export class PositionOffsetTransformer extends PositionOffsetTransformerBase { - private readonly lineStartOffsetByLineIdx: number[]; - private readonly lineEndOffsetByLineIdx: number[]; - - constructor(public readonly text: string) { - super(); - - this.lineStartOffsetByLineIdx = []; - this.lineEndOffsetByLineIdx = []; - - this.lineStartOffsetByLineIdx.push(0); - for (let i = 0; i < text.length; i++) { - if (text.charAt(i) === '\n') { - this.lineStartOffsetByLineIdx.push(i + 1); - if (i > 0 && text.charAt(i - 1) === '\r') { - this.lineEndOffsetByLineIdx.push(i - 1); - } else { - this.lineEndOffsetByLineIdx.push(i); - } - } - } - this.lineEndOffsetByLineIdx.push(text.length); - } - - override getOffset(position: Position): number { - const valPos = this._validatePosition(position); - return this.lineStartOffsetByLineIdx[valPos.lineNumber - 1] + valPos.column - 1; - } - - private _validatePosition(position: Position): Position { - if (position.lineNumber < 1) { - return new Position(1, 1); - } - const lineCount = this.textLength.lineCount + 1; - if (position.lineNumber > lineCount) { - const lineLength = this.getLineLength(lineCount); - return new Position(lineCount, lineLength + 1); - } - if (position.column < 1) { - return new Position(position.lineNumber, 1); - } - const lineLength = this.getLineLength(position.lineNumber); - if (position.column - 1 > lineLength) { - return new Position(position.lineNumber, lineLength + 1); - } - return position; - } - - override getPosition(offset: number): Position { - const idx = findLastIdxMonotonous(this.lineStartOffsetByLineIdx, i => i <= offset); - const lineNumber = idx + 1; - const column = offset - this.lineStartOffsetByLineIdx[idx] + 1; - return new Position(lineNumber, column); - } - - getTextLength(offsetRange: OffsetRange): TextLength { - return TextLength.ofRange(this.getRange(offsetRange)); - } - - get textLength(): TextLength { - const lineIdx = this.lineStartOffsetByLineIdx.length - 1; - return new TextLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); - } - - getLineLength(lineNumber: number): number { - return this.lineEndOffsetByLineIdx[lineNumber - 1] - this.lineStartOffsetByLineIdx[lineNumber - 1]; - } +// TODO@hediet this is dept and needs to go. See https://github.com/microsoft/vscode/issues/251126. +export function ensureDependenciesAreSet(): void { + // Noop } diff --git a/src/util/vs/editor/common/core/text/positionToOffsetImpl.ts b/src/util/vs/editor/common/core/text/positionToOffsetImpl.ts new file mode 100644 index 000000000..6a2f9d4c0 --- /dev/null +++ b/src/util/vs/editor/common/core/text/positionToOffsetImpl.ts @@ -0,0 +1,144 @@ +//!!! 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 { findLastIdxMonotonous } from '../../../../base/common/arraysFind'; +import { StringEdit, StringReplacement } from '../edits/stringEdit'; +import { OffsetRange } from '../ranges/offsetRange'; +import { Position } from '../position'; +import { Range } from '../range'; +import type { TextReplacement, TextEdit } from '../edits/textEdit'; +import type { TextLength } from './textLength'; + +export abstract class PositionOffsetTransformerBase { + abstract getOffset(position: Position): number; + + getOffsetRange(range: Range): OffsetRange { + return new OffsetRange( + this.getOffset(range.getStartPosition()), + this.getOffset(range.getEndPosition()) + ); + } + + abstract getPosition(offset: number): Position; + + getRange(offsetRange: OffsetRange): Range { + return Range.fromPositions( + this.getPosition(offsetRange.start), + this.getPosition(offsetRange.endExclusive) + ); + } + + getStringEdit(edit: TextEdit): StringEdit { + const edits = edit.replacements.map(e => this.getStringReplacement(e)); + return new Deps.deps.StringEdit(edits); + } + + getStringReplacement(edit: TextReplacement): StringReplacement { + return new Deps.deps.StringReplacement(this.getOffsetRange(edit.range), edit.text); + } + + getTextReplacement(edit: StringReplacement): TextReplacement { + return new Deps.deps.TextReplacement(this.getRange(edit.replaceRange), edit.newText); + } + + getTextEdit(edit: StringEdit): TextEdit { + const edits = edit.replacements.map(e => this.getTextReplacement(e)); + return new Deps.deps.TextEdit(edits); + } +} + +interface IDeps { + StringEdit: typeof StringEdit; + StringReplacement: typeof StringReplacement; + TextReplacement: typeof TextReplacement; + TextEdit: typeof TextEdit; + TextLength: typeof TextLength; +} + +class Deps { + static _deps: IDeps | undefined = undefined; + static get deps(): IDeps { + if (!this._deps) { + throw new Error('Dependencies not set. Call _setDependencies first.'); + } + return this._deps; + } +} + +/** This is to break circular module dependencies. */ +export function _setPositionOffsetTransformerDependencies(deps: IDeps): void { + Deps._deps = deps; +} + +export class PositionOffsetTransformer extends PositionOffsetTransformerBase { + private readonly lineStartOffsetByLineIdx: number[]; + private readonly lineEndOffsetByLineIdx: number[]; + + constructor(public readonly text: string) { + super(); + + this.lineStartOffsetByLineIdx = []; + this.lineEndOffsetByLineIdx = []; + + this.lineStartOffsetByLineIdx.push(0); + for (let i = 0; i < text.length; i++) { + if (text.charAt(i) === '\n') { + this.lineStartOffsetByLineIdx.push(i + 1); + if (i > 0 && text.charAt(i - 1) === '\r') { + this.lineEndOffsetByLineIdx.push(i - 1); + } else { + this.lineEndOffsetByLineIdx.push(i); + } + } + } + this.lineEndOffsetByLineIdx.push(text.length); + } + + override getOffset(position: Position): number { + const valPos = this._validatePosition(position); + return this.lineStartOffsetByLineIdx[valPos.lineNumber - 1] + valPos.column - 1; + } + + private _validatePosition(position: Position): Position { + if (position.lineNumber < 1) { + return new Position(1, 1); + } + const lineCount = this.textLength.lineCount + 1; + if (position.lineNumber > lineCount) { + const lineLength = this.getLineLength(lineCount); + return new Position(lineCount, lineLength + 1); + } + if (position.column < 1) { + return new Position(position.lineNumber, 1); + } + const lineLength = this.getLineLength(position.lineNumber); + if (position.column - 1 > lineLength) { + return new Position(position.lineNumber, lineLength + 1); + } + return position; + } + + override getPosition(offset: number): Position { + const idx = findLastIdxMonotonous(this.lineStartOffsetByLineIdx, i => i <= offset); + const lineNumber = idx + 1; + const column = offset - this.lineStartOffsetByLineIdx[idx] + 1; + return new Position(lineNumber, column); + } + + getTextLength(offsetRange: OffsetRange): TextLength { + return Deps.deps.TextLength.ofRange(this.getRange(offsetRange)); + } + + get textLength(): TextLength { + const lineIdx = this.lineStartOffsetByLineIdx.length - 1; + return new Deps.deps.TextLength(lineIdx, this.text.length - this.lineStartOffsetByLineIdx[lineIdx]); + } + + getLineLength(lineNumber: number): number { + return this.lineEndOffsetByLineIdx[lineNumber - 1] - this.lineStartOffsetByLineIdx[lineNumber - 1]; + } +} diff --git a/src/util/vs/editor/common/core/text/textLength.ts b/src/util/vs/editor/common/core/text/textLength.ts index 6caf99bf8..5093b7dde 100644 --- a/src/util/vs/editor/common/core/text/textLength.ts +++ b/src/util/vs/editor/common/core/text/textLength.ts @@ -4,10 +4,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { LineRange } from '../ranges/lineRange'; import { Position } from '../position'; import { Range } from '../range'; -import { LineRange } from '../ranges/lineRange'; -import { OffsetRange } from '../ranges/offsetRange'; /** * Represents a non-negative length of text in terms of line and column count. @@ -56,14 +55,6 @@ export class TextLength { return new TextLength(line, column); } - public static ofSubstr(str: string, range: OffsetRange): TextLength { - return TextLength.ofText(range.substring(str)); - } - - public static sum(fragments: readonly T[], getLength: (f: T) => TextLength): TextLength { - return fragments.reduce((acc, f) => acc.add(getLength(f)), TextLength.zero); - } - constructor( public readonly lineCount: number, public readonly columnCount: number diff --git a/src/util/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts b/src/util/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts index 82f061ede..246893894 100644 --- a/src/util/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts +++ b/src/util/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts @@ -143,7 +143,10 @@ export class DefaultLinesDiffComputer implements ILinesDiffComputer { scanForWhitespaceChanges(originalLines.length - seq1LastStart); - const changes = lineRangeMappingFromRangeMappings(alignments, new ArrayText(originalLines), new ArrayText(modifiedLines)); + const original = new ArrayText(originalLines); + const modified = new ArrayText(modifiedLines); + + const changes = lineRangeMappingFromRangeMappings(alignments, original, modified); let moves: MovedText[] = []; if (options.computeMoves) { diff --git a/src/util/vs/editor/common/diff/rangeMapping.ts b/src/util/vs/editor/common/diff/rangeMapping.ts index 55f1a388f..66e66f106 100644 --- a/src/util/vs/editor/common/diff/rangeMapping.ts +++ b/src/util/vs/editor/common/diff/rangeMapping.ts @@ -196,6 +196,17 @@ function isValidLineNumber(lineNumber: number, lines: string[]): boolean { * Also contains inner range mappings. */ export class DetailedLineRangeMapping extends LineRangeMapping { + public static toTextEdit(mapping: readonly DetailedLineRangeMapping[], modified: AbstractText): TextEdit { + const replacements: TextReplacement[] = []; + for (const m of mapping) { + for (const r of m.innerChanges ?? []) { + const replacement = r.toTextEdit(modified); + replacements.push(replacement); + } + } + return new TextEdit(replacements); + } + public static fromRangeMappings(rangeMappings: RangeMapping[]): DetailedLineRangeMapping { const originalRange = LineRange.join(rangeMappings.map(r => LineRange.fromRangeInclusive(r.originalRange))); const modifiedRange = LineRange.join(rangeMappings.map(r => LineRange.fromRangeInclusive(r.modifiedRange))); diff --git a/src/util/vs/editor/common/languageSelector.ts b/src/util/vs/editor/common/languageSelector.ts deleted file mode 100644 index 46fe53402..000000000 --- a/src/util/vs/editor/common/languageSelector.ts +++ /dev/null @@ -1,146 +0,0 @@ -//!!! 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 { IRelativePattern, match as matchGlobPattern } from '../../base/common/glob'; -import { URI } from '../../base/common/uri'; -import { normalize } from '../../base/common/path'; - -export interface LanguageFilter { - readonly language?: string; - readonly scheme?: string; - readonly pattern?: string | IRelativePattern; - readonly notebookType?: string; - /** - * This provider is implemented in the UI thread. - */ - readonly hasAccessToAllModels?: boolean; - readonly exclusive?: boolean; - - /** - * This provider comes from a builtin extension. - */ - readonly isBuiltin?: boolean; -} - -export type LanguageSelector = string | LanguageFilter | ReadonlyArray; - -export function score(selector: LanguageSelector | undefined, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean, candidateNotebookUri: URI | undefined, candidateNotebookType: string | undefined): number { - - if (Array.isArray(selector)) { - // array -> take max individual value - let ret = 0; - for (const filter of selector) { - const value = score(filter, candidateUri, candidateLanguage, candidateIsSynchronized, candidateNotebookUri, candidateNotebookType); - if (value === 10) { - return value; // already at the highest - } - if (value > ret) { - ret = value; - } - } - return ret; - - } else if (typeof selector === 'string') { - - if (!candidateIsSynchronized) { - return 0; - } - - // short-hand notion, desugars to - // 'fooLang' -> { language: 'fooLang'} - // '*' -> { language: '*' } - if (selector === '*') { - return 5; - } else if (selector === candidateLanguage) { - return 10; - } else { - return 0; - } - - } else if (selector) { - // filter -> select accordingly, use defaults for scheme - const { language, pattern, scheme, hasAccessToAllModels, notebookType } = selector as LanguageFilter; // TODO: microsoft/TypeScript#42768 - - if (!candidateIsSynchronized && !hasAccessToAllModels) { - return 0; - } - - // selector targets a notebook -> use the notebook uri instead - // of the "normal" document uri. - if (notebookType && candidateNotebookUri) { - candidateUri = candidateNotebookUri; - } - - let ret = 0; - - if (scheme) { - if (scheme === candidateUri.scheme) { - ret = 10; - } else if (scheme === '*') { - ret = 5; - } else { - return 0; - } - } - - if (language) { - if (language === candidateLanguage) { - ret = 10; - } else if (language === '*') { - ret = Math.max(ret, 5); - } else { - return 0; - } - } - - if (notebookType) { - if (notebookType === candidateNotebookType) { - ret = 10; - } else if (notebookType === '*' && candidateNotebookType !== undefined) { - ret = Math.max(ret, 5); - } else { - return 0; - } - } - - if (pattern) { - let normalizedPattern: string | IRelativePattern; - if (typeof pattern === 'string') { - normalizedPattern = pattern; - } else { - // Since this pattern has a `base` property, we need - // to normalize this path first before passing it on - // because we will compare it against `Uri.fsPath` - // which uses platform specific separators. - // Refs: https://github.com/microsoft/vscode/issues/99938 - normalizedPattern = { ...pattern, base: normalize(pattern.base) }; - } - - if (normalizedPattern === candidateUri.fsPath || matchGlobPattern(normalizedPattern, candidateUri.fsPath)) { - ret = 10; - } else { - return 0; - } - } - - return ret; - - } else { - return 0; - } -} - - -export function targetsNotebooks(selector: LanguageSelector): boolean { - if (typeof selector === 'string') { - return false; - } else if (Array.isArray(selector)) { - return selector.some(targetsNotebooks); - } else { - return !!(selector).notebookType; - } -} diff --git a/src/util/vs/nls.ts b/src/util/vs/nls.ts index 8379d3120..4701d9b39 100644 --- a/src/util/vs/nls.ts +++ b/src/util/vs/nls.ts @@ -1,7 +1,5 @@ //!!! DO NOT modify, this file was COPIED from 'microsoft/vscode' -declare var document: any; - /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. @@ -12,6 +10,7 @@ import { getNLSLanguage, getNLSMessages } from './nls.messages'; // eslint-disable-next-line local/code-import-patterns export { getNLSLanguage, getNLSMessages } from './nls.messages'; +declare const document: { location?: { hash?: string } } | undefined; const isPseudo = getNLSLanguage() === 'pseudo' || (typeof document !== 'undefined' && document.location && typeof document.location.hash === 'string' && document.location.hash.indexOf('pseudo=true') >= 0); export interface ILocalizeInfo { diff --git a/src/util/vs/platform/instantiation/common/instantiation.ts b/src/util/vs/platform/instantiation/common/instantiation.ts index 99c386ccc..29ad899e8 100644 --- a/src/util/vs/platform/instantiation/common/instantiation.ts +++ b/src/util/vs/platform/instantiation/common/instantiation.ts @@ -33,7 +33,6 @@ export interface IConstructorSignature { export interface ServicesAccessor { get(id: ServiceIdentifier): T; - getIfExists(id: ServiceIdentifier): T | undefined; } export const IInstantiationService = createDecorator('instantiationService'); diff --git a/src/util/vs/platform/instantiation/common/instantiationService.ts b/src/util/vs/platform/instantiation/common/instantiationService.ts index c04e7f04a..4f3617e9d 100644 --- a/src/util/vs/platform/instantiation/common/instantiationService.ts +++ b/src/util/vs/platform/instantiation/common/instantiationService.ts @@ -6,14 +6,14 @@ *--------------------------------------------------------------------------------------------*/ import { GlobalIdleValue } from '../../../base/common/async'; -import { illegalState } from '../../../base/common/errors'; import { Event } from '../../../base/common/event'; +import { illegalState } from '../../../base/common/errors'; import { DisposableStore, dispose, IDisposable, isDisposable, toDisposable } from '../../../base/common/lifecycle'; -import { LinkedList } from '../../../base/common/linkedList'; import { SyncDescriptor, SyncDescriptor0 } from './descriptors'; import { Graph } from './graph'; -import { _util, GetLeadingNonServiceArgs, IInstantiationService, ServiceIdentifier, ServicesAccessor } from './instantiation'; +import { GetLeadingNonServiceArgs, IInstantiationService, ServiceIdentifier, ServicesAccessor, _util } from './instantiation'; import { ServiceCollection } from './serviceCollection'; +import { LinkedList } from '../../../base/common/linkedList'; // TRACING const _enableAllTracing = false @@ -106,13 +106,6 @@ export class InstantiationService implements IInstantiationService { throw new Error(`[invokeFunction] unknown service '${id}'`); } return result; - }, - getIfExists: (id: ServiceIdentifier) => { - if (_done) { - throw illegalState('service accessor is only valid during the invocation of its target method'); - } - const result = this._getOrCreateServiceInstance(id, _trace); - return result; } }; return fn(accessor, ...args); @@ -128,7 +121,7 @@ export class InstantiationService implements IInstantiationService { this._throwIfDisposed(); let _trace: Trace; - let result: any; + let result: unknown; if (ctorOrDescriptor instanceof SyncDescriptor) { _trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor.ctor); result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace); diff --git a/test/simulation/inlineEdit/inlineEditTester.ts b/test/simulation/inlineEdit/inlineEditTester.ts index 27f8620f8..72cdf5fdb 100644 --- a/test/simulation/inlineEdit/inlineEditTester.ts +++ b/test/simulation/inlineEdit/inlineEditTester.ts @@ -143,7 +143,10 @@ export class InlineEditTester { })); } - const stestRuntime = accessor.getIfExists(ISimulationTestRuntime); + let stestRuntime: ISimulationTestRuntime | undefined; + try { + stestRuntime = accessor.get(ISimulationTestRuntime); + } catch { } if (stestRuntime) { const nesUserEditHistory: ISerializedNesUserEditsHistory = { diff --git a/test/simulation/workbench/utils/simulationExec.ts b/test/simulation/workbench/utils/simulationExec.ts index 6d07e6ca9..034cc059a 100644 --- a/test/simulation/workbench/utils/simulationExec.ts +++ b/test/simulation/workbench/utils/simulationExec.ts @@ -92,7 +92,7 @@ function splitToLines(source: AsyncIterable): AsyncIterableObject { return new AsyncIterableObject((emitter) => { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const proc = cp.spawn('node', [SIMULATION_MAIN_PATH, ...args], { stdio: 'pipe' }); const listener = token.onCancellationRequested(() => { proc.kill('SIGTERM'); @@ -175,7 +175,7 @@ class MainProcessEventHandler { const idMap = this.idMap; return new AsyncIterableObject((emitter) => { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const cancellationListener = token.onCancellationRequested(() => { ipcRenderer.send('kill-process', { id }); });