Skip to content

Commit 0a5233c

Browse files
wenytang-msCopilot
andcommitted
Improve Java LSP tool result handoff
Return structured file and line fields from findSymbol so the model can consume precise ranges without repeating symbol search. Accept line-suffixed Java locations in file structure resolution and document outlineInput handoff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3858fdd commit 0a5233c

4 files changed

Lines changed: 83 additions & 23 deletions

File tree

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
{
5353
"name": "lsp_java_getFileStructure",
5454
"toolReferenceName": "javaFileStructure",
55-
"modelDescription": "Get a known Java file's outline: classes, interfaces, methods, fields, symbol kinds, and line ranges, to pick a precise read_file range instead of reading the whole file.\n\nUse after lsp_java_findSymbol returns a file, or when the user gave a Java file path; do not guess paths. Not for workspace-wide search\u2014use lsp_java_findSymbol for that. Do not re-call for the same file unless the first result was empty.",
55+
"modelDescription": "Get a known Java file's outline: classes, interfaces, methods, fields, symbol kinds, and line ranges, to pick a precise read_file range instead of reading the whole file.\n\nUse after lsp_java_findSymbol returns outlineInput/file, or when the user gave a Java file path; do not guess paths. This accepts a workspace-relative Java path and tolerates a trailing line suffix such as src/Foo.java:42. Not for workspace-wide search\u2014use lsp_java_findSymbol for that. Do not re-call for the same file unless the first result was empty.",
5656
"displayName": "Java: Get File Structure",
5757
"userDescription": "Get a Java file outline with classes, methods, fields, and line ranges.",
5858
"tags": [
@@ -69,7 +69,7 @@
6969
"properties": {
7070
"uri": {
7171
"type": "string",
72-
"description": "Workspace-relative path to a Java file. Must be a known path from prior tool results or user input — do not guess."
72+
"description": "Workspace-relative path to a Java file. Prefer outlineInput or file returned by lsp_java_findSymbol. A trailing line suffix like src/Foo.java:42 is tolerated. Must be a known path from prior tool results or user input — do not guess."
7373
}
7474
},
7575
"required": [
@@ -80,7 +80,7 @@
8080
{
8181
"name": "lsp_java_findSymbol",
8282
"toolReferenceName": "javaFindSymbol",
83-
"modelDescription": "Find Java class, interface, method, or field definitions across the workspace by name or partial identifier. Prefer over grep_search, file_search, semantic_search, or search subagents for Java symbol lookup.\n\nOn relevant results, do not repeat with a similar query; continue with lsp_java_getFileStructure or read_file on the returned line range. The tool retries internally, so on an empty result do not re-search\u2014retry once only if it reports indexing in progress, otherwise use generic search.\n\nDo not use for non-Java files, literals, comments, build/XML files, or conceptual exploration.",
83+
"modelDescription": "Find Java class, interface, method, or field definitions across the workspace by name or partial identifier. Prefer over grep_search, file_search, semantic_search, or search subagents for Java symbol lookup.\n\nResults include file, startLine, endLine, range, and outlineInput. On relevant results, do not repeat with a similar query; use read_file on the returned file/range, or lsp_java_getFileStructure with outlineInput when broader file context is needed. The tool retries internally, so on an empty result do not re-search\u2014retry once only if it reports indexing in progress, otherwise use generic search.\n\nDo not use for non-Java files, literals, comments, build/XML files, or conceptual exploration.",
8484
"displayName": "Java: Find Symbol",
8585
"userDescription": "Find Java class, method, field, or interface definitions by name.",
8686
"tags": [

resources/instruments/javaLspContext.instructions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ For Java symbol navigation, two compiler-accurate `lsp_java_*` tools are availab
1010

1111
If these tools are not already available in the current tool list, load them with `tool_search` using a query such as `Java LSP symbol navigation lsp_java`.
1212

13-
Use `lsp_java_findSymbol` before `grep_search`, `search_subagent`, `semantic_search`, or `file_search` only when the task is to locate Java symbols by name or partial identifier. If it returns relevant symbols, do not call it again with the same or similar query; next use `lsp_java_getFileStructure` for the returned file or `read_file` on the smallest useful line range.
13+
Use `lsp_java_findSymbol` before `grep_search`, `search_subagent`, `semantic_search`, or `file_search` only when the task is to locate Java symbols by name or partial identifier. If it returns relevant symbols, do not call it again with the same or similar query; next use `read_file` on the returned `file`/`range`, or call `lsp_java_getFileStructure` with `outlineInput` when broader file context is needed.
1414

15-
Use `lsp_java_getFileStructure` only with a path confirmed by the user or a previous tool result. Do not guess paths. Use generic search for string literals, comments, XML, Gradle/Maven files, non-Java files, or broad conceptual exploration. `findSymbol` already retries internally with a normalized identifier, so do not re-issue the same search on an empty result: if it reports indexing in progress, retry once after a short pause; otherwise fall back to generic search.
15+
Use `lsp_java_getFileStructure` only with a path confirmed by the user or a previous tool result. Prefer `outlineInput` or `file` from `findSymbol`; a trailing line suffix such as `src/Foo.java:42` is tolerated, but do not guess paths. Use generic search for string literals, comments, XML, Gradle/Maven files, non-Java files, or broad conceptual exploration. `findSymbol` already retries internally with a normalized identifier, so do not re-issue the same search on an empty result: if it reports indexing in progress, retry once after a short pause; otherwise fall back to generic search.

resources/skills/java-lsp-tools/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ Two compiler-accurate tools backed by the Java Language Server (jdtls). They ret
1212
### `lsp_java_findSymbol`
1313
Search for Java symbol definitions (classes, methods, fields) by name across the workspace. Supports partial matching.
1414
- Input: `{ query, limit? }` — limit defaults to 20, max 50
15-
- Output: `{ results: [{ name, kind, container?, location, range }], total }` (~60 tokens); `range` is `L start-end`
15+
- Output: `{ results: [{ name, kind, container?, file, startLine, endLine, location, range, outlineInput }], total, nextStep }`; `range` is `L start-end`, and `outlineInput` can be passed to `lsp_java_getFileStructure`
1616
- **Use instead of** `grep_search`, `file_search`, `semantic_search`, or `search_subagent` when looking for where a Java class/method/field is defined by identifier
1717
- Do not repeat with the same or similar query after relevant results are returned
1818

1919
### `lsp_java_getFileStructure`
2020
Get hierarchical outline of a Java file (classes, methods, fields) with line ranges.
21-
- Input: `{ uri }` — workspace-relative path. Must be a known path from prior tool results or user input — do not guess
21+
- Input: `{ uri }` — workspace-relative path. Prefer `outlineInput` or `file` from `lsp_java_findSymbol`; a trailing line suffix such as `src/Foo.java:42` is tolerated. Must be a known path from prior tool results or user input — do not guess
2222
- Output: symbol tree with `L start-end` ranges (~100 tokens)
2323
- **Use before** `read_file` when you need to choose a precise line range in a known Java file
2424

@@ -36,7 +36,7 @@ Get hierarchical outline of a Java file (classes, methods, fields) with line ran
3636

3737
**findSymbol → getFileStructure → read_file (specific lines only)**
3838

39-
If `findSymbol` returns relevant symbols, move forward to `getFileStructure` or `read_file`; do not call `findSymbol` again with the same or similar identifier.
39+
If `findSymbol` returns relevant symbols, use `read_file` on the returned `file`/`range`, or call `getFileStructure` with `outlineInput` when broader file context is needed. Do not call `findSymbol` again with the same or similar identifier unless the returned symbols are irrelevant.
4040

4141
## Fallback
4242

src/copilot/tools/javaContextTools.ts

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,37 @@ function getToolErrorCode(error: unknown): string {
7979
return "unexpectedError";
8080
}
8181

82+
function stripJavaLocationSuffix(input: string): string {
83+
let normalized = input.trim();
84+
if (normalized.length >= 2) {
85+
const first = normalized[0];
86+
const last = normalized[normalized.length - 1];
87+
if ((first === "\"" && last === "\"") || (first === "'" && last === "'") || (first === "`" && last === "`")) {
88+
normalized = normalized.substring(1, normalized.length - 1).trim();
89+
}
90+
}
91+
92+
const javaExt = ".java";
93+
const javaIndex = normalized.toLowerCase().lastIndexOf(javaExt);
94+
if (javaIndex < 0) {
95+
return normalized;
96+
}
97+
98+
const fileEnd = javaIndex + javaExt.length;
99+
const suffix = normalized.substring(fileEnd);
100+
return /^:L?\d+(?:[-:]\d+)?$/i.test(suffix) ? normalized.substring(0, fileEnd) : normalized;
101+
}
102+
82103
/**
83104
* Resolve a file path to a vscode.Uri.
84105
* Accepts:
85106
* - Full file URI: "file:///home/user/project/src/Main.java"
86107
* - Relative path: "src/main/java/Main.java"
108+
* - Location string: "src/main/java/Main.java:42"
87109
* - Absolute path: "/home/user/project/src/Main.java" or "C:\\Users\\...\\Main.java"
88110
*
89-
* Relative paths are resolved against the first workspace folder.
111+
* Relative paths are resolved against the first workspace folder unless they
112+
* start with a workspace folder name in a multi-root workspace.
90113
* The resolved URI must use the file: scheme and fall under a workspace folder.
91114
*/
92115
function resolveFileUri(input: string): vscode.Uri {
@@ -96,19 +119,31 @@ function resolveFileUri(input: string): vscode.Uri {
96119
}
97120

98121
let uri: vscode.Uri;
122+
const normalizedInput = stripJavaLocationSuffix(input);
99123

100-
if (input.includes("://")) {
124+
if (normalizedInput.includes("://")) {
101125
// URI string (e.g. "file:///home/user/project/src/Main.java")
102-
uri = vscode.Uri.parse(input);
126+
uri = vscode.Uri.parse(normalizedInput);
103127
if (uri.scheme !== "file") {
104128
throw new Error(`Unsupported URI scheme "${uri.scheme}". Only file: URIs are allowed.`);
105129
}
106-
} else if (path.isAbsolute(input)) {
130+
} else if (path.isAbsolute(normalizedInput)) {
107131
// Absolute filesystem path (Unix or Windows)
108-
uri = vscode.Uri.file(input);
132+
uri = vscode.Uri.file(normalizedInput);
109133
} else {
110-
// Relative path — resolve against first workspace folder
111-
uri = vscode.Uri.joinPath(folders[0].uri, input);
134+
// Relative path — resolve against a matching workspace folder when
135+
// asRelativePath included the folder name, otherwise use the first root.
136+
const normalizedRelativePath = normalizedInput.replace(/\\/g, "/");
137+
const matchingFolder = folders.find(folder =>
138+
normalizedRelativePath === folder.name || normalizedRelativePath.startsWith(`${folder.name}/`));
139+
if (matchingFolder) {
140+
const pathInFolder = normalizedRelativePath === matchingFolder.name
141+
? ""
142+
: normalizedRelativePath.substring(matchingFolder.name.length + 1);
143+
uri = vscode.Uri.joinPath(matchingFolder.uri, pathInFolder);
144+
} else {
145+
uri = vscode.Uri.joinPath(folders[0].uri, normalizedInput);
146+
}
112147
}
113148

114149
// Ensure the resolved path is under a workspace folder
@@ -288,15 +323,40 @@ const findSymbolTool: vscode.LanguageModelTool<FindSymbolInput> = {
288323
return toResult(noMatchesPayload);
289324
}
290325
totalResults = symbols.length;
291-
const results = symbols.slice(0, limit).map(s => ({
292-
name: s.name,
293-
kind: vscode.SymbolKind[s.kind],
294-
container: s.containerName || undefined,
295-
location: `${vscode.workspace.asRelativePath(s.location.uri)}:${s.location.range.start.line + 1}`,
296-
range: `L${s.location.range.start.line + 1}-${s.location.range.end.line + 1}`,
297-
}));
326+
const results = symbols.slice(0, limit).map(s => {
327+
const file = vscode.workspace.asRelativePath(s.location.uri);
328+
const startLine = s.location.range.start.line + 1;
329+
const endLine = s.location.range.end.line + 1;
330+
return {
331+
name: s.name,
332+
kind: vscode.SymbolKind[s.kind],
333+
container: s.containerName || undefined,
334+
file,
335+
startLine,
336+
endLine,
337+
location: `${file}:${startLine}`,
338+
range: `L${startLine}-${endLine}`,
339+
outlineInput: file,
340+
};
341+
});
298342
resultCount = results.length;
299-
const findSymbolPayload = { results, total: symbols.length };
343+
const nextStep = symbols.length <= 3
344+
? [
345+
"These are exact Java symbol locations.",
346+
"Use read_file on the returned file/range, or lsp_java_getFileStructure with outlineInput for broader file context.",
347+
"Do not call lsp_java_findSymbol again for the same or similar symbol unless these results are irrelevant.",
348+
].join(" ")
349+
: symbols.length > resultCount
350+
? [
351+
"Many symbols matched.",
352+
"Refine only if the returned locations are not specific enough;",
353+
"otherwise use read_file on the relevant returned file/range.",
354+
].join(" ")
355+
: [
356+
"Use read_file on the relevant returned file/range,",
357+
"or lsp_java_getFileStructure with outlineInput when file outline is needed.",
358+
].join(" ");
359+
const findSymbolPayload = { results, total: symbols.length, nextStep };
300360
responseCharCount = getResponseCharCount(findSymbolPayload);
301361
return toResult(findSymbolPayload);
302362
} catch (e) {

0 commit comments

Comments
 (0)