From 00c3213adc67c82a227f7b4bb1998f0a0be57acc Mon Sep 17 00:00:00 2001 From: nsingh Date: Wed, 20 Sep 2017 16:08:05 -0700 Subject: [PATCH] Add support to include imports through additional edits --- .../boot/java/snippets/JavaSnippet.java | 25 ++++--- .../java/snippets/JavaSnippetBuilder.java | 34 ++++++++++ .../java/snippets/JavaSnippetCompletion.java | 68 +++++++++++++++++++ .../java/snippets/JavaSnippetManager.java | 7 +- .../completion/ICompletionProposal.java | 3 + .../VscodeCompletionEngineAdapter.java | 53 ++++++++++----- .../languageserver/util/PrefixFinder.java | 7 +- 7 files changed, 168 insertions(+), 29 deletions(-) create mode 100644 headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java create mode 100644 headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java index 0ca9fa08e..e5da5175c 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java @@ -15,11 +15,9 @@ import java.util.Optional; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.lsp4j.CompletionItemKind; -import org.springframework.ide.vscode.boot.java.handlers.SimpleCompletionFactory; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; -import org.springframework.ide.vscode.commons.util.Renderables; -import org.springframework.ide.vscode.commons.util.text.IDocument; import com.google.common.base.Supplier; @@ -46,15 +44,14 @@ public class JavaSnippet { } public Optional generateCompletion(Supplier snippetBuilderFactory, - IDocument doc, int offset, ASTNode node, String query) { + DocumentRegion query, ASTNode node) { if (context.appliesTo(node)) { return Optional.of( - SimpleCompletionFactory.simpleProposal(doc, - offset, + new JavaSnippetCompletion(snippetBuilderFactory, query, - kind, template, "Snippet", Renderables.NO_DESCRIPTION - ).setLabel(name) + this + ) ); } @@ -65,4 +62,16 @@ public class JavaSnippet { return this.name; } + public String getTemplate() { + return this.template; + } + + public List getImports() { + return this.imports; + } + + public CompletionItemKind getKind() { + return kind; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java new file mode 100644 index 000000000..9be93138a --- /dev/null +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2017 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.ide.vscode.boot.java.snippets; + +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; + +import com.google.common.base.Supplier; + +public class JavaSnippetBuilder{ + + private Supplier snippetBuilderFactory; + + public JavaSnippetBuilder(Supplier snippetBuilderFactory) { + this.snippetBuilderFactory = snippetBuilderFactory; + } + + public DocumentEdits createEdit(DocumentRegion query, String template) { + DocumentEdits edit = new DocumentEdits(query.getDocument()); + + edit.replace(query.getStart(), query.getEnd(), template); + return edit; + } + +} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java new file mode 100644 index 000000000..266105a05 --- /dev/null +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2017 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.ide.vscode.boot.java.snippets; + +import java.util.Optional; + +import org.eclipse.lsp4j.CompletionItemKind; +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; + +import com.google.common.base.Supplier; + +public class JavaSnippetCompletion implements ICompletionProposal{ + + private DocumentRegion query; + private JavaSnippet javaSnippet; + private Supplier snippetBuilderFactory; + + public JavaSnippetCompletion(Supplier snippetBuilderFactory, DocumentRegion query, JavaSnippet javaSnippet) { + this.snippetBuilderFactory = snippetBuilderFactory; + this.query = query; + this.javaSnippet = javaSnippet; + } + + @Override + public String getLabel() { + return javaSnippet.getName(); + } + + @Override + public CompletionItemKind getKind() { + return javaSnippet.getKind(); + } + + @Override + public DocumentEdits getTextEdit() { + return new JavaSnippetBuilder(snippetBuilderFactory).createEdit(query, javaSnippet.getTemplate()); + } + + @Override + public String getDetail() { + return "Snippet"; + } + + @Override + public Renderable getDocumentation() { + return Renderables.NO_DESCRIPTION; + } + + @Override + public Optional getAdditionalEdit() { + DocumentEdits edit = new DocumentEdits(query.getDocument()); + edit.insert(0, "import my.stuff;\n"); + return Optional.of(edit ); + } +} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java index ce5fef47e..24d16bea2 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java @@ -16,6 +16,7 @@ import java.util.List; import org.eclipse.jdt.core.dom.ASTNode; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder; import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; import org.springframework.ide.vscode.commons.util.FuzzyMatcher; @@ -48,11 +49,11 @@ public class JavaSnippetManager { public Collection getCompletions(IDocument doc, int offset, ASTNode node) { Collection completions = new ArrayList<>(); - String query = PREFIX_FINDER.getPrefix(doc, offset); + DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset); for (JavaSnippet javaSnippet : snippets) { - if (FuzzyMatcher.matchScore(query, javaSnippet.getName()) != 0) { - javaSnippet.generateCompletion(snippetBuilderFactory, doc, offset, node, query) + if (FuzzyMatcher.matchScore(query.toString(), javaSnippet.getName()) != 0) { + javaSnippet.generateCompletion(snippetBuilderFactory, query, node) .ifPresent((completion) -> completions.add(completion)); } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java index a48bc91e2..defec6285 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java @@ -11,6 +11,8 @@ package org.springframework.ide.vscode.commons.languageserver.completion; +import java.util.Optional; + import org.eclipse.lsp4j.CompletionItemKind; import org.springframework.ide.vscode.commons.util.Renderable; @@ -23,6 +25,7 @@ public interface ICompletionProposal { String getLabel(); CompletionItemKind getKind(); DocumentEdits getTextEdit(); + default Optional getAdditionalEdit() { return Optional.empty(); } String getDetail(); Renderable getDocumentation(); 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 d730fde76..c8706cb94 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 @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -35,6 +36,8 @@ import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; +import com.google.common.collect.ImmutableList; + import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -174,7 +177,19 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { private static void resolveItem(TextDocument doc, ICompletionProposal completion, CompletionItem item) throws Exception { item.setDocumentation(toMarkdown(completion.getDocumentation())); - adaptEdits(item, doc, completion.getTextEdit()); + Optional mainEdit = adaptEdits(doc, completion.getTextEdit()); + if (mainEdit.isPresent()) { + item.setTextEdit(mainEdit.get()); + item.setInsertTextFormat(InsertTextFormat.Snippet); + } else { + item.setInsertText(""); + } + + completion.getAdditionalEdit().ifPresent(edit -> { + adaptEdits(doc, edit).ifPresent(extraEdit -> { + item.setAdditionalTextEdits(ImmutableList.of(extraEdit)); + }); + }); } private static String toMarkdown(Renderable r) { @@ -184,24 +199,28 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { return null; } - private static void adaptEdits(CompletionItem item, TextDocument doc, DocumentEdits edits) throws Exception { - TextReplace replaceEdit = edits.asReplacement(doc); - if (replaceEdit==null) { - //The original edit does nothing. - item.setInsertText(""); - } else { - TextDocument newDoc = doc.copy(); - edits.apply(newDoc); - TextEdit vscodeEdit = new TextEdit(); - vscodeEdit.setRange(doc.toRange(replaceEdit.start, replaceEdit.end-replaceEdit.start)); - if (Boolean.getBoolean("lsp.completions.indentation.enable")) { - vscodeEdit.setNewText(replaceEdit.newText); + private static Optional adaptEdits(TextDocument doc, DocumentEdits edits) { + try { + TextReplace replaceEdit = edits.asReplacement(doc); + if (replaceEdit==null) { + //The original edit does nothing. + return Optional.empty(); } else { - vscodeEdit.setNewText(vscodeIndentFix(doc, vscodeEdit.getRange().getStart(), replaceEdit.newText)); + TextDocument newDoc = doc.copy(); + edits.apply(newDoc); + TextEdit vscodeEdit = new TextEdit(); + vscodeEdit.setRange(doc.toRange(replaceEdit.start, replaceEdit.end-replaceEdit.start)); + if (Boolean.getBoolean("lsp.completions.indentation.enable")) { + vscodeEdit.setNewText(replaceEdit.newText); + } else { + vscodeEdit.setNewText(vscodeIndentFix(doc, vscodeEdit.getRange().getStart(), replaceEdit.newText)); + } + //TODO: cursor offset within newText? for now we assume its always at the end. + return Optional.of(vscodeEdit); } - //TODO: cursor offset within newText? for now we assume its always at the end. - item.setTextEdit(vscodeEdit); - item.setInsertTextFormat(InsertTextFormat.Snippet); + } catch (Exception e) { + Log.log(e); + return Optional.empty(); } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PrefixFinder.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PrefixFinder.java index d96acc7e2..9674f0041 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PrefixFinder.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PrefixFinder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015 Pivotal, Inc. + * Copyright (c) 2015, 2017 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 @@ -32,4 +32,9 @@ public abstract class PrefixFinder { return getPrefix(doc, offset, 0); } protected abstract boolean isPrefixChar(char c); + + public DocumentRegion getPrefixRegion(IDocument doc, int offset) { + String prefix = getPrefix(doc, offset); + return prefix != null ? new DocumentRegion(doc, offset - prefix.length(), offset) : null; + } } \ No newline at end of file