Relaxed indentation context
This commit is contained in:
@@ -404,4 +404,15 @@ public class DocumentEdits implements ProposalApplier {
|
||||
insert(start, newText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds extra indentation at the position of the first edit in this {@link DocumentEdits}
|
||||
*/
|
||||
public void indentFirstEdit(String indentString) {
|
||||
if (edits.size()>0) {
|
||||
Edit firstEdit = edits.get(0);
|
||||
int offset = firstEdit.getStart();
|
||||
edits.add(0, new Insertion(offset, indentString));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,14 +10,20 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.completion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
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.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
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;
|
||||
@@ -65,16 +71,116 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
||||
SRootNode root = doc.getStructure();
|
||||
SNode current = root.find(offset);
|
||||
SNode contextNode = getContextNode(doc, current, offset);
|
||||
if (contextNode!=null) {
|
||||
YamlAssistContext context = getContext(doc, contextNode);
|
||||
if (context==null && isDubiousKey(contextNode, offset)) {
|
||||
current = current.getParent();
|
||||
contextNode = contextNode.getParent();
|
||||
context = getContext(doc, contextNode);
|
||||
List<ICompletionProposal> all = new ArrayList<>(getPreciseCompletions(offset, doc, current, contextNode));
|
||||
all.addAll(addIndentations(getRelaxedCompletions(offset, doc, contextNode, current)));
|
||||
return all;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Collection<? extends ICompletionProposal> addIndentations(
|
||||
Collection<? extends ICompletionProposal> completions) {
|
||||
if (!completions.isEmpty()) {
|
||||
List<ICompletionProposal> transformed = new ArrayList<>();
|
||||
for (ICompletionProposal p : completions) {
|
||||
transformed.add(indented(p));
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public ICompletionProposal indented(ICompletionProposal proposal) {
|
||||
ScoreableProposal transformed = new ScoreableProposal() {
|
||||
|
||||
DocumentEdits indentedEdit = null;
|
||||
|
||||
@Override
|
||||
public synchronized DocumentEdits getTextEdit() {
|
||||
if (indentedEdit==null) {
|
||||
indentedEdit = proposal.getTextEdit();
|
||||
indentedEdit.indentFirstEdit(YamlIndentUtil.INDENT_STR);
|
||||
}
|
||||
if (context!=null) {
|
||||
return context.getCompletions(doc, current, offset);
|
||||
return indentedEdit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return "➔ "+proposal.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return proposal.getKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return proposal.getDocumentation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return proposal.getDetail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getBaseScore() {
|
||||
if (proposal instanceof ScoreableProposal) {
|
||||
return ((ScoreableProposal) proposal).getBaseScore();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
transformed.deemphasize();
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private Collection<? extends ICompletionProposal> getRelaxedCompletions(int offset, YamlDocument doc, SNode preciseContextNode, SNode currentNode) throws Exception {
|
||||
if (preciseContextNode!=null) {
|
||||
SNode contextNode = getRelaxedContextNode(preciseContextNode, currentNode);
|
||||
YamlAssistContext context = getContext(doc, contextNode);
|
||||
if (context!=null) {
|
||||
return context.getCompletions(doc, currentNode, offset);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean isBarrenKey(SNode node) throws Exception {
|
||||
if (node.getNodeType()==SNodeType.KEY) {
|
||||
SKeyNode keyNode = (SKeyNode) node;
|
||||
String value = keyNode.getSimpleValue();
|
||||
return value.trim().isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private SNode getRelaxedContextNode(SNode preciseContextNode, SNode currentNode) throws Exception {
|
||||
while (currentNode!=null) {
|
||||
if (currentNode.getParent()==preciseContextNode) {
|
||||
if (isBarrenKey(currentNode)) {
|
||||
return currentNode;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
currentNode = currentNode.getParent();
|
||||
}
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
protected Collection<ICompletionProposal> getPreciseCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode)
|
||||
throws Exception {
|
||||
if (contextNode!=null) {
|
||||
YamlAssistContext context = getContext(doc, contextNode);
|
||||
if (context==null && isDubiousKey(contextNode, offset)) {
|
||||
current = current.getParent();
|
||||
contextNode = contextNode.getParent();
|
||||
context = getContext(doc, contextNode);
|
||||
}
|
||||
if (context!=null) {
|
||||
return context.getCompletions(doc, current, offset);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -27,7 +27,7 @@ public abstract class YamlPathSegment {
|
||||
}
|
||||
|
||||
public static class AnyChild extends YamlPathSegment {
|
||||
|
||||
|
||||
private static AnyChild INSTANCE = new AnyChild();
|
||||
|
||||
private AnyChild() {}
|
||||
@@ -61,10 +61,12 @@ public abstract class YamlPathSegment {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toNavString() {
|
||||
return "["+index+"]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toPropString() {
|
||||
return "["+index+"]";
|
||||
}
|
||||
@@ -175,6 +177,7 @@ public abstract class YamlPathSegment {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toNavString();
|
||||
}
|
||||
@@ -194,7 +197,7 @@ public abstract class YamlPathSegment {
|
||||
public static YamlPathSegment keyAt(String key) {
|
||||
return new KeyAtKey(key);
|
||||
}
|
||||
|
||||
|
||||
public static YamlPathSegment anyChild() {
|
||||
return AnyChild.INSTANCE;
|
||||
}
|
||||
|
||||
@@ -757,6 +757,10 @@ public class YamlStructureParser {
|
||||
return doc.textBetween(getStart(), getColonOffset());
|
||||
}
|
||||
|
||||
public String getSimpleValue() {
|
||||
return doc.textBetween(getColonOffset()+1, getNodeEnd());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the offset of the ':' character that separates the 'key' from the 'value' area.
|
||||
* @return Absolute offset (from beginning of document).
|
||||
@@ -789,6 +793,7 @@ public class YamlStructureParser {
|
||||
return indentedText;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Iterable<String> getKeyAliases(String key) {
|
||||
|
||||
@@ -518,6 +518,14 @@ public class Editor {
|
||||
.get();
|
||||
}
|
||||
|
||||
public void assertCompletionWithLabel(String expectLabel, String expectedResult) throws Exception {
|
||||
CompletionItem completion = assertCompletionWithLabel(expectLabel);
|
||||
String saveText = getText();
|
||||
apply(completion);
|
||||
assertEquals(expectedResult, getText());
|
||||
setText(saveText);
|
||||
}
|
||||
|
||||
|
||||
public void setSelection(int start, int end) {
|
||||
Assert.assertTrue(start>=0);
|
||||
@@ -618,4 +626,5 @@ public class Editor {
|
||||
public String getLanguageId() {
|
||||
return languageId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import com.google.common.collect.Multiset;
|
||||
* and completion engine).
|
||||
*/
|
||||
public class ConcourseModel {
|
||||
|
||||
public static final YamlPath JOB_NAMES_PATH = new YamlPath(
|
||||
anyChild(),
|
||||
valueAt("jobs"),
|
||||
|
||||
@@ -2618,7 +2618,7 @@ public class ConcourseEditorTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore @Test public void relaxedIndentContextMoreSpaces() throws Exception {
|
||||
@Test public void relaxedIndentContextMoreSpaces() throws Exception {
|
||||
Editor editor;
|
||||
|
||||
editor = harness.newEditor(
|
||||
@@ -2635,20 +2635,72 @@ public class ConcourseEditorTest {
|
||||
"source",
|
||||
"type",
|
||||
//For the nested context:
|
||||
"branch",
|
||||
"commit_verification_key_ids",
|
||||
"commit_verification_keys",
|
||||
"disable_ci_skip",
|
||||
"git_config",
|
||||
"gpg_keyserver",
|
||||
"ignore_paths",
|
||||
"password",
|
||||
"paths",
|
||||
"private_key",
|
||||
"skip_ssl_verification",
|
||||
"tag_filter",
|
||||
"uri",
|
||||
"username"
|
||||
"➔ branch",
|
||||
"➔ commit_verification_key_ids",
|
||||
"➔ commit_verification_keys",
|
||||
"➔ disable_ci_skip",
|
||||
"➔ git_config",
|
||||
"➔ gpg_keyserver",
|
||||
"➔ ignore_paths",
|
||||
"➔ password",
|
||||
"➔ paths",
|
||||
"➔ private_key",
|
||||
"➔ skip_ssl_verification",
|
||||
"➔ tag_filter",
|
||||
"➔ uri",
|
||||
"➔ username"
|
||||
);
|
||||
|
||||
editor.assertCompletionWithLabel("check_every",
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" check_every: <*>"
|
||||
);
|
||||
|
||||
editor.assertCompletionWithLabel("➔ branch",
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" branch: <*>"
|
||||
);
|
||||
editor.assertCompletionWithLabel("➔ commit_verification_key_ids",
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" commit_verification_key_ids:\n" +
|
||||
" - <*>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void relaxedIndentContextMoreSpaces2() throws Exception {
|
||||
|
||||
assertContextualCompletions(
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" <*>"
|
||||
, // =========
|
||||
"bra<*>"
|
||||
, //=>
|
||||
" branch: <*>"
|
||||
);
|
||||
|
||||
assertContextualCompletions(
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" type: git\n" +
|
||||
" source:\n" +
|
||||
" <*>"
|
||||
, // =========
|
||||
"comverids<*>"
|
||||
, //=>
|
||||
" commit_verification_key_ids:\n" +
|
||||
" - <*>"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user