summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/javacs/CustomLanguageClient.java15
-rw-r--r--src/main/java/org/javacs/InferSourcePath.java4
-rw-r--r--src/main/java/org/javacs/JavaLanguageServer.java898
-rw-r--r--src/main/java/org/javacs/JavaTextDocumentService.java787
-rw-r--r--src/main/java/org/javacs/JavaWorkspaceService.java56
-rw-r--r--src/main/java/org/javacs/Main.java20
-rw-r--r--src/main/java/org/javacs/Parser.java15
-rw-r--r--src/main/java/org/javacs/lsp/CancelParams.java2
-rw-r--r--src/main/java/org/javacs/lsp/CodeLens.java8
-rw-r--r--src/main/java/org/javacs/lsp/CodeLensParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/Command.java8
-rw-r--r--src/main/java/org/javacs/lsp/CompletionContext.java6
-rw-r--r--src/main/java/org/javacs/lsp/CompletionItem.java7
-rw-r--r--src/main/java/org/javacs/lsp/CompletionList.java7
-rw-r--r--src/main/java/org/javacs/lsp/CompletionParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/Diagnostic.java2
-rw-r--r--src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/DocumentFormattingParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/DocumentSymbolParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/FormattingOptions.java7
-rw-r--r--src/main/java/org/javacs/lsp/Hover.java11
-rw-r--r--src/main/java/org/javacs/lsp/InitializeResult.java6
-rw-r--r--src/main/java/org/javacs/lsp/LSP.java169
-rw-r--r--src/main/java/org/javacs/lsp/LanguageServer.java4
-rw-r--r--src/main/java/org/javacs/lsp/Location.java11
-rw-r--r--src/main/java/org/javacs/lsp/MarkedString.java7
-rw-r--r--src/main/java/org/javacs/lsp/MarkupContent.java10
-rw-r--r--src/main/java/org/javacs/lsp/Position.java7
-rw-r--r--src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java7
-rw-r--r--src/main/java/org/javacs/lsp/Range.java7
-rw-r--r--src/main/java/org/javacs/lsp/SignatureHelp.java11
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentIdentifier.java6
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentPositionParams.java7
-rw-r--r--src/main/java/org/javacs/lsp/TextEdit.java7
-rw-r--r--src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java6
-rw-r--r--src/test/java/org/javacs/CodeLensTest.java45
-rw-r--r--src/test/java/org/javacs/CompletionsBase.java30
-rw-r--r--src/test/java/org/javacs/CompletionsTest.java55
-rw-r--r--src/test/java/org/javacs/FindReferencesTest.java27
-rw-r--r--src/test/java/org/javacs/GotoTest.java26
-rw-r--r--src/test/java/org/javacs/JavaCompilerServiceTest.java2
-rw-r--r--src/test/java/org/javacs/LanguageServerFixture.java45
-rw-r--r--src/test/java/org/javacs/SearchTest.java37
-rw-r--r--src/test/java/org/javacs/SignatureHelpTest.java35
-rw-r--r--src/test/java/org/javacs/SymbolUnderCursorTest.java15
-rw-r--r--src/test/java/org/javacs/lsp/LanguageServerTest.java18
-rw-r--r--src/test/java/org/javacs/lsp/LspTest.java15
47 files changed, 1274 insertions, 1223 deletions
diff --git a/src/main/java/org/javacs/CustomLanguageClient.java b/src/main/java/org/javacs/CustomLanguageClient.java
deleted file mode 100644
index 3eae781..0000000
--- a/src/main/java/org/javacs/CustomLanguageClient.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.javacs;
-
-import org.javacs.lsp.*;
-
-public interface CustomLanguageClient extends LanguageClient {
-
- @JsonNotification("java/startProgress")
- void javaStartProgress(JavaStartProgressParams params);
-
- @JsonNotification("java/reportProgress")
- void javaReportProgress(JavaReportProgressParams params);
-
- @JsonNotification("java/endProgress")
- void javaEndProgress();
-}
diff --git a/src/main/java/org/javacs/InferSourcePath.java b/src/main/java/org/javacs/InferSourcePath.java
index d54c987..b6f30f2 100644
--- a/src/main/java/org/javacs/InferSourcePath.java
+++ b/src/main/java/org/javacs/InferSourcePath.java
@@ -45,7 +45,9 @@ class InferSourcePath {
var packageName = Objects.toString(Parser.parse(java).getPackageName(), "");
var packagePath = packageName.replace('.', File.separatorChar);
var dir = java.getParent();
- if (!dir.endsWith(packagePath)) {
+ if (packagePath.isEmpty()) {
+ return Optional.of(dir);
+ } else if (!dir.endsWith(packagePath)) {
LOG.warning("Java source file " + java + " is not in " + packagePath);
return Optional.empty();
} else {
diff --git a/src/main/java/org/javacs/JavaLanguageServer.java b/src/main/java/org/javacs/JavaLanguageServer.java
index cb60c95..dcf2d0e 100644
--- a/src/main/java/org/javacs/JavaLanguageServer.java
+++ b/src/main/java/org/javacs/JavaLanguageServer.java
@@ -1,34 +1,57 @@
package org.javacs;
+import com.google.gson.*;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.ParamTree;
+import com.sun.source.tree.MethodTree;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
import java.net.URI;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
-import java.util.concurrent.CompletableFuture;
+import java.util.UUID;
+import java.util.function.Function;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import org.javacs.lsp.*;
-class JavaLanguageServer implements LanguageServer {
+class JavaLanguageServer extends LanguageServer {
private static final Logger LOG = Logger.getLogger("main");
private Path workspaceRoot;
- private CustomLanguageClient client;
+ private final LanguageClient client;
private Set<String> externalDependencies = Set.of();
private Set<Path> classPath = Set.of();
JavaCompilerService compiler;
- final JavaTextDocumentService textDocuments = new JavaTextDocumentService(this);
- final JavaWorkspaceService workspace = new JavaWorkspaceService(this);
+ private final Map<URI, VersionedContent> activeDocuments = new HashMap<>();
- private static DiagnosticSeverity severity(Diagnostic.Kind kind) {
+ private static int severity(Diagnostic.Kind kind) {
switch (kind) {
case ERROR:
return DiagnosticSeverity.Error;
@@ -55,7 +78,7 @@ class JavaLanguageServer implements LanguageServer {
void publishDiagnostics(Collection<URI> files, List<Diagnostic<? extends JavaFileObject>> javaDiagnostics) {
for (var f : files) {
- List<org.eclipse.lsp4j.Diagnostic> ds = new ArrayList<>();
+ List<org.javacs.lsp.Diagnostic> ds = new ArrayList<>();
for (var j : javaDiagnostics) {
if (j.getSource() == null) {
LOG.warning("No source in warning " + j.getMessage(null));
@@ -64,19 +87,18 @@ class JavaLanguageServer implements LanguageServer {
var uri = j.getSource().toUri();
if (uri.equals(f)) {
- var content = textDocuments.contents(uri).content;
+ var content = contents(uri).content;
var start = position(content, j.getStartPosition());
var end = position(content, j.getEndPosition());
- var sev = severity(j.getKind());
- var d = new org.eclipse.lsp4j.Diagnostic();
- d.setSeverity(sev);
- d.setRange(new Range(start, end));
- d.setCode(j.getCode());
- d.setMessage(j.getMessage(null));
+ var d = new org.javacs.lsp.Diagnostic();
+ d.severity = severity(j.getKind());
+ d.range = new Range(start, end);
+ d.code = j.getCode();
+ d.message = j.getMessage(null);
ds.add(d);
}
}
- client.publishDiagnostics(new PublishDiagnosticsParams(f.toString(), ds));
+ client.publishDiagnostics(new PublishDiagnosticsParams(f, ds));
}
}
@@ -91,30 +113,44 @@ class JavaLanguageServer implements LanguageServer {
publishDiagnostics(uris, compiler.compileBatch(uris).lint());
}
+ private static final Gson gson = new Gson();
+
+ private void javaStartProgress(JavaStartProgressParams params) {
+ client.customNotification("java/startProgress", gson.toJsonTree(params));
+ }
+
+ private void javaReportProgress(JavaReportProgressParams params) {
+ client.customNotification("java/reportProgress", gson.toJsonTree(params));
+ }
+
+ private void javaEndProgress() {
+ client.customNotification("java/endProgress", JsonNull.INSTANCE);
+ }
+
private JavaCompilerService createCompiler() {
Objects.requireNonNull(workspaceRoot, "Can't create compiler because workspaceRoot has not been initialized");
- client.javaStartProgress(new JavaStartProgressParams("Configure javac"));
- client.javaReportProgress(new JavaReportProgressParams("Finding source roots"));
+ javaStartProgress(new JavaStartProgressParams("Configure javac"));
+ javaReportProgress(new JavaReportProgressParams("Finding source roots"));
var sourcePath = InferSourcePath.sourcePath(workspaceRoot); // TODO show each folder as we find it
// If classpath is specified by the user, don't infer anything
if (!classPath.isEmpty()) {
- client.javaEndProgress();
+ javaEndProgress();
return new JavaCompilerService(sourcePath, classPath, Collections.emptySet());
}
// Otherwise, combine inference with user-specified external dependencies
else {
var infer = new InferConfig(workspaceRoot, externalDependencies);
- client.javaReportProgress(new JavaReportProgressParams("Inferring class path"));
+ javaReportProgress(new JavaReportProgressParams("Inferring class path"));
var classPath = infer.classPath();
- client.javaReportProgress(new JavaReportProgressParams("Inferring doc path"));
+ javaReportProgress(new JavaReportProgressParams("Inferring doc path"));
var docPath = infer.buildDocPath();
- client.javaEndProgress();
+ javaEndProgress();
return new JavaCompilerService(sourcePath, classPath, docPath);
}
}
@@ -134,56 +170,818 @@ class JavaLanguageServer implements LanguageServer {
}
@Override
- public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
- this.workspaceRoot = Paths.get(URI.create(params.getRootUri()));
+ public InitializeResult initialize(InitializeParams params) {
+ this.workspaceRoot = Paths.get(params.rootUri);
+
+ var c = new JsonObject();
+ c.addProperty("textDocumentSync", 2); // Incremental
+ c.addProperty("hoverProvider", true);
+ var completionOptions = new JsonObject();
+ completionOptions.addProperty("resolveProvider", true);
+ var triggerCharacters = new JsonArray();
+ triggerCharacters.add(".");
+ completionOptions.add("triggerCharacters", triggerCharacters);
+ c.add("completionProvider", completionOptions);
+ var signatureHelpOptions = new JsonObject();
+ var signatureTrigger = new JsonArray();
+ signatureTrigger.add("(");
+ signatureTrigger.add(",");
+ signatureHelpOptions.add("triggerCharacters", signatureTrigger);
+ c.add("signatureHelpProvider", signatureHelpOptions);
+ c.addProperty("referencesProvider", true);
+ c.addProperty("definitionProvider", true);
+ c.addProperty("workspaceSymbolProvider", true);
+ c.addProperty("documentSymbolProvider", true);
+ c.addProperty("documentFormattingProvider", true);
+ var codeLensOptions = new JsonObject();
+ codeLensOptions.addProperty("resolveProvider", true);
+ c.add("codeLensProvider", codeLensOptions);
- var result = new InitializeResult();
- var c = new ServerCapabilities();
+ return new InitializeResult(c);
+ }
+
+ @Override
+ public void initialized() {
+ this.compiler = createCompiler();
+ }
- c.setTextDocumentSync(TextDocumentSyncKind.Incremental);
- c.setDefinitionProvider(true);
- c.setCompletionProvider(new CompletionOptions(true, List.of(".")));
- c.setHoverProvider(true);
- c.setWorkspaceSymbolProvider(true);
- c.setReferencesProvider(true);
- c.setDocumentSymbolProvider(true);
- c.setSignatureHelpProvider(new SignatureHelpOptions(List.of("(", ",")));
- c.setDocumentFormattingProvider(true);
- c.setCodeLensProvider(new CodeLensOptions(true));
+ @Override
+ public void shutdown() {}
- result.setCapabilities(c);
+ public JavaLanguageServer(LanguageClient client) {
+ this.client = client;
+ }
- return CompletableFuture.completedFuture(result);
+ @Override
+ public List<SymbolInformation> workspaceSymbols(WorkspaceSymbolParams params) {
+ List<SymbolInformation> list =
+ compiler.findSymbols(params.query)
+ .map(Parser::asSymbolInformation)
+ .limit(50)
+ .collect(Collectors.toList());
+ return list;
}
@Override
- public void initialized(InitializedParams params) {
- this.compiler = createCompiler();
+ public void didChangeConfiguration(DidChangeConfigurationParams change) {
+ var settings = (JsonObject) change.settings;
+ var java = settings.getAsJsonObject("java");
+
+ var externalDependencies = java.getAsJsonArray("externalDependencies");
+ var strings = new HashSet<String>();
+ for (var each : externalDependencies) strings.add(each.getAsString());
+ setExternalDependencies(strings);
+
+ var classPath = java.getAsJsonArray("classPath");
+ var paths = new HashSet<Path>();
+ for (var each : classPath) paths.add(Paths.get(each.getAsString()).toAbsolutePath());
+ setClassPath(paths);
+ }
+
+ @Override
+ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {}
+
+ private Integer completionItemKind(Element e) {
+ switch (e.getKind()) {
+ case ANNOTATION_TYPE:
+ return CompletionItemKind.Interface;
+ case CLASS:
+ return CompletionItemKind.Class;
+ case CONSTRUCTOR:
+ return CompletionItemKind.Constructor;
+ case ENUM:
+ return CompletionItemKind.Enum;
+ case ENUM_CONSTANT:
+ return CompletionItemKind.EnumMember;
+ case EXCEPTION_PARAMETER:
+ return CompletionItemKind.Variable;
+ case FIELD:
+ return CompletionItemKind.Field;
+ case STATIC_INIT:
+ case INSTANCE_INIT:
+ return CompletionItemKind.Function;
+ case INTERFACE:
+ return CompletionItemKind.Interface;
+ case LOCAL_VARIABLE:
+ return CompletionItemKind.Variable;
+ case METHOD:
+ return CompletionItemKind.Method;
+ case PACKAGE:
+ return CompletionItemKind.Module;
+ case PARAMETER:
+ return CompletionItemKind.Variable;
+ case RESOURCE_VARIABLE:
+ return CompletionItemKind.Variable;
+ case TYPE_PARAMETER:
+ return CompletionItemKind.TypeParameter;
+ case OTHER:
+ default:
+ return null;
+ }
}
+ /** Cache of completions from the last call to `completion` */
+ private final Map<String, Completion> lastCompletions = new HashMap<>();
+
@Override
- public CompletableFuture<Object> shutdown() {
- return CompletableFuture.completedFuture(null);
+ public Optional<CompletionList> completion(TextDocumentPositionParams position) {
+ var uri = position.textDocument.uri;
+ var content = contents(uri).content;
+ var line = position.position.line + 1;
+ var column = position.position.character + 1;
+ lastCompletions.clear();
+ // Figure out what kind of completion we want to do
+ var maybeCtx = compiler.parseFile(uri, content).completionContext(line, column);
+ if (!maybeCtx.isPresent()) {
+ var items = new ArrayList<CompletionItem>();
+ for (var name : CompileFocus.TOP_LEVEL_KEYWORDS) {
+ var i = new CompletionItem();
+ i.label = name;
+ i.kind = CompletionItemKind.Keyword;
+ i.detail = "keyword";
+ items.add(i);
+ }
+ return Optional.of(new CompletionList(true, items));
+ }
+ // Compile again, focusing on a region that depends on what type of completion we want to do
+ var ctx = maybeCtx.get();
+ var focus = compiler.compileFocus(uri, content, ctx.line, ctx.character);
+ // Do a specific type of completion
+ List<Completion> cs;
+ boolean isIncomplete;
+ switch (ctx.kind) {
+ case MemberSelect:
+ cs = focus.completeMembers(false);
+ isIncomplete = false;
+ break;
+ case MemberReference:
+ cs = focus.completeMembers(true);
+ isIncomplete = false;
+ break;
+ case Identifier:
+ cs = focus.completeIdentifiers(ctx.inClass, ctx.inMethod, ctx.partialName);
+ isIncomplete = cs.size() >= CompileFocus.MAX_COMPLETION_ITEMS;
+ break;
+ case Annotation:
+ cs = focus.completeAnnotations(ctx.partialName);
+ isIncomplete = cs.size() >= CompileFocus.MAX_COMPLETION_ITEMS;
+ break;
+ case Case:
+ cs = focus.completeCases();
+ isIncomplete = false;
+ break;
+ default:
+ throw new RuntimeException("Unexpected completion context " + ctx.kind);
+ }
+ // Convert to CompletionItem
+ var result = new ArrayList<CompletionItem>();
+ for (var c : cs) {
+ var i = new CompletionItem();
+ var id = UUID.randomUUID().toString();
+ i.data = new JsonPrimitive(id);
+ lastCompletions.put(id, c);
+ if (c.element != null) {
+ i.label = c.element.getSimpleName().toString();
+ i.kind = completionItemKind(c.element);
+ // Detailed name will be resolved later, using docs to fill in method names
+ if (!(c.element instanceof ExecutableElement)) i.detail = c.element.toString();
+ i.sortText = 2 + i.label;
+ } else if (c.packagePart != null) {
+ i.label = c.packagePart.name;
+ i.kind = CompletionItemKind.Module;
+ i.detail = c.packagePart.fullName;
+ i.sortText = 2 + i.label;
+ } else if (c.keyword != null) {
+ i.label = c.keyword;
+ i.kind = CompletionItemKind.Keyword;
+ i.detail = "keyword";
+ i.sortText = 3 + i.label;
+ } else if (c.className != null) {
+ i.label = Parser.lastName(c.className.name);
+ i.kind = CompletionItemKind.Class;
+ i.detail = c.className.name;
+ if (c.className.isImported) i.sortText = 2 + i.label;
+ else i.sortText = 4 + i.label;
+ } else if (c.snippet != null) {
+ i.label = c.snippet.label;
+ i.kind = CompletionItemKind.Snippet;
+ i.insertText = c.snippet.snippet;
+ i.insertTextFormat = InsertTextFormat.Snippet;
+ i.sortText = 1 + i.label;
+ } else throw new RuntimeException(c + " is not valid");
+
+ result.add(i);
+ }
+ return Optional.of(new CompletionList(isIncomplete, result));
+ }
+
+ private String resolveDocDetail(MethodTree doc) {
+ var args = new StringJoiner(", ");
+ for (var p : doc.getParameters()) {
+ args.add(p.getName());
+ }
+ return String.format("%s %s(%s)", doc.getReturnType(), doc.getName(), args);
+ }
+
+ private String resolveDefaultDetail(ExecutableElement method) {
+ var args = new StringJoiner(", ");
+ var missingParamNames =
+ method.getParameters().stream().allMatch(p -> p.getSimpleName().toString().matches("arg\\d+"));
+ for (var p : method.getParameters()) {
+ if (missingParamNames) args.add(ShortTypePrinter.print(p.asType()));
+ else args.add(p.getSimpleName().toString());
+ }
+ return String.format("%s %s(%s)", ShortTypePrinter.print(method.getReturnType()), method.getSimpleName(), args);
+ }
+
+ private String asMarkdown(List<? extends DocTree> lines) {
+ var join = new StringJoiner("\n");
+ for (var l : lines) join.add(l.toString());
+ var html = join.toString();
+ return Docs.htmlToMarkdown(html);
+ }
+
+ private String asMarkdown(DocCommentTree comment) {
+ var lines = comment.getFirstSentence();
+ return asMarkdown(lines);
+ }
+
+ private MarkupContent asMarkupContent(DocCommentTree comment) {
+ var markdown = asMarkdown(comment);
+ var content = new MarkupContent();
+ content.kind = MarkupKind.Markdown;
+ content.value = markdown;
+ return content;
}
@Override
- public void exit() {}
+ public CompletionItem resolveCompletionItem(CompletionItem unresolved) {
+ var idJson = (JsonPrimitive) unresolved.data;
+ var id = idJson.getAsString();
+ var cached = lastCompletions.get(id);
+ if (cached == null) {
+ LOG.warning("CompletionItem " + id + " was not in the cache");
+ return unresolved;
+ }
+ if (cached.element != null) {
+ if (cached.element instanceof ExecutableElement) {
+ var method = (ExecutableElement) cached.element;
+ var tree = compiler.docs().methodTree(method);
+ var detail = tree.map(this::resolveDocDetail).orElse(resolveDefaultDetail(method));
+ unresolved.detail = detail;
+
+ var doc = compiler.docs().methodDoc(method);
+ var markdown = doc.map(this::asMarkupContent);
+ if (markdown.isPresent()) unresolved.documentation = markdown.get();
+ } else if (cached.element instanceof TypeElement) {
+ var type = (TypeElement) cached.element;
+ var doc = compiler.docs().classDoc(type);
+ var markdown = doc.map(this::asMarkupContent);
+ if (markdown.isPresent()) unresolved.documentation = markdown.get();
+ } else {
+ LOG.info("Don't know how to look up docs for element " + cached.element);
+ }
+ // TODO constructors, fields
+ } else if (cached.className != null) {
+ var doc = compiler.docs().classDoc(cached.className.name);
+ var markdown = doc.map(this::asMarkupContent);
+ if (markdown.isPresent()) unresolved.documentation = markdown.get();
+ }
+ return unresolved;
+ }
+
+ private String hoverTypeDeclaration(TypeElement t) {
+ var result = new StringBuilder();
+ switch (t.getKind()) {
+ case ANNOTATION_TYPE:
+ result.append("@interface");
+ break;
+ case INTERFACE:
+ result.append("interface");
+ break;
+ case CLASS:
+ result.append("class");
+ break;
+ case ENUM:
+ result.append("enum");
+ break;
+ default:
+ LOG.warning("Don't know what to call type element " + t);
+ result.append("???");
+ }
+ result.append(" ").append(ShortTypePrinter.print(t.asType()));
+ var superType = ShortTypePrinter.print(t.getSuperclass());
+ switch (superType) {
+ case "Object":
+ case "none":
+ break;
+ default:
+ result.append(" extends ").append(superType);
+ }
+ return result.toString();
+ }
+
+ private String hoverCode(Element e) {
+ if (e instanceof ExecutableElement) {
+ var m = (ExecutableElement) e;
+ return ShortTypePrinter.printMethod(m);
+ } else if (e instanceof VariableElement) {
+ var v = (VariableElement) e;
+ return ShortTypePrinter.print(v.asType()) + " " + v;
+ } else if (e instanceof TypeElement) {
+ var t = (TypeElement) e;
+ var lines = new StringJoiner("\n");
+ lines.add(hoverTypeDeclaration(t) + " {");
+ for (var member : t.getEnclosedElements()) {
+ // TODO check accessibility
+ if (member instanceof ExecutableElement || member instanceof VariableElement) {
+ lines.add(" " + hoverCode(member) + ";");
+ } else if (member instanceof TypeElement) {
+ lines.add(" " + hoverTypeDeclaration((TypeElement) member) + " { /* removed */ }");
+ }
+ }
+ lines.add("}");
+ return lines.toString();
+ } else return e.toString();
+ }
+
+ private Optional<String> hoverDocs(Element e) {
+ if (e instanceof ExecutableElement) {
+ var m = (ExecutableElement) e;
+ return compiler.docs().methodDoc(m).map(this::asMarkdown);
+ } else if (e instanceof TypeElement) {
+ var t = (TypeElement) e;
+ return compiler.docs().classDoc(t).map(this::asMarkdown);
+ } else return Optional.empty();
+ }
+
+ private CompileFile hoverCache;
+
+ private void updateHoverCache(URI uri, String contents) {
+ if (hoverCache == null || !hoverCache.file.equals(uri) || !hoverCache.contents.equals(contents)) {
+ LOG.info("File has changed since last hover, recompiling");
+ hoverCache = compiler.compileFile(uri, contents);
+ }
+ }
@Override
- public TextDocumentService getTextDocumentService() {
- return textDocuments;
+ public Optional<Hover> hover(TextDocumentPositionParams position) {
+ // Compile entire file if it's changed since last hover
+ var uri = position.textDocument.uri;
+ var content = contents(uri).content;
+ updateHoverCache(uri, content);
+
+ // Find element undeer cursor
+ var line = position.position.line + 1;
+ var column = position.position.character + 1;
+ var el = hoverCache.element(line, column);
+ if (!el.isPresent()) return Optional.empty();
+
+ // Add code hover message
+ var result = new ArrayList<MarkedString>();
+ var code = hoverCode(el.get());
+ result.add(new MarkedString("java.hover", code));
+ // Add docs hover message
+ var docs = hoverDocs(el.get());
+ if (docs.isPresent()) {
+ result.add(new MarkedString("markdown", docs.get()));
+ }
+
+ return Optional.of(new Hover(result));
+ }
+
+ private List<ParameterInformation> signatureParamsFromDocs(MethodTree method, DocCommentTree doc) {
+ var ps = new ArrayList<ParameterInformation>();
+ var paramComments = new HashMap<String, String>();
+ for (var tag : doc.getBlockTags()) {
+ if (tag.getKind() == DocTree.Kind.PARAM) {
+ var param = (ParamTree) tag;
+ paramComments.put(param.getName().toString(), asMarkdown(param.getDescription()));
+ }
+ }
+ for (var param : method.getParameters()) {
+ var info = new ParameterInformation();
+ var name = param.getName().toString();
+ info.label = name;
+ if (paramComments.containsKey(name)) {
+ var markdown = paramComments.get(name);
+ info.documentation = new MarkupContent("markdown", markdown);
+ } else {
+ var markdown = Objects.toString(param.getType(), "");
+ info.documentation = new MarkupContent("markdown", markdown);
+ }
+ ps.add(info);
+ }
+ return ps;
+ }
+
+ private List<ParameterInformation> signatureParamsFromMethod(ExecutableElement e) {
+ var missingParamNames = ShortTypePrinter.missingParamNames(e);
+ var ps = new ArrayList<ParameterInformation>();
+ for (var v : e.getParameters()) {
+ var p = new ParameterInformation();
+ if (missingParamNames) p.label = ShortTypePrinter.print(v.asType());
+ else p.label = v.getSimpleName().toString();
+ ps.add(p);
+ }
+ return ps;
+ }
+
+ private SignatureInformation asSignatureInformation(ExecutableElement e) {
+ var i = new SignatureInformation();
+ var ps = signatureParamsFromMethod(e);
+ var doc = compiler.docs().methodDoc(e);
+ var tree = compiler.docs().methodTree(e);
+ if (doc.isPresent() && tree.isPresent()) ps = signatureParamsFromDocs(tree.get(), doc.get());
+ var args = ps.stream().map(p -> p.label).collect(Collectors.joining(", "));
+ var name = e.getSimpleName().toString();
+ if (name.equals("<init>")) name = e.getEnclosingElement().getSimpleName().toString();
+ i.label = name + "(" + args + ")";
+ i.parameters = ps;
+ return i;
+ }
+
+ private SignatureHelp asSignatureHelp(MethodInvocation invoke) {
+ // TODO use docs to get parameter names
+ var sigs = new ArrayList<SignatureInformation>();
+ for (var e : invoke.overloads) {
+ sigs.add(asSignatureInformation(e));
+ }
+ var activeSig = invoke.activeMethod.map(invoke.overloads::indexOf).orElse(0);
+ return new SignatureHelp(sigs, activeSig, invoke.activeParameter);
}
@Override
- public WorkspaceService getWorkspaceService() {
- return workspace;
+ public Optional<SignatureHelp> signatureHelp(TextDocumentPositionParams position) {
+ var uri = position.textDocument.uri;
+ var content = contents(uri).content;
+ var line = position.position.line + 1;
+ var column = position.position.character + 1;
+ var focus = compiler.compileFocus(uri, content, line, column);
+ var help = focus.methodInvocation().map(this::asSignatureHelp);
+ return help;
}
- void installClient(CustomLanguageClient client) {
- this.client = client;
+ @Override
+ public List<Location> gotoDefinition(TextDocumentPositionParams position) {
+ var fromUri = position.textDocument.uri;
+ var fromLine = position.position.line + 1;
+ var fromColumn = position.position.character + 1;
+ var fromContent = contents(fromUri).content;
+ var fromFocus = compiler.compileFocus(fromUri, fromContent, fromLine, fromColumn);
+ var toEl = fromFocus.element();
+ var toUri = fromFocus.declaringFile(toEl);
+ if (!toUri.isPresent()) return List.of();
+ var toContent = contents(toUri.get()).content;
+ var toFile = compiler.compileFile(toUri.get(), toContent);
+ var toPath = toFile.find(new Ptr(toEl));
+ if (!toPath.isPresent()) return List.of();
+ // Figure out where in the file the definition is
+ var toRange = toFile.range(toPath.get());
+ if (!toRange.isPresent()) return List.of();
+ var to = new Location(toUri.get(), toRange.get());
+ return List.of(to);
}
- CustomLanguageClient client() {
- return this.client;
+ class ReportProgress implements ReportReferencesProgress, AutoCloseable {
+ private final Function<Integer, String> scanMessage, checkMessage;
+
+ ReportProgress(
+ String startMessage, Function<Integer, String> scanMessage, Function<Integer, String> checkMessage) {
+ this.scanMessage = scanMessage;
+ this.checkMessage = checkMessage;
+ javaStartProgress(new JavaStartProgressParams(startMessage));
+ }
+
+ private int percent(int n, int d) {
+ double nD = n, dD = d;
+ double ratio = nD / dD;
+ return (int) (ratio * 100);
+ }
+
+ public void scanForPotentialReferences(int nScanned, int nFiles) {
+ var message = scanMessage.apply(nFiles);
+ if (nScanned == 0) {
+ javaReportProgress(new JavaReportProgressParams(message));
+ } else {
+ var increment = percent(nScanned, nFiles) > percent(nScanned - 1, nFiles) ? 1 : 0;
+ javaReportProgress(new JavaReportProgressParams(message, increment));
+ }
+ }
+
+ public void checkPotentialReferences(int nCompiled, int nPotential) {
+ var message = checkMessage.apply(nCompiled);
+ if (nCompiled == 0) {
+ javaReportProgress(new JavaReportProgressParams(message));
+ } else {
+ var increment = percent(nCompiled, nPotential) > percent(nCompiled - 1, nPotential) ? 1 : 0;
+ javaReportProgress(new JavaReportProgressParams(message, increment));
+ }
+ }
+
+ @Override
+ public void close() {
+ javaEndProgress();
+ }
+ }
+
+ @Override
+ public List<Location> findReferences(ReferenceParams position) {
+ var toUri = position.textDocument.uri;
+ var toContent = contents(toUri).content;
+ var toLine = position.position.line + 1;
+ var toColumn = position.position.character + 1;
+ var toEl = compiler.compileFocus(toUri, toContent, toLine, toColumn).element();
+ var fromFiles = compiler.potentialReferences(toEl);
+ if (fromFiles.isEmpty()) return List.of();
+ var batch = compiler.compileBatch(fromFiles);
+ var fromTreePaths = batch.references(toEl);
+ var result = new ArrayList<Location>();
+ for (var path : fromTreePaths) {
+ var fromUri = path.getCompilationUnit().getSourceFile().toUri();
+ var fromRange = batch.range(path);
+ if (!fromRange.isPresent()) {
+ LOG.warning(String.format("Couldn't locate `%s`", path.getLeaf()));
+ continue;
+ }
+ var from = new Location(fromUri, fromRange.get());
+ result.add(from);
+ }
+ return result;
+ }
+
+ @Override
+ public List<SymbolInformation> documentSymbol(DocumentSymbolParams params) {
+ var uri = params.textDocument.uri;
+ var content = contents(uri).content;
+ var result =
+ Parser.documentSymbols(Paths.get(uri), content)
+ .stream()
+ .map(Parser::asSymbolInformation)
+ .collect(Collectors.toList());
+ return result;
+ }
+
+ @Override
+ public List<CodeLens> codeLens(CodeLensParams params) {
+ // TODO just create a blank code lens on every method, then resolve it async
+ var uri = params.textDocument.uri;
+ var content = contents(uri).content;
+ var parse = compiler.parseFile(uri, content);
+ var declarations = parse.declarations();
+ var result = new ArrayList<CodeLens>();
+ for (var d : declarations) {
+ var range = parse.range(d);
+ if (!range.isPresent()) continue;
+ var className = JavaCompilerService.className(d);
+ var memberName = JavaCompilerService.memberName(d);
+ // If test class or method, add "Run Test" code lens
+ if (parse.isTestClass(d)) {
+ var arguments = new JsonArray();
+ arguments.add(uri.toString());
+ arguments.add(className);
+ arguments.add(JsonNull.INSTANCE);
+ var command = new Command("Run All Tests", "java.command.test.run", arguments);
+ var lens = new CodeLens(range.get(), command, null);
+ result.add(lens);
+ // TODO run all tests in file
+ // TODO run all tests in package
+ } else if (parse.isTestMethod(d)) {
+ var arguments = new JsonArray();
+ arguments.add(uri.toString());
+ arguments.add(className);
+ if (memberName.isPresent()) arguments.add(memberName.get());
+ else arguments.add(JsonNull.INSTANCE);
+ var command = new Command("Run Test", "java.command.test.run", arguments);
+ var lens = new CodeLens(range.get(), command, null);
+ result.add(lens);
+ }
+ // If method or field, add an unresolved "_ references" code lens
+ if (memberName.isPresent()) {
+ var start = range.get().start;
+ var line = start.line;
+ var character = start.character;
+ var data = new JsonArray();
+ data.add("java.command.findReferences");
+ data.add(uri.toString());
+ data.add(line);
+ data.add(character);
+ data.add(new Ptr(d).toString());
+ var lens = new CodeLens(range.get(), null, data);
+ result.add(lens);
+ }
+ }
+ return result;
+ }
+
+ private Map<Ptr, Integer> cacheCountReferences = Collections.emptyMap();
+ private URI cacheCountReferencesFile = URI.create("file:///NONE");
+
+ private void updateCacheCountReferences(URI current) {
+ if (cacheCountReferencesFile.equals(current)) return;
+ LOG.info(String.format("Update cached reference count to %s...", current));
+ var content = contents(current).content;
+ cacheCountReferences = compiler.countReferences(current, content);
+ cacheCountReferencesFile = current;
+ }
+
+ @Override
+ public CodeLens resolveCodeLens(CodeLens unresolved) {
+ // Unpack data
+ var data = unresolved.data;
+ var command = data.get(0).getAsString();
+ assert command.equals("java.command.findReferences");
+ var uriString = data.get(1).getAsString();
+ var line = data.get(2).getAsInt();
+ var character = data.get(3).getAsInt();
+ var ptrString = data.get(4).getAsString();
+ // Parse data
+ var uri = URI.create(uriString);
+ var ptr = new Ptr(ptrString);
+ // Update cache if necessary
+ updateCacheCountReferences(uri);
+ // Read reference count from cache
+ var count = cacheCountReferences.getOrDefault(ptr, 0);
+ // Update command
+ String title;
+ if (count == 1) title = "1 reference";
+ else title = String.format("%d references", count);
+ var arguments = new JsonArray();
+ arguments.add(uri.toString());
+ arguments.add(line);
+ arguments.add(character);
+ unresolved.command = new Command(title, command, arguments);
+
+ return unresolved;
+ }
+
+ private List<TextEdit> fixImports(URI java) {
+ var contents = contents(java).content;
+ var fix = compiler.compileFile(java, contents).fixImports();
+ // TODO if imports already match fixed-imports, return empty list
+ // TODO preserve comments and other details of existing imports
+ var edits = new ArrayList<TextEdit>();
+ // Delete all existing imports
+ for (var i : fix.parsed.getImports()) {
+ if (!i.isStatic()) {
+ var offset = fix.sourcePositions.getStartPosition(fix.parsed, i);
+ var line = (int) fix.parsed.getLineMap().getLineNumber(offset) - 1;
+ var delete = new TextEdit(new Range(new Position(line, 0), new Position(line + 1, 0)), "");
+ edits.add(delete);
+ }
+ }
+ if (fix.fixedImports.isEmpty()) return edits;
+ // Find a place to insert the new imports
+ long insertLine = -1;
+ var insertText = new StringBuilder();
+ // If there are imports, use the start of the first import as the insert position
+ for (var i : fix.parsed.getImports()) {
+ if (!i.isStatic() && insertLine == -1) {
+ long offset = fix.sourcePositions.getStartPosition(fix.parsed, i);
+ insertLine = fix.parsed.getLineMap().getLineNumber(offset) - 1;
+ }
+ }
+ // If there are no imports, insert after the package declaration
+ if (insertLine == -1 && fix.parsed.getPackageName() != null) {
+ long offset = fix.sourcePositions.getEndPosition(fix.parsed, fix.parsed.getPackageName());
+ insertLine = fix.parsed.getLineMap().getLineNumber(offset);
+ insertText.append("\n");
+ }
+ // If there are no imports and no package, insert at the top of the file
+ if (insertLine == -1) {
+ insertLine = 0;
+ }
+ // Insert each import
+ fix.fixedImports
+ .stream()
+ .sorted()
+ .forEach(
+ i -> {
+ insertText.append("import ").append(i).append(";\n");
+ });
+ var insertPosition = new Position((int) insertLine, 0);
+ var insert = new TextEdit(new Range(insertPosition, insertPosition), insertText.toString());
+ edits.add(insert);
+ return edits;
+ }
+
+ @Override
+ public List<TextEdit> formatting(DocumentFormattingParams params) {
+ var uri = params.textDocument.uri;
+ return fixImports(uri);
+ }
+
+ @Override
+ public WorkspaceEdit rename(RenameParams params) {
+ return null; // TODO
+ }
+
+ private boolean isJava(URI uri) {
+ return uri.getPath().endsWith(".java");
+ }
+
+ @Override
+ public void didOpenTextDocument(DidOpenTextDocumentParams params) {
+ var document = params.textDocument;
+ var uri = document.uri;
+ if (isJava(uri)) {
+ activeDocuments.put(uri, new VersionedContent(document.text, document.version));
+ lint(Collections.singleton(uri));
+ }
+ }
+
+ @Override
+ public void didChangeTextDocument(DidChangeTextDocumentParams params) {
+ var document = params.textDocument;
+ var uri = document.uri;
+ if (isJava(uri)) {
+ var existing = activeDocuments.get(uri);
+ var newText = existing.content;
+
+ if (document.version > existing.version) {
+ for (var change : params.contentChanges) {
+ if (change.range == null) newText = change.text;
+ else newText = patch(newText, change);
+ }
+
+ activeDocuments.put(uri, new VersionedContent(newText, document.version));
+ } else LOG.warning("Ignored change with version " + document.version + " <= " + existing.version);
+ }
+ }
+
+ private String patch(String sourceText, TextDocumentContentChangeEvent change) {
+ try {
+ var range = change.range;
+ var reader = new BufferedReader(new StringReader(sourceText));
+ var writer = new StringWriter();
+
+ // Skip unchanged lines
+ int line = 0;
+
+ while (line < range.start.line) {
+ writer.write(reader.readLine() + '\n');
+ line++;
+ }
+
+ // Skip unchanged chars
+ for (int character = 0; character < range.start.character; character++) writer.write(reader.read());
+
+ // Write replacement text
+ writer.write(change.text);
+
+ // Skip replaced text
+ reader.skip(change.rangeLength);
+
+ // Write remaining text
+ while (true) {
+ int next = reader.read();
+
+ if (next == -1) return writer.toString();
+ else writer.write(next);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void didCloseTextDocument(DidCloseTextDocumentParams params) {
+ var document = params.textDocument;
+ var uri = document.uri;
+ if (isJava(uri)) {
+ // Remove from source cache
+ activeDocuments.remove(uri);
+
+ // Clear diagnostics
+ publishDiagnostics(Collections.singletonList(uri), List.of());
+ }
+ }
+
+ @Override
+ public void didSaveTextDocument(DidSaveTextDocumentParams params) {
+ var uri = params.textDocument.uri;
+ if (isJava(uri)) {
+ // Re-lint all active documents
+ lint(activeDocuments.keySet());
+ // TODO update config when java file implies a new source root
+ }
+ // TODO update config when pom.xml changes
+ }
+
+ Set<URI> activeDocuments() {
+ return activeDocuments.keySet();
+ }
+
+ VersionedContent contents(URI openFile) {
+ if (activeDocuments.containsKey(openFile)) {
+ return activeDocuments.get(openFile);
+ } else {
+ try {
+ var content = Files.readAllLines(Paths.get(openFile)).stream().collect(Collectors.joining("\n"));
+ return new VersionedContent(content, -1);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
}
diff --git a/src/main/java/org/javacs/JavaTextDocumentService.java b/src/main/java/org/javacs/JavaTextDocumentService.java
deleted file mode 100644
index 6d7dd30..0000000
--- a/src/main/java/org/javacs/JavaTextDocumentService.java
+++ /dev/null
@@ -1,787 +0,0 @@
-package org.javacs;
-
-import com.google.gson.JsonPrimitive;
-import com.sun.source.doctree.DocCommentTree;
-import com.sun.source.doctree.DocTree;
-import com.sun.source.doctree.ParamTree;
-import com.sun.source.tree.MethodTree;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.lang.annotation.Annotation;
-import java.net.URI;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Function;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import javax.lang.model.element.*;
-import org.javacs.lsp.*;
-
-class JavaTextDocumentService implements TextDocumentService {
- private final JavaLanguageServer server;
- private final Map<URI, VersionedContent> activeDocuments = new HashMap<>();
-
- JavaTextDocumentService(JavaLanguageServer server) {
- this.server = server;
- }
-
- private CompletionItemKind completionItemKind(Element e) {
- switch (e.getKind()) {
- case ANNOTATION_TYPE:
- return CompletionItemKind.Interface;
- case CLASS:
- return CompletionItemKind.Class;
- case CONSTRUCTOR:
- return CompletionItemKind.Constructor;
- case ENUM:
- return CompletionItemKind.Enum;
- case ENUM_CONSTANT:
- return CompletionItemKind.EnumMember;
- case EXCEPTION_PARAMETER:
- return CompletionItemKind.Variable;
- case FIELD:
- return CompletionItemKind.Field;
- case STATIC_INIT:
- case INSTANCE_INIT:
- return CompletionItemKind.Function;
- case INTERFACE:
- return CompletionItemKind.Interface;
- case LOCAL_VARIABLE:
- return CompletionItemKind.Variable;
- case METHOD:
- return CompletionItemKind.Method;
- case PACKAGE:
- return CompletionItemKind.Module;
- case PARAMETER:
- return CompletionItemKind.Variable;
- case RESOURCE_VARIABLE:
- return CompletionItemKind.Variable;
- case TYPE_PARAMETER:
- return CompletionItemKind.TypeParameter;
- case OTHER:
- default:
- return null;
- }
- }
-
- /** Cache of completions from the last call to `completion` */
- private final Map<String, Completion> lastCompletions = new HashMap<>();
-
- @Override
- public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams position) {
- var uri = URI.create(position.getTextDocument().getUri());
- var content = contents(uri).content;
- var line = position.getPosition().getLine() + 1;
- var column = position.getPosition().getCharacter() + 1;
- lastCompletions.clear();
- // Figure out what kind of completion we want to do
- var maybeCtx = server.compiler.parseFile(uri, content).completionContext(line, column);
- if (!maybeCtx.isPresent()) {
- var items = new ArrayList<CompletionItem>();
- for (var name : CompileFocus.TOP_LEVEL_KEYWORDS) {
- var i = new CompletionItem();
- i.setLabel(name);
- i.setKind(CompletionItemKind.Keyword);
- i.setDetail("keyword");
- items.add(i);
- }
- var list = new CompletionList(true, items);
- return CompletableFuture.completedFuture(Either.forRight(list));
- }
- // Compile again, focusing on a region that depends on what type of completion we want to do
- var ctx = maybeCtx.get();
- var focus = server.compiler.compileFocus(uri, content, ctx.line, ctx.character);
- // Do a specific type of completion
- List<Completion> cs;
- boolean isIncomplete;
- switch (ctx.kind) {
- case MemberSelect:
- cs = focus.completeMembers(false);
- isIncomplete = false;
- break;
- case MemberReference:
- cs = focus.completeMembers(true);
- isIncomplete = false;
- break;
- case Identifier:
- cs = focus.completeIdentifiers(ctx.inClass, ctx.inMethod, ctx.partialName);
- isIncomplete = cs.size() >= CompileFocus.MAX_COMPLETION_ITEMS;
- break;
- case Annotation:
- cs = focus.completeAnnotations(ctx.partialName);
- isIncomplete = cs.size() >= CompileFocus.MAX_COMPLETION_ITEMS;
- break;
- case Case:
- cs = focus.completeCases();
- isIncomplete = false;
- break;
- default:
- throw new RuntimeException("Unexpected completion context " + ctx.kind);
- }
- // Convert to CompletionItem
- var result = new ArrayList<CompletionItem>();
- for (var c : cs) {
- var i = new CompletionItem();
- var id = UUID.randomUUID().toString();
- i.setData(id);
- lastCompletions.put(id, c);
- if (c.element != null) {
- i.setLabel(c.element.getSimpleName().toString());
- i.setKind(completionItemKind(c.element));
- // Detailed name will be resolved later, using docs to fill in method names
- if (!(c.element instanceof ExecutableElement)) i.setDetail(c.element.toString());
- i.setSortText(2 + i.getLabel());
- } else if (c.packagePart != null) {
- i.setLabel(c.packagePart.name);
- i.setKind(CompletionItemKind.Module);
- i.setDetail(c.packagePart.fullName);
- i.setSortText(2 + i.getLabel());
- } else if (c.keyword != null) {
- i.setLabel(c.keyword);
- i.setKind(CompletionItemKind.Keyword);
- i.setDetail("keyword");
- i.setSortText(3 + i.getLabel());
- } else if (c.className != null) {
- i.setLabel(Parser.lastName(c.className.name));
- i.setKind(CompletionItemKind.Class);
- i.setDetail(c.className.name);
- if (c.className.isImported) i.setSortText(2 + i.getLabel());
- else i.setSortText(4 + i.getLabel());
- } else if (c.snippet != null) {
- i.setLabel(c.snippet.label);
- i.setKind(CompletionItemKind.Snippet);
- i.setInsertText(c.snippet.snippet);
- i.setInsertTextFormat(InsertTextFormat.Snippet);
- i.setSortText(1 + i.getLabel());
- } else throw new RuntimeException(c + " is not valid");
-
- result.add(i);
- }
- return CompletableFuture.completedFuture(Either.forRight(new CompletionList(isIncomplete, result)));
- }
-
- private String resolveDocDetail(MethodTree doc) {
- var args = new StringJoiner(", ");
- for (var p : doc.getParameters()) {
- args.add(p.getName());
- }
- return String.format("%s %s(%s)", doc.getReturnType(), doc.getName(), args);
- }
-
- private String resolveDefaultDetail(ExecutableElement method) {
- var args = new StringJoiner(", ");
- var missingParamNames =
- method.getParameters().stream().allMatch(p -> p.getSimpleName().toString().matches("arg\\d+"));
- for (var p : method.getParameters()) {
- if (missingParamNames) args.add(ShortTypePrinter.print(p.asType()));
- else args.add(p.getSimpleName().toString());
- }
- return String.format("%s %s(%s)", ShortTypePrinter.print(method.getReturnType()), method.getSimpleName(), args);
- }
-
- private String asMarkdown(List<? extends DocTree> lines) {
- var join = new StringJoiner("\n");
- for (var l : lines) join.add(l.toString());
- var html = join.toString();
- return Docs.htmlToMarkdown(html);
- }
-
- private String asMarkdown(DocCommentTree comment) {
- var lines = comment.getFirstSentence();
- return asMarkdown(lines);
- }
-
- private MarkupContent asMarkupContent(DocCommentTree comment) {
- var markdown = asMarkdown(comment);
- var content = new MarkupContent();
- content.setKind(MarkupKind.MARKDOWN);
- content.setValue(markdown);
- return content;
- }
-
- @Override
- public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem unresolved) {
- var idJson = (JsonPrimitive) unresolved.getData();
- var id = idJson.getAsString();
- var cached = lastCompletions.get(id);
- if (cached == null) {
- LOG.warning("CompletionItem " + id + " was not in the cache");
- return CompletableFuture.completedFuture(unresolved);
- }
- if (cached.element != null) {
- if (cached.element instanceof ExecutableElement) {
- var method = (ExecutableElement) cached.element;
- var tree = server.compiler.docs().methodTree(method);
- var detail = tree.map(this::resolveDocDetail).orElse(resolveDefaultDetail(method));
- unresolved.setDetail(detail);
-
- var doc = server.compiler.docs().methodDoc(method);
- var markdown = doc.map(this::asMarkupContent);
- markdown.ifPresent(unresolved::setDocumentation);
- } else if (cached.element instanceof TypeElement) {
- var type = (TypeElement) cached.element;
- var doc = server.compiler.docs().classDoc(type);
- var markdown = doc.map(this::asMarkupContent);
- markdown.ifPresent(unresolved::setDocumentation);
- } else {
- LOG.info("Don't know how to look up docs for element " + cached.element);
- }
- // TODO constructors, fields
- } else if (cached.className != null) {
- var doc = server.compiler.docs().classDoc(cached.className.name);
- var markdown = doc.map(this::asMarkupContent);
- markdown.ifPresent(unresolved::setDocumentation);
- }
- return CompletableFuture.completedFuture(unresolved);
- }
-
- private String hoverTypeDeclaration(TypeElement t) {
- var result = new StringBuilder();
- switch (t.getKind()) {
- case ANNOTATION_TYPE:
- result.append("@interface");
- break;
- case INTERFACE:
- result.append("interface");
- break;
- case CLASS:
- result.append("class");
- break;
- case ENUM:
- result.append("enum");
- break;
- default:
- LOG.warning("Don't know what to call type element " + t);
- result.append("???");
- }
- result.append(" ").append(ShortTypePrinter.print(t.asType()));
- var superType = ShortTypePrinter.print(t.getSuperclass());
- switch (superType) {
- case "Object":
- case "none":
- break;
- default:
- result.append(" extends ").append(superType);
- }
- return result.toString();
- }
-
- private String hoverCode(Element e) {
- if (e instanceof ExecutableElement) {
- var m = (ExecutableElement) e;
- return ShortTypePrinter.printMethod(m);
- } else if (e instanceof VariableElement) {
- var v = (VariableElement) e;
- return ShortTypePrinter.print(v.asType()) + " " + v;
- } else if (e instanceof TypeElement) {
- var t = (TypeElement) e;
- var lines = new StringJoiner("\n");
- lines.add(hoverTypeDeclaration(t) + " {");
- for (var member : t.getEnclosedElements()) {
- // TODO check accessibility
- if (member instanceof ExecutableElement || member instanceof VariableElement) {
- lines.add(" " + hoverCode(member) + ";");
- } else if (member instanceof TypeElement) {
- lines.add(" " + hoverTypeDeclaration((TypeElement) member) + " { /* removed */ }");
- }
- }
- lines.add("}");
- return lines.toString();
- } else return e.toString();
- }
-
- private Optional<String> hoverDocs(Element e) {
- if (e instanceof ExecutableElement) {
- var m = (ExecutableElement) e;
- return server.compiler.docs().methodDoc(m).map(this::asMarkdown);
- } else if (e instanceof TypeElement) {
- var t = (TypeElement) e;
- return server.compiler.docs().classDoc(t).map(this::asMarkdown);
- } else return Optional.empty();
- }
-
- private CompileFile hoverCache;
-
- private void updateHoverCache(URI uri, String contents) {
- if (hoverCache == null || !hoverCache.file.equals(uri) || !hoverCache.contents.equals(contents)) {
- LOG.info("File has changed since last hover, recompiling");
- hoverCache = server.compiler.compileFile(uri, contents);
- }
- }
-
- @Override
- public CompletableFuture<Hover> hover(TextDocumentPositionParams position) {
- // Compile entire file if it's changed since last hover
- var uri = URI.create(position.getTextDocument().getUri());
- var content = contents(uri).content;
- updateHoverCache(uri, content);
-
- // Find element undeer cursor
- var line = position.getPosition().getLine() + 1;
- var column = position.getPosition().getCharacter() + 1;
- var el = hoverCache.element(line, column);
- if (!el.isPresent()) return CompletableFuture.completedFuture(new Hover(Collections.emptyList()));
-
- // Add code hover message
- var result = new ArrayList<Either<String, MarkedString>>();
- var code = hoverCode(el.get());
- result.add(Either.forRight(new MarkedString("java.hover", code)));
- // Add docs hover message
- var docs = hoverDocs(el.get());
- if (docs.isPresent()) {
- result.add(Either.forLeft(docs.get()));
- }
-
- return CompletableFuture.completedFuture(new Hover(result));
- }
-
- private List<ParameterInformation> signatureParamsFromDocs(MethodTree method, DocCommentTree doc) {
- var ps = new ArrayList<ParameterInformation>();
- var paramComments = new HashMap<String, String>();
- for (var tag : doc.getBlockTags()) {
- if (tag.getKind() == DocTree.Kind.PARAM) {
- var param = (ParamTree) tag;
- paramComments.put(param.getName().toString(), asMarkdown(param.getDescription()));
- }
- }
- for (var param : method.getParameters()) {
- var info = new ParameterInformation();
- var name = param.getName().toString();
- info.setLabel(name);
- if (paramComments.containsKey(name)) info.setDocumentation(paramComments.get(name));
- else info.setDocumentation(Objects.toString(param.getType(), ""));
- ps.add(info);
- }
- return ps;
- }
-
- private List<ParameterInformation> signatureParamsFromMethod(ExecutableElement e) {
- var missingParamNames = ShortTypePrinter.missingParamNames(e);
- var ps = new ArrayList<ParameterInformation>();
- for (var v : e.getParameters()) {
- var p = new ParameterInformation();
- if (missingParamNames) p.setLabel(ShortTypePrinter.print(v.asType()));
- else p.setLabel(v.getSimpleName().toString());
- ps.add(p);
- }
- return ps;
- }
-
- private SignatureInformation asSignatureInformation(ExecutableElement e) {
- var i = new SignatureInformation();
- var ps = signatureParamsFromMethod(e);
- var doc = server.compiler.docs().methodDoc(e);
- var tree = server.compiler.docs().methodTree(e);
- if (doc.isPresent() && tree.isPresent()) ps = signatureParamsFromDocs(tree.get(), doc.get());
- var args = ps.stream().map(p -> p.getLabel()).collect(Collectors.joining(", "));
- var name = e.getSimpleName().toString();
- if (name.equals("<init>")) name = e.getEnclosingElement().getSimpleName().toString();
- i.setLabel(name + "(" + args + ")");
- i.setParameters(ps);
- return i;
- }
-
- private SignatureHelp asSignatureHelp(MethodInvocation invoke) {
- // TODO use docs to get parameter names
- var sigs = new ArrayList<SignatureInformation>();
- for (var e : invoke.overloads) {
- sigs.add(asSignatureInformation(e));
- }
- var activeSig = invoke.activeMethod.map(invoke.overloads::indexOf).orElse(0);
- return new SignatureHelp(sigs, activeSig, invoke.activeParameter);
- }
-
- @Override
- public CompletableFuture<SignatureHelp> signatureHelp(TextDocumentPositionParams position) {
- var uri = URI.create(position.getTextDocument().getUri());
- var content = contents(uri).content;
- var line = position.getPosition().getLine() + 1;
- var column = position.getPosition().getCharacter() + 1;
- var help =
- server.compiler
- .compileFocus(uri, content, line, column)
- .methodInvocation()
- .map(this::asSignatureHelp)
- .orElse(new SignatureHelp());
- return CompletableFuture.completedFuture(help);
- }
-
- @Override
- public CompletableFuture<List<? extends Location>> definition(TextDocumentPositionParams position) {
- var fromUri = URI.create(position.getTextDocument().getUri());
- var fromLine = position.getPosition().getLine() + 1;
- var fromColumn = position.getPosition().getCharacter() + 1;
- var fromContent = contents(fromUri).content;
- var fromFocus = server.compiler.compileFocus(fromUri, fromContent, fromLine, fromColumn);
- var toEl = fromFocus.element();
- var toUri = fromFocus.declaringFile(toEl);
- if (!toUri.isPresent()) return CompletableFuture.completedFuture(List.of());
- var toContent = contents(toUri.get()).content;
- var toFile = server.compiler.compileFile(toUri.get(), toContent);
- var toPath = toFile.find(new Ptr(toEl));
- if (!toPath.isPresent()) return CompletableFuture.completedFuture(List.of());
- // Figure out where in the file the definition is
- var toRange = toFile.range(toPath.get());
- if (!toRange.isPresent()) return CompletableFuture.completedFuture(List.of());
- var to = new Location(toUri.get().toString(), toRange.get());
- return CompletableFuture.completedFuture(List.of(to));
- }
-
- class ReportProgress implements ReportReferencesProgress, AutoCloseable {
- private final Function<Integer, String> scanMessage, checkMessage;
-
- ReportProgress(
- String startMessage, Function<Integer, String> scanMessage, Function<Integer, String> checkMessage) {
- this.scanMessage = scanMessage;
- this.checkMessage = checkMessage;
- server.client().javaStartProgress(new JavaStartProgressParams(startMessage));
- }
-
- private int percent(int n, int d) {
- double nD = n, dD = d;
- double ratio = nD / dD;
- return (int) (ratio * 100);
- }
-
- public void scanForPotentialReferences(int nScanned, int nFiles) {
- var message = scanMessage.apply(nFiles);
- if (nScanned == 0) {
- server.client().javaReportProgress(new JavaReportProgressParams(message));
- } else {
- var increment = percent(nScanned, nFiles) > percent(nScanned - 1, nFiles) ? 1 : 0;
- server.client().javaReportProgress(new JavaReportProgressParams(message, increment));
- }
- }
-
- public void checkPotentialReferences(int nCompiled, int nPotential) {
- var message = checkMessage.apply(nCompiled);
- if (nCompiled == 0) {
- server.client().javaReportProgress(new JavaReportProgressParams(message));
- } else {
- var increment = percent(nCompiled, nPotential) > percent(nCompiled - 1, nPotential) ? 1 : 0;
- server.client().javaReportProgress(new JavaReportProgressParams(message, increment));
- }
- }
-
- @Override
- public void close() {
- server.client().javaEndProgress();
- }
- }
-
- @Override
- public CompletableFuture<List<? extends Location>> references(ReferenceParams position) {
- var toUri = URI.create(position.getTextDocument().getUri());
- var toContent = contents(toUri).content;
- var toLine = position.getPosition().getLine() + 1;
- var toColumn = position.getPosition().getCharacter() + 1;
- var toEl = server.compiler.compileFocus(toUri, toContent, toLine, toColumn).element();
- var fromFiles = server.compiler.potentialReferences(toEl);
- if (fromFiles.isEmpty()) return CompletableFuture.completedFuture(List.of());
- var batch = server.compiler.compileBatch(fromFiles);
- var fromTreePaths = batch.references(toEl);
- var result = new ArrayList<Location>();
- for (var path : fromTreePaths) {
- var fromUri = path.getCompilationUnit().getSourceFile().toUri();
- var fromRange = batch.range(path);
- if (!fromRange.isPresent()) {
- LOG.warning(String.format("Couldn't locate `%s`", path.getLeaf()));
- continue;
- }
- var from = new Location(fromUri.toString(), fromRange.get());
- result.add(from);
- }
- return CompletableFuture.completedFuture(result);
- }
-
- @Override
- public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(TextDocumentPositionParams position) {
- return null;
- }
-
- @Override
- public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(
- DocumentSymbolParams params) {
- var uri = URI.create(params.getTextDocument().getUri());
- var content = contents(uri).content;
- var result =
- Parser.documentSymbols(Paths.get(uri), content)
- .stream()
- .map(Parser::asSymbolInformation)
- .map(Either::<SymbolInformation, DocumentSymbol>forLeft)
- .collect(Collectors.toList());
- return CompletableFuture.completedFuture(result);
- }
-
- @Override
- public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
- return null;
- }
-
- @Override
- public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
- // TODO just create a blank code lens on every method, then resolve it async
- var uri = URI.create(params.getTextDocument().getUri());
- var content = contents(uri).content;
- var parse = server.compiler.parseFile(uri, content);
- var declarations = parse.declarations();
- var result = new ArrayList<CodeLens>();
- for (var d : declarations) {
- var range = parse.range(d);
- if (!range.isPresent()) continue;
- var className = JavaCompilerService.className(d);
- var memberName = JavaCompilerService.memberName(d);
- // If test class or method, add "Run Test" code lens
- if (parse.isTestClass(d)) {
- var command =
- new Command("Run All Tests", "java.command.test.run", Arrays.asList(uri, className, null));
- var lens = new CodeLens(range.get(), command, null);
- result.add(lens);
- // TODO run all tests in file
- // TODO run all tests in package
- } else if (parse.isTestMethod(d)) {
- var command =
- new Command(
- "Run Test",
- "java.command.test.run",
- Arrays.asList(uri, className, memberName.orElse(null)));
- var lens = new CodeLens(range.get(), command, null);
- result.add(lens);
- }
- // If method or field, add an unresolved "_ references" code lens
- if (memberName.isPresent()) {
- var start = range.get().getStart();
- var line = start.getLine();
- var character = start.getCharacter();
- List<Object> data = List.of("java.command.findReferences", uri, line, character, new Ptr(d).toString());
- var lens = new CodeLens(range.get(), null, data);
- result.add(lens);
- }
- }
- return CompletableFuture.completedFuture(result);
- }
-
- private Map<Ptr, Integer> cacheCountReferences = Collections.emptyMap();
- private URI cacheCountReferencesFile = URI.create("file:///NONE");
-
- private void updateCacheCountReferences(URI current) {
- if (cacheCountReferencesFile.equals(current)) return;
- LOG.info(String.format("Update cached reference count to %s...", current));
- var content = contents(current).content;
- cacheCountReferences = server.compiler.countReferences(current, content);
- cacheCountReferencesFile = current;
- }
-
- @Override
- public CompletableFuture<CodeLens> resolveCodeLens(CodeLens unresolved) {
- // Unpack data
- var data = (JsonArray) unresolved.getData();
- var command = data.get(0).getAsString();
- assert command.equals("java.command.findReferences");
- var uriString = data.get(1).getAsString();
- var line = data.get(2).getAsInt();
- var character = data.get(3).getAsInt();
- var ptrString = data.get(4).getAsString();
- // Parse data
- var uri = URI.create(uriString);
- var ptr = new Ptr(ptrString);
- // Update cache if necessary
- updateCacheCountReferences(uri);
- // Read reference count from cache
- var count = cacheCountReferences.getOrDefault(ptr, 0);
- // Update command
- String title;
- if (count == 1) title = "1 reference";
- else title = String.format("%d references", count);
- unresolved.setCommand(new Command(title, command, List.of(uri, line, character)));
-
- return CompletableFuture.completedFuture(unresolved);
- }
-
- private List<TextEdit> fixImports(URI java) {
- var contents = server.textDocuments.contents(java).content;
- var fix = server.compiler.compileFile(java, contents).fixImports();
- // TODO if imports already match fixed-imports, return empty list
- // TODO preserve comments and other details of existing imports
- var edits = new ArrayList<TextEdit>();
- // Delete all existing imports
- for (var i : fix.parsed.getImports()) {
- if (!i.isStatic()) {
- var offset = fix.sourcePositions.getStartPosition(fix.parsed, i);
- var line = (int) fix.parsed.getLineMap().getLineNumber(offset) - 1;
- var delete = new TextEdit(new Range(new Position(line, 0), new Position(line + 1, 0)), "");
- edits.add(delete);
- }
- }
- if (fix.fixedImports.isEmpty()) return edits;
- // Find a place to insert the new imports
- long insertLine = -1;
- var insertText = new StringBuilder();
- // If there are imports, use the start of the first import as the insert position
- for (var i : fix.parsed.getImports()) {
- if (!i.isStatic() && insertLine == -1) {
- long offset = fix.sourcePositions.getStartPosition(fix.parsed, i);
- insertLine = fix.parsed.getLineMap().getLineNumber(offset) - 1;
- }
- }
- // If there are no imports, insert after the package declaration
- if (insertLine == -1 && fix.parsed.getPackageName() != null) {
- long offset = fix.sourcePositions.getEndPosition(fix.parsed, fix.parsed.getPackageName());
- insertLine = fix.parsed.getLineMap().getLineNumber(offset);
- insertText.append("\n");
- }
- // If there are no imports and no package, insert at the top of the file
- if (insertLine == -1) {
- insertLine = 0;
- }
- // Insert each import
- fix.fixedImports
- .stream()
- .sorted()
- .forEach(
- i -> {
- insertText.append("import ").append(i).append(";\n");
- });
- var insertPosition = new Position((int) insertLine, 0);
- var insert = new TextEdit(new Range(insertPosition, insertPosition), insertText.toString());
- edits.add(insert);
- return edits;
- }
-
- @Override
- public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
- var uri = URI.create(params.getTextDocument().getUri());
- return CompletableFuture.completedFuture(fixImports(uri));
- }
-
- @Override
- public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
- return null;
- }
-
- @Override
- public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams params) {
- return null;
- }
-
- @Override
- public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
- return null;
- }
-
- private boolean isJava(URI uri) {
- return uri.getPath().endsWith(".java");
- }
-
- @Override
- public void didOpen(DidOpenTextDocumentParams params) {
- var document = params.getTextDocument();
- var uri = URI.create(document.getUri());
- if (isJava(uri)) {
- activeDocuments.put(uri, new VersionedContent(document.getText(), document.getVersion()));
- server.lint(Collections.singleton(uri));
- }
- }
-
- @Override
- public void didChange(DidChangeTextDocumentParams params) {
- var document = params.getTextDocument();
- var uri = URI.create(document.getUri());
- if (isJava(uri)) {
- var existing = activeDocuments.get(uri);
- var newText = existing.content;
-
- if (document.getVersion() > existing.version) {
- for (var change : params.getContentChanges()) {
- if (change.getRange() == null) newText = change.getText();
- else newText = patch(newText, change);
- }
-
- activeDocuments.put(uri, new VersionedContent(newText, document.getVersion()));
- } else LOG.warning("Ignored change with version " + document.getVersion() + " <= " + existing.version);
- }
- }
-
- private String patch(String sourceText, TextDocumentContentChangeEvent change) {
- try {
- var range = change.getRange();
- var reader = new BufferedReader(new StringReader(sourceText));
- var writer = new StringWriter();
-
- // Skip unchanged lines
- int line = 0;
-
- while (line < range.getStart().getLine()) {
- writer.write(reader.readLine() + '\n');
- line++;
- }
-
- // Skip unchanged chars
- for (int character = 0; character < range.getStart().getCharacter(); character++)
- writer.write(reader.read());
-
- // Write replacement text
- writer.write(change.getText());
-
- // Skip replaced text
- reader.skip(change.getRangeLength());
-
- // Write remaining text
- while (true) {
- int next = reader.read();
-
- if (next == -1) return writer.toString();
- else writer.write(next);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void didClose(DidCloseTextDocumentParams params) {
- var document = params.getTextDocument();
- var uri = URI.create(document.getUri());
- if (isJava(uri)) {
- // Remove from source cache
- activeDocuments.remove(uri);
-
- // Clear diagnostics
- server.publishDiagnostics(Collections.singletonList(uri), Collections.emptyList());
- }
- }
-
- @Override
- public void didSave(DidSaveTextDocumentParams params) {
- var uri = URI.create(params.getTextDocument().getUri());
- if (isJava(uri)) {
- // Re-lint all active documents
- server.lint(activeDocuments.keySet());
- // TODO update config when java file implies a new source root
- }
- // TODO update config when pom.xml changes
- }
-
- Set<URI> activeDocuments() {
- return activeDocuments.keySet();
- }
-
- VersionedContent contents(URI openFile) {
- if (activeDocuments.containsKey(openFile)) {
- return activeDocuments.get(openFile);
- } else {
- try {
- var content = Files.readAllLines(Paths.get(openFile)).stream().collect(Collectors.joining("\n"));
- return new VersionedContent(content, -1);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private static final Logger LOG = Logger.getLogger("main");
-}
diff --git a/src/main/java/org/javacs/JavaWorkspaceService.java b/src/main/java/org/javacs/JavaWorkspaceService.java
deleted file mode 100644
index f605396..0000000
--- a/src/main/java/org/javacs/JavaWorkspaceService.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.javacs;
-
-import com.google.gson.JsonObject;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashSet;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import org.javacs.lsp.*;
-
-class JavaWorkspaceService implements WorkspaceService {
- private static final Logger LOG = Logger.getLogger("main");
-
- private final JavaLanguageServer server;
-
- JavaWorkspaceService(JavaLanguageServer server) {
- this.server = server;
- }
-
- @Override
- public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
- return null;
- }
-
- @Override
- public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams params) {
- List<SymbolInformation> list =
- server.compiler
- .findSymbols(params.getQuery())
- .map(Parser::asSymbolInformation)
- .limit(50)
- .collect(Collectors.toList());
- return CompletableFuture.completedFuture(list);
- }
-
- @Override
- public void didChangeConfiguration(DidChangeConfigurationParams change) {
- var settings = (JsonObject) change.getSettings();
- var java = settings.getAsJsonObject("java");
-
- var externalDependencies = java.getAsJsonArray("externalDependencies");
- var strings = new HashSet<String>();
- for (var each : externalDependencies) strings.add(each.getAsString());
- server.setExternalDependencies(strings);
-
- var classPath = java.getAsJsonArray("classPath");
- var paths = new HashSet<Path>();
- for (var each : classPath) paths.add(Paths.get(each.getAsString()).toAbsolutePath());
- server.setClassPath(paths);
- }
-
- @Override
- public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {}
-}
diff --git a/src/main/java/org/javacs/Main.java b/src/main/java/org/javacs/Main.java
index 985c17b..f8dd455 100644
--- a/src/main/java/org/javacs/Main.java
+++ b/src/main/java/org/javacs/Main.java
@@ -1,6 +1,5 @@
package org.javacs;
-import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.javacs.lsp.*;
@@ -14,25 +13,16 @@ public class Main {
for (var h : root.getHandlers()) h.setFormatter(new LogFormat());
}
+ private JavaLanguageServer createServer(LanguageClient client) {
+ return new JavaLanguageServer(client);
+ }
+
public static void main(String[] args) {
try {
// Logger.getLogger("").addHandler(new FileHandler("javacs.%u.log", false));
setRootFormat();
- var server = new JavaLanguageServer();
- var threads = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "client"));
- var launcher =
- new Launcher.Builder<CustomLanguageClient>()
- .setLocalService(server)
- .setRemoteInterface(CustomLanguageClient.class)
- .setInput(System.in)
- .setOutput(System.out)
- .setExecutorService(threads)
- .create();
-
- server.installClient(launcher.getRemoteProxy());
- launcher.startListening();
- LOG.info(String.format("java.version is %s", System.getProperty("java.version")));
+ LSP.connect(JavaLanguageServer::new, System.in, System.out);
} catch (Throwable t) {
LOG.log(Level.SEVERE, t.getMessage(), t);
diff --git a/src/main/java/org/javacs/Parser.java b/src/main/java/org/javacs/Parser.java
index 13cf305..f67b0af 100644
--- a/src/main/java/org/javacs/Parser.java
+++ b/src/main/java/org/javacs/Parser.java
@@ -91,7 +91,7 @@ class Parser {
return true;
}
- private static void onError(Diagnostic<? extends JavaFileObject> err) {
+ private static void onError(javax.tools.Diagnostic<? extends JavaFileObject> err) {
// Too noisy, this only comes up in parse tasks which tend to be less important
// LOG.warning(err.getMessage(Locale.getDefault()));
}
@@ -185,11 +185,10 @@ class Parser {
int startLine = (int) lines.getLineNumber(start) - 1, startCol = (int) lines.getColumnNumber(start) - 1;
int endLine = (int) lines.getLineNumber(end) - 1, endCol = (int) lines.getColumnNumber(end) - 1;
var dUri = cu.getSourceFile().toUri();
- return new Location(
- dUri.toString(), new Range(new Position(startLine, startCol), new Position(endLine, endCol)));
+ return new Location(dUri, new Range(new Position(startLine, startCol), new Position(endLine, endCol)));
}
- private static SymbolKind asSymbolKind(Tree.Kind k) {
+ private static Integer asSymbolKind(Tree.Kind k) {
switch (k) {
case ANNOTATION_TYPE:
case CLASS:
@@ -247,10 +246,10 @@ class Parser {
static SymbolInformation asSymbolInformation(TreePath path) {
var i = new SymbolInformation();
var t = path.getLeaf();
- i.setKind(asSymbolKind(t.getKind()));
- i.setName(symbolName(t));
- i.setContainerName(containerName(path));
- i.setLocation(Parser.location(path));
+ i.kind = asSymbolKind(t.getKind());
+ i.name = symbolName(t);
+ i.containerName = containerName(path);
+ i.location = Parser.location(path);
return i;
}
diff --git a/src/main/java/org/javacs/lsp/CancelParams.java b/src/main/java/org/javacs/lsp/CancelParams.java
index cf7acce..cb75622 100644
--- a/src/main/java/org/javacs/lsp/CancelParams.java
+++ b/src/main/java/org/javacs/lsp/CancelParams.java
@@ -1,5 +1,5 @@
package org.javacs.lsp;
public class CancelParams {
- public String id;
+ public int id;
}
diff --git a/src/main/java/org/javacs/lsp/CodeLens.java b/src/main/java/org/javacs/lsp/CodeLens.java
index 79158a7..31da084 100644
--- a/src/main/java/org/javacs/lsp/CodeLens.java
+++ b/src/main/java/org/javacs/lsp/CodeLens.java
@@ -6,4 +6,12 @@ public class CodeLens {
public Range range;
public Command command;
public JsonArray data;
+
+ public CodeLens() {}
+
+ public CodeLens(Range range, Command command, JsonArray data) {
+ this.range = range;
+ this.command = command;
+ this.data = data;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/CodeLensParams.java b/src/main/java/org/javacs/lsp/CodeLensParams.java
index 7958ac7..65b4afd 100644
--- a/src/main/java/org/javacs/lsp/CodeLensParams.java
+++ b/src/main/java/org/javacs/lsp/CodeLensParams.java
@@ -2,4 +2,10 @@ package org.javacs.lsp;
public class CodeLensParams {
public TextDocumentIdentifier textDocument;
+
+ public CodeLensParams() {}
+
+ public CodeLensParams(TextDocumentIdentifier textDocument) {
+ this.textDocument = textDocument;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/Command.java b/src/main/java/org/javacs/lsp/Command.java
index a212c92..979d6cc 100644
--- a/src/main/java/org/javacs/lsp/Command.java
+++ b/src/main/java/org/javacs/lsp/Command.java
@@ -5,4 +5,12 @@ import com.google.gson.JsonArray;
public class Command {
public String title, command;
public JsonArray arguments;
+
+ public Command() {}
+
+ public Command(String title, String command, JsonArray arguments) {
+ this.title = title;
+ this.command = command;
+ this.arguments = arguments;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/CompletionContext.java b/src/main/java/org/javacs/lsp/CompletionContext.java
deleted file mode 100644
index f10547f..0000000
--- a/src/main/java/org/javacs/lsp/CompletionContext.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.javacs.lsp;
-
-public class CompletionContext {
- public int triggerKind;
- public String triggerCharacter;
-}
diff --git a/src/main/java/org/javacs/lsp/CompletionItem.java b/src/main/java/org/javacs/lsp/CompletionItem.java
index 1a46753..678a535 100644
--- a/src/main/java/org/javacs/lsp/CompletionItem.java
+++ b/src/main/java/org/javacs/lsp/CompletionItem.java
@@ -1,6 +1,6 @@
package org.javacs.lsp;
-import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import java.util.List;
public class CompletionItem {
@@ -9,10 +9,11 @@ public class CompletionItem {
public String detail;
public MarkupContent documentation;
public boolean deprecated, preselect;
- public String sortText, filterText, insertText, insertTextFormat;
+ public String sortText, filterText, insertText;
+ public int insertTextFormat;
public TextEdit textEdit;
public List<TextEdit> additionalTextEdits;
public List<Character> commitCharacters;
public Command command;
- public JsonArray data;
+ public JsonElement data;
}
diff --git a/src/main/java/org/javacs/lsp/CompletionList.java b/src/main/java/org/javacs/lsp/CompletionList.java
index 48d3155..777a80f 100644
--- a/src/main/java/org/javacs/lsp/CompletionList.java
+++ b/src/main/java/org/javacs/lsp/CompletionList.java
@@ -5,4 +5,11 @@ import java.util.List;
public class CompletionList {
public boolean isIncomplete;
public List<CompletionItem> items;
+
+ public CompletionList() {}
+
+ public CompletionList(boolean isIncomplete, List<CompletionItem> items) {
+ this.isIncomplete = isIncomplete;
+ this.items = items;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/CompletionParams.java b/src/main/java/org/javacs/lsp/CompletionParams.java
deleted file mode 100644
index c3dd2ef..0000000
--- a/src/main/java/org/javacs/lsp/CompletionParams.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.javacs.lsp;
-
-public class CompletionParams extends TextDocumentPositionParams {
- public CompletionContext context;
-}
diff --git a/src/main/java/org/javacs/lsp/Diagnostic.java b/src/main/java/org/javacs/lsp/Diagnostic.java
index 75986f7..404957e 100644
--- a/src/main/java/org/javacs/lsp/Diagnostic.java
+++ b/src/main/java/org/javacs/lsp/Diagnostic.java
@@ -2,6 +2,6 @@ package org.javacs.lsp;
public class Diagnostic {
public Range range;
- public int severity;
+ public Integer severity;
public String code, source, message;
}
diff --git a/src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java b/src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java
index 1083a95..1c12d1e 100644
--- a/src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java
+++ b/src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java
@@ -2,4 +2,10 @@ package org.javacs.lsp;
public class DidOpenTextDocumentParams {
public TextDocumentItem textDocument;
+
+ public DidOpenTextDocumentParams() {}
+
+ public DidOpenTextDocumentParams(TextDocumentItem textDocument) {
+ this.textDocument = textDocument;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/DocumentFormattingParams.java b/src/main/java/org/javacs/lsp/DocumentFormattingParams.java
new file mode 100644
index 0000000..4ccee64
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DocumentFormattingParams.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class DocumentFormattingParams {
+ public TextDocumentIdentifier textDocument;
+ public FormattingOptions options;
+}
diff --git a/src/main/java/org/javacs/lsp/DocumentSymbolParams.java b/src/main/java/org/javacs/lsp/DocumentSymbolParams.java
index ff8e740..9670d50 100644
--- a/src/main/java/org/javacs/lsp/DocumentSymbolParams.java
+++ b/src/main/java/org/javacs/lsp/DocumentSymbolParams.java
@@ -2,4 +2,10 @@ package org.javacs.lsp;
public class DocumentSymbolParams {
public TextDocumentIdentifier textDocument;
+
+ public DocumentSymbolParams() {}
+
+ public DocumentSymbolParams(TextDocumentIdentifier textDocument) {
+ this.textDocument = textDocument;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/FormattingOptions.java b/src/main/java/org/javacs/lsp/FormattingOptions.java
new file mode 100644
index 0000000..28f0da3
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/FormattingOptions.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+public class FormattingOptions {
+ public int tabSize;
+ public boolean insertSpaces;
+ // TODO other properties embedded at same level
+}
diff --git a/src/main/java/org/javacs/lsp/Hover.java b/src/main/java/org/javacs/lsp/Hover.java
index 25dadd0..972c408 100644
--- a/src/main/java/org/javacs/lsp/Hover.java
+++ b/src/main/java/org/javacs/lsp/Hover.java
@@ -5,4 +5,15 @@ import java.util.List;
public class Hover {
public List<MarkedString> contents;
public Range range;
+
+ public Hover() {}
+
+ public Hover(List<MarkedString> contents) {
+ this.contents = contents;
+ }
+
+ public Hover(List<MarkedString> contents, Range range) {
+ this.contents = contents;
+ this.range = range;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/InitializeResult.java b/src/main/java/org/javacs/lsp/InitializeResult.java
index a8c8cee..8a56e2f 100644
--- a/src/main/java/org/javacs/lsp/InitializeResult.java
+++ b/src/main/java/org/javacs/lsp/InitializeResult.java
@@ -4,4 +4,10 @@ import com.google.gson.JsonObject;
public class InitializeResult {
public JsonObject capabilities;
+
+ public InitializeResult() {}
+
+ public InitializeResult(JsonObject capabilities) {
+ this.capabilities = capabilities;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/LSP.java b/src/main/java/org/javacs/lsp/LSP.java
index 06da2d7..f9b4572 100644
--- a/src/main/java/org/javacs/lsp/LSP.java
+++ b/src/main/java/org/javacs/lsp/LSP.java
@@ -4,8 +4,10 @@ import com.google.common.base.Charsets;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import java.io.*;
+import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -15,13 +17,13 @@ public class LSP {
private static String readHeader(InputStream client) {
var line = new StringBuilder();
- for (var next = read(client); next != -1; next = read(client)) {
+ for (var next = read(client); true; next = read(client)) {
if (next == '\r') {
var last = read(client);
assert last == '\n';
break;
}
- line.append((char) next);
+ line.append(next);
}
return line.toString();
}
@@ -36,9 +38,16 @@ public class LSP {
return -1;
}
- private static int read(InputStream client) {
+ static class EndOfStream extends RuntimeException {}
+
+ private static char read(InputStream client) {
try {
- return client.read();
+ var c = client.read();
+ if (c == -1) {
+ LOG.warning("Stream from client has been closed, throwing kill exception...");
+ throw new EndOfStream();
+ }
+ return (char) c;
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -48,15 +57,14 @@ public class LSP {
// Eat whitespace
// Have observed problems with extra \r\n sequences from VSCode
var next = read(client);
- while (next != -1 && Character.isWhitespace(next)) {
+ while (Character.isWhitespace(next)) {
next = read(client);
}
// Append next
var result = new StringBuilder();
var i = 0;
while (true) {
- if (next == -1) break;
- result.append((char) next);
+ result.append(next);
i++;
if (i == byteLength) break;
next = read(client);
@@ -93,12 +101,20 @@ public class LSP {
}
static void respond(OutputStream client, int requestId, Object params) {
+ if (params instanceof Optional) {
+ var option = (Optional) params;
+ params = option.orElse(null);
+ }
var jsonText = gson.toJson(params);
var messageText = String.format("{\"jsonrpc\":\"2.0\",\"id\":%d,\"result\":%s}", requestId, jsonText);
writeClient(client, messageText);
}
private static void notifyClient(OutputStream client, String method, Object params) {
+ if (params instanceof Optional) {
+ var option = (Optional) params;
+ params = option.orElse(null);
+ }
var jsonText = gson.toJson(params);
var messageText = String.format("{\"jsonrpc\":\"2.0\",\"method\":\"%s\",\"params\":%s}", method, jsonText);
writeClient(client, messageText);
@@ -113,49 +129,58 @@ public class LSP {
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
- var json = gson.toJson(params);
- notifyClient(send, "textDocument/publishDiagnostics", json);
+ notifyClient(send, "textDocument/publishDiagnostics", params);
}
@Override
public void showMessage(ShowMessageParams params) {
- var json = gson.toJson(params);
- notifyClient(send, "window/showMessage", json);
+ notifyClient(send, "window/showMessage", params);
}
@Override
public void registerCapability(String id, JsonElement options) {
- var p = new RegistrationParams();
- p.id = UUID.randomUUID().toString();
- p.id = id;
- p.registerOptions = options;
- var json = gson.toJson(p);
- notifyClient(send, "client/registerCapability", json);
+ var params = new RegistrationParams();
+ params.id = UUID.randomUUID().toString();
+ params.id = id;
+ params.registerOptions = options;
+
+ notifyClient(send, "client/registerCapability", params);
}
@Override
public void customNotification(String method, JsonElement params) {
- var json = gson.toJson(params);
- notifyClient(send, method, json);
+ notifyClient(send, method, params);
}
}
public static void connect(
Function<LanguageClient, LanguageServer> serverFactory, InputStream receive, OutputStream send) {
var server = serverFactory.apply(new RealClient(send));
- var pendingRequests = new ArrayBlockingQueue<Message>(10);
+ var pending = new ArrayBlockingQueue<Message>(10);
+ var endOfStream = new Message();
// Read messages and process cancellations on a separate thread
class MessageReader implements Runnable {
void peek(Message message) {
if (message.method.equals("$/cancelRequest")) {
var params = gson.fromJson(message.params, CancelParams.class);
- var removed = pendingRequests.removeIf(r -> r.id.equals(params.id));
+ var removed = pending.removeIf(r -> r.id != null && r.id.equals(params.id));
if (removed) LOG.info(String.format("Cancelled request %d, which had not yet started", params.id));
else LOG.info(String.format("Cannot cancel request %d because it has already started", params.id));
}
}
+ private boolean kill() {
+ LOG.info("Read stream has been closed, putting kill message onto queue...");
+ try {
+ pending.put(endOfStream);
+ return true;
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, "Failed to put kill message onto queue, will try again...", e);
+ return false;
+ }
+ }
+
@Override
public void run() {
LOG.info("Placing incoming messages on queue...");
@@ -165,10 +190,9 @@ public class LSP {
var token = nextToken(receive);
var message = parseMessage(token);
peek(message);
- pendingRequests.put(message);
- LOG.info(
- String.format(
- "Added message %d to queue, length is %d", message.id, pendingRequests.size()));
+ pending.put(message);
+ } catch (EndOfStream __) {
+ if (kill()) return;
} catch (Exception e) {
LOG.log(Level.SEVERE, e.getMessage(), e);
}
@@ -184,14 +208,22 @@ public class LSP {
processMessages:
while (true) {
try {
- var request = pendingRequests.take();
- LOG.info(String.format("Read message %d from queue, length is %d", request.id, pendingRequests.size()));
- switch (request.method) {
+ // Take a break every 1s to check if receive has been closed
+ var r = pending.poll(1, TimeUnit.SECONDS);
+ // If receive has been closed, exit
+ if (r == endOfStream) {
+ LOG.warning("Stream from client has been closed, exiting...");
+ break processMessages;
+ }
+ // If poll(_) failed, loop again
+ if (r == null) continue;
+ // Otherwise, process the new message
+ switch (r.method) {
case "initialize":
{
- var params = gson.fromJson(request.params, InitializeParams.class);
+ var params = gson.fromJson(r.params, InitializeParams.class);
var response = server.initialize(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "initialized":
@@ -211,145 +243,152 @@ public class LSP {
}
case "workspace/didChangeWorkspaceFolders":
{
- var params = gson.fromJson(request.params, DidChangeWorkspaceFoldersParams.class);
+ var params = gson.fromJson(r.params, DidChangeWorkspaceFoldersParams.class);
server.didChangeWorkspaceFolders(params);
break;
}
case "workspace/didChangeConfiguration":
{
- var params = gson.fromJson(request.params, DidChangeConfigurationParams.class);
+ var params = gson.fromJson(r.params, DidChangeConfigurationParams.class);
server.didChangeConfiguration(params);
break;
}
case "workspace/didChangeWatchedFiles":
{
- var params = gson.fromJson(request.params, DidChangeWatchedFilesParams.class);
+ var params = gson.fromJson(r.params, DidChangeWatchedFilesParams.class);
server.didChangeWatchedFiles(params);
break;
}
case "workspace/symbols":
{
- var params = gson.fromJson(request.params, WorkspaceSymbolParams.class);
+ var params = gson.fromJson(r.params, WorkspaceSymbolParams.class);
var response = server.workspaceSymbols(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/didOpen":
{
- var params = gson.fromJson(request.params, DidOpenTextDocumentParams.class);
+ var params = gson.fromJson(r.params, DidOpenTextDocumentParams.class);
server.didOpenTextDocument(params);
break;
}
case "textDocument/didChange":
{
- var params = gson.fromJson(request.params, DidChangeTextDocumentParams.class);
+ var params = gson.fromJson(r.params, DidChangeTextDocumentParams.class);
server.didChangeTextDocument(params);
break;
}
case "textDocument/willSave":
{
- var params = gson.fromJson(request.params, WillSaveTextDocumentParams.class);
+ var params = gson.fromJson(r.params, WillSaveTextDocumentParams.class);
server.willSaveTextDocument(params);
break;
}
case "textDocument/willSaveWaitUntil":
{
- var params = gson.fromJson(request.params, WillSaveTextDocumentParams.class);
+ var params = gson.fromJson(r.params, WillSaveTextDocumentParams.class);
var response = server.willSaveWaitUntilTextDocument(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/didSave":
{
- var params = gson.fromJson(request.params, DidSaveTextDocumentParams.class);
+ var params = gson.fromJson(r.params, DidSaveTextDocumentParams.class);
server.didSaveTextDocument(params);
break;
}
case "textDocument/didClose":
{
- var params = gson.fromJson(request.params, DidCloseTextDocumentParams.class);
+ var params = gson.fromJson(r.params, DidCloseTextDocumentParams.class);
server.didCloseTextDocument(params);
break;
}
case "textDocument/completion":
{
- var params = gson.fromJson(request.params, CompletionParams.class);
+ var params = gson.fromJson(r.params, TextDocumentPositionParams.class);
var response = server.completion(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "completionItem/resolve":
{
- var params = gson.fromJson(request.params, CompletionItem.class);
+ var params = gson.fromJson(r.params, CompletionItem.class);
var response = server.resolveCompletionItem(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/hover":
{
- var params = gson.fromJson(request.params, TextDocumentPositionParams.class);
+ var params = gson.fromJson(r.params, TextDocumentPositionParams.class);
var response = server.hover(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/signatureHelp":
{
- var params = gson.fromJson(request.params, TextDocumentPositionParams.class);
+ var params = gson.fromJson(r.params, TextDocumentPositionParams.class);
var response = server.signatureHelp(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/definition":
{
- var params = gson.fromJson(request.params, TextDocumentPositionParams.class);
+ var params = gson.fromJson(r.params, TextDocumentPositionParams.class);
var response = server.gotoDefinition(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/references":
{
- var params = gson.fromJson(request.params, ReferenceParams.class);
+ var params = gson.fromJson(r.params, ReferenceParams.class);
var response = server.findReferences(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/documentSymbol":
{
- var params = gson.fromJson(request.params, DocumentSymbolParams.class);
+ var params = gson.fromJson(r.params, DocumentSymbolParams.class);
var response = server.documentSymbol(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/codeAction":
{
- var params = gson.fromJson(request.params, CodeActionParams.class);
+ var params = gson.fromJson(r.params, CodeActionParams.class);
var response = server.codeAction(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/codeLens":
{
- var params = gson.fromJson(request.params, CodeLensParams.class);
+ var params = gson.fromJson(r.params, CodeLensParams.class);
var response = server.codeLens(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "codeLens/resolve":
{
- var params = gson.fromJson(request.params, CodeLens.class);
+ var params = gson.fromJson(r.params, CodeLens.class);
var response = server.resolveCodeLens(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
break;
}
case "textDocument/rename":
{
- var params = gson.fromJson(request.params, RenameParams.class);
+ var params = gson.fromJson(r.params, RenameParams.class);
var response = server.rename(params);
- respond(send, request.id, response);
+ respond(send, r.id, response);
+ break;
+ }
+ case "textDocument/formatting":
+ {
+ var params = gson.fromJson(r.params, DocumentFormattingParams.class);
+ var response = server.formatting(params);
+ respond(send, r.id, response);
break;
}
default:
- LOG.warning(String.format("Don't know what to do with method `%s`", request.method));
+ LOG.warning(String.format("Don't know what to do with method `%s`", r.method));
}
} catch (Exception e) {
LOG.log(Level.SEVERE, e.getMessage(), e);
diff --git a/src/main/java/org/javacs/lsp/LanguageServer.java b/src/main/java/org/javacs/lsp/LanguageServer.java
index e969c2e..c46808f 100644
--- a/src/main/java/org/javacs/lsp/LanguageServer.java
+++ b/src/main/java/org/javacs/lsp/LanguageServer.java
@@ -99,4 +99,8 @@ public class LanguageServer {
public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) {
throw new RuntimeException("Unimplemented");
}
+
+ public List<TextEdit> formatting(DocumentFormattingParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
}
diff --git a/src/main/java/org/javacs/lsp/Location.java b/src/main/java/org/javacs/lsp/Location.java
index b35bf82..8484c78 100644
--- a/src/main/java/org/javacs/lsp/Location.java
+++ b/src/main/java/org/javacs/lsp/Location.java
@@ -3,6 +3,13 @@ package org.javacs.lsp;
import java.net.URI;
public class Location {
- URI uri;
- Range range;
+ public URI uri;
+ public Range range;
+
+ public Location() {}
+
+ public Location(URI uri, Range range) {
+ this.uri = uri;
+ this.range = range;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/MarkedString.java b/src/main/java/org/javacs/lsp/MarkedString.java
index da0f33f..d042d78 100644
--- a/src/main/java/org/javacs/lsp/MarkedString.java
+++ b/src/main/java/org/javacs/lsp/MarkedString.java
@@ -2,4 +2,11 @@ package org.javacs.lsp;
public class MarkedString {
public String language, value;
+
+ public MarkedString() {}
+
+ public MarkedString(String language, String value) {
+ this.language = language;
+ this.value = value;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/MarkupContent.java b/src/main/java/org/javacs/lsp/MarkupContent.java
index 3e2d58f..11b4f43 100644
--- a/src/main/java/org/javacs/lsp/MarkupContent.java
+++ b/src/main/java/org/javacs/lsp/MarkupContent.java
@@ -1,6 +1,12 @@
package org.javacs.lsp;
public class MarkupContent {
- public MarkupKind kind;
- public String value;
+ public String kind, value;
+
+ public MarkupContent() {}
+
+ public MarkupContent(String kind, String value) {
+ this.kind = kind;
+ this.value = value;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/Position.java b/src/main/java/org/javacs/lsp/Position.java
index ae6b466..6c1fa40 100644
--- a/src/main/java/org/javacs/lsp/Position.java
+++ b/src/main/java/org/javacs/lsp/Position.java
@@ -3,4 +3,11 @@ package org.javacs.lsp;
public class Position {
// 0-based
public int line, character;
+
+ public Position() {}
+
+ public Position(int line, int character) {
+ this.line = line;
+ this.character = character;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java b/src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java
index 8e19d05..f2502c9 100644
--- a/src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java
+++ b/src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java
@@ -6,4 +6,11 @@ import java.util.List;
public class PublishDiagnosticsParams {
public URI uri;
public List<Diagnostic> diagnostics;
+
+ public PublishDiagnosticsParams() {}
+
+ public PublishDiagnosticsParams(URI uri, List<Diagnostic> diagnostics) {
+ this.uri = uri;
+ this.diagnostics = diagnostics;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/Range.java b/src/main/java/org/javacs/lsp/Range.java
index 0d0e3c0..95bb123 100644
--- a/src/main/java/org/javacs/lsp/Range.java
+++ b/src/main/java/org/javacs/lsp/Range.java
@@ -2,4 +2,11 @@ package org.javacs.lsp;
public class Range {
public Position start, end;
+
+ public Range() {}
+
+ public Range(Position start, Position end) {
+ this.start = start;
+ this.end = end;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/SignatureHelp.java b/src/main/java/org/javacs/lsp/SignatureHelp.java
index eb25037..15fe366 100644
--- a/src/main/java/org/javacs/lsp/SignatureHelp.java
+++ b/src/main/java/org/javacs/lsp/SignatureHelp.java
@@ -4,6 +4,13 @@ import java.util.List;
public class SignatureHelp {
public List<SignatureInformation> signatures;
- public Integer activeSignature;
- public Integer activeParameter;
+ public Integer activeSignature, activeParameter;
+
+ public SignatureHelp() {}
+
+ public SignatureHelp(List<SignatureInformation> signatures, Integer activeSignature, Integer activeParameter) {
+ this.signatures = signatures;
+ this.activeSignature = activeSignature;
+ this.activeParameter = activeParameter;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentIdentifier.java b/src/main/java/org/javacs/lsp/TextDocumentIdentifier.java
index dce19b8..15f99dd 100644
--- a/src/main/java/org/javacs/lsp/TextDocumentIdentifier.java
+++ b/src/main/java/org/javacs/lsp/TextDocumentIdentifier.java
@@ -4,4 +4,10 @@ import java.net.URI;
public class TextDocumentIdentifier {
public URI uri;
+
+ public TextDocumentIdentifier() {}
+
+ public TextDocumentIdentifier(URI uri) {
+ this.uri = uri;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentPositionParams.java b/src/main/java/org/javacs/lsp/TextDocumentPositionParams.java
index 1d719cb..0a20afa 100644
--- a/src/main/java/org/javacs/lsp/TextDocumentPositionParams.java
+++ b/src/main/java/org/javacs/lsp/TextDocumentPositionParams.java
@@ -3,4 +3,11 @@ package org.javacs.lsp;
public class TextDocumentPositionParams {
public TextDocumentIdentifier textDocument;
public Position position;
+
+ public TextDocumentPositionParams() {}
+
+ public TextDocumentPositionParams(TextDocumentIdentifier textDocument, Position position) {
+ this.textDocument = textDocument;
+ this.position = position;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/TextEdit.java b/src/main/java/org/javacs/lsp/TextEdit.java
index 03fe4e5..9278adb 100644
--- a/src/main/java/org/javacs/lsp/TextEdit.java
+++ b/src/main/java/org/javacs/lsp/TextEdit.java
@@ -3,4 +3,11 @@ package org.javacs.lsp;
public class TextEdit {
public Range range;
public String newText;
+
+ public TextEdit() {}
+
+ public TextEdit(Range range, String newText) {
+ this.range = range;
+ this.newText = newText;
+ }
}
diff --git a/src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java b/src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java
index d3859f9..eea3c4a 100644
--- a/src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java
+++ b/src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java
@@ -2,4 +2,10 @@ package org.javacs.lsp;
public class WorkspaceSymbolParams {
public String query;
+
+ public WorkspaceSymbolParams() {}
+
+ public WorkspaceSymbolParams(String query) {
+ this.query = query;
+ }
}
diff --git a/src/test/java/org/javacs/CodeLensTest.java b/src/test/java/org/javacs/CodeLensTest.java
index 47f265d..29935c5 100644
--- a/src/test/java/org/javacs/CodeLensTest.java
+++ b/src/test/java/org/javacs/CodeLensTest.java
@@ -6,9 +6,6 @@ import static org.junit.Assert.*;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
-import java.util.StringJoiner;
-import java.util.concurrent.ExecutionException;
import org.javacs.lsp.*;
import org.junit.Test;
@@ -18,34 +15,24 @@ public class CodeLensTest {
private List<? extends CodeLens> lenses(String file) {
var uri = FindResource.uri(file);
- var params = new CodeLensParams(new TextDocumentIdentifier(uri.toString()));
- try {
- var lenses = server.getTextDocumentService().codeLens(params).get();
- var resolved = new ArrayList<CodeLens>();
- for (var lens : lenses) {
- if (lens.getCommand() == null) {
- var gson = new Gson();
- var data = lens.getData();
- var dataJson = gson.toJsonTree(data);
- lens.setData(dataJson);
- lens = server.getTextDocumentService().resolveCodeLens(lens).get();
- }
- resolved.add(lens);
+ var params = new CodeLensParams(new TextDocumentIdentifier(uri));
+ var lenses = server.codeLens(params);
+ var resolved = new ArrayList<CodeLens>();
+ for (var lens : lenses) {
+ if (lens.command == null) {
+ var gson = new Gson();
+ var data = lens.data;
+ lens = server.resolveCodeLens(lens);
}
- return resolved;
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
+ resolved.add(lens);
}
+ return resolved;
}
private List<String> commands(List<? extends CodeLens> lenses) {
var commands = new ArrayList<String>();
for (var lens : lenses) {
- var command = new StringJoiner(", ");
- for (var arg : lens.getCommand().getArguments()) {
- command.add(Objects.toString(arg));
- }
- commands.add(command.toString());
+ commands.add(String.format("%s(%s)", lens.command.command, lens.command.arguments));
}
return commands;
}
@@ -53,8 +40,8 @@ public class CodeLensTest {
private List<String> titles(List<? extends CodeLens> lenses) {
var titles = new ArrayList<String>();
for (var lens : lenses) {
- var line = lens.getRange().getStart().getLine() + 1;
- var title = lens.getCommand().getTitle();
+ var line = lens.range.start.line + 1;
+ var title = lens.command.title;
titles.add(line + ":" + title);
}
return titles;
@@ -66,9 +53,9 @@ public class CodeLensTest {
assertThat(lenses, not(empty()));
var commands = commands(lenses);
- assertThat(commands, hasItem(containsString("HasTest, null")));
- assertThat(commands, hasItem(containsString("HasTest, testMethod")));
- assertThat(commands, hasItem(containsString("HasTest, otherTestMethod")));
+ assertThat(commands, hasItem(containsString("\"HasTest\",null")));
+ assertThat(commands, hasItem(containsString("\"HasTest\",\"testMethod\"")));
+ assertThat(commands, hasItem(containsString("\"HasTest\",\"otherTestMethod\"")));
}
@Test
diff --git a/src/test/java/org/javacs/CompletionsBase.java b/src/test/java/org/javacs/CompletionsBase.java
index 8ad10a8..30af123 100644
--- a/src/test/java/org/javacs/CompletionsBase.java
+++ b/src/test/java/org/javacs/CompletionsBase.java
@@ -2,7 +2,6 @@ package org.javacs;
import com.google.gson.Gson;
import java.util.*;
-import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -18,9 +17,9 @@ public class CompletionsBase {
}
static String itemInsertTemplate(CompletionItem i) {
- var text = i.getInsertText();
+ var text = i.insertText;
- if (text == null) text = i.getLabel();
+ if (text == null) text = i.label;
assert text != null : "Either insertText or label must be defined";
@@ -37,9 +36,9 @@ public class CompletionsBase {
var items = items(file, row, column);
var result = new HashSet<String>();
for (var i : items) {
- i.setData(new Gson().toJsonTree(i.getData()));
+ i.data = new Gson().toJsonTree(i.data);
var resolved = resolve(i);
- result.add(resolved.getDetail());
+ result.add(resolved.detail);
}
return result;
}
@@ -59,9 +58,9 @@ public class CompletionsBase {
}
static String itemInsertText(CompletionItem i) {
- var text = i.getInsertText();
+ var text = i.insertText;
- if (text == null) text = i.getLabel();
+ if (text == null) text = i.label;
assert text != null : "Either insertText or label must be defined";
@@ -76,8 +75,7 @@ public class CompletionsBase {
return items.stream()
.flatMap(
i -> {
- if (i.getDocumentation() != null)
- return Stream.of(i.getDocumentation().getRight().getValue().trim());
+ if (i.documentation != null) return Stream.of(i.documentation.value.trim());
else return Stream.empty();
})
.collect(Collectors.toSet());
@@ -88,20 +86,12 @@ public class CompletionsBase {
protected List<? extends CompletionItem> items(String file, int row, int column) {
var uri = FindResource.uri(file);
var position =
- new CompletionParams(new TextDocumentIdentifier(uri.toString()), new Position(row - 1, column - 1));
+ new TextDocumentPositionParams(new TextDocumentIdentifier(uri), new Position(row - 1, column - 1));
- try {
- return server.getTextDocumentService().completion(position).get().getRight().getItems();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
+ return server.completion(position).get().items;
}
protected CompletionItem resolve(CompletionItem item) {
- try {
- return server.getTextDocumentService().resolveCompletionItem(item).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
+ return server.resolveCompletionItem(item);
}
}
diff --git a/src/test/java/org/javacs/CompletionsTest.java b/src/test/java/org/javacs/CompletionsTest.java
index 864ff24..d213a74 100644
--- a/src/test/java/org/javacs/CompletionsTest.java
+++ b/src/test/java/org/javacs/CompletionsTest.java
@@ -215,8 +215,8 @@ public class CompletionsTest extends CompletionsBase {
}
private static String sortText(CompletionItem i) {
- if (i.getSortText() != null) return i.getSortText();
- else return i.getLabel();
+ if (i.sortText != null) return i.sortText;
+ else return i.label;
}
@Test
@@ -288,9 +288,9 @@ public class CompletionsTest extends CompletionsBase {
var items = items(file, 9, 17);
for (var item : items) {
- if ("ArrayList".equals(item.getLabel())) {
- assertThat(item.getAdditionalTextEdits(), not(nullValue()));
- assertThat(item.getAdditionalTextEdits(), not(empty()));
+ if ("ArrayList".equals(item.label)) {
+ assertThat(item.additionalTextEdits, not(nullValue()));
+ assertThat(item.additionalTextEdits, not(empty()));
return;
}
@@ -308,8 +308,8 @@ public class CompletionsTest extends CompletionsBase {
var items = items(file, 6, 10);
for (var item : items) {
- if ("AutocompleteMember".equals(item.getLabel())) {
- assertThat(item.getAdditionalTextEdits(), either(empty()).or(nullValue()));
+ if ("AutocompleteMember".equals(item.label)) {
+ assertThat(item.additionalTextEdits, either(empty()).or(nullValue()));
return;
}
@@ -327,8 +327,8 @@ public class CompletionsTest extends CompletionsBase {
var items = items(file, 11, 38);
for (var item : items) {
- if ("ArrayIndexOutOfBoundsException".equals(item.getLabel())) {
- assertThat(item.getAdditionalTextEdits(), either(empty()).or(nullValue()));
+ if ("ArrayIndexOutOfBoundsException".equals(item.label)) {
+ assertThat(item.additionalTextEdits, either(empty()).or(nullValue()));
return;
}
@@ -346,8 +346,8 @@ public class CompletionsTest extends CompletionsBase {
var items = items(file, 6, 10);
for (var item : items) {
- if ("AutocompleteOther".equals(item.getLabel())) {
- assertThat(item.getAdditionalTextEdits(), either(empty()).or(nullValue()));
+ if ("AutocompleteOther".equals(item.label)) {
+ assertThat(item.additionalTextEdits, either(empty()).or(nullValue()));
return;
}
@@ -365,8 +365,8 @@ public class CompletionsTest extends CompletionsBase {
var items = items(file, 12, 14);
for (var item : items) {
- if ("Arrays".equals(item.getLabel())) {
- assertThat(item.getAdditionalTextEdits(), either(empty()).or(nullValue()));
+ if ("Arrays".equals(item.label)) {
+ assertThat(item.additionalTextEdits, either(empty()).or(nullValue()));
return;
}
@@ -384,8 +384,8 @@ public class CompletionsTest extends CompletionsBase {
var items = items(file, 10, 26);
for (var item : items) {
- if ("ArrayBlockingQueue".equals(item.getLabel())) {
- assertThat(item.getAdditionalTextEdits(), either(empty()).or(nullValue()));
+ if ("ArrayBlockingQueue".equals(item.label)) {
+ assertThat(item.additionalTextEdits, either(empty()).or(nullValue()));
return;
}
@@ -400,7 +400,7 @@ public class CompletionsTest extends CompletionsBase {
// Static methods
var items = items(file, 8, 17);
- var suggestions = items.stream().map(i -> i.getLabel()).collect(Collectors.toSet());
+ var suggestions = items.stream().map(i -> i.label).collect(Collectors.toSet());
assertThat(suggestions, hasItems("add", "addAll"));
}
@@ -476,8 +476,8 @@ public class CompletionsTest extends CompletionsBase {
// Static methods
var items = items(file, 5, 18);
- var suggestions = items.stream().map(i -> i.getLabel()).collect(Collectors.toSet());
- var details = items.stream().map(i -> i.getDetail()).collect(Collectors.toSet());
+ var suggestions = items.stream().map(i -> i.label).collect(Collectors.toSet());
+ var details = items.stream().map(i -> i.detail).collect(Collectors.toSet());
assertThat(suggestions, hasItems("restMethod"));
assertThat(details, hasItems("void (String... params)"));
@@ -501,14 +501,13 @@ public class CompletionsTest extends CompletionsBase {
// Static methods
var items = items(file, 6, 19);
- var suggestions = Lists.transform(items, i -> i.getInsertText());
+ var suggestions = Lists.transform(items, i -> i.insertText);
assertThat(suggestions, hasItems("ArrayList<>($0)"));
for (var each : items) {
- if (each.getInsertText().equals("ArrayList<>"))
- assertThat(
- "new ? auto-imports", each.getAdditionalTextEdits(), both(not(empty())).and(not(nullValue())));
+ if (each.insertText.equals("ArrayList<>"))
+ assertThat("new ? auto-imports", each.additionalTextEdits, both(not(empty())).and(not(nullValue())));
}
}
@@ -557,17 +556,17 @@ public class CompletionsTest extends CompletionsBase {
// Static methods
var items = items(file, 4, 25);
- var suggestions = Lists.transform(items, i -> i.getLabel());
+ var suggestions = Lists.transform(items, i -> i.label);
assertThat(suggestions, hasItems("OtherPackagePublic"));
assertThat(suggestions, not(hasItems("OtherPackagePrivate")));
// Imports are now being managed by FixImports
// for (var item : items) {
- // if (item.getLabel().equals("OtherPackagePublic"))
+ // if (item.label.equals("OtherPackagePublic"))
// assertThat(
// "Don't import when completing imports",
- // item.getAdditionalTextEdits(),
+ // item.additionalTextEdits,
// either(empty()).or(nullValue()));
// }
}
@@ -578,14 +577,14 @@ public class CompletionsTest extends CompletionsBase {
// Static methods
var items = items(file, 5, 14);
- var suggestions = Lists.transform(items, i -> i.getLabel());
+ var suggestions = Lists.transform(items, i -> i.label);
assertThat(suggestions, hasItems("OtherPackagePublic"));
assertThat(suggestions, not(hasItems("OtherPackagePrivate")));
// for (var item : items) {
- // if (item.getLabel().equals("OtherPackagePublic"))
- // assertThat("Auto-import OtherPackagePublic", item.getAdditionalTextEdits(), not(empty()));
+ // if (item.label.equals("OtherPackagePublic"))
+ // assertThat("Auto-import OtherPackagePublic", item.additionalTextEdits, not(empty()));
// }
}
diff --git a/src/test/java/org/javacs/FindReferencesTest.java b/src/test/java/org/javacs/FindReferencesTest.java
index 9356ded..e8d51c3 100644
--- a/src/test/java/org/javacs/FindReferencesTest.java
+++ b/src/test/java/org/javacs/FindReferencesTest.java
@@ -3,10 +3,8 @@ package org.javacs;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import java.net.URI;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import org.javacs.lsp.*;
import org.junit.Test;
@@ -20,22 +18,17 @@ public class FindReferencesTest {
var uri = FindResource.uri(file);
var params = new ReferenceParams();
- params.setTextDocument(new TextDocumentIdentifier(uri.toString()));
- params.setUri(uri.toString());
- params.setPosition(new Position(row - 1, column - 1));
-
- try {
- var locations = server.getTextDocumentService().references(params).get();
- var strings = new ArrayList<String>();
- for (var l : locations) {
- var fileName = Parser.fileName(URI.create(l.getUri()));
- var line = l.getRange().getStart().getLine();
- strings.add(String.format("%s(%d)", fileName, line + 1));
- }
- return strings;
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
+ params.textDocument = new TextDocumentIdentifier(uri);
+ params.position = new Position(row - 1, column - 1);
+
+ var locations = server.findReferences(params);
+ var strings = new ArrayList<String>();
+ for (var l : locations) {
+ var fileName = Parser.fileName(l.uri);
+ var line = l.range.start.line;
+ strings.add(String.format("%s(%d)", fileName, line + 1));
}
+ return strings;
}
@Test
diff --git a/src/test/java/org/javacs/GotoTest.java b/src/test/java/org/javacs/GotoTest.java
index ac60efc..74bc9dd 100644
--- a/src/test/java/org/javacs/GotoTest.java
+++ b/src/test/java/org/javacs/GotoTest.java
@@ -3,11 +3,9 @@ package org.javacs;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
-import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ExecutionException;
import org.javacs.lsp.*;
import org.junit.Ignore;
import org.junit.Test;
@@ -128,30 +126,24 @@ public class GotoTest {
private List<String> doGoto(String file, int row, int column) {
TextDocumentIdentifier document = new TextDocumentIdentifier();
- document.setUri(FindResource.uri(file).toString());
+ document.uri = FindResource.uri(file);
Position position = new Position();
- position.setLine(row);
- position.setCharacter(column);
+ position.line = row;
+ position.character = column;
TextDocumentPositionParams p = new TextDocumentPositionParams();
- p.setTextDocument(document);
- p.setPosition(position);
+ p.textDocument = document;
+ p.position = position;
- // TODO extends is not coloring correctly
- List<? extends Location> locations;
- try {
- locations = server.getTextDocumentService().definition(p).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
+ var locations = server.gotoDefinition(p);
var strings = new ArrayList<String>();
for (var l : locations) {
- var fileName = Paths.get(URI.create(l.getUri())).getFileName();
- var start = l.getRange().getStart();
- strings.add(String.format("%s:%d", fileName, start.getLine() + 1));
+ var fileName = Paths.get(l.uri).getFileName();
+ var start = l.range.start;
+ strings.add(String.format("%s:%d", fileName, start.line + 1));
}
return strings;
}
diff --git a/src/test/java/org/javacs/JavaCompilerServiceTest.java b/src/test/java/org/javacs/JavaCompilerServiceTest.java
index ffcbc26..7b8d20c 100644
--- a/src/test/java/org/javacs/JavaCompilerServiceTest.java
+++ b/src/test/java/org/javacs/JavaCompilerServiceTest.java
@@ -244,7 +244,7 @@ public class JavaCompilerServiceTest {
var uri = r.getCompilationUnit().getSourceFile().toUri();
var fileName = Paths.get(uri).getFileName();
var range = batch.range(r).get();
- stringify.add(String.format("%s:%d", fileName, range.getStart().getLine() + 1));
+ stringify.add(String.format("%s:%d", fileName, range.start.line + 1));
}
assertThat(stringify, hasItem("GotoDefinition.java:3"));
assertThat(stringify, not(hasItem("GotoDefinition.java:6")));
diff --git a/src/test/java/org/javacs/LanguageServerFixture.java b/src/test/java/org/javacs/LanguageServerFixture.java
index 6f0a481..2c93283 100644
--- a/src/test/java/org/javacs/LanguageServerFixture.java
+++ b/src/test/java/org/javacs/LanguageServerFixture.java
@@ -1,8 +1,8 @@
package org.javacs;
+import com.google.gson.JsonElement;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.logging.Logger;
import org.javacs.lsp.*;
@@ -16,53 +16,32 @@ class LanguageServerFixture {
}
static JavaLanguageServer getJavaLanguageServer() {
- return getJavaLanguageServer(DEFAULT_WORKSPACE_ROOT, diagnostic -> LOG.info(diagnostic.getMessage()));
+ return getJavaLanguageServer(DEFAULT_WORKSPACE_ROOT, diagnostic -> LOG.info(diagnostic.message));
}
static JavaLanguageServer getJavaLanguageServer(Path workspaceRoot, Consumer<Diagnostic> onError) {
return getJavaLanguageServer(
workspaceRoot,
- new CustomLanguageClient() {
- @Override
- public void telemetryEvent(Object o) {}
-
- @Override
- public void publishDiagnostics(PublishDiagnosticsParams publishDiagnosticsParams) {
- publishDiagnosticsParams.getDiagnostics().forEach(onError);
- }
-
- @Override
- public void showMessage(MessageParams messageParams) {}
-
- @Override
- public CompletableFuture<MessageActionItem> showMessageRequest(
- ShowMessageRequestParams showMessageRequestParams) {
- return null;
+ new LanguageClient() {
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ params.diagnostics.forEach(onError);
}
- @Override
- public void logMessage(MessageParams messageParams) {}
-
- @Override
- public void javaStartProgress(JavaStartProgressParams params) {}
+ public void showMessage(ShowMessageParams params) {}
- @Override
- public void javaReportProgress(JavaReportProgressParams params) {}
+ public void registerCapability(String method, JsonElement options) {}
- @Override
- public void javaEndProgress() {}
+ public void customNotification(String method, JsonElement params) {}
});
}
- private static JavaLanguageServer getJavaLanguageServer(Path workspaceRoot, CustomLanguageClient client) {
- var server = new JavaLanguageServer();
+ private static JavaLanguageServer getJavaLanguageServer(Path workspaceRoot, LanguageClient client) {
+ var server = new JavaLanguageServer(client);
var init = new InitializeParams();
- init.setRootUri(workspaceRoot.toUri().toString());
-
- server.installClient(client);
+ init.rootUri = workspaceRoot.toUri();
server.initialize(init);
- server.initialized(null);
+ server.initialized();
return server;
}
diff --git a/src/test/java/org/javacs/SearchTest.java b/src/test/java/org/javacs/SearchTest.java
index ace86e8..3ecc567 100644
--- a/src/test/java/org/javacs/SearchTest.java
+++ b/src/test/java/org/javacs/SearchTest.java
@@ -9,7 +9,6 @@ import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.javacs.lsp.*;
@@ -27,37 +26,25 @@ public class SearchTest {
var textContent = Joiner.on("\n").join(Files.readAllLines(Paths.get(uri)));
var document = new TextDocumentItem();
- document.setUri(uri.toString());
- document.setText(textContent);
+ document.uri = uri;
+ document.text = textContent;
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(document, null));
+ server.didOpenTextDocument(new DidOpenTextDocumentParams(document));
}
private static Set<String> searchWorkspace(String query, int limit) {
- try {
- return server.getWorkspaceService()
- .symbol(new WorkspaceSymbolParams(query))
- .get()
- .stream()
- .map(result -> result.getName())
- .limit(limit)
- .collect(Collectors.toSet());
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
+ return server.workspaceSymbols(new WorkspaceSymbolParams(query))
+ .stream()
+ .map(result -> result.name)
+ .limit(limit)
+ .collect(Collectors.toSet());
}
private static Set<String> searchFile(URI uri) {
- try {
- return server.getTextDocumentService()
- .documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(uri.toString())))
- .get()
- .stream()
- .map(result -> result.getLeft().getName())
- .collect(Collectors.toSet());
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
+ return server.documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(uri)))
+ .stream()
+ .map(result -> result.name)
+ .collect(Collectors.toSet());
}
@Test
diff --git a/src/test/java/org/javacs/SignatureHelpTest.java b/src/test/java/org/javacs/SignatureHelpTest.java
index 6019021..ec05577 100644
--- a/src/test/java/org/javacs/SignatureHelpTest.java
+++ b/src/test/java/org/javacs/SignatureHelpTest.java
@@ -4,7 +4,6 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.javacs.lsp.*;
import org.junit.Test;
@@ -13,34 +12,34 @@ public class SignatureHelpTest {
public void signatureHelp() throws IOException {
var help = doHelp("/org/javacs/example/SignatureHelp.java", 7, 36);
- assertThat(help.getSignatures(), hasSize(2));
+ assertThat(help.signatures, hasSize(2));
}
@Test
public void partlyFilledIn() throws IOException {
var help = doHelp("/org/javacs/example/SignatureHelp.java", 8, 39);
- assertThat(help.getSignatures(), hasSize(2));
- assertThat(help.getActiveSignature(), equalTo(1));
- assertThat(help.getActiveParameter(), equalTo(1));
+ assertThat(help.signatures, hasSize(2));
+ assertThat(help.activeSignature, equalTo(1));
+ assertThat(help.activeParameter, equalTo(1));
}
@Test
public void constructor() throws IOException {
var help = doHelp("/org/javacs/example/SignatureHelp.java", 9, 27);
- assertThat(help.getSignatures(), hasSize(1));
- assertThat(help.getSignatures().get(0).getLabel(), startsWith("SignatureHelp"));
+ assertThat(help.signatures, hasSize(1));
+ assertThat(help.signatures.get(0).label, startsWith("SignatureHelp"));
}
@Test
public void platformConstructor() throws IOException {
var help = doHelp("/org/javacs/example/SignatureHelp.java", 10, 26);
- assertThat(help.getSignatures(), not(empty()));
- assertThat(help.getSignatures().get(0).getLabel(), startsWith("ArrayList"));
+ assertThat(help.signatures, not(empty()));
+ assertThat(help.signatures.get(0).label, startsWith("ArrayList"));
// TODO
- // assertThat(help.getSignatures().get(0).getDocumentation(), not(nullValue()));
+ // assertThat(help.signatures.get(0).documentation, not(nullValue()));
}
private static final JavaLanguageServer server = LanguageServerFixture.getJavaLanguageServer();
@@ -48,22 +47,18 @@ public class SignatureHelpTest {
private SignatureHelp doHelp(String file, int row, int column) throws IOException {
var document = new TextDocumentIdentifier();
- document.setUri(FindResource.uri(file).toString());
+ document.uri = FindResource.uri(file);
var position = new Position();
- position.setLine(row - 1);
- position.setCharacter(column - 1);
+ position.line = row - 1;
+ position.character = column - 1;
var p = new TextDocumentPositionParams();
- p.setTextDocument(document);
- p.setPosition(position);
+ p.textDocument = document;
+ p.position = position;
- try {
- return server.getTextDocumentService().signatureHelp(p).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
+ return server.signatureHelp(p).get();
}
}
diff --git a/src/test/java/org/javacs/SymbolUnderCursorTest.java b/src/test/java/org/javacs/SymbolUnderCursorTest.java
index ca04883..a606f5d 100644
--- a/src/test/java/org/javacs/SymbolUnderCursorTest.java
+++ b/src/test/java/org/javacs/SymbolUnderCursorTest.java
@@ -4,7 +4,6 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.StringJoiner;
-import java.util.concurrent.ExecutionException;
import org.javacs.lsp.*;
import org.junit.Ignore;
import org.junit.Test;
@@ -108,18 +107,10 @@ public class SymbolUnderCursorTest {
private String symbolAt(String file, int line, int character) {
var pos =
new TextDocumentPositionParams(
- new TextDocumentIdentifier(FindResource.uri(file).toString()),
- new Position(line - 1, character - 1));
+ new TextDocumentIdentifier(FindResource.uri(file)), new Position(line - 1, character - 1));
var result = new StringJoiner("\n");
- try {
- server.getTextDocumentService()
- .hover(pos)
- .get()
- .getContents()
- .getLeft()
- .forEach(hover -> result.add(hover.isLeft() ? hover.getLeft() : hover.getRight().getValue()));
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
+ for (var h : server.hover(pos).get().contents) {
+ result.add(h.value);
}
return result.toString();
}
diff --git a/src/test/java/org/javacs/lsp/LanguageServerTest.java b/src/test/java/org/javacs/lsp/LanguageServerTest.java
index 75a1faf..4330403 100644
--- a/src/test/java/org/javacs/lsp/LanguageServerTest.java
+++ b/src/test/java/org/javacs/lsp/LanguageServerTest.java
@@ -65,10 +65,24 @@ public class LanguageServerTest {
throws IOException, InterruptedException, ExecutionException, TimeoutException {
// Send initialize message and wait for ack
sendToServer(initializeMessage);
- receivedInitialize.get(1, TimeUnit.SECONDS);
+ receivedInitialize.get(10, TimeUnit.SECONDS);
// Send exit message and wait for exit
sendToServer(exitMessage);
- main.join(1000);
+ main.join(10_000);
+ assertThat("Main thread has quit", main.isAlive(), equalTo(false));
+ }
+
+ @Test
+ public void endOfStreamKillsServer()
+ throws IOException, InterruptedException, ExecutionException, TimeoutException {
+ // Send initialize message and wait for ack
+ sendToServer(initializeMessage);
+ receivedInitialize.get(10, TimeUnit.SECONDS);
+ // Close stream
+ writeClientToServer.close();
+ clientToServer.close();
+ // Wait for exit
+ main.join(10_000);
assertThat("Main thread has quit", main.isAlive(), equalTo(false));
}
}
diff --git a/src/test/java/org/javacs/lsp/LspTest.java b/src/test/java/org/javacs/lsp/LspTest.java
index 3f65774..9ef7c4b 100644
--- a/src/test/java/org/javacs/lsp/LspTest.java
+++ b/src/test/java/org/javacs/lsp/LspTest.java
@@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
+import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@@ -47,6 +48,20 @@ public class LspTest {
}
@Test
+ public void writeOptional() {
+ LSP.respond(writer, 1, Optional.of(1));
+ var expected = "Content-Length: 35\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":1}";
+ assertThat(bufferToString(), equalTo(expected));
+ }
+
+ @Test
+ public void writeEmpty() {
+ LSP.respond(writer, 1, Optional.empty());
+ var expected = "Content-Length: 38\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}";
+ assertThat(bufferToString(), equalTo(expected));
+ }
+
+ @Test
public void readMessage() throws IOException {
var message = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}";
var header = String.format("Content-Length: %d\r\n\r\n", message.getBytes().length);