merge PR #5746 via eval/pr-5746: fix(lsp): returned null for missing configuration
This commit is contained in:
@@ -68,6 +68,9 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed the `write` approval gate misclassifying `xd://` device writes as `exec` when the mounted tool declared a function-valued (argument-dependent) `approval`: the gate discarded the function and never decoded the device JSON payload, so read/write device operations prompted in non-yolo modes their approval mode permits. It now parses valid object payloads and evaluates the mounted tool's normal approval decision, while malformed JSON, non-object payloads, and unknown devices still fall back to `exec` and prompt ([#5727](https://github.com/can1357/oh-my-pi/issues/5727)).
|
||||
### Fixed
|
||||
|
||||
- Fixed custom LSP servers such as `roslyn-language-server` crashing after initialization when they request unconfigured `workspace/configuration` sections; missing settings now receive the spec-required `null` instead of `{}` ([#5745](https://github.com/can1357/oh-my-pi/issues/5745)).
|
||||
|
||||
## [17.0.1] - 2026-07-16
|
||||
|
||||
|
||||
@@ -428,7 +428,7 @@ async function handleConfigurationRequest(client: LspClient, message: LspJsonRpc
|
||||
const items = params?.items ?? [];
|
||||
const result = items.map(item => {
|
||||
const section = item.section ?? "";
|
||||
return client.config.settings?.[section] ?? {};
|
||||
return client.config.settings?.[section] ?? null;
|
||||
});
|
||||
await sendResponse(client, message.id, result, "workspace/configuration");
|
||||
}
|
||||
|
||||
@@ -429,6 +429,103 @@ describe("lsp regressions", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("answers missing workspace configuration sections with null in request order", async () => {
|
||||
const tempDir = TempDir.createSync("@omp-lsp-configuration-null-");
|
||||
try {
|
||||
const server = installFakeLsp((message, srv) => {
|
||||
if (message.method === "initialize") {
|
||||
srv.send({ jsonrpc: "2.0", id: message.id, result: { capabilities: {} } });
|
||||
} else if (message.method === "initialized") {
|
||||
srv.send({
|
||||
jsonrpc: "2.0",
|
||||
id: 5745,
|
||||
method: "workspace/configuration",
|
||||
params: {
|
||||
items: [
|
||||
{ section: "razor.format.attribute_indent_style" },
|
||||
{ section: "html.auto_closing_tags" },
|
||||
{},
|
||||
],
|
||||
},
|
||||
});
|
||||
} else if (message.method === "shutdown") {
|
||||
srv.send({ jsonrpc: "2.0", id: message.id, result: null });
|
||||
} else if (message.method === "exit") {
|
||||
srv.exit(0);
|
||||
}
|
||||
});
|
||||
const config: ServerConfig = {
|
||||
command: "fake-lsp",
|
||||
fileTypes: ["cs"],
|
||||
rootMarkers: [],
|
||||
settings: { "html.auto_closing_tags": true },
|
||||
};
|
||||
|
||||
await lspClient.getOrCreateClient(config, tempDir.path(), 1_000);
|
||||
const response = await server.waitFor(message => message.id === 5745 && message.method === undefined);
|
||||
|
||||
expect(response.result).toEqual([null, true, null]);
|
||||
} finally {
|
||||
await lspClient.shutdownAll();
|
||||
tempDir.removeSync();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the session alive when configuration is pulled after didChangeConfiguration", async () => {
|
||||
const tempDir = TempDir.createSync("@omp-lsp-configuration-session-");
|
||||
let configurationAccepted = false;
|
||||
try {
|
||||
const server = installFakeLsp((message, srv) => {
|
||||
if (message.method === "initialize") {
|
||||
srv.send({ jsonrpc: "2.0", id: message.id, result: { capabilities: { hoverProvider: true } } });
|
||||
} else if (message.method === "workspace/didChangeConfiguration") {
|
||||
srv.send({
|
||||
jsonrpc: "2.0",
|
||||
id: "roslyn-config",
|
||||
method: "workspace/configuration",
|
||||
params: { items: [{ section: "razor.format.attribute_indent_style" }] },
|
||||
});
|
||||
} else if (message.id === "roslyn-config" && message.method === undefined) {
|
||||
if (Array.isArray(message.result) && message.result[0] === null) {
|
||||
configurationAccepted = true;
|
||||
} else {
|
||||
srv.exit(-6);
|
||||
}
|
||||
} else if (message.method === "textDocument/hover" && configurationAccepted) {
|
||||
srv.send({ jsonrpc: "2.0", id: message.id, result: { contents: "string C.Target" } });
|
||||
} else if (message.method === "shutdown") {
|
||||
srv.send({ jsonrpc: "2.0", id: message.id, result: null });
|
||||
} else if (message.method === "exit") {
|
||||
srv.exit(0);
|
||||
}
|
||||
});
|
||||
const config: ServerConfig = {
|
||||
command: "fake-lsp",
|
||||
fileTypes: ["cs"],
|
||||
rootMarkers: [],
|
||||
};
|
||||
|
||||
const client = await lspClient.getOrCreateClient(config, tempDir.path(), 1_000);
|
||||
await server.waitFor(message => message.id === "roslyn-config" && message.method === undefined);
|
||||
const result = await lspClient.sendRequest(
|
||||
client,
|
||||
"textDocument/hover",
|
||||
{
|
||||
textDocument: { uri: fileToUri(path.join(tempDir.path(), "Target.cs")) },
|
||||
position: { line: 0, character: 6 },
|
||||
},
|
||||
undefined,
|
||||
50,
|
||||
);
|
||||
|
||||
expect(result).toEqual({ contents: "string C.Target" });
|
||||
expect(configurationAccepted).toBe(true);
|
||||
} finally {
|
||||
await lspClient.shutdownAll();
|
||||
tempDir.removeSync();
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts dynamic capability registration before semantic requests", async () => {
|
||||
const tempDir = TempDir.createSync("@omp-lsp-dynamic-registration-");
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user