From 1dfba38500ef62f3d85c3f6f193262e01fc29ed2 Mon Sep 17 00:00:00 2001 From: aboyko Date: Thu, 28 Sep 2023 10:22:12 -0400 Subject: [PATCH] Spring properties postfix computed in completion item resolve --- .../commons/java/SpringProjectUtil.java | 2 +- .../completion/DocumentEdits.java | 61 +++++- .../completion/ICompletionProposal.java | 5 +- .../completion/TransformedCompletion.java | 11 +- .../VscodeCompletionEngineAdapter.java | 205 ++++++++++++++++-- .../languageserver/util/Lsp4jUtils.java | 46 ++++ .../util/SimpleLanguageServer.java | 14 +- .../commons/rewrite/java/ORAstUtils.java | 1 + .../ide/vscode/commons/util/StringUtil.java | 32 ++- .../yaml/completion/YamlPathEdits.java | 56 ++++- .../languageserver/testharness/Editor.java | 5 + .../testharness/LanguageServerHarness.java | 10 +- .../app/BootLanguageServerInitializer.java | 2 +- .../boot/common/AbstractPropertyProposal.java | 6 +- .../common/PropertyCompletionFactory.java | 6 +- .../DataRepositoryCompletionProcessor.java | 10 +- .../java/data/FindByCompletionProposal.java | 11 +- ...epositoryQueryStartCompletionProvider.java | 3 +- ...aRepositoryStandardCompletionProvider.java | 2 +- ...toryPrefixSensitiveCompletionProvider.java | 7 +- .../handlers/BootJavaCompletionEngine.java | 2 +- .../java/handlers/CompletionProvider.java | 6 +- .../java/scope/ScopeCompletionProcessor.java | 8 +- .../boot/java/snippets/JavaSnippet.java | 3 +- .../java/snippets/JavaSnippetBuilder.java | 3 +- .../java/snippets/JavaSnippetCompletion.java | 7 +- .../java/snippets/JavaSnippetManager.java | 3 +- .../java/value/ValueCompletionProcessor.java | 7 +- ...opertiesCompletionProposalsCalculator.java | 10 +- .../ApplicationYamlAssistContext.java | 10 +- .../harness/AbstractPropsEditorTest.java | 6 +- .../utils/test/XmlBeansHyperlinkTest.java | 24 +- 32 files changed, 471 insertions(+), 113 deletions(-) create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/Lsp4jUtils.java diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java index f133403be..cbc32c73c 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java @@ -106,7 +106,7 @@ public class SpringProjectUtil { log.error("", e); } return Optional.empty(); - }).isEmpty(); + }).isPresent(); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java index 8cfe7a421..62064eabf 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/DocumentEdits.java @@ -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 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 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 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 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 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(); + } + } + } + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java index cbcb9b02c..3fd9ec129 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java @@ -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 getAdditionalEdit() { return Optional.empty(); } + default Optional> 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. diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java index 3e47041df..a1e5eb2cb 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java @@ -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> getAdditionalEdit() { + return original.getAdditionalEdit(); + } + } \ No newline at end of file diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java index 22f0ef50a..e826a74c3 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java @@ -15,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 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 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 subCommands = item.getCommand().getArguments(); + for (ListIterator 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 subCommands = item.getCommand().getArguments(); + for (ListIterator 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 filter(Collection 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 mainEdit = adaptEdits(doc, completion.getTextEdit(), usedSnippets); + Optional 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 adaptEdits(TextDocument doc, DocumentEdits edits, AtomicBoolean usedSnippets) { + private Optional 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(); + } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/Lsp4jUtils.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/Lsp4jUtils.java new file mode 100644 index 000000000..5ad7e2450 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/Lsp4jUtils.java @@ -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(); + } + } + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 393706cac..1b1fc2682 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -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 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 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 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); diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java index 2146efc2b..d2eb49146 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java @@ -314,6 +314,7 @@ public class ORAstUtils { public static List parseInputs(JavaParser parser, Iterable inputs, Consumer 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() { diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/StringUtil.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/StringUtil.java index 06fe1eb82..34fa4bf7d 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/StringUtil.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/StringUtil.java @@ -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 diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlPathEdits.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlPathEdits.java index cf84903ab..4c59560eb 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlPathEdits.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlPathEdits.java @@ -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 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 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 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 { diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index d32d45bfa..f8e15bab8 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -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) { diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 2aacea7c3..450d81408 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -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 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 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 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 actualSymbols = getWorkspaceSymbols(query).stream().map(sym -> sym.getName()).collect(Collectors.toSet()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java index fa615d86c..431c20678 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java @@ -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); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java index 59735a462..2ccded773 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java @@ -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 { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java index 9ca754842..a53fc256f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java @@ -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 prop, TypeUtil typeUtil) { + public AbstractPropertyProposal property(IDocument doc, DocumentEdits applier, Match 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 diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java index cff1af18f..824e2a93d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java @@ -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 completions) { - } - - @Override - public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { + public void provideCompletions(ASTNode node, int offset, TextDocument doc, Collection completions) { TypeDeclaration type = ASTUtils.findDeclaringType(node); DataRepositoryDefinition repo = getDataRepositoryDefinition(type); if(repo != null && repo.getDomainType() != null){ diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java index b3de40c50..a55c8a7b1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java @@ -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 additionalEdits; + private Supplier additionalEdits; private String filter; private boolean triggerNextCompletion; public FindByCompletionProposal(String label, CompletionItemKind kind, DocumentEdits edits, String details, - Renderable doc, Optional additionalEdits, String filter, boolean triggerNextCompletion) { + Renderable doc, Supplier 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 additionalEdits) { + public static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion, boolean triggerNextCompletion, Supplier 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 getAdditionalEdit() { - return additionalEdits; + public Optional> getAdditionalEdit() { + return Optional.ofNullable(additionalEdits); } @Override diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryQueryStartCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryQueryStartCompletionProvider.java index e409916eb..6d071fe43 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryQueryStartCompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryQueryStartCompletionProvider.java @@ -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)); } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryStandardCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryStandardCompletionProvider.java index a251d4f21..90d15cd55 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryStandardCompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/DataRepositoryStandardCompletionProvider.java @@ -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)); } } \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/prefixsensitive/DataRepositoryPrefixSensitiveCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/prefixsensitive/DataRepositoryPrefixSensitiveCompletionProvider.java index cb9c27644..6742d58a7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/prefixsensitive/DataRepositoryPrefixSensitiveCompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/prefixsensitive/DataRepositoryPrefixSensitiveCompletionProvider.java @@ -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 additionalEdits = ASTUtils.getImportsEdit((CompilationUnit) node.getRoot(), imports, doc); + Supplier 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); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java index ce68095c5..da4fd7b13 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java @@ -64,7 +64,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec }); } - private void collectCompletionsForAnnotations(ASTNode node, int offset, IDocument doc, Collection completions) { + private void collectCompletionsForAnnotations(ASTNode node, int offset, TextDocument doc, Collection completions) { Annotation annotation = null; ASTNode exactNode = node; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java index 405cb7661..af2e34918 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java @@ -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 completions); - void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions); + default void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, Collection completions) {}; + default void provideCompletions(ASTNode node, int offset, TextDocument doc, Collection completions) {}; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java index 80d5ffcc9..a1cc45e60 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java @@ -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 completions) { + int offset, TextDocument doc, Collection 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 completions) { - } - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java index 4e5326c50..3d4335d7f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java @@ -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; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java index 044e8a445..64425c498 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java @@ -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 diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java index ffa27007a..affcdf578 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java @@ -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 getAdditionalEdit() { - return javaSnippet.getImports().flatMap(imports -> ASTUtils.getImportsEdit(cu, imports, query.getDocument())); + public Optional> getAdditionalEdit() { + return javaSnippet.getImports().map(imports -> () -> ASTUtils.getImportsEdit(cu, imports, query.getDocument()).orElse(null)); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java index 0f0a22d75..b12d9297d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java @@ -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 snippets = new ArrayList<>(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java index e882cc476..8a7863a6e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java @@ -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 completions) { + int offset, TextDocument doc, Collection completions) { try { // case: @Value(<*>) @@ -103,10 +104,6 @@ public class ValueCompletionProcessor implements CompletionProvider { } } - @Override - public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { - } - private void computeProposalsForSimpleName(ASTNode node, Collection completions, int offset, IDocument doc) { String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java index e950e190d..b08d08536 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java @@ -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); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java index b81298c7b..4cf381387 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java @@ -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; }); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/AbstractPropsEditorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/AbstractPropsEditorTest.java index 2ff8433e7..f8cd9a33a 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/AbstractPropsEditorTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/AbstractPropsEditorTest.java @@ -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 completions = editor.getCompletions(); + List 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++) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java index 4073de1c0..f3364f735 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java @@ -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 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 {