Skip to content

Commit 01f0a89

Browse files
authored
Merge branch 'main' into ci/parallel-e2e-plans
2 parents 692c93e + 7be414f commit 01f0a89

16 files changed

Lines changed: 633 additions & 60 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to the "vscode-java-dependency" extension will be documented
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## 0.27.5
8+
9+
- fix - Check GHCP modernization version before call gotoAgentMode command in https://github.com/microsoft/vscode-java-dependency/pull/1022
10+
- perf - Tune Java LSP tool selection guidance in https://github.com/microsoft/vscode-java-dependency/pull/1020
11+
712
## 0.27.4
813

914
- fix - Implement LSP tools for stable builds in https://github.com/microsoft/vscode-java-dependency/pull/1014

jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.Collections;
1717
import java.util.EnumMap;
1818
import java.util.HashMap;
19+
import java.util.LinkedHashSet;
1920
import java.util.LinkedList;
2021
import java.util.List;
2122
import java.util.Map;
@@ -371,7 +372,7 @@ private static List<PackageNode> getPackageRootChildren(PackageParams query, IPr
371372
throw new CoreException(
372373
new Status(IStatus.ERROR, JdtlsExtActivator.PLUGIN_ID, String.format("No package root found for %s", query.getPath())));
373374
}
374-
List<Object> result = getPackageFragmentRootContent(packageRoot, query.isHierarchicalView(), pm);
375+
List<Object> result = getPackageFragmentRootContent(packageRoot, query.isHierarchicalView(), query.getSyncPaths(), pm);
375376
ResourceSet resourceSet = new ResourceSet(result, query.isHierarchicalView());
376377
ResourceVisitor visitor = new JavaResourceVisitor(packageRoot.getJavaProject());
377378
resourceSet.accept(visitor);
@@ -508,8 +509,62 @@ private static List<PackageNode> getFolderChildren(PackageParams query, IProgres
508509
* @param pm the progress monitor
509510
*/
510511
public static List<Object> getPackageFragmentRootContent(IPackageFragmentRoot root, boolean isHierarchicalView, IProgressMonitor pm) throws CoreException {
512+
return getPackageFragmentRootContent(root, isHierarchicalView, null, pm);
513+
}
514+
515+
public static List<Object> getPackageFragmentRootContent(IPackageFragmentRoot root, boolean isHierarchicalView, List<String> syncPaths, IProgressMonitor pm) throws CoreException {
511516
ArrayList<Object> result = new ArrayList<>();
512-
refreshLocal(root.getResource(), pm);
517+
IResource rootResource = root.getResource();
518+
if (rootResource instanceof IContainer && rootResource.exists()
519+
&& root.getKind() == IPackageFragmentRoot.K_SOURCE) {
520+
// Packages created out-of-band (e.g. by code generators or
521+
// refactor-moves that write straight to disk) are otherwise never
522+
// surfaced. A shallow DEPTH_ONE refresh only syncs the source root's
523+
// immediate children and never discovers brand-new nested package
524+
// folders. Even a DEPTH_INFINITE resource refresh is not enough on
525+
// its own: the Java Model keeps a cached list of package fragments
526+
// for the root, so closing it forces getChildren() below to rebuild
527+
// that list from the freshly refreshed resource tree.
528+
//
529+
// On auto-refresh the client passes the changed resource URIs in
530+
// syncPaths so we only deep-refresh those subtrees instead of the
531+
// whole source tree. If any path cannot be resolved to an existing
532+
// resource inside this root we conservatively fall back to a full
533+
// DEPTH_INFINITE refresh so no package is ever missed.
534+
// See https://github.com/microsoft/vscode-java-dependency/issues/914
535+
boolean refreshedTargets = false;
536+
if (syncPaths != null && !syncPaths.isEmpty()) {
537+
Set<IResource> targets = new LinkedHashSet<>();
538+
boolean allResolved = true;
539+
for (String syncPath : syncPaths) {
540+
IResource target = findNearestExistingResource(syncPath, (IContainer) rootResource);
541+
if (target == null) {
542+
allResolved = false;
543+
break;
544+
}
545+
// Multiple changed paths can resolve to the same existing
546+
// ancestor (e.g. several new files in one new package); the
547+
// set keeps each distinct subtree so it is refreshed once.
548+
targets.add(target);
549+
}
550+
if (allResolved && !targets.isEmpty()) {
551+
for (IResource target : targets) {
552+
refreshLocal(target, IResource.DEPTH_INFINITE, pm);
553+
}
554+
refreshedTargets = true;
555+
}
556+
}
557+
if (!refreshedTargets) {
558+
refreshLocal(rootResource, IResource.DEPTH_INFINITE, pm);
559+
}
560+
try {
561+
root.close();
562+
} catch (JavaModelException e) {
563+
JdtlsExtActivator.log(e);
564+
}
565+
} else {
566+
refreshLocal(rootResource, IResource.DEPTH_ONE, pm);
567+
}
513568
if (isHierarchicalView) {
514569
Map<String, IJavaElement> map = new HashMap<>();
515570
for (IJavaElement child : root.getChildren()) {
@@ -599,13 +654,56 @@ public static IJavaProject getJavaProject(String projectUri) {
599654
}
600655

601656
private static void refreshLocal(IResource resource, IProgressMonitor monitor) {
657+
refreshLocal(resource, IResource.DEPTH_ONE, monitor);
658+
}
659+
660+
private static void refreshLocal(IResource resource, int depth, IProgressMonitor monitor) {
602661
if (resource == null || !resource.exists()) {
603662
return;
604663
}
605664
try {
606-
resource.refreshLocal(IResource.DEPTH_ONE, monitor);
665+
resource.refreshLocal(depth, monitor);
607666
} catch (CoreException e) {
608667
JdtlsExtActivator.log(e);
609668
}
610669
}
670+
671+
/**
672+
* Resolve a changed resource URI to the nearest ancestor that already exists
673+
* in the workspace resource tree and lives inside the given source root.
674+
* Used to scope an auto-refresh to only the affected subtree. Returns null
675+
* when the URI cannot be mapped to a resource within the root, in which case
676+
* the caller falls back to a full refresh so no package is missed.
677+
*/
678+
private static IResource findNearestExistingResource(String uriStr, IContainer root) {
679+
if (StringUtils.isBlank(uriStr) || root == null) {
680+
return null;
681+
}
682+
try {
683+
URI uri = JDTUtils.toURI(uriStr);
684+
if (uri == null) {
685+
return null;
686+
}
687+
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
688+
IResource resource = null;
689+
IFile[] files = wsRoot.findFilesForLocationURI(uri);
690+
if (files.length > 0) {
691+
resource = files[0];
692+
} else {
693+
IContainer[] containers = wsRoot.findContainersForLocationURI(uri);
694+
if (containers.length > 0) {
695+
resource = containers[0];
696+
}
697+
}
698+
while (resource != null && !resource.exists()) {
699+
resource = resource.getParent();
700+
}
701+
if (resource != null && root.getFullPath().isPrefixOf(resource.getFullPath())) {
702+
return resource;
703+
}
704+
} catch (Exception e) {
705+
JdtlsExtActivator.logException("Failed to resolve sync path " + uriStr, e);
706+
}
707+
return null;
708+
}
611709
}

jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageParams.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
package com.microsoft.jdtls.ext.core;
1313

14+
import java.util.List;
15+
1416
import com.microsoft.jdtls.ext.core.model.NodeKind;
1517

1618
/**
@@ -31,6 +33,14 @@ public class PackageParams {
3133

3234
private boolean isHierarchicalView;
3335

36+
/**
37+
* Optional list of resource URIs (sent by the client on auto-refresh) that
38+
* have just changed on disk. When present, the server only refreshes the
39+
* affected subtrees instead of deeply refreshing the whole source root.
40+
* See https://github.com/microsoft/vscode-java-dependency/issues/914
41+
*/
42+
private List<String> syncPaths;
43+
3444
public PackageParams() {
3545
}
3646

@@ -97,4 +107,12 @@ public void setRootPath(String rootPath) {
97107
this.rootPath = rootPath;
98108
}
99109

110+
public List<String> getSyncPaths() {
111+
return syncPaths;
112+
}
113+
114+
public void setSyncPaths(List<String> syncPaths) {
115+
this.syncPaths = syncPaths;
116+
}
117+
100118
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-java-dependency",
33
"displayName": "Project Manager for Java",
44
"description": "%description%",
5-
"version": "0.27.4",
5+
"version": "0.27.5",
66
"publisher": "vscjava",
77
"preview": false,
88
"aiKey": "5c642b22-e845-4400-badb-3f8509a70777",
@@ -52,8 +52,15 @@
5252
{
5353
"name": "lsp_java_getFileStructure",
5454
"toolReferenceName": "javaFileStructure",
55-
"modelDescription": "Get the outline (classes, methods, fields) of a Java file with symbol kinds and line ranges.\n\nUse before read_file to find specific line ranges. For searching across files, use lsp_java_findSymbol instead.\n\nOnly use file paths confirmed from prior tool results or user input. If unsure, call lsp_java_findSymbol first.",
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.",
5656
"displayName": "Java: Get File Structure",
57+
"userDescription": "Get a Java file outline with classes, methods, fields, and line ranges.",
58+
"tags": [
59+
"java",
60+
"lsp",
61+
"code-navigation",
62+
"file-outline"
63+
],
5764
"canBeReferencedInPrompt": true,
5865
"icon": "$(symbol-class)",
5966
"when": "config.vscode-java-dependency.enableLspTools && javaLSReady",
@@ -73,8 +80,15 @@
7380
{
7481
"name": "lsp_java_findSymbol",
7582
"toolReferenceName": "javaFindSymbol",
76-
"modelDescription": "Search for Java symbol definitions (classes, interfaces, methods, fields) across the entire workspace by name. Returns precise locations in ~60 tokens vs ~500+ tokens from grep_search.\n\nWhen to use:\n- To find where a class, method, or field is defined — faster and more precise than grep_search\n- To discover file paths before using other tools\n- Supports partial matching (e.g. 'UserSvc' finds 'UserServiceImpl')\n\nWhen NOT to use:\n- For non-Java files\n- To search for string literals, comments, or non-symbol text (use grep_search)",
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.",
7784
"displayName": "Java: Find Symbol",
85+
"userDescription": "Find Java class, method, field, or interface definitions by name.",
86+
"tags": [
87+
"java",
88+
"lsp",
89+
"code-navigation",
90+
"symbol-search"
91+
],
7892
"canBeReferencedInPrompt": true,
7993
"icon": "$(search)",
8094
"when": "config.vscode-java-dependency.enableLspTools && javaLSReady",
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
---
2-
description: REQUIRED for Java files. Provides compiler-accurate lsp_java_findSymbol and lsp_java_getFileStructure tools that replace grep_search, search_subagent, semantic_search, and file_search for Java symbol navigation.
2+
description: Use Java LSP tools for precise Java symbol navigation. Prefer lsp_java_findSymbol and lsp_java_getFileStructure over generic search only when locating Java classes, methods, fields, or file outlines.
33
applyTo: '**/*.java'
44
---
55

6-
For Java files, two compiler-accurate `lsp_java_*` tools are available and return structured results in ~50 tokens vs ~500+ from generic search:
6+
For Java symbol navigation, two compiler-accurate `lsp_java_*` tools are available and return structured results with smaller, easier-to-interpret payloads than generic search:
77

88
- `lsp_java_findSymbol(query)` — find class/method/field definitions by name across the workspace
99
- `lsp_java_getFileStructure(uri)` — get file outline (classes, methods, fields) with line ranges
1010

11-
These are deferred tools. Load them with `tool_search_tool_regex` using pattern `lsp_java_` before first use.
11+
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-
Prefer these over `grep_search`, `search_subagent`, `semantic_search`, `file_search`, or full-file `read_file` when navigating Java symbols. Always use `findSymbol` to discover file paths before passing them to `getFileStructure` — do not guess paths. Fall back to `grep_search` if a tool returns empty or errors.
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.
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.
Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
11
---
22
name: java-lsp-tools
3-
description: Compiler-accurate Java code navigation via the Java Language Server. Use lsp_java_findSymbol to locate symbols and lsp_java_getFileStructure to inspect file outlines. Prefer over grep_search for Java symbol navigation.
3+
description: Compiler-accurate Java symbol navigation via the Java Language Server. Use lsp_java_findSymbol for Java identifiers and lsp_java_getFileStructure for known Java files; prefer them over generic search only for symbol/file-outline navigation.
44
---
55

66
# Java LSP Tools
77

8-
Two compiler-accurate tools backed by the Java Language Server (jdtls). They return structured JSON with fewer tokens than `grep_search` or `read_file`.
8+
Two compiler-accurate tools backed by the Java Language Server (jdtls). They return structured JSON that is easier to interpret than generic search results for Java symbol navigation.
99

1010
## Tools
1111

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: `{ name, kind, location }` per result (~60 tokens)
16-
- **Use instead of** `grep_search` when looking for where a class/method is defined
15+
- Output: `{ results: [{ name, kind, container?, location, range }], total }` (~60 tokens); `range` is `L start-end`
16+
- **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
17+
- Do not repeat with the same or similar query after relevant results are returned
1718

1819
### `lsp_java_getFileStructure`
1920
Get hierarchical outline of a Java file (classes, methods, fields) with line ranges.
2021
- Input: `{ uri }` — workspace-relative path. Must be a known path from prior tool results or user input — do not guess
2122
- Output: symbol tree with `L start-end` ranges (~100 tokens)
22-
- **Use instead of** `read_file` full scan when you need to understand a file's layout
23+
- **Use before** `read_file` when you need to choose a precise line range in a known Java file
2324

2425
## When to Use
2526

2627
| Task | Use | Not |
2728
|---|---|---|
2829
| Find class/method/field definition | `lsp_java_findSymbol` | `grep_search` |
29-
| See file outline before reading | `lsp_java_getFileStructure` | `read_file` full file |
30+
| See known Java file outline before reading | `lsp_java_getFileStructure` | `read_file` full file |
3031
| Search non-Java files (xml, gradle) | `grep_search` | lsp tools |
3132
| Search string literals or comments | `grep_search` | lsp tools |
33+
| Explore broad concepts without identifiers | `semantic_search` or `search_subagent` | lsp tools |
3234

3335
## Typical Workflow
3436

3537
**findSymbol → getFileStructure → read_file (specific lines only)**
3638

39+
If `findSymbol` returns relevant symbols, move forward to `getFileStructure` or `read_file`; do not call `findSymbol` again with the same or similar identifier.
40+
3741
## Fallback
3842

39-
- `findSymbol` returns empty → retry with shorter keyword, then fall back to `grep_search`
40-
- Path error → use `findSymbol` to discover correct path first
43+
- `findSymbol` returns empty → it already retried internally with a normalized identifier, so do not re-issue the same search. If the result says indexing is in progress, retry once after a short pause; otherwise fall back to `grep_search`
44+
- Path error (`fileNotFound`) → use `findSymbol` to discover the correct path first; do not guess paths
4145
- Tool error / jdtls not ready → fall back to `grep_search` + `read_file`, don't retry more than once

src/constants.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ export namespace ExtensionName {
3636
export const APP_MODERNIZATION_FOR_JAVA = "vscjava.migrate-java-to-azure";
3737
// Java upgrade extension is merged into app modernization extension
3838
export const APP_MODERNIZATION_UPGRADE_FOR_JAVA = APP_MODERNIZATION_FOR_JAVA;
39-
export const APP_MODERNIZATION_EXTENSION_NAME = "GitHub Copilot app modernization";
39+
export const APP_MODERNIZATION_EXTENSION_NAME = "GitHub Copilot modernization";
4040
}
4141

4242
export namespace Upgrade {
4343
export const PACKAGE_ID_FOR_JAVA_RUNTIME = "java:*";
44+
/** Minimum version of the appmod extension that supports gotoAgentMode command */
45+
export const MIN_APPMOD_VERSION = "1.15.0";
46+
export const SOURCE_JAVA_UPGRADE = "vscode-java-dependency.java-upgrade";
47+
export const SOURCE_CVE = "vscode-java-dependency.cve";
4448
}
4549

4650
/**

0 commit comments

Comments
 (0)