Spring properties postfix computed in completion item resolve
This commit is contained in:
@@ -106,7 +106,7 @@ public class SpringProjectUtil {
|
||||
log.error("", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}).isEmpty();
|
||||
}).isPresent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.languageserver.completion;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -138,13 +139,22 @@ public class DocumentEdits implements ProposalApplier {
|
||||
private class Insertion extends Edit {
|
||||
private int offset;
|
||||
private String text;
|
||||
private Supplier<String> resolveInsert;
|
||||
private boolean resolved;
|
||||
|
||||
|
||||
public Insertion(boolean grabCursor, int offset, String insert) {
|
||||
this(grabCursor, offset, insert, null);
|
||||
}
|
||||
|
||||
public Insertion(boolean grabCursor, int offset, String insert, Supplier<String> resolveInsert) {
|
||||
super(grabCursor);
|
||||
this.offset = offset;
|
||||
this.text = insert;
|
||||
this.resolveInsert = resolveInsert;
|
||||
this.resolved = resolveInsert == null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void apply(DocumentState doc) throws BadLocationException {
|
||||
doc.insert(grabCursor, offset, text);
|
||||
@@ -164,6 +174,15 @@ public class DocumentEdits implements ProposalApplier {
|
||||
public int getEnd() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void resolve() {
|
||||
text = resolveInsert.get();
|
||||
resolved = true;
|
||||
}
|
||||
|
||||
public boolean isResolved() {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class Edit {
|
||||
@@ -176,6 +195,10 @@ public class DocumentEdits implements ProposalApplier {
|
||||
abstract void apply(DocumentState doc) throws BadLocationException;
|
||||
@Override
|
||||
public abstract String toString();
|
||||
public boolean isResolved() {
|
||||
return true;
|
||||
}
|
||||
public void resolve() {}
|
||||
}
|
||||
|
||||
private class Deletion extends Edit {
|
||||
@@ -365,7 +388,11 @@ public class DocumentEdits implements ProposalApplier {
|
||||
public void insert(int offset, String insert) {
|
||||
edits.add(new Insertion(grabCursor, offset, insert));
|
||||
}
|
||||
|
||||
|
||||
public void lazyInsert(int offset, String insert, Supplier<String> resolveText) {
|
||||
edits.add(new Insertion(grabCursor, offset, insert, resolveText));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getSelection() throws Exception {
|
||||
DocumentState selectionState = new DocumentState(null);
|
||||
@@ -487,6 +514,12 @@ public class DocumentEdits implements ProposalApplier {
|
||||
Matcher matcher = NON_WS_CHAR.matcher(insert.text);
|
||||
if (matcher.find()) {
|
||||
insert.text = transformFun.apply(matcher.start(), insert.text);
|
||||
if (!insert.isResolved()) {
|
||||
Supplier<String> originalSupl = insert.resolveInsert;
|
||||
insert.resolveInsert = () -> {
|
||||
return transformFun.apply(matcher.start(), originalSupl.get());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,10 +557,34 @@ public class DocumentEdits implements ProposalApplier {
|
||||
if (ins.offset>=del.start && ins.offset <=del.end && replacedText.startsWith(prefix)) {
|
||||
del.start+=prefix.length();
|
||||
ins.text = ins.text.substring(prefix.length());
|
||||
if (!ins.isResolved()) {
|
||||
Supplier<String> originalSupl = ins.resolveInsert;
|
||||
ins.resolveInsert = () -> {
|
||||
return originalSupl.get().substring(prefix.length());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isResolved() {
|
||||
for (Edit e : edits) {
|
||||
if (!e.isResolved()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void resolve() {
|
||||
for (Edit edit : edits) {
|
||||
if (!edit.isResolved()) {
|
||||
edit.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
package org.springframework.ide.vscode.commons.languageserver.completion;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
@@ -25,13 +26,13 @@ public interface ICompletionProposal {
|
||||
String getLabel();
|
||||
CompletionItemKind getKind();
|
||||
DocumentEdits getTextEdit();
|
||||
default Optional<DocumentEdits> getAdditionalEdit() { return Optional.empty(); }
|
||||
default Optional<Supplier<DocumentEdits>> getAdditionalEdit() { return Optional.empty(); }
|
||||
default boolean isTriggeringNextCompletionRequest() { return false; }
|
||||
|
||||
String getDetail();
|
||||
Renderable getDocumentation();
|
||||
default String getFilterText() { return getLabel(); }
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a proposal to make it standout less somehow.
|
||||
* @param howmuch A 'weight' for the deemphasis. Allowing to deempasize some proposals more than others.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2023 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
|
||||
@@ -10,6 +10,9 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.languageserver.completion;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
|
||||
@@ -78,4 +81,10 @@ public abstract class TransformedCompletion extends ScoreableProposal {
|
||||
public String getFilterText() {
|
||||
return original.getFilterText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Supplier<DocumentEdits>> getAdditionalEdit() {
|
||||
return original.getAdditionalEdit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,12 +15,16 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
@@ -28,16 +32,21 @@ import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.MarkupContent;
|
||||
import org.eclipse.lsp4j.MarkupKind;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.Lsp4jUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SortKeys;
|
||||
import org.springframework.ide.vscode.commons.protocol.CursorMovement;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
@@ -45,6 +54,8 @@ import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
@@ -53,6 +64,9 @@ import com.google.gson.JsonPrimitive;
|
||||
public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(VscodeCompletionEngineAdapter.class);
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
|
||||
public static class LazyCompletionResolver {
|
||||
private int nextId = 0; //Used to assign unique id to completion items.
|
||||
@@ -78,6 +92,12 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized String resolveLater(Consumer<CompletionItem> resolver) {
|
||||
String id = nextId();
|
||||
resolvers.put(id, resolver);
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized void resolveNow(CancelChecker cancelToken, CompletionItem unresolved) {
|
||||
Object id = unresolved.getData();
|
||||
if (id!=null) {
|
||||
@@ -98,6 +118,8 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
|
||||
private final static int DEFAULT_MAX_COMPLETIONS = 50;
|
||||
private int maxCompletions = DEFAULT_MAX_COMPLETIONS; //TODO: move this to CompletionEngineOptions.
|
||||
|
||||
private final String RESOLVE_EDIT_COMMAND;
|
||||
|
||||
private SimpleLanguageServer server;
|
||||
private ICompletionEngine engine;
|
||||
@@ -116,6 +138,44 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
this.engine = engine;
|
||||
this.resolver = resolver;
|
||||
this.filter = filter;
|
||||
|
||||
// Command must be unique for each instance due to completion engine since it can be for different schemas under the same LS umbrella
|
||||
this.RESOLVE_EDIT_COMMAND = "sts." + server.EXTENSION_ID + ".resolve.completion.edit." + UUID.randomUUID();
|
||||
|
||||
server.onCommand(RESOLVE_EDIT_COMMAND, params -> {
|
||||
String uri = params.getArguments().get(0) instanceof String ? (String) params.getArguments().get(0) : ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
String resolveId = params.getArguments().get(1) instanceof String ? (String) params.getArguments().get(1) : ((JsonElement) params.getArguments().get(1)).getAsString();
|
||||
JsonElement editJson = params.getArguments().get(2) instanceof JsonElement ? (JsonElement) params.getArguments().get(2) : GSON.toJsonTree(params.getArguments().get(2));
|
||||
TextEdit mainEdit = GSON.fromJson(editJson, TextEdit.class);
|
||||
if (isMagicIndentingClient()) {
|
||||
// Reverse sync edit magic client indentation. This indentation only works during completion application not command execution
|
||||
// The reversed edit text is needed to properly determine text to replace
|
||||
mainEdit.setNewText(revertVscodeIndentFix(server.getTextDocumentService().getLatestSnapshot(uri), mainEdit.getRange().getStart(), mainEdit.getNewText()));
|
||||
}
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
CompletionItem unresolved = new CompletionItem(RESOLVE_EDIT_COMMAND);
|
||||
unresolved.setTextEdit(Either.forLeft(mainEdit));
|
||||
unresolved.setData(resolveId);
|
||||
resolver.resolveNow(null, unresolved);
|
||||
return unresolved.getTextEdit().getLeft();
|
||||
}).thenCompose(newEdit -> {
|
||||
Position pos = Lsp4jUtils.getPositionAtEndOfEdit(mainEdit);
|
||||
Position cursorPos = Lsp4jUtils.getPositionAtEndOfEdit(newEdit);
|
||||
String newText = newEdit.getNewText();
|
||||
return server.getClient().applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Map.of(
|
||||
uri, List.of(
|
||||
new TextEdit(new Range(mainEdit.getRange().getStart(), pos), newText)
|
||||
)
|
||||
)))).thenCompose(res -> {
|
||||
if (res.isApplied()) {
|
||||
return server.getClient().moveCursor(new CursorMovement(uri, cursorPos));
|
||||
} else {
|
||||
return CompletableFuture.failedStage(new IllegalStateException("Failed to apply edit previously, aborting moving the cursor"));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void setMaxCompletions(int maxCompletions) {
|
||||
@@ -216,25 +276,101 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
item.setKind(completion.getKind());
|
||||
item.setSortText(sortkeys.next());
|
||||
item.setFilterText(completion.getFilterText());
|
||||
item.setDetail(completion.getDetail());
|
||||
if (completion.isDeprecated()) {
|
||||
item.setDeprecated(completion.isDeprecated());
|
||||
}
|
||||
resolveEdits(doc, completion, item); //Warning. Its not allowed by LSP spec to resolveEdits
|
||||
//lazy as we used to do in the past.
|
||||
if (completion.getDocumentation() != null) {
|
||||
if (resolver!=null) {
|
||||
item.setData(resolver.resolveLater(completion, doc));
|
||||
} else {
|
||||
resolveItem(doc, completion, item);
|
||||
}
|
||||
|
||||
resolveMainEdit(doc, completion, item);
|
||||
|
||||
if (resolver != null) {
|
||||
item.setData(resolver.resolveLater(completionItem -> {
|
||||
try {
|
||||
resolveCompletionItem(completionItem, completion, doc);
|
||||
} catch (Exception e) {
|
||||
log.error("Error resolving completion", e);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
resolveCompletionItem(item, completion, doc);
|
||||
}
|
||||
|
||||
List<Object> commands = new ArrayList<>(2);
|
||||
if (LspClient.currentClient() != LspClient.Client.ECLIPSE) {
|
||||
/*
|
||||
* Eclipse client always send completionItem resolve request before applying completion.
|
||||
* However, LSP doesn't guarantee this in general and addtionalEdits must be on lines different from the main edit.
|
||||
* Due to LSP limitation it is best to execute extra edits modifying main edit via the command
|
||||
*/
|
||||
if (!completion.getTextEdit().isResolved() && item.getTextEdit().isLeft()) {
|
||||
commands.add(new Command("Resolve edit", RESOLVE_EDIT_COMMAND, List.of(doc.getUri(), item.getData(), item.getTextEdit().getLeft())));
|
||||
}
|
||||
}
|
||||
if (completion.isTriggeringNextCompletionRequest()) {
|
||||
item.setCommand(new Command("Completion Proposal Request", "editor.action.triggerSuggest"));
|
||||
commands.add(new Command("Completion Proposal Request", "editor.action.triggerSuggest"));
|
||||
}
|
||||
if (!commands.isEmpty()) {
|
||||
Command command = (Command)commands.get(0);
|
||||
if (commands.size() == 1 && command.getCommand().equals("editor.action.triggerSuggest")) {
|
||||
item.setCommand(command);
|
||||
} else {
|
||||
item.setCommand(new Command("Commands", server.COMMAND_LIST_COMMAND_ID, commands));
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private void resolveCompletionItem(CompletionItem item, ICompletionProposal completion, TextDocument doc) throws Exception {
|
||||
item.setDetail(completion.getDetail());
|
||||
if (completion.getDocumentation() != null) {
|
||||
resolveItem(doc, completion, item);
|
||||
}
|
||||
|
||||
if (!completion.getTextEdit().isResolved()) {
|
||||
completion.getTextEdit().resolve();
|
||||
}
|
||||
// Keep main edit resolution outside of the if block above. If resolve completion item and command are executed in parallel command would need to generate the new edit
|
||||
resolveMainEdit(doc, completion, item);
|
||||
|
||||
resolveAdditionalEdits(doc, completion, item);
|
||||
|
||||
// Remove the Resolve Edit Command if present since everything is resolved already (Not expected to be around for Eclipse client)
|
||||
if (item.getCommand() != null) {
|
||||
if (server.COMMAND_LIST_COMMAND_ID.equals(item.getCommand().getCommand())) {
|
||||
List<Object> subCommands = item.getCommand().getArguments();
|
||||
for (ListIterator<Object> itr = subCommands.listIterator(); itr.hasNext();) {
|
||||
Object o = itr.next();
|
||||
Command subCommand = o instanceof Command ? (Command) o : GSON.fromJson(o instanceof JsonElement ? (JsonElement) o : GSON.toJsonTree(o), Command.class);
|
||||
if (RESOLVE_EDIT_COMMAND.equals(subCommand.getCommand())) {
|
||||
itr.remove();
|
||||
// Only one such command expected
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (subCommands.size() == 1) {
|
||||
item.setCommand((Command)subCommands.get(0));
|
||||
} else if (subCommands.isEmpty()) {
|
||||
item.setCommand(null);
|
||||
}
|
||||
} else if (RESOLVE_EDIT_COMMAND.equals(item.getCommand().getCommand())) {
|
||||
item.setCommand(null);
|
||||
}
|
||||
}
|
||||
if (item.getCommand() != null && server.COMMAND_LIST_COMMAND_ID.equals(item.getCommand().getCommand())) {
|
||||
List<Object> subCommands = item.getCommand().getArguments();
|
||||
for (ListIterator<Object> itr = subCommands.listIterator(); itr.hasNext();) {
|
||||
Object o = itr.next();
|
||||
Command subCommand = o instanceof Command ? (Command) o : GSON.fromJson(o instanceof JsonElement ? (JsonElement) o : GSON.toJsonTree(o), Command.class);
|
||||
if (RESOLVE_EDIT_COMMAND.equals(subCommand.getCommand())) {
|
||||
itr.remove();
|
||||
// Only one such command expected
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (subCommands.isEmpty()) {
|
||||
item.setCommand(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ICompletionProposal> filter(Collection<ICompletionProposal> completions) {
|
||||
if (filter.isPresent()) {
|
||||
@@ -259,9 +395,9 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
item.setDocumentation(content);
|
||||
}
|
||||
|
||||
private void resolveEdits(TextDocument doc, ICompletionProposal completion, CompletionItem item) {
|
||||
private void resolveMainEdit(TextDocument doc, ICompletionProposal completion, CompletionItem item) {
|
||||
AtomicBoolean usedSnippets = new AtomicBoolean();
|
||||
Optional<TextEdit> mainEdit = adaptEdits(doc, completion.getTextEdit(), usedSnippets);
|
||||
Optional<TextEdit> mainEdit = adaptEdits(doc, completion.getTextEdit(), usedSnippets, isCommandExecution(item));
|
||||
if (mainEdit.isPresent()) {
|
||||
item.setTextEdit(Either.forLeft(mainEdit.get()));
|
||||
if (server.hasCompletionSnippetSupport()) {
|
||||
@@ -272,11 +408,21 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
} else {
|
||||
item.setInsertText("");
|
||||
}
|
||||
|
||||
completion.getAdditionalEdit().ifPresent(edit -> {
|
||||
adaptEdits(doc, edit, null).ifPresent(extraEdit -> {
|
||||
item.setAdditionalTextEdits(ImmutableList.of(extraEdit));
|
||||
});
|
||||
}
|
||||
|
||||
private void resolveAdditionalEdits(TextDocument doc, ICompletionProposal completion, CompletionItem item) {
|
||||
completion.getAdditionalEdit().ifPresent(editSupplier -> {
|
||||
DocumentEdits edit = editSupplier.get();
|
||||
if (edit != null) {
|
||||
if (!edit.isResolved()) {
|
||||
edit.resolve();
|
||||
}
|
||||
adaptEdits(doc, edit, null, isCommandExecution(item)).ifPresent(extraEdit -> {
|
||||
item.setAdditionalTextEdits(ImmutableList.of(extraEdit));
|
||||
});
|
||||
} else {
|
||||
item.setAdditionalTextEdits(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -287,10 +433,10 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Optional<TextEdit> adaptEdits(TextDocument doc, DocumentEdits edits, AtomicBoolean usedSnippets) {
|
||||
private Optional<TextEdit> adaptEdits(TextDocument doc, DocumentEdits edits, AtomicBoolean usedSnippets, boolean ignoreClientIndent) {
|
||||
try {
|
||||
TextReplace replaceEdit = edits.asReplacement(doc);
|
||||
if (usedSnippets != null) {
|
||||
TextReplace replaceEdit = edits == null ? null : edits.asReplacement(doc);
|
||||
if (usedSnippets != null && edits != null) {
|
||||
usedSnippets.set(edits.hasSnippets());
|
||||
}
|
||||
if (replaceEdit==null) {
|
||||
@@ -317,7 +463,7 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
usedSnippets.set(true);
|
||||
}
|
||||
}
|
||||
if (isMagicIndentingClient()) {
|
||||
if (isMagicIndentingClient() && !ignoreClientIndent) {
|
||||
newText = vscodeIndentFix(doc, vscodeEdit.getRange().getStart(), replaceEdit.newText);
|
||||
}
|
||||
vscodeEdit.setNewText(newText);
|
||||
@@ -351,6 +497,19 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
}
|
||||
return newText;
|
||||
}
|
||||
|
||||
private static String revertVscodeIndentFix(TextDocument doc, Position start, String newText) {
|
||||
IndentUtil indenter = new IndentUtil(doc);
|
||||
try {
|
||||
String refIndent = indenter.getReferenceIndent(doc.toOffset(start), doc);
|
||||
if (!refIndent.isEmpty()) {
|
||||
return StringUtil.reverseStripIndentation(refIndent, newText);
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
return newText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItem resolveCompletion(CancelChecker cancelToken, CompletionItem unresolved) {
|
||||
@@ -370,4 +529,8 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
boolean include(ICompletionProposal proposal);
|
||||
|
||||
}
|
||||
|
||||
private boolean isCommandExecution(CompletionItem item) {
|
||||
return RESOLVE_EDIT_COMMAND == item.getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 VMware, 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.languageserver.util;
|
||||
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
|
||||
public class Lsp4jUtils {
|
||||
|
||||
public static Position getPositionAtEndOfEdit(TextEdit edit) {
|
||||
int numberOfLinebreaks = numberOfLineBreaks(edit.getNewText());
|
||||
return new Position(
|
||||
edit.getRange().getStart().getLine() + numberOfLinebreaks,
|
||||
numberOfLinebreaks == 0 ? edit.getRange().getStart().getCharacter() + edit.getNewText().length() :
|
||||
lengthOfLastLine(edit.getNewText())
|
||||
);
|
||||
}
|
||||
|
||||
private static int numberOfLineBreaks(String s) {
|
||||
int numOfLinebreaks = 0;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
if (s.charAt(i) == '\n') {
|
||||
numOfLinebreaks++;
|
||||
}
|
||||
}
|
||||
return numOfLinebreaks;
|
||||
}
|
||||
|
||||
private static int lengthOfLastLine(String s) {
|
||||
int idx = s.lastIndexOf('\n');
|
||||
if (idx >= 0) {
|
||||
return s.length() - idx - 1;
|
||||
} else {
|
||||
return s.length();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -111,6 +111,7 @@ import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -140,6 +141,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd
|
||||
|
||||
public final String EXTENSION_ID;
|
||||
public final String CODE_ACTION_COMMAND_ID;
|
||||
public final String COMMAND_LIST_COMMAND_ID;
|
||||
|
||||
public final LazyCompletionResolver completionResolver = createCompletionResolver();
|
||||
|
||||
@@ -271,6 +273,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd
|
||||
Assert.isNotNull(extensionId);
|
||||
this.EXTENSION_ID = extensionId;
|
||||
this.CODE_ACTION_COMMAND_ID = "sts."+EXTENSION_ID+".codeAction";
|
||||
this.COMMAND_LIST_COMMAND_ID = "sts." + EXTENSION_ID + ".commandList";
|
||||
}
|
||||
|
||||
protected CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
|
||||
@@ -296,6 +299,14 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd
|
||||
});
|
||||
})
|
||||
.toFuture();
|
||||
} else if (COMMAND_LIST_COMMAND_ID.equals(params.getCommand())) {
|
||||
Gson gson = new Gson();
|
||||
CompletableFuture<Object> execution = CompletableFuture.completedFuture(null);
|
||||
for (Object json : params.getArguments()) {
|
||||
Command cmd = json instanceof Command ? (Command) json : gson.fromJson(json instanceof JsonElement ? (JsonElement) json : gson.toJsonTree(json), Command.class);
|
||||
execution = execution.thenCompose(r -> getWorkspaceService().executeCommand(new ExecuteCommandParams(cmd.getCommand(), cmd.getArguments())));
|
||||
}
|
||||
return execution;
|
||||
}
|
||||
log.warn("Unknown command ignored: "+params.getCommand());
|
||||
return CompletableFuture.completedFuture(false);
|
||||
@@ -426,7 +437,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd
|
||||
|
||||
public Disposable onCommand(String id, ExecuteCommandHandler commandHandler) {
|
||||
synchronized (commands) {
|
||||
Assert.isLegal(!commands.containsKey(id));
|
||||
Assert.isLegal(!commands.containsKey(id), "Command '" + id + "' is already registered");
|
||||
commands.put(id, commandHandler);
|
||||
}
|
||||
return () -> {
|
||||
@@ -489,6 +500,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd
|
||||
List<String> supportedCommands = new ArrayList<>();
|
||||
if (hasQuickFixes()) {
|
||||
supportedCommands.add(CODE_ACTION_COMMAND_ID);
|
||||
supportedCommands.add(COMMAND_LIST_COMMAND_ID);
|
||||
}
|
||||
supportedCommands.addAll(commands.keySet());
|
||||
ExecuteCommandOptions executeCommandOptions = new ExecuteCommandOptions(supportedCommands);
|
||||
|
||||
@@ -314,6 +314,7 @@ public class ORAstUtils {
|
||||
public static List<CompilationUnit> parseInputs(JavaParser parser, Iterable<Parser.Input> inputs, Consumer<SourceFile> parseCallback) {
|
||||
ExecutionContext ctx = new InMemoryExecutionContext(ORAstUtils::logExceptionWhileParsing);
|
||||
ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true);
|
||||
ctx.putMessage(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, false);
|
||||
if (parseCallback != null) {
|
||||
ParsingExecutionContextView parseContext = ParsingExecutionContextView.view(ctx);
|
||||
parseContext.setParsingListener(new ParsingEventListener() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2023 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
|
||||
@@ -210,6 +210,32 @@ public class StringUtil {
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public static String reverseStripIndentation(String indent, String indentedText) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
boolean first = true;
|
||||
Matcher matcher = NEWLINE.matcher(indentedText);
|
||||
int pos = 0;
|
||||
while (matcher.find()) {
|
||||
int newline = matcher.start();
|
||||
int newline_end = matcher.end();
|
||||
String line = indentedText.substring(pos, newline);
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
line = reverseStripIndentationFromLine(indent, line);
|
||||
}
|
||||
out.append(line);
|
||||
out.append(indentedText.substring(newline, newline_end));
|
||||
pos = newline_end;
|
||||
}
|
||||
String line = indentedText.substring(pos);
|
||||
if (!first) {
|
||||
line = reverseStripIndentationFromLine(indent, line);
|
||||
}
|
||||
out.append(line);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public static String stripIndentation(int indent, String indentedText) {
|
||||
return stripIndentation(Strings.repeat(" ", indent), indentedText);
|
||||
}
|
||||
@@ -222,6 +248,10 @@ public class StringUtil {
|
||||
return line.substring(start);
|
||||
}
|
||||
|
||||
public static String reverseStripIndentationFromLine(String indent, String line) {
|
||||
return indent + line;
|
||||
}
|
||||
|
||||
public static String[] split(String string, char c) {
|
||||
//Why not use String.split? Because when the string being split ends with separator, it drops the final
|
||||
// empty string. But... we need that empty string! I.e. we want the number of pieces to allways be equal
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.completion;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
@@ -57,6 +59,41 @@ public class YamlPathEdits extends DocumentEdits {
|
||||
* the 'missing' portion of the path is found and the edits
|
||||
* are created there.
|
||||
*/
|
||||
public void createPath(SChildBearingNode node, YamlPath path, Supplier<String> appendText) throws Exception {
|
||||
//This code doesn't handle selection of subddocuments
|
||||
// or creation of new subdocuments so must not call it on
|
||||
//ROOT node but start at an appropriate SDocNode (or below)
|
||||
Assert.isLegal(node.getNodeType()!=SNodeType.ROOT);
|
||||
if (!path.isEmpty()) {
|
||||
YamlPathSegment s = path.getSegment(0);
|
||||
if (s.getType()==YamlPathSegmentType.VAL_AT_KEY) {
|
||||
String key = s.toPropString();
|
||||
SKeyNode existing = node.getChildWithKey(key);
|
||||
if (existing==null) {
|
||||
createNewPath(node, path, appendText);
|
||||
} else {
|
||||
createPath(existing, path.tail(), appendText);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//whole path already exists. Just try to move cursor somewhere
|
||||
// sensible in the existing tail-end-node of the path.
|
||||
SNode child = node.getFirstRealChild();
|
||||
if (child!=null) {
|
||||
moveCursorTo(child.getStart());
|
||||
} else if (node.getNodeType()==SNodeType.KEY) {
|
||||
SKeyNode keyNode = (SKeyNode) node;
|
||||
int colonOffset = keyNode.getColonOffset();
|
||||
char c = doc.getChar(colonOffset+1);
|
||||
if (c==' ') {
|
||||
moveCursorTo(colonOffset+2); //cursor after the ": "
|
||||
} else {
|
||||
moveCursorTo(colonOffset+1); //cursor after the ":"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createPath(SChildBearingNode node, YamlPath path, String appendText) throws Exception {
|
||||
//This code doesn't handle selection of subddocuments
|
||||
// or creation of new subdocuments so must not call it on
|
||||
@@ -92,13 +129,25 @@ public class YamlPathEdits extends DocumentEdits {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void createNewPath(SChildBearingNode parent, YamlPath path, Supplier<String> appendText) throws Exception {
|
||||
int indent = YamlIndentUtil.getNewChildKeyIndent(parent);
|
||||
int insertionPoint = getNewPathInsertionOffset(parent);
|
||||
boolean startOnNewLine = true;
|
||||
if (appendText == null) {
|
||||
insert(insertionPoint, createPathInsertionText(path, indent, startOnNewLine, ""));
|
||||
} else {
|
||||
lazyInsert(insertionPoint, createPathInsertionText(path, indent, startOnNewLine, ""), () -> createPathInsertionText(path, indent, startOnNewLine, appendText.get()));
|
||||
}
|
||||
}
|
||||
|
||||
private void createNewPath(SChildBearingNode parent, YamlPath path, String appendText) throws Exception {
|
||||
int indent = YamlIndentUtil.getNewChildKeyIndent(parent);
|
||||
int insertionPoint = getNewPathInsertionOffset(parent);
|
||||
boolean startOnNewLine = true;
|
||||
insert(insertionPoint, createPathInsertionText(path, indent, startOnNewLine, appendText));
|
||||
}
|
||||
|
||||
|
||||
protected String createPathInsertionText(YamlPath path, int indent, boolean startOnNewLine, String appendText) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
@@ -138,9 +187,10 @@ public class YamlPathEdits extends DocumentEdits {
|
||||
}
|
||||
}
|
||||
|
||||
public void createPathInPlace(SNode contextNode, YamlPath relativePath, int insertionPoint, String appendText) throws Exception {
|
||||
public void createPathInPlace(SNode contextNode, YamlPath relativePath, int insertionPoint, Supplier<String> appendText) throws Exception {
|
||||
int indent = YamlIndentUtil.getNewChildKeyIndent(contextNode);
|
||||
insert(insertionPoint, createPathInsertionText(relativePath, indent, needNewline(contextNode, insertionPoint), appendText));
|
||||
boolean needNewline = needNewline(contextNode, insertionPoint);
|
||||
lazyInsert(insertionPoint, createPathInsertionText(relativePath, indent, needNewline, ""), () -> createPathInsertionText(relativePath, indent, needNewline, appendText.get()));
|
||||
}
|
||||
|
||||
private boolean needNewline(SNode contextNode, int insertionPoint) throws Exception {
|
||||
|
||||
@@ -507,6 +507,11 @@ public class Editor {
|
||||
selectionStart = selectionEnd = selectionStart - (end - start) + replaceWith.length();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply command
|
||||
if (completion.getCommand() != null) {
|
||||
harness.executeCommand(completion.getCommand());
|
||||
}
|
||||
}
|
||||
|
||||
private String getInsertText(CompletionItem completion) {
|
||||
|
||||
@@ -719,7 +719,7 @@ public class LanguageServerHarness {
|
||||
assertNotNull(completions);
|
||||
assertFalse(completions.isEmpty());
|
||||
CompletionItem completion = editor.getFirstCompletion();
|
||||
editor.apply(completion);
|
||||
editor.apply(resolveCompletionItem(completion));
|
||||
assertEquals(expectTextAfter, editor.getText());
|
||||
}
|
||||
|
||||
@@ -735,7 +735,7 @@ public class LanguageServerHarness {
|
||||
List<? extends CompletionItem> completions = editor.getCompletions();
|
||||
for (CompletionItem ci : completions) {
|
||||
editor = newEditor(textBefore);
|
||||
editor.apply(ci);
|
||||
editor.apply(resolveCompletionItem(ci));
|
||||
actual.append(editor.getText());
|
||||
actual.append("\n-------------------\n");
|
||||
}
|
||||
@@ -749,7 +749,7 @@ public class LanguageServerHarness {
|
||||
List<? extends CompletionItem> completions = editor.getCompletions();
|
||||
for (CompletionItem ci : completions) {
|
||||
editor = newEditor(textBefore);
|
||||
editor.apply(ci);
|
||||
editor.apply(resolveCompletionItem(ci));
|
||||
if (editor.getText().equals(expectTextAfter)) {
|
||||
return;
|
||||
}
|
||||
@@ -1007,6 +1007,10 @@ public class LanguageServerHarness {
|
||||
List<? extends WorkspaceSymbol> r = server.getWorkspaceService().symbol(params).get().getRight();
|
||||
return ImmutableList.copyOf(r);
|
||||
}
|
||||
|
||||
public Object executeCommand(Command command) throws Exception {
|
||||
return server.getWorkspaceService().executeCommand(new ExecuteCommandParams(command.getCommand(), command.getArguments())).get();
|
||||
}
|
||||
|
||||
public void assertWorkspaceSymbols(String query, String... expectedSymbols) throws Exception {
|
||||
Set<String> actualSymbols = getWorkspaceSymbols(query).stream().map(sym -> sym.getName()).collect(Collectors.toSet());
|
||||
|
||||
@@ -134,7 +134,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
CompositeCompletionEngine compositeCompletionEngine = new CompositeCompletionEngine();
|
||||
completionEngines.forEach(compositeCompletionEngine::add);
|
||||
completionEngineAdapter = server.createCompletionEngineAdapter(compositeCompletionEngine);
|
||||
completionEngineAdapter.setMaxCompletions(100);
|
||||
completionEngineAdapter.setMaxCompletions(-1);
|
||||
documents.onCompletion(completionEngineAdapter::getCompletions);
|
||||
documents.onCompletionResolve(completionEngineAdapter::resolveCompletion);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2015, 2023 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
|
||||
@@ -17,7 +17,7 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
|
||||
public abstract class AbstractPropertyProposal extends ScoreableProposal {
|
||||
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return niceTypeName(getType());
|
||||
@@ -101,7 +101,7 @@ public abstract class AbstractPropertyProposal extends ScoreableProposal {
|
||||
public final DocumentEdits getTextEdit() {
|
||||
return this.proposalApplier;
|
||||
}
|
||||
|
||||
|
||||
// @Override
|
||||
// public void apply(IDocument document) {
|
||||
// try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014, 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2014, 2023 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
|
||||
@@ -62,11 +62,11 @@ public class PropertyCompletionFactory {
|
||||
};
|
||||
}
|
||||
|
||||
public ScoreableProposal property(IDocument doc, DocumentEdits applier, Match<PropertyInfo> prop, TypeUtil typeUtil) {
|
||||
public AbstractPropertyProposal property(IDocument doc, DocumentEdits applier, Match<PropertyInfo> prop, TypeUtil typeUtil) {
|
||||
return new PropertyProposal(doc, applier, prop, typeUtil);
|
||||
}
|
||||
|
||||
public ScoreableProposal beanProperty(IDocument doc, final String contextProperty, final Type contextType, final String pattern, final TypedProperty property, final double score, DocumentEdits applier, final TypeUtil typeUtil) {
|
||||
public AbstractPropertyProposal beanProperty(IDocument doc, final String contextProperty, final Type contextType, final String pattern, final TypedProperty property, final double score, DocumentEdits applier, final TypeUtil typeUtil) {
|
||||
AbstractPropertyProposal proposal = new AbstractPropertyProposal(doc, applier) {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,7 +14,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryCompletionProvider;
|
||||
@@ -25,8 +24,8 @@ import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
@@ -44,12 +43,7 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
|
||||
int offset, IDocument doc, Collection<ICompletionProposal> completions) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection<ICompletionProposal> completions) {
|
||||
public void provideCompletions(ASTNode node, int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
|
||||
TypeDeclaration type = ASTUtils.findDeclaringType(node);
|
||||
DataRepositoryDefinition repo = getDataRepositoryDefinition(type);
|
||||
if(repo != null && repo.getDomainType() != null){
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
@@ -24,12 +25,12 @@ public class FindByCompletionProposal implements ICompletionProposal {
|
||||
private DocumentEdits edits;
|
||||
private String details;
|
||||
private Renderable doc;
|
||||
private Optional<DocumentEdits> additionalEdits;
|
||||
private Supplier<DocumentEdits> additionalEdits;
|
||||
private String filter;
|
||||
private boolean triggerNextCompletion;
|
||||
|
||||
public FindByCompletionProposal(String label, CompletionItemKind kind, DocumentEdits edits, String details,
|
||||
Renderable doc, Optional<DocumentEdits> additionalEdits, String filter, boolean triggerNextCompletion) {
|
||||
Renderable doc, Supplier<DocumentEdits> additionalEdits, String filter, boolean triggerNextCompletion) {
|
||||
super();
|
||||
this.label = label;
|
||||
this.kind = kind;
|
||||
@@ -41,7 +42,7 @@ public class FindByCompletionProposal implements ICompletionProposal {
|
||||
this.triggerNextCompletion = triggerNextCompletion;
|
||||
}
|
||||
|
||||
public static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion, boolean triggerNextCompletion, Optional<DocumentEdits> additionalEdits) {
|
||||
public static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion, boolean triggerNextCompletion, Supplier<DocumentEdits> additionalEdits) {
|
||||
DocumentEdits edits = new DocumentEdits(null, false);
|
||||
String filter = label;
|
||||
if (prefix != null && label.startsWith(prefix)) {
|
||||
@@ -84,8 +85,8 @@ public class FindByCompletionProposal implements ICompletionProposal {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DocumentEdits> getAdditionalEdit() {
|
||||
return additionalEdits;
|
||||
public Optional<Supplier<DocumentEdits>> getAdditionalEdit() {
|
||||
return Optional.ofNullable(additionalEdits);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
package org.springframework.ide.vscode.boot.java.data.providers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
@@ -34,7 +33,7 @@ public class DataRepositoryQueryStartCompletionProvider implements DataRepositor
|
||||
for(QueryMethodSubject queryMethodSubject : QueryMethodSubject.QUERY_METHOD_SUBJECTS){
|
||||
String toInsert = queryMethodSubject.key() + "By";
|
||||
if(prefix == null || (toInsert.length() > localPrefix.length() && toInsert.startsWith(localPrefix)) || isOffsetAfterWhitespace(doc, offset)) {
|
||||
completions.add(FindByCompletionProposal.createProposal(offset, CompletionItemKind.Text, prefix, toInsert, toInsert, true, Optional.empty()));
|
||||
completions.add(FindByCompletionProposal.createProposal(offset, CompletionItemKind.Text, prefix, toInsert, toInsert, true, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class DataRepositoryStandardCompletionProvider implements DataRepositoryC
|
||||
completion.append(StringUtils.uncapitalize(domainProperty.getName()));
|
||||
completion.append(");");
|
||||
|
||||
return FindByCompletionProposal.createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString(), false, ASTUtils.getImportsEdit((CompilationUnit)node.getRoot(), imprts, doc));
|
||||
return FindByCompletionProposal.createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString(), false, () -> ASTUtils.getImportsEdit((CompilationUnit)node.getRoot(), imprts, doc).orElse(null));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
@@ -114,8 +114,7 @@ public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepo
|
||||
if (toReplace.startsWith(lastWord)) {
|
||||
DocumentEdits edits = new DocumentEdits(null, false);
|
||||
edits.replace(offset - lastWord.length(), offset, toReplace);
|
||||
DocumentEdits additionalEdits = new DocumentEdits(null, false);
|
||||
ICompletionProposal proposal = new FindByCompletionProposal(toReplace, CompletionItemKind.Text, edits, "property " + toReplace, null, Optional.of(additionalEdits), lastWord, true);
|
||||
ICompletionProposal proposal = new FindByCompletionProposal(toReplace, CompletionItemKind.Text, edits, "property " + toReplace, null, null, lastWord, true);
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
@@ -141,7 +140,7 @@ public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepo
|
||||
newText.append(";");
|
||||
int replaceStart = calculateReplaceOffset(offset, localPrefix, fullPrefix, returnType);
|
||||
edits.replace(replaceStart, offset, newText.toString());
|
||||
Optional<DocumentEdits> additionalEdits = ASTUtils.getImportsEdit((CompilationUnit) node.getRoot(), imports, doc);
|
||||
Supplier<DocumentEdits> additionalEdits = () -> ASTUtils.getImportsEdit((CompilationUnit) node.getRoot(), imports, doc).orElse(null);
|
||||
ICompletionProposal proposal = new FindByCompletionProposal(signature, CompletionItemKind.Method, edits, null, null, additionalEdits, signature, false);
|
||||
completions.add(proposal);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec
|
||||
});
|
||||
}
|
||||
|
||||
private void collectCompletionsForAnnotations(ASTNode node, int offset, IDocument doc, Collection<ICompletionProposal> completions) {
|
||||
private void collectCompletionsForAnnotations(ASTNode node, int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
|
||||
Annotation annotation = null;
|
||||
ASTNode exactNode = node;
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public interface CompletionProvider {
|
||||
|
||||
void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, IDocument doc, Collection<ICompletionProposal> completions);
|
||||
void provideCompletions(ASTNode node, int offset, IDocument doc, Collection<ICompletionProposal> completions);
|
||||
default void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, Collection<ICompletionProposal> completions) {};
|
||||
default void provideCompletions(ASTNode node, int offset, TextDocument doc, Collection<ICompletionProposal> completions) {};
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
@@ -29,7 +29,7 @@ public class ScopeCompletionProcessor implements CompletionProvider {
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
|
||||
int offset, IDocument doc, Collection<ICompletionProposal> completions) {
|
||||
int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
|
||||
|
||||
try {
|
||||
if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair) {
|
||||
@@ -82,8 +82,4 @@ public class ScopeCompletionProcessor implements CompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection<ICompletionProposal> completions) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.snippets;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
@@ -20,8 +21,6 @@ import org.springframework.ide.vscode.commons.languageserver.completion.IComplet
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
public class JavaSnippet {
|
||||
|
||||
private JavaSnippetContext context;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.snippets;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -20,8 +21,6 @@ import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
/**
|
||||
* Respobsible for converting eclipse-like template string into lsp snippet text.
|
||||
* @author Kris De Volder
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java.snippets;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
@@ -22,8 +23,6 @@ import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
public class JavaSnippetCompletion implements ICompletionProposal{
|
||||
|
||||
private DocumentRegion query;
|
||||
@@ -64,7 +63,7 @@ public class JavaSnippetCompletion implements ICompletionProposal{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DocumentEdits> getAdditionalEdit() {
|
||||
return javaSnippet.getImports().flatMap(imports -> ASTUtils.getImportsEdit(cu, imports, query.getDocument()));
|
||||
public Optional<java.util.function.Supplier<DocumentEdits>> getAdditionalEdit() {
|
||||
return javaSnippet.getImports().map(imports -> () -> ASTUtils.getImportsEdit(cu, imports, query.getDocument()).orElse(null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.snippets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
@@ -23,8 +24,6 @@ import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
public class JavaSnippetManager {
|
||||
|
||||
private List<JavaSnippet> snippets = new ArrayList<>();
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
@@ -55,7 +56,7 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
|
||||
int offset, IDocument doc, Collection<ICompletionProposal> completions) {
|
||||
int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
|
||||
|
||||
try {
|
||||
// case: @Value(<*>)
|
||||
@@ -103,10 +104,6 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection<ICompletionProposal> completions) {
|
||||
}
|
||||
|
||||
private void computeProposalsForSimpleName(ASTNode node, Collection<ICompletionProposal> completions, int offset,
|
||||
IDocument doc) {
|
||||
String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2023 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
|
||||
@@ -235,11 +235,9 @@ public class PropertiesCompletionProposalsCalculator {
|
||||
for (TypedProperty prop : objectProperties) {
|
||||
double score = FuzzyMatcher.matchScore(prefix, prop.getName());
|
||||
if (score!=0) {
|
||||
Type valueType = prop.getType();
|
||||
String postFix = propertyCompletionPostfix(typeUtil, valueType);
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.delete(navOffset+1, offset);
|
||||
edits.insert(offset, prop.getName()+postFix);
|
||||
edits.lazyInsert(offset, prop.getName(), () -> prop.getName() + propertyCompletionPostfix(typeUtil, prop.getType()));
|
||||
proposals.add(
|
||||
completionFactory.beanProperty(doc, null, type, prefix, prop, score, edits, typeUtil)
|
||||
);
|
||||
@@ -344,10 +342,10 @@ public class PropertiesCompletionProposalsCalculator {
|
||||
try {
|
||||
docEdits = LazyProposalApplier.from(() -> {
|
||||
try {
|
||||
Type type = TypeParser.parse(match.data.getType());
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.delete(offset-prefix.length(), offset);
|
||||
edits.insert(offset, match.data.getId() + propertyCompletionPostfix(typeUtil, type));
|
||||
String key = match.data.getId();
|
||||
edits.lazyInsert(offset, key, () -> key + propertyCompletionPostfix(typeUtil, TypeParser.parse(match.data.getType())));
|
||||
return edits;
|
||||
} catch (Throwable t) {
|
||||
log.error("{}", t);
|
||||
|
||||
@@ -26,13 +26,11 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.common.InformationTemplates;
|
||||
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
|
||||
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataGroup;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.Deprecation;
|
||||
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.metadata.IndexNavigator;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo.PropertySource;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.HintProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
|
||||
@@ -46,7 +44,6 @@ import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
|
||||
import org.springframework.ide.vscode.boot.metadata.util.PropertyDocUtils;
|
||||
import org.springframework.ide.vscode.boot.properties.hover.PropertiesDefinitionCalculator;
|
||||
import org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlASTReconciler;
|
||||
import org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.java.IField;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaElement;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
@@ -208,7 +205,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
|
||||
//property not yet defined
|
||||
Type type = p.getType();
|
||||
edits.delete(queryOffset, query);
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendTextFor(type));
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, () -> appendTextFor(type));
|
||||
proposals.add(completionFactory.beanProperty(doc.getDocument(),
|
||||
contextPath.toPropString(), getType(),
|
||||
query, p, score, edits, typeUtil)
|
||||
@@ -511,15 +508,14 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
|
||||
// context. If it doesn't we can create it as any child of the context
|
||||
// so that includes, right at place the user is typing now.
|
||||
SNode existingNode = contextNode.traverse(nextSegment);
|
||||
String appendText = appendTextFor(TypeParser.parse(match.data.getType()));
|
||||
if (existingNode==null) {
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendText);
|
||||
edits.createPathInPlace(contextNode, relativePath, queryOffset, () -> appendTextFor(TypeParser.parse(match.data.getType())));
|
||||
} else {
|
||||
String wholeLine = file.getLineTextAtOffset(queryOffset);
|
||||
if (wholeLine.trim().equals(query.trim())) {
|
||||
edits.deleteLineBackwardAtOffset(queryOffset);
|
||||
}
|
||||
edits.createPath(getContextRoot(file), YamlPath.fromProperty(match.data.getId()), appendText);
|
||||
edits.createPath(getContextRoot(file), YamlPath.fromProperty(match.data.getId()), () -> appendTextFor(TypeParser.parse(match.data.getType())));
|
||||
}
|
||||
return edits;
|
||||
});
|
||||
|
||||
@@ -204,6 +204,7 @@ public abstract class AbstractPropsEditorTest {
|
||||
for (int i = 0; i < actualLabels.length; i++) {
|
||||
actualLabels[i] = completions.get(i).getLabel();
|
||||
if (includeDetail) {
|
||||
completions.set(i, harness.resolveCompletionItem(completions.get(i)));
|
||||
String detail = completions.get(i).getDetail();
|
||||
if (detail != null && !detail.isEmpty()) {
|
||||
actualLabels[i] += " : " + detail;
|
||||
@@ -221,7 +222,10 @@ public abstract class AbstractPropsEditorTest {
|
||||
completionDetails[i] = expectCompletions[i][1];
|
||||
}
|
||||
Editor editor = newEditor(editorText);
|
||||
List<CompletionItem> completions = editor.getCompletions();
|
||||
List<CompletionItem> completions = editor.getCompletions()
|
||||
.stream()
|
||||
.map(ci -> harness.resolveCompletionItem(ci))
|
||||
.collect(Collectors.toList());
|
||||
String[] actualLabels = new String[completions.size()];
|
||||
String[] actualDetails = new String[completions.size()];
|
||||
for (int i = 0; i < completions.size(); i++) {
|
||||
|
||||
@@ -73,15 +73,15 @@ public class XmlBeansHyperlinkTest {
|
||||
private ProjectsHarness projects = ProjectsHarness.INSTANCE;
|
||||
private MavenJavaProject project;
|
||||
|
||||
private Level originalLevel;
|
||||
// private Level originalLevel;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
final Logger logger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
originalLevel = logger.getLevel();
|
||||
logger.setLevel(Level.DEBUG);
|
||||
|
||||
log.debug("-------------------------------------------------");
|
||||
// final Logger logger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
// originalLevel = logger.getLevel();
|
||||
// logger.setLevel(Level.DEBUG);
|
||||
//
|
||||
// log.debug("-------------------------------------------------");
|
||||
harness.intialize(null);
|
||||
|
||||
Map<String, Object> supportXML = new HashMap<>();
|
||||
@@ -105,12 +105,12 @@ public class XmlBeansHyperlinkTest {
|
||||
initProject.get(1500, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
log.debug("-------------------------------------------------");
|
||||
final Logger logger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
logger.setLevel(originalLevel);
|
||||
}
|
||||
// @AfterEach
|
||||
// public void tearDown() {
|
||||
// log.debug("-------------------------------------------------");
|
||||
// final Logger logger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
// logger.setLevel(originalLevel);
|
||||
// }
|
||||
|
||||
@Test
|
||||
void testBeanClassHyperlink() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user