Relaxed CA with less indentation

This commit is contained in:
Kris De Volder
2017-05-02 19:15:33 -07:00
parent b53febf31e
commit f5fc8d0601
8 changed files with 144 additions and 44 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String> labelPredicate) throws Exception {
List<String> 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();

View File

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