From 9d498c93130472f6d22b70fe45873c6d9dc6f84a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:17:28 +0000 Subject: [PATCH 1/6] Initial plan From 4d3604acdb6cbc2b4944576a6d9447e6bbdac124 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:26:16 +0000 Subject: [PATCH 2/6] Fix diagnostics pull after quick reopen Co-authored-by: dbaeumer <1931590+dbaeumer@users.noreply.github.com> --- client-node-tests/src/integration.test.ts | 68 ++++++++++++++++++++++- client/src/common/diagnostic.ts | 23 ++++++-- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index 9c343701a..e2b7ee8b2 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -247,12 +247,13 @@ suite('Client integration', () => { run: { module: serverModule, transport: lsclient.TransportKind.ipc }, debug: { module: serverModule, transport: lsclient.TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } } }; - const documentSelector: lsclient.DocumentSelector = [{ scheme: 'lsptests', language: 'bat' }]; + const documentSelector: lsclient.DocumentSelector = [{ scheme: 'lsptests', language: 'bat' }, { scheme: 'lsptests', language: 'plaintext' }]; middleware = {}; const clientOptions: lsclient.LanguageClientOptions = { documentSelector, synchronize: {}, initializationOptions: {}, middleware, workspaceFolder: { index: 0, name: 'test_folder', uri: vscode.Uri.parse(`${fsProvider.scheme}:///`) }, + diagnosticPullOptions: { onChange: true } }; client = new lsclient.LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions); @@ -1444,6 +1445,71 @@ suite('Client integration', () => { assert.strictEqual(reporterCalled, true); }); + test('Document diagnostic pull after quick reopen', async () => { + await vscode.window.showTextDocument(document); + let initialPullFinished!: () => void; + const initialPull = new Promise((resolve) => { + initialPullFinished = resolve; + }); + (middleware as DiagnosticProviderMiddleware).provideDiagnostics = async (document, previousResultId, token, next) => { + const result = await next(document, previousResultId, token); + initialPullFinished(); + return result; + }; + document = await vscode.languages.setTextDocumentLanguage(document, 'plaintext'); + await initialPull; + await new Promise((resolve) => setTimeout(resolve, 0)); + + let pullCount = 0; + let releasePull!: () => void; + const holdPull = new Promise((resolve) => { + releasePull = resolve; + }); + let firstPullStarted!: () => void; + const firstPull = new Promise((resolve) => { + firstPullStarted = resolve; + }); + let firstPullFinished!: () => void; + const firstPullDone = new Promise((resolve) => { + firstPullFinished = resolve; + }); + let expectEditPull = false; + let editPullStarted!: () => void; + const editPull = new Promise((resolve) => { + editPullStarted = resolve; + }); + (middleware as DiagnosticProviderMiddleware).provideDiagnostics = async (document, previousResultId, token, next) => { + const currentPull = ++pullCount; + if (currentPull === 1) { + firstPullStarted(); + await holdPull; + } + const result = await next(document, previousResultId, token); + if (currentPull === 1) { + firstPullFinished(); + } + if (expectEditPull) { + editPullStarted(); + } + return result; + }; + + const changeLanguage = vscode.languages.setTextDocumentLanguage(document, 'bat'); + await firstPull; + document = await changeLanguage; + releasePull(); + await firstPullDone; + await new Promise((resolve) => setTimeout(resolve, 0)); + + expectEditPull = true; + const edit = new vscode.WorkspaceEdit(); + edit.insert(document.uri, new vscode.Position(0, 0), ' '); + await vscode.workspace.applyEdit(edit); + await editPull; + (middleware as DiagnosticProviderMiddleware).provideDiagnostics = undefined; + await revertAllDirty(); + }).timeout(5000); + test('Type Hierarchy', async () => { const provider = client.getFeature(lsclient.TypeHierarchyPrepareRequest.method).getProvider(document); isDefined(provider); diff --git a/client/src/common/diagnostic.ts b/client/src/common/diagnostic.ts index ac3168555..8cfb20886 100644 --- a/client/src/common/diagnostic.ts +++ b/client/src/common/diagnostic.ts @@ -307,6 +307,7 @@ class DiagnosticRequestor implements Disposable { public readonly provider: vsdiag.DiagnosticProvider; private readonly diagnostics: DiagnosticCollection; private readonly openRequests: Map; + private readonly pendingDocumentForgets: Map; private readonly documentStates: DocumentPullStateTracker; private workspaceErrorCounter: number; @@ -324,6 +325,7 @@ class DiagnosticRequestor implements Disposable { this.diagnostics = this.createDiagnosticCollection(); this.openRequests = new Map(); + this.pendingDocumentForgets = new Map(); this.documentStates = new DocumentPullStateTracker(); this.workspaceErrorCounter = 0; } @@ -361,6 +363,10 @@ class DiagnosticRequestor implements Disposable { this.documentStates.unTrack(kind, document); } + public cancelPendingForget(document: TextDocument | Uri): void { + this.pendingDocumentForgets.delete(DocumentOrUri.asKey(document)); + } + public pull(document: TextDocument | Uri, cb?: () => void): void { if (this.isDisposed) { return; @@ -454,8 +460,13 @@ class DiagnosticRequestor implements Disposable { if (request !== undefined) { this.openRequests.set(key, { state: RequestStateKind.reschedule, document: document }); } else { + const pendingForget = {}; + this.pendingDocumentForgets.set(key, pendingForget); this.pull(document, () => { - this.forget(PullState.document, document); + if (this.pendingDocumentForgets.get(key) === pendingForget) { + this.pendingDocumentForgets.delete(key); + this.forget(PullState.document, document); + } }); } @@ -916,11 +927,12 @@ class DiagnosticFeatureProviderImpl implements DiagnosticProviderShape { const openFeature = client.getFeature(DidOpenTextDocumentNotification.method); disposables.push(openFeature.onNotificationSent((event) => { const textDocument = event.textDocument; - // We already know about this document. This can happen via a tab open. - if (this.diagnosticRequestor.knowsSameVersion(PullState.document, textDocument)) { - return; - } if (matches(textDocument)) { + this.diagnosticRequestor.cancelPendingForget(textDocument); + // We already know about this document. This can happen via a tab open. + if (this.diagnosticRequestor.knowsSameVersion(PullState.document, textDocument)) { + return; + } this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); }); } })); @@ -929,6 +941,7 @@ class DiagnosticFeatureProviderImpl implements DiagnosticProviderShape { // Send a pull for all opened cells in the notebook. for (const cell of event.getCells()) { if (matchesCell(cell)) { + this.diagnosticRequestor.cancelPendingForget(cell.document); this.diagnosticRequestor.pull(cell.document, () => { addToBackgroundIfNeeded(cell.document); }); } } From 46a6da81935dc637c4fa33c4855bfc69e5fd67fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:27:22 +0000 Subject: [PATCH 3/6] Tighten diagnostics race test Co-authored-by: dbaeumer <1931590+dbaeumer@users.noreply.github.com> --- client-node-tests/src/integration.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index e2b7ee8b2..612d40384 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -89,6 +89,10 @@ function isFullDocumentDiagnosticReport(value: vsdiag.DocumentDiagnosticReport): assert.ok(value.kind === vsdiag.DocumentDiagnosticReportKind.full); } +function waitForNextTurn(): Promise { + return new Promise((resolve) => setTimeout(resolve, 0)); +} + function processExists(pid: number): boolean { try { process.kill(pid, 0); @@ -1458,7 +1462,7 @@ suite('Client integration', () => { }; document = await vscode.languages.setTextDocumentLanguage(document, 'plaintext'); await initialPull; - await new Promise((resolve) => setTimeout(resolve, 0)); + await waitForNextTurn(); let pullCount = 0; let releasePull!: () => void; @@ -1489,6 +1493,7 @@ suite('Client integration', () => { firstPullFinished(); } if (expectEditPull) { + expectEditPull = false; editPullStarted(); } return result; @@ -1499,7 +1504,7 @@ suite('Client integration', () => { document = await changeLanguage; releasePull(); await firstPullDone; - await new Promise((resolve) => setTimeout(resolve, 0)); + await waitForNextTurn(); expectEditPull = true; const edit = new vscode.WorkspaceEdit(); From 5d4444546222754f204aebd044fada8dd316377f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:28:39 +0000 Subject: [PATCH 4/6] Make diagnostics test signals one-shot Co-authored-by: dbaeumer <1931590+dbaeumer@users.noreply.github.com> --- client-node-tests/src/integration.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index 612d40384..435a0be1a 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -1451,13 +1451,14 @@ suite('Client integration', () => { test('Document diagnostic pull after quick reopen', async () => { await vscode.window.showTextDocument(document); - let initialPullFinished!: () => void; + let initialPullFinished: (() => void) | undefined; const initialPull = new Promise((resolve) => { initialPullFinished = resolve; }); (middleware as DiagnosticProviderMiddleware).provideDiagnostics = async (document, previousResultId, token, next) => { const result = await next(document, previousResultId, token); - initialPullFinished(); + initialPullFinished?.(); + initialPullFinished = undefined; return result; }; document = await vscode.languages.setTextDocumentLanguage(document, 'plaintext'); From 0ac4f7cc6a04584ef69de5460be9d0f640ef850d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:29:51 +0000 Subject: [PATCH 5/6] Clarify diagnostics race synchronization Co-authored-by: dbaeumer <1931590+dbaeumer@users.noreply.github.com> --- client-node-tests/src/integration.test.ts | 8 ++++---- client/src/common/diagnostic.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index 435a0be1a..f7dad7175 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -1478,7 +1478,7 @@ suite('Client integration', () => { const firstPullDone = new Promise((resolve) => { firstPullFinished = resolve; }); - let expectEditPull = false; + let expectedEditPull: number | undefined; let editPullStarted!: () => void; const editPull = new Promise((resolve) => { editPullStarted = resolve; @@ -1493,8 +1493,8 @@ suite('Client integration', () => { if (currentPull === 1) { firstPullFinished(); } - if (expectEditPull) { - expectEditPull = false; + if (currentPull === expectedEditPull) { + expectedEditPull = undefined; editPullStarted(); } return result; @@ -1507,7 +1507,7 @@ suite('Client integration', () => { await firstPullDone; await waitForNextTurn(); - expectEditPull = true; + expectedEditPull = pullCount + 1; const edit = new vscode.WorkspaceEdit(); edit.insert(document.uri, new vscode.Position(0, 0), ' '); await vscode.workspace.applyEdit(edit); diff --git a/client/src/common/diagnostic.ts b/client/src/common/diagnostic.ts index 8cfb20886..b70e55921 100644 --- a/client/src/common/diagnostic.ts +++ b/client/src/common/diagnostic.ts @@ -307,7 +307,7 @@ class DiagnosticRequestor implements Disposable { public readonly provider: vsdiag.DiagnosticProvider; private readonly diagnostics: DiagnosticCollection; private readonly openRequests: Map; - private readonly pendingDocumentForgets: Map; + private readonly pendingDocumentForgets: Map; private readonly documentStates: DocumentPullStateTracker; private workspaceErrorCounter: number; @@ -460,7 +460,7 @@ class DiagnosticRequestor implements Disposable { if (request !== undefined) { this.openRequests.set(key, { state: RequestStateKind.reschedule, document: document }); } else { - const pendingForget = {}; + const pendingForget = Symbol(); this.pendingDocumentForgets.set(key, pendingForget); this.pull(document, () => { if (this.pendingDocumentForgets.get(key) === pendingForget) { From 8eb8ceaa5cfdf5873f19ab05b28ef407e02f6b4b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:30:42 +0000 Subject: [PATCH 6/6] Align diagnostics test signal naming Co-authored-by: dbaeumer <1931590+dbaeumer@users.noreply.github.com> --- client-node-tests/src/integration.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index f7dad7175..1a84ca151 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -1479,9 +1479,9 @@ suite('Client integration', () => { firstPullFinished = resolve; }); let expectedEditPull: number | undefined; - let editPullStarted!: () => void; + let editPullFinished!: () => void; const editPull = new Promise((resolve) => { - editPullStarted = resolve; + editPullFinished = resolve; }); (middleware as DiagnosticProviderMiddleware).provideDiagnostics = async (document, previousResultId, token, next) => { const currentPull = ++pullCount; @@ -1495,7 +1495,7 @@ suite('Client integration', () => { } if (currentPull === expectedEditPull) { expectedEditPull = undefined; - editPullStarted(); + editPullFinished(); } return result; };