Skip to content

Commit 8ef1af7

Browse files
authored
feat: lsp tools exp (#972)
* feat: lmt for lsp * fix: update * feat: update * fix: update * fix: adjust the prompt to load lsp java tools to replace search subagent * perf: update when * feat: lsp tools and chat instrument * feat: update the feature in exp * docs: remove unusless id * feat: update * docs: update * fix: update the code to fix the comments * fix: update * fix: chat instrument
1 parent 3249b00 commit 8ef1af7

File tree

16 files changed

+1122
-15
lines changed

16 files changed

+1122
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ You can tell that the glob pattern is supported. And here's more - you can incl
5757

5858
## Requirements
5959

60-
- VS Code (version 1.83.1+)
60+
- VS Code (version 1.95.0+)
6161
- [Language Support for Java by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java)
6262

6363

jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<command id="java.project.checkImportStatus" />
1313
<command id="java.project.getImportClassContent" />
1414
<command id="java.project.getDependencies" />
15+
<command id="java.project.getFileImports" />
1516
</delegateCommandHandler>
1617
</extension>
1718
<extension
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2018 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.jdtls.ext.core;
13+
14+
import java.util.ArrayList;
15+
import java.util.HashSet;
16+
import java.util.List;
17+
import java.util.Set;
18+
19+
import org.eclipse.core.runtime.IProgressMonitor;
20+
import org.eclipse.jdt.core.Flags;
21+
import org.eclipse.jdt.core.ICompilationUnit;
22+
import org.eclipse.jdt.core.IImportDeclaration;
23+
import org.eclipse.jdt.core.IJavaProject;
24+
import org.eclipse.jdt.core.IPackageFragmentRoot;
25+
import org.eclipse.jdt.ls.core.internal.JDTUtils;
26+
27+
import com.microsoft.jdtls.ext.core.model.FileImportsResult;
28+
import com.microsoft.jdtls.ext.core.model.FileImportsResult.ImportEntry;
29+
import com.microsoft.jdtls.ext.core.model.FileImportsResult.StaticImportEntry;
30+
31+
/**
32+
* Lightweight command handler for AI context tools.
33+
* All methods in this class are designed to be non-blocking and fast (< 10ms).
34+
* They only read AST-level information and do NOT trigger classpath resolution,
35+
* type resolution, or any expensive JDT operations.
36+
*/
37+
public class AiContextCommand {
38+
39+
// Well-known JDK package prefixes
40+
private static final Set<String> JDK_PREFIXES = new HashSet<>();
41+
static {
42+
JDK_PREFIXES.add("java.");
43+
JDK_PREFIXES.add("javax.");
44+
JDK_PREFIXES.add("jdk.");
45+
JDK_PREFIXES.add("sun.");
46+
JDK_PREFIXES.add("com.sun.");
47+
JDK_PREFIXES.add("org.xml.");
48+
JDK_PREFIXES.add("org.w3c.");
49+
JDK_PREFIXES.add("jakarta."); // Jakarta EE (post Java EE)
50+
}
51+
52+
/**
53+
* Get the classified import list of a Java file.
54+
* This is a lightweight AST-only operation — it reads import declarations
55+
* without doing any type resolution (findType) or classpath resolution.
56+
*
57+
* Typical response time: < 5ms
58+
*
59+
* @param arguments List containing the file URI as the first element
60+
* @param monitor Progress monitor for cancellation support
61+
* @return FileImportsResult with classified imports
62+
*/
63+
public static FileImportsResult getFileImports(List<Object> arguments, IProgressMonitor monitor) {
64+
FileImportsResult result = new FileImportsResult();
65+
result.imports = new ArrayList<>();
66+
result.staticImports = new ArrayList<>();
67+
68+
if (arguments == null || arguments.isEmpty()) {
69+
result.error = "No arguments provided";
70+
return result;
71+
}
72+
73+
try {
74+
String fileUri = (String) arguments.get(0);
75+
if (fileUri == null || fileUri.trim().isEmpty()) {
76+
result.error = "Invalid file URI";
77+
return result;
78+
}
79+
80+
// Resolve compilation unit from URI — this is fast, just a model lookup
81+
java.net.URI uri = JDTUtils.toURI(fileUri);
82+
ICompilationUnit compilationUnit = JDTUtils.resolveCompilationUnit(uri);
83+
84+
if (compilationUnit == null || !compilationUnit.exists()) {
85+
result.error = "File not found or not a Java file: " + fileUri;
86+
return result;
87+
}
88+
89+
// Get project-relative file path (strip the leading project segment
90+
// from the Eclipse workspace-relative path, e.g. "/my-project/src/Foo.java" → "src/Foo.java")
91+
IJavaProject javaProject = compilationUnit.getJavaProject();
92+
result.file = compilationUnit.getPath().removeFirstSegments(1).toString();
93+
94+
// Collect project source package names for classification
95+
Set<String> projectPackages = collectProjectPackages(javaProject);
96+
97+
// Read import declarations — pure AST operation, no type resolution
98+
IImportDeclaration[] imports = compilationUnit.getImports();
99+
if (imports == null || imports.length == 0) {
100+
return result; // No imports, return empty (not an error)
101+
}
102+
103+
for (IImportDeclaration imp : imports) {
104+
if (monitor.isCanceled()) {
105+
break;
106+
}
107+
108+
String name = imp.getElementName();
109+
boolean isStatic = Flags.isStatic(imp.getFlags());
110+
boolean isOnDemand = name.endsWith(".*");
111+
112+
if (isStatic) {
113+
StaticImportEntry entry = new StaticImportEntry();
114+
entry.name = name;
115+
entry.memberKind = "unknown"; // Would need findType to know — skip
116+
entry.source = classifyByPackageName(name, projectPackages);
117+
result.staticImports.add(entry);
118+
} else {
119+
ImportEntry entry = new ImportEntry();
120+
entry.name = name;
121+
entry.kind = "unknown"; // Would need findType to know — skip
122+
entry.source = classifyByPackageName(name, projectPackages);
123+
entry.artifact = null; // Would need classpath attributes — skip for now
124+
entry.isOnDemand = isOnDemand;
125+
result.imports.add(entry);
126+
}
127+
}
128+
129+
return result;
130+
131+
} catch (Exception e) {
132+
JdtlsExtActivator.logException("Error in getFileImports", e);
133+
result.error = "Exception: " + e.getMessage();
134+
return result;
135+
}
136+
}
137+
138+
/**
139+
* Classify an import by its package name prefix.
140+
* This is a heuristic — no type resolution involved.
141+
*
142+
* @param qualifiedName the fully qualified name of the import
143+
* @param projectPackages set of package names found in the project's source roots
144+
* @return "jdk", "project", or "external"
145+
*/
146+
private static String classifyByPackageName(String qualifiedName, Set<String> projectPackages) {
147+
// Check JDK
148+
for (String prefix : JDK_PREFIXES) {
149+
if (qualifiedName.startsWith(prefix)) {
150+
return "jdk";
151+
}
152+
}
153+
154+
// Check project packages
155+
String packageName = getPackageName(qualifiedName);
156+
if (packageName != null && projectPackages.contains(packageName)) {
157+
return "project";
158+
}
159+
160+
// Check if any project package is a prefix of this import
161+
for (String projPkg : projectPackages) {
162+
if (qualifiedName.startsWith(projPkg + ".")) {
163+
return "project";
164+
}
165+
}
166+
167+
return "external";
168+
}
169+
170+
/**
171+
* Get the package name from a fully qualified name.
172+
* e.g., "com.example.model.Order" → "com.example.model"
173+
* "com.example.model.*" → "com.example.model"
174+
*/
175+
private static String getPackageName(String qualifiedName) {
176+
if (qualifiedName == null) {
177+
return null;
178+
}
179+
// Handle wildcard imports
180+
if (qualifiedName.endsWith(".*")) {
181+
return qualifiedName.substring(0, qualifiedName.length() - 2);
182+
}
183+
int lastDot = qualifiedName.lastIndexOf('.');
184+
if (lastDot > 0) {
185+
return qualifiedName.substring(0, lastDot);
186+
}
187+
return null;
188+
}
189+
190+
/**
191+
* Collect all package names that exist in the project's source roots.
192+
* This uses getPackageFragmentRoots(K_SOURCE) which is fast — it reads
193+
* the project model, not the filesystem.
194+
*/
195+
private static Set<String> collectProjectPackages(IJavaProject javaProject) {
196+
Set<String> packages = new HashSet<>();
197+
if (javaProject == null) {
198+
return packages;
199+
}
200+
201+
try {
202+
IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
203+
for (IPackageFragmentRoot root : roots) {
204+
if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
205+
org.eclipse.jdt.core.IJavaElement[] children = root.getChildren();
206+
for (org.eclipse.jdt.core.IJavaElement child : children) {
207+
if (child instanceof org.eclipse.jdt.core.IPackageFragment) {
208+
String pkgName = child.getElementName();
209+
if (pkgName != null && !pkgName.isEmpty()) {
210+
packages.add(pkgName);
211+
}
212+
}
213+
}
214+
}
215+
}
216+
} catch (Exception e) {
217+
// Non-critical — fall back to treating everything as external
218+
JdtlsExtActivator.logException("Error collecting project packages", e);
219+
}
220+
221+
return packages;
222+
}
223+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
4141
return ProjectCommand.getImportClassContent(arguments, monitor);
4242
case "java.project.getDependencies":
4343
return ProjectCommand.getProjectDependencies(arguments, monitor);
44+
case "java.project.getFileImports":
45+
return AiContextCommand.getFileImports(arguments, monitor);
4446
default:
4547
break;
4648
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.microsoft.jdtls.ext.core.model;
2+
3+
import java.util.List;
4+
5+
/**
6+
* L1: Detailed class information.
7+
* AI calls this for specific classes it needs to understand, not for all imports.
8+
*/
9+
public class ClassDetailResult {
10+
11+
public String qualifiedName; // "com.example.model.Order"
12+
public String kind; // "class" | "interface" | "enum" | "annotation"
13+
public String uri; // file URI (for project source) or jar URI
14+
public String source; // "project" | "external" | "jdk"
15+
public String artifact; // GAV for external: "com.google.code.gson:gson:2.10.1"
16+
17+
public String signature; // "public class Order implements Serializable"
18+
public String superClass; // "java.lang.Object" (null if Object)
19+
public List<String> interfaces; // ["java.io.Serializable"]
20+
public List<String> annotations; // ["@Entity", "@Table(name = \"orders\")"]
21+
22+
public String javadocSummary; // First sentence only, null if none
23+
24+
public List<String> constructors; // ["Order()", "Order(String orderId, Customer customer)"]
25+
public List<String> methods; // ["String getOrderId()", "void setStatus(OrderStatus)"]
26+
public List<String> fields; // ["private String orderId", "private List<OrderItem> items"]
27+
28+
public int totalMethodCount; // actual total (methods list may be truncated)
29+
public int totalFieldCount; // actual total
30+
31+
public String error; // null if success
32+
33+
/**
34+
* Builder-style static factories for common cases
35+
*/
36+
public static ClassDetailResult notFound(String qualifiedName) {
37+
ClassDetailResult r = new ClassDetailResult();
38+
r.qualifiedName = qualifiedName;
39+
r.error = "Type not found: " + qualifiedName;
40+
return r;
41+
}
42+
43+
public static ClassDetailResult error(String qualifiedName, String message) {
44+
ClassDetailResult r = new ClassDetailResult();
45+
r.qualifiedName = qualifiedName;
46+
r.error = message;
47+
return r;
48+
}
49+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.microsoft.jdtls.ext.core.model;
2+
3+
import java.util.List;
4+
5+
/**
6+
* L1: Detailed dependency information with query filtering.
7+
* AI calls this when it needs to investigate specific dependencies.
8+
*/
9+
public class DependencyDetailsResult {
10+
11+
public List<DependencyEntry> dependencies;
12+
public String error; // null if success
13+
14+
public static class DependencyEntry {
15+
public String groupId; // "com.google.code.gson"
16+
public String artifactId; // "gson"
17+
public String version; // "2.10.1"
18+
public String scope; // "compile" | "test" | "runtime" | "provided" | "system"
19+
public boolean isDirect; // true = declared in pom.xml/build.gradle
20+
public String broughtBy; // for transitive: "com.google.guava:guava:32.1.3-jre"
21+
public String jarFileName; // "gson-2.10.1.jar"
22+
23+
public DependencyEntry() {}
24+
25+
public DependencyEntry(String groupId, String artifactId, String version,
26+
String scope, boolean isDirect, String broughtBy,
27+
String jarFileName) {
28+
this.groupId = groupId;
29+
this.artifactId = artifactId;
30+
this.version = version;
31+
this.scope = scope;
32+
this.isDirect = isDirect;
33+
this.broughtBy = broughtBy;
34+
this.jarFileName = jarFileName;
35+
}
36+
}
37+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.microsoft.jdtls.ext.core.model;
2+
3+
import java.util.List;
4+
5+
/**
6+
* L0: Import list for a Java file.
7+
* Returns classified imports without expanding class details.
8+
*/
9+
public class FileImportsResult {
10+
11+
public String file; // project-relative file path (e.g. "src/main/java/com/example/Foo.java")
12+
public List<ImportEntry> imports;
13+
public List<StaticImportEntry> staticImports;
14+
public String error; // null if success
15+
16+
public static class ImportEntry {
17+
public String name; // fully qualified name: "com.example.model.Order"
18+
public String kind; // "class" | "interface" | "enum" | "annotation" | "unknown"
19+
public String source; // "project" | "external" | "jdk"
20+
public String artifact; // only for "external": "spring-context", null for others
21+
public boolean isOnDemand; // true for wildcard imports (e.g. "import com.example.model.*")
22+
23+
public ImportEntry() {}
24+
25+
public ImportEntry(String name, String kind, String source, String artifact) {
26+
this.name = name;
27+
this.kind = kind;
28+
this.source = source;
29+
this.artifact = artifact;
30+
}
31+
}
32+
33+
public static class StaticImportEntry {
34+
public String name; // "org.junit.Assert.assertEquals"
35+
public String memberKind; // "method" | "field" | "unknown"
36+
public String source; // "project" | "external" | "jdk"
37+
38+
public StaticImportEntry() {}
39+
40+
public StaticImportEntry(String name, String memberKind, String source) {
41+
this.name = name;
42+
this.memberKind = memberKind;
43+
this.source = source;
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)