diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java index 89be1757e..3d2b859c6 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java @@ -10,11 +10,12 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.completion; +import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_INDENTED_PROPOSAL; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +24,7 @@ import org.springframework.ide.vscode.commons.languageserver.completion.IComplet import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument; @@ -34,8 +36,6 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider; import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil; -import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.*; - /** * Implements {@link ICompletionEngine} for .yml file, based on a YamlAssistContextProvider * which has to to be injected into engine via its contructor. @@ -106,7 +106,10 @@ public class YamlCompletionEngine implements ICompletionEngine { return transformed; } - private Collection getDashedCompletions(int offset, YamlDocument doc, SNode preciseContextNode, SNode currentNode) throws Exception { + private Collection getDashedCompletions( + int offset, YamlDocument doc, + SNode preciseContextNode, SNode currentNode + ) throws Exception { SNode contextNode = getContextNode(doc, currentNode, offset, "- "); if (preciseContextNode!=contextNode) { return getBaseCompletions(offset, doc, currentNode, contextNode, true); @@ -129,10 +132,23 @@ public class YamlCompletionEngine implements ICompletionEngine { private boolean isIndentRelaxable(SNode contextNode) throws Exception { return contextNode!=null && ( isBarrenKey(contextNode) || - contextNode.getNodeType()==SNodeType.SEQ + isBarrenSeq(contextNode) ); } + private boolean isBarrenSeq(SNode node) { + try { + if (node.getNodeType()==SNodeType.SEQ) { + SSeqNode seqNode = (SSeqNode) node; + String value = seqNode.getTextWithoutChildren(); + return "-".equals(value.trim()); + } + } catch (Exception e) { + Log.log(e); + } + return false; + } + private boolean isBarrenKey(SNode node) throws Exception { if (node.getNodeType()==SNodeType.KEY) { SKeyNode keyNode = (SKeyNode) node; @@ -213,7 +229,7 @@ public class YamlCompletionEngine implements ICompletionEngine { while (node.getNodeType()!=SNodeType.DOC && ( nodeIndent==-1 || nodeIndent>currentIndent || - nodeIndent==currentIndent && node.getNodeType()==SNodeType.SEQ + nodeIndent==currentIndent && (node.getNodeType()==SNodeType.SEQ || node.getNodeType() == SNodeType.RAW) )) { node = node.getParent(); nodeIndent = node.getIndent(); diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java index ceb2a86b8..d9847d2e5 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -579,7 +580,6 @@ public class YamlStructureParser { public abstract class SLeafNode extends SNode { - public SLeafNode(SChildBearingNode parent, YamlDocument doc, int indent, int start, int end) { super(parent, doc, indent, start, end); @@ -735,6 +735,15 @@ public class YamlStructureParser { return offset>=getStart()+dashLen && offset <= getTreeEnd(); } + + public String getTextWithoutChildren() { + int end = getNodeEnd(); + Optional child = getChildren().stream().findFirst(); + if (child.isPresent()) { + end = Math.min(end, child.get().getStart()); + } + return doc.textBetween(getStart(), end); + } } public class SKeyNode extends SChildBearingNode { 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 e6300f907..3c712d013 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 @@ -46,6 +46,7 @@ import org.eclipse.lsp4j.TextDocumentPositionParams; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.junit.Assert; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.util.text.LanguageId; import com.google.common.collect.ImmutableList; @@ -290,6 +291,10 @@ public class Editor { } public List assertCompletions(String... expectTextAfter) throws Exception { + return assertCompletions((item) -> true, expectTextAfter); + } + + public List assertCompletions(Predicate filter, String... expectTextAfter) throws Exception { StringBuilder expect = new StringBuilder(); StringBuilder actual = new StringBuilder(); for (String after : expectTextAfter) { @@ -299,10 +304,12 @@ public class Editor { List completions = getCompletions(); for (CompletionItem completion : completions) { - Editor editor = this.clone(); - editor.apply(completion); - actual.append(editor.getText()); - actual.append("\n-------------------\n"); + if (filter.test(completion)) { + Editor editor = this.clone(); + editor.apply(completion); + actual.append(editor.getText()); + actual.append("\n-------------------\n"); + } } assertEquals(expect.toString(), actual.toString()); return completions; diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java index 40e361471..72faa9476 100644 --- a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java @@ -1148,6 +1148,28 @@ public class ManifestYamlEditorTest { when(service.getName()).thenReturn("my-service"); when(service.getPlan()).thenReturn("cheap-plan"); + editor = harness.newEditor( + "services:\n"+ + "- blah\n"+ + "ser<*>" + ); + editor.assertCompletionWithLabel((s) -> s.startsWith("- "), + "services:\n"+ + "- blah\n"+ + "- my-service<*>" + ); + + editor = harness.newEditor( + "services:\n"+ + "- blah\n"+ + "<*>" + ); + editor.assertCompletionWithLabel((s) -> s.startsWith("- "), + "services:\n"+ + "- blah\n"+ + "- my-service<*>" + ); + editor = harness.newEditor( "applications:\n" + "- name: foo\n" + @@ -1201,6 +1223,26 @@ public class ManifestYamlEditorTest { ); } + @Test public void noRelaxedValueCompletionsInListItemContexts() throws Exception { + //See: https://www.pivotaltracker.com/story/show/144393355 + + CFServiceInstance service = mock(CFServiceInstance.class); + when(cloudfoundry.client.getServices()).thenReturn(ImmutableList.of(service)); + when(service.getName()).thenReturn("my-service"); + when(service.getPlan()).thenReturn("cheap-plan"); + + Editor editor = harness.newEditor( + "services:\n" + + "- some-service\n" + + "<*>" + ); + editor.assertCompletions((c) -> c.getLabel().contains("my-service"), + "services:\n" + + "- some-service\n" + + "- my-service<*>" + ); + } + ////////////////////////////////////////////////////////////////////////////// private List assertCompletions(String textBefore, String... textAfter) throws Exception {