From 84be4966071c0db999c292651c3b976364b65045 Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sun, 26 Jul 2026 10:57:10 +0530 Subject: [PATCH 1/2] feat(lsp): add pull diagnostics and native TypeScript 7 - support pull diagnostics and server requests - replace legacy TypeScript server with native TypeScript 7 --- src/cm/lsp/clientManager.ts | 49 +++++- src/cm/lsp/diagnostics.ts | 276 ++++++++++++++++++++++++++++--- src/cm/lsp/providerUtils.ts | 6 + src/cm/lsp/serverLauncher.ts | 33 +++- src/cm/lsp/serverRegistry.ts | 5 + src/cm/lsp/servers/javascript.ts | 52 ++---- src/cm/lsp/transport.ts | 120 ++++++++++---- src/cm/lsp/types.ts | 26 +++ src/cm/lsp/workspace.ts | 7 + 9 files changed, 469 insertions(+), 105 deletions(-) diff --git a/src/cm/lsp/clientManager.ts b/src/cm/lsp/clientManager.ts index 4dd47daa0..e6b2eedc9 100644 --- a/src/cm/lsp/clientManager.ts +++ b/src/cm/lsp/clientManager.ts @@ -12,7 +12,11 @@ import lspStatusBar from "components/lspStatusBar"; import notificationManager from "lib/notificationManager"; import Uri from "utils/Uri"; import Url from "utils/Url"; -import { clearDiagnosticsEffect } from "./diagnostics"; +import { + clearDiagnosticsEffect, + disposePullDiagnostics, + lspDiagnosticsAutoSyncExtension, +} from "./diagnostics"; import { supportsBuiltinFormatting } from "./formattingSupport"; import { documentColorsExtension } from "./documentColors"; import { inlayHintsExtension } from "./inlayHints"; @@ -74,6 +78,22 @@ function safeString(value: unknown): string { return value != null ? String(value) : ""; } +function formatInitializationError(error: unknown): string { + if (isPlainObject(error)) { + const message = safeString(error.message).trim(); + const code = + typeof error.code === "number" || typeof error.code === "string" + ? ` (${error.code})` + : ""; + if (message) return `Initialization failed${code}: ${message}`; + } + const message = + error instanceof Error ? error.message : safeString(error).trim(); + return message + ? `Initialization failed: ${message}` + : "Initialization failed"; +} + function isSettingsOrKeybindingsFile( server: LspServerDefinition, uri: string | null | undefined, @@ -359,6 +379,14 @@ export class LspClientManager { originalUri && originalUri !== normalizedUri ? [originalUri] : []; clientState.attach(normalizedUri, view as EditorView, aliases); lspExtensions.push(plugin); + if (diagnosticsUiExtension) { + lspExtensions.push( + lspDiagnosticsAutoSyncExtension( + clientState.client, + normalizedUri, + ), + ); + } } catch (error) { console.error( `Failed to initialize LSP client for ${server.id}`, @@ -632,11 +660,14 @@ export class LspClientManager { ? defaultExtensions.filter((ext) => ext !== diagnosticsExtension) : defaultExtensions; - const progressCapabilities: LSPClientExtension = { + const clientCapabilities: LSPClientExtension = { clientCapabilities: { window: { workDoneProgress: true, }, + workspace: { + configuration: true, + }, }, }; @@ -644,7 +675,7 @@ export class LspClientManager { ...filteredBuiltins, ...extraExtensions, ...serverExtensions, - progressCapabilities, + clientCapabilities, ]; clientConfig.extensions = mergedExtensions; @@ -906,6 +937,17 @@ export class LspClientManager { client.__acodeLoggedInfo = true; } } catch (error) { + addLspLog( + server.id, + "error", + formatInitializationError(error), + error, + ); + try { + client?.disconnect(); + } catch { + /* Client may not have finished connecting */ + } if (transportHandle) { await transportHandle.dispose?.(); } else { @@ -995,6 +1037,7 @@ export class LspClientManager { }; const dispose = async (): Promise => { + disposePullDiagnostics(client); try { client.disconnect(); } catch (error) { diff --git a/src/cm/lsp/diagnostics.ts b/src/cm/lsp/diagnostics.ts index ae671b374..616b2dfef 100644 --- a/src/cm/lsp/diagnostics.ts +++ b/src/cm/lsp/diagnostics.ts @@ -8,8 +8,11 @@ import { StateEffect, StateField, } from "@codemirror/state"; -import { type EditorView, ViewPlugin } from "@codemirror/view"; +import { type EditorView, ViewPlugin, type ViewUpdate } from "@codemirror/view"; +import { addLspLogFor } from "./logs"; import type { + DocumentDiagnosticParams, + DocumentDiagnosticReport, LSPClientWithWorkspace, LSPPluginAPI, LspDiagnostic, @@ -22,6 +25,34 @@ let diagnosticsEventTimer: ReturnType | null = null; let diagnosticsViewCount = 0; export const LSP_DIAGNOSTICS_EVENT = "acode:lsp-diagnostics-updated"; +const PULL_DIAGNOSTICS_DELAY = 250; + +interface PullDiagnosticsState { + timers: Map>; + generations: Map; + resultIds: Map; + failures: Map; +} + +const pullDiagnosticsStates = new WeakMap(); + +function getPullDiagnosticsState(client: LSPClient): PullDiagnosticsState { + let state = pullDiagnosticsStates.get(client); + if (!state) { + state = { + timers: new Map(), + generations: new Map(), + resultIds: new Map(), + failures: new Map(), + }; + pullDiagnosticsStates.set(client, state); + } + return state; +} + +function supportsPullDiagnostics(client: LSPClient): boolean { + return !!client.serverCapabilities?.diagnosticProvider; +} function isCoarsePointerDevice(): boolean { if (typeof window !== "undefined") { @@ -170,6 +201,200 @@ function sameDiagnostics( return true; } +function applyDiagnostics( + client: LSPClient, + uri: string, + version: number | undefined, + rawDiagnostics: RawDiagnostic[], +): boolean { + const clientWithWorkspace = client as unknown as LSPClientWithWorkspace; + const file = clientWithWorkspace.workspace.getFile(uri); + if (!file || (version != null && version !== file.version)) { + return false; + } + const view = file.getView(); + if (!view) return false; + const plugin = LSPPlugin.get(view) as LSPPluginAPI | null; + if (!plugin) return false; + + const diagnostics = collectLspDiagnostics(plugin, rawDiagnostics); + const current = view.state.field(lspPublishedDiagnostics, false) ?? []; + if (sameDiagnostics(current, diagnostics)) { + return true; + } + + view.dispatch({ + effects: storeLspDiagnostics(diagnostics), + }); + scheduleDiagnosticsUpdated(); + return true; +} + +async function pullDiagnostics( + client: LSPClient, + uri: string, + generation: number, +): Promise { + if (!supportsPullDiagnostics(client)) return; + + client.sync(); + const clientWithWorkspace = client as unknown as LSPClientWithWorkspace; + const file = clientWithWorkspace.workspace.getFile(uri); + if (!file) return; + + const state = getPullDiagnosticsState(client); + const version = file.version; + const provider = client.serverCapabilities?.diagnosticProvider; + const params: DocumentDiagnosticParams = { + textDocument: { uri }, + }; + if ( + provider && + typeof provider === "object" && + "identifier" in provider && + typeof provider.identifier === "string" + ) { + params.identifier = provider.identifier; + } + const previousResultId = state.resultIds.get(uri); + if (previousResultId !== undefined) { + params.previousResultId = previousResultId; + } + + try { + const report = await client.request< + DocumentDiagnosticParams, + DocumentDiagnosticReport + >("textDocument/diagnostic", params); + if (state.generations.get(uri) !== generation) return; + state.failures.delete(uri); + + const currentFile = clientWithWorkspace.workspace.getFile(uri); + if (!currentFile || currentFile.version !== version) { + schedulePullDiagnostics(client, uri, 0); + return; + } + + if (report.kind === "unchanged") { + state.resultIds.set(uri, report.resultId); + return; + } + + if (typeof report.resultId === "string") { + state.resultIds.set(uri, report.resultId); + } else { + state.resultIds.delete(uri); + } + applyDiagnostics(client, uri, version, report.items); + } catch (error) { + if (state.generations.get(uri) === generation) { + const message = + error instanceof Error ? error.message : String(error); + const failures = (state.failures.get(uri) ?? 0) + 1; + state.failures.set(uri, failures); + if (/timed out/i.test(message) && failures <= 2) { + schedulePullDiagnostics(client, uri, failures * 750); + return; + } + addLspLogFor( + client, + "warn", + `Diagnostic pull failed for ${uri}: ${message}`, + error, + ); + console.warn(`[LSP:Diagnostics] Pull failed for ${uri}`, error); + } + } +} + +export function schedulePullDiagnostics( + client: LSPClient, + uri: string, + delay = PULL_DIAGNOSTICS_DELAY, +): void { + if (!supportsPullDiagnostics(client)) { + if (client.connected && !client.serverCapabilities) { + void client.initializing + .then(() => { + schedulePullDiagnostics(client, uri, delay); + }) + .catch(() => {}); + } + return; + } + + const state = getPullDiagnosticsState(client); + const existing = state.timers.get(uri); + if (existing != null) clearTimeout(existing); + + const generation = (state.generations.get(uri) ?? 0) + 1; + state.generations.set(uri, generation); + state.timers.set( + uri, + setTimeout(() => { + state.timers.delete(uri); + void pullDiagnostics(client, uri, generation); + }, Math.max(0, delay)), + ); +} + +export function schedulePullDiagnosticsForOpenFiles( + client: LSPClient, + delay = 0, +): void { + if (!supportsPullDiagnostics(client)) return; + for (const file of client.workspace.files) { + schedulePullDiagnostics(client, file.uri, delay); + } +} + +export function forgetPullDiagnostics(client: LSPClient, uri: string): void { + const state = pullDiagnosticsStates.get(client); + if (!state) return; + const timer = state.timers.get(uri); + if (timer != null) clearTimeout(timer); + state.timers.delete(uri); + state.generations.delete(uri); + state.resultIds.delete(uri); + state.failures.delete(uri); +} + +export function disposePullDiagnostics(client: LSPClient): void { + const state = pullDiagnosticsStates.get(client); + if (!state) return; + for (const timer of state.timers.values()) { + clearTimeout(timer); + } + state.generations.clear(); + state.failures.clear(); + pullDiagnosticsStates.delete(client); +} + +export function lspDiagnosticsAutoSyncExtension( + client: LSPClient, + uri: string, +): Extension { + return ViewPlugin.fromClass( + class { + pending: ReturnType | null = null; + + update(update: ViewUpdate): void { + if (!update.docChanged) return; + if (this.pending != null) clearTimeout(this.pending); + this.pending = setTimeout(() => { + this.pending = null; + client.sync(); + schedulePullDiagnostics(client, uri, 0); + }, 500); + } + + destroy(): void { + if (this.pending != null) clearTimeout(this.pending); + } + }, + ); +} + function scheduleDiagnosticsUpdated(): void { if (diagnosticsEventTimer != null) return; diagnosticsEventTimer = setTimeout(() => { @@ -230,7 +455,7 @@ export function lspDiagnosticsClientExtension(): { clientCapabilities: Record; notificationHandlers: Record< string, - (client: LSPClient, params: PublishDiagnosticsParams) => boolean + (client: LSPClient, params: unknown) => boolean >; } { return { @@ -242,36 +467,33 @@ export function lspDiagnosticsClientExtension(): { dataSupport: true, versionSupport: true, }, + diagnostic: { + dynamicRegistration: false, + relatedDocumentSupport: false, + }, + }, + workspace: { + diagnostics: { + refreshSupport: true, + }, }, }, notificationHandlers: { "textDocument/publishDiagnostics": ( client: LSPClient, - params: PublishDiagnosticsParams, + rawParams: unknown, ): boolean => { - const clientWithWorkspace = client as unknown as LSPClientWithWorkspace; - const file = clientWithWorkspace.workspace.getFile(params.uri); - if ( - !file || - (params.version != null && params.version !== file.version) - ) { - return true; - } - const view = file.getView(); - if (!view) return true; - const plugin = LSPPlugin.get(view) as LSPPluginAPI | null; - if (!plugin) return true; - - const diagnostics = collectLspDiagnostics(plugin, params.diagnostics); - const current = view.state.field(lspPublishedDiagnostics, false) ?? []; - if (sameDiagnostics(current, diagnostics)) { - return true; - } - - view.dispatch({ - effects: storeLspDiagnostics(diagnostics), - }); - scheduleDiagnosticsUpdated(); + const params = rawParams as PublishDiagnosticsParams; + applyDiagnostics( + client, + params.uri, + params.version, + params.diagnostics, + ); + return true; + }, + "workspace/diagnostic/refresh": (client: LSPClient): boolean => { + schedulePullDiagnosticsForOpenFiles(client); return true; }, }, @@ -316,7 +538,7 @@ interface DiagnosticsExtension { clientCapabilities: Record; notificationHandlers: Record< string, - (client: LSPClient, params: PublishDiagnosticsParams) => boolean + (client: LSPClient, params: unknown) => boolean >; editorExtension: Extension[]; } diff --git a/src/cm/lsp/providerUtils.ts b/src/cm/lsp/providerUtils.ts index 93e8a3094..d669f17ea 100644 --- a/src/cm/lsp/providerUtils.ts +++ b/src/cm/lsp/providerUtils.ts @@ -22,8 +22,10 @@ export interface ManagedServerOptions { versionCommand?: string; updateCommand?: string; uninstallCommand?: string; + logOutput?: "all" | "warnings-and-errors"; startupTimeout?: number; initializationOptions?: Record; + workspaceConfiguration?: Record; clientConfig?: LspServerManifest["clientConfig"]; resolveLanguageId?: LspServerManifest["resolveLanguageId"]; rootUri?: LspServerManifest["rootUri"]; @@ -79,8 +81,10 @@ export function defineServer(options: ManagedServerOptions): LspServerManifest { versionCommand, updateCommand, uninstallCommand, + logOutput, startupTimeout, initializationOptions, + workspaceConfiguration, clientConfig, resolveLanguageId, rootUri, @@ -104,6 +108,7 @@ export function defineServer(options: ManagedServerOptions): LspServerManifest { versionCommand, updateCommand, uninstallCommand, + logOutput, install: installer, bridge: bridgeCommand ? { @@ -117,6 +122,7 @@ export function defineServer(options: ManagedServerOptions): LspServerManifest { }, startupTimeout, initializationOptions, + workspaceConfiguration, clientConfig, resolveLanguageId, rootUri, diff --git a/src/cm/lsp/serverLauncher.ts b/src/cm/lsp/serverLauncher.ts index c7db21e10..ea39576d5 100644 --- a/src/cm/lsp/serverLauncher.ts +++ b/src/cm/lsp/serverLauncher.ts @@ -168,7 +168,7 @@ async function readPortFromFile(filePath: string): Promise { /** * Get the port for a running LSP server from the axs port file. - * @param serverName - The LSP server binary name (e.g., "typescript-language-server") + * @param serverName - The LSP server binary name (e.g., "pylsp") * @param session - Session ID for port file naming */ export async function getLspPort( @@ -907,20 +907,35 @@ async function performInstallCheck( async function startInteractiveServer( command: string, serverId: string, + logOutput: LauncherConfig["logOutput"] = "all", ): Promise { const executor = getExecutor(); const callback: ExecutorCallback = (type, data) => { + if (type === "stderr" && /proot warning/i.test(data)) return; + if (type === "stdout" && /listening on/i.test(data)) { + signalServerReady(serverId); + } + + if (logOutput === "warnings-and-errors") { + const level = /\b(error|failed|failure|fatal|panic)\b/i.test(data) + ? "error" + : /\b(warn(?:ing)?|unknown method|disabled|not supported|lacks)\b/i.test( + data, + ) + ? "warn" + : null; + if (!level) return; + addLspLog(serverId, level, data); + console[level](`[LSP:${serverId}] ${data}`); + return; + } + if (type === "stderr") { - if (/proot warning/i.test(data)) return; addLspLog(serverId, "stderr", data); console.warn(`[LSP:${serverId}] ${data}`); } else if (type === "stdout" && data && data.trim()) { addLspLog(serverId, "info", data); console.info(`[LSP:${serverId}] ${data}`); - // Detect when the axs proxy signals it's listening - if (/listening on/i.test(data)) { - signalServerReady(serverId); - } } }; const uuid = await executor.start(command, callback, true); @@ -1104,7 +1119,11 @@ export async function ensureServerRunning( } try { - const uuid = await startInteractiveServer(command, key); + const uuid = await startInteractiveServer( + command, + key, + launcher.logOutput, + ); // For auto-port discovery, wait for server ready signal then read port let discoveredPort: number | undefined; diff --git a/src/cm/lsp/serverRegistry.ts b/src/cm/lsp/serverRegistry.ts index 8175c2f46..155ab3152 100644 --- a/src/cm/lsp/serverRegistry.ts +++ b/src/cm/lsp/serverRegistry.ts @@ -241,6 +241,10 @@ function sanitizeDefinition( versionCommand: rawLauncher.versionCommand, updateCommand: rawLauncher.updateCommand, uninstallCommand: rawLauncher.uninstallCommand, + logOutput: + rawLauncher.logOutput === "warnings-and-errors" + ? "warnings-and-errors" + : "all", install: rawLauncher.install && typeof rawLauncher.install === "object" ? { @@ -298,6 +302,7 @@ function sanitizeDefinition( languages: sanitizeLanguages(definition.languages), transport: sanitizedTransport, initializationOptions: clone(definition.initializationOptions), + workspaceConfiguration: clone(definition.workspaceConfiguration), clientConfig: clone(definition.clientConfig), startupTimeout: typeof definition.startupTimeout === "number" diff --git a/src/cm/lsp/servers/javascript.ts b/src/cm/lsp/servers/javascript.ts index 066359275..2725ab0dc 100644 --- a/src/cm/lsp/servers/javascript.ts +++ b/src/cm/lsp/servers/javascript.ts @@ -4,9 +4,9 @@ import { resolveJsTsLanguageId } from "./shared"; export const javascriptServers: LspServerManifest[] = [ defineServer({ - id: "typescript", - label: "TypeScript / JavaScript", - useWorkspaceFolders: true, + id: "typescript-native", + label: "TypeScript 7 / JavaScript (Native STDIO)", + useWorkspaceFolders: false, languages: [ "javascript", "javascriptreact", @@ -18,49 +18,27 @@ export const javascriptServers: LspServerManifest[] = [ transport: { kind: "websocket", }, - command: "typescript-language-server", - args: ["--stdio"], - checkCommand: "which typescript-language-server", + command: "tsc", + args: ["--lsp", "--stdio"], + checkCommand: "which tsc && tsc --version | grep -q '^Version 7\\.'", + versionCommand: "tsc --version", installer: installers.npm({ - executable: "typescript-language-server", - packages: ["typescript-language-server", "typescript"], + executable: "tsc", + packages: ["@typescript/native@npm:typescript@^7.0.2"], }), + logOutput: "warnings-and-errors", enabled: true, initializationOptions: { provideFormatter: true, hostInfo: "acode", - tsserver: { - maxTsServerMemory: 4096, - useSeparateSyntaxServer: true, - }, - preferences: { - includeInlayParameterNameHints: "all", - includeInlayParameterNameHintsWhenArgumentMatchesName: true, - includeInlayFunctionParameterTypeHints: true, - includeInlayVariableTypeHints: true, - includeInlayVariableTypeHintsWhenTypeMatchesName: false, - includeInlayPropertyDeclarationTypeHints: true, - includeInlayFunctionLikeReturnTypeHints: true, - includeInlayEnumMemberValueHints: true, - importModuleSpecifierPreference: "shortest", - importModuleSpecifierEnding: "auto", - includePackageJsonAutoImports: "auto", - provideRefactorNotApplicableReason: true, - allowIncompleteCompletions: true, - allowRenameOfImportPath: true, - generateReturnInDocTemplate: true, - organizeImportsIgnoreCase: "auto", - organizeImportsCollation: "ordinal", - organizeImportsCollationConfig: "default", - autoImportFileExcludePatterns: [], - preferTypeOnlyAutoImports: false, - }, + }, + workspaceConfiguration: { completions: { completeFunctionCalls: true, }, - diagnostics: { - reportStyleChecksAsWarnings: true, - }, + }, + clientConfig: { + timeout: 15000, }, resolveLanguageId: ({ languageId, languageName }) => resolveJsTsLanguageId(languageId, languageName), diff --git a/src/cm/lsp/transport.ts b/src/cm/lsp/transport.ts index d760893ac..1095687e6 100644 --- a/src/cm/lsp/transport.ts +++ b/src/cm/lsp/transport.ts @@ -73,6 +73,45 @@ function createWebSocketTransport( const encoder = binaryMode ? new TextEncoder() : null; + function resolveWorkspaceConfiguration(section: unknown): unknown { + const configuration = server.workspaceConfiguration; + if (!configuration || typeof section !== "string" || !section.trim()) { + return configuration ?? null; + } + + let value: unknown = configuration; + for (const key of section.split(".")) { + if ( + !value || + typeof value !== "object" || + !Object.prototype.hasOwnProperty.call(value, key) + ) { + return null; + } + value = (value as Record)[key]; + } + return value; + } + + function sendMessage(message: string): void { + if (!socket || socket.readyState !== WebSocket.OPEN) return; + if (binaryMode && encoder) { + socket.send(encoder.encode(message)); + } else { + socket.send(message); + } + } + + function notifyListeners(data: string): void { + listeners.forEach((listener) => { + try { + listener(data); + } catch (error) { + console.error("LSP transport listener failed", error); + } + }); + } + function createSocket(): WebSocket { try { // pylsp's websocket endpoint does not require subprotocol negotiation. @@ -124,50 +163,69 @@ function createWebSocketTransport( console.debug(`[LSP:${server.id}] <=`, data); } - // Temporary fix - // Intercept server requests that the CodeMirror LSP client doesn't handle - // The client only handles notifications, but some servers (e.g., TypeScript) - // send requests like window/workDoneProgress/create that need a response try { const msg = JSON.parse(data); - if ( - msg && - typeof msg.id !== "undefined" && - msg.method === "window/workDoneProgress/create" - ) { - // This is a request, respond with success + if (msg && typeof msg.id !== "undefined") { + let handled = true; + let result: unknown = null; + switch (msg.method) { + case "window/workDoneProgress/create": + case "workspace/diagnostic/refresh": + case "client/registerCapability": + case "client/unregisterCapability": + break; + case "workspace/configuration": + result = Array.isArray(msg.params?.items) + ? msg.params.items.map( + (item: { section?: unknown }) => + resolveWorkspaceConfiguration(item?.section), + ) + : []; + break; + case "workspace/workspaceFolders": { + const rootUri = context.rootUri; + result = rootUri + ? [ + { + uri: rootUri, + name: + rootUri.replace(/\/$/, "").split("/").pop() || + rootUri, + }, + ] + : null; + break; + } + default: + handled = false; + } + if (!handled) { + notifyListeners(data); + return; + } const response = JSON.stringify({ jsonrpc: "2.0", id: msg.id, - result: null, + result, }); if (context?.debugWebSocket) { console.debug(`[LSP:${server.id}] => (auto-response)`, response); } - if (socket && socket.readyState === WebSocket.OPEN) { - if (binaryMode && encoder) { - socket.send(encoder.encode(response)); - } else { - socket.send(response); - } + sendMessage(response); + if (msg.method === "workspace/diagnostic/refresh") { + notifyListeners( + JSON.stringify({ + jsonrpc: "2.0", + method: msg.method, + params: msg.params ?? {}, + }), + ); } - // Don't pass this request to listeners since we handled it - console.info( - `[LSP:${server.id}] Auto-responded to window/workDoneProgress/create`, - ); return; } - } catch (_) { - // Not valid JSON or missing fields, pass through normally - } + } catch (_) {} - listeners.forEach((listener) => { - try { - listener(data); - } catch (error) { - console.error("LSP transport listener failed", error); - } - }); + notifyListeners(data); } function handleClose(event: CloseEvent): void { diff --git a/src/cm/lsp/types.ts b/src/cm/lsp/types.ts index 1448056c7..5c53b9690 100644 --- a/src/cm/lsp/types.ts +++ b/src/cm/lsp/types.ts @@ -232,6 +232,7 @@ export interface LauncherConfig { command?: string; args?: string[]; startCommand?: string | string[]; + logOutput?: "all" | "warnings-and-errors"; checkCommand?: string; versionCommand?: string; updateCommand?: string; @@ -284,6 +285,7 @@ export interface LspServerManifest { languages?: string[]; transport?: TransportDescriptor; initializationOptions?: Record; + workspaceConfiguration?: Record; clientConfig?: Record | AcodeClientConfig; startupTimeout?: number; capabilityOverrides?: Record; @@ -339,6 +341,7 @@ export interface LspServerDefinition { languages: string[]; transport: TransportDescriptor; initializationOptions?: Record; + workspaceConfiguration?: Record; clientConfig?: AcodeClientConfig; startupTimeout?: number; capabilityOverrides?: Record; @@ -545,6 +548,29 @@ export interface PublishDiagnosticsParams { diagnostics: RawDiagnostic[]; } +export interface DocumentDiagnosticParams { + textDocument: { + uri: string; + }; + identifier?: string; + previousResultId?: string; +} + +export interface FullDocumentDiagnosticReport { + kind: "full"; + resultId?: string; + items: RawDiagnostic[]; +} + +export interface UnchangedDocumentDiagnosticReport { + kind: "unchanged"; + resultId: string; +} + +export type DocumentDiagnosticReport = + | FullDocumentDiagnosticReport + | UnchangedDocumentDiagnosticReport; + export interface RawDiagnostic { range: Range; severity?: number; diff --git a/src/cm/lsp/workspace.ts b/src/cm/lsp/workspace.ts index 0ac350ff8..c67ba8e26 100644 --- a/src/cm/lsp/workspace.ts +++ b/src/cm/lsp/workspace.ts @@ -3,6 +3,10 @@ import { LSPPlugin, Workspace } from "@codemirror/lsp-client"; import type { Text, TransactionSpec } from "@codemirror/state"; import type { EditorView } from "@codemirror/view"; import { getModeForPath } from "cm/modelist"; +import { + forgetPullDiagnostics, + schedulePullDiagnostics, +} from "./diagnostics"; import { addLspLogFor, type LspLogLevel } from "./logs"; import type { WorkspaceFileUpdate, WorkspaceOptions } from "./types"; @@ -83,6 +87,7 @@ export default class AcodeWorkspace extends Workspace { this.#fileMap.set(uri, file); this.files.push(file); this.client.didOpen(file); + schedulePullDiagnostics(this.client, uri, 0); } file.views.add(view); return file; @@ -161,6 +166,7 @@ export default class AcodeWorkspace extends Workspace { if (!file.views.size) { this.client.didClose(uri); + forgetPullDiagnostics(this.client, uri); this.#removeFileEntry(file); } } @@ -176,6 +182,7 @@ export default class AcodeWorkspace extends Workspace { connected(): void { for (const file of this.files) { this.client.didOpen(file); + schedulePullDiagnostics(this.client, file.uri, 0); } } From 1c2a9b36ba266b4dedc018ecfdd02d25edbcb9fa Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sun, 26 Jul 2026 11:04:39 +0530 Subject: [PATCH 2/2] fix initial diagnostic pull --- src/cm/lsp/diagnostics.ts | 4 ++++ src/cm/lsp/workspace.ts | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cm/lsp/diagnostics.ts b/src/cm/lsp/diagnostics.ts index 616b2dfef..c1a37966e 100644 --- a/src/cm/lsp/diagnostics.ts +++ b/src/cm/lsp/diagnostics.ts @@ -378,6 +378,10 @@ export function lspDiagnosticsAutoSyncExtension( class { pending: ReturnType | null = null; + constructor() { + schedulePullDiagnostics(client, uri, 0); + } + update(update: ViewUpdate): void { if (!update.docChanged) return; if (this.pending != null) clearTimeout(this.pending); diff --git a/src/cm/lsp/workspace.ts b/src/cm/lsp/workspace.ts index c67ba8e26..23fd9588b 100644 --- a/src/cm/lsp/workspace.ts +++ b/src/cm/lsp/workspace.ts @@ -87,7 +87,6 @@ export default class AcodeWorkspace extends Workspace { this.#fileMap.set(uri, file); this.files.push(file); this.client.didOpen(file); - schedulePullDiagnostics(this.client, uri, 0); } file.views.add(view); return file;