summaryrefslogtreecommitdiff
path: root/src/main/java/org/javacs/JavaLanguageServer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/javacs/JavaLanguageServer.java')
-rw-r--r--src/main/java/org/javacs/JavaLanguageServer.java197
1 files changed, 26 insertions, 171 deletions
diff --git a/src/main/java/org/javacs/JavaLanguageServer.java b/src/main/java/org/javacs/JavaLanguageServer.java
index 3e26760..8731fbb 100644
--- a/src/main/java/org/javacs/JavaLanguageServer.java
+++ b/src/main/java/org/javacs/JavaLanguageServer.java
@@ -15,7 +15,6 @@ import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.io.BufferedReader;
-import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
@@ -30,7 +29,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -39,7 +37,6 @@ import java.util.Set;
import java.util.StringJoiner;
import java.util.UUID;
import java.util.logging.Logger;
-import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
@@ -797,34 +794,6 @@ class JavaLanguageServer extends LanguageServer {
return Optional.of(result);
}
- class Progress implements ReportProgress, AutoCloseable {
- @Override
- public void start(String message) {
- javaStartProgress(new JavaStartProgressParams(message));
- }
-
- @Override
- public void progress(String message, int n, int total) {
- if (n == 0) {
- javaReportProgress(new JavaReportProgressParams(message));
- } else {
- var increment = percent(n, total) > percent(n - 1, total) ? 1 : 0;
- javaReportProgress(new JavaReportProgressParams(message, increment));
- }
- }
-
- private int percent(int n, int d) {
- double nD = n, dD = d;
- double ratio = nD / dD;
- return (int) (ratio * 100);
- }
-
- @Override
- public void close() {
- javaEndProgress();
- }
- }
-
@Override
public Optional<List<Location>> findReferences(ReferenceParams position) {
var toUri = position.textDocument.uri;
@@ -851,7 +820,6 @@ class JavaLanguageServer extends LanguageServer {
var toElAgain = batch.element(toUri, toLine, toColumn).get();
// Find all references to toElAgain
- // TODO this should references to supers of toEl
var fromTreePaths = batch.references(toElAgain);
if (!fromTreePaths.isPresent()) return Optional.empty();
var result = new ArrayList<Location>();
@@ -1018,6 +986,7 @@ class JavaLanguageServer extends LanguageServer {
var line = start.line;
var character = start.character;
var data = new JsonArray();
+ // TODO would textDocument/references do the same thing?
data.add("java.command.findReferences");
data.add(uri.toString());
data.add(line);
@@ -1028,135 +997,6 @@ class JavaLanguageServer extends LanguageServer {
return result;
}
- private Map<Ptr, Integer> cacheCountReferences = Collections.emptyMap();
- private URI cacheCountReferencesFile = URI.create("file:///NONE");
- private int cacheCountReferencesVersion = -1;
-
- private void updateCacheCountReferences(URI current) {
- if (cacheCountReferencesFile.equals(current) && cacheCountReferencesVersion == contents(current).version)
- return;
- LOG.info(String.format("Update cached reference count to %s...", current));
- var contents = contents(current);
- try (var progress = new Progress()) {
- cacheCountReferences = countReferences(current, contents.content, progress);
- }
- cacheCountReferencesFile = current;
- cacheCountReferencesVersion = contents.version;
- }
-
- private Map<URI, Index> index = new HashMap<>();
-
- private void updateIndex(Collection<URI> possible, ReportProgress progress) {
- LOG.info(String.format("Check %d files for modifications compared to index...", possible.size()));
-
- // signatureMatches tests if edits to the current file invalidate an index
- var signatureMatches = hoverCache.signatureMatches();
- // Figure out all files that have been changed, or contained errors at the time they were indexed
- var outOfDate = new ArrayList<URI>();
- var hasError = new ArrayList<URI>();
- var wrongSig = new ArrayList<URI>();
- for (var p : possible) {
- if (!index.containsKey(p)) {
- outOfDate.add(p);
- continue;
- }
- var i = index.get(p);
- var modified = Instant.ofEpochMilli(new File(p).lastModified());
- // TODO can modified rewind when you checkout a branch?
- if (modified.isAfter(i.modified)) outOfDate.add(p);
- else if (i.containsError) hasError.add(p);
- else if (!signatureMatches.test(i.refs)) wrongSig.add(p);
- }
- if (outOfDate.size() > 0) LOG.info(String.format("... %d files are out-of-date", outOfDate.size()));
- if (hasError.size() > 0) LOG.info(String.format("... %d files contain errors", hasError.size()));
- if (wrongSig.size() > 0)
- LOG.info(String.format("... %d files refer to methods that have changed", wrongSig.size()));
-
- // If there's nothing to update, return
- var needsUpdate = new ArrayList<URI>();
- needsUpdate.addAll(outOfDate);
- needsUpdate.addAll(hasError);
- needsUpdate.addAll(wrongSig);
- if (needsUpdate.isEmpty()) return;
-
- // If there's more than 1 file, report progress
- if (needsUpdate.size() > 10) { // TODO this could probably be tuned to be based on bytes of code
- progress.start(String.format("Index %d files", needsUpdate.size()));
- } else {
- progress = ReportProgress.EMPTY;
- }
-
- // Compile in a batch and update the index
- var counts = compiler.compileBatch(needsUpdate, progress).countReferences();
- index.putAll(counts);
- }
-
- public Map<Ptr, Integer> countReferences(URI file, String contents, ReportProgress progress) {
- updateHoverCache(file, contents(file).content);
-
- // List all files that import file
- var toPackage = Objects.toString(hoverCache.root.getPackageName(), "");
- var toClasses = hoverCache.allClassNames();
- var possible = potentialReferencesToClasses(toPackage, toClasses);
- if (possible.isEmpty()) {
- LOG.info("No potential references to " + file);
- return Map.of();
- }
- // Reindex only files that are out-of-date
- updateIndex(possible, progress);
- // Assemble results
- var result = new HashMap<Ptr, Integer>();
- for (var p : possible) {
- if (!index.containsKey(p)) {
- LOG.warning("Did not index " + p);
- continue;
- }
- var i = index.get(p);
- for (var r : i.refs) {
- var count = result.getOrDefault(r, 0);
- result.put(r, count + 1);
- }
- }
- return result;
- }
-
- // TODO should probably cache this
- private Collection<URI> potentialReferencesToClasses(String toPackage, List<String> toClasses) {
- // Filter for files that import toPackage.toClass
- var result = new LinkedHashSet<URI>();
- for (var file : sourcePath.allJavaFiles()) {
- if (importsAnyClass(toPackage, toClasses, file)) {
- result.add(file.toUri());
- }
- }
- return result;
- }
-
- private static boolean importsAnyClass(String toPackage, List<String> toClasses, Path file) {
- if (toPackage.isEmpty()) return true; // If package is empty, everyone imports it
- var toClass = toClasses.stream().collect(Collectors.joining("|"));
- var samePackage = Pattern.compile("^package +" + toPackage + ";");
- var importClass = Pattern.compile("^import +" + toPackage + "\\.(" + toClass + ");");
- var importStar = Pattern.compile("^import +" + toPackage + "\\.\\*;");
- var importStatic = Pattern.compile("^import +static +" + toPackage + "\\.");
- var startOfClass = Pattern.compile("^[\\w ]*class +\\w+");
- try (var read = Files.newBufferedReader(file)) {
- while (true) {
- var line = read.readLine();
- if (line == null) break;
- if (startOfClass.matcher(line).find()) break;
- if (samePackage.matcher(line).find()) return true;
- if (importClass.matcher(line).find()) return true;
- if (importStar.matcher(line).find()) return true;
- if (importStatic.matcher(line).find()) return true;
- if (importClass.matcher(line).find()) return true;
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return false;
- }
-
@Override
public CodeLens resolveCodeLens(CodeLens unresolved) {
// TODO This is pretty klugey, should happen asynchronously after CodeLenses are shown
@@ -1183,19 +1023,34 @@ class JavaLanguageServer extends LanguageServer {
return unresolved;
}
- private String countReferencesTitle(URI uri, int line, int character) {
- updateHoverCache(uri, contents(uri).content);
- var el = hoverCache.element(line, character);
- if (!el.isPresent()) {
- LOG.warning(String.format("No element to resolve code lens at %s(%d,%d)", uri.getPath(), line, character));
+ private String countReferencesTitle(URI toUri, int toLine, int toColumn) {
+ var toContent = contents(toUri).content;
+
+ // Compile from-file and identify element under cursor
+ LOG.warning(String.format("Looking for references to %s(%d,%d)...", toUri.getPath(), toLine, toColumn));
+ updateHoverCache(toUri, toContent);
+ var toEl = hoverCache.element(toLine, toColumn);
+ if (!toEl.isPresent()) {
+ LOG.warning("...no element at code lens");
return "? references";
}
- var ptr = new Ptr(el.get());
- // Update cache if necessary
- updateCacheCountReferences(uri);
- // Read reference count from cache
- var count = cacheCountReferences.getOrDefault(ptr, 0);
+ // Compile all files that *might* contain references to toEl
+ // TODO if this gets too big, just show "Many references"
+ var fromFiles = compiler.potentialReferences(toEl.get());
+ fromFiles.add(toUri);
+ // TODO instead of pruning words (2x speedup at best),
+ // compile the whole file and save all references to all declarations in all open files.
+ // As soon as any open file is edited, discard this cache.
+ // This will re-use work across code lenses.
+ var batch = compiler.compileBatch(pruneWord(fromFiles, toEl.get()));
+
+ // Find toEl again, so that we have an Element from the current batch
+ var toElAgain = batch.element(toUri, toLine, toColumn).get();
+
+ // Find all references to toElAgain
+ var fromTreePaths = batch.references(toElAgain);
+ var count = fromTreePaths.orElse(List.of()).size();
if (count == 1) return "1 reference";
return String.format("%d references", count);
}