Skip to content

Commit f98bf51

Browse files
committed
Speed up operations immediately after file is opened
1 parent 546c6ef commit f98bf51

3 files changed

Lines changed: 112 additions & 88 deletions

File tree

‎src/main/java/org/javacs/JavaLanguageServer.java‎

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
import com.sun.source.doctree.DocCommentTree;
99
import com.sun.source.doctree.DocTree;
1010
import com.sun.source.doctree.ParamTree;
11+
import com.sun.source.tree.ClassTree;
12+
import com.sun.source.tree.CompilationUnitTree;
1113
import com.sun.source.tree.MethodTree;
14+
import com.sun.source.tree.Tree;
15+
import com.sun.source.tree.VariableTree;
16+
import com.sun.source.util.TreePath;
1217
import java.io.BufferedReader;
1318
import java.io.IOException;
1419
import java.io.StringReader;
@@ -228,7 +233,7 @@ public JavaLanguageServer(LanguageClient client) {
228233
public List<SymbolInformation> workspaceSymbols(WorkspaceSymbolParams params) {
229234
var list = new ArrayList<SymbolInformation>();
230235
for (var s : compiler.findSymbols(params.query, 50)) {
231-
var i = Parser.asSymbolInformation(s);
236+
var i = asSymbolInformation(s);
232237
list.add(i);
233238
}
234239
return list;
@@ -742,35 +747,112 @@ public List<Location> findReferences(ReferenceParams position) {
742747
return result;
743748
}
744749

750+
private ParseFile cacheParse;
751+
private URI cacheParseFile = URI.create("file:///NONE");;
752+
private int cacheParseVersion = -1;
753+
754+
private void updateCachedParse(URI file) {
755+
if (file.equals(cacheParseFile) && contents(file).version == cacheParseVersion) return;
756+
LOG.info(String.format("Updating cached parse file to %s", file));
757+
var contents = contents(file);
758+
cacheParse = compiler.parseFile(file, contents.content);
759+
cacheParseFile = file;
760+
cacheParseVersion = contents.version;
761+
}
762+
745763
@Override
746764
public List<SymbolInformation> documentSymbol(DocumentSymbolParams params) {
747765
var uri = params.textDocument.uri;
748766
if (!isJavaFile(uri)) return List.of();
749-
var content = contents(uri).content;
750-
var result =
751-
Parser.documentSymbols(Paths.get(uri), content)
752-
.stream()
753-
.map(Parser::asSymbolInformation)
754-
.collect(Collectors.toList());
755-
return result;
767+
updateCachedParse(uri);
768+
var paths = cacheParse.documentSymbols();
769+
var infos = new ArrayList<SymbolInformation>();
770+
for (var p : paths) {
771+
infos.add(asSymbolInformation(p));
772+
}
773+
return infos;
774+
}
775+
776+
static SymbolInformation asSymbolInformation(TreePath path) {
777+
var i = new SymbolInformation();
778+
var t = path.getLeaf();
779+
i.kind = asSymbolKind(t.getKind());
780+
i.name = symbolName(t);
781+
i.containerName = containerName(path);
782+
i.location = Parser.location(path);
783+
return i;
784+
}
785+
786+
private static Integer asSymbolKind(Tree.Kind k) {
787+
switch (k) {
788+
case ANNOTATION_TYPE:
789+
case CLASS:
790+
return SymbolKind.Class;
791+
case ENUM:
792+
return SymbolKind.Enum;
793+
case INTERFACE:
794+
return SymbolKind.Interface;
795+
case METHOD:
796+
return SymbolKind.Method;
797+
case TYPE_PARAMETER:
798+
return SymbolKind.TypeParameter;
799+
case VARIABLE:
800+
// This method is used for symbol-search functionality,
801+
// where we only return fields, not local variables
802+
return SymbolKind.Field;
803+
default:
804+
return null;
805+
}
806+
}
807+
808+
private static String containerName(TreePath path) {
809+
var parent = path.getParentPath();
810+
while (parent != null) {
811+
var t = parent.getLeaf();
812+
if (t instanceof ClassTree) {
813+
var c = (ClassTree) t;
814+
return c.getSimpleName().toString();
815+
} else if (t instanceof CompilationUnitTree) {
816+
var c = (CompilationUnitTree) t;
817+
return Objects.toString(c.getPackageName(), "");
818+
} else {
819+
parent = parent.getParentPath();
820+
}
821+
}
822+
return null;
823+
}
824+
825+
private static String symbolName(Tree t) {
826+
if (t instanceof ClassTree) {
827+
var c = (ClassTree) t;
828+
return c.getSimpleName().toString();
829+
} else if (t instanceof MethodTree) {
830+
var m = (MethodTree) t;
831+
return m.getName().toString();
832+
} else if (t instanceof VariableTree) {
833+
var v = (VariableTree) t;
834+
return v.getName().toString();
835+
} else {
836+
LOG.warning("Don't know how to create SymbolInformation from " + t);
837+
return "???";
838+
}
756839
}
757840

758841
@Override
759842
public List<CodeLens> codeLens(CodeLensParams params) {
760843
// TODO just create a blank code lens on every method, then resolve it async
761844
var uri = params.textDocument.uri;
762845
if (!isJavaFile(uri)) return List.of();
763-
var content = contents(uri).content;
764-
var parse = compiler.parseFile(uri, content);
765-
var declarations = parse.declarations();
846+
updateCachedParse(uri);
847+
var declarations = cacheParse.declarations();
766848
var result = new ArrayList<CodeLens>();
767849
for (var d : declarations) {
768-
var range = parse.range(d);
850+
var range = cacheParse.range(d);
769851
if (!range.isPresent()) continue;
770852
var className = JavaCompilerService.className(d);
771853
var memberName = JavaCompilerService.memberName(d);
772854
// If test class or method, add "Run Test" code lens
773-
if (parse.isTestClass(d)) {
855+
if (cacheParse.isTestClass(d)) {
774856
var arguments = new JsonArray();
775857
arguments.add(uri.toString());
776858
arguments.add(className);
@@ -780,7 +862,7 @@ public List<CodeLens> codeLens(CodeLensParams params) {
780862
result.add(lens);
781863
// TODO run all tests in file
782864
// TODO run all tests in package
783-
} else if (parse.isTestMethod(d)) {
865+
} else if (cacheParse.isTestMethod(d)) {
784866
var arguments = new JsonArray();
785867
arguments.add(uri.toString());
786868
arguments.add(className);
@@ -823,6 +905,11 @@ private void updateCacheCountReferences(URI current) {
823905

824906
@Override
825907
public CodeLens resolveCodeLens(CodeLens unresolved) {
908+
// TODO This is pretty klugey, should happen asynchronously after CodeLenses are shown
909+
if (!recentlyOpened.isEmpty()) {
910+
lint(recentlyOpened);
911+
recentlyOpened.clear();
912+
}
826913
// Unpack data
827914
var data = unresolved.data;
828915
var command = data.get(0).getAsString();
@@ -916,14 +1003,17 @@ private boolean isJavaFile(URI uri) {
9161003
return uri.getScheme().equals("file") && uri.getPath().endsWith(".java");
9171004
}
9181005

1006+
private List<URI> recentlyOpened = new ArrayList<>();
1007+
9191008
@Override
9201009
public void didOpenTextDocument(DidOpenTextDocumentParams params) {
9211010
var document = params.textDocument;
9221011
var uri = document.uri;
923-
if (isJavaFile(uri)) {
924-
activeDocuments.put(uri, new VersionedContent(document.text, document.version));
925-
lint(Collections.singleton(uri));
926-
}
1012+
if (!isJavaFile(uri)) return;
1013+
LOG.info(String.format("Opened %s", Parser.fileName(uri)));
1014+
activeDocuments.put(uri, new VersionedContent(document.text, document.version));
1015+
recentlyOpened.add(uri);
1016+
updateCachedParse(uri); // So that subsequent documentSymbol and codeLens requests will be faster
9271017
}
9281018

9291019
@Override

‎src/main/java/org/javacs/ParseFile.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,5 +278,9 @@ static Optional<Range> range(JavacTask task, String contents, TreePath path) {
278278
return Optional.of(range);
279279
}
280280

281+
public List<TreePath> documentSymbols() {
282+
return Parser.findSymbolsMatching(root, "");
283+
}
284+
281285
private static final Logger LOG = Logger.getLogger("main");
282286
}

‎src/main/java/org/javacs/Parser.java‎

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,6 @@ List<TreePath> run() {
218218
return new Find().run();
219219
}
220220

221-
static List<TreePath> documentSymbols(Path java, String content) {
222-
var parse = parse(new StringFileObject(content, java.toUri()));
223-
return findSymbolsMatching(parse, "");
224-
}
225-
226221
static Location location(TreePath p) {
227222
// This is very questionable, will this Trees object actually work?
228223
var task = parseTask(p.getCompilationUnit().getSourceFile());
@@ -237,71 +232,6 @@ static Location location(TreePath p) {
237232
return new Location(dUri, new Range(new Position(startLine, startCol), new Position(endLine, endCol)));
238233
}
239234

240-
private static Integer asSymbolKind(Tree.Kind k) {
241-
switch (k) {
242-
case ANNOTATION_TYPE:
243-
case CLASS:
244-
return SymbolKind.Class;
245-
case ENUM:
246-
return SymbolKind.Enum;
247-
case INTERFACE:
248-
return SymbolKind.Interface;
249-
case METHOD:
250-
return SymbolKind.Method;
251-
case TYPE_PARAMETER:
252-
return SymbolKind.TypeParameter;
253-
case VARIABLE:
254-
// This method is used for symbol-search functionality,
255-
// where we only return fields, not local variables
256-
return SymbolKind.Field;
257-
default:
258-
return null;
259-
}
260-
}
261-
262-
private static String containerName(TreePath path) {
263-
var parent = path.getParentPath();
264-
while (parent != null) {
265-
var t = parent.getLeaf();
266-
if (t instanceof ClassTree) {
267-
var c = (ClassTree) t;
268-
return c.getSimpleName().toString();
269-
} else if (t instanceof CompilationUnitTree) {
270-
var c = (CompilationUnitTree) t;
271-
return Objects.toString(c.getPackageName(), "");
272-
} else {
273-
parent = parent.getParentPath();
274-
}
275-
}
276-
return null;
277-
}
278-
279-
private static String symbolName(Tree t) {
280-
if (t instanceof ClassTree) {
281-
var c = (ClassTree) t;
282-
return c.getSimpleName().toString();
283-
} else if (t instanceof MethodTree) {
284-
var m = (MethodTree) t;
285-
return m.getName().toString();
286-
} else if (t instanceof VariableTree) {
287-
var v = (VariableTree) t;
288-
return v.getName().toString();
289-
} else {
290-
LOG.warning("Don't know how to create SymbolInformation from " + t);
291-
return "???";
292-
}
293-
}
294-
295-
static SymbolInformation asSymbolInformation(TreePath path) {
296-
var i = new SymbolInformation();
297-
var t = path.getLeaf();
298-
i.kind = asSymbolKind(t.getKind());
299-
i.name = symbolName(t);
300-
i.containerName = containerName(path);
301-
i.location = Parser.location(path);
302-
return i;
303-
}
304-
305235
/** Find all already-imported symbols in all .java files in workspace */
306236
static ExistingImports existingImports(Collection<Path> allJavaFiles) {
307237
var classes = new HashSet<String>();

0 commit comments

Comments
 (0)