fix(lsp): support server workspace configuration

This commit is contained in:
2026-05-05 23:44:21 -04:00
parent 81ab984a86
commit 99ce79ac88
3 changed files with 85 additions and 3 deletions

View File

@@ -3,8 +3,42 @@
// //
// Add new servers here. `match` is a list of file extensions (no dot) OR // Add new servers here. `match` is a list of file extensions (no dot) OR
// language ids; either matches. // language ids; either matches.
import * as fs from "node:fs";
import * as path from "node:path";
import type { ServerConfig } from "./src/types.ts"; import type { ServerConfig } from "./src/types.ts";
// Resolve Python Path - Prefer the project's virtualenv when present so
// Pyright sees the same interpreter/dependencies as project commands.
function resolvePythonPath(rootDir: string): string | undefined {
const candidates = [
path.join(rootDir, ".venv", "bin", "python"),
path.join(rootDir, "venv", "bin", "python"),
];
return candidates.find((candidate) => fs.existsSync(candidate));
}
// Pyright Settings - Minimal editor settings needed for diagnostics and import
// resolution. Shared by workspace/configuration and didChangeConfiguration.
function pyrightSettings(rootDir: string): {
pythonPath: string | undefined;
analysis: {
diagnosticMode: string;
typeCheckingMode: string;
autoSearchPaths: boolean;
useLibraryCodeForTypes: boolean;
};
} {
return {
pythonPath: resolvePythonPath(rootDir),
analysis: {
diagnosticMode: "openFilesOnly",
typeCheckingMode: "basic",
autoSearchPaths: true,
useLibraryCodeForTypes: true,
},
};
}
// Global Root Markers — appended to every server's rootMarkers list // Global Root Markers — appended to every server's rootMarkers list
export const globalRootMarkers = [".git"]; export const globalRootMarkers = [".git"];
@@ -32,6 +66,15 @@ export const servers: ServerConfig[] = [
args: ["--stdio"], args: ["--stdio"],
rootMarkers: ["pyproject.toml", "setup.py", "setup.cfg"], rootMarkers: ["pyproject.toml", "setup.py", "setup.cfg"],
languageId: "python", languageId: "python",
workspaceConfiguration: {
initialSettings: ({ rootDir }) => ({ python: pyrightSettings(rootDir) }),
getSection: (section, { rootDir }) => {
const settings = pyrightSettings(rootDir);
if (section === "python") return settings;
if (section === "python.analysis") return settings.analysis;
return null;
},
},
}, },
{ {
id: "lua-language-server", id: "lua-language-server",

View File

@@ -102,9 +102,19 @@ export class LspClient {
} }
}, },
); );
// Accept Common Server Requests - Return empty/null so servers don't // Accept Common Server Requests - Return one configuration response per
// stall. Good enough for a CLI; a real client would answer properly. // requested item. Server-specific settings live in server.ts so adding
this.conn.onRequest("workspace/configuration", () => []); // another picky server doesn't grow conditionals in this transport layer.
this.conn.onRequest(
"workspace/configuration",
(params: { items?: { section?: string }[] }) => {
const items = params.items ?? [];
const config = this.server.workspaceConfiguration;
return items.map((item) =>
config?.getSection?.(item.section, { rootDir, env }) ?? null,
);
},
);
this.conn.onRequest("client/registerCapability", () => null); this.conn.onRequest("client/registerCapability", () => null);
this.conn.onRequest("client/unregisterCapability", () => null); this.conn.onRequest("client/unregisterCapability", () => null);
@@ -136,6 +146,16 @@ export class LspClient {
}, },
}); });
this.conn.sendNotification("initialized", {}); this.conn.sendNotification("initialized", {});
// Push Configuration - Some servers do not always request workspace/configuration,
// but still consume settings delivered through didChangeConfiguration.
const settings = this.server.workspaceConfiguration?.initialSettings?.({
rootDir,
env,
});
if (settings !== undefined && settings !== null) {
this.conn.sendNotification("workspace/didChangeConfiguration", { settings });
}
} }
// Wait For Ready - Resolves when there are no outstanding progress // Wait For Ready - Resolves when there are no outstanding progress

View File

@@ -17,6 +17,22 @@ export class ServerNotFoundError extends Error {
} }
} }
export interface WorkspaceConfigurationContext {
rootDir: string;
env: NodeJS.ProcessEnv;
}
export interface ServerWorkspaceConfiguration {
// Initial Settings - Optional payload pushed via workspace/didChangeConfiguration
// after initialize/initialized for servers that don't always request config.
initialSettings?: (ctx: WorkspaceConfigurationContext) => unknown;
// Section Settings - Optional handler for workspace/configuration requests.
getSection?: (
section: string | undefined,
ctx: WorkspaceConfigurationContext,
) => unknown;
}
export interface ServerConfig { export interface ServerConfig {
// Stable identifier (useful for logs and future daemon cache keys). // Stable identifier (useful for logs and future daemon cache keys).
id: string; id: string;
@@ -38,6 +54,9 @@ export interface ServerConfig {
// hover/definition/references/completion/documentSymbol but included // hover/definition/references/completion/documentSymbol but included
// in lsp_diagnostics and auto-check. // in lsp_diagnostics and auto-check.
diagnosticsOnly?: boolean; diagnosticsOnly?: boolean;
// Workspace Configuration - Optional server-specific settings exposed through
// workspace/configuration and workspace/didChangeConfiguration.
workspaceConfiguration?: ServerWorkspaceConfiguration;
} }
// Supported high-level commands exposed via the CLI. Extend this union // Supported high-level commands exposed via the CLI. Extend this union