diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/CursorMovement.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/CursorMovement.java new file mode 100644 index 000000000..9b80ce028 --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/CursorMovement.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2018 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.ls.eclipse.commons; + +import org.eclipse.lsp4j.Position; + +public class CursorMovement { + private String uri; + private Position position; + + public CursorMovement() { + } + + public CursorMovement(String uri, Position position) { + this.uri = uri; + this.position = position; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java index 669f13e02..f996da000 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java @@ -30,8 +30,8 @@ public interface STS4LanguageClient extends LanguageClient { @JsonNotification("sts/progress") void progress(ProgressParams progressEvent); -// TODO: @JsonRequest("sts/moveCursor") -// CompletableFuture moveCursor(CursorMovement cursorMovement); + @JsonRequest("sts/moveCursor") + CompletableFuture moveCursor(CursorMovement cursorMovement); @JsonRequest("sts/addClasspathListener") CompletableFuture addClasspathListener(ClasspathListenerParams params); diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java index 796f80056..f78f0aa2a 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.tuple.Pair; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; @@ -25,10 +26,12 @@ import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelExtension; import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.lsp4e.LSPEclipseUtils; import org.eclipse.lsp4e.LanguageClientImpl; import org.eclipse.lsp4e.LanguageServiceAccessor; @@ -224,4 +227,32 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La return CompletableFuture.completedFuture(response); } + @Override + public CompletableFuture moveCursor(CursorMovement cursorMovement) { + //WARNING: this code never ran. It is meant to be called by server 'moveCursor' command + // when appplying jumpy completions. But lsp4e doesn't execute commands from completions + // because they haven't implemented support for that part of the protocol yet. + Throwable problem = null; + for (Pair editorAndDoc : StsLspEclipseUtil.getTextEditorsForUri(cursorMovement.getUri())) { + try { + AbstractTextEditor editor = editorAndDoc.getRight(); + IDocument doc = editorAndDoc.getLeft(); + int cursor = LSPEclipseUtils.toOffset(cursorMovement.getPosition(), doc); + moveCursor(editor, cursor); + } catch (Exception e) { + if (problem==null) { + problem = e; + } + } + } + return problem == null + ? CompletableFuture.completedFuture("ok") + : Futures.fail(problem); + } + + private void moveCursor(AbstractTextEditor editor, int cursor) { + ISelectionProvider sp = editor.getSelectionProvider(); + sp.setSelection(new TextSelection(cursor, 0)); + } + } diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/StsLspEclipseUtil.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/StsLspEclipseUtil.java new file mode 100644 index 000000000..b12a7ed9c --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/StsLspEclipseUtil.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2018 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.ls.eclipse.commons; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.jface.text.IDocument; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServiceAccessor; +import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.texteditor.AbstractTextEditor; + +public class StsLspEclipseUtil { + + public static List> getTextEditorsForUri(String uri) { + List> editors = new ArrayList<>(1); //Typical expectation is just one editor. + for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) { + for (IWorkbenchPage page : window.getPages()) { + for (IEditorReference editor : page.getEditorReferences()) { + try { + IEditorInput input = editor.getEditorInput(); + if (input!=null) { + IDocument doc = LSPEclipseUtils.getDocument(input); + Collection infos = LanguageServiceAccessor.getLSPDocumentInfosFor(doc, (x) -> true); + for (LSPDocumentInfo lspDoc : infos) { + if (uri.equals(lspDoc.getFileUri().toString())) { + boolean restore = false; + IEditorPart editorPart = editor.getEditor(restore ); + if (editorPart instanceof AbstractTextEditor) { + editors.add(Pair.of(doc, (AbstractTextEditor) editorPart)); + } + } + } + } + } catch (PartInitException e) { + LanguageServerCommonsActivator.logError(e, e.getMessage()); + } + } + } + } + return editors; + } + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java index af6840707..fae4f234c 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java @@ -35,6 +35,7 @@ import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; import org.eclipse.lsp4j.services.LanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.LoggingFormat; +import org.springframework.ide.vscode.commons.languageserver.util.LspClient; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.Log; @@ -60,6 +61,7 @@ public abstract class LaunguageServerApp { private static final int SERVER_STANDALONE_PORT = 5007; public static void start(String name, Provider languageServerFactory) throws IOException, InterruptedException { + Log.info("ClientType = "+LspClient.currentClient()); System.setProperty(STS4_LANGUAGESERVER_NAME, name); //makes it easy to recognize language server processes. LaunguageServerApp app = new LaunguageServerApp() { @Override diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/LspCompletionInterpreter.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/LspCompletionInterpreter.java index aeb8520f8..503d1e463 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/LspCompletionInterpreter.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/LspCompletionInterpreter.java @@ -19,6 +19,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.Direction; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.OffsetTransformer; +import org.springframework.ide.vscode.commons.languageserver.util.LspClient; +import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.StringUtil; @@ -135,7 +137,7 @@ public class LspCompletionInterpreter implements IDocumentState { if (afterEdit!=null) additionalEdits.add(afterEdit); item.setAdditionalTextEdits(additionalEdits.build()); - if (mainEdit!=null && mainEdit.getNewText().equals("")) { + if (LspClient.currentClient()==Client.VSCODE && mainEdit!=null && mainEdit.getNewText().equals("")) { //Vscode handles this poorly and adds 'junk' instead. mainEdit.setNewText(" "); // Adding a space. It is still junk but it is at least not immediately visible. } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java index 02bf007e0..811887b2a 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java @@ -155,6 +155,16 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { } } list.setItems(items); + System.err.println(">>> completions [isInconplete = "+list.isIncomplete()+"]"); + for (CompletionItem item : items) { + System.err.println( + "lbl = '"+item.getLabel()+"' " + + "sort = '"+item.getSortText()+"' " + + "filt = '"+item.getFilterText()+"' " + + "newText = '"+item.getTextEdit().getNewText()+"'" + ); + } + System.err.println("<<< completions"); return list; }) .subscribeOn(Schedulers.elastic()); //!!! without this the mono will just be computed on the same thread that calls it. diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java index 655b406cd..4167f440c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java @@ -1450,59 +1450,64 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { } @Test public void testJumpyInsertion() throws Exception { - String[] names = {"foo", "nested", "bar"}; - int levels = 4; - generateNestedProperties(levels, names, ""); + //We care more about eclipse for this test case because it fails for vscode + // because we have to add some extra in vscode as a workaround for this bug: + // https://github.com/Microsoft/vscode-languageserver-node/issues/361 + withSystemProperty("sts.lsp.client", "eclipse", () -> { + String[] names = {"foo", "nested", "bar"}; + int levels = 4; + generateNestedProperties(levels, names, ""); - assertCompletion( - "foo:\n" + - " nested:\n" + - " bar:\n" + - " foo:\n" + - "other:\n" + - "foo.nested.bar.b<*>" - , - "foo:\n" + - " nested:\n" + - " bar:\n" + - " foo:\n" + - " bar: <*>\n" + - "other:" - ); + assertCompletion( + "foo:\n" + + " nested:\n" + + " bar:\n" + + " foo:\n" + + "other:\n" + + "foo.nested.bar.b<*>" + , + "foo:\n" + + " nested:\n" + + " bar:\n" + + " foo:\n" + + " bar: <*>\n" + + "other:" + ); - assertCompletion( - "foo:\n" + - " nested:\n" + - " bar:\n" + - " foo:\n" + - "other:\n" + - "foo.nested.nested.b<*>" - , - "foo:\n" + - " nested:\n" + - " bar:\n" + - " foo:\n" + - " nested:\n" + - " bar: <*>\n"+ - "other:" - ); + assertCompletion( + "foo:\n" + + " nested:\n" + + " bar:\n" + + " foo:\n" + + "other:\n" + + "foo.nested.nested.b<*>" + , + "foo:\n" + + " nested:\n" + + " bar:\n" + + " foo:\n" + + " nested:\n" + + " bar: <*>\n"+ + "other:" + ); - assertCompletion( - "foo.nested.nested.b<*>\n" + - "foo:\n" + - " nested:\n" + - " bar:\n" + - " foo:\n" + - "other:" - , - "foo:\n" + - " nested:\n" + - " bar:\n" + - " foo:\n" + - " nested:\n" + - " bar: <*>\n"+ - "other:" - ); + assertCompletion( + "foo.nested.nested.b<*>\n" + + "foo:\n" + + " nested:\n" + + " bar:\n" + + " foo:\n" + + "other:" + , + "foo:\n" + + " nested:\n" + + " bar:\n" + + " foo:\n" + + " nested:\n" + + " bar: <*>\n"+ + "other:" + ); + }); } @Test public void testBooleanValueCompletion() throws Exception {