Relaxed indentation context

This commit is contained in:
Kris De Volder
2017-04-04 18:02:32 -07:00
parent 77100d3893
commit d0fd620fec
7 changed files with 211 additions and 26 deletions

View File

@@ -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));
}
}
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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"),

View File

@@ -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" +
" - <*>"
);
}