/******************************************************************************* * Copyright (c) 2015 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.completions; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.IRegion; import org.springframework.ide.eclipse.editor.support.completions.DocumentEdits; import org.springframework.ide.eclipse.editor.support.util.YamlIndentUtil; import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument; import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath; import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment; import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment.YamlPathSegmentType; import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SChildBearingNode; 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; /** * Helper class that provides methods for creating the edits in a YamlDocument that * insert new 'property paths' into the document. * * @author Kris De Volder */ public class YamlPathEdits extends DocumentEdits { private YamlDocument doc; private YamlIndentUtil indentUtil; public YamlPathEdits(YamlDocument doc) { super(doc.getDocument()); this.doc = doc; this.indentUtil = new YamlIndentUtil(doc); } /** * Create the necessary edits to ensure that a given property * path exists, placing cursor in the right place also to start * start typing the property value. * <p> * This also handles cases where all or some of the path already * exists. In the former case no edits are performed only cursor * movement. In the latter case, the right place to start inserting * the 'missing' portion of the path is found and the edits * are created there. */ public void createPath(SChildBearingNode node, YamlPath path, String appendText) throws Exception { //This code doesn't handle selection of subddocuments // or creation of new subdocuments so must not call it on //ROOT node but start at an appropriate SDocNode (or below) Assert.isLegal(node.getNodeType()!=SNodeType.ROOT); if (!path.isEmpty()) { YamlPathSegment s = path.getSegment(0); if (s.getType()==YamlPathSegmentType.VAL_AT_KEY) { String key = s.toPropString(); SKeyNode existing = node.getChildWithKey(key); if (existing==null) { createNewPath(node, path, appendText); } else { createPath(existing, path.tail(), appendText); } } } else { //whole path already exists. Just try to move cursor somewhere // sensible in the existing tail-end-node of the path. SNode child = node.getFirstRealChild(); if (child!=null) { moveCursorTo(child.getStart()); } else if (node.getNodeType()==SNodeType.KEY) { SKeyNode keyNode = (SKeyNode) node; int colonOffset = keyNode.getColonOffset(); char c = doc.getChar(colonOffset+1); if (c==' ') { moveCursorTo(colonOffset+2); //cursor after the ": " } else { moveCursorTo(colonOffset+1); //cursor after the ":" } } } } private void createNewPath(SChildBearingNode parent, YamlPath path, String appendText) throws Exception { int indent = getChildIndent(parent); int insertionPoint = getNewPathInsertionOffset(parent); boolean startOnNewLine = true; insert(insertionPoint, createPathInsertionText(path, indent, startOnNewLine, appendText)); } protected String createPathInsertionText(YamlPath path, int indent, boolean startOnNewLine, String appendText) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < path.size(); i++) { if (startOnNewLine||i>0) { indentUtil.addNewlineWithIndent(indent, buf); } String key = path.getSegment(i).toPropString(); buf.append(YamlUtil.stringEscape(key)); buf.append(":"); if (i<path.size()-1) { indent += YamlIndentUtil.INDENT_BY; } } buf.append(indentUtil.applyIndentation(appendText, indent)); return buf.toString(); } private int getChildIndent(SNode parent) { if (parent.getNodeType()==SNodeType.DOC) { return parent.getIndent(); } else { return parent.getIndent()+YamlIndentUtil.INDENT_BY; } } private int getNewPathInsertionOffset(SChildBearingNode parent) throws Exception { int insertAfterLine = doc.getLineOfOffset(parent.getTreeEnd()); while (insertAfterLine>=0 && doc.getLineIndentation(insertAfterLine)==-1) { insertAfterLine--; } if (insertAfterLine<0) { //This code is probably 'dead' because: // - it can only occur if all lines in the 'parent' are empty // - if parent is any other node than SRootNode then it must have at least one // non-emtpy line // => parent must be SRootNode and only contain comment or empty lines // But in that case we will never need to compute a 'new path insertion offset' // since we will always be in the case where completions are to be inserted // in place (i.e. at the current cursor). return 0; //insert at beginning of document } else { IRegion r = doc.getLineInformation(insertAfterLine); return r.getOffset() + r.getLength(); } } public void createPathInPlace(SNode contextNode, YamlPath relativePath, int insertionPoint, String appendText) throws Exception { int indent = getChildIndent(contextNode); insert(insertionPoint, createPathInsertionText(relativePath, indent, needNewline(contextNode, insertionPoint), appendText)); } private boolean needNewline(SNode contextNode, int insertionPoint) throws Exception { if (contextNode.getNodeType()==SNodeType.SEQ) { // after a '- ' its okay to put key on same line return false; } else { return lineHasTextBefore(insertionPoint); } } private boolean lineHasTextBefore(int insertionPoint) throws Exception { String textBefore = doc.getLineTextBefore(insertionPoint); return !textBefore.trim().isEmpty(); } /** * Deletes this node, and all of its children. */ public void deleteNode(SNode node) throws Exception { delete(node.getStart(), node.getTreeEnd()); deleteLineBackwardAtOffset(node.getStart()); } }