@trim21/personal-pi-extensions 0.1.489 → 0.1.491
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/bwrap/approval-suggest.ts +1 -1
- package/src/bwrap/core.ts +6 -6
- package/src/bwrap/mihomo-config.ts +3 -1
- package/src/bwrap/network-stack.ts +2 -2
- package/src/claude-code/edit-utils.ts +4 -4
- package/src/claude-code/files.ts +41 -26
- package/src/claude-code/glob.ts +1 -1
- package/src/gh-readonly.ts +12 -12
- package/src/lib/cli.ts +3 -6
- package/src/lib/lsp/client.ts +139 -9
- package/src/lib/lsp/inspect-tool.ts +376 -0
- package/src/lib/lsp/language.ts +1 -1
- package/src/lib/lsp/lsp-find-definition.md +8 -0
- package/src/lib/lsp/lsp-find-reference.md +8 -0
- package/src/lib/lsp/lsp-inspect.md +8 -0
- package/src/lib/lsp/lsp.ts +200 -34
- package/src/lib/lsp/rename-tool.ts +2 -2
- package/src/lib/ui.ts +2 -2
- package/src/lib/write-guard.ts +1 -1
- package/src/openai-cost/config.ts +1 -1
- package/src/openai-cost/cost.ts +1 -1
- package/src/opencode/edit-engine.ts +1 -1
- package/src/opencode/files.ts +34 -23
- package/src/spawn-agent.ts +2 -2
- package/src/talk/index.ts +2 -8
- package/src/talk/registry.ts +1 -1
- package/src/vision-agent.ts +2 -2
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 只读 LSP 符号查询工具,由 claude-code / opencode 两个工具集共享注册:
|
|
3
|
+
*
|
|
4
|
+
* - lsp-find-definition:textDocument/definition → 定义位置列表
|
|
5
|
+
* - lsp-find-reference:textDocument/references(含声明处)→ 按文件分组
|
|
6
|
+
* - lsp-inspect:textDocument/hover → hover 内容原样透传
|
|
7
|
+
*
|
|
8
|
+
* 三个工具共用 lsp-rename 的定位与消歧语义(file_path + line + symbol,
|
|
9
|
+
* character 消歧):按词边界枚举行内候选逐个探测,格式化输出一致即视为
|
|
10
|
+
* 同一符号的多次出现,不一致报歧义并列出候选列号。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { readFile, stat } from "node:fs/promises";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { Type } from "typebox";
|
|
19
|
+
import type { Hover } from "vscode-languageserver-types";
|
|
20
|
+
|
|
21
|
+
import { resolvePathArg } from "../path.js";
|
|
22
|
+
import type { InspectLocation } from "./client.js";
|
|
23
|
+
import type { LspService } from "./lsp.js";
|
|
24
|
+
import { type LspPosition, symbolCandidates } from "./rename.js";
|
|
25
|
+
|
|
26
|
+
/** references 输出上限:每文件最多列出的行片段数。 */
|
|
27
|
+
const MAX_ENTRIES_PER_FILE = 10;
|
|
28
|
+
/** references 输出上限:最多列出片段的文件数,超出部分按计数汇总。 */
|
|
29
|
+
const MAX_FILES_LISTED = 30;
|
|
30
|
+
const MAX_SNIPPET_LENGTH = 200;
|
|
31
|
+
|
|
32
|
+
/** 一次探测的产出:text 是消歧分组键,subtitle 供 pendant 摘要。 */
|
|
33
|
+
interface InspectOutput {
|
|
34
|
+
text: string;
|
|
35
|
+
subtitle: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadPrompt(fileName: string): string {
|
|
39
|
+
return readFileSync(fileURLToPath(new URL(fileName, import.meta.url)), "utf8").trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const FIND_DEFINITION_PROMPT = loadPrompt("lsp-find-definition.md");
|
|
43
|
+
const FIND_REFERENCE_PROMPT = loadPrompt("lsp-find-reference.md");
|
|
44
|
+
const INSPECT_PROMPT = loadPrompt("lsp-inspect.md");
|
|
45
|
+
|
|
46
|
+
const POSITION_SCHEMA = Type.Object(
|
|
47
|
+
{
|
|
48
|
+
file_path: Type.String({
|
|
49
|
+
description:
|
|
50
|
+
"A file containing the symbol (absolute path, or relative / ~/ path resolved against the session cwd)",
|
|
51
|
+
}),
|
|
52
|
+
line: Type.Integer({
|
|
53
|
+
minimum: 1,
|
|
54
|
+
description:
|
|
55
|
+
"1-based line number where the symbol appears (any occurrence works, not only the definition)",
|
|
56
|
+
}),
|
|
57
|
+
symbol: Type.String({
|
|
58
|
+
minLength: 1,
|
|
59
|
+
description: "The symbol's name exactly as it appears on that line",
|
|
60
|
+
}),
|
|
61
|
+
character: Type.Optional(
|
|
62
|
+
Type.Integer({
|
|
63
|
+
minimum: 1,
|
|
64
|
+
description:
|
|
65
|
+
"1-based character offset of the symbol on that line. Only needed when the tool reports an ambiguity error on this line",
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
{ additionalProperties: false },
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
async function readSymbolFile(
|
|
73
|
+
cwd: string,
|
|
74
|
+
filePathArg: string,
|
|
75
|
+
): Promise<{ path: string; content: string }> {
|
|
76
|
+
const filePath = resolvePathArg(cwd, filePathArg);
|
|
77
|
+
try {
|
|
78
|
+
await stat(filePath);
|
|
79
|
+
const content = await readFile(filePath, "utf8");
|
|
80
|
+
return { path: filePath, content };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
83
|
+
throw new Error(`File does not exist: ${filePath}`, { cause: error });
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 目标行内容片段(跨文件缓存;读取失败静默跳过片段,坐标仍然输出)。 */
|
|
90
|
+
async function lineSnippet(
|
|
91
|
+
path: string,
|
|
92
|
+
line: number,
|
|
93
|
+
cache: Map<string, string[]>,
|
|
94
|
+
): Promise<string> {
|
|
95
|
+
let lines = cache.get(path);
|
|
96
|
+
if (lines === undefined) {
|
|
97
|
+
try {
|
|
98
|
+
const content = await readFile(path, "utf8");
|
|
99
|
+
lines = content.split("\n");
|
|
100
|
+
} catch {
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
cache.set(path, lines);
|
|
104
|
+
}
|
|
105
|
+
const text = (lines[line] ?? "").replace(/\r$/, "");
|
|
106
|
+
return text.length > MAX_SNIPPET_LENGTH ? `${text.slice(0, MAX_SNIPPET_LENGTH)}…` : text;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function toCoordinates(location: InspectLocation): string {
|
|
110
|
+
return `${location.path}:${location.line + 1}:${location.character + 1}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function formatDefinitionLocations(
|
|
114
|
+
locations: InspectLocation[],
|
|
115
|
+
cache: Map<string, string[]>,
|
|
116
|
+
): Promise<InspectOutput> {
|
|
117
|
+
if (locations.length === 0) {
|
|
118
|
+
return { text: "No definition found for this symbol.", subtitle: "0 definition(s)" };
|
|
119
|
+
}
|
|
120
|
+
const lines = [`Found ${locations.length} definition(s):`];
|
|
121
|
+
for (const location of locations) {
|
|
122
|
+
const snippet = await lineSnippet(location.path, location.line, cache);
|
|
123
|
+
lines.push(`- ${toCoordinates(location)}${snippet === "" ? "" : `\n ${snippet}`}`);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
text: lines.join("\n"),
|
|
127
|
+
subtitle: `${locations.length} definition(s)`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function formatReferenceLocations(
|
|
132
|
+
locations: InspectLocation[],
|
|
133
|
+
cache: Map<string, string[]>,
|
|
134
|
+
): Promise<InspectOutput> {
|
|
135
|
+
if (locations.length === 0) {
|
|
136
|
+
return { text: "No references found for this symbol.", subtitle: "0 reference(s)" };
|
|
137
|
+
}
|
|
138
|
+
const byPath = new Map<string, InspectLocation[]>();
|
|
139
|
+
for (const location of locations) {
|
|
140
|
+
const existing = byPath.get(location.path);
|
|
141
|
+
if (existing) existing.push(location);
|
|
142
|
+
else byPath.set(location.path, [location]);
|
|
143
|
+
}
|
|
144
|
+
const paths = [...byPath.keys()];
|
|
145
|
+
const sections = [`Found ${locations.length} reference(s) in ${paths.length} file(s):`];
|
|
146
|
+
for (const path of paths.slice(0, MAX_FILES_LISTED)) {
|
|
147
|
+
const entries = byPath.get(path);
|
|
148
|
+
if (!entries) continue;
|
|
149
|
+
const shown = entries.slice(0, MAX_ENTRIES_PER_FILE);
|
|
150
|
+
sections.push(`### ${path} (${entries.length})`);
|
|
151
|
+
for (const entry of shown) {
|
|
152
|
+
const snippet = await lineSnippet(path, entry.line, cache);
|
|
153
|
+
sections.push(
|
|
154
|
+
`- ${entry.line + 1}:${entry.character + 1}${snippet === "" ? "" : `: ${snippet}`}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (entries.length > shown.length) {
|
|
158
|
+
sections.push(`(+${entries.length - shown.length} more in this file)`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const omitted = paths.slice(MAX_FILES_LISTED);
|
|
162
|
+
if (omitted.length > 0) {
|
|
163
|
+
const omittedCount = omitted.reduce((sum, path) => sum + (byPath.get(path)?.length ?? 0), 0);
|
|
164
|
+
sections.push(`(+${omittedCount} reference(s) in ${omitted.length} more file(s) not shown)`);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
text: sections.join("\n"),
|
|
168
|
+
subtitle: `${locations.length} reference(s) in ${paths.length} file(s)`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
type MarkedString = string | { language: string; value: string };
|
|
173
|
+
|
|
174
|
+
function formatMarkedString(marked: MarkedString): string {
|
|
175
|
+
return typeof marked === "string" ? marked : `\`\`\`${marked.language}\n${marked.value}\n\`\`\``;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** hover contents 原样透传:仅做结构格式化(MarkedString → code fence),不改写内容。 */
|
|
179
|
+
function formatHoverContents(contents: Hover["contents"]): string {
|
|
180
|
+
if (typeof contents === "string") return contents;
|
|
181
|
+
if (Array.isArray(contents)) {
|
|
182
|
+
return contents.map((marked) => formatMarkedString(marked)).join("\n\n");
|
|
183
|
+
}
|
|
184
|
+
if ("language" in contents) return formatMarkedString(contents);
|
|
185
|
+
return contents.value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 行内同名候选逐个探测:输出一致即同一符号的多次出现;不一致报歧义并列出
|
|
190
|
+
* 各候选的 1-based 列号(与 lsp-rename 的消歧行为一致)。
|
|
191
|
+
*/
|
|
192
|
+
async function probeSymbolCandidates(options: {
|
|
193
|
+
content: string;
|
|
194
|
+
filePath: string;
|
|
195
|
+
line: number;
|
|
196
|
+
symbol: string;
|
|
197
|
+
character?: number;
|
|
198
|
+
signal?: AbortSignal;
|
|
199
|
+
probe: (position: LspPosition) => Promise<InspectOutput>;
|
|
200
|
+
}): Promise<InspectOutput> {
|
|
201
|
+
const candidates = symbolCandidates(
|
|
202
|
+
options.content,
|
|
203
|
+
options.line - 1,
|
|
204
|
+
options.symbol,
|
|
205
|
+
options.character === undefined ? undefined : options.character - 1,
|
|
206
|
+
);
|
|
207
|
+
if (candidates.length === 0) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Symbol "${options.symbol}" not found on line ${options.line} of ${options.filePath}. Read the file again and locate the symbol.`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const outputs: InspectOutput[] = [];
|
|
213
|
+
for (const candidate of candidates) {
|
|
214
|
+
options.signal?.throwIfAborted();
|
|
215
|
+
outputs.push(await options.probe(candidate));
|
|
216
|
+
}
|
|
217
|
+
const groups = new Map<string, { output: InspectOutput; candidates: LspPosition[] }>();
|
|
218
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
219
|
+
const output = outputs.at(index);
|
|
220
|
+
if (!output) continue;
|
|
221
|
+
const group = groups.get(output.text);
|
|
222
|
+
if (group) group.candidates.push(candidate);
|
|
223
|
+
else groups.set(output.text, { output, candidates: [candidate] });
|
|
224
|
+
}
|
|
225
|
+
if (groups.size > 1) {
|
|
226
|
+
const listing = [...groups.values()]
|
|
227
|
+
.map((group) =>
|
|
228
|
+
group.candidates
|
|
229
|
+
.map((candidate) => `- line ${candidate.line + 1}, column ${candidate.character + 1}`)
|
|
230
|
+
.join("\n"),
|
|
231
|
+
)
|
|
232
|
+
.join("\n");
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Ambiguous symbol on line ${options.line} of ${options.filePath}: several distinct symbols share the name "${options.symbol}". Retry with 'character' (1-based) to pick one:\n${listing}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const first = [...groups.values()].at(0);
|
|
238
|
+
if (!first) throw new Error("LSP inspect returned no result");
|
|
239
|
+
return first.output;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function registerLspInspectTools(pi: ExtensionAPI, service: LspService): void {
|
|
243
|
+
pi.registerTool({
|
|
244
|
+
name: "lsp-find-definition",
|
|
245
|
+
label: "Lsp Find Definition",
|
|
246
|
+
description:
|
|
247
|
+
"Find where a code symbol is defined via LSP. Returns 1-based path:line:col locations with source line snippets.",
|
|
248
|
+
promptSnippet: "Find symbol definitions via LSP",
|
|
249
|
+
promptGuidelines: [FIND_DEFINITION_PROMPT],
|
|
250
|
+
parameters: POSITION_SCHEMA,
|
|
251
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
252
|
+
signal?.throwIfAborted();
|
|
253
|
+
const { path: filePath, content } = await readSymbolFile(ctx.cwd, params.file_path);
|
|
254
|
+
const cache = new Map<string, string[]>();
|
|
255
|
+
const output = await probeSymbolCandidates({
|
|
256
|
+
content,
|
|
257
|
+
filePath,
|
|
258
|
+
line: params.line,
|
|
259
|
+
symbol: params.symbol,
|
|
260
|
+
character: params.character,
|
|
261
|
+
signal,
|
|
262
|
+
probe: async (position) => {
|
|
263
|
+
const result = await service.inspect({
|
|
264
|
+
file: filePath,
|
|
265
|
+
cwd: ctx.cwd,
|
|
266
|
+
line: position.line,
|
|
267
|
+
character: position.character,
|
|
268
|
+
query: "definition",
|
|
269
|
+
options: { signal },
|
|
270
|
+
});
|
|
271
|
+
return formatDefinitionLocations(result.locations, cache);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
content: [{ type: "text" as const, text: output.text }],
|
|
276
|
+
details: {
|
|
277
|
+
pendant: {
|
|
278
|
+
title: "lsp-find-definition",
|
|
279
|
+
subtitle: `${params.symbol} · ${output.subtitle}`,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
pi.registerTool({
|
|
287
|
+
name: "lsp-find-reference",
|
|
288
|
+
label: "Lsp Find Reference",
|
|
289
|
+
description:
|
|
290
|
+
"Find all references to a code symbol across the workspace via LSP (includes the declaration). Grouped by file with 1-based line:col and source snippets.",
|
|
291
|
+
promptSnippet: "Find symbol references via LSP",
|
|
292
|
+
promptGuidelines: [FIND_REFERENCE_PROMPT],
|
|
293
|
+
parameters: POSITION_SCHEMA,
|
|
294
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
295
|
+
signal?.throwIfAborted();
|
|
296
|
+
const { path: filePath, content } = await readSymbolFile(ctx.cwd, params.file_path);
|
|
297
|
+
const cache = new Map<string, string[]>();
|
|
298
|
+
const output = await probeSymbolCandidates({
|
|
299
|
+
content,
|
|
300
|
+
filePath,
|
|
301
|
+
line: params.line,
|
|
302
|
+
symbol: params.symbol,
|
|
303
|
+
character: params.character,
|
|
304
|
+
signal,
|
|
305
|
+
probe: async (position) => {
|
|
306
|
+
const result = await service.inspect({
|
|
307
|
+
file: filePath,
|
|
308
|
+
cwd: ctx.cwd,
|
|
309
|
+
line: position.line,
|
|
310
|
+
character: position.character,
|
|
311
|
+
query: "references",
|
|
312
|
+
options: { signal },
|
|
313
|
+
});
|
|
314
|
+
return formatReferenceLocations(result.locations, cache);
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
content: [{ type: "text" as const, text: output.text }],
|
|
319
|
+
details: {
|
|
320
|
+
pendant: {
|
|
321
|
+
title: "lsp-find-reference",
|
|
322
|
+
subtitle: `${params.symbol} · ${output.subtitle}`,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
pi.registerTool({
|
|
330
|
+
name: "lsp-inspect",
|
|
331
|
+
label: "Lsp Inspect",
|
|
332
|
+
description:
|
|
333
|
+
"Get hover information (type signature, documentation) for a code symbol via LSP. Content is passed through from the language server.",
|
|
334
|
+
promptSnippet: "Get hover info for a symbol via LSP",
|
|
335
|
+
promptGuidelines: [INSPECT_PROMPT],
|
|
336
|
+
parameters: POSITION_SCHEMA,
|
|
337
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
338
|
+
signal?.throwIfAborted();
|
|
339
|
+
const { path: filePath, content } = await readSymbolFile(ctx.cwd, params.file_path);
|
|
340
|
+
const output = await probeSymbolCandidates({
|
|
341
|
+
content,
|
|
342
|
+
filePath,
|
|
343
|
+
line: params.line,
|
|
344
|
+
symbol: params.symbol,
|
|
345
|
+
character: params.character,
|
|
346
|
+
signal,
|
|
347
|
+
probe: async (position) => {
|
|
348
|
+
const result = await service.inspect({
|
|
349
|
+
file: filePath,
|
|
350
|
+
cwd: ctx.cwd,
|
|
351
|
+
line: position.line,
|
|
352
|
+
character: position.character,
|
|
353
|
+
query: "hover",
|
|
354
|
+
options: { signal },
|
|
355
|
+
});
|
|
356
|
+
if (result.hover === null) {
|
|
357
|
+
return {
|
|
358
|
+
text: `No hover information for '${params.symbol}' at line ${params.line} of ${filePath}.`,
|
|
359
|
+
subtitle: "no hover info",
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
text: formatHoverContents(result.hover.contents),
|
|
364
|
+
subtitle: "hover",
|
|
365
|
+
};
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: "text" as const, text: output.text }],
|
|
370
|
+
details: {
|
|
371
|
+
pendant: { title: "lsp-inspect", subtitle: `${params.symbol} · ${output.subtitle}` },
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
package/src/lib/lsp/language.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 文件扩展名 → LSP languageId 映射,用于 didOpen 通知。
|
|
3
3
|
* 移植自 opencode packages/opencode/src/lsp/language.ts。
|
|
4
4
|
*/
|
|
5
|
-
export const LANGUAGE_EXTENSIONS: Record<string, string> = {
|
|
5
|
+
export const LANGUAGE_EXTENSIONS: Record<string, string | undefined> = {
|
|
6
6
|
".abap": "abap",
|
|
7
7
|
".bat": "bat",
|
|
8
8
|
".bib": "bibtex",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## LSP Find Definition
|
|
2
|
+
|
|
3
|
+
Find where a code symbol is defined and update nothing — read-only lookup via LSP.
|
|
4
|
+
|
|
5
|
+
- Locate the symbol with `file_path` + `line` (1-based) + `symbol` (the symbol's name exactly as it appears on that line). Any occurrence works — it does not have to be the definition.
|
|
6
|
+
- The tool computes the column itself; do not pass `character` unless asked to disambiguate.
|
|
7
|
+
- When several distinct symbols share the same name on that line, the tool refuses to guess: it reports an ambiguity error listing the candidate columns. Re-run with `character` (1-based) to pick one.
|
|
8
|
+
- Returns every definition site as `path:line:col` (1-based) with a source line snippet — imports, re-exports and overloads are resolved by the language server, unlike text search.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## LSP Find Reference
|
|
2
|
+
|
|
3
|
+
Find every reference to a code symbol across the workspace — read-only lookup via LSP.
|
|
4
|
+
|
|
5
|
+
- Locate the symbol with `file_path` + `line` (1-based) + `symbol` (the symbol's name exactly as it appears on that line). Any occurrence works — it does not have to be the definition.
|
|
6
|
+
- The tool computes the column itself; do not pass `character` unless asked to disambiguate.
|
|
7
|
+
- When several distinct symbols share the same name on that line, the tool refuses to guess: it reports an ambiguity error listing the candidate columns. Re-run with `character` (1-based) to pick one.
|
|
8
|
+
- Results are grouped by file with 1-based `line:col` and source snippets; the declaration site is included. Run it before `lsp-rename` to preview the blast radius.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## LSP Inspect
|
|
2
|
+
|
|
3
|
+
Read a code symbol's hover information (type signature, documentation) without opening the file — read-only via LSP.
|
|
4
|
+
|
|
5
|
+
- Locate the symbol with `file_path` + `line` (1-based) + `symbol` (the symbol's name exactly as it appears on that line). Any occurrence works — it does not have to be the definition.
|
|
6
|
+
- The tool computes the column itself; do not pass `character` unless asked to disambiguate.
|
|
7
|
+
- When several distinct symbols share the same name on that line, the tool refuses to guess: it reports an ambiguity error listing the candidate columns. Re-run with `character` (1-based) to pick one.
|
|
8
|
+
- The hover content is passed through from the language server as-is.
|