Name the diagnostics resource parameter; make symbol lookup pure

This commit is contained in:
EFLKumo committed 2026-09-20 15:36:30 +08:00
1 parent 1b7678ad3d
commit 6e5fd5ba79
3 files changed
+22 -20

No files matched your search

+18 -16
View File
@@ -4,7 +4,7 @@
* forwarded as MCP log notifications. * forwarded as MCP log notifications.
*/ */
import { Effect, Layer, Schema, Stream } from "effect"; import { Effect, Layer, Schema, Stream } from "effect";
import { McpServer } from "effect/unstable/ai"; import { McpSchema, McpServer } from "effect/unstable/ai";
import { Diagnostics } from "./lsp/Diagnostics.ts"; import { Diagnostics } from "./lsp/Diagnostics.ts";
import { Documents } from "./lsp/Documents.ts"; import { Documents } from "./lsp/Documents.ts";
import * as Editor from "./lsp/Editor.ts"; import * as Editor from "./lsp/Editor.ts";
@@ -46,23 +46,25 @@ export const layer = Layer.effectDiscard(
content: Effect.map(Editor.diagnostics({}), json), content: Effect.map(Editor.diagnostics({}), json),
}); });
yield* McpServer.registerResource`lsp://diagnostics/${Schema.String}`({ yield* McpServer.registerResource`lsp://diagnostics/${McpSchema.param("file", Schema.String)}`(
name: "File diagnostics", {
description: name: "File diagnostics",
"Diagnostics of one file (path URL-encoded, relative to the workspace root)", description:
mimeType: "application/json", "Diagnostics of one file (path URL-encoded, relative to the workspace root)",
completion: { mimeType: "application/json",
param0: (input) => completion: {
Effect.map(docs.opened, (uris) => file: (input) =>
uris.map(docs.file).filter((file) => file.startsWith(input)), Effect.map(docs.opened, (uris) =>
uris.map(docs.file).filter((file) => file.startsWith(input)),
),
},
content: (_, file) =>
Effect.map(
Editor.diagnostics({ file: decodeURIComponent(file) }),
json,
), ),
}, },
content: (_, file) => );
Effect.map(
Editor.diagnostics({ file: decodeURIComponent(file) }),
json,
),
});
const updated = (uri: string) => const updated = (uri: string) =>
mcp.notifications["notifications/resources/updated"]({ uri }); mcp.notifications["notifications/resources/updated"]({ uri });
+3 -3
View File
@@ -125,7 +125,7 @@ const locate = Effect.fn("Editor.locate")(function* (where: Model.Where) {
where.column !== undefined where.column !== undefined
? where.column - 1 ? where.column - 1
: where.symbol !== undefined : where.symbol !== undefined
? yield* Effect.orElseSucceed(findSymbol(text, where.symbol), () => -1) ? findSymbol(text, where.symbol)
: Math.max(0, text.search(/\S/)); : Math.max(0, text.search(/\S/));
if (character < 0) { if (character < 0) {
return yield* new LspError({ return yield* new LspError({
@@ -140,13 +140,13 @@ const locate = Effect.fn("Editor.locate")(function* (where: Model.Where) {
return { doc, position, textDocument: { uri: doc.uri } }; return { doc, position, textDocument: { uri: doc.uri } };
}); });
/** Index of `symbol` as a whole word on the line, else its first occurrence, else -1. */
const findSymbol = (text: string, symbol: string) => { const findSymbol = (text: string, symbol: string) => {
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const index = text.search( const index = text.search(
new RegExp(`(?<![\\p{L}\\p{N}_$])${escaped}(?![\\p{L}\\p{N}_$])`, "u"), new RegExp(`(?<![\\p{L}\\p{N}_$])${escaped}(?![\\p{L}\\p{N}_$])`, "u"),
); );
const found = index >= 0 ? index : text.indexOf(symbol); return index >= 0 ? index : text.indexOf(symbol);
return found >= 0 ? Effect.succeed(found) : Effect.fail(found);
}; };
const locations = Effect.fn("Editor.locations")(function* ( const locations = Effect.fn("Editor.locations")(function* (
+1 -1
View File
@@ -137,7 +137,7 @@ describe("MCP server", () => {
const { resourceTemplates } = yield* mcp["resources/templates/list"]({}); const { resourceTemplates } = yield* mcp["resources/templates/list"]({});
assert.deepStrictEqual( assert.deepStrictEqual(
resourceTemplates.map((t) => t.uriTemplate), resourceTemplates.map((t) => t.uriTemplate),
["lsp://diagnostics/{param0}"], ["lsp://diagnostics/{file}"],
); );
}).pipe(Effect.scoped), }).pipe(Effect.scoped),
); );