Spring properties postfix computed in completion item resolve

This commit is contained in:
aboyko
2023-09-28 10:22:12 -04:00
parent 862fd9b992
commit 1dfba38500
32 changed files with 471 additions and 113 deletions

View File

@@ -106,7 +106,7 @@ public class SpringProjectUtil {
log.error("", e);
}
return Optional.empty();
}).isEmpty();
}).isPresent();
}

View File

@@ -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();
}
}
}
}

View File

@@ -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.

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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){

View File

@@ -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

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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) {};
}

View File

@@ -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) {
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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<>();

View File

@@ -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());

View File

@@ -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);

View File

@@ -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;
});

View File

@@ -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++) {

View File

@@ -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 {