This is an automated email from the ASF dual-hosted git repository. jlahoda pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new 39e97b9 Assorted language server protocol client improvements 39e97b9 is described below commit 39e97b900a8d604f3968e07605ee0d7709f37977 Author: Jan Lahoda <jlah...@netbeans.org> AuthorDate: Sun Aug 4 20:24:30 2019 +0200 Assorted language server protocol client improvements --- ide/lsp.client/nbproject/project.xml | 55 ++++++++ .../netbeans/modules/lsp/client/LSPBindings.java | 8 +- .../src/org/netbeans/modules/lsp/client/Utils.java | 103 ++++++++++---- .../lsp/client/bindings/LanguageClientImpl.java | 4 +- .../TextDocumentSyncServerCapabilityHandler.java | 6 +- .../org/netbeans/modules/lsp/client/UtilsTest.java | 157 +++++++++++++++++++++ 6 files changed, 301 insertions(+), 32 deletions(-) diff --git a/ide/lsp.client/nbproject/project.xml b/ide/lsp.client/nbproject/project.xml index 00ad85f..f897849 100644 --- a/ide/lsp.client/nbproject/project.xml +++ b/ide/lsp.client/nbproject/project.xml @@ -172,6 +172,14 @@ </run-dependency> </dependency> <dependency> + <code-name-base>org.openide.loaders</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>7.72</specification-version> + </run-dependency> + </dependency> + <dependency> <code-name-base>org.openide.modules</code-name-base> <build-prerequisite/> <compile-dependency/> @@ -220,6 +228,53 @@ </run-dependency> </dependency> </module-dependencies> + <test-dependencies> + <test-type> + <name>unit</name> + <test-dependency> + <code-name-base>org.netbeans.libs.junit4</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.libs.junit5</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.editor</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.editor.lib2</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.editor.plain</code-name-base> + <recursive/> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.masterfs</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.nbjunit</code-name-base> + <recursive/> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.projectapi.nb</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.settings</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.openide.loaders</code-name-base> + <compile-dependency/> + </test-dependency> + </test-type> + </test-dependencies> <public-packages> <package>org.netbeans.modules.lsp.client.spi</package> </public-packages> diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java index a0eb7be..5a4d60a 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java @@ -38,8 +38,10 @@ import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.DocumentSymbolCapabilities; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; +import org.eclipse.lsp4j.ResourceOperationKind; import org.eclipse.lsp4j.TextDocumentClientCapabilities; import org.eclipse.lsp4j.WorkspaceClientCapabilities; +import org.eclipse.lsp4j.WorkspaceEditCapabilities; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.launch.LSPLauncher; import org.eclipse.lsp4j.services.LanguageServer; @@ -169,7 +171,11 @@ public class LSPBindings { DocumentSymbolCapabilities dsc = new DocumentSymbolCapabilities(); dsc.setHierarchicalDocumentSymbolSupport(true); tdcc.setDocumentSymbol(dsc); - initParams.setCapabilities(new ClientCapabilities(new WorkspaceClientCapabilities(), tdcc, null)); + WorkspaceClientCapabilities wcc = new WorkspaceClientCapabilities(); + wcc.setWorkspaceEdit(new WorkspaceEditCapabilities()); + wcc.getWorkspaceEdit().setDocumentChanges(true); + wcc.getWorkspaceEdit().setResourceOperations(Arrays.asList(ResourceOperationKind.Create, ResourceOperationKind.Delete, ResourceOperationKind.Rename)); + initParams.setCapabilities(new ClientCapabilities(wcc, tdcc, null)); return server.initialize(initParams).get(); } diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java index b924944..206e97e 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java @@ -18,6 +18,7 @@ */ package org.netbeans.modules.lsp.client; +import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -29,8 +30,14 @@ import javax.swing.text.Document; import javax.swing.text.StyledDocument; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.CreateFile; +import org.eclipse.lsp4j.DeleteFile; import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.RenameFile; +import org.eclipse.lsp4j.ResourceOperation; +import org.eclipse.lsp4j.ResourceOperationKind; +import org.eclipse.lsp4j.TextDocumentEdit; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -38,7 +45,10 @@ import org.netbeans.api.editor.document.LineDocument; import org.netbeans.api.editor.document.LineDocumentUtils; import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; import org.openide.filesystems.URLMapper; +import org.openide.loaders.DataFolder; +import org.openide.loaders.DataObject; import org.openide.text.NbDocument; import org.openide.util.Exceptions; @@ -61,36 +71,77 @@ public class Utils { return LineDocumentUtils.getLineStartFromIndex((LineDocument) doc, pos.getLine()) + pos.getCharacter(); } - public static void applyWorkspaceEditor(WorkspaceEdit edit) { - for (Map.Entry<String, List<TextEdit>> e : edit.getChanges().entrySet()) { - try { - FileObject file = URLMapper.findFileObject(new URI(e.getKey()).toURL()); - EditorCookie ec = file.getLookup().lookup(EditorCookie.class); - Document doc = ec != null ? ec.openDocument() : null; - if (doc == null) { - continue; + public static void applyWorkspaceEdit(WorkspaceEdit edit) { + if (edit.getDocumentChanges() != null) { + for (Either<TextDocumentEdit, ResourceOperation> change : edit.getDocumentChanges()) { + if (change.isLeft()) { + applyEdits(change.getLeft().getTextDocument().getUri(), change.getLeft().getEdits()); + } else { + switch (change.getRight().getKind()) { + case ResourceOperationKind.Create: + try { + FileUtil.createData(new File(new URI(((CreateFile) change.getRight()).getUri()))); + } catch (IOException | URISyntaxException ex) { + Exceptions.printStackTrace(ex); + } + break; + case ResourceOperationKind.Delete: + try { + URLMapper.findFileObject(new URI(((DeleteFile) change.getRight()).getUri()).toURL()).delete(); + } catch (IOException | URISyntaxException ex) { + Exceptions.printStackTrace(ex); + } + break; + case ResourceOperationKind.Rename: + try { + File target = new File(new URI(((RenameFile) change.getRight()).getNewUri())); + FileObject targetFolder = FileUtil.createFolder(target.getParentFile()); + FileObject source = URLMapper.findFileObject(new URI(((RenameFile) change.getRight()).getOldUri()).toURL()); + DataObject od = DataObject.find(source); + //XXX: should move and rename in one go! + od.move(DataFolder.findFolder(targetFolder)); + od.rename(target.getName()); + } catch (IOException | URISyntaxException ex) { + Exceptions.printStackTrace(ex); + } + break; + } } - NbDocument.runAtomic((StyledDocument) doc, () -> { - e.getValue() - .stream() - .sorted((te1, te2) -> te1.getRange().getEnd().getLine() == te2.getRange().getEnd().getLine() ? te1.getRange().getEnd().getCharacter() - te2.getRange().getEnd().getCharacter() : te1.getRange().getEnd().getLine() - te2.getRange().getEnd().getLine()) - .forEach(te -> { - try { - int start = Utils.getOffset(doc, te.getRange().getStart()); - int end = Utils.getOffset(doc, te.getRange().getEnd()); - doc.remove(start, end - start); - doc.insertString(start, te.getNewText(), null); - } catch (BadLocationException ex) { - Exceptions.printStackTrace(ex); - } - }); - }); - } catch (URISyntaxException | IOException ex) { - Exceptions.printStackTrace(ex); + } + } else { + for (Map.Entry<String, List<TextEdit>> e : edit.getChanges().entrySet()) { + applyEdits(e.getKey(), e.getValue()); } } } + private static void applyEdits(String uri, List<TextEdit> edits) { + try { + FileObject file = URLMapper.findFileObject(new URI(uri).toURL()); + EditorCookie ec = file.getLookup().lookup(EditorCookie.class); + Document doc = ec != null ? ec.openDocument() : null; + if (doc == null) { + return ; + } + NbDocument.runAtomic((StyledDocument) doc, () -> { + edits + .stream() + .sorted((te1, te2) -> te1.getRange().getEnd().getLine() == te2.getRange().getEnd().getLine() ? te1.getRange().getEnd().getCharacter() - te2.getRange().getEnd().getCharacter() : te1.getRange().getEnd().getLine() - te2.getRange().getEnd().getLine()) + .forEach(te -> { + try { + int start = Utils.getOffset(doc, te.getRange().getStart()); + int end = Utils.getOffset(doc, te.getRange().getEnd()); + doc.remove(start, end - start); + doc.insertString(start, te.getNewText(), null); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + }); + }); + } catch (URISyntaxException | IOException ex) { + Exceptions.printStackTrace(ex); + } + } public static void applyCodeAction(LSPBindings server, Either<Command, CodeAction> cmd) { try { Command command; @@ -98,7 +149,7 @@ public class Utils { if (cmd.isLeft()) { command = cmd.getLeft(); } else { - Utils.applyWorkspaceEditor(cmd.getRight().getEdit()); + Utils.applyWorkspaceEdit(cmd.getRight().getEdit()); command = cmd.getRight().getCommand(); } if (command != null) { diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java index bb4abce..f655fa0 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java @@ -124,12 +124,12 @@ public class LanguageClientImpl implements LanguageClient { severityMap.put(DiagnosticSeverity.Error, Severity.ERROR); severityMap.put(DiagnosticSeverity.Hint, Severity.HINT); severityMap.put(DiagnosticSeverity.Information, Severity.HINT); - severityMap.put(DiagnosticSeverity.Warning, Severity.HINT); + severityMap.put(DiagnosticSeverity.Warning, Severity.WARNING); } @Override public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) { - Utils.applyWorkspaceEditor(params.getEdit()); + Utils.applyWorkspaceEdit(params.getEdit()); return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(true)); } diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java index 3c59ffa..5057e37 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java @@ -68,9 +68,9 @@ public class TextDocumentSyncServerCapabilityHandler { newOpened.removeAll(lastOpened); Set<JTextComponent> newClosed = Collections.newSetFromMap(new IdentityHashMap<>()); newClosed.addAll(lastOpened); - newClosed.removeAll(newOpened); - lastOpened.removeAll(newClosed); - lastOpened.addAll(newOpened); + newClosed.removeAll(currentOpened); + lastOpened.clear(); + lastOpened.addAll(currentOpened); for (JTextComponent opened : newOpened) { FileObject file = NbEditorUtilities.getFileObject(opened.getDocument()); diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/UtilsTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/UtilsTest.java new file mode 100644 index 0000000..bfc833e --- /dev/null +++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/UtilsTest.java @@ -0,0 +1,157 @@ +/* + * 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.lsp.client; + +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.swing.text.StyledDocument; +import org.eclipse.lsp4j.CreateFile; +import org.eclipse.lsp4j.DeleteFile; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.RenameFile; +import org.eclipse.lsp4j.TextDocumentEdit; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.editor.plain.PlainKit; +import org.netbeans.spi.editor.mimelookup.MimeDataProvider; +import org.openide.LifecycleManager; +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; +import org.openide.util.lookup.ServiceProvider; + +public class UtilsTest extends NbTestCase { + + public UtilsTest(String name) { + super(name); + } + + public void testApplyTextEdit() throws Exception { + clearWorkDir(); + FileObject wd = FileUtil.toFileObject(getWorkDir()); + FileObject sourceFile1 = wd.createData("Test1.txt"); + try (OutputStream out = sourceFile1.getOutputStream()) { + out.write(("0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n").getBytes("UTF-8")); + } + FileObject sourceFile2 = wd.createData("Test2.txt"); + try (OutputStream out = sourceFile2.getOutputStream()) { + out.write(("0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n").getBytes("UTF-8")); + } + Map<String, List<TextEdit>> changes = new HashMap<>(); + changes.put(Utils.toURI(sourceFile1), Arrays.asList(new TextEdit(new Range(new Position(2, 3), new Position(2, 6)), "a"), + new TextEdit(new Range(new Position(1, 2), new Position(1, 6)), "b"), + new TextEdit(new Range(new Position(3, 1), new Position(4, 4)), "c"))); + changes.put(Utils.toURI(sourceFile2), Arrays.asList(new TextEdit(new Range(new Position(2, 3), new Position(2, 6)), "a"), + new TextEdit(new Range(new Position(1, 2), new Position(1, 6)), "b"), + new TextEdit(new Range(new Position(3, 1), new Position(4, 4)), "c"))); + WorkspaceEdit edit = new WorkspaceEdit(changes); + Utils.applyWorkspaceEdit(edit); + assertContent("0123456789\n" + + "01b6789\n" + + "012a6789\n" + + "0c456789\n", + sourceFile1); + assertContent("0123456789\n" + + "01b6789\n" + + "012a6789\n" + + "0c456789\n", + sourceFile2); + LifecycleManager.getDefault().saveAll(); + } + + public void testApplyChanges() throws Exception { + clearWorkDir(); + FileObject wd = FileUtil.toFileObject(getWorkDir()); + FileObject sourceFile1 = wd.createData("Test1.txt"); + try (OutputStream out = sourceFile1.getOutputStream()) { + out.write(("0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n").getBytes("UTF-8")); + } + FileObject sourceFile2 = wd.createData("Test2.txt"); + try (OutputStream out = sourceFile2.getOutputStream()) { + out.write(("0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n" + + "0123456789\n").getBytes("UTF-8")); + } + FileObject sourceFile3 = wd.createData("Test3.txt"); + WorkspaceEdit edit = new WorkspaceEdit(Arrays.asList(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(Utils.toURI(sourceFile1), -1), Arrays.asList(new TextEdit(new Range(new Position(2, 3), new Position(2, 6)), "a"), + new TextEdit(new Range(new Position(1, 2), new Position(1, 6)), "b"), + new TextEdit(new Range(new Position(3, 1), new Position(4, 4)), "c")))), + Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(Utils.toURI(sourceFile2), -1), Arrays.asList(new TextEdit(new Range(new Position(2, 3), new Position(2, 6)), "a"), + new TextEdit(new Range(new Position(1, 2), new Position(1, 6)), "b"), + new TextEdit(new Range(new Position(3, 1), new Position(4, 4)), "c")))), + Either.forRight(new CreateFile(Utils.toURI(sourceFile2).replace("Test2", "Test4"))), + Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(Utils.toURI(sourceFile2).replace("Test2", "Test4"), -1), Arrays.asList(new TextEdit(new Range(new Position(1, 1), new Position(1, 1)), "new content")))), + Either.forRight(new DeleteFile(Utils.toURI(sourceFile3))), + Either.forRight(new RenameFile(Utils.toURI(sourceFile1), Utils.toURI(sourceFile1).replace("Test1", "Test1a"))))); + Utils.applyWorkspaceEdit(edit); + assertContent("0123456789\n" + + "01b6789\n" + + "012a6789\n" + + "0c456789\n", + wd.getFileObject("Test1a.txt")); + assertContent("0123456789\n" + + "01b6789\n" + + "012a6789\n" + + "0c456789\n", + wd.getFileObject("Test2.txt")); + assertContent("new content", wd.getFileObject("Test4.txt")); + assertNull(wd.getFileObject("Test3.txt")); + LifecycleManager.getDefault().saveAll(); + } + + private void assertContent(String expectedContent, FileObject sourceFile) throws Exception { + EditorCookie ec = sourceFile.getLookup().lookup(EditorCookie.class); + StyledDocument doc = ec.openDocument(); + assertEquals(expectedContent, + doc.getText(0, doc.getLength())); + } + @ServiceProvider(service=MimeDataProvider.class) + public static final class MimeDataProviderImpl implements MimeDataProvider { + public Lookup getLookup(MimePath mimePath) { + if (mimePath.getPath().equals("text/plain")) { + return Lookups.singleton(new PlainKit()); + } + return Lookup.EMPTY; + } + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists