diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index af0554a0e..faa6b75b4 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/vscode-extensions/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.util.ExceptionUtil.getSimpleError; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -29,7 +27,6 @@ import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.FuzzyMatcher; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.Renderable; -import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.ValueParseException; import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java index aa47f39d9..bfff38498 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SNodeDynamicSchemaContext.java @@ -69,5 +69,9 @@ public class SNodeDynamicSchemaContext extends CachingSchemaContext { return contextPath; } + @Override + public String toString() { + return "SNodeDynamicSchemaContext("+contextPath+")"; + } } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java index 6a1d91557..f080f9a71 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/structure/YamlStructureParser.java @@ -16,9 +16,7 @@ import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -32,7 +30,6 @@ import org.springframework.ide.vscode.commons.yaml.path.KeyAliases; import org.springframework.ide.vscode.commons.yaml.path.YamlNavigable; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; -import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SKeyNode; import org.springframework.ide.vscode.commons.yaml.util.Streams; import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil; @@ -42,9 +39,6 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; -import reactor.util.function.Tuple2; -import reactor.util.function.Tuples; - /** * A robust, coarse-grained parser that guesses the structure of a * yml document based on indentation levels. @@ -97,13 +91,20 @@ public class YamlStructureParser { // either "..." or "---" at the start of a line // followed by arbitrary amount of whitepsace // optionally followed by a "#" end of line comment. - //Note: "..." isn't a document separator but document terminator. Treating it as a separator is // technically not correct. As the structure parser is meant to be 'robust' and do something // sensible with incorrect input this makes sense here. The effect it will have is that user // can type after a document terminator and get content assist as if they are in a new document. // (They will also receive a syntax error message from the more formal and precise SnakeYaml parser) + + /** + * Stuff to ignore at the beginning of a yaml file (before the start of the first document): + */ + private static final Pattern SKIP_AT_START_OF_DOC = Pattern.compile( + "^((\\s*\\#)|(\\%)).*" + ); + // public static final Pattern SEQ_LINE = Pattern.compile( // "^( *)- .*"); @@ -153,8 +154,12 @@ public class YamlStructureParser { public int getStart() { return start; } + public boolean matches(Pattern pat, boolean stripiIndentation) throws Exception { + CharSequence text = stripiIndentation ? getTextWithoutIndent():getText(); + return pat.matcher(text).matches(); + } public boolean matches(Pattern pat) throws Exception { - return pat.matcher(getTextWithoutIndent()).matches(); + return matches(pat, true); } public String getTextWithoutIndent() throws Exception { return doc.textBetween(getStart()+getIndent(), getEnd()); @@ -192,6 +197,13 @@ public class YamlStructureParser { this.doc = doc; } + public YamlLine peek() throws Exception { + if (nextLine < doc.getDocument().getNumberOfLines()) { + return YamlLine.atLineNumber(doc, nextLine); + } + return null; //means EOF + } + public YamlLine read() throws Exception { if (nextLine < doc.getDocument().getNumberOfLines()) { return YamlLine.atLineNumber(doc, nextLine++); @@ -272,7 +284,7 @@ public class YamlStructureParser { public IDocument getDocument() { return doc.getDocument(); } - + public String getText() throws Exception { return doc.textBetween(start, end); } @@ -281,6 +293,7 @@ public class YamlStructureParser { * Default implementation, doesn't support any type of traversal operation. * Subclasses must override and implement where appropriate. */ + @Override public Stream traverseAmbiguously(YamlPathSegment s) { return Stream.empty(); } @@ -290,6 +303,7 @@ public class YamlStructureParser { * the interface. This is only here to prevent subclasses from implementing * this. They should implement traverseAmbiguously instead. */ + @Override public SNode traverse(YamlPathSegment s) { return traverseAmbiguously(s).findFirst().orElse(null); } @@ -308,7 +322,7 @@ public class YamlStructureParser { nodes.add(node); } } - + public YamlPath getPath() throws Exception { List path = new ArrayList<>(); for (SNode node : getPathNodes()) { @@ -321,7 +335,7 @@ public class YamlStructureParser { } /** - * Determine a YamlPathSegment that corresponds to given node. This may be + * Determine a YamlPathSegment that corresponds to given node. This may be * null because not all SNodes can be interpreted as 'step' in the yml * structure (e.g. raw nodes will return null, as will the 'root' node). */ @@ -528,7 +542,7 @@ public class YamlStructureParser { } return Stream.empty(); } - + public SKeyNode getChildWithKey(String key) { return (SKeyNode)getChildrenWithKey(key).findFirst().orElse(null); } @@ -612,9 +626,16 @@ public class YamlStructureParser { public SRootNode parse() throws Exception { SRootNode root = new SRootNode(input.getDocument()); - SDocNode doc = new SDocNode(root,0,0); - SChildBearingNode parent = doc; - YamlLine line; + SChildBearingNode parent = root; + YamlLine line = input.peek(); + while (line!=null && line.matches(SKIP_AT_START_OF_DOC, false)) { + input.read(); + line = input.peek(); + } + if (line!=null && !line.matches(DOCUMENT_SEPERATOR)) { + //document separator missing, create document implicitly + parent = new SDocNode(root,0,0); + } while (null!=(line=input.read())) { int indent = line.getIndent(); if (indent==-1) { diff --git a/vscode-extensions/commons/commons-yaml/src/test/java/org/springframework/ide/vscode/yaml/structure/YamlStructureParserTest.java b/vscode-extensions/commons/commons-yaml/src/test/java/org/springframework/ide/vscode/yaml/structure/YamlStructureParserTest.java index 2b0881287..f2bacd2d3 100644 --- a/vscode-extensions/commons/commons-yaml/src/test/java/org/springframework/ide/vscode/yaml/structure/YamlStructureParserTest.java +++ b/vscode-extensions/commons/commons-yaml/src/test/java/org/springframework/ide/vscode/yaml/structure/YamlStructureParserTest.java @@ -32,6 +32,51 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser public class YamlStructureParserTest { + @Test public void ignoreLeadingYamlCruftBeforeLeadingDocumentSeparator() throws Exception { + String[] stuffToIgnore = { + "#comment", + " # comment with leading spaces", + "%directive" + }; + + for (String stuff : stuffToIgnore) { + System.out.println(stuff); + MockYamlEditor editor = new MockYamlEditor( + stuff+"\n" + + "---\n" + + "hello:\n"+ + " world:\n" + + " message\n" + ); + assertParseOneDoc(editor, + "DOC(0): ---", + " KEY(0): hello:", + " KEY(2): world:", + " RAW(4): message", + " RAW(-1): " + ); + + } + + } + + @Test public void testLeadingDocumentSeparator() throws Exception { + MockYamlEditor editor = new MockYamlEditor( + "---\n" + + "hello:\n"+ + " world:\n" + + " message\n" + ); + + assertParseOneDoc(editor, + "DOC(0): ---", + " KEY(0): hello:", + " KEY(2): world:", + " RAW(4): message", + " RAW(-1): " + ); + } + @Test public void testSimple() throws Exception { MockYamlEditor editor = new MockYamlEditor( "hello:\n"+ @@ -76,7 +121,7 @@ public class YamlStructureParserTest { ); assertParseOneDoc(editor, "DOC(0): ", - " RAW(-1): #A comment", +// " RAW(-1): #A comment", " KEY(0): hello:", " RAW(-1): #Another comment", " KEY(2): world:", diff --git a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index 03e8bced7..fd140c85c 100644 --- a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -227,11 +227,7 @@ public class Editor { : ""; } - private List reconcile() throws Exception { - // We assume the language server works synchronously for now and it does an immediate reconcile - // when the document changes. In the future this is probably not going to be the case though and then this - // method will need to somehow ensure the linter is done working before retrieving the problems from the - // test harness. + public List reconcile() throws Exception { PublishDiagnosticsParams diagnostics = harness.getDiagnostics(document); if (diagnostics!=null) { return diagnostics.getDiagnostics(); diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java index e7b13212c..cebadc223 100644 --- a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java @@ -2429,15 +2429,64 @@ public class ConcourseEditorTest { editor.assertHoverContains("path", 2, "The path to a directory where the output will be taken from"); } + @Test public void PT_140711495_triple_dash_at_start_of_file_disrupts_content_assist() throws Exception { + assertContextualCompletions( + "#leading comment\n" + + "---\n" + + "resources:\n" + + "- name: my-repo\n" + + " type: git\n" + + " source:\n" + + " uri: https://github.com/spring-projects/sts4.git\n" + + " <*>" + , // ================== + "bra<*>" + , // ==> + "branch: <*>" + ); + + assertContextualCompletions( + "---\n" + + "resources:\n" + + "- name: my-repo\n" + + " type: git\n" + + " source:\n" + + " uri: https://github.com/spring-projects/sts4.git\n" + + " <*>" + , // ================== + "bra<*>" + , // ==> + "branch: <*>" + ); + + assertContextualCompletions( +// "---\n" + + "resources:\n" + + "- name: my-repo\n" + + " type: git\n" + + " source:\n" + + " uri: https://github.com/spring-projects/sts4.git\n" + + " <*>" + , // ================== + "bra<*>" + , // ==> + "branch: <*>" + ); + } + ////////////////////////////////////////////////////////////////////////////// private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception { + Editor editor = harness.newEditor(conText); + editor.reconcile(); //this ensures the conText is parsed and its AST is cached (will be used for + //dynamic CA when the conText + textBefore is not parsable. assertContains(CURSOR, conText); textBefore = conText.replace(CURSOR, textBefore); textAfter = Arrays.stream(textAfter) .map((String t) -> conText.replace(CURSOR, t)) .collect(Collectors.toList()).toArray(new String[0]); - assertCompletions(textBefore, textAfter); + editor.setText(textBefore); + editor.assertCompletions(textAfter); } private void assertCompletions(String textBefore, String... textAfter) throws Exception {