From 6e5fd5ba79fbced110777ecc5f3ce96e897adf77 Mon Sep 17 00:00:00 2001 From: EFLKumo <96906415+EFLKumo@users.noreply.github.com> Date: Sat, 19 Sep 2026 00:46:44 +0800 Subject: [PATCH] Name the diagnostics resource parameter; make symbol lookup pure --- src/Resources.ts | 34 ++++++++++++++++++---------------- src/lsp/Editor.ts | 6 +++--- test/Server.test.ts | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/Resources.ts b/src/Resources.ts index f9effb6..9717a88 100644 --- a/src/Resources.ts +++ b/src/Resources.ts @@ -4,7 +4,7 @@ * forwarded as MCP log notifications. */ 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 { Documents } from "./lsp/Documents.ts"; import * as Editor from "./lsp/Editor.ts"; @@ -46,23 +46,25 @@ export const layer = Layer.effectDiscard( content: Effect.map(Editor.diagnostics({}), json), }); - yield* McpServer.registerResource`lsp://diagnostics/${Schema.String}`({ - name: "File diagnostics", - description: - "Diagnostics of one file (path URL-encoded, relative to the workspace root)", - mimeType: "application/json", - completion: { - param0: (input) => - Effect.map(docs.opened, (uris) => - uris.map(docs.file).filter((file) => file.startsWith(input)), + yield* McpServer.registerResource`lsp://diagnostics/${McpSchema.param("file", Schema.String)}`( + { + name: "File diagnostics", + description: + "Diagnostics of one file (path URL-encoded, relative to the workspace root)", + mimeType: "application/json", + completion: { + file: (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) => mcp.notifications["notifications/resources/updated"]({ uri }); diff --git a/src/lsp/Editor.ts b/src/lsp/Editor.ts index f23834b..5e0e492 100644 --- a/src/lsp/Editor.ts +++ b/src/lsp/Editor.ts @@ -125,7 +125,7 @@ const locate = Effect.fn("Editor.locate")(function* (where: Model.Where) { where.column !== undefined ? where.column - 1 : where.symbol !== undefined - ? yield* Effect.orElseSucceed(findSymbol(text, where.symbol), () => -1) + ? findSymbol(text, where.symbol) : Math.max(0, text.search(/\S/)); if (character < 0) { 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 } }; }); +/** Index of `symbol` as a whole word on the line, else its first occurrence, else -1. */ const findSymbol = (text: string, symbol: string) => { const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const index = text.search( new RegExp(`(?= 0 ? index : text.indexOf(symbol); - return found >= 0 ? Effect.succeed(found) : Effect.fail(found); + return index >= 0 ? index : text.indexOf(symbol); }; const locations = Effect.fn("Editor.locations")(function* ( diff --git a/test/Server.test.ts b/test/Server.test.ts index 6d66527..4329c05 100644 --- a/test/Server.test.ts +++ b/test/Server.test.ts @@ -137,7 +137,7 @@ describe("MCP server", () => { const { resourceTemplates } = yield* mcp["resources/templates/list"]({}); assert.deepStrictEqual( resourceTemplates.map((t) => t.uriTemplate), - ["lsp://diagnostics/{param0}"], + ["lsp://diagnostics/{file}"], ); }).pipe(Effect.scoped), );