From f5fc8d06016b655164456dfce32c37e142f2de15 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Tue, 2 May 2017 19:15:33 -0700 Subject: [PATCH] Relaxed CA with less indentation --- .../completion/DocumentEdits.java | 5 +- .../VscodeCompletionEngineAdapter.java | 3 +- .../completion/TransformedCompletion.java | 7 +- .../yaml/completion/YTypeAssistContext.java | 26 ++++--- .../yaml/completion/YamlCompletionEngine.java | 67 +++++++++++++++++-- .../yaml/structure/YamlStructureParser.java | 10 +++ .../languageserver/testharness/Editor.java | 11 ++- .../vscode/concourse/ConcourseEditorTest.java | 59 ++++++++++------ 8 files changed, 144 insertions(+), 44 deletions(-) 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 387830934..8a3a1b544 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 @@ -447,6 +447,10 @@ public class DocumentEdits implements ProposalApplier { } } + public void firstDelete(int start, int end) { + edits.add(0, new Deletion(grabCursor, start, end)); + } + /** * Find first non-whitepace insertion edit and transform its contents. * @param transformFun receives the insertion text of the target edit and the offset of the first non-whitespace character @@ -478,5 +482,4 @@ public class DocumentEdits implements ProposalApplier { public void freezeCursor() { this.grabCursor = false; } - } 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 56e6d3453..d5bf7c76c 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 @@ -28,7 +28,6 @@ import org.springframework.ide.vscode.commons.languageserver.completion.Document 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.util.Futures; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -154,6 +153,6 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { //TODO: item is pre-resoved so we don't do anything, but we really should somehow defer some work, such as // for example computing docs and edits to resolve time. //The tricky part is that we have to probably remember infos about the unresolved elements somehow so we can resolve later. - return Futures.of(unresolved); + return CompletableFuture.completedFuture(unresolved); } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java index 44d39b248..3e5887e1c 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TransformedCompletion.java @@ -27,7 +27,7 @@ public abstract class TransformedCompletion extends ScoreableProposal { protected final ICompletionProposal original; private DocumentEdits transformedEdit = null; - + public TransformedCompletion(ICompletionProposal proposal) { this.original = proposal; } @@ -73,4 +73,9 @@ public abstract class TransformedCompletion extends ScoreableProposal { } return 0; } + + @Override + public String getFilterText() { + return original.getFilterText(); + } } \ No newline at end of file diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index bf80a09fb..69fe322ed 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -10,8 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.completion; -import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_EXISTS; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -43,7 +41,6 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument; -import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode; import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode; import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil; @@ -132,17 +129,18 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { query, p, score, edits, typeUtil) ); } else { - //property already defined - // instead of filtering, navigate to the place where its defined. - deleteQueryAndLine(doc, query, queryOffset, edits); - //Cast to SChildBearingNode cannot fail because otherwise definedProps would be the empty set. - edits.createPath((SChildBearingNode) contextNode, relativePath, ""); - proposals.add( - completionFactory().beanProperty(doc.getDocument(), - contextPath.toPropString(), getType(), - query, p, score, edits, typeUtil) - .deemphasize(DEEMP_EXISTS) //deemphasize because it already exists - ); + // This piece below deactivated becuase moving cursor like this doesn't work in vscode +// //property already defined +// // instead of filtering, navigate to the place where its defined. +// deleteQueryAndLine(doc, query, queryOffset, edits); +// //Cast to SChildBearingNode cannot fail because otherwise definedProps would be the empty set. +// edits.createPath((SChildBearingNode) contextNode, relativePath, ""); +// proposals.add( +// completionFactory().beanProperty(doc.getDocument(), +// contextPath.toPropString(), getType(), +// query, p, score, edits, typeUtil) +// .deemphasize(DEEMP_EXISTS) //deemphasize because it already exists +// ); } } } 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 c6fbdbbbe..050e7243e 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,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.completion; +import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DEDENTED_PROPOSAL; import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_INDENTED_PROPOSAL; import java.util.ArrayList; @@ -17,6 +18,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +26,7 @@ import org.springframework.ide.vscode.commons.languageserver.completion.Document import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.Unicodes; @@ -49,6 +52,8 @@ import com.google.common.collect.ImmutableList; * @author Kris De Volder */ public class YamlCompletionEngine implements ICompletionEngine { + + Pattern SPACES = Pattern.compile("[ ]+"); final static Logger logger = LoggerFactory.getLogger(YamlCompletionEngine.class); @@ -117,9 +122,9 @@ public class YamlCompletionEngine implements ICompletionEngine { for (ICompletionProposal p : completions) { ICompletionProposal p_fixed = null; if (p.getLabel().startsWith("- ")) { - p_fixed = indentFix(p, dashyIndent - baseIndent, contextNode); + p_fixed = indentFix(p, dashyIndent - baseIndent, currentNode, contextNode); } else { - p_fixed = indentFix(p, plainIndent - baseIndent, contextNode); + p_fixed = indentFix(p, plainIndent - baseIndent, currentNode, contextNode); } if (p_fixed!=null) { transformed.add(p_fixed); @@ -130,14 +135,31 @@ public class YamlCompletionEngine implements ICompletionEngine { return Collections.emptyList(); } - protected ICompletionProposal indentFix(ICompletionProposal p, int fixIndentBy, SNode contextNode) { + protected ICompletionProposal indentFix(ICompletionProposal p, int fixIndentBy, SNode currentNode, SNode contextNode) { if (fixIndentBy==0) { return p; - } else if (fixIndentBy>0 && isExtraIndentRelaxable(contextNode)) { - return indented(p, Strings.repeat(" ", fixIndentBy)); + } else if (fixIndentBy>0) { + if (isExtraIndentRelaxable(contextNode)) { + return indented(p, Strings.repeat(" ", fixIndentBy)); + } } else { // fixIndentBy < 0 - return null; + if (isLesserIndentRelaxable(currentNode, contextNode)) { + return dedented(p, -fixIndentBy, contextNode.getDocument()); + } } + return null; + } + + private boolean isLesserIndentRelaxable(final SNode currentNode, final SNode contextNode) { + SChildBearingNode parent = currentNode.getParent(); + while (parent!=null && parent!=contextNode) { + SNode lastChild = parent.getLastRealChild(); + if (lastChild!=null && lastChild.getStart()>=currentNode.getNodeEnd()) { + return false; + } + parent = parent.getParent(); + } + return true; } /** @@ -160,6 +182,39 @@ public class YamlCompletionEngine implements ICompletionEngine { : contextNode.getIndent() + YamlIndentUtil.INDENT_BY; } + public ICompletionProposal dedented(ICompletionProposal proposal, int numSpacesToRemove, IDocument doc) { + Assert.isLegal(numSpacesToRemove>0); + int spacesEnd = proposal.getTextEdit().getFirstEditStart(); + int spacesStart = spacesEnd-numSpacesToRemove; + int numArrows = numSpacesToRemove / YamlIndentUtil.INDENT_BY; + String spaces = new DocumentRegion(doc, spacesStart, spacesEnd).toString(); + if (spaces.length()==numSpacesToRemove && SPACES.matcher(spaces).matches()) { + ScoreableProposal transformed = new TransformedCompletion(proposal) { + @Override public String tranformLabel(String originalLabel) { + return Strings.repeat(Unicodes.LEFT_ARROW+" ", numArrows) + originalLabel; + } + @Override public DocumentEdits transformEdit(DocumentEdits originalEdit) { + originalEdit.firstDelete(spacesStart, spacesEnd); + return originalEdit; + } + @Override + public String getFilterText() { + //If we don't add the spaces, vscode won't show the completions. + // Presumably this is because it matches the filtter text to the text it thinks its going + // to replace. Since we are replacing these removed spaces, they must be part of the filtertext + return spaces + super.getFilterText(); + } + }; + transformed.deemphasize(DEEMP_DEDENTED_PROPOSAL*numArrows); + return transformed; + } + // we can't dedent the proposal by the requested amount of space. So err on the safe + // side and ignore the proposal. (Otherwise me might end up deleting non-space chars + // in our attempt to de-dent.) + return null; + } + + public ICompletionProposal indented(ICompletionProposal proposal, String indentStr) { ScoreableProposal transformed = new TransformedCompletion(proposal) { @Override public String tranformLabel(String originalLabel) { 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 86e638b87..f1e098c2b 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 @@ -37,6 +37,7 @@ import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; @@ -581,6 +582,15 @@ public class YamlStructureParser { return null; } + public SNode getLastRealChild() { + for (SNode c : Lists.reverse(getChildren())) { + if (c.getIndent()>=0) { + return c; + } + } + return null; + } + } public abstract class SLeafNode extends SNode { 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 37ee1527e..5ca2ce00d 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,7 +46,6 @@ 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; @@ -346,6 +345,16 @@ public class Editor { } } + public void assertNoCompletionsWithLabel(Predicate labelPredicate) throws Exception { + List found = getCompletions().stream() + .map(c -> c.getLabel()) + .filter(labelPredicate) + .collect(Collectors.toList()); + if (!found.isEmpty()) { + fail("Found but not expected: "+found); + } + } + public void assertDoesNotContainCompletions(String... notToBeFound) throws Exception { StringBuilder actual = new StringBuilder(); diff --git a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java index 80a60096f..4c2b0ac5d 100644 --- a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java +++ b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java @@ -24,6 +24,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.commons.util.IOUtil; +import org.springframework.ide.vscode.commons.util.Unicodes; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.languageserver.testharness.CodeAction; import org.springframework.ide.vscode.languageserver.testharness.Editor; @@ -207,8 +208,6 @@ public class ConcourseEditorTest { editor.assertProblems( "a-resource|does not exist" ); - - //TODO: Add more test cases for structural problem? } @Test @@ -840,7 +839,8 @@ public class ConcourseEditorTest { "- name: every5minutes\n" + " type: time\n" + " source:\n" + - " <*>" + " <*>\n" + + " blah: blah" , // ====================== "<*>" , // => @@ -973,7 +973,8 @@ public class ConcourseEditorTest { "- name: the-repo\n" + " type: git\n" + " source:\n" + - " <*>" + " <*>\n" + + " blah: blah" , //================ "<*>" , // ==> @@ -1075,7 +1076,8 @@ public class ConcourseEditorTest { " plan:\n" + " - get: my-git\n" + " params:\n" + - " <*>"; + " <*>\n" + + " blah: blah"; assertContextualCompletions(context, "<*>" @@ -1149,7 +1151,8 @@ public class ConcourseEditorTest { " plan:\n" + " - put: my-git\n" + " params:\n" + - " <*>"; + " <*>\n" + + " blah: blah"; assertContextualCompletions(context, "<*>" @@ -1975,7 +1978,8 @@ public class ConcourseEditorTest { "- name: version\n" + " type: semver\n" + " source:\n" + - "<*>"; + "<*>\n" + + " blah: blah"; assertContextualCompletions(conText, " driver: git\n" + " <*>" @@ -2003,8 +2007,6 @@ public class ConcourseEditorTest { , " driver: git\n" + " username: <*>" - , - " driver: git<*>" ); } @@ -2874,9 +2876,9 @@ public class ConcourseEditorTest { editor.assertCompletionLabels( //For the 'exact' context: "check_every", - "name", - "source", - "type", + //"name", exists + //"source", exists + //"type", exists //For the nested context: "→ branch", "→ commit_verification_key_ids", @@ -2891,7 +2893,16 @@ public class ConcourseEditorTest { "→ skip_ssl_verification", "→ tag_filter", "→ uri", - "→ username" + "→ username", + // For the top-level context: + "← groups", + "← jobs", + "← resource_types", + // For the 'next job' context: + "← - check_every", + "← - name", + "← - source", + "← - type" ); editor.assertCompletionWithLabel("check_every", @@ -2965,9 +2976,9 @@ public class ConcourseEditorTest { "max_in_flight", "serial", "serial_groups", - "name", - "plan", - "public", + //"name", exists + //"plan", exists + //"public", exists //Completions with '-' "- aggregate", "- do", @@ -2989,7 +3000,18 @@ public class ConcourseEditorTest { "→ privileged", "→ tags", "→ timeout", - "→ task" + //"→ task" exists + "← groups\n" + + "← resource_types\n" + + "← resources\n" + + "← - build_logs_to_retain\n" + + "← - disable_manual_trigger\n" + + "← - max_in_flight\n" + + "← - name\n" + + "← - plan\n" + + "← - public\n" + + "← - serial\n" + + "← - serial_groups" ); } @@ -3375,7 +3397,6 @@ public class ConcourseEditorTest { ); } - @Ignore // Doesn't work yet. Enable once implemented. @Test public void relaxedContentAssistLessSpaces() throws Exception { Editor editor; @@ -3429,7 +3450,7 @@ public class ConcourseEditorTest { " <*>\n" + " trigger: true\n" ); - editor.assertCompletions("blah"); + editor.assertNoCompletionsWithLabel(label -> label.startsWith(Unicodes.LEFT_ARROW+" "));; } //////////////////////////////////////////////////////////////////////////////