|
|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
* Copyright (c) 2016 Pivotal, Inc.
|
|
|
|
|
* Copyright (c) 2016-2017 Pivotal, Inc.
|
|
|
|
|
* All rights reserved. This program and the accompanying materials
|
|
|
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
|
|
|
* which accompanies this distribution, and is available at
|
|
|
|
|
@@ -16,6 +16,7 @@ import java.util.ArrayList;
|
|
|
|
|
import java.util.Collection;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
@@ -29,6 +30,7 @@ import org.springframework.ide.vscode.commons.util.Unicodes;
|
|
|
|
|
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;
|
|
|
|
|
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode;
|
|
|
|
|
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SKeyNode;
|
|
|
|
|
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
|
|
|
|
|
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNodeType;
|
|
|
|
|
@@ -37,9 +39,12 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser
|
|
|
|
|
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
|
|
|
|
|
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
|
|
|
|
|
|
|
|
|
|
import com.google.common.base.Strings;
|
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Implements {@link ICompletionEngine} for .yml file, based on a YamlAssistContextProvider
|
|
|
|
|
* which has to to be injected into engine via its contructor.
|
|
|
|
|
* which has to to be injected into engine via its constructor.
|
|
|
|
|
*
|
|
|
|
|
* @author Kris De Volder
|
|
|
|
|
*/
|
|
|
|
|
@@ -72,65 +77,104 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
|
|
|
|
if (!doc.isCommented(offset)) {
|
|
|
|
|
SRootNode root = doc.getStructure();
|
|
|
|
|
SNode current = root.find(offset);
|
|
|
|
|
SNode contextNode = getContextNode(doc, current, offset);
|
|
|
|
|
List<ICompletionProposal> all = new ArrayList<>(getBaseCompletions(offset, doc, current, contextNode, false));
|
|
|
|
|
all.addAll(getMoreIndentedCompletions(offset, doc, contextNode, current));
|
|
|
|
|
all.addAll(getDashedCompletions(offset, doc, contextNode, current));
|
|
|
|
|
return all;
|
|
|
|
|
List<SNode> contextNodes = getContextNodes(doc, current, offset);
|
|
|
|
|
if (current.getNodeType()==SNodeType.RAW) {
|
|
|
|
|
//relaxed indentation
|
|
|
|
|
List<ICompletionProposal> completions = new ArrayList<>();
|
|
|
|
|
int cursorIndent = doc.getColumn(offset);
|
|
|
|
|
int nodeIndent = current.getIndent();
|
|
|
|
|
int baseIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
|
|
|
|
|
for (SNode contextNode : contextNodes) {
|
|
|
|
|
completions.addAll(getRelaxedCompletions(offset, doc, current, contextNode, baseIndent));
|
|
|
|
|
}
|
|
|
|
|
return completions;
|
|
|
|
|
} else {
|
|
|
|
|
//precise indentation only
|
|
|
|
|
Assert.isLegal(contextNodes.size()<=1);
|
|
|
|
|
for (SNode contextNode : contextNodes) {
|
|
|
|
|
return getBaseCompletions(offset, doc, current, contextNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Collection<ICompletionProposal> addIndentations(
|
|
|
|
|
Collection<ICompletionProposal> completions) {
|
|
|
|
|
protected Collection<? extends ICompletionProposal> getRelaxedCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode, int baseIndent) {
|
|
|
|
|
try {
|
|
|
|
|
return fixIndentations(getBaseCompletions(offset, doc, current, contextNode),
|
|
|
|
|
current, contextNode, baseIndent);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.log(e);
|
|
|
|
|
}
|
|
|
|
|
return ImmutableList.of();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected Collection<? extends ICompletionProposal> fixIndentations(Collection<ICompletionProposal> completions, SNode currentNode, SNode contextNode, int baseIndent) {
|
|
|
|
|
if (!completions.isEmpty()) {
|
|
|
|
|
int dashyIndent = getTargetIndent(contextNode, currentNode, true);
|
|
|
|
|
int plainIndent = getTargetIndent(contextNode, currentNode, false);
|
|
|
|
|
List<ICompletionProposal> transformed = new ArrayList<>();
|
|
|
|
|
for (ICompletionProposal p : completions) {
|
|
|
|
|
transformed.add(indented(p));
|
|
|
|
|
ICompletionProposal p_fixed = null;
|
|
|
|
|
if (p.getLabel().startsWith("- ")) {
|
|
|
|
|
p_fixed = indentFix(p, dashyIndent - baseIndent, contextNode);
|
|
|
|
|
} else {
|
|
|
|
|
p_fixed = indentFix(p, plainIndent - baseIndent, contextNode);
|
|
|
|
|
}
|
|
|
|
|
if (p_fixed!=null) {
|
|
|
|
|
transformed.add(p_fixed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return transformed;
|
|
|
|
|
}
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ICompletionProposal indented(ICompletionProposal proposal) {
|
|
|
|
|
protected ICompletionProposal indentFix(ICompletionProposal p, int fixIndentBy, SNode contextNode) {
|
|
|
|
|
if (fixIndentBy==0) {
|
|
|
|
|
return p;
|
|
|
|
|
} else if (fixIndentBy>0 && isExtraIndentRelaxable(contextNode)) {
|
|
|
|
|
return indented(p, Strings.repeat(" ", fixIndentBy));
|
|
|
|
|
} else { // fixIndentBy < 0
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine the indentation level needed to line up with other contextNode children.
|
|
|
|
|
* If the contextNode has no children, then compute a proper default indentation where
|
|
|
|
|
* a new child could be added.
|
|
|
|
|
*/
|
|
|
|
|
private int getTargetIndent(SNode contextNode, SNode currentNode, boolean dashy) {
|
|
|
|
|
Optional<SNode> child = Optional.empty();
|
|
|
|
|
if (contextNode instanceof SChildBearingNode) {
|
|
|
|
|
child = ((SChildBearingNode)contextNode).getChildren().stream()
|
|
|
|
|
.filter(c -> c!=currentNode && c.getIndent()>=0)
|
|
|
|
|
.max((c1, c2) -> Integer.compare(c1.getIndent(), c2.getIndent()));
|
|
|
|
|
}
|
|
|
|
|
if (child.isPresent()) {
|
|
|
|
|
return child.get().getIndent();
|
|
|
|
|
}
|
|
|
|
|
return (dashy || contextNode.getNodeType()==SNodeType.DOC)
|
|
|
|
|
? contextNode.getIndent()
|
|
|
|
|
: contextNode.getIndent() + YamlIndentUtil.INDENT_BY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ICompletionProposal indented(ICompletionProposal proposal, String indentStr) {
|
|
|
|
|
ScoreableProposal transformed = new TransformedCompletion(proposal) {
|
|
|
|
|
@Override public String tranformLabel(String originalLabel) {
|
|
|
|
|
return Unicodes.RIGHT_ARROW+" " + originalLabel;
|
|
|
|
|
}
|
|
|
|
|
@Override public DocumentEdits transformEdit(DocumentEdits originalEdit) {
|
|
|
|
|
originalEdit.indentFirstEdit(YamlIndentUtil.INDENT_STR);
|
|
|
|
|
originalEdit.indentFirstEdit(indentStr);
|
|
|
|
|
return originalEdit;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
transformed.deemphasize(DEEMP_INDENTED_PROPOSAL);
|
|
|
|
|
transformed.deemphasize(DEEMP_INDENTED_PROPOSAL*indentStr.length()/2);
|
|
|
|
|
return transformed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Collection<? extends ICompletionProposal> getDashedCompletions(
|
|
|
|
|
int offset, YamlDocument doc,
|
|
|
|
|
SNode preciseContextNode, SNode currentNode
|
|
|
|
|
) throws Exception {
|
|
|
|
|
SNode contextNode = getContextNode(doc, currentNode, offset, "- ");
|
|
|
|
|
if (preciseContextNode!=contextNode) {
|
|
|
|
|
return getBaseCompletions(offset, doc, currentNode, contextNode, true);
|
|
|
|
|
}
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Collection<ICompletionProposal> getMoreIndentedCompletions(int offset, YamlDocument doc, SNode preciseContextNode, SNode currentNode) throws Exception {
|
|
|
|
|
SNode contextNode = getContextNode(doc, currentNode, offset, YamlIndentUtil.INDENT_STR);
|
|
|
|
|
if (preciseContextNode!=contextNode && isIndentRelaxable(contextNode)) {
|
|
|
|
|
YamlAssistContext context = getContext(doc, contextNode);
|
|
|
|
|
if (context!=null) {
|
|
|
|
|
return addIndentations(context.getCompletions(doc, currentNode, offset));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isIndentRelaxable(SNode contextNode) throws Exception {
|
|
|
|
|
private boolean isExtraIndentRelaxable(SNode contextNode) {
|
|
|
|
|
return contextNode!=null && (
|
|
|
|
|
isBarrenKey(contextNode) ||
|
|
|
|
|
isBarrenSeq(contextNode)
|
|
|
|
|
@@ -150,16 +194,20 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isBarrenKey(SNode node) throws Exception {
|
|
|
|
|
if (node.getNodeType()==SNodeType.KEY) {
|
|
|
|
|
SKeyNode keyNode = (SKeyNode) node;
|
|
|
|
|
String value = keyNode.getSimpleValue();
|
|
|
|
|
return value.trim().isEmpty();
|
|
|
|
|
private boolean isBarrenKey(SNode node) {
|
|
|
|
|
try {
|
|
|
|
|
if (node.getNodeType()==SNodeType.KEY) {
|
|
|
|
|
SKeyNode keyNode = (SKeyNode) node;
|
|
|
|
|
String value = keyNode.getSimpleValue();
|
|
|
|
|
return value.trim().isEmpty();
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.log(e);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Collection<? extends ICompletionProposal> getBaseCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode, boolean onlyDashes) throws Exception {
|
|
|
|
|
private Collection<ICompletionProposal> getBaseCompletions(int offset, YamlDocument doc, SNode current, SNode contextNode) throws Exception {
|
|
|
|
|
if (contextNode!=null) {
|
|
|
|
|
YamlAssistContext context = getContext(doc, contextNode);
|
|
|
|
|
if (context==null && isDubiousKey(contextNode, offset)) {
|
|
|
|
|
@@ -168,13 +216,7 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
|
|
|
|
context = getContext(doc, contextNode);
|
|
|
|
|
}
|
|
|
|
|
if (context!=null) {
|
|
|
|
|
Collection<ICompletionProposal> all = new ArrayList<>();
|
|
|
|
|
if (onlyDashes) {
|
|
|
|
|
all.addAll(context.getDashedCompletions(doc, current, offset));
|
|
|
|
|
} else {
|
|
|
|
|
all.addAll(context.getCompletions(doc, current, offset));
|
|
|
|
|
}
|
|
|
|
|
return all;
|
|
|
|
|
return context.getCompletions(doc, current, offset);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
@@ -204,8 +246,15 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected SNode getContextNode(YamlDocument doc, SNode node, int offset, String adjustIndentStr) throws Exception {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get context node candidates taking into account that we want to have a 'relaxed' interpretation
|
|
|
|
|
* of the context node with respect to the current indentation where we ask for a completion.
|
|
|
|
|
* To allow for the ambiguity in indentation a list of context nodes is returned instead of a
|
|
|
|
|
* single node. (Note we may still return a singleton list for cases where relaxed indentation
|
|
|
|
|
* doesn't seem desirable).
|
|
|
|
|
*/
|
|
|
|
|
protected List<SNode> getContextNodes(YamlDocument doc, SNode node, int offset) {
|
|
|
|
|
if (node==null) {
|
|
|
|
|
return null;
|
|
|
|
|
} else if (node.getNodeType()==SNodeType.KEY) {
|
|
|
|
|
@@ -213,62 +262,98 @@ public class YamlCompletionEngine implements ICompletionEngine {
|
|
|
|
|
// contexts for content assistance
|
|
|
|
|
SKeyNode keyNode = (SKeyNode)node;
|
|
|
|
|
if (keyNode.isInValue(offset)) {
|
|
|
|
|
return keyNode;
|
|
|
|
|
return ImmutableList.of(keyNode);
|
|
|
|
|
} else {
|
|
|
|
|
return keyNode.getParent();
|
|
|
|
|
}
|
|
|
|
|
} else if (node.getNodeType()==SNodeType.RAW) {
|
|
|
|
|
if (adjustIndentStr.startsWith("- ")) {
|
|
|
|
|
// We are trying to determine context node for a completion that starts with a '- '.
|
|
|
|
|
// Yaml indentation rules means we have to treat this differently because '-' doesn't
|
|
|
|
|
// have to be indented to be considered as nested under a key node!
|
|
|
|
|
int cursorIndent = doc.getColumn(offset);
|
|
|
|
|
int nodeIndent = node.getIndent();
|
|
|
|
|
int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
|
|
|
|
|
while (node.getNodeType()!=SNodeType.DOC && (
|
|
|
|
|
nodeIndent==-1 ||
|
|
|
|
|
nodeIndent>currentIndent ||
|
|
|
|
|
nodeIndent==currentIndent && (node.getNodeType()==SNodeType.SEQ || node.getNodeType() == SNodeType.RAW)
|
|
|
|
|
)) {
|
|
|
|
|
node = node.getParent();
|
|
|
|
|
nodeIndent = node.getIndent();
|
|
|
|
|
}
|
|
|
|
|
return node;
|
|
|
|
|
} else {
|
|
|
|
|
//Treat raw node as a 'key node'. This is basically assuming that is misclasified
|
|
|
|
|
// by structure parser because the ':' was not yet typed into the document.
|
|
|
|
|
|
|
|
|
|
//Complication: if line with cursor is empty or the cursor is inside the indentation
|
|
|
|
|
// area then the structure may not reflect correctly the context. This is because
|
|
|
|
|
// the correct context depends on text the user has not typed yet.(which will change the
|
|
|
|
|
// indentation level of the current line. So we must use the cursorIndentation
|
|
|
|
|
// rather than the structure-tree to determine the 'context' node.
|
|
|
|
|
int adjustIndent = adjustIndentStr==null? 0 : adjustIndentStr.length();
|
|
|
|
|
int cursorIndent = YamlIndentUtil.add(doc.getColumn(offset), adjustIndent);
|
|
|
|
|
int nodeIndent = YamlIndentUtil.add(node.getIndent(), adjustIndent);
|
|
|
|
|
int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
|
|
|
|
|
while (nodeIndent==-1 || (nodeIndent>=currentIndent && node.getNodeType()!=SNodeType.DOC)) {
|
|
|
|
|
node = node.getParent();
|
|
|
|
|
nodeIndent = node.getIndent();
|
|
|
|
|
}
|
|
|
|
|
return node;
|
|
|
|
|
return ImmutableList.of(keyNode.getParent());
|
|
|
|
|
}
|
|
|
|
|
} else if (node.getNodeType()==SNodeType.SEQ) {
|
|
|
|
|
SSeqNode seqNode = (SSeqNode)node;
|
|
|
|
|
if (seqNode.isInValue(offset)) {
|
|
|
|
|
return seqNode;
|
|
|
|
|
return ImmutableList.of(seqNode);
|
|
|
|
|
} else {
|
|
|
|
|
return seqNode.getParent();
|
|
|
|
|
return ImmutableList.of(seqNode.getParent());
|
|
|
|
|
}
|
|
|
|
|
} else if (node.getNodeType()==SNodeType.DOC) {
|
|
|
|
|
return node;
|
|
|
|
|
return ImmutableList.of(node);
|
|
|
|
|
} else if (node.getNodeType()==SNodeType.RAW) {
|
|
|
|
|
//This node has flexibility around indentation. So this is where me need to build a list of candidates!
|
|
|
|
|
ImmutableList.Builder<SNode> contextNodes = ImmutableList.builder();
|
|
|
|
|
while (node!=null ) {
|
|
|
|
|
//Any node that represents a 'step' between contexts must be kept.
|
|
|
|
|
if (node.getSegment()!=null) {
|
|
|
|
|
contextNodes.add(node);
|
|
|
|
|
}
|
|
|
|
|
node = node.getParent();
|
|
|
|
|
}
|
|
|
|
|
return contextNodes.build();
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
return ImmutableList.of();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected SNode getContextNode(YamlDocument doc, SNode node, int offset) throws Exception {
|
|
|
|
|
return getContextNode(doc, node, offset, "");
|
|
|
|
|
}
|
|
|
|
|
// protected SNode getContextNode(YamlDocument doc, SNode node, int offset, String adjustIndentStr) throws Exception {
|
|
|
|
|
// if (node==null) {
|
|
|
|
|
// return null;
|
|
|
|
|
// } else if (node.getNodeType()==SNodeType.KEY) {
|
|
|
|
|
// //slight complication. The area in the key and value of a key node represent different
|
|
|
|
|
// // contexts for content assistance
|
|
|
|
|
// SKeyNode keyNode = (SKeyNode)node;
|
|
|
|
|
// if (keyNode.isInValue(offset)) {
|
|
|
|
|
// return keyNode;
|
|
|
|
|
// } else {
|
|
|
|
|
// return keyNode.getParent();
|
|
|
|
|
// }
|
|
|
|
|
// } else if (node.getNodeType()==SNodeType.RAW) {
|
|
|
|
|
// if (adjustIndentStr.startsWith("- ")) {
|
|
|
|
|
// // We are trying to determine context node for a completion that starts with a '- '.
|
|
|
|
|
// // Yaml indentation rules means we have to treat this differently because '-' doesn't
|
|
|
|
|
// // have to be indented to be considered as nested under a key node!
|
|
|
|
|
// int cursorIndent = doc.getColumn(offset);
|
|
|
|
|
// int nodeIndent = node.getIndent();
|
|
|
|
|
// int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
|
|
|
|
|
// while (node.getNodeType()!=SNodeType.DOC && (
|
|
|
|
|
// nodeIndent==-1 ||
|
|
|
|
|
// nodeIndent>currentIndent ||
|
|
|
|
|
// nodeIndent==currentIndent && (node.getNodeType()==SNodeType.SEQ || node.getNodeType() == SNodeType.RAW)
|
|
|
|
|
// )) {
|
|
|
|
|
// node = node.getParent();
|
|
|
|
|
// nodeIndent = node.getIndent();
|
|
|
|
|
// }
|
|
|
|
|
// return node;
|
|
|
|
|
// } else {
|
|
|
|
|
// //Treat raw node as a 'key node'. This is basically assuming that is misclasified
|
|
|
|
|
// // by structure parser because the ':' was not yet typed into the document.
|
|
|
|
|
//
|
|
|
|
|
// //Complication: if line with cursor is empty or the cursor is inside the indentation
|
|
|
|
|
// // area then the structure may not reflect correctly the context. This is because
|
|
|
|
|
// // the correct context depends on text the user has not typed yet.(which will change the
|
|
|
|
|
// // indentation level of the current line. So we must use the cursorIndentation
|
|
|
|
|
// // rather than the structure-tree to determine the 'context' node.
|
|
|
|
|
// int adjustIndent = adjustIndentStr==null? 0 : adjustIndentStr.length();
|
|
|
|
|
// int cursorIndent = YamlIndentUtil.add(doc.getColumn(offset), adjustIndent);
|
|
|
|
|
// int nodeIndent = YamlIndentUtil.add(node.getIndent(), adjustIndent);
|
|
|
|
|
// int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
|
|
|
|
|
// while (nodeIndent==-1 || (nodeIndent>=currentIndent && node.getNodeType()!=SNodeType.DOC)) {
|
|
|
|
|
// node = node.getParent();
|
|
|
|
|
// nodeIndent = node.getIndent();
|
|
|
|
|
// }
|
|
|
|
|
// return node;
|
|
|
|
|
// }
|
|
|
|
|
// } else if (node.getNodeType()==SNodeType.SEQ) {
|
|
|
|
|
// SSeqNode seqNode = (SSeqNode)node;
|
|
|
|
|
// if (seqNode.isInValue(offset)) {
|
|
|
|
|
// return seqNode;
|
|
|
|
|
// } else {
|
|
|
|
|
// return seqNode.getParent();
|
|
|
|
|
// }
|
|
|
|
|
// } else if (node.getNodeType()==SNodeType.DOC) {
|
|
|
|
|
// return node;
|
|
|
|
|
// }
|
|
|
|
|
// return null;
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// protected SNode getContextNode(YamlDocument doc, SNode node, int offset) throws Exception {
|
|
|
|
|
// return getContextNode(doc, node, offset, "");
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
protected YamlPath getContextPath(YamlDocument doc, SNode node, int offset) throws Exception {
|
|
|
|
|
if (node==null) {
|
|
|
|
|
|