You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by lk...@apache.org on 2020/10/23 01:10:59 UTC
[netbeans] 12/18: [lsp] partial fix of invalidation of breakpoints
during file open (#2462)
This is an automated email from the ASF dual-hosted git repository.
lkishalmi pushed a commit to branch release122
in repository https://gitbox.apache.org/repos/asf/netbeans.git
commit a2bdbacfea69ca1b68d16f5e6b650e9be5865bc4
Author: Svatopluk Dedic <sv...@oracle.com>
AuthorDate: Thu Oct 22 09:10:26 2020 +0200
[lsp] partial fix of invalidation of breakpoints during file open (#2462)
* Fixed reset of all breakpoints on document open.
* Tested position stability during didOpen.
---
.../server/protocol/TextDocumentServiceImpl.java | 46 ++++-
.../java/lsp/server/protocol/ServerTest.java | 212 ++++++++++++++++++---
2 files changed, 219 insertions(+), 39 deletions(-)
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index 5728e9a..e0a1df0 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -57,6 +57,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
import java.util.prefs.Preferences;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
@@ -1009,19 +1010,32 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
try {
FileObject file = fromUri(params.getTextDocument().getUri());
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
- Document doc = ec.openDocument();
- openedDocuments.put(params.getTextDocument().getUri(), doc);
- String text = params.getTextDocument().getText();
- try {
- doc.remove(0, doc.getLength());
- doc.insertString(0, text, null);
- } catch (BadLocationException ex) {
- //TODO: include stack trace:
- client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+ Document doc = ec.getDocument();
+ // the document may be not opened yet. Clash with in-memory content can happen only if
+ // the doc was opened prior to request reception.
+ if (doc != null) {
+ String text = params.getTextDocument().getText();
+ try {
+ // could be faster with CharSequence, but requires a dependency on
+ // org.netbeans.modules.editor.util
+ if (!text.contentEquals(doc.getText(0, doc.getLength()))) {
+ doc.remove(0, doc.getLength());
+ doc.insertString(0, text, null);
+ }
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ //TODO: include stack trace:
+ client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+ }
+ } else {
+ doc = ec.openDocument();
}
+ openedDocuments.put(params.getTextDocument().getUri(), doc);
runDiagnoticTasks(params.getTextDocument().getUri());
} catch (IOException ex) {
throw new IllegalStateException(ex);
+ } finally {
+ reportNotificationDone("didOpen", params);
}
}
@@ -1041,11 +1055,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
}
});
runDiagnoticTasks(params.getTextDocument().getUri());
+ reportNotificationDone("didChange", params);
}
@Override
public void didClose(DidCloseTextDocumentParams params) {
openedDocuments.remove(params.getTextDocument().getUri());
+ reportNotificationDone("didClose", params);
}
@Override
@@ -1279,4 +1295,16 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
}
return edits;
}
+
+ private static void reportNotificationDone(String s, Object parameter) {
+ if (HOOK_NOTIFICATION != null) {
+ HOOK_NOTIFICATION.accept(s, parameter);
+ }
+ }
+
+ /**
+ * For testing only; calls that do not return a result should call
+ * this hook, if defined, with the method name and parameter.
+ */
+ static BiConsumer<String, Object> HOOK_NOTIFICATION = null;
}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 67d9409..4d7231c 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -23,12 +23,15 @@ 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;
@@ -37,7 +40,10 @@ 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.StyledDocument;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionParams;
@@ -49,6 +55,7 @@ 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;
@@ -79,7 +86,6 @@ 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.junit.Assume;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.source.JavaSource;
@@ -93,10 +99,14 @@ 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;
@@ -153,8 +163,42 @@ public class ServerTest extends NbTestCase {
@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");
@@ -163,35 +207,7 @@ public class ServerTest extends NbTestCase {
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());
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient(), client.getInputStream(), client.getOutputStream());
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
@@ -255,7 +271,143 @@ public class ServerTest extends NbTestCase {
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);
+
+ public void accept(String n, Object params){
+ switch (n) {
+ case "didOpen":
+ didOpenCompleted.release();
+ break;
+ case "didClose":
+ didCloseCompleted.release();
+ break;
+ }
+ }
+ }
+
+ /**
+ * 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 =
+ "public class Test \n"
+ + "{ \n"
+ + " int i = \"\".hashCode();\n"
+ + " public void run() {\n"
+ + " this.test(); \n"
+ + " }\n\n"
+ + " /**Test.*/public void test() {\n"
+ + " }\n"
+ + "}";
+ 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);
+ }
+
public void testCodeActionWithRemoval() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists