summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGeorge Fraser <george@fivetran.com>2018-12-28 12:31:18 -0800
committerGeorge Fraser <george@fivetran.com>2018-12-28 12:31:18 -0800
commit1f1267e0c7d89750dbd6ff8806a73f8cfd9e673e (patch)
tree01f65dfc24489fce38f3d18c3f751b13a851ccbd /src
parentf19f80b3bdfc99f2062710f3d2193c8a16f26ecd (diff)
downloadjava-language-server-1f1267e0c7d89750dbd6ff8806a73f8cfd9e673e.zip
Minimalist LSP implementation
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/javacs/JavaTextDocumentService.java2
-rw-r--r--src/main/java/org/javacs/lsp/CancelParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/CodeAction.java10
-rw-r--r--src/main/java/org/javacs/lsp/CodeActionContext.java8
-rw-r--r--src/main/java/org/javacs/lsp/CodeActionKind.java11
-rw-r--r--src/main/java/org/javacs/lsp/CodeActionParams.java7
-rw-r--r--src/main/java/org/javacs/lsp/CodeLens.java9
-rw-r--r--src/main/java/org/javacs/lsp/CodeLensParams.java5
-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.java18
-rw-r--r--src/main/java/org/javacs/lsp/CompletionItemKind.java29
-rw-r--r--src/main/java/org/javacs/lsp/CompletionList.java8
-rw-r--r--src/main/java/org/javacs/lsp/CompletionParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/CompletionTriggerKind.java18
-rw-r--r--src/main/java/org/javacs/lsp/Diagnostic.java7
-rw-r--r--src/main/java/org/javacs/lsp/DiagnosticSeverity.java5
-rw-r--r--src/main/java/org/javacs/lsp/DiagnosticTag.java5
-rw-r--r--src/main/java/org/javacs/lsp/DidChangeConfigurationParams.java7
-rw-r--r--src/main/java/org/javacs/lsp/DidChangeTextDocumentParams.java8
-rw-r--r--src/main/java/org/javacs/lsp/DidChangeWatchedFilesParams.java7
-rw-r--r--src/main/java/org/javacs/lsp/DidChangeWorkspaceFoldersParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/DidCloseTextDocumentParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/DidOpenTextDocument.java5
-rw-r--r--src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/DidSaveTextDocumentParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/DocumentHighlight.java6
-rw-r--r--src/main/java/org/javacs/lsp/DocumentHighlightKind.java5
-rw-r--r--src/main/java/org/javacs/lsp/DocumentLink.java9
-rw-r--r--src/main/java/org/javacs/lsp/DocumentSymbol.java11
-rw-r--r--src/main/java/org/javacs/lsp/DocumentSymbolParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/ErrorCodes.java18
-rw-r--r--src/main/java/org/javacs/lsp/FileChangeType.java5
-rw-r--r--src/main/java/org/javacs/lsp/FileEvent.java8
-rw-r--r--src/main/java/org/javacs/lsp/Hover.java8
-rw-r--r--src/main/java/org/javacs/lsp/InitializeParams.java14
-rw-r--r--src/main/java/org/javacs/lsp/InitializeResult.java7
-rw-r--r--src/main/java/org/javacs/lsp/InsertTextFormat.java5
-rw-r--r--src/main/java/org/javacs/lsp/LSP.java361
-rw-r--r--src/main/java/org/javacs/lsp/LanguageClient.java13
-rw-r--r--src/main/java/org/javacs/lsp/LanguageServer.java102
-rw-r--r--src/main/java/org/javacs/lsp/Location.java8
-rw-r--r--src/main/java/org/javacs/lsp/MarkedString.java5
-rw-r--r--src/main/java/org/javacs/lsp/MarkupContent.java6
-rw-r--r--src/main/java/org/javacs/lsp/MarkupKind.java5
-rw-r--r--src/main/java/org/javacs/lsp/Message.java10
-rw-r--r--src/main/java/org/javacs/lsp/MessageType.java12
-rw-r--r--src/main/java/org/javacs/lsp/NotificationMessage.java8
-rw-r--r--src/main/java/org/javacs/lsp/ParameterInformation.java6
-rw-r--r--src/main/java/org/javacs/lsp/Position.java6
-rw-r--r--src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java9
-rw-r--r--src/main/java/org/javacs/lsp/README.md8
-rw-r--r--src/main/java/org/javacs/lsp/Range.java5
-rw-r--r--src/main/java/org/javacs/lsp/ReferenceContext.java5
-rw-r--r--src/main/java/org/javacs/lsp/ReferenceParams.java5
-rw-r--r--src/main/java/org/javacs/lsp/RegistrationParams.java8
-rw-r--r--src/main/java/org/javacs/lsp/RenameParams.java7
-rw-r--r--src/main/java/org/javacs/lsp/RequestMessage.java8
-rw-r--r--src/main/java/org/javacs/lsp/ResponseError.java9
-rw-r--r--src/main/java/org/javacs/lsp/ResponseMessage.java9
-rw-r--r--src/main/java/org/javacs/lsp/ShowMessageParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/SignatureHelp.java9
-rw-r--r--src/main/java/org/javacs/lsp/SignatureInformation.java9
-rw-r--r--src/main/java/org/javacs/lsp/SymbolInformation.java9
-rw-r--r--src/main/java/org/javacs/lsp/SymbolKind.java30
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentContentChangeEvent.java7
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentEdit.java8
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentIdentifier.java7
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentItem.java10
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentPositionParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/TextDocumentSaveReason.java5
-rw-r--r--src/main/java/org/javacs/lsp/TextEdit.java6
-rw-r--r--src/main/java/org/javacs/lsp/VersionedTextDocumentIdentifier.java8
-rw-r--r--src/main/java/org/javacs/lsp/WillSaveTextDocumentParams.java6
-rw-r--r--src/main/java/org/javacs/lsp/WorkspaceEdit.java9
-rw-r--r--src/main/java/org/javacs/lsp/WorkspaceFolder.java8
-rw-r--r--src/main/java/org/javacs/lsp/WorkspaceFoldersChangeEvent.java7
-rw-r--r--src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java5
-rw-r--r--src/test/java/org/javacs/lsp/LanguageServerTest.java74
-rw-r--r--src/test/java/org/javacs/lsp/LspTest.java65
80 files changed, 1223 insertions, 1 deletions
diff --git a/src/main/java/org/javacs/JavaTextDocumentService.java b/src/main/java/org/javacs/JavaTextDocumentService.java
index 6707cd0..a1cc285 100644
--- a/src/main/java/org/javacs/JavaTextDocumentService.java
+++ b/src/main/java/org/javacs/JavaTextDocumentService.java
@@ -326,7 +326,7 @@ class JavaTextDocumentService implements TextDocumentService {
var line = position.getPosition().getLine() + 1;
var column = position.getPosition().getCharacter() + 1;
var el = hoverCache.element(line, column);
- if (el.isEmpty()) return CompletableFuture.completedFuture(new Hover(Collections.emptyList()));
+ if (!el.isPresent()) return CompletableFuture.completedFuture(new Hover(Collections.emptyList()));
// Add code hover message
var result = new ArrayList<Either<String, MarkedString>>();
diff --git a/src/main/java/org/javacs/lsp/CancelParams.java b/src/main/java/org/javacs/lsp/CancelParams.java
new file mode 100644
index 0000000..cf7acce
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CancelParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class CancelParams {
+ public String id;
+}
diff --git a/src/main/java/org/javacs/lsp/CodeAction.java b/src/main/java/org/javacs/lsp/CodeAction.java
new file mode 100644
index 0000000..b07a5a9
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CodeAction.java
@@ -0,0 +1,10 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class CodeAction {
+ public String title, kind;
+ public List<Diagnostic> diagnostics;
+ public WorkspaceEdit edit;
+ public Command command;
+}
diff --git a/src/main/java/org/javacs/lsp/CodeActionContext.java b/src/main/java/org/javacs/lsp/CodeActionContext.java
new file mode 100644
index 0000000..7ded497
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CodeActionContext.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class CodeActionContext {
+ public List<Diagnostic> diagnostics;
+ public List<CodeActionKind> only;
+}
diff --git a/src/main/java/org/javacs/lsp/CodeActionKind.java b/src/main/java/org/javacs/lsp/CodeActionKind.java
new file mode 100644
index 0000000..4495545
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CodeActionKind.java
@@ -0,0 +1,11 @@
+package org.javacs.lsp;
+
+public class CodeActionKind {
+ public static final String QuickFix = "quickfix",
+ Refactor = "refactor",
+ RefactorExtract = "refactor.extract",
+ RefactorInline = "refactor.inline",
+ RefactorRewrite = "refactor.rewrite",
+ Source = "source",
+ SourceOrganizeImports = "source.organizeImports";
+}
diff --git a/src/main/java/org/javacs/lsp/CodeActionParams.java b/src/main/java/org/javacs/lsp/CodeActionParams.java
new file mode 100644
index 0000000..77bb798
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CodeActionParams.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+public class CodeActionParams {
+ public TextDocumentIdentifier textDocument;
+ public Range range;
+ public CodeActionContext context;
+}
diff --git a/src/main/java/org/javacs/lsp/CodeLens.java b/src/main/java/org/javacs/lsp/CodeLens.java
new file mode 100644
index 0000000..79158a7
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CodeLens.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonArray;
+
+public class CodeLens {
+ public Range range;
+ public Command command;
+ public JsonArray data;
+}
diff --git a/src/main/java/org/javacs/lsp/CodeLensParams.java b/src/main/java/org/javacs/lsp/CodeLensParams.java
new file mode 100644
index 0000000..7958ac7
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CodeLensParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class CodeLensParams {
+ public TextDocumentIdentifier textDocument;
+}
diff --git a/src/main/java/org/javacs/lsp/Command.java b/src/main/java/org/javacs/lsp/Command.java
new file mode 100644
index 0000000..a212c92
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/Command.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonArray;
+
+public class Command {
+ public String title, command;
+ public JsonArray arguments;
+}
diff --git a/src/main/java/org/javacs/lsp/CompletionContext.java b/src/main/java/org/javacs/lsp/CompletionContext.java
new file mode 100644
index 0000000..f10547f
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CompletionContext.java
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 0000000..1a46753
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CompletionItem.java
@@ -0,0 +1,18 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonArray;
+import java.util.List;
+
+public class CompletionItem {
+ public String label;
+ public int kind;
+ public String detail;
+ public MarkupContent documentation;
+ public boolean deprecated, preselect;
+ public String sortText, filterText, insertText, insertTextFormat;
+ public TextEdit textEdit;
+ public List<TextEdit> additionalTextEdits;
+ public List<Character> commitCharacters;
+ public Command command;
+ public JsonArray data;
+}
diff --git a/src/main/java/org/javacs/lsp/CompletionItemKind.java b/src/main/java/org/javacs/lsp/CompletionItemKind.java
new file mode 100644
index 0000000..3a7d18c
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CompletionItemKind.java
@@ -0,0 +1,29 @@
+package org.javacs.lsp;
+
+public class CompletionItemKind {
+ public static final int Text = 1,
+ Method = 2,
+ Function = 3,
+ Constructor = 4,
+ Field = 5,
+ Variable = 6,
+ Class = 7,
+ Interface = 8,
+ Module = 9,
+ Property = 10,
+ Unit = 11,
+ Value = 12,
+ Enum = 13,
+ Keyword = 14,
+ Snippet = 15,
+ Color = 16,
+ File = 17,
+ Reference = 18,
+ Folder = 19,
+ EnumMember = 20,
+ Constant = 21,
+ Struct = 22,
+ Event = 23,
+ Operator = 24,
+ TypeParameter = 25;
+}
diff --git a/src/main/java/org/javacs/lsp/CompletionList.java b/src/main/java/org/javacs/lsp/CompletionList.java
new file mode 100644
index 0000000..48d3155
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CompletionList.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class CompletionList {
+ public boolean isIncomplete;
+ public List<CompletionItem> items;
+}
diff --git a/src/main/java/org/javacs/lsp/CompletionParams.java b/src/main/java/org/javacs/lsp/CompletionParams.java
new file mode 100644
index 0000000..c3dd2ef
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CompletionParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class CompletionParams extends TextDocumentPositionParams {
+ public CompletionContext context;
+}
diff --git a/src/main/java/org/javacs/lsp/CompletionTriggerKind.java b/src/main/java/org/javacs/lsp/CompletionTriggerKind.java
new file mode 100644
index 0000000..caae0fb
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/CompletionTriggerKind.java
@@ -0,0 +1,18 @@
+package org.javacs.lsp;
+
+public class CompletionTriggerKind {
+ /**
+ * Completion was triggered by typing an identifier (24x7 code complete), manual invocation (e.g Ctrl+Space) or via
+ * API.
+ */
+ public static final int Invoked = 1;
+
+ /**
+ * Completion was triggered by a trigger character specified by the `triggerCharacters` properties of the
+ * `CompletionRegistrationOptions`.
+ */
+ public static final int TriggerCharacter = 2;
+
+ /** Completion was re-triggered as the current completion list is incomplete. */
+ public static final int TriggerForIncompleteCompletions = 3;
+}
diff --git a/src/main/java/org/javacs/lsp/Diagnostic.java b/src/main/java/org/javacs/lsp/Diagnostic.java
new file mode 100644
index 0000000..75986f7
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/Diagnostic.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+public class Diagnostic {
+ public Range range;
+ public int severity;
+ public String code, source, message;
+}
diff --git a/src/main/java/org/javacs/lsp/DiagnosticSeverity.java b/src/main/java/org/javacs/lsp/DiagnosticSeverity.java
new file mode 100644
index 0000000..1b9a867
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DiagnosticSeverity.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DiagnosticSeverity {
+ public static final int Error = 1, Warning = 2, Information = 3, Hint = 4;
+}
diff --git a/src/main/java/org/javacs/lsp/DiagnosticTag.java b/src/main/java/org/javacs/lsp/DiagnosticTag.java
new file mode 100644
index 0000000..09aba74
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DiagnosticTag.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DiagnosticTag {
+ public static final int Unnecessary = 1;
+}
diff --git a/src/main/java/org/javacs/lsp/DidChangeConfigurationParams.java b/src/main/java/org/javacs/lsp/DidChangeConfigurationParams.java
new file mode 100644
index 0000000..8554ae1
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidChangeConfigurationParams.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public class DidChangeConfigurationParams {
+ public JsonElement settings;
+}
diff --git a/src/main/java/org/javacs/lsp/DidChangeTextDocumentParams.java b/src/main/java/org/javacs/lsp/DidChangeTextDocumentParams.java
new file mode 100644
index 0000000..75b8dd3
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidChangeTextDocumentParams.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class DidChangeTextDocumentParams {
+ public VersionedTextDocumentIdentifier textDocument;
+ public List<TextDocumentContentChangeEvent> contentChanges;
+}
diff --git a/src/main/java/org/javacs/lsp/DidChangeWatchedFilesParams.java b/src/main/java/org/javacs/lsp/DidChangeWatchedFilesParams.java
new file mode 100644
index 0000000..23e525d
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidChangeWatchedFilesParams.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class DidChangeWatchedFilesParams {
+ public List<FileEvent> changes;
+}
diff --git a/src/main/java/org/javacs/lsp/DidChangeWorkspaceFoldersParams.java b/src/main/java/org/javacs/lsp/DidChangeWorkspaceFoldersParams.java
new file mode 100644
index 0000000..742ee4a
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidChangeWorkspaceFoldersParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DidChangeWorkspaceFoldersParams {
+ public WorkspaceFoldersChangeEvent event;
+}
diff --git a/src/main/java/org/javacs/lsp/DidCloseTextDocumentParams.java b/src/main/java/org/javacs/lsp/DidCloseTextDocumentParams.java
new file mode 100644
index 0000000..67dc5e0
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidCloseTextDocumentParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DidCloseTextDocumentParams {
+ public TextDocumentIdentifier textDocument;
+}
diff --git a/src/main/java/org/javacs/lsp/DidOpenTextDocument.java b/src/main/java/org/javacs/lsp/DidOpenTextDocument.java
new file mode 100644
index 0000000..ccbcf4b
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidOpenTextDocument.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DidOpenTextDocument {
+ public TextDocumentItem textDocument;
+}
diff --git a/src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java b/src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java
new file mode 100644
index 0000000..1083a95
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidOpenTextDocumentParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DidOpenTextDocumentParams {
+ public TextDocumentItem textDocument;
+}
diff --git a/src/main/java/org/javacs/lsp/DidSaveTextDocumentParams.java b/src/main/java/org/javacs/lsp/DidSaveTextDocumentParams.java
new file mode 100644
index 0000000..6c38a53
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DidSaveTextDocumentParams.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class DidSaveTextDocumentParams {
+ public TextDocumentIdentifier textDocument;
+ public String text;
+}
diff --git a/src/main/java/org/javacs/lsp/DocumentHighlight.java b/src/main/java/org/javacs/lsp/DocumentHighlight.java
new file mode 100644
index 0000000..b3bbf79
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DocumentHighlight.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class DocumentHighlight {
+ public Range range;
+ public int kind;
+}
diff --git a/src/main/java/org/javacs/lsp/DocumentHighlightKind.java b/src/main/java/org/javacs/lsp/DocumentHighlightKind.java
new file mode 100644
index 0000000..1712c82
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DocumentHighlightKind.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DocumentHighlightKind {
+ public static final int Text = 1, Read = 2, Write = 3;
+}
diff --git a/src/main/java/org/javacs/lsp/DocumentLink.java b/src/main/java/org/javacs/lsp/DocumentLink.java
new file mode 100644
index 0000000..06877fa
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DocumentLink.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonArray;
+
+public class DocumentLink {
+ public Range range;
+ public String target;
+ public JsonArray data;
+}
diff --git a/src/main/java/org/javacs/lsp/DocumentSymbol.java b/src/main/java/org/javacs/lsp/DocumentSymbol.java
new file mode 100644
index 0000000..4510aa9
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DocumentSymbol.java
@@ -0,0 +1,11 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class DocumentSymbol {
+ public String name, detail;
+ public int kind;
+ public boolean deprecated;
+ public Range range, selectionRange;
+ public List<DocumentSymbol> children;
+}
diff --git a/src/main/java/org/javacs/lsp/DocumentSymbolParams.java b/src/main/java/org/javacs/lsp/DocumentSymbolParams.java
new file mode 100644
index 0000000..ff8e740
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/DocumentSymbolParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class DocumentSymbolParams {
+ public TextDocumentIdentifier textDocument;
+}
diff --git a/src/main/java/org/javacs/lsp/ErrorCodes.java b/src/main/java/org/javacs/lsp/ErrorCodes.java
new file mode 100644
index 0000000..98b92f7
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/ErrorCodes.java
@@ -0,0 +1,18 @@
+package org.javacs.lsp;
+
+public class ErrorCodes {
+ public static final int ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+ serverErrorStart = -32099,
+ serverErrorEnd = -32000,
+ ServerNotInitialized = -32002,
+ UnknownErrorCode = -32001,
+
+ // TODO comment doesn't highlight properly
+ // Defined by the protocol.
+ RequestCancelled = -32800,
+ ContentModified = -32801;
+}
diff --git a/src/main/java/org/javacs/lsp/FileChangeType.java b/src/main/java/org/javacs/lsp/FileChangeType.java
new file mode 100644
index 0000000..076abcd
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/FileChangeType.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class FileChangeType {
+ public static final int Created = 1, Changed = 2, Deleted = 3;
+}
diff --git a/src/main/java/org/javacs/lsp/FileEvent.java b/src/main/java/org/javacs/lsp/FileEvent.java
new file mode 100644
index 0000000..2f766e3
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/FileEvent.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+
+public class FileEvent {
+ public URI uri;
+ public int type;
+}
diff --git a/src/main/java/org/javacs/lsp/Hover.java b/src/main/java/org/javacs/lsp/Hover.java
new file mode 100644
index 0000000..25dadd0
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/Hover.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class Hover {
+ public List<MarkedString> contents;
+ public Range range;
+}
diff --git a/src/main/java/org/javacs/lsp/InitializeParams.java b/src/main/java/org/javacs/lsp/InitializeParams.java
new file mode 100644
index 0000000..5c86b8b
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/InitializeParams.java
@@ -0,0 +1,14 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+import java.net.URI;
+import java.util.List;
+
+public class InitializeParams {
+ public int processId;
+ public String rootPath;
+ public URI rootUri;
+ public JsonElement initializationOptions;
+ public String trace;
+ public List<WorkspaceFolder> workspaceFolders;
+}
diff --git a/src/main/java/org/javacs/lsp/InitializeResult.java b/src/main/java/org/javacs/lsp/InitializeResult.java
new file mode 100644
index 0000000..a8c8cee
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/InitializeResult.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonObject;
+
+public class InitializeResult {
+ public JsonObject capabilities;
+}
diff --git a/src/main/java/org/javacs/lsp/InsertTextFormat.java b/src/main/java/org/javacs/lsp/InsertTextFormat.java
new file mode 100644
index 0000000..54adbc2
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/InsertTextFormat.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class InsertTextFormat {
+ public static final int PlainText = 1, Snippet = 2;
+}
diff --git a/src/main/java/org/javacs/lsp/LSP.java b/src/main/java/org/javacs/lsp/LSP.java
new file mode 100644
index 0000000..06da2d7
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/LSP.java
@@ -0,0 +1,361 @@
+package org.javacs.lsp;
+
+import com.google.common.base.Charsets;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import java.io.*;
+import java.util.UUID;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class LSP {
+ private static final Gson gson = new Gson();
+
+ private static String readHeader(InputStream client) {
+ var line = new StringBuilder();
+ for (var next = read(client); next != -1; next = read(client)) {
+ if (next == '\r') {
+ var last = read(client);
+ assert last == '\n';
+ break;
+ }
+ line.append((char) next);
+ }
+ return line.toString();
+ }
+
+ private static int parseHeader(String header) {
+ var contentLength = "Content-Length: ";
+ if (header.startsWith(contentLength)) {
+ var tail = header.substring(contentLength.length());
+ var length = Integer.parseInt(tail);
+ return length;
+ }
+ return -1;
+ }
+
+ private static int read(InputStream client) {
+ try {
+ return client.read();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String readLength(InputStream client, int byteLength) {
+ // Eat whitespace
+ // Have observed problems with extra \r\n sequences from VSCode
+ var next = read(client);
+ while (next != -1 && 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);
+ i++;
+ if (i == byteLength) break;
+ next = read(client);
+ }
+ return result.toString();
+ }
+
+ static String nextToken(InputStream client) {
+ var contentLength = -1;
+ while (true) {
+ var line = readHeader(client);
+ // If header is empty, next line is the start of the message
+ if (line.isEmpty()) return readLength(client, contentLength);
+ // If header contains length, save it
+ var maybeLength = parseHeader(line);
+ if (maybeLength != -1) contentLength = maybeLength;
+ }
+ }
+
+ static Message parseMessage(String token) {
+ return gson.fromJson(token, Message.class);
+ }
+
+ private static void writeClient(OutputStream client, String messageText) {
+ var messageBytes = messageText.getBytes(Charsets.UTF_8);
+ var headerText = String.format("Content-Length: %d\r\n\r\n", messageBytes.length);
+ var headerBytes = headerText.getBytes(Charsets.UTF_8);
+ try {
+ client.write(headerBytes);
+ client.write(messageBytes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static void respond(OutputStream client, int requestId, Object params) {
+ 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) {
+ var jsonText = gson.toJson(params);
+ var messageText = String.format("{\"jsonrpc\":\"2.0\",\"method\":\"%s\",\"params\":%s}", method, jsonText);
+ writeClient(client, messageText);
+ }
+
+ private static class RealClient implements LanguageClient {
+ final OutputStream send;
+
+ RealClient(OutputStream send) {
+ this.send = send;
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ var json = gson.toJson(params);
+ notifyClient(send, "textDocument/publishDiagnostics", json);
+ }
+
+ @Override
+ public void showMessage(ShowMessageParams params) {
+ var json = gson.toJson(params);
+ notifyClient(send, "window/showMessage", json);
+ }
+
+ @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);
+ }
+
+ @Override
+ public void customNotification(String method, JsonElement params) {
+ var json = gson.toJson(params);
+ notifyClient(send, method, json);
+ }
+ }
+
+ 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);
+
+ // 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));
+ 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));
+ }
+ }
+
+ @Override
+ public void run() {
+ LOG.info("Placing incoming messages on queue...");
+
+ while (true) {
+ try {
+ 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()));
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, e.getMessage(), e);
+ }
+ }
+ }
+ }
+ Thread reader = new Thread(new MessageReader(), "reader");
+ reader.setDaemon(true);
+ reader.start();
+
+ // Process messages on main thread
+ LOG.info("Reading messages from queue...");
+ 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) {
+ case "initialize":
+ {
+ var params = gson.fromJson(request.params, InitializeParams.class);
+ var response = server.initialize(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "initialized":
+ {
+ server.initialized();
+ break;
+ }
+ case "shutdown":
+ {
+ LOG.warning("Got shutdown message");
+ break;
+ }
+ case "exit":
+ {
+ LOG.warning("Got exit message, exiting...");
+ break processMessages;
+ }
+ case "workspace/didChangeWorkspaceFolders":
+ {
+ var params = gson.fromJson(request.params, DidChangeWorkspaceFoldersParams.class);
+ server.didChangeWorkspaceFolders(params);
+ break;
+ }
+ case "workspace/didChangeConfiguration":
+ {
+ var params = gson.fromJson(request.params, DidChangeConfigurationParams.class);
+ server.didChangeConfiguration(params);
+ break;
+ }
+ case "workspace/didChangeWatchedFiles":
+ {
+ var params = gson.fromJson(request.params, DidChangeWatchedFilesParams.class);
+ server.didChangeWatchedFiles(params);
+ break;
+ }
+ case "workspace/symbols":
+ {
+ var params = gson.fromJson(request.params, WorkspaceSymbolParams.class);
+ var response = server.workspaceSymbols(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/didOpen":
+ {
+ var params = gson.fromJson(request.params, DidOpenTextDocumentParams.class);
+ server.didOpenTextDocument(params);
+ break;
+ }
+ case "textDocument/didChange":
+ {
+ var params = gson.fromJson(request.params, DidChangeTextDocumentParams.class);
+ server.didChangeTextDocument(params);
+ break;
+ }
+ case "textDocument/willSave":
+ {
+ var params = gson.fromJson(request.params, WillSaveTextDocumentParams.class);
+ server.willSaveTextDocument(params);
+ break;
+ }
+ case "textDocument/willSaveWaitUntil":
+ {
+ var params = gson.fromJson(request.params, WillSaveTextDocumentParams.class);
+ var response = server.willSaveWaitUntilTextDocument(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/didSave":
+ {
+ var params = gson.fromJson(request.params, DidSaveTextDocumentParams.class);
+ server.didSaveTextDocument(params);
+ break;
+ }
+ case "textDocument/didClose":
+ {
+ var params = gson.fromJson(request.params, DidCloseTextDocumentParams.class);
+ server.didCloseTextDocument(params);
+ break;
+ }
+ case "textDocument/completion":
+ {
+ var params = gson.fromJson(request.params, CompletionParams.class);
+ var response = server.completion(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "completionItem/resolve":
+ {
+ var params = gson.fromJson(request.params, CompletionItem.class);
+ var response = server.resolveCompletionItem(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/hover":
+ {
+ var params = gson.fromJson(request.params, TextDocumentPositionParams.class);
+ var response = server.hover(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/signatureHelp":
+ {
+ var params = gson.fromJson(request.params, TextDocumentPositionParams.class);
+ var response = server.signatureHelp(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/definition":
+ {
+ var params = gson.fromJson(request.params, TextDocumentPositionParams.class);
+ var response = server.gotoDefinition(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/references":
+ {
+ var params = gson.fromJson(request.params, ReferenceParams.class);
+ var response = server.findReferences(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/documentSymbol":
+ {
+ var params = gson.fromJson(request.params, DocumentSymbolParams.class);
+ var response = server.documentSymbol(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/codeAction":
+ {
+ var params = gson.fromJson(request.params, CodeActionParams.class);
+ var response = server.codeAction(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/codeLens":
+ {
+ var params = gson.fromJson(request.params, CodeLensParams.class);
+ var response = server.codeLens(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "codeLens/resolve":
+ {
+ var params = gson.fromJson(request.params, CodeLens.class);
+ var response = server.resolveCodeLens(params);
+ respond(send, request.id, response);
+ break;
+ }
+ case "textDocument/rename":
+ {
+ var params = gson.fromJson(request.params, RenameParams.class);
+ var response = server.rename(params);
+ respond(send, request.id, response);
+ break;
+ }
+ default:
+ LOG.warning(String.format("Don't know what to do with method `%s`", request.method));
+ }
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, e.getMessage(), e);
+ }
+ }
+ }
+
+ private static final Logger LOG = Logger.getLogger("main");
+}
diff --git a/src/main/java/org/javacs/lsp/LanguageClient.java b/src/main/java/org/javacs/lsp/LanguageClient.java
new file mode 100644
index 0000000..c603574
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/LanguageClient.java
@@ -0,0 +1,13 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public interface LanguageClient {
+ public void publishDiagnostics(PublishDiagnosticsParams params);
+
+ public void showMessage(ShowMessageParams params);
+
+ public void registerCapability(String method, JsonElement options);
+
+ public void customNotification(String method, JsonElement params);
+}
diff --git a/src/main/java/org/javacs/lsp/LanguageServer.java b/src/main/java/org/javacs/lsp/LanguageServer.java
new file mode 100644
index 0000000..e969c2e
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/LanguageServer.java
@@ -0,0 +1,102 @@
+package org.javacs.lsp;
+
+import java.util.List;
+import java.util.Optional;
+
+public class LanguageServer {
+ public InitializeResult initialize(InitializeParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void initialized() {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void shutdown() {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void didChangeConfiguration(DidChangeConfigurationParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void didOpenTextDocument(DidOpenTextDocumentParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void didChangeTextDocument(DidChangeTextDocumentParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void willSaveTextDocument(WillSaveTextDocumentParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public List<TextEdit> willSaveWaitUntilTextDocument(WillSaveTextDocumentParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void didSaveTextDocument(DidSaveTextDocumentParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void didCloseTextDocument(DidCloseTextDocumentParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public Optional<CompletionList> completion(TextDocumentPositionParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public CompletionItem resolveCompletionItem(CompletionItem params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public Optional<Hover> hover(TextDocumentPositionParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public Optional<SignatureHelp> signatureHelp(TextDocumentPositionParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public List<Location> gotoDefinition(TextDocumentPositionParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public List<Location> findReferences(ReferenceParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public List<SymbolInformation> documentSymbol(DocumentSymbolParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public List<SymbolInformation> workspaceSymbols(WorkspaceSymbolParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public List<Command> codeAction(CodeActionParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public List<CodeLens> codeLens(CodeLensParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public CodeLens resolveCodeLens(CodeLens params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public WorkspaceEdit rename(RenameParams params) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams 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
new file mode 100644
index 0000000..b35bf82
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/Location.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+
+public class Location {
+ URI uri;
+ Range range;
+}
diff --git a/src/main/java/org/javacs/lsp/MarkedString.java b/src/main/java/org/javacs/lsp/MarkedString.java
new file mode 100644
index 0000000..da0f33f
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/MarkedString.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class MarkedString {
+ public String language, value;
+}
diff --git a/src/main/java/org/javacs/lsp/MarkupContent.java b/src/main/java/org/javacs/lsp/MarkupContent.java
new file mode 100644
index 0000000..3e2d58f
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/MarkupContent.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class MarkupContent {
+ public MarkupKind kind;
+ public String value;
+}
diff --git a/src/main/java/org/javacs/lsp/MarkupKind.java b/src/main/java/org/javacs/lsp/MarkupKind.java
new file mode 100644
index 0000000..7706dbf
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/MarkupKind.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class MarkupKind {
+ public static final String PlainText = "plaintext", Markdown = "markdown";
+}
diff --git a/src/main/java/org/javacs/lsp/Message.java b/src/main/java/org/javacs/lsp/Message.java
new file mode 100644
index 0000000..3998c0b
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/Message.java
@@ -0,0 +1,10 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public class Message {
+ public String jsonrpc;
+ public Integer id;
+ public String method;
+ public JsonElement params;
+}
diff --git a/src/main/java/org/javacs/lsp/MessageType.java b/src/main/java/org/javacs/lsp/MessageType.java
new file mode 100644
index 0000000..35aeccb
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/MessageType.java
@@ -0,0 +1,12 @@
+package org.javacs.lsp;
+
+public class MessageType {
+ /** An error message. */
+ public static final int Error = 1;
+ /** A warning message. */
+ public static final int Warning = 2;
+ /** An information message. */
+ public static final int Info = 3;
+ /** A log message. */
+ public static final int Log = 4;
+}
diff --git a/src/main/java/org/javacs/lsp/NotificationMessage.java b/src/main/java/org/javacs/lsp/NotificationMessage.java
new file mode 100644
index 0000000..6e46a20
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/NotificationMessage.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public class NotificationMessage {
+ public String method;
+ public JsonElement params;
+}
diff --git a/src/main/java/org/javacs/lsp/ParameterInformation.java b/src/main/java/org/javacs/lsp/ParameterInformation.java
new file mode 100644
index 0000000..0692bbb
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/ParameterInformation.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class ParameterInformation {
+ public String label;
+ public MarkupContent documentation;
+}
diff --git a/src/main/java/org/javacs/lsp/Position.java b/src/main/java/org/javacs/lsp/Position.java
new file mode 100644
index 0000000..ae6b466
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/Position.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class Position {
+ // 0-based
+ public int line, character;
+}
diff --git a/src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java b/src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java
new file mode 100644
index 0000000..8e19d05
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/PublishDiagnosticsParams.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+import java.util.List;
+
+public class PublishDiagnosticsParams {
+ public URI uri;
+ public List<Diagnostic> diagnostics;
+}
diff --git a/src/main/java/org/javacs/lsp/README.md b/src/main/java/org/javacs/lsp/README.md
new file mode 100644
index 0000000..d90ceb4
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/README.md
@@ -0,0 +1,8 @@
+# LSP module
+`org.javacs.lsp` is a minimalist implementation of the Language Server Protocol.
+It doesn't necessarily implement all features of the LSP, because it's only intended to serve the purposes of the Java Language Server.
+
+## Why not use https://github.com/eclipse/lsp4j?
+One of the design goals of the JLS is to be deployed as a zero-dependency native application,
+so that it works correctly regardless of what version of Java you have on your system.
+`lsp4j` isn't compatible with the Java Module System, which we need in order to use jlink. \ No newline at end of file
diff --git a/src/main/java/org/javacs/lsp/Range.java b/src/main/java/org/javacs/lsp/Range.java
new file mode 100644
index 0000000..0d0e3c0
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/Range.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class Range {
+ public Position start, end;
+}
diff --git a/src/main/java/org/javacs/lsp/ReferenceContext.java b/src/main/java/org/javacs/lsp/ReferenceContext.java
new file mode 100644
index 0000000..7451854
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/ReferenceContext.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class ReferenceContext {
+ public boolean includeDeclaration;
+}
diff --git a/src/main/java/org/javacs/lsp/ReferenceParams.java b/src/main/java/org/javacs/lsp/ReferenceParams.java
new file mode 100644
index 0000000..c58be0d
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/ReferenceParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class ReferenceParams extends TextDocumentPositionParams {
+ public ReferenceContext context;
+}
diff --git a/src/main/java/org/javacs/lsp/RegistrationParams.java b/src/main/java/org/javacs/lsp/RegistrationParams.java
new file mode 100644
index 0000000..9030aa0
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/RegistrationParams.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public class RegistrationParams {
+ public String id, method;
+ public JsonElement registerOptions;
+}
diff --git a/src/main/java/org/javacs/lsp/RenameParams.java b/src/main/java/org/javacs/lsp/RenameParams.java
new file mode 100644
index 0000000..6e4a897
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/RenameParams.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+public class RenameParams {
+ public TextDocumentIdentifier textDocument;
+ public Position position;
+ public String newName;
+}
diff --git a/src/main/java/org/javacs/lsp/RequestMessage.java b/src/main/java/org/javacs/lsp/RequestMessage.java
new file mode 100644
index 0000000..d214108
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/RequestMessage.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public class RequestMessage {
+ public String id, method;
+ public JsonElement params;
+}
diff --git a/src/main/java/org/javacs/lsp/ResponseError.java b/src/main/java/org/javacs/lsp/ResponseError.java
new file mode 100644
index 0000000..d6a19c8
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/ResponseError.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public class ResponseError {
+ public int code;
+ public String message;
+ public JsonElement data;
+}
diff --git a/src/main/java/org/javacs/lsp/ResponseMessage.java b/src/main/java/org/javacs/lsp/ResponseMessage.java
new file mode 100644
index 0000000..d982de3
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/ResponseMessage.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import com.google.gson.JsonElement;
+
+public class ResponseMessage {
+ public String id;
+ public JsonElement result;
+ public ResponseError error;
+}
diff --git a/src/main/java/org/javacs/lsp/ShowMessageParams.java b/src/main/java/org/javacs/lsp/ShowMessageParams.java
new file mode 100644
index 0000000..9ac0938
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/ShowMessageParams.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class ShowMessageParams {
+ public int type;
+ public String message;
+}
diff --git a/src/main/java/org/javacs/lsp/SignatureHelp.java b/src/main/java/org/javacs/lsp/SignatureHelp.java
new file mode 100644
index 0000000..eb25037
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/SignatureHelp.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class SignatureHelp {
+ public List<SignatureInformation> signatures;
+ public Integer activeSignature;
+ public Integer activeParameter;
+}
diff --git a/src/main/java/org/javacs/lsp/SignatureInformation.java b/src/main/java/org/javacs/lsp/SignatureInformation.java
new file mode 100644
index 0000000..b5a7f61
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/SignatureInformation.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class SignatureInformation {
+ public String label;
+ public MarkupContent documentation;
+ public List<ParameterInformation> parameters;
+}
diff --git a/src/main/java/org/javacs/lsp/SymbolInformation.java b/src/main/java/org/javacs/lsp/SymbolInformation.java
new file mode 100644
index 0000000..fb1e0ad
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/SymbolInformation.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+public class SymbolInformation {
+ public String name;
+ public int kind;
+ public boolean deprecated;
+ public Location location;
+ public String containerName;
+}
diff --git a/src/main/java/org/javacs/lsp/SymbolKind.java b/src/main/java/org/javacs/lsp/SymbolKind.java
new file mode 100644
index 0000000..9a9880d
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/SymbolKind.java
@@ -0,0 +1,30 @@
+package org.javacs.lsp;
+
+public class SymbolKind {
+ public static final int File = 1,
+ Module = 2,
+ Namespace = 3,
+ Package = 4,
+ Class = 5,
+ Method = 6,
+ Property = 7,
+ Field = 8,
+ Constructor = 9,
+ Enum = 10,
+ Interface = 11,
+ Function = 12,
+ Variable = 13,
+ Constant = 14,
+ String = 15,
+ Number = 16,
+ Boolean = 17,
+ Array = 18,
+ Object = 19,
+ Key = 20,
+ Null = 21,
+ EnumMember = 22,
+ Struct = 23,
+ Event = 24,
+ Operator = 25,
+ TypeParameter = 26;
+}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentContentChangeEvent.java b/src/main/java/org/javacs/lsp/TextDocumentContentChangeEvent.java
new file mode 100644
index 0000000..366b187
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/TextDocumentContentChangeEvent.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+public class TextDocumentContentChangeEvent {
+ public Range range;
+ public Integer rangeLength;
+ public String text;
+}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentEdit.java b/src/main/java/org/javacs/lsp/TextDocumentEdit.java
new file mode 100644
index 0000000..0078de0
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/TextDocumentEdit.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class TextDocumentEdit {
+ public VersionedTextDocumentIdentifier textDocument;
+ public List<TextEdit> edits;
+}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentIdentifier.java b/src/main/java/org/javacs/lsp/TextDocumentIdentifier.java
new file mode 100644
index 0000000..dce19b8
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/TextDocumentIdentifier.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+
+public class TextDocumentIdentifier {
+ public URI uri;
+}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentItem.java b/src/main/java/org/javacs/lsp/TextDocumentItem.java
new file mode 100644
index 0000000..2a68cf6
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/TextDocumentItem.java
@@ -0,0 +1,10 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+
+public class TextDocumentItem {
+ public URI uri;
+ public String languageId;
+ public int version;
+ public String text;
+}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentPositionParams.java b/src/main/java/org/javacs/lsp/TextDocumentPositionParams.java
new file mode 100644
index 0000000..1d719cb
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/TextDocumentPositionParams.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class TextDocumentPositionParams {
+ public TextDocumentIdentifier textDocument;
+ public Position position;
+}
diff --git a/src/main/java/org/javacs/lsp/TextDocumentSaveReason.java b/src/main/java/org/javacs/lsp/TextDocumentSaveReason.java
new file mode 100644
index 0000000..1f39e56
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/TextDocumentSaveReason.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class TextDocumentSaveReason {
+ public static final int Manual = 1, AfterDelay = 2, FocusOut = 3;
+}
diff --git a/src/main/java/org/javacs/lsp/TextEdit.java b/src/main/java/org/javacs/lsp/TextEdit.java
new file mode 100644
index 0000000..03fe4e5
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/TextEdit.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class TextEdit {
+ public Range range;
+ public String newText;
+}
diff --git a/src/main/java/org/javacs/lsp/VersionedTextDocumentIdentifier.java b/src/main/java/org/javacs/lsp/VersionedTextDocumentIdentifier.java
new file mode 100644
index 0000000..c374e3b
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/VersionedTextDocumentIdentifier.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+
+public class VersionedTextDocumentIdentifier {
+ public URI uri;
+ public int version;
+}
diff --git a/src/main/java/org/javacs/lsp/WillSaveTextDocumentParams.java b/src/main/java/org/javacs/lsp/WillSaveTextDocumentParams.java
new file mode 100644
index 0000000..df6b181
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/WillSaveTextDocumentParams.java
@@ -0,0 +1,6 @@
+package org.javacs.lsp;
+
+public class WillSaveTextDocumentParams {
+ public TextDocumentIdentifier textDocument;
+ public int reason;
+}
diff --git a/src/main/java/org/javacs/lsp/WorkspaceEdit.java b/src/main/java/org/javacs/lsp/WorkspaceEdit.java
new file mode 100644
index 0000000..e2cffa1
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/WorkspaceEdit.java
@@ -0,0 +1,9 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+public class WorkspaceEdit {
+ public Map<URI, List<TextEdit>> changes;
+}
diff --git a/src/main/java/org/javacs/lsp/WorkspaceFolder.java b/src/main/java/org/javacs/lsp/WorkspaceFolder.java
new file mode 100644
index 0000000..0d5c6ec
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/WorkspaceFolder.java
@@ -0,0 +1,8 @@
+package org.javacs.lsp;
+
+import java.net.URI;
+
+public class WorkspaceFolder {
+ public URI uri;
+ public String name;
+}
diff --git a/src/main/java/org/javacs/lsp/WorkspaceFoldersChangeEvent.java b/src/main/java/org/javacs/lsp/WorkspaceFoldersChangeEvent.java
new file mode 100644
index 0000000..0cac0c9
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/WorkspaceFoldersChangeEvent.java
@@ -0,0 +1,7 @@
+package org.javacs.lsp;
+
+import java.util.List;
+
+public class WorkspaceFoldersChangeEvent {
+ public List<WorkspaceFolder> added, removed;
+}
diff --git a/src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java b/src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java
new file mode 100644
index 0000000..d3859f9
--- /dev/null
+++ b/src/main/java/org/javacs/lsp/WorkspaceSymbolParams.java
@@ -0,0 +1,5 @@
+package org.javacs.lsp;
+
+public class WorkspaceSymbolParams {
+ public String query;
+}
diff --git a/src/test/java/org/javacs/lsp/LanguageServerTest.java b/src/test/java/org/javacs/lsp/LanguageServerTest.java
new file mode 100644
index 0000000..75a1faf
--- /dev/null
+++ b/src/test/java/org/javacs/lsp/LanguageServerTest.java
@@ -0,0 +1,74 @@
+package org.javacs.lsp;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.javacs.Main;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LanguageServerTest {
+ PipedInputStream clientToServer = new PipedInputStream(10 * 1024 * 1024),
+ serverToClient = new PipedInputStream(10 * 1024 * 1024);
+ PipedOutputStream writeClientToServer, writeServerToClient;
+ LanguageServer mockServer;
+ Thread main;
+ CompletableFuture<Void> receivedInitialize = new CompletableFuture<>();
+
+ class TestLanguageServer extends LanguageServer {
+ @Override
+ public InitializeResult initialize(InitializeParams params) {
+ receivedInitialize.complete(null);
+ return new InitializeResult();
+ }
+ }
+
+ static {
+ Main.setRootFormat();
+ }
+
+ @Before
+ public void connectServerAndInitialize() throws IOException {
+ writeClientToServer = new PipedOutputStream(clientToServer);
+ writeServerToClient = new PipedOutputStream(serverToClient);
+ main = new Thread(this::runServer, "runServer");
+ main.start();
+ }
+
+ private void runServer() {
+ LSP.connect(this::serverFactory, clientToServer, writeServerToClient);
+ }
+
+ private LanguageServer serverFactory(LanguageClient client) {
+ mockServer = new TestLanguageServer();
+ return mockServer;
+ }
+
+ private void sendToServer(String message) throws IOException {
+ var header = String.format("Content-Length: %d\r\n\r\n", message.getBytes().length);
+ writeClientToServer.write(header.getBytes());
+ writeClientToServer.write(message.getBytes());
+ }
+
+ String initializeMessage = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}";
+ String exitMessage = "{\"jsonrpc\":\"2.0\",\"method\":\"exit\"}";
+
+ @Test
+ public void exitMessageKillsServer()
+ throws IOException, InterruptedException, ExecutionException, TimeoutException {
+ // Send initialize message and wait for ack
+ sendToServer(initializeMessage);
+ receivedInitialize.get(1, TimeUnit.SECONDS);
+ // Send exit message and wait for exit
+ sendToServer(exitMessage);
+ main.join(1000);
+ 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
new file mode 100644
index 0000000..3f65774
--- /dev/null
+++ b/src/test/java/org/javacs/lsp/LspTest.java
@@ -0,0 +1,65 @@
+package org.javacs.lsp;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import com.google.common.base.Charsets;
+import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LspTest {
+ PipedInputStream buffer = new PipedInputStream(10 * 1024 * 1024); // 10 MB buffer
+ PipedOutputStream writer = new PipedOutputStream();
+
+ @Before
+ public void connectBuffer() throws IOException {
+ writer.connect(buffer);
+ }
+
+ String bufferToString() {
+ try {
+ var available = buffer.available();
+ var bytes = new byte[available];
+ var read = buffer.read(bytes);
+ assert read == available;
+ return new String(bytes, Charsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void writeResponse() {
+ LSP.respond(writer, 1, 2);
+ var expected = "Content-Length: 35\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":2}";
+ assertThat(bufferToString(), equalTo(expected));
+ }
+
+ @Test
+ public void writeMultibyteCharacters() {
+ LSP.respond(writer, 1, "🔥");
+ var expected = "Content-Length: 40\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"🔥\"}";
+ 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);
+ writer.write(header.getBytes());
+ writer.write(message.getBytes());
+
+ var token = LSP.nextToken(buffer);
+ assertThat(token, equalTo(message));
+
+ var parse = LSP.parseMessage(token);
+ assertThat(parse.jsonrpc, equalTo("2.0"));
+ assertThat(parse.id, equalTo(1));
+ assertThat(parse.method, equalTo("initialize"));
+ assertThat(parse.params, equalTo(new JsonObject()));
+ }
+}