/*=============================================================================# # Copyright (c) 2012-2016 Stephan Wahlbrink (WalWare.de) and others. # 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: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.docmlet.tex.core.refactoring; import java.util.Arrays; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.AbstractDocument; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IRegion; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import de.walware.ecommons.ltk.ast.AstSelection; import de.walware.ecommons.ltk.ast.IAstNode; import de.walware.ecommons.text.IndentUtil; import de.walware.ecommons.text.IndentUtil.IndentEditAction; import de.walware.docmlet.tex.core.ITexCoreAccess; import de.walware.docmlet.tex.core.TexCodeStyleSettings; import de.walware.docmlet.tex.core.TexCore; import de.walware.docmlet.tex.core.ast.TexAst.NodeType; import de.walware.docmlet.tex.core.ast.TexAstNode; public class LtxSourceIndenter { private TexCodeStyleSettings fCodeStyle; private AbstractDocument fDocument; private TexAstNode fRootNode; private int fRefLine; private int fFirstLine; private int fLastLine; private IndentUtil fUtil; private int[] fLineColumns; public void setup(final ITexCoreAccess coreAccess) { fCodeStyle = coreAccess.getTexCodeStyle(); } private void init(final AbstractDocument document, final TexAstNode node, final int firstLine, final int lastLine) throws BadLocationException { if (document == null) { throw new NullPointerException("document"); } if (node == null) { throw new NullPointerException("node"); } if (firstLine < 0 || lastLine < 0 || firstLine > lastLine) { throw new IllegalArgumentException("line index"); } fDocument = document; fRootNode = node; fFirstLine = firstLine; fLastLine = lastLine; final int count = fDocument.getNumberOfLines(0, fDocument.getLineOffset(fLastLine)); fLineColumns = new int[count + 2]; Arrays.fill(fLineColumns, -1); fUtil = new IndentUtil(document, fCodeStyle); } public void clear() { fDocument = null; fRootNode = null; fCodeStyle = null; fUtil = null; fLineColumns = null; } public int getNewIndentColumn(final int line) throws BadLocationException { return fLineColumns[line]; } public int getNewIndentOffset(final int line) { try { return fUtil.getIndentedOffsetAt(line, fLineColumns[line]); } catch (final BadLocationException e) { return -1; } } public TextEdit getIndentEdits(final AbstractDocument document, final TexAstNode node, final int codeOffset, final int firstLine, final int lastLine) throws CoreException { try { init(document, node, firstLine, lastLine); computeIndent(codeOffset); return createEdits(); } catch (final BadLocationException e) { throw createFailedException(e); } } protected void computeIndent(final int codeOffset) throws BadLocationException { fRefLine = fFirstLine - 1; if (fRefLine >= 0) { fLineColumns[fRefLine] = fUtil.getLineIndent(fRefLine, false)[IndentUtil.COLUMN_IDX]; for (int i = fRefLine + 1; i < fFirstLine; i++) { fLineColumns[i] = fLineColumns[fRefLine]; } } int line = fFirstLine; while (line <= fLastLine) { boolean addEnv = false; if (line > 0 && fCodeStyle.getIndentEnvDepth() > 0) { fLineColumns[line] = fLineColumns[line - 1]; final IRegion prevLine = fDocument.getLineInformation(line - 1); final IRegion currentLine = fDocument.getLineInformation(line); final int currentLineStop = currentLine.getOffset() + currentLine.getLength(); IAstNode node = AstSelection.search(fRootNode, prevLine.getOffset()+prevLine.getLength(), prevLine.getOffset()+prevLine.getLength(), AstSelection.MODE_COVERING_SAME_LAST ).getCovering(); while (node != null && !(node instanceof TexAstNode)) { node = node.getParent(); } if (node instanceof TexAstNode) { TexAstNode texNode = (TexAstNode) node; final Set<String> envs = fCodeStyle.getIndentEnvLabels(); while (texNode != null && (texNode.getOffset() >= prevLine.getOffset() || texNode.getEndOffset() <= currentLineStop )) { if (texNode.getNodeType() == NodeType.ENVIRONMENT) { if (envs == null || envs.contains(texNode.getText())) { if (texNode.getOffset() >= prevLine.getOffset() && texNode.getEndOffset() > currentLineStop ) { addEnv = true; } else if (texNode.getOffset() < prevLine.getOffset() && texNode.getEndOffset() <= currentLineStop ) { final int beginLine = fDocument.getLineOfOffset(texNode.getOffset()); if (fLineColumns[beginLine] < 0) { fLineColumns[beginLine] = fUtil.getLineIndent(beginLine, false)[IndentUtil.COLUMN_IDX]; } fLineColumns[line] = fLineColumns[beginLine]; } break; } } texNode= texNode.getTexParent(); } } if (addEnv) { fLineColumns[line] += fCodeStyle.getIndentEnvDepth() * fUtil.getLevelColumns(); } line ++; } } } protected MultiTextEdit createEdits() throws BadLocationException, CoreException { final MultiTextEdit edits = new MultiTextEdit(); final IndentEditAction action = new IndentEditAction() { @Override public int getIndentColumn(final int line, final int lineOffset) throws BadLocationException { return getNewIndentColumn(line); } @Override public void doEdit(final int line, final int offset, final int length, final StringBuilder text) throws BadLocationException { if (text != null) { edits.addChild(new ReplaceEdit(offset, length, text.toString())); } } }; fUtil.changeIndent(fFirstLine, fLastLine, action); return edits; } protected CoreException createFailedException(final Throwable e) { return new CoreException(new Status(Status.ERROR, TexCore.PLUGIN_ID, -1, "Indentation failed", e)); } }