/******************************************************************************* * Copyright (c) 2016 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 * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.editor.support.yaml; import java.util.Collection; import java.util.Collections; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.springframework.ide.eclipse.editor.support.EditorSupportActivator; import org.springframework.ide.eclipse.editor.support.completions.CompletionFactory; import org.springframework.ide.eclipse.editor.support.completions.ICompletionEngine; import org.springframework.ide.eclipse.editor.support.util.YamlIndentUtil; import org.springframework.ide.eclipse.editor.support.yaml.completions.YamlAssistContext; import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath; import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SKeyNode; import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode; import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNodeType; import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SRootNode; import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SSeqNode; import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureProvider; /** * Abstract superclass to make it easier to define {@link ICompletionEngine} implementation * for .yml file. * * @author Kris De Volder */ public class YamlCompletionEngine implements ICompletionEngine { private final YamlAssistContextProvider contextProvider; protected final YamlStructureProvider structureProvider; public YamlCompletionEngine(YamlStructureProvider structureProvider, YamlAssistContextProvider contextProvider) { Assert.isNotNull(structureProvider); Assert.isNotNull(contextProvider); this.structureProvider= structureProvider; this.contextProvider = contextProvider; } protected final YamlAssistContext getGlobalContext(YamlDocument doc) { return contextProvider.getGlobalAssistContext(doc); } protected CompletionFactory proposalFactory() { return CompletionFactory.DEFAULT; } @Override public Collection<ICompletionProposal> getCompletions(IDocument _doc, int offset) throws Exception { YamlDocument doc = new YamlDocument(_doc, structureProvider); if (!doc.isCommented(offset)) { SRootNode root = doc.getStructure(); SNode current = root.find(offset); YamlPath contextPath = getContextPath(doc, current, offset); YamlAssistContext context = getContext(doc, offset, current, contextPath); if (context==null && isDubiousKey(current, offset)) { current = current.getParent(); contextPath = contextPath.dropLast(); context = getContext(doc, offset, current, contextPath); } if (context!=null) { return context.getCompletions(doc, current, offset); } } return Collections.emptyList(); } /** * A 'dubious' key is when the cursor is positioned right after a key's ':' character. * This key is 'dubious' in that if the user would type a non-whitespace character next... * then that key is no longer a key but parses as a 'value' instead. */ private boolean isDubiousKey(SNode node, int offset) { if (node.getNodeType()==SNodeType.KEY) { SKeyNode key = (SKeyNode)node; return key.getColonOffset()+1==offset; } return false; } protected YamlAssistContext getContext(YamlDocument doc, int offset, SNode node, YamlPath contextPath) { try { return contextPath.traverse(getGlobalContext(doc)); } catch (Exception e) { EditorSupportActivator.log(e); return null; } } protected YamlPath getContextPath(YamlDocument doc, SNode node, int offset) throws Exception { if (node==null) { return YamlPath.EMPTY; } 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.getPath(); } else { return keyNode.getParent().getPath(); } } else if (node.getNodeType()==SNodeType.RAW) { //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 structur-tree to determine the 'context' node. int cursorIndent = doc.getColumn(offset); int nodeIndent = node.getIndent(); int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent); while (node.getIndent()==-1 || (node.getIndent()>=currentIndent && node.getNodeType()!=SNodeType.DOC)) { node = node.getParent(); } return node.getPath(); } else if (node.getNodeType()==SNodeType.SEQ) { SSeqNode seqNode = (SSeqNode)node; if (seqNode.isInValue(offset)) { return seqNode.getPath(); } else { return seqNode.getParent().getPath(); } } else if (node.getNodeType()==SNodeType.DOC) { return node.getPath(); } else { throw new IllegalStateException("Missing case"); } } }