| /*=============================================================================# |
| # Copyright (c) 2007, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.r.core.rsource; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Arrays; |
| |
| 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.text.edits.MultiTextEdit; |
| import org.eclipse.text.edits.ReplaceEdit; |
| import org.eclipse.text.edits.TextEdit; |
| |
| import org.eclipse.statet.ecommons.text.IndentUtil; |
| import org.eclipse.statet.ecommons.text.IndentUtil.IndentEditAction; |
| |
| import org.eclipse.statet.internal.r.core.RCorePlugin; |
| import org.eclipse.statet.ltk.ast.core.AstNode; |
| import org.eclipse.statet.ltk.ast.core.AstVisitor; |
| import org.eclipse.statet.r.core.RCodeStyleSettings; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.RCoreAccess; |
| import org.eclipse.statet.r.core.rsource.ast.Block; |
| import org.eclipse.statet.r.core.rsource.ast.CForLoop; |
| import org.eclipse.statet.r.core.rsource.ast.CIfElse; |
| import org.eclipse.statet.r.core.rsource.ast.CRepeatLoop; |
| import org.eclipse.statet.r.core.rsource.ast.CWhileLoop; |
| import org.eclipse.statet.r.core.rsource.ast.FCall; |
| import org.eclipse.statet.r.core.rsource.ast.FDef; |
| import org.eclipse.statet.r.core.rsource.ast.GenericVisitor; |
| import org.eclipse.statet.r.core.rsource.ast.Group; |
| import org.eclipse.statet.r.core.rsource.ast.NodeType; |
| import org.eclipse.statet.r.core.rsource.ast.RAstNode; |
| import org.eclipse.statet.r.core.rsource.ast.SourceComponent; |
| import org.eclipse.statet.r.core.rsource.ast.SubIndexed; |
| import org.eclipse.statet.r.core.source.RHeuristicTokenScanner; |
| |
| |
| /** |
| * Indents R source code in a document |
| */ |
| public class RSourceIndenter { |
| |
| |
| private IndentUtil util; |
| private final RHeuristicTokenScanner scanner; |
| private final ComputeIndentVisitor computeVisitor; |
| |
| private AbstractDocument document; |
| private AstNode rootNode; |
| private RCodeStyleSettings codeStyle; |
| |
| private int refLine; |
| private int firstLine; |
| private int lastLine; |
| |
| private int[] lineOffsets; |
| private int[] lineLevels; |
| |
| private ScopeFactory factory; |
| |
| |
| private class ComputeIndentVisitor extends GenericVisitor implements AstVisitor { |
| |
| |
| private int startOffset; |
| private int stopOffset; |
| private int currentLine; |
| |
| |
| void computeIndent() throws InvocationTargetException { |
| try { |
| this.currentLine= (RSourceIndenter.this.refLine >= 0) ? RSourceIndenter.this.refLine : RSourceIndenter.this.firstLine; |
| this.startOffset= RSourceIndenter.this.document.getLineOffset(this.currentLine); |
| this.stopOffset= RSourceIndenter.this.document.getLineOffset(RSourceIndenter.this.lastLine) + RSourceIndenter.this.document.getLineLength(RSourceIndenter.this.lastLine); |
| RSourceIndenter.this.rootNode.accept(this); |
| } |
| catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| |
| private final boolean checkOffset(final int offset) { |
| if (offset >= RSourceIndenter.this.lineOffsets[this.currentLine]) { // offset is first char in line |
| do { |
| RSourceIndenter.this.lineLevels[this.currentLine]= RSourceIndenter.this.factory.getIndent(this.currentLine); |
| } while (offset >= RSourceIndenter.this.lineOffsets[++this.currentLine]); |
| return true; |
| } |
| return false; |
| } |
| |
| private void checkBeforeOffset(final int offset) { |
| if (offset >= RSourceIndenter.this.lineOffsets[this.currentLine + 1]) { // offset is first char in line |
| final int level= RSourceIndenter.this.factory.getIndent(this.currentLine); |
| do { |
| RSourceIndenter.this.lineLevels[this.currentLine++]= level; |
| } while (offset >= RSourceIndenter.this.lineOffsets[this.currentLine + 1]); |
| } |
| } |
| |
| private boolean checkNode(final RAstNode node) throws InvocationTargetException { |
| final int offset= node.getStartOffset(); |
| if (checkOffset(offset)) { |
| return (node.getEndOffset() >= RSourceIndenter.this.lineOffsets[this.currentLine]); |
| } |
| // touches format region |
| if (node.getEndOffset() >= this.startOffset && offset <= this.stopOffset) { |
| return true; |
| } |
| // not interesting |
| return false; |
| } |
| |
| |
| private final void checkExprListChilds(final RAstNode node) throws InvocationTargetException { |
| try { |
| final int count= node.getChildCount(); |
| for (int i= 0; i < count; i++) { |
| final RAstNode child= node.getChild(i); |
| RSourceIndenter.this.factory.createCommonExprScope(child.getStartOffset(), child); |
| child.acceptInR(this); |
| RSourceIndenter.this.factory.leaveScope(); |
| } |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final AstNode node) throws InvocationTargetException { |
| if (node.getEndOffset() >= this.startOffset && node.getStartOffset() <= this.stopOffset) { |
| if (node instanceof RAstNode) { |
| ((RAstNode) node).acceptInR(this); |
| } |
| else { |
| node.acceptInChildren(this); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(final SourceComponent node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createSourceScope(0, node); |
| if (node.getEndOffset() >= this.startOffset && node.getStartOffset() <= this.stopOffset) { |
| checkExprListChilds(node); |
| } |
| checkOffset(Integer.MAX_VALUE - 2); |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final Block node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createBlockScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| RSourceIndenter.this.factory.updateEnterBrackets(); |
| checkExprListChilds(node); |
| checkBeforeOffset(node.getEndOffset()); |
| RSourceIndenter.this.factory.updateLeaveBrackets(); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final Group node) throws InvocationTargetException { |
| try { |
| if (checkNode(node)) { |
| RSourceIndenter.this.factory.createGroupContScope(node.getStartOffset() + 1, node.getExprChild()); |
| node.getExprChild().acceptInR(this); |
| checkBeforeOffset(node.getEndOffset()); |
| |
| checkOffset(node.getEndOffset()); |
| RSourceIndenter.this.factory.leaveScope(); |
| } |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| private final void checkControlCondChild(final int open, final RAstNode child, final int close) throws InvocationTargetException { |
| try { |
| if (open >= 0) { |
| checkOffset(open); |
| RSourceIndenter.this.factory.createControlCondScope(open + 1, child); |
| child.acceptInR(this); |
| checkBeforeOffset(close); |
| |
| checkOffset(close); |
| RSourceIndenter.this.factory.leaveScope(); |
| } |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| private final void checkControlContChild(final RAstNode child) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createControlContScope(child.getStartOffset(), child); |
| child.acceptInR(this); |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final CIfElse node) throws InvocationTargetException { |
| try { |
| boolean inElseIf= false; |
| if (node.getRParent().getNodeType() == NodeType.C_IF |
| && ((CIfElse) node.getParent()).getElseChild() == node) { |
| RSourceIndenter.this.factory.leaveScope(); |
| inElseIf= true; |
| } |
| else { |
| RSourceIndenter.this.factory.createControlScope(node.getStartOffset(), node); |
| } |
| if (checkNode(node)) { |
| checkControlCondChild(node.getCondOpenOffset(), node.getCondChild(), node.getCondCloseOffset()); |
| checkControlContChild(node.getThenChild()); |
| if (node.hasElse()) { |
| checkOffset(node.getElseOffset()); |
| checkControlContChild(node.getElseChild()); |
| } |
| checkOffset(node.getEndOffset()); |
| } |
| if (inElseIf) { |
| RSourceIndenter.this.factory.createDummy(); |
| } |
| else { |
| RSourceIndenter.this.factory.leaveScope(); |
| } |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final CForLoop node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createControlScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| checkControlCondChild(node.getCondOpenOffset(), node.getCondChild(), node.getCondCloseOffset()); |
| checkControlContChild(node.getContChild()); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final CWhileLoop node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createControlScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| checkControlCondChild(node.getCondOpenOffset(), node.getCondChild(), node.getCondCloseOffset()); |
| checkControlContChild(node.getContChild()); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final CRepeatLoop node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createControlScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| checkControlContChild(node.getContChild()); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| private final void checkArglist(final RAstNode node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createArglistScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| node.acceptInRChildren(this); |
| // checkBeforeOffset(node.getEndOffset()); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| private final void checkFDeflist(final RAstNode node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createFDeflistScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| node.acceptInRChildren(this); |
| // checkBeforeOffset(node.getEndOffset()); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| private final void checkArg(final RAstNode node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createCommonExprScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| node.acceptInRChildren(this); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final FDef node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createFDefScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| node.getArgsChild().acceptInR(this); |
| RSourceIndenter.this.factory.updateEnterFDefBody(); |
| checkControlContChild(node.getContChild()); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final FDef.Args node) throws InvocationTargetException { |
| checkFDeflist(node); |
| } |
| |
| @Override |
| public void visit(final FDef.Arg node) throws InvocationTargetException { |
| checkArg(node); |
| } |
| |
| @Override |
| public void visit(final FCall node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createFCallScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| node.getRefChild().acceptInR(this); |
| node.getArgsChild().acceptInR(this); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final FCall.Args node) throws InvocationTargetException { |
| checkArglist(node); |
| } |
| |
| @Override |
| public void visit(final FCall.Arg node) throws InvocationTargetException { |
| checkArg(node); |
| } |
| |
| @Override |
| public void visit(final SubIndexed node) throws InvocationTargetException { |
| try { |
| RSourceIndenter.this.factory.createFCallScope(node.getStartOffset(), node); |
| if (checkNode(node)) { |
| node.getRefChild().acceptInR(this); |
| node.getArgsChild().acceptInR(this); |
| checkOffset(node.getEndOffset()); |
| } |
| RSourceIndenter.this.factory.leaveScope(); |
| } catch (final BadLocationException e) { |
| throw new InvocationTargetException(e); |
| } |
| } |
| |
| @Override |
| public void visit(final SubIndexed.Args node) throws InvocationTargetException { |
| checkArglist(node); |
| } |
| |
| @Override |
| public void visit(final SubIndexed.Arg node) throws InvocationTargetException { |
| checkArg(node); |
| } |
| |
| @Override |
| public void visitNode(final RAstNode node) throws InvocationTargetException { |
| if (checkNode(node)) { |
| node.acceptInRChildren(this); |
| checkOffset(node.getEndOffset()); |
| } |
| } |
| |
| } |
| |
| |
| public RSourceIndenter(final RHeuristicTokenScanner scanner) { |
| this.scanner= scanner; |
| this.computeVisitor= new ComputeIndentVisitor(); |
| } |
| |
| public RSourceIndenter(final RHeuristicTokenScanner scanner, final RCoreAccess access) { |
| this(scanner); |
| setup(access); |
| } |
| |
| |
| // public void indent(final AbstractDocument document, final AstInfo<RAstNode> ast, final int firstLine, final int lastLine, |
| // final RCoreAccess access, final WorkingContext context) throws CoreException { |
| // try { |
| // setup(document, ast, access); |
| // computeIndent(firstLine, lastLine); |
| // final MultiTextEdit edits= createEdits(); |
| // if (edits != null && edits.getChildrenSize() > 0) { |
| // context.syncExec(new SourceDocumentRunnable(document, this.ast.stamp, |
| // (edits.getChildrenSize() > 50) ? DocumentRewriteSessionType.SEQUENTIAL : DocumentRewriteSessionType.SEQUENTIAL) { |
| // @Override |
| // public void run(final AbstractDocument document) throws InvocationTargetException { |
| // try { |
| // edits.apply(document, TextEdit.NONE); |
| // } |
| // catch (final MalformedTreeException e) { |
| // throw new InvocationTargetException(e); |
| // } |
| // catch (final BadLocationException e) { |
| // throw new InvocationTargetException(e); |
| // } |
| // } |
| // }); |
| // } |
| // } |
| // catch (final InvocationTargetException e) { |
| // throw createFailedException(e); |
| // } |
| // catch (final BadLocationException e) { |
| // throw createFailedException(e); |
| // } |
| // } |
| |
| // public void indentLine(final AbstractDocument document, final AstInfo<RAstNode> ast, final int line, |
| // final RCoreAccess access) throws CoreException { |
| // try { |
| // setup(document, ast, access); |
| // computeIndent(line, 0); |
| // final MultiTextEdit edits= createEdits(); |
| // if (edits != null && edits.getChildrenSize() > 0) { |
| // edits.apply(document); |
| // } |
| // } |
| // catch (BadLocationException e) { |
| // throw createFailedException(e); |
| // } |
| // } |
| |
| public void setup(final RCoreAccess access) { |
| this.codeStyle= access.getRCodeStyle(); |
| } |
| |
| /** |
| * Release resources from last computation. |
| * After clear, you can not longer call the <code>get...(...)</code> methods. |
| */ |
| public void clear() { |
| this.document= null; |
| this.rootNode= null; |
| this.codeStyle= null; |
| this.util= null; |
| this.lineLevels= null; |
| } |
| |
| |
| public int getNewIndentColumn(final int line) throws BadLocationException { |
| return getNewIndentColumn(line, this.document.getLineOffset(line)); |
| } |
| |
| private int getNewIndentColumn(final int line, final int lineOffset) throws BadLocationException { |
| if (getDocumentChar(lineOffset) == '#' && getDocumentChar(lineOffset + 1) != '#') { |
| return 0; |
| } |
| return this.lineLevels[line]; |
| } |
| |
| public int getNewIndentOffset(final int line) { |
| try { |
| return this.util.getIndentedOffsetAt(line, this.lineLevels[line]); |
| } catch (final BadLocationException e) { |
| return -1; |
| } |
| } |
| |
| |
| public TextEdit getIndentEdits(final AbstractDocument document, final AstNode root, |
| final int codeOffset, final int firstLine, final int lastLine) throws CoreException { |
| try { |
| this.document= document; |
| this.rootNode= root; |
| computeIndent(codeOffset, firstLine, lastLine); |
| return createEdits(); |
| } |
| catch (final BadLocationException e) { |
| throw createFailedException(e); |
| } |
| } |
| |
| protected void computeIndent(final int codeOffset, final int firstLine, final int lastLine) throws BadLocationException { |
| try { |
| this.codeStyle.getReadLock().lock(); |
| this.util= new IndentUtil(this.document, this.codeStyle); |
| this.firstLine= firstLine; |
| this.lastLine= lastLine; |
| |
| this.scanner.configure(this.document); |
| |
| this.refLine= -1; |
| int cand= this.firstLine; |
| SEARCH_REF_LINE : while (cand > 0) { |
| int refOffset= this.scanner.findAnyNonBlankBackward(this.document.getLineOffset(cand), RHeuristicTokenScanner.UNBOUND, true); |
| if (refOffset >= codeOffset) { // line found |
| cand= this.document.getLineOfOffset(refOffset); |
| refOffset= this.scanner.findAnyNonBlankForward(this.document.getLineOffset(cand), refOffset + 1, true); |
| if (this.document.getChar(refOffset) != '#' || this.document.getChar(refOffset + 1) == '#') { |
| this.refLine= cand; |
| break SEARCH_REF_LINE; |
| } |
| } |
| break SEARCH_REF_LINE; |
| } |
| |
| final int startLine= (this.refLine >= 0) ? this.refLine : this.firstLine; |
| final int count= this.document.getNumberOfLines(0, this.document.getLineOffset(this.lastLine)); |
| this.lineLevels= new int[count + 2]; |
| Arrays.fill(this.lineLevels, -1); |
| this.lineOffsets= new int[count + 3]; |
| for (int i= startLine; i < count; i++) { |
| this.lineOffsets[i]= this.document.getLineOffset(i); |
| } |
| this.lineOffsets[count]= (count < this.document.getNumberOfLines()) ? this.document.getLineOffset(count) : this.document.getLength(); |
| this.lineOffsets[count]= Integer.MAX_VALUE; |
| this.lineOffsets[count + 1]= Integer.MAX_VALUE; |
| this.lineOffsets[count + 2]= Integer.MAX_VALUE; |
| |
| this.factory= new ScopeFactory(this.util, this.codeStyle, this.document); |
| this.computeVisitor.computeIndent(); |
| correctLevels(); |
| } catch (final InvocationTargetException e) { |
| final Throwable targetException= e.getTargetException(); |
| if (targetException instanceof BadLocationException) { |
| throw (BadLocationException) targetException; |
| } |
| else { |
| RCorePlugin.logError("Unexpected error while indent sources", e); //$NON-NLS-1$ |
| } |
| } |
| finally { |
| if (this.codeStyle != null) { |
| this.codeStyle.getReadLock().unlock(); |
| this.codeStyle= null; |
| } |
| this.factory= null; |
| this.lineOffsets= null; |
| } |
| } |
| |
| protected void correctLevels() throws BadLocationException { |
| int shift= 0; |
| if (this.refLine >= 0) { |
| this.lineLevels[this.refLine]= this.lineLevels[this.refLine]; |
| shift= this.util.getLineIndent(this.refLine, false)[IndentUtil.COLUMN_IDX] - this.lineLevels[this.refLine]; |
| this.lineLevels[this.refLine]+= shift; |
| } |
| else { |
| shift= 0; |
| } |
| |
| // System.out.println("SHIFT=" + shift); |
| // if (fRefLine > 0) { |
| // System.out.println("REF=" + " " + (fRefLine + 1) + " (" + fLineOffsets[fRefLine] + " ): " + fLineLevels[fRefLine]); |
| // } |
| // else { |
| // System.out.println("NOREF"); |
| // } |
| // for (int i= this.firstLine; i <= this.lastLine; i++) { |
| // System.out.println(" " + (i + 1) + " (" + this.lineOffsets[i] + " ): " + this.lineLevels[i]); |
| // } |
| // System.out.println(); |
| |
| this.lineLevels[this.firstLine]+= + shift; |
| if (this.lineLevels[this.firstLine] < 0) { |
| shift-= this.lineLevels[this.firstLine]; |
| this.lineLevels[this.firstLine]= 0; |
| } |
| for (int line= this.firstLine + 1; line <= this.lastLine; line++) { |
| this.lineLevels[line]+= shift; |
| if (this.lineLevels[line] < 0) { |
| this.lineLevels[line]= 0; |
| } |
| } |
| } |
| |
| 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, lineOffset); |
| } |
| @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())); |
| } |
| } |
| }; |
| this.util.changeIndent(this.firstLine, this.lastLine, action); |
| return edits; |
| } |
| |
| protected final int getDocumentChar(final int idx) throws BadLocationException { |
| if (idx >= 0 && idx < this.document.getLength()) { |
| return this.document.getChar(idx); |
| } |
| return -1; |
| } |
| |
| protected CoreException createFailedException(final Throwable e) { |
| return new CoreException(new Status(Status.ERROR, RCore.BUNDLE_ID, -1, "Indentation failed", e)); |
| } |
| |
| } |
| |
| |
| class ScopeFactory { |
| |
| |
| private static interface IndentStrategy { |
| |
| int getIndent(Scope scope, int line); |
| |
| } |
| |
| public final static class Scope { |
| |
| int baseColumn; |
| int startLine; |
| RAstNode commandNode; |
| Scope parent; |
| IndentStrategy strategy; |
| |
| int getIndent(final int line) { |
| return this.strategy.getIndent(this, line); |
| } |
| |
| } |
| |
| |
| private static final int POOL_SIZE= 50; |
| private final int levelMult; |
| private final int wrappedCol; |
| private final int blockCol; |
| private Scope scope; |
| private final Scope[] pool= new Scope[POOL_SIZE]; |
| private int poolPointer= 0; |
| |
| private final IndentUtil util; |
| private final RCodeStyleSettings style; |
| private final AbstractDocument doc; |
| |
| |
| public ScopeFactory(final IndentUtil util, final RCodeStyleSettings style, final AbstractDocument doc) { |
| this.util= util; |
| this.style= style; |
| this.doc= doc; |
| this.levelMult= this.util.getLevelColumns(); |
| this.wrappedCol= this.style.getIndentWrappedCommandDepth()*this.levelMult; |
| this.blockCol= this.style.getIndentBlockDepth()*this.levelMult; |
| } |
| |
| private class FirstLineStrategy implements IndentStrategy { |
| @Override |
| public int getIndent(final Scope scope, final int line) { |
| if (line <= scope.startLine) { |
| return scope.baseColumn; |
| } |
| else { |
| return scope.baseColumn + ScopeFactory.this.wrappedCol; |
| } |
| } |
| } |
| private class FixStrategy implements IndentStrategy { |
| @Override |
| public int getIndent(final Scope scope, final int line) { |
| return scope.baseColumn; |
| } |
| } |
| |
| |
| private final IndentStrategy FIX_STRAT= new FixStrategy(); |
| private final IndentStrategy FIRSTLINE_STRAT= new FirstLineStrategy(); |
| |
| |
| private final void initNew(final int offset, final int line, final RAstNode node, final IndentStrategy strat, final int baseColumn) { |
| Scope scope; |
| if (this.poolPointer < POOL_SIZE) { |
| if (this.pool[this.poolPointer] == null) { |
| this.pool[this.poolPointer]= new Scope(); |
| } |
| scope= this.pool[this.poolPointer]; |
| } |
| else { |
| scope= new Scope(); |
| } |
| this.poolPointer++; |
| scope.parent= this.scope; |
| scope.baseColumn= baseColumn; |
| scope.strategy= strat; |
| scope.startLine= line; |
| scope.commandNode= node; |
| |
| this.scope= scope; |
| } |
| |
| |
| // private final void updateCommandLine(final int offset, final RAstNode node) { |
| // if (this.scope.commandDepth <= 1) { |
| // while (offset >= this.lineOffsets[this.commandStartLine + 1]) { |
| // this.commandStartLine++; |
| // } |
| // this.scope.commandNode= node; |
| // this.scope.commandStartLine= this.commandStartLine; |
| // } |
| // } |
| |
| public final Scope createDummy() { |
| initNew(0, 0, null, null, 0); |
| return this.scope; |
| } |
| |
| public final void createSourceScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIX_STRAT, 0); |
| } |
| |
| public final void createBlockScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| switch (node.getRParent().getNodeType()) { |
| case C_IF: |
| case C_FOR: |
| case C_WHILE: |
| case F_DEF: |
| if (node.getRParent().getChild(0) == node) { |
| // first are conditions |
| break; |
| } |
| //$FALL-THROUGH$ |
| case C_REPEAT: |
| // use control level instead of cont level |
| initNew(node.getStartOffset(), line, node, this.FIX_STRAT, this.scope.parent.baseColumn); |
| return; |
| default: |
| break; |
| } |
| |
| initNew(node.getStartOffset(), line, node, this.FIX_STRAT, this.scope.getIndent(line)); |
| } |
| |
| public final void createCommonExprScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIRSTLINE_STRAT, this.scope.getIndent(line)); |
| } |
| |
| public final void createGroupContScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIX_STRAT, this.scope.getIndent(line + 1) + this.style.getIndentGroupDepth()*this.levelMult); |
| } |
| |
| public final void createControlScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIX_STRAT, this.scope.getIndent(line)); |
| boolean compact= true; |
| if (compact && node.getNodeType() == NodeType.C_IF |
| && ((CIfElse) node).hasElse()) { |
| compact= false; |
| } |
| if (!useParent(compact, false, node)) { |
| this.scope.baseColumn= this.scope.parent.getIndent(line + 1); |
| } |
| } |
| |
| public final void createControlCondScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIRSTLINE_STRAT, this.scope.getIndent(line)); |
| } |
| |
| public final void createControlContScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIRSTLINE_STRAT, this.scope.getIndent(line) + this.blockCol); |
| } |
| |
| public final void createFCallScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIX_STRAT, this.scope.getIndent(line)); |
| if (!useParent(true, true, node)) { |
| this.scope.baseColumn= this.scope.parent.getIndent(line + 1); |
| } |
| } |
| |
| public final void createFDefScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIX_STRAT, this.scope.getIndent(line)); |
| } |
| |
| public final void createFDeflistScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| initNew(offset, line, node, this.FIX_STRAT, this.scope.getIndent(line) + this.wrappedCol); |
| } |
| |
| public final void createArglistScope(final int offset, final RAstNode node) throws BadLocationException { |
| final int line= this.doc.getLineOfOffset(offset); |
| // TODO: this can cause deep indentation - use compact detection of parent? |
| initNew(offset, line, node, this.FIX_STRAT, this.scope.getIndent(line) + this.wrappedCol); |
| } |
| |
| public final void leaveScope() { |
| this.scope= this.scope.parent; |
| this.poolPointer--; |
| } |
| |
| private final boolean useParent(final boolean compact, final boolean onlyAssignments, RAstNode node) throws BadLocationException { |
| if (this.scope.parent.commandNode == node) { |
| return true; |
| } |
| if (compact |
| && this.scope.startLine == this.scope.parent.startLine |
| && sameLine(this.scope.commandNode.getEndOffset(), this.scope.parent.commandNode.getEndOffset()) |
| ) { |
| return true; |
| } |
| if (onlyAssignments) { |
| ITER_OPS : while (true) { |
| node= node.getRParent(); |
| switch (node.getNodeType()) { |
| case A_LEFT: |
| case A_RIGHT: |
| case A_EQUALS: |
| case A_COLON: |
| continue ITER_OPS; |
| case BLOCK: |
| case SOURCELINES: |
| case C_IF: |
| case C_FOR: |
| case C_WHILE: |
| case C_REPEAT: |
| case F_CALL_ARG: |
| case F_DEF_ARG: |
| case SUB_INDEXED_ARG: |
| return true; |
| default: |
| break ITER_OPS; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private final boolean sameLine(final int offset1, final int offset2) throws BadLocationException { |
| return (offset1 == offset2 |
| || this.doc.getLineOfOffset(offset1) == this.doc.getLineOfOffset(offset2)); |
| } |
| |
| public final void updateEnterBrackets() { |
| this.scope.baseColumn+= this.blockCol; |
| } |
| |
| public final void updateLeaveBrackets() { |
| this.scope.baseColumn-= this.blockCol; |
| } |
| |
| public final void updateEnterFDefBody() throws BadLocationException { |
| if (useParent(true, true, this.scope.commandNode)) { |
| this.scope.baseColumn= this.scope.parent.baseColumn; |
| } |
| } |
| |
| public final int getIndent(final int line) { |
| return this.scope.getIndent(line); |
| } |
| |
| } |