Skip to content

Commit ae84ffc

Browse files
Fix @-class autocomplete breaking when a project is connected to the AI chat (#280)
* Initial plan * Fix code completion to suggest only classes from current project Co-authored-by: jGauravGupta <15934072+jGauravGupta@users.noreply.github.com> * Fix AIQueryCompletionProvider to show only project source classes, not full classpath Co-authored-by: jGauravGupta <15934072+jGauravGupta@users.noreply.github.com> * Always scan all open NetBeans projects for class completions Co-authored-by: jGauravGupta <15934072+jGauravGupta@users.noreply.github.com> * Fix stale class-name cache when open project set changes Co-authored-by: jGauravGupta <15934072+jGauravGupta@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jGauravGupta <15934072+jGauravGupta@users.noreply.github.com>
1 parent f77d3b8 commit ae84ffc

2 files changed

Lines changed: 69 additions & 66 deletions

File tree

src/main/java/io/github/jeddict/ai/classpath/JeddictQueryCompletionQuery.java

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616
package io.github.jeddict.ai.classpath;
1717

1818
import java.util.ArrayList;
19+
import java.util.Collections;
1920
import java.util.List;
21+
import java.util.Set;
2022
import java.util.function.Consumer;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.Stream;
2125
import javax.swing.text.BadLocationException;
2226
import javax.swing.text.Document;
2327
import org.netbeans.api.java.classpath.ClassPath;
2428
import org.netbeans.api.project.Project;
2529
import org.netbeans.api.project.ProjectUtils;
2630
import org.netbeans.api.project.ui.OpenProjects;
27-
import org.netbeans.modules.editor.NbEditorUtilities;
2831
import org.netbeans.spi.editor.completion.CompletionResultSet;
2932
import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
3033
import org.openide.filesystems.FileObject;
@@ -35,6 +38,7 @@ public class JeddictQueryCompletionQuery extends AsyncCompletionQuery {
3538
private static List<String> cachedClassNames = null;
3639
private static long lastScanTimestamp = 0;
3740
private static final long CACHE_EXPIRY_MS = 60 * 1000;
41+
private static Set<String> cachedProjectPaths = Collections.emptySet();
3842
public static final String JEDDICT_EDITOR_CALLBACK = "jeddict-editor-callback";
3943

4044
@Override
@@ -49,34 +53,22 @@ protected void query(CompletionResultSet resultSet, Document doc, int caretOffse
4953
resultSet.finish();
5054
return;
5155
}
52-
if (System.currentTimeMillis() - lastScanTimestamp > CACHE_EXPIRY_MS) {
53-
cachedClassNames = null;
54-
}
55-
if (cachedClassNames == null) {
56-
cachedClassNames = new ArrayList<>();
57-
FileObject file = NbEditorUtilities.getFileObject(doc);
58-
boolean found = false;
59-
if (file != null) {
60-
ClassPath classPath = ClassPath.getClassPath(file, ClassPath.COMPILE);
61-
if (classPath != null) {
62-
for (ClassPath.Entry entry : classPath.entries()) {
63-
FileObject root = entry.getRoot();
64-
if (root != null) {
65-
collectClassNames(root, "", cachedClassNames);
66-
found = true;
67-
}
68-
}
69-
}
70-
}
71-
72-
if (!found) {
73-
scanAllProjects(cachedClassNames);
56+
Project[] currentOpenProjects = OpenProjects.getDefault().getOpenProjects();
57+
Set<String> currentProjectPaths = openProjectPaths(currentOpenProjects);
58+
List<String> snapshot;
59+
synchronized (JeddictQueryCompletionQuery.class) {
60+
boolean ttlExpired = System.currentTimeMillis() - lastScanTimestamp > CACHE_EXPIRY_MS;
61+
boolean projectsChanged = !currentProjectPaths.equals(cachedProjectPaths);
62+
if (cachedClassNames == null || ttlExpired || projectsChanged) {
63+
cachedClassNames = new ArrayList<>();
64+
scanProjects(currentOpenProjects, cachedClassNames);
65+
cachedProjectPaths = currentProjectPaths;
66+
lastScanTimestamp = System.currentTimeMillis();
7467
}
75-
76-
lastScanTimestamp = System.currentTimeMillis();
68+
snapshot = cachedClassNames;
7769
}
7870

79-
for (String fqcn : cachedClassNames) {
71+
for (String fqcn : snapshot) {
8072
if (prefix.isEmpty() || fqcn.toLowerCase().contains(prefix.toLowerCase())) {
8173
resultSet.addItem(new JeddictQueryCompletionItem(fqcn, caretOffset, prefix, callback));
8274
}
@@ -97,24 +89,41 @@ private void collectClassNames(FileObject file, String pkg, List<String> classNa
9789
}
9890
}
9991

100-
private void scanAllProjects(List<String> classNames) {
101-
for (Project project : OpenProjects.getDefault().getOpenProjects()) {
102-
for (var sourceGroup : ProjectUtils.getSources(project).getSourceGroups("java")) {
103-
ClassPath classPath = ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.SOURCE);
104-
if (classPath != null) {
105-
for (ClassPath.Entry entry : classPath.entries()) {
106-
FileObject root = entry.getRoot();
107-
if (root != null) {
108-
for (FileObject child : root.getChildren()) {
109-
collectClassNames(child, "", classNames);
110-
}
111-
}
112-
}
92+
private boolean scanClassPathEntries(ClassPath classPath, List<String> classNames) {
93+
boolean found = false;
94+
for (ClassPath.Entry entry : classPath.entries()) {
95+
FileObject root = entry.getRoot();
96+
if (root != null) {
97+
for (FileObject child : root.getChildren()) {
98+
collectClassNames(child, "", classNames);
11399
}
100+
found = true;
101+
}
102+
}
103+
return found;
104+
}
105+
106+
private void scanProject(Project project, List<String> classNames) {
107+
for (var sourceGroup : ProjectUtils.getSources(project).getSourceGroups("java")) {
108+
ClassPath classPath = ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.SOURCE);
109+
if (classPath != null) {
110+
scanClassPathEntries(classPath, classNames);
114111
}
115112
}
116113
}
117114

115+
private void scanProjects(Project[] projects, List<String> classNames) {
116+
for (Project project : projects) {
117+
scanProject(project, classNames);
118+
}
119+
}
120+
121+
private static Set<String> openProjectPaths(Project[] projects) {
122+
return Stream.of(projects)
123+
.map(p -> p.getProjectDirectory().getPath())
124+
.collect(Collectors.toSet());
125+
}
126+
118127
private String extractPrefix(Document doc, int caretOffset) {
119128
try {
120129
int start = caretOffset - 1;

src/main/java/io/github/jeddict/ai/components/AIQueryCompletionProvider.java

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,7 @@ public int getAutoQueryTypes(JTextComponent component, String typedText) {
8989
private static final class Query extends AsyncCompletionQuery {
9090

9191
private static final Set<ClassIndex.SearchScope> SCOPES
92-
= EnumSet.of(
93-
ClassIndex.SearchScope.SOURCE,
94-
ClassIndex.SearchScope.DEPENDENCIES
95-
);
92+
= EnumSet.of(ClassIndex.SearchScope.SOURCE);
9693

9794
@Override
9895
protected void query(CompletionResultSet rs,
@@ -105,31 +102,33 @@ protected void query(CompletionResultSet rs,
105102
return;
106103
}
107104

108-
Project project = getOpenProject();
109-
if (project == null) {
110-
return;
111-
}
112-
113-
ClassIndex index = getClassIndex(project);
114-
if (index == null) {
105+
Project[] openProjects = OpenProjects.getDefault().getOpenProjects();
106+
if (openProjects.length == 0) {
115107
return;
116108
}
117109

118110
Set<String> results = new LinkedHashSet<>();
119111

120-
// Packages
121-
results.addAll(
122-
index.getPackageNames(prefix, true, SCOPES)
123-
);
112+
for (Project project : openProjects) {
113+
ClassIndex index = getClassIndex(project);
114+
if (index == null) {
115+
continue;
116+
}
117+
118+
// Packages
119+
results.addAll(
120+
index.getPackageNames(prefix, true, SCOPES)
121+
);
124122

125-
// Classes
126-
for (ElementHandle<TypeElement> h
127-
: index.getDeclaredTypes(
128-
prefix,
129-
ClassIndex.NameKind.PREFIX,
130-
SCOPES)) {
123+
// Classes
124+
for (ElementHandle<TypeElement> h
125+
: index.getDeclaredTypes(
126+
prefix,
127+
ClassIndex.NameKind.PREFIX,
128+
SCOPES)) {
131129

132-
results.add(h.getQualifiedName());
130+
results.add(h.getQualifiedName());
131+
}
133132
}
134133

135134
for (String s : results) {
@@ -162,11 +161,6 @@ private static String getPrefix(Document doc, int caretOffset) {
162161
}
163162
}
164163

165-
private static Project getOpenProject() {
166-
Project[] open = OpenProjects.getDefault().getOpenProjects();
167-
return open.length > 0 ? open[0] : null;
168-
}
169-
170164
private static ClassIndex getClassIndex(Project project) {
171165
SourceGroup[] groups = ProjectUtils.getSources(project)
172166
.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);

0 commit comments

Comments
 (0)