blob: 38bccfe7f9dbfac514d4028c3d196797b5008543 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.lsp.server.protocol;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightKind;
import org.eclipse.lsp4j.DocumentHighlightParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ReferenceContext;
import org.eclipse.lsp4j.ReferenceParams;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.project.Project;
import org.netbeans.api.sendopts.CommandLine;
import org.netbeans.junit.NbTestCase;
import org.netbeans.modules.java.source.BootClassPathUtil;
import org.netbeans.modules.parsing.impl.indexing.implspi.CacheFolderProvider;
import org.netbeans.spi.java.classpath.ClassPathProvider;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.project.ProjectFactory;
import org.netbeans.spi.project.ProjectState;
import org.netbeans.spi.project.ui.ProjectOpenedHook;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.LineCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.modules.ModuleInfo;
import org.openide.modules.Places;
import org.openide.text.Line;
import org.openide.text.NbDocument;
import org.openide.util.Lookup;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ServiceProvider;
/**
*
* @author lahvac
*/
public class ServerTest extends NbTestCase {
private Socket client;
private Thread serverThread;
public ServerTest(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
super.setUp();
clearWorkDir();
ServerSocket srv = new ServerSocket(0, 1, InetAddress.getLoopbackAddress());
serverThread = new Thread(() -> {
try {
Socket server = srv.accept();
Path tempDir = Files.createTempDirectory("lsp-server");
File userdir = tempDir.resolve("scratch-user").toFile();
File cachedir = tempDir.resolve("scratch-cache").toFile();
System.setProperty("netbeans.user", userdir.getAbsolutePath());
File varLog = new File(new File(userdir, "var"), "log");
varLog.mkdirs();
System.setProperty("jdk.home", System.getProperty("java.home")); //for j2seplatform
Class<?> main = Class.forName("org.netbeans.core.startup.Main");
main.getDeclaredMethod("initializeURLFactory").invoke(null);
new File(cachedir, "index").mkdirs();
Class jsClass = JavaSource.class;
File javaCluster = Utilities.toFile(jsClass.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getParentFile();
System.setProperty("netbeans.dirs", javaCluster.getAbsolutePath());
CacheFolderProvider.getCacheFolderForRoot(Places.getUserDirectory().toURI().toURL(), EnumSet.noneOf(CacheFolderProvider.Kind.class), CacheFolderProvider.Mode.EXISTENT);
Lookup.getDefault().lookup(ModuleInfo.class); //start the module system
CommandLine.getDefault().process(new String[] {"--start-java-language-server"}, server.getInputStream(), server.getOutputStream(), System.err, getWorkDir());
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
});
serverThread.start();
client = new Socket(srv.getInetAddress(), srv.getLocalPort());
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
TextDocumentServiceImpl.HOOK_NOTIFICATION = null;
serverThread.stop();
}
List<Diagnostic>[] diags = new List[1];
class LspClient implements LanguageClient {
List<MessageParams> loggedMessages = new ArrayList<>();
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
synchronized (diags) {
diags[0] = params.getDiagnostics();
diags.notifyAll();
}
}
@Override
public void showMessage(MessageParams arg0) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
loggedMessages.add(arg0);
}
}
public void testMain() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
String code = "public class Test { int i = \"\".hashCode(); public void run() { this.test(); } /**Test.*/public void test() {} }";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient(), client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
assertDiags(diags);//errors
assertDiags(diags);//hints
int hashCodeStart = code.indexOf("hashCode");
Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get();
assertTrue(completion.isRight());
List<String> actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
assertEquals(Arrays.asList("Method:hashCode() : int"), actualItems);
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1);
id.setUri(src.toURI().toString());
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "hashCode".length())), "hashCode".length(), "equ"))));
assertDiags(diags, "Error:0:31-0:34");//errors
assertDiags(diags, "Error:0:31-0:34");//hints
completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get();
actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
if (jdk9Plus()) {
assertEquals(Arrays.asList("Method:equals(Object anObject) : boolean", "Method:equalsIgnoreCase(String anotherString) : boolean"), actualItems);
}
int testStart = code.indexOf("test") + "equ".length() - "hashCode".length();
completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, testStart + 3))).get();
List<CompletionItem> actualCompletionItem = completion.getRight().getItems();
actualItems = actualCompletionItem.stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
assertEquals(Arrays.asList("Method:test() : void"), actualItems);
assertEquals(null, actualCompletionItem.get(0).getDocumentation());
CompletionItem resolvedItem = server.getTextDocumentService().resolveCompletionItem(actualCompletionItem.get(0)).get();
assertEquals("**[Test](*0)**\n" +
"\n" +
"```\n" +
"public void test()\n" +
"```\n" +
"\n" +
"Test.\n" +
"\n",
resolvedItem.getDocumentation().getRight().getValue());
completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, 0))).get();
actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
assertTrue(actualItems.contains("Keyword:interface"));
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "equ".length())), "equ".length(), "hashCode"))));
int closingBrace = code.lastIndexOf("}");
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "public String c(Object o) {\nreturn o;\n}"))));
List<Diagnostic> diagnostics = assertDiags(diags, "Error:1:0-1:9"); //errors
assertDiags(diags, "Error:1:0-1:9");//hints
List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 0), new Position(1, 9)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get();
String log = codeActions.toString();
assertEquals(log, 2, codeActions.size());
assertTrue(log, codeActions.get(0).isRight());
CodeAction action = codeActions.get(0).getRight();
assertEquals("Cast ...o to String", action.getTitle());
assertEquals(1, action.getEdit().getDocumentChanges().size());
assertEquals(1, action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().size());
TextEdit edit = action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().get(0);
assertEquals(1, edit.getRange().getStart().getLine());
assertEquals(7, edit.getRange().getStart().getCharacter());
assertEquals(1, edit.getRange().getEnd().getLine());
assertEquals(7, edit.getRange().getEnd().getCharacter());
assertEquals("(String) ", edit.getNewText());
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "public void assignToSelf(Object o) { o = o; }"))));
assertDiags(diags, "Error:1:0-1:9");//errors
assertDiags(diags, "Error:1:0-1:9", "Warning:0:148-0:153", "Warning:0:152-0:153");//hints
}
private class OpenCloseHook {
private Semaphore didOpenCompleted = new Semaphore(0);
private Semaphore didCloseCompleted = new Semaphore(0);
private Semaphore didChangeCompleted = new Semaphore(0);
public void accept(String n, Object params){
switch (n) {
case "didOpen":
didOpenCompleted.release();
break;
case "didClose":
didCloseCompleted.release();
break;
case "didChange":
didChangeCompleted.release();
break;
}
}
}
private static final String SAMPLE_CODE =
"public class Test \n"
+ "{ \n"
+ " int i = \"\".hashCode();\n"
+ " public void run() {\n"
+ " this.test(); \n"
+ " }\n\n"
+ " /**Test.*/public void test() {\n"
+ " }\n"
+ "}";
/**
* Checks that opening the document preserves lines. This is necessary for breakpoints
* or computed markers. The test will:
* <ul>
* <li>Open a document, create a Line object (which uses PositionRefs). Close the doucment. Load with didOpen(). This is the initial scenario.
* <li>Leave line's document opened; load with didOpen(). Simulates the case that the backend has been working with the text.
* <li>Initially opens a document with didOpen(). Then simulate close with didClose() with a recorded position; open again with didOpen().
* </ul>
*
* @throws Exception
*/
public void testDidOpenPreservesLines() throws Exception {
File src = new File(getWorkDir(), "Test.java");
File src2 = new File(getWorkDir(), "Test2.java");
File src3 = new File(getWorkDir(), "Test3.java");
src.getParentFile().mkdirs();
String code = SAMPLE_CODE;
String code2 = code.replace("Test", "Test2");
String code3 = code.replace("Test", "Test3");
try (Writer w = new FileWriter(src)) {
w.write(code);
}
try (Writer w = new FileWriter(src2)) {
w.write(code2);
}
try (Writer w = new FileWriter(src3)) {
w.write(code3);
}
FileObject f1 = FileUtil.toFileObject(src);
EditorCookie cake = f1.getLookup().lookup(EditorCookie.class);
LineCookie lines = f1.getLookup().lookup(LineCookie.class);
StyledDocument d = cake.openDocument();
javax.swing.text.Position p = NbDocument.createPosition(d, 23, javax.swing.text.Position.Bias.Forward);
int offset1 = p.getOffset();
int line1 = NbDocument.findLineNumber(d, p.getOffset());
Line lineObject1 = lines.getLineSet().getCurrent(line1);
cake.close();
FileObject f2 = FileUtil.toFileObject(src2);
cake = f2.getLookup().lookup(EditorCookie.class);
StyledDocument d2 = cake.openDocument();
javax.swing.text.Position p2 = NbDocument.createPosition(d2, 40, javax.swing.text.Position.Bias.Forward);
int offset2 = p2.getOffset();
int line2 = NbDocument.findLineNumber(d2, offset2);
LineCookie lines2 = f2.getLookup().lookup(LineCookie.class);
Line lineObject2 = lines2.getLineSet().getCurrent(line2);
OpenCloseHook hook = new OpenCloseHook();
TextDocumentServiceImpl.HOOK_NOTIFICATION = hook::accept;
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient(), client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src2.toURI().toString(), "java", 0, code2)));
assertTrue(hook.didOpenCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
int nl2 = NbDocument.findLineNumber(d2, p2.getOffset());
assertEquals(line2, lineObject2.getLineNumber());
assertEquals(line2, nl2);
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
assertTrue(hook.didOpenCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
d = cake.openDocument();
int nl1 = NbDocument.findLineNumber(d, p.getOffset());
assertEquals(line1, lineObject1.getLineNumber());
assertEquals(line1, nl1);
FileObject f3 = FileUtil.toFileObject(src3);
TextDocumentItem tdi = new TextDocumentItem(src3.toURI().toString(), "java", 0, code3);
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(tdi));
assertTrue(hook.didOpenCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
cake = f3.getLookup().lookup(EditorCookie.class);
StyledDocument d3 = cake.openDocument();
javax.swing.text.Position p3 = NbDocument.createPosition(d3, 40, javax.swing.text.Position.Bias.Forward);
int offset3 = p3.getOffset();
int line3 = NbDocument.findLineNumber(d3, offset3);
LineCookie lines3 = f3.getLookup().lookup(LineCookie.class);
Line lineObject3 = lines3.getLineSet().getCurrent(line3);
server.getTextDocumentService().didClose(new DidCloseTextDocumentParams(new TextDocumentIdentifier(src3.toURI().toString())));
assertTrue(hook.didCloseCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
// open again
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(tdi));
assertTrue(hook.didOpenCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
int nl3 = NbDocument.findLineNumber(d, p3.getOffset());
assertEquals(line3, lineObject3.getLineNumber());
assertEquals(line3, nl3);
// close and release the document, too
server.getTextDocumentService().didClose(new DidCloseTextDocumentParams(new TextDocumentIdentifier(src3.toURI().toString())));
assertTrue(hook.didCloseCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
Reference<StyledDocument> refDoc = new WeakReference<>(d3);
d3 = null;
assertGC("Document should be collected", refDoc);
assertNull(cake.getDocument());
// open again
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(tdi));
assertTrue(hook.didOpenCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
nl3 = NbDocument.findLineNumber(d, p3.getOffset());
assertEquals(line3, lineObject3.getLineNumber());
assertEquals(line3, nl3);
}
/**
* Simulates Ctrl-N ve VScode plus paste of initial content, then save. According to the
* report, DidOpen will come with an empty forced initial content. The DidChange comes that will
* inject the pasted content.
* @throws Exception
*/
public void testSimulateNewUnnamedFile() throws Exception {
File src = new File(getWorkDir(), "Test.java");
String code = SAMPLE_CODE;
// write in an initial code on the disk
try (Writer w = new FileWriter(src)) {
w.write(code);
}
FileObject f1 = FileUtil.toFileObject(src);
OpenCloseHook hook = new OpenCloseHook();
TextDocumentServiceImpl.HOOK_NOTIFICATION = hook::accept;
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient(), client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
// open with empty initial content.
String uriString = src.toURI().toString();
server.getTextDocumentService().didOpen(
new DidOpenTextDocumentParams(new TextDocumentItem(uriString, "java", 0, ""))
);
EditorCookie cake = f1.getLookup().lookup(EditorCookie.class);
assertTrue(hook.didOpenCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(uriString, 1);
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(
id, Arrays.asList(
new TextDocumentContentChangeEvent(new Range(new Position(0, 0), new Position(0, 0)), 0, code)
)
));
assertTrue(hook.didChangeCompleted.tryAcquire(400, TimeUnit.MILLISECONDS));
Document doc = cake.openDocument();
assertEquals(code, doc.getText(0, doc.getLength()));
}
public void testCodeActionWithRemoval() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
String code = "public class Test { public String c(String s) {\nreturn s.toString();\n} }";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
List<Diagnostic>[] diags = new List[1];
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
synchronized (diags) {
diags[0] = params.getDiagnostics();
diags.notifyAll();
}
}
@Override
public void showMessage(MessageParams arg0) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
assertDiags(diags); //errors
List<Diagnostic> diagnostics = assertDiags(diags, "Warning:1:7-1:19");//hints
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1);
id.setUri(src.toURI().toString());
List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 7), new Position(1, 19)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get();
String log = codeActions.toString();
assertEquals(log, 1, codeActions.size());
assertTrue(log, codeActions.get(0).isRight());
CodeAction action = codeActions.get(0).getRight();
assertEquals("Remove .toString()", action.getTitle());
assertEquals(1, action.getEdit().getDocumentChanges().size());
assertEquals(1, action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().size());
TextEdit edit = action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().get(0);
assertEquals(1, edit.getRange().getStart().getLine());
assertEquals(8, edit.getRange().getStart().getCharacter());
assertEquals(1, edit.getRange().getEnd().getLine());
assertEquals(19, edit.getRange().getEnd().getCharacter());
assertEquals("", edit.getNewText());
}
private List<Diagnostic> assertDiags(List<Diagnostic>[] diags, String... expected) {
synchronized (diags) {
while (diags[0] == null) {
try {
diags.wait();
} catch (InterruptedException ex) {
//ignore
}
}
Set<String> actualDiags = diags[0].stream()
.map(d -> d.getSeverity() + ":" +
d.getRange().getStart().getLine() + ":" + d.getRange().getStart().getCharacter() + "-" +
d.getRange().getEnd().getLine() + ":" + d.getRange().getEnd().getCharacter())
.collect(Collectors.toSet());
String diagsMessage = diags[0].stream()
.map(d -> d.getSeverity() + ":" +
d.getRange().getStart().getLine() + ":" + d.getRange().getStart().getCharacter() + "-" +
d.getRange().getEnd().getLine() + ":" + d.getRange().getEnd().getCharacter() + ": " +
d.getMessage())
.collect(Collectors.joining("\n"));
assertEquals(diagsMessage, new HashSet<>(Arrays.asList(expected)), actualDiags);
List<Diagnostic> result = diags[0];
diags[0] = null;
return result;
}
}
public void testNavigator() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
String code = "public class Test {\n" +
" private int field;\n" +
" public void method() {\n" +
" }\n" +
" class Inner {\n" +
" public void innerMethod() {\n" +
" }\n" +
" }\n" +
"}\n";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
FileUtil.refreshFor(getWorkDir());
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
}
@Override
public void showMessage(MessageParams arg0) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
List<Either<SymbolInformation, DocumentSymbol>> symbols = server.getTextDocumentService().documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(src.toURI().toString()))).get();
String textualSymbols = "";
String sep = "";
for (Either<SymbolInformation, DocumentSymbol> sym : symbols) {
assertTrue(sym.isRight());
textualSymbols += sep;
textualSymbols += toString(sym.getRight());
sep = ", ";
}
String expected = "Class:Test:Range [\n" +
" start = Position [\n" +
" line = 0\n" +
" character = 0\n" +
" ]\n" +
" end = Position [\n" +
" line = 8\n" +
" character = 1\n" +
" ]\n" +
"]:(Class:Inner:Range [\n" +
" start = Position [\n" +
" line = 4\n" +
" character = 4\n" +
" ]\n" +
" end = Position [\n" +
" line = 7\n" +
" character = 5\n" +
" ]\n" +
"]:(Method:innerMethod:Range [\n" +
" start = Position [\n" +
" line = 5\n" +
" character = 8\n" +
" ]\n" +
" end = Position [\n" +
" line = 6\n" +
" character = 9\n" +
" ]\n" +
"]:()), Field:field:Range [\n" +
" start = Position [\n" +
" line = 1\n" +
" character = 4\n" +
" ]\n" +
" end = Position [\n" +
" line = 1\n" +
" character = 22\n" +
" ]\n" +
"]:(), Method:method:Range [\n" +
" start = Position [\n" +
" line = 2\n" +
" character = 4\n" +
" ]\n" +
" end = Position [\n" +
" line = 3\n" +
" character = 5\n" +
" ]\n" +
"]:())";
assertEquals(expected, textualSymbols);
}
private String toString(DocumentSymbol sym) {
return sym.getKind().toString() + ":" +
sym.getName() + ":" +
sym.getRange() + ":" +
sym.getChildren()
.stream()
.map(this::toString)
.collect(Collectors.joining(", ", "(", ")"));
}
public void testGoToDefinition() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
String code = "public class Test {\n" +
" private int field;\n" +
" public void method(int ppp) {\n" +
" System.err.println(field);\n" +
" System.err.println(ppp);\n" +
" new Other().test();\n" +
" }\n" +
"}\n";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
File otherSrc = new File(getWorkDir(), "Other.java");
try (Writer w = new FileWriter(otherSrc)) {
w.write("/**Some source*/\n" +
"public class Other {\n" +
" public void test() { }\n" +
"}");
}
FileUtil.refreshFor(getWorkDir());
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
}
@Override
public void showMessage(MessageParams arg0) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
Position pos = new Position(3, 30);
List<? extends Location> definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
assertEquals(1, definition.size());
assertEquals(src.toURI().toString(), definition.get(0).getUri());
assertEquals(1, definition.get(0).getRange().getStart().getLine());
assertEquals(4, definition.get(0).getRange().getStart().getCharacter());
assertEquals(1, definition.get(0).getRange().getEnd().getLine());
assertEquals(22, definition.get(0).getRange().getEnd().getCharacter());
pos = new Position(4, 30);
definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
assertEquals(1, definition.size());
assertEquals(src.toURI().toString(), definition.get(0).getUri());
assertEquals(2, definition.get(0).getRange().getStart().getLine());
assertEquals(23, definition.get(0).getRange().getStart().getCharacter());
assertEquals(2, definition.get(0).getRange().getEnd().getLine());
assertEquals(30, definition.get(0).getRange().getEnd().getCharacter());
pos = new Position(5, 22);
definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
assertEquals(1, definition.size());
assertEquals(otherSrc.toURI().toString(), definition.get(0).getUri());
assertEquals(2, definition.get(0).getRange().getStart().getLine());
assertEquals(4, definition.get(0).getRange().getStart().getCharacter());
assertEquals(2, definition.get(0).getRange().getEnd().getLine());
assertEquals(26, definition.get(0).getRange().getEnd().getCharacter());
}
public void testOpenProjectOpenJDK() throws Exception {
getWorkDir().mkdirs();
FileObject root = FileUtil.toFileObject(getWorkDir());
try (Writer w = new OutputStreamWriter(FileUtil.createData(root, "jdk/src/java.base/share/classes/java/lang/Object.java").getOutputStream(), StandardCharsets.UTF_8)) {
w.write("package java.lang; public class Object {}");
}
FileUtil.createData(root, "jdk/src/java.base/share/classes/impl/Service.java");
FileObject javaBaseMI = FileUtil.createData(root, "jdk/src/java.base/share/classes/module-info.java");
try (Writer w = new OutputStreamWriter(javaBaseMI.getOutputStream(), StandardCharsets.UTF_8)) {
w.write("module java.base { exports java.lang; }");
}
try (Writer w = new OutputStreamWriter(FileUtil.createData(root, "jdk/src/java.compiler/share/classes/module-info.java").getOutputStream(), StandardCharsets.UTF_8)) {
w.write("module java.compiler { }");
}
List<Diagnostic>[] diags = new List[1];
boolean[] indexingComplete = new boolean[1];
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
synchronized (diags) {
diags[0] = params.getDiagnostics();
diags.notifyAll();
}
}
@Override
public void showMessage(MessageParams params) {
if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
synchronized (indexingComplete) {
indexingComplete[0] = true;
indexingComplete.notifyAll();
}
} else {
throw new UnsupportedOperationException("Unexpected message.");
}
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeParams initParams = new InitializeParams();
initParams.setWorkspaceFolders(Arrays.asList(new WorkspaceFolder(root.getFileObject("jdk/src/java.base").toURI().toString())));
InitializeResult result = server.initialize(initParams).get();
synchronized (indexingComplete) {
while (!indexingComplete[0]) {
try {
indexingComplete.wait();
} catch (InterruptedException ex) {
//ignore...
}
}
}
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(javaBaseMI.toURI().toString(), "java", 0, javaBaseMI.asText("UTF-8"))));
assertDiags(diags);
}
public void testMarkOccurrences() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
String code = "public class Test {\n" +
" public int method(int ppp) {\n" +
" if (ppp < 0) return -1;\n" +
" else if (ppp > 0) return 1;\n" +
" else return 0;\n" +
" }\n" +
"}\n";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
FileUtil.refreshFor(getWorkDir());
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
}
@Override
public void showMessage(MessageParams arg0) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
assertTrue(result.getCapabilities().getDocumentHighlightProvider());
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 13))).get(),
"<none>:2:21-2:31", "<none>:3:26-3:35", "<none>:4:13-4:22");
assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 27))).get(),
"<none>:1:26-1:29", "<none>:2:12-2:15", "<none>:3:17-3:20");
}
public void testAdvancedCompletion1() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
String code = "public class Test {\n" +
" private void t(String s) {\n" +
" \n" +
" }\n" +
"}\n";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
List<Diagnostic>[] diags = new List[1];
CountDownLatch indexingComplete = new CountDownLatch(1);
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
synchronized (diags) {
diags[0] = params.getDiagnostics();
diags.notifyAll();
}
}
@Override
public void showMessage(MessageParams params) {
if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
indexingComplete.countDown();
} else {
throw new UnsupportedOperationException("Unexpected message.");
}
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeParams initParams = new InitializeParams();
initParams.setRootUri(getWorkDir().toURI().toString());
InitializeResult result = server.initialize(initParams).get();
indexingComplete.await();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
{
VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id1, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(2, 8), new Position(2, 8)), 0, "s."))));
Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(2, 8 + "s.".length()))).get();
assertTrue(completion.isRight());
Optional<CompletionItem> lengthItem = completion.getRight().getItems().stream().filter(ci -> "length() : int".equals(ci.getLabel())).findAny();
assertTrue(lengthItem.isPresent());
assertEquals(InsertTextFormat.PlainText, lengthItem.get().getInsertTextFormat());
assertEquals("length()", lengthItem.get().getInsertText());
Optional<CompletionItem> substringItem = completion.getRight().getItems().stream().filter(ci -> ci.getLabel().startsWith("substring(") && ci.getLabel().contains(",")).findAny();
assertTrue(substringItem.isPresent());
assertEquals(InsertTextFormat.PlainText, substringItem.get().getInsertTextFormat());
assertEquals("substring(", substringItem.get().getInsertText());
}
{
VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(1, 1), new Position(1, 1)), 0, "@java.lang."))));
Position afterJavaLang = new Position(1, 1 + "@java.lang.".length());
{
Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLang)).get();
assertTrue(completion.isRight());
Optional<CompletionItem> annotationItem = completion.getRight().getItems().stream().filter(ci -> "annotation".equals(ci.getLabel())).findAny();
assertTrue(annotationItem.isPresent());
assertEquals("annotation", annotationItem.get().getLabel());
assertEquals(CompletionItemKind.Folder, annotationItem.get().getKind());
}
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(afterJavaLang, afterJavaLang), 0, "annotation."))));
Position afterJavaLangAnnotation = new Position(1, afterJavaLang.getCharacter() + "annotation.".length());
{
Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLangAnnotation)).get();
assertTrue(completion.isRight());
completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
Optional<CompletionItem> targetItem = completion.getRight().getItems().stream().filter(ci -> "Target".equals(ci.getLabel())).findAny();
assertTrue(targetItem.isPresent());
assertEquals("Target", targetItem.get().getLabel()); //TODO: insert text '('!
assertEquals(CompletionItemKind.Interface, targetItem.get().getKind());
}
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(afterJavaLangAnnotation, afterJavaLangAnnotation), 0, "Target("))));
Position afterTarget = new Position(1, afterJavaLangAnnotation.getCharacter() + "Target(".length());
{
Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get();
assertTrue(completion.isRight());
completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
Optional<CompletionItem> methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny();
assertTrue(methodItem.isPresent());
assertEquals(InsertTextFormat.PlainText, methodItem.get().getInsertTextFormat());
assertEquals("ElementType.METHOD", methodItem.get().getInsertText());
assertEquals(1, methodItem.get().getAdditionalTextEdits().size());
assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getLine());
assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getCharacter());
assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getLine());
assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getCharacter());
assertEquals("\nimport java.lang.annotation.ElementType;\n\n", methodItem.get().getAdditionalTextEdits().get(0).getNewText());
}
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, 0), new Position(0, 0)), 0, "import java.lang.annotation.ElementType;"))));
{
//import already exists:
Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get();
assertTrue(completion.isRight());
completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
Optional<CompletionItem> methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny();
assertTrue(methodItem.isPresent());
assertEquals(InsertTextFormat.PlainText, methodItem.get().getInsertTextFormat());
assertEquals("ElementType.METHOD", methodItem.get().getInsertText());
assertEquals(0, methodItem.get().getAdditionalTextEdits().size());
}
}
}
public void testFixImports() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
String code = "public class Test {\n" +
" private void t() {\n" +
" List l;\n" +
" }\n" +
"}\n";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
List<Diagnostic>[] diags = new List[1];
CountDownLatch indexingComplete = new CountDownLatch(1);
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
@Override
public void showStatusBarMessage(ShowStatusMessageParams params) {
if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
indexingComplete.countDown();
}
}
@Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
synchronized (diags) {
diags[0] = params.getDiagnostics();
diags.notifyAll();
}
}
@Override
public void showMessage(MessageParams params) {
throw new UnsupportedOperationException("Unexpected message.");
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeParams initParams = new InitializeParams();
initParams.setInitializationOptions(new JsonParser().parse(
"{ nbcodeCapabilities: { statusBarMessageSupport : true } }").getAsJsonObject());
initParams.setRootUri(getWorkDir().toURI().toString());
InitializeResult result = server.initialize(initParams).get();
indexingComplete.await();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
Diagnostic unresolvable = assertDiags(diags, "Error:2:8-2:12").get(0);
List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(new TextDocumentIdentifier(src.toURI().toString()), unresolvable.getRange(), new CodeActionContext(Arrays.asList(unresolvable)))).get();
if (jdk9Plus()) {
assertEquals(2, codeActions.size());
}
}
public void testFindUsages() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
String code = "public class Test {\n" +
" int val = new Test2().get();\n" +
"}\n";
try (Writer w = new FileWriter(src)) {
w.write(code);
}
File src2 = new File(getWorkDir(), "Test2.java");
try (Writer w = new FileWriter(src2)) {
w.write("public class Test2 extends Test {\n" +
" Test t;\n" +
" void m(Test p) {};\n" +
" int get() { return t.val; };\n" +
"}\n");
}
List<Diagnostic>[] diags = new List[1];
CountDownLatch indexingComplete = new CountDownLatch(1);
Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void publishDiagnostics(PublishDiagnosticsParams params) {
synchronized (diags) {
diags[0] = params.getDiagnostics();
diags.notifyAll();
}
}
@Override
public void showMessage(MessageParams params) {
if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
indexingComplete.countDown();
} else {
throw new UnsupportedOperationException("Unexpected message.");
}
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void logMessage(MessageParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
}, client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeParams initParams = new InitializeParams();
initParams.setRootUri(getWorkDir().toURI().toString());
InitializeResult result = server.initialize(initParams).get();
indexingComplete.await();
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
{
ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()),
new Position(0, 15),
new ReferenceContext(false));
Set<? extends String> locations = server.getTextDocumentService().references(params).get().stream().map(this::toString).collect(Collectors.toSet());
Set<? extends String> expected = new HashSet<>(Arrays.asList("Test2.java:1:4-1:8", "Test2.java:0:27-0:31", "Test2.java:2:11-2:15"));
assertEquals(expected, locations);
}
{
ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()),
new Position(0, 15),
new ReferenceContext(true));
Set<? extends String> locations = server.getTextDocumentService().references(params).get().stream().map(this::toString).collect(Collectors.toSet());
Set<? extends String> expected = new HashSet<>(Arrays.asList("Test2.java:1:4-1:8", "Test2.java:0:27-0:31", "Test2.java:2:11-2:15", "Test.java:0:13-0:17"));
assertEquals(expected, locations);
}
{
ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src2.toURI().toString()),
new Position(0, 29),
new ReferenceContext(true));
Set<? extends String> locations = server.getTextDocumentService().references(params).get().stream().map(this::toString).collect(Collectors.toSet());
Set<? extends String> expected = new HashSet<>(Arrays.asList("Test2.java:1:4-1:8", "Test2.java:0:27-0:31", "Test2.java:2:11-2:15", "Test.java:0:13-0:17"));
assertEquals(expected, locations);
}
{
ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src2.toURI().toString()),
new Position(3, 10),
new ReferenceContext(true));
Set<? extends String> locations = server.getTextDocumentService().references(params).get().stream().map(this::toString).collect(Collectors.toSet());
Set<? extends String> expected = new HashSet<>(Arrays.asList("Test.java:1:26-1:29", "Test2.java:3:8-3:11"));
assertEquals(expected, locations);
}
{
ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src2.toURI().toString()),
new Position(3, 27),
new ReferenceContext(true));
Set<? extends String> locations = server.getTextDocumentService().references(params).get().stream().map(this::toString).collect(Collectors.toSet());
Set<? extends String> expected = new HashSet<>(Arrays.asList("Test.java:1:8-1:11", "Test2.java:3:25-3:28"));
assertEquals(expected, locations);
}
}
private String toString(Location location) {
String path = location.getUri();
String simpleName = path.substring(path.lastIndexOf('/') + 1);
return simpleName + ":" + location.getRange().getStart().getLine() + ":" + location.getRange().getStart().getCharacter() +
"-" + location.getRange().getEnd().getLine() + ":" + location.getRange().getEnd().getCharacter();
}
private void assertHighlights(List<? extends DocumentHighlight> highlights, String... expected) {
Set<String> stringHighlights = new HashSet<>();
for (DocumentHighlight h : highlights) {
DocumentHighlightKind kind = h.getKind();
stringHighlights.add((kind != null ? kind.name() : "<none>") + ":" +
h.getRange().getStart().getLine() + ":" + h.getRange().getStart().getCharacter() + "-" +
h.getRange().getEnd().getLine() + ":" + h.getRange().getEnd().getCharacter());
}
assertEquals(new HashSet<>(Arrays.asList(expected)),
stringHighlights);
}
private static boolean jdk9Plus() {
String version = System.getProperty("java.version");
if (version == null || version.startsWith("1.")) {
return false;
}
return true;
}
//make sure files can access other files in the same directory:
@ServiceProvider(service=ClassPathProvider.class, position=100)
public static final class ClassPathProviderImpl implements ClassPathProvider {
@Override
public ClassPath findClassPath(FileObject file, String type) {
if (ClassPath.SOURCE.equals(type) && file.isData()) {
return ClassPathSupport.createClassPath(file.getParent());
}
if (ClassPath.BOOT.equals(type)) {
return BootClassPathUtil.getBootClassPath();
}
return null;
}
}
//tests may run as a project, so that indexing works properly:
@ServiceProvider(service=ProjectFactory.class)
public static class TestProjectFactory implements ProjectFactory {
@Override
public boolean isProject(FileObject projectDirectory) {
return projectDirectory.getFileObject(".test-project") != null;
}
@Override
public Project loadProject(FileObject projectDirectory, ProjectState state) throws IOException {
if (isProject(projectDirectory)) {
ClassPath source = ClassPathSupport.createClassPath(projectDirectory);
Lookup lookup = Lookups.fixed(new ProjectOpenedHook() {
@Override
protected void projectOpened() {
GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {source});
}
@Override
protected void projectClosed() {
GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, new ClassPath[] {source});
}
}, new ClassPathProvider() {
@Override
public ClassPath findClassPath(FileObject file, String type) {
switch (type) {
case ClassPath.SOURCE: return source;
case ClassPath.BOOT: return BootClassPathUtil.getBootClassPath();
}
return null;
}
}
);
return new Project() {
@Override
public FileObject getProjectDirectory() {
return projectDirectory;
}
@Override
public Lookup getLookup() {
return lookup;
}
};
}
return null;
}
@Override
public void saveProject(Project project) throws IOException, ClassCastException {
}
}
static {
System.setProperty("SourcePath.no.source.filter", "true");
}
}