| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| * Anton Leherbauer (Wind River Systems) |
| * Sergey Prigogin (Google) |
| *******************************************************************************/ |
| package org.eclipse.cdt.internal.formatter; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; |
| import org.eclipse.cdt.core.formatter.DefaultCodeFormatterOptions; |
| import org.eclipse.cdt.core.parser.util.CharArrayUtils; |
| import org.eclipse.cdt.internal.formatter.align.Alignment; |
| import org.eclipse.cdt.internal.formatter.align.AlignmentException; |
| import org.eclipse.cdt.internal.formatter.scanner.Scanner; |
| import org.eclipse.cdt.internal.formatter.scanner.Token; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.text.edits.MultiTextEdit; |
| import org.eclipse.text.edits.ReplaceEdit; |
| import org.eclipse.text.edits.TextEdit; |
| |
| /** |
| * This class is responsible for dumping formatted source. |
| * |
| * @since 4.0 |
| */ |
| public class Scribe { |
| private static final String EMPTY_STRING= ""; //$NON-NLS-1$ |
| private static final String SPACE= " "; //$NON-NLS-1$ |
| |
| private static final int INITIAL_SIZE= 100; |
| |
| private final DefaultCodeFormatterOptions preferences; |
| public final Scanner scanner; |
| |
| /** one-based column */ |
| public int column= 1; |
| |
| // Most specific alignment. |
| public Alignment currentAlignment; |
| public Alignment memberAlignment; |
| public AlignmentException currentAlignmentException; |
| |
| /** @see Alignment#tailFormatter */ |
| private Runnable tailFormatter; |
| |
| public Token currentToken; |
| |
| // Edits management |
| private OptimizedReplaceEdit[] edits; |
| public int editsIndex; |
| |
| public int indentationLevel; |
| public int numberOfIndentations; |
| public int indentationSize; |
| |
| private final String lineSeparator; |
| private final boolean indentEmptyLines; |
| private final int pageWidth; |
| private boolean preserveNewLines; |
| private boolean checkLineWrapping; |
| public int lastNumberOfNewLines; |
| private boolean preserveLineBreakIndentation; |
| boolean formatBrace; |
| public int line; |
| |
| public boolean needSpace; |
| public boolean pendingSpace; |
| |
| public int tabLength; |
| public int tabChar; |
| private final boolean useTabsOnlyForLeadingIndents; |
| |
| private int textRegionEnd; |
| private int textRegionStart; |
| public int scannerEndPosition; |
| |
| private List<Position> fSkipPositions= Collections.emptyList(); |
| |
| private boolean skipOverInactive; |
| |
| private int fSkipStartOffset= Integer.MAX_VALUE; |
| private int fSkipEndOffset; |
| private int fSkippedIndentations; |
| |
| /* Comments formatting */ |
| private static final int NO_TRAILING_COMMENT = 0x0000; |
| private static final int BASIC_TRAILING_COMMENT = 0x0100; |
| private int[] lineOffsets; |
| private int numLines; |
| |
| // Class to store previous line comment information |
| static class LineComment { |
| boolean contiguous; |
| int currentIndentation; |
| int indentation; |
| int lines; |
| char[] leadingSpaces = CharArrayUtils.EMPTY_CHAR_ARRAY; |
| } |
| final LineComment lastLineComment = new LineComment(); |
| |
| Scribe(CodeFormatterVisitor formatter, int offset, int length) { |
| scanner= new Scanner(); |
| preferences= formatter.preferences; |
| pageWidth= preferences.page_width; |
| tabLength= preferences.tab_size; |
| indentationLevel= 0; // initialize properly |
| numberOfIndentations= 0; |
| useTabsOnlyForLeadingIndents= preferences.use_tabs_only_for_leading_indentations; |
| indentEmptyLines= preferences.indent_empty_lines; |
| tabChar= preferences.tab_char; |
| if (tabChar == DefaultCodeFormatterOptions.MIXED) { |
| indentationSize= preferences.indentation_size; |
| } else { |
| indentationSize= tabLength; |
| } |
| lineSeparator= preferences.line_separator; |
| indentationLevel= preferences.initial_indentation_level * indentationSize; |
| preserveNewLines = false; |
| textRegionStart= offset; |
| textRegionEnd= offset + length; |
| reset(); |
| } |
| |
| private final void addDeleteEdit(int start, int end) { |
| addOptimizedReplaceEdit(start, end - start + 1, EMPTY_STRING); |
| } |
| |
| public final void addInsertEdit(int insertPosition, CharSequence insertedString) { |
| addOptimizedReplaceEdit(insertPosition, 0, insertedString); |
| } |
| |
| /** |
| * Adds a replace edit. |
| * @param start start offset (inclusive) |
| * @param end end offset (inclusive) |
| * @param replacement the replacement string |
| */ |
| public final void addReplaceEdit(int start, int end, CharSequence replacement) { |
| addOptimizedReplaceEdit(start, end - start + 1, replacement); |
| } |
| |
| private final void addOptimizedReplaceEdit(int offset, int length, CharSequence replacement) { |
| if (edits.length == editsIndex) { |
| resize(); |
| } |
| if (editsIndex > 0) { |
| // Try to merge last two edits |
| final OptimizedReplaceEdit previous= edits[editsIndex - 1]; |
| final int previousOffset= previous.offset; |
| final int previousLength= previous.length; |
| final int endOffsetOfPreviousEdit= previousOffset + previousLength; |
| final int replacementLength= replacement.length(); |
| final String previousReplacement= previous.replacement; |
| final int previousReplacementLength= previousReplacement.length(); |
| if (previousOffset == offset && previousLength == length |
| && (replacementLength == 0 || previousReplacementLength == 0)) { |
| if (currentAlignment != null) { |
| final Location location= currentAlignment.location; |
| if (location.editsIndex == editsIndex) { |
| location.editsIndex--; |
| location.textEdit= previous; |
| } |
| } |
| editsIndex--; |
| return; |
| } |
| if (endOffsetOfPreviousEdit == offset) { |
| if (length != 0) { |
| if (replacementLength != 0) { |
| editsIndex--; |
| appendOptimizedReplaceEdit(previousOffset, previousLength + length, |
| previousReplacement + replacement); |
| } else if (previousLength + length == previousReplacementLength) { |
| // Check the characters. If they are identical, |
| // we can get rid of the previous edit. |
| boolean canBeRemoved= true; |
| loop: for (int i= previousOffset; i < previousOffset + previousReplacementLength; i++) { |
| if (scanner.source[i] != previousReplacement.charAt(i - previousOffset)) { |
| editsIndex--; |
| appendOptimizedReplaceEdit(previousOffset, previousReplacementLength, |
| previousReplacement); |
| canBeRemoved= false; |
| break loop; |
| } |
| } |
| if (canBeRemoved) { |
| if (currentAlignment != null) { |
| final Location location= currentAlignment.location; |
| if (location.editsIndex == editsIndex) { |
| location.editsIndex--; |
| location.textEdit= previous; |
| } |
| } |
| editsIndex--; |
| } |
| } else { |
| editsIndex--; |
| appendOptimizedReplaceEdit(previousOffset, previousLength + length, |
| previousReplacement); |
| } |
| } else { |
| if (replacementLength != 0) { |
| editsIndex--; |
| appendOptimizedReplaceEdit(previousOffset, previousLength, |
| previousReplacement + replacement); |
| } |
| } |
| } else { |
| assert endOffsetOfPreviousEdit < offset; |
| appendOptimizedReplaceEdit(offset, length, replacement); |
| } |
| } else { |
| appendOptimizedReplaceEdit(offset, length, replacement); |
| } |
| } |
| |
| /** |
| * Trims redundant prefix from a replacement edit and, if there is anything left, appends |
| * the replacement edit to the edits array. |
| */ |
| private void appendOptimizedReplaceEdit(int offset, int length, CharSequence replacement) { |
| int replacementLength = replacement.length(); |
| int i; |
| for (i = 0; i < length && i < replacementLength; i++, offset++) { |
| if (scanner.source[offset] != replacement.charAt(i)) |
| break; |
| } |
| length -= i; |
| if (i > 0) { |
| replacement = i == replacementLength ? |
| EMPTY_STRING : replacement.subSequence(i, replacementLength); |
| } |
| if (length > 0 || replacement.length() > 0) { |
| edits[editsIndex++]= new OptimizedReplaceEdit(offset, length, replacement); |
| } |
| } |
| |
| public void alignFragment(Alignment alignment, int fragmentIndex) { |
| alignment.alignFragment(fragmentIndex); |
| } |
| |
| public void consumeNextToken() { |
| printComment(); |
| currentToken= scanner.nextToken(); |
| addDeleteEdit(scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()); |
| } |
| |
| public Alignment createAlignment(String name, int mode, int count, int sourceRestart) { |
| return createAlignment(name, mode, Alignment.R_INNERMOST, count, sourceRestart); |
| } |
| |
| public Alignment createAlignment(String name, int mode, int count, int sourceRestart, |
| boolean adjust) { |
| return createAlignment(name, mode, Alignment.R_INNERMOST, count, sourceRestart, adjust); |
| } |
| |
| public Alignment createAlignment(String name, int mode, int tieBreakRule, int count, |
| int sourceRestart) { |
| return createAlignment(name, mode, tieBreakRule, count, sourceRestart, |
| preferences.continuation_indentation, false); |
| } |
| |
| public Alignment createAlignment(String name, int mode, int count, int sourceRestart, |
| int continuationIndent, boolean adjust) { |
| return createAlignment(name, mode, Alignment.R_INNERMOST, count, sourceRestart, |
| continuationIndent, adjust); |
| } |
| |
| public Alignment createAlignment(String name, int mode, int tieBreakRule, int count, |
| int sourceRestart, int continuationIndent, boolean adjust) { |
| Alignment alignment= new Alignment(name, mode, tieBreakRule, this, count, sourceRestart, |
| continuationIndent); |
| // adjust break indentation |
| if (adjust && memberAlignment != null) { |
| Alignment current= memberAlignment; |
| while (current.enclosing != null) { |
| current= current.enclosing; |
| } |
| if ((current.mode & Alignment.M_MULTICOLUMN) != 0) { |
| final int indentSize= indentationSize; |
| switch (current.chunkKind) { |
| case Alignment.CHUNK_METHOD: |
| case Alignment.CHUNK_TYPE: |
| if ((mode & Alignment.M_INDENT_BY_ONE) != 0) { |
| alignment.breakIndentationLevel= indentationLevel + indentSize; |
| } else { |
| alignment.breakIndentationLevel= indentationLevel + |
| continuationIndent * indentSize; |
| } |
| alignment.update(); |
| break; |
| case Alignment.CHUNK_FIELD: |
| if ((mode & Alignment.M_INDENT_BY_ONE) != 0) { |
| alignment.breakIndentationLevel= current.originalIndentationLevel + indentSize; |
| } else { |
| alignment.breakIndentationLevel= current.originalIndentationLevel + |
| continuationIndent * indentSize; |
| } |
| alignment.update(); |
| break; |
| } |
| } else { |
| switch (current.mode & Alignment.SPLIT_MASK) { |
| case Alignment.M_COMPACT_SPLIT: |
| case Alignment.M_COMPACT_FIRST_BREAK_SPLIT: |
| case Alignment.M_NEXT_PER_LINE_SPLIT: |
| case Alignment.M_NEXT_SHIFTED_SPLIT: |
| case Alignment.M_ONE_PER_LINE_SPLIT: |
| final int indentSize= indentationSize; |
| switch (current.chunkKind) { |
| case Alignment.CHUNK_METHOD: |
| case Alignment.CHUNK_TYPE: |
| if ((mode & Alignment.M_INDENT_BY_ONE) != 0) { |
| alignment.breakIndentationLevel= indentationLevel + indentSize; |
| } else { |
| alignment.breakIndentationLevel= indentationLevel + |
| continuationIndent * indentSize; |
| } |
| alignment.update(); |
| break; |
| case Alignment.CHUNK_FIELD: |
| if ((mode & Alignment.M_INDENT_BY_ONE) != 0) { |
| alignment.breakIndentationLevel= current.originalIndentationLevel + |
| indentSize; |
| } else { |
| alignment.breakIndentationLevel= current.originalIndentationLevel + |
| continuationIndent * indentSize; |
| } |
| alignment.update(); |
| break; |
| } |
| break; |
| } |
| } |
| } |
| return alignment; |
| } |
| |
| public Alignment createMemberAlignment(String name, int mode, int count, int sourceRestart) { |
| Alignment alignment= createAlignment(name, mode, Alignment.R_INNERMOST, count, sourceRestart); |
| alignment.breakIndentationLevel= indentationLevel; |
| return alignment; |
| } |
| |
| public void enterAlignment(Alignment alignment) { |
| alignment.enclosing= currentAlignment; |
| currentAlignment= alignment; |
| } |
| |
| public void enterMemberAlignment(Alignment alignment) { |
| alignment.enclosing= memberAlignment; |
| memberAlignment= alignment; |
| } |
| |
| public void exitAlignment(Alignment alignment, boolean discardAlignment) { |
| Alignment current= currentAlignment; |
| while (current != null) { |
| if (current == alignment) |
| break; |
| current= current.enclosing; |
| } |
| if (current == null) { |
| throw new AbortFormatting("could not find matching alignment: " + alignment); //$NON-NLS-1$ |
| } |
| indentationLevel= alignment.location.outputIndentationLevel; |
| numberOfIndentations= alignment.location.numberOfIndentations; |
| if (discardAlignment) { |
| currentAlignment= alignment.enclosing; |
| } |
| } |
| |
| public void exitMemberAlignment(Alignment alignment) { |
| Alignment current= memberAlignment; |
| while (current != null) { |
| if (current == alignment) |
| break; |
| current= current.enclosing; |
| } |
| if (current == null) { |
| throw new AbortFormatting("could not find matching alignment: " + alignment); //$NON-NLS-1$ |
| } |
| indentationLevel= current.location.outputIndentationLevel; |
| numberOfIndentations= current.location.numberOfIndentations; |
| memberAlignment= current.enclosing; |
| } |
| |
| public Alignment getAlignment(String name) { |
| if (currentAlignment != null) { |
| return currentAlignment.getAlignment(name); |
| } |
| return null; |
| } |
| |
| private int getIndentationOfOffset(int offset) { |
| int beginningOfLine = getLineStart(offset); |
| int indent = 0; |
| for (int i= beginningOfLine; i < offset; i++) { |
| indent = computeIndentation(scanner.source[i], indent); |
| } |
| return indent; |
| } |
| |
| /** |
| * Computes indentation after applying a character at a given indentation position. |
| * @param text the text to be applied. |
| * @param indent the initial indentation. |
| * @return the resulting indentation. |
| */ |
| private int computeIndentation(char c, int indent) { |
| switch (c) { |
| case '\t': |
| return tabLength > 0 ? indent + tabLength - indent % tabLength : indent; |
| case '\r': |
| case '\n': |
| return 0; |
| default: |
| return indent + 1; |
| } |
| } |
| |
| /** |
| * Computes indentation after applying a given text at a given indentation position. |
| * @param text the text to be applied. |
| * @param indent the initial indentation. |
| * @return the resulting indentation. |
| */ |
| private int computeIndentation(char[] text, int indent) { |
| if (text == null) |
| return indent; |
| int length = text.length; |
| for (int i = 0; i < length; i++) { |
| indent = computeIndentation(text[i], indent); |
| } |
| return indent; |
| } |
| |
| private int computeIndentation(CharSequence text, int indent) { |
| if (text == null) |
| return indent; |
| int length = text.length(); |
| for (int i = 0; i < length; i++) { |
| indent = computeIndentation(text.charAt(i), indent); |
| } |
| return indent; |
| } |
| |
| public String getEmptyLines(int linesNumber) { |
| StringBuilder buffer= new StringBuilder(); |
| if (lastNumberOfNewLines == 0) { |
| linesNumber++; // add an extra line breaks |
| for (int i= 0; i < linesNumber; i++) { |
| if (indentEmptyLines) |
| printIndentationIfNecessary(buffer); |
| buffer.append(lineSeparator); |
| } |
| lastNumberOfNewLines += linesNumber; |
| line += linesNumber; |
| column= 1; |
| needSpace= false; |
| pendingSpace= false; |
| } else if (lastNumberOfNewLines == 1) { |
| for (int i= 0; i < linesNumber; i++) { |
| if (indentEmptyLines) |
| printIndentationIfNecessary(buffer); |
| buffer.append(lineSeparator); |
| } |
| lastNumberOfNewLines += linesNumber; |
| line += linesNumber; |
| column= 1; |
| needSpace= false; |
| pendingSpace= false; |
| } else { |
| if ((lastNumberOfNewLines - 1) >= linesNumber) { |
| // there is no need to add new lines |
| return EMPTY_STRING; |
| } |
| final int realNewLineNumber= linesNumber - lastNumberOfNewLines + 1; |
| for (int i= 0; i < realNewLineNumber; i++) { |
| if (indentEmptyLines) |
| printIndentationIfNecessary(buffer); |
| buffer.append(lineSeparator); |
| } |
| lastNumberOfNewLines += realNewLineNumber; |
| line += realNewLineNumber; |
| column= 1; |
| needSpace= false; |
| pendingSpace= false; |
| } |
| return String.valueOf(buffer); |
| } |
| |
| public OptimizedReplaceEdit getLastEdit() { |
| if (editsIndex > 0) { |
| return edits[editsIndex - 1]; |
| } |
| return null; |
| } |
| |
| Alignment getMemberAlignment() { |
| return memberAlignment; |
| } |
| |
| public String getNewLine() { |
| if (lastNumberOfNewLines >= 1) { |
| column= 1; // Ensure that the scribe is at the beginning of a new line |
| return EMPTY_STRING; |
| } |
| line++; |
| lastNumberOfNewLines= 1; |
| column= 1; |
| needSpace= false; |
| pendingSpace= false; |
| return lineSeparator; |
| } |
| |
| /** |
| * Returns next indentation level based on column estimated position (if |
| * column is not indented, then uses indentationLevel) |
| */ |
| public int getNextIndentationLevel(int someColumn) { |
| int indent= someColumn - 1; |
| if (indent == 0) |
| return indentationLevel; |
| if (tabChar == DefaultCodeFormatterOptions.TAB && !useTabsOnlyForLeadingIndents) { |
| // Round up to a multiple of indentationSize. |
| indent += indentationSize - 1; |
| return indent - indent % indentationSize; |
| } else { |
| return indent; |
| } |
| } |
| |
| private String getPreserveEmptyLines(int count) { |
| if (count == 0 && !preserveNewLines) { |
| // preserve line breaks in wrapping if specified |
| if (!preferences.join_wrapped_lines && lastNumberOfNewLines == 0) { |
| // Create new line |
| StringBuilder tempBuffer = new StringBuilder(); |
| tempBuffer.append(getNewLine()); |
| |
| if (currentAlignment != null && !formatBrace) { |
| indentationLevel = currentAlignment.breakIndentationLevel; |
| } |
| |
| // Set the flag to indicate that a specific indentation is currently in used |
| preserveLineBreakIndentation = true; |
| |
| // Print the computed indentation in the buffer |
| printIndentationIfNecessary(tempBuffer); |
| |
| return tempBuffer.toString(); |
| } |
| return EMPTY_STRING; |
| } |
| if (preferences.number_of_empty_lines_to_preserve != 0) { |
| int linesToPreserve= Math.min(count, preferences.number_of_empty_lines_to_preserve); |
| return getEmptyLines(linesToPreserve); |
| } else if (preserveNewLines) { |
| return getNewLine(); |
| } |
| return EMPTY_STRING; |
| } |
| |
| public TextEdit getRootEdit() { |
| MultiTextEdit edit= null; |
| int length= textRegionEnd - textRegionStart; |
| if (textRegionStart <= 0) { |
| if (length <= 0) { |
| edit= new MultiTextEdit(0, 0); |
| } else { |
| edit= new MultiTextEdit(0, textRegionEnd); |
| } |
| } else { |
| edit= new MultiTextEdit(textRegionStart, length); |
| } |
| for (int i= 0, max= editsIndex; i < max; i++) { |
| OptimizedReplaceEdit currentEdit= edits[i]; |
| if (isValidEdit(currentEdit)) { |
| edit.addChild(new ReplaceEdit(currentEdit.offset, currentEdit.length, currentEdit.replacement)); |
| } |
| } |
| edits= null; |
| return edit; |
| } |
| |
| public void handleLineTooLong() { |
| // Search for closest breakable alignment, using tie-breaking rules. |
| // Look for innermost breakable one. |
| int relativeDepth= 0; |
| Alignment targetAlignment= currentAlignment; |
| while (targetAlignment != null && targetAlignment.tieBreakRule == Alignment.R_INNERMOST) { |
| if (targetAlignment.couldBreak()) { |
| throwAlignmentException(AlignmentException.LINE_TOO_LONG, relativeDepth); |
| } |
| targetAlignment= targetAlignment.enclosing; |
| relativeDepth++; |
| } |
| |
| // Look for outermost breakable one. |
| relativeDepth= 0; |
| int outerMostDepth= -1; |
| targetAlignment= currentAlignment; |
| while (targetAlignment != null) { |
| if (targetAlignment.tieBreakRule == Alignment.R_OUTERMOST && targetAlignment.couldBreak()) { |
| outerMostDepth= relativeDepth; |
| } |
| targetAlignment= targetAlignment.enclosing; |
| relativeDepth++; |
| } |
| if (outerMostDepth >= 0) { |
| throwAlignmentException(AlignmentException.LINE_TOO_LONG, outerMostDepth); |
| } |
| |
| // Look for innermost breakable one but don't stop if we encounter a R_OUTERMOST |
| // tie-breaking rule. |
| relativeDepth= 0; |
| targetAlignment= currentAlignment; |
| while (targetAlignment != null) { |
| if (targetAlignment.couldBreak()) { |
| throwAlignmentException(AlignmentException.LINE_TOO_LONG, relativeDepth); |
| } |
| targetAlignment= targetAlignment.enclosing; |
| relativeDepth++; |
| } |
| // Did not find any breakable location - proceed |
| } |
| |
| private void throwAlignmentException(int kind, int relativeDepth) { |
| AlignmentException e= new AlignmentException(kind, relativeDepth); |
| currentAlignmentException= e; |
| throw e; |
| } |
| |
| public void indent() { |
| if (shouldSkip(scanner.getCurrentPosition())) { |
| fSkippedIndentations++; |
| return; |
| } |
| indentationLevel += indentationSize; |
| numberOfIndentations++; |
| } |
| |
| /** |
| * @param translationUnitSource |
| */ |
| public void initializeScanner(char[] translationUnitSource) { |
| scanner.setSource(translationUnitSource); |
| scannerEndPosition= translationUnitSource.length; |
| scanner.resetTo(0, scannerEndPosition); |
| edits= new OptimizedReplaceEdit[INITIAL_SIZE]; |
| // Locate line breaks. |
| lineOffsets = new int[200]; |
| numLines = 0; |
| lineOffsets[numLines++] = 0; |
| for (int i = 0; i < translationUnitSource.length; i++) { |
| if (translationUnitSource[i] == '\n') { |
| int len = lineOffsets.length; |
| if (numLines >= len) |
| System.arraycopy(lineOffsets, 0, lineOffsets = new int[len + (len + 1) / 2], 0, len); |
| lineOffsets[numLines++] = i + 1; |
| } |
| } |
| } |
| |
| /** |
| * @param list |
| */ |
| public void setSkipPositions(List<Position> list) { |
| fSkipPositions= list; |
| skipOverInactive= !list.isEmpty(); |
| } |
| |
| /** |
| * Returns offset of the start of the line containing a given offset. |
| */ |
| private int getLineStart(int offset) { |
| // Binary search |
| int down = 0; |
| int up = numLines; |
| while (true) { |
| int mid = (down + up) / 2; |
| int lineOffset = lineOffsets[mid]; |
| if (mid == down) { |
| return lineOffset; |
| } |
| if (lineOffset > offset) { |
| up = mid; |
| } else { |
| down = mid; |
| } |
| } |
| } |
| |
| private boolean isOnFirstColumn(int offset) { |
| return getLineStart(offset) == offset; |
| } |
| |
| private boolean isValidEdit(OptimizedReplaceEdit edit) { |
| final int editLength= edit.length; |
| final int editReplacementLength= edit.replacement.length(); |
| final int editOffset= edit.offset; |
| if (editLength != 0) { |
| if (textRegionStart <= editOffset && editOffset + editLength <= textRegionEnd) { |
| if (editReplacementLength != 0 && editLength == editReplacementLength) { |
| for (int i= editOffset, max= editOffset + editLength; i < max; i++) { |
| if (scanner.source[i] != edit.replacement.charAt(i - editOffset)) { |
| return true; |
| } |
| } |
| return false; |
| } else { |
| return true; |
| } |
| } else if (editOffset + editLength == textRegionStart) { |
| int i= editOffset; |
| for (int max= editOffset + editLength; i < max; i++) { |
| int replacementStringIndex= i - editOffset; |
| if (replacementStringIndex >= editReplacementLength |
| || scanner.source[i] != edit.replacement.charAt(replacementStringIndex)) { |
| break; |
| } |
| } |
| if (i - editOffset != editReplacementLength && i != editOffset + editLength - 1) { |
| edit.offset= textRegionStart; |
| edit.length= 0; |
| edit.replacement= edit.replacement.substring(i - editOffset); |
| return true; |
| } |
| } |
| } else if (textRegionStart <= editOffset && editOffset < textRegionEnd) { |
| return true; |
| } else if (editOffset == scannerEndPosition && editOffset == textRegionEnd) { |
| return true; |
| } |
| return false; |
| } |
| |
| private void preserveEmptyLines(int count, int insertPosition) { |
| if (count > 0) { |
| if (preferences.number_of_empty_lines_to_preserve != 0) { |
| int linesToPreserve= Math.min(count, preferences.number_of_empty_lines_to_preserve); |
| printEmptyLines(linesToPreserve, insertPosition); |
| } else { |
| printNewLine(insertPosition); |
| } |
| } else { |
| printNewLine(insertPosition); |
| } |
| } |
| |
| public void printRaw(int startOffset, int length) { |
| if (length <= 0) { |
| return; |
| } |
| int currentPosition= scanner.getCurrentPosition(); |
| if (shouldSkip(currentPosition)) { |
| return; |
| } |
| if (startOffset > currentPosition) { |
| printComment(); |
| currentPosition= scanner.getCurrentPosition(); |
| } |
| if (startOffset + length < currentPosition) { |
| return; // Don't move backwards |
| } |
| boolean savedPreserveNL= preserveNewLines; |
| boolean savedSkipOverInactive= skipOverInactive; |
| int savedScannerEndPos= scannerEndPosition; |
| preserveNewLines= true; |
| skipOverInactive= false; |
| scannerEndPosition= startOffset + length; |
| try { |
| scanner.resetTo(Math.max(startOffset, currentPosition), startOffset + length); |
| int parenLevel= 0; |
| while (true) { |
| boolean hasWhitespace= printComment(); |
| currentToken= scanner.nextToken(); |
| if (currentToken == null) { |
| if (hasWhitespace) { |
| space(); |
| } |
| break; |
| } |
| if (pendingSpace) { |
| addInsertEdit(scanner.getCurrentTokenStartPosition(), SPACE); |
| pendingSpace= false; |
| needSpace= false; |
| } |
| switch (currentToken.type) { |
| case Token.tLBRACE: { |
| scanner.resetTo(scanner.getCurrentTokenStartPosition(), scannerEndPosition); |
| formatOpeningBrace(preferences.brace_position_for_block, |
| preferences.insert_space_before_opening_brace_in_block); |
| if (preferences.indent_statements_compare_to_block) { |
| indent(); |
| } |
| break; |
| } |
| case Token.tRBRACE: { |
| scanner.resetTo(scanner.getCurrentTokenStartPosition(), scannerEndPosition); |
| if (preferences.indent_statements_compare_to_block) { |
| unIndent(); |
| } |
| formatClosingBrace(preferences.brace_position_for_block); |
| break; |
| } |
| case Token.tLPAREN: |
| ++parenLevel; |
| print(currentToken.getLength(), hasWhitespace); |
| if (parenLevel > 0) { |
| indentForContinuation(); |
| if (column <= indentationLevel) { |
| // HACK: avoid indent in same line |
| column= indentationLevel + 1; |
| } |
| } |
| break; |
| case Token.tRPAREN: |
| --parenLevel; |
| if (parenLevel >= 0) { |
| unIndentForContinuation(); |
| } |
| print(currentToken.getLength(), hasWhitespace); |
| break; |
| case Token.tSEMI: |
| print(currentToken.getLength(), preferences.insert_space_before_semicolon); |
| break; |
| case Token.t_catch: |
| case Token.t_else: |
| if (preferences.insert_new_line_before_else_in_if_statement) { |
| printNewLine(currentToken.offset); |
| } else { |
| hasWhitespace= true; |
| } |
| print(currentToken.getLength(), hasWhitespace); |
| break; |
| default: |
| if (currentToken.isVisibilityModifier() |
| && !preferences.indent_access_specifier_compare_to_type_header) { |
| int indentLevel= indentationLevel; |
| if (indentationLevel > 0) |
| unIndent(); |
| print(currentToken.getLength(), hasWhitespace); |
| while (indentationLevel < indentLevel) { |
| indent(); |
| } |
| } else { |
| print(currentToken.getLength(), hasWhitespace); |
| } |
| } |
| hasWhitespace= false; |
| } |
| } finally { |
| scannerEndPosition= savedScannerEndPos; |
| scanner.resetTo(startOffset + length, scannerEndPosition); |
| skipOverInactive= savedSkipOverInactive; |
| preserveNewLines= savedPreserveNL; |
| } |
| } |
| |
| public void indentForContinuation() { |
| for (int i= 0; i < preferences.continuation_indentation; i++) { |
| indent(); |
| } |
| } |
| |
| public void unIndentForContinuation() { |
| for (int i= 0; i < preferences.continuation_indentation; i++) { |
| unIndent(); |
| } |
| } |
| |
| public void formatOpeningBrace(String bracePosition, boolean insertSpaceBeforeBrace) { |
| if (DefaultCodeFormatterConstants.NEXT_LINE.equals(bracePosition)) { |
| printNewLine(); |
| } else if (DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED.equals(bracePosition)) { |
| printNewLine(); |
| indent(); |
| } |
| printNextToken(Token.tLBRACE, insertSpaceBeforeBrace); |
| |
| printTrailingComment(); |
| } |
| |
| public void formatClosingBrace(String block_brace_position) { |
| printNextToken(Token.tRBRACE); |
| printTrailingComment(); |
| if (DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED.equals(block_brace_position)) { |
| unIndent(); |
| } |
| } |
| |
| private void print(int length, boolean considerSpaceIfAny) { |
| if (checkLineWrapping && length + column - 1 > pageWidth) { |
| handleLineTooLong(); |
| } |
| lastNumberOfNewLines= 0; |
| if (indentationLevel != 0) { |
| printIndentationIfNecessary(); |
| } |
| if (considerSpaceIfAny) { |
| space(); |
| } |
| if (pendingSpace) { |
| addInsertEdit(scanner.getCurrentTokenStartPosition(), SPACE); |
| } |
| if (checkLineWrapping && length + column - 1 > pageWidth) { |
| handleLineTooLong(); |
| } |
| pendingSpace= false; |
| column += length; |
| needSpace= true; |
| } |
| |
| private void printBlockComment(boolean forceNewLine) { |
| int currentTokenStartPosition= scanner.getCurrentTokenStartPosition(); |
| int currentTokenEndPosition= scanner.getCurrentTokenEndPosition() + 1; |
| |
| scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition); |
| int currentCharacter; |
| boolean isNewLine= false; |
| int start= currentTokenStartPosition; |
| int nextCharacterStart= currentTokenStartPosition; |
| printIndentationIfNecessary(); |
| if (pendingSpace) { |
| addInsertEdit(currentTokenStartPosition, SPACE); |
| } |
| needSpace= false; |
| pendingSpace= false; |
| int previousStart= currentTokenStartPosition; |
| |
| while (nextCharacterStart <= currentTokenEndPosition && (currentCharacter= scanner.getNextChar()) != -1) { |
| nextCharacterStart= scanner.getCurrentPosition(); |
| |
| switch (currentCharacter) { |
| case '\r': |
| if (isNewLine) { |
| line++; |
| } |
| start= previousStart; |
| isNewLine= true; |
| if (scanner.getNextChar('\n')) { |
| currentCharacter= '\n'; |
| nextCharacterStart= scanner.getCurrentPosition(); |
| } |
| break; |
| case '\n': |
| if (isNewLine) { |
| line++; |
| } |
| start= previousStart; |
| isNewLine= true; |
| break; |
| default: |
| if (isNewLine) { |
| if (Character.isWhitespace((char) currentCharacter)) { |
| int previousStartPosition= scanner.getCurrentPosition(); |
| while (currentCharacter != -1 && currentCharacter != '\r' && |
| currentCharacter != '\n' && |
| Character.isWhitespace((char) currentCharacter)) { |
| previousStart= nextCharacterStart; |
| previousStartPosition= scanner.getCurrentPosition(); |
| currentCharacter= scanner.getNextChar(); |
| nextCharacterStart= scanner.getCurrentPosition(); |
| } |
| if (currentCharacter == '\r' || currentCharacter == '\n') { |
| nextCharacterStart= previousStartPosition; |
| } |
| } |
| column= 1; |
| line++; |
| |
| StringBuilder buffer= new StringBuilder(); |
| buffer.append(lineSeparator); |
| printIndentationIfNecessary(buffer); |
| buffer.append(' '); |
| |
| addReplaceEdit(start, previousStart - 1, String.valueOf(buffer)); |
| } else { |
| column += (nextCharacterStart - previousStart); |
| } |
| isNewLine= false; |
| } |
| previousStart= nextCharacterStart; |
| scanner.setCurrentPosition(nextCharacterStart); |
| } |
| lastNumberOfNewLines= 0; |
| needSpace= false; |
| scanner.resetTo(currentTokenEndPosition, scannerEndPosition); |
| if (forceNewLine) { |
| startNewLine(); |
| } |
| } |
| |
| private void printPreprocessorDirective() { |
| int currentTokenStartPosition= scanner.getCurrentTokenStartPosition(); |
| int currentTokenEndPosition= scanner.getCurrentTokenEndPosition() + 1; |
| |
| scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition); |
| int currentCharacter; |
| boolean isNewLine= false; |
| int nextCharacterStart= currentTokenStartPosition; |
| needSpace= false; |
| pendingSpace= false; |
| int previousStart= currentTokenStartPosition; |
| |
| while (nextCharacterStart <= currentTokenEndPosition && |
| (currentCharacter= scanner.getNextChar()) != -1) { |
| nextCharacterStart= scanner.getCurrentPosition(); |
| |
| switch (currentCharacter) { |
| case '\r': |
| isNewLine= true; |
| if (scanner.getNextChar('\n')) { |
| currentCharacter= '\n'; |
| nextCharacterStart= scanner.getCurrentPosition(); |
| } |
| break; |
| case '\n': |
| isNewLine= true; |
| break; |
| default: |
| if (isNewLine) { |
| column= 1; |
| line++; |
| } else { |
| column += (nextCharacterStart - previousStart); |
| } |
| isNewLine= false; |
| } |
| previousStart= nextCharacterStart; |
| scanner.setCurrentPosition(nextCharacterStart); |
| } |
| lastNumberOfNewLines= isNewLine ? 1 : 0; |
| needSpace= false; |
| if (currentAlignment != null) { |
| indentationLevel= currentAlignment.breakIndentationLevel; |
| } |
| scanner.resetTo(currentTokenEndPosition, scannerEndPosition); |
| } |
| |
| public void printEndOfTranslationUnit() { |
| int currentTokenStartPosition= scanner.getCurrentPosition(); |
| if (currentTokenStartPosition <= scannerEndPosition) { |
| printRaw(currentTokenStartPosition, scannerEndPosition - currentTokenStartPosition + 1); |
| } |
| } |
| |
| public boolean printComment() { |
| return printComment(NO_TRAILING_COMMENT); |
| } |
| |
| /** |
| * Prints comment at the current position. |
| * |
| * @return {@code true} if a writespace character was encountered preceding the next token, |
| */ |
| public boolean printComment(int trailing) { |
| // If we have a space between two tokens we ensure it will be dumped in the formatted |
| // string. |
| int currentTokenStartPosition= scanner.getCurrentPosition(); |
| if (shouldSkip(currentTokenStartPosition)) { |
| return false; |
| } |
| boolean hasComment= false; |
| boolean hasLineComment= false; |
| boolean hasWhitespace= false; |
| char[] whiteSpaces= CharArrayUtils.EMPTY_CHAR_ARRAY; |
| int lines= 0; |
| while ((currentToken= scanner.nextToken()) != null) { |
| if (skipOverInactive) { |
| Position inactivePos= getInactivePosAt(scanner.getCurrentTokenStartPosition()); |
| if (inactivePos != null) { |
| int startOffset= Math.min(scanner.getCurrentTokenStartPosition(), |
| inactivePos.getOffset()); |
| int endOffset= Math.min(scannerEndPosition, |
| inactivePos.getOffset() + inactivePos.getLength()); |
| if (startOffset < endOffset) { |
| int savedIndentLevel= indentationLevel; |
| scanner.resetTo(scanner.getCurrentTokenStartPosition(), scanner.eofPosition); |
| printRaw(startOffset, endOffset - startOffset); |
| while (indentationLevel > savedIndentLevel) { |
| unIndent(); |
| } |
| while (indentationLevel < savedIndentLevel) { |
| indent(); |
| } |
| scanner.resetTo(endOffset, scanner.eofPosition); |
| continue; |
| } |
| } |
| } |
| int tokenStartPosition = scanner.getCurrentTokenStartPosition(); |
| switch (currentToken.type) { |
| case Token.tWHITESPACE: |
| whiteSpaces= scanner.getCurrentTokenSource(); |
| int whitespacesEndPosition = scanner.getCurrentTokenEndPosition(); |
| lines= 0; |
| for (int i= 0, max= whiteSpaces.length; i < max; i++) { |
| switch (whiteSpaces[i]) { |
| case '\r': |
| if ((i + 1) < max) { |
| if (whiteSpaces[i + 1] == '\n') { |
| i++; |
| } |
| } |
| lines++; |
| break; |
| case '\n': |
| lines++; |
| } |
| } |
| // If following token is a line comment on the same line or the line just after, |
| // then it might be not really formatted as a trailing comment. |
| boolean realTrailing = trailing != NO_TRAILING_COMMENT; |
| if (realTrailing && scanner.peekNextChar() == '/' && lines == 0) { |
| boolean canChangeTrailing = false; |
| // For basic trailing comment preceded by a line comment, then it depends on |
| // the comments relative position when following comment column (after having |
| // been rounded) is below the preceding one, then it becomes not a good idea |
| // to change the trailing flag. |
| if (trailing == BASIC_TRAILING_COMMENT && hasLineComment) { |
| int currentCommentIndentation = computeIndentation(whiteSpaces, 0); |
| int relativeIndentation = |
| currentCommentIndentation - lastLineComment.currentIndentation; |
| if (tabLength == 0) { |
| canChangeTrailing = relativeIndentation == 0; |
| } else { |
| canChangeTrailing = relativeIndentation > -tabLength; |
| } |
| } |
| // If the trailing can be changed, then look at the following tokens. |
| if (canChangeTrailing) { |
| int currentTokenPosition = scanner.getCurrentTokenStartPosition(); |
| if (scanner.getNextToken() == Token.tLINECOMMENT) { |
| realTrailing = !hasLineComment; |
| switch (scanner.getNextToken()) { |
| case Token.tLINECOMMENT: |
| // At least two contiguous line comments. |
| // The formatter should not consider comments as trailing ones. |
| realTrailing = false; |
| break; |
| case Token.tWHITESPACE: |
| if (scanner.getNextToken() == Token.tLINECOMMENT) { |
| // At least two contiguous line comments. |
| // The formatter should not consider comments as trailing ones. |
| realTrailing = false; |
| } |
| break; |
| } |
| } |
| scanner.resetTo(currentTokenPosition, scanner.eofPosition); |
| scanner.getNextToken(); // Get current token again to restore the scanner state. |
| } |
| } |
| // Strategy to consume spaces and eventually leave at this stage |
| // depends on the fact that a trailing comment is expected or not |
| if (realTrailing) { |
| // If a line comment is consumed, no other comment can be on the same line after |
| if (hasLineComment) { |
| if (lines >= 1) { |
| currentTokenStartPosition = tokenStartPosition; |
| preserveEmptyLines(lines, currentTokenStartPosition); |
| addDeleteEdit(currentTokenStartPosition, whitespacesEndPosition); |
| scanner.resetTo(scanner.getCurrentPosition(), scannerEndPosition); |
| return hasWhitespace; |
| } |
| scanner.resetTo(currentTokenStartPosition, scannerEndPosition); |
| return hasWhitespace; |
| } |
| // If one or several new lines are consumed, following comments |
| // cannot be considered as trailing ones. |
| if (lines >= 1) { |
| if (hasComment) { |
| printNewLine(tokenStartPosition); |
| } |
| scanner.resetTo(currentTokenStartPosition, scannerEndPosition); |
| return hasWhitespace; |
| } |
| // Delete consumed white spaces |
| hasWhitespace = true; |
| currentTokenStartPosition = scanner.getCurrentPosition(); |
| addDeleteEdit(tokenStartPosition, whitespacesEndPosition); |
| } else { |
| if (lines == 0) { |
| hasWhitespace= true; |
| addDeleteEdit(scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()); |
| } else if (hasComment) { |
| if (lines == 1) { |
| printNewLine(scanner.getCurrentTokenStartPosition()); |
| } else { |
| preserveEmptyLines(lines - 1, scanner.getCurrentTokenStartPosition()); |
| } |
| addDeleteEdit(scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()); |
| } else if (hasLineComment) { |
| preserveEmptyLines(lines, scanner.getCurrentTokenStartPosition()); |
| addDeleteEdit(scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()); |
| } else if (lines != 0 && (!preferences.join_wrapped_lines || preferences.number_of_empty_lines_to_preserve != 0)) { |
| String preservedEmptyLines= getPreserveEmptyLines(lines - 1); |
| addReplaceEdit(scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition(), |
| preservedEmptyLines); |
| hasWhitespace= preservedEmptyLines.length() == 0; |
| } else { |
| addDeleteEdit(scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()); |
| hasWhitespace= true; |
| } |
| } |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| break; |
| case Token.tLINECOMMENT: |
| if (lines >= 1) { |
| if (lines > 1) { |
| preserveEmptyLines(lines - 1, scanner.getCurrentTokenStartPosition()); |
| } else if (lines == 1) { |
| printNewLine(scanner.getCurrentTokenStartPosition()); |
| } |
| } else if (hasWhitespace) { |
| // Look whether comments line may be contiguous or not |
| // Note that when preceding token is a comment line, then only one line |
| // is enough to have an empty line as the line end is included in the comment line. |
| // If comments are contiguous, store the white spaces to be able to compute |
| // the current comment indentation |
| if (lines > 1 || (lines == 1 && hasLineComment)) { |
| lastLineComment.contiguous = false; |
| } |
| lastLineComment.leadingSpaces = whiteSpaces; |
| lastLineComment.lines = lines; |
| } |
| whiteSpaces= CharArrayUtils.EMPTY_CHAR_ARRAY; |
| hasWhitespace= false; |
| printLineComment(); |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| hasLineComment= true; |
| lines= 0; |
| break; |
| case Token.tBLOCKCOMMENT: |
| if (lines >= 1) { |
| if (lines > 1) { |
| preserveEmptyLines(lines - 1, scanner.getCurrentTokenStartPosition()); |
| } else if (lines == 1) { |
| printNewLine(scanner.getCurrentTokenStartPosition()); |
| } |
| } else if (hasWhitespace) { |
| space(); |
| } |
| whiteSpaces= CharArrayUtils.EMPTY_CHAR_ARRAY; |
| hasWhitespace= false; |
| printBlockComment(false); |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| hasLineComment= false; |
| hasComment= true; |
| lines= 0; |
| break; |
| case Token.tPREPROCESSOR: |
| case Token.tPREPROCESSOR_DEFINE: |
| case Token.tPREPROCESSOR_INCLUDE: |
| if (column != 1) |
| printNewLine(scanner.getCurrentTokenStartPosition()); |
| if (lines >= 1) { |
| if (lines > 1) { |
| preserveEmptyLines(lines - 1, scanner.getCurrentTokenStartPosition()); |
| } else if (lines == 1) { |
| // printNewLine(scanner.getCurrentTokenStartPosition()); |
| } |
| } |
| whiteSpaces= CharArrayUtils.EMPTY_CHAR_ARRAY; |
| hasWhitespace= false; |
| printPreprocessorDirective(); |
| printNewLine(); |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| hasLineComment= false; |
| hasComment= false; |
| lines= 0; |
| break; |
| default: |
| // step back one token |
| scanner.resetTo(currentTokenStartPosition, scannerEndPosition); |
| return hasWhitespace; |
| } |
| } |
| scanner.resetTo(currentTokenStartPosition, scannerEndPosition); |
| return hasWhitespace; |
| } |
| |
| /** |
| * @param offset |
| * @return |
| */ |
| private Position getInactivePosAt(int offset) { |
| for (Iterator<Position> iter= fSkipPositions.iterator(); iter.hasNext();) { |
| Position pos= iter.next(); |
| if (pos.includes(offset)) { |
| return pos; |
| } |
| } |
| return null; |
| } |
| |
| private void printLineComment() { |
| int currentTokenStartPosition = scanner.getCurrentTokenStartPosition(); |
| int currentTokenEndPosition = scanner.getCurrentTokenEndPosition() + 1; |
| scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition); |
| int currentCharacter; |
| int start = currentTokenStartPosition; |
| int nextCharacterStart = currentTokenStartPosition; |
| |
| // Print comment line indentation |
| int commentIndentationLevel; |
| boolean onFirstColumn = isOnFirstColumn(start); |
| if (indentationLevel == 0) { |
| commentIndentationLevel = column - 1; |
| } else { |
| if (onFirstColumn && preferences.never_indent_line_comments_on_first_column) { |
| commentIndentationLevel = column - 1; |
| } else { |
| // Indentation may be specific for contiguous comment |
| // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=293300 |
| if (lastLineComment.contiguous) { |
| // The leading spaces have been set while looping in the printComment(int) method |
| int currentCommentIndentation = computeIndentation(lastLineComment.leadingSpaces, 0); |
| // Keep the current comment indentation when over the previous contiguous line comment |
| // and the previous comment has not been re-indented |
| int relativeIndentation = currentCommentIndentation - lastLineComment.currentIndentation; |
| boolean similarCommentsIndentation = false; |
| if (tabLength == 0) { |
| similarCommentsIndentation = relativeIndentation == 0; |
| } else if (relativeIndentation > -tabLength) { |
| similarCommentsIndentation = relativeIndentation == 0 |
| || currentCommentIndentation != 0 && lastLineComment.currentIndentation != 0; |
| } |
| if (similarCommentsIndentation && lastLineComment.indentation != indentationLevel) { |
| int currentIndentationLevel = indentationLevel; |
| indentationLevel = lastLineComment.indentation; |
| printIndentationIfNecessary(); |
| indentationLevel = currentIndentationLevel; |
| commentIndentationLevel = lastLineComment.indentation; |
| } else { |
| printIndentationIfNecessary(); |
| commentIndentationLevel = column - 1; |
| } |
| } else { |
| printIndentationIfNecessary(); |
| commentIndentationLevel = column - 1; |
| } |
| } |
| } |
| |
| // Prepare white space before the comment. |
| StringBuilder whitespace = null; |
| if (!lastLineComment.contiguous && commentIndentationLevel != indentationLevel && |
| !(preferences.never_indent_line_comments_on_first_column && onFirstColumn)) { |
| whitespace = new StringBuilder(); |
| int whitespaceStartPosition = currentTokenStartPosition - lastLineComment.leadingSpaces.length; |
| int indent = getIndentationOfOffset(whitespaceStartPosition); |
| int distance = computeIndentation(lastLineComment.leadingSpaces, indent) - indent; |
| if (preferences.comment_preserve_white_space_between_code_and_line_comment && |
| distance >= preferences.comment_min_distance_between_code_and_line_comment) { |
| whitespace.append(lastLineComment.leadingSpaces); |
| } else { |
| for (int i = 0; i < preferences.comment_min_distance_between_code_and_line_comment; i++) { |
| whitespace.append(' '); |
| } |
| } |
| } |
| |
| // Store line comment information |
| lastLineComment.currentIndentation = getIndentationOfOffset(currentTokenStartPosition); |
| lastLineComment.contiguous = true; |
| |
| while (true) { |
| Location location = new Location(this, scanner.getCurrentPosition()); |
| int commentIndent = commentIndentationLevel; |
| |
| // Add pending space if necessary |
| if (whitespace != null) { |
| addInsertEdit(currentTokenStartPosition, whitespace); |
| commentIndent = computeIndentation(whitespace, commentIndentationLevel); |
| needSpace = false; |
| pendingSpace = false; |
| } |
| lastLineComment.indentation = commentIndent; |
| |
| int previousStart = currentTokenStartPosition; |
| |
| int indent = commentIndent; |
| loop: while (nextCharacterStart <= currentTokenEndPosition && |
| (currentCharacter = scanner.getNextChar()) != -1) { |
| nextCharacterStart = scanner.getCurrentPosition(); |
| |
| switch (currentCharacter) { |
| case '\r': |
| case '\n': |
| start = previousStart; |
| break loop; |
| } |
| indent = computeIndentation((char) currentCharacter, indent); |
| previousStart = nextCharacterStart; |
| } |
| |
| if (start != currentTokenStartPosition) { |
| // This means that the line comment doesn't end the file |
| addReplaceEdit(start, currentTokenEndPosition - 1, lineSeparator); |
| line++; |
| column = 1; |
| lastNumberOfNewLines = 1; |
| } |
| if (!checkLineWrapping || indent <= pageWidth || whitespace == null || |
| commentIndent - commentIndentationLevel <= preferences.comment_min_distance_between_code_and_line_comment) { |
| break; |
| } |
| |
| // Maximum line length was exceeded. Try to reduce white space before the comment by |
| // removing the last white space character. |
| whitespace.deleteCharAt(whitespace.length() - 1); |
| if (computeIndentation(lastLineComment.leadingSpaces, commentIndentationLevel) - commentIndentationLevel < |
| preferences.comment_min_distance_between_code_and_line_comment) { |
| // The white space shrank too much. Rebuild it to satisfy the minimum distance |
| // requirement. |
| whitespace.delete(0, whitespace.length()); |
| for (int i = 0; i < preferences.comment_min_distance_between_code_and_line_comment; i++) { |
| whitespace.append(' '); |
| } |
| } |
| resetAt(location); |
| scanner.resetTo(location.inputOffset, scanner.eofPosition); |
| } |
| |
| needSpace = false; |
| pendingSpace = false; |
| // realign to the proper value |
| if (currentAlignment != null) { |
| if (memberAlignment != null) { |
| // select the last alignment |
| if (currentAlignment.location.inputOffset > memberAlignment.location.inputOffset) { |
| if (currentAlignment.couldBreak() && currentAlignment.wasSplit) { |
| currentAlignment.performFragmentEffect(); |
| } |
| } else { |
| indentationLevel = Math.max(indentationLevel, memberAlignment.breakIndentationLevel); |
| } |
| } else if (currentAlignment.couldBreak() && currentAlignment.wasSplit) { |
| currentAlignment.performFragmentEffect(); |
| } |
| if (currentAlignment.name.equals(Alignment.BINARY_EXPRESSION) && |
| currentAlignment.enclosing != null && |
| currentAlignment.enclosing.equals(Alignment.BINARY_EXPRESSION) && |
| indentationLevel < currentAlignment.breakIndentationLevel) { |
| indentationLevel = currentAlignment.breakIndentationLevel; |
| } |
| } |
| scanner.resetTo(currentTokenEndPosition, scannerEndPosition); |
| } |
| |
| public void printEmptyLines(int linesNumber) { |
| printEmptyLines(linesNumber, scanner.getCurrentTokenEndPosition() + 1); |
| } |
| |
| private void printEmptyLines(int linesNumber, int insertPosition) { |
| final String buffer= getEmptyLines(linesNumber); |
| if (!buffer.isEmpty()) { |
| addInsertEdit(insertPosition, buffer); |
| } |
| } |
| |
| void printIndentationIfNecessary() { |
| if (column > indentationLevel) |
| return; |
| StringBuilder buffer= new StringBuilder(); |
| printIndentationIfNecessary(buffer); |
| if (buffer.length() > 0) { |
| addInsertEdit(scanner.getCurrentTokenStartPosition(), buffer); |
| pendingSpace= false; |
| } |
| } |
| |
| private void printIndentationIfNecessary(StringBuilder buffer) { |
| switch (tabChar) { |
| case DefaultCodeFormatterOptions.TAB: |
| boolean useTabsForLeadingIndents= useTabsOnlyForLeadingIndents; |
| int numberOfLeadingIndents= numberOfIndentations; |
| int indentationsAsTab= 0; |
| if (useTabsForLeadingIndents) { |
| while (column <= indentationLevel) { |
| if (indentationsAsTab < numberOfLeadingIndents) { |
| buffer.append('\t'); |
| indentationsAsTab++; |
| int complement= tabLength - ((column - 1) % tabLength); // amount of space |
| column += complement; |
| needSpace= false; |
| } else { |
| buffer.append(' '); |
| column++; |
| needSpace= false; |
| } |
| } |
| } else { |
| while (column <= indentationLevel - indentationLevel % tabLength) { |
| buffer.append('\t'); |
| int complement= tabLength - ((column - 1) % tabLength); // amount of space |
| column += complement; |
| needSpace= false; |
| } |
| while (column <= indentationLevel) { |
| buffer.append(' '); |
| column++; |
| needSpace= false; |
| } |
| } |
| break; |
| case DefaultCodeFormatterOptions.SPACE: |
| while (column <= indentationLevel) { |
| buffer.append(' '); |
| column++; |
| needSpace= false; |
| } |
| break; |
| case DefaultCodeFormatterOptions.MIXED: |
| useTabsForLeadingIndents= useTabsOnlyForLeadingIndents; |
| numberOfLeadingIndents= numberOfIndentations; |
| indentationsAsTab= 0; |
| if (useTabsForLeadingIndents) { |
| final int columnForLeadingIndents= numberOfLeadingIndents * indentationSize; |
| while (column <= indentationLevel) { |
| if (column <= columnForLeadingIndents) { |
| if ((column - 1 + tabLength) <= indentationLevel) { |
| buffer.append('\t'); |
| column += tabLength; |
| } else if ((column - 1 + indentationSize) <= indentationLevel) { |
| // print one indentation |
| for (int i= 0, max= indentationSize; i < max; i++) { |
| buffer.append(' '); |
| column++; |
| } |
| } else { |
| buffer.append(' '); |
| column++; |
| } |
| } else { |
| for (int i= column, max= indentationLevel; i <= max; i++) { |
| buffer.append(' '); |
| column++; |
| } |
| } |
| needSpace= false; |
| } |
| } else { |
| while (column <= indentationLevel) { |
| if ((column - 1 + tabLength) <= indentationLevel) { |
| buffer.append('\t'); |
| column += tabLength; |
| } else if ((column - 1 + indentationSize) <= indentationLevel) { |
| // print one indentation |
| for (int i= 0, max= indentationSize; i < max; i++) { |
| buffer.append(' '); |
| column++; |
| } |
| } else { |
| buffer.append(' '); |
| column++; |
| } |
| needSpace= false; |
| } |
| } |
| break; |
| } |
| } |
| |
| public void enterNode() { |
| lastLineComment.contiguous = false; |
| } |
| |
| public void startNewLine() { |
| if (column > 1) { |
| printNewLine(); |
| } |
| } |
| |
| public void printNewLine() { |
| printNewLine(scanner.getCurrentPosition()); |
| } |
| |
| public void printNewLine(int insertPosition) { |
| if (shouldSkip(scanner.getCurrentPosition())) { |
| return; |
| } |
| if (lastNumberOfNewLines >= 1) { |
| // Ensure that the scribe is at the beginning of a new line |
| // only if no specific indentation has been previously set. |
| if (!preserveLineBreakIndentation) { |
| column = 1; |
| } |
| preserveLineBreakIndentation = false; |
| return; |
| } |
| addInsertEdit(insertPosition, lineSeparator); |
| line++; |
| lastNumberOfNewLines= 1; |
| column= 1; |
| needSpace= false; |
| pendingSpace= false; |
| preserveLineBreakIndentation = false; |
| lastLineComment.contiguous = false; |
| } |
| |
| public void printNextToken(int expectedTokenType) { |
| printNextToken(expectedTokenType, false); |
| } |
| |
| public void printNextToken(int expectedTokenType, boolean considerSpaceIfAny) { |
| // Set brace flag, it's useful for the scribe while preserving line breaks |
| switch (expectedTokenType) { |
| case Token.tRBRACE: |
| case Token.tLBRACE: |
| formatBrace = true; |
| lastLineComment.contiguous = false; |
| } |
| try { |
| printComment(); |
| if (shouldSkip(scanner.getCurrentPosition())) { |
| return; |
| } |
| currentToken= scanner.nextToken(); |
| if (currentToken == null || expectedTokenType != currentToken.type) { |
| if (pendingSpace) { |
| addInsertEdit(scanner.getCurrentTokenStartPosition(), SPACE); |
| } |
| pendingSpace= false; |
| needSpace= true; |
| throw new AbortFormatting( |
| "[" + (line + 1) + "/" + column + "] Unexpected token type, expecting:" + //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ |
| expectedTokenType + ", actual:" + currentToken);//$NON-NLS-1$ |
| } |
| print(currentToken.getLength(), considerSpaceIfAny); |
| } finally { |
| formatBrace = false; |
| } |
| } |
| |
| public void printNextToken(int[] expectedTokenTypes) { |
| printNextToken(expectedTokenTypes, false); |
| } |
| |
| public void printNextToken(int[] expectedTokenTypes, boolean considerSpaceIfAny) { |
| printComment(); |
| if (shouldSkip(scanner.getCurrentPosition())) { |
| return; |
| } |
| currentToken= scanner.nextToken(); |
| if (Arrays.binarySearch(expectedTokenTypes, currentToken.type) < 0) { |
| if (pendingSpace) { |
| addInsertEdit(scanner.getCurrentTokenStartPosition(), SPACE); |
| } |
| pendingSpace= false; |
| needSpace= true; |
| StringBuilder expectations= new StringBuilder(5); |
| for (int i= 0; i < expectedTokenTypes.length; i++) { |
| if (i > 0) { |
| expectations.append(','); |
| } |
| expectations.append(expectedTokenTypes[i]); |
| } |
| throw new AbortFormatting( |
| "[" + (line + 1) + "/" + column + "] unexpected token type, expecting:[" + expectations.toString() + "], actual:" + currentToken); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| print(currentToken.getLength(), considerSpaceIfAny); |
| } |
| |
| private void printRuler(StringBuilder stringBuffer) { |
| for (int i= 0; i < pageWidth; i++) { |
| if ((i % tabLength) == 0) { |
| stringBuffer.append('+'); |
| } else { |
| stringBuffer.append('-'); |
| } |
| } |
| stringBuffer.append(lineSeparator); |
| |
| for (int i= 0; i < (pageWidth / tabLength); i++) { |
| stringBuffer.append(i); |
| stringBuffer.append('\t'); |
| } |
| } |
| |
| public void printSpaces(int numSpaces) { |
| if (numSpaces > 0) { |
| int currentPosition= scanner.getCurrentPosition(); |
| StringBuilder spaces = new StringBuilder(numSpaces); |
| for (int i = 0; i < numSpaces; i++) { |
| spaces.append(' '); |
| } |
| addInsertEdit(currentPosition, spaces); |
| pendingSpace= false; |
| needSpace= false; |
| } |
| } |
| |
| public void printTrailingComment() { |
| printComment(BASIC_TRAILING_COMMENT); |
| } |
| |
| void redoAlignment(AlignmentException e) { |
| if (e.relativeDepth > 0) { // if exception targets a distinct context |
| e.relativeDepth--; // record fact that current context got traversed |
| currentAlignment= currentAlignment.enclosing; // pop currentLocation |
| throw e; // rethrow |
| } |
| // Reset scribe/scanner to restart at this given location |
| resetAt(currentAlignment.location); |
| scanner.resetTo(currentAlignment.location.inputOffset, scanner.eofPosition); |
| // Clean alignment chunkKind so it will think it is a new chunk again |
| currentAlignment.chunkKind= 0; |
| currentAlignmentException= null; |
| } |
| |
| void redoMemberAlignment(AlignmentException e) { |
| // Reset scribe/scanner to restart at this given location |
| resetAt(memberAlignment.location); |
| scanner.resetTo(memberAlignment.location.inputOffset, scanner.eofPosition); |
| // Clean alignment chunkKind so it will think it is a new chunk again |
| memberAlignment.chunkKind= 0; |
| currentAlignmentException= null; |
| } |
| |
| public void reset() { |
| checkLineWrapping= true; |
| line= 0; |
| column= 1; |
| editsIndex= 0; |
| } |
| |
| private void resetAt(Location location) { |
| line= location.outputLine; |
| column= location.outputColumn; |
| indentationLevel= location.outputIndentationLevel; |
| needSpace= location.needSpace; |
| pendingSpace= location.pendingSpace; |
| numberOfIndentations= location.numberOfIndentations; |
| lastNumberOfNewLines= location.lastNumberOfNewLines; |
| editsIndex= location.editsIndex; |
| if (editsIndex > 0) { |
| edits[editsIndex - 1]= location.textEdit; |
| } |
| setTailFormatter(location.tailFormatter); |
| } |
| |
| private void resize() { |
| System.arraycopy(edits, 0, (edits= new OptimizedReplaceEdit[editsIndex * 2]), 0, editsIndex); |
| } |
| |
| public void space() { |
| if (!needSpace) |
| return; |
| if (shouldSkip(scanner.getCurrentPosition())) { |
| return; |
| } |
| lastNumberOfNewLines= 0; |
| pendingSpace= true; |
| column++; |
| needSpace= false; |
| } |
| |
| public void undoSpace() { |
| if (pendingSpace) { |
| pendingSpace = false; |
| needSpace = true; |
| column--; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder buffer= new StringBuilder(); |
| buffer.append("(page width = ").append(pageWidth).append(") - (tabChar = "); //$NON-NLS-1$//$NON-NLS-2$ |
| switch (tabChar) { |
| case DefaultCodeFormatterOptions.TAB: |
| buffer.append("TAB"); //$NON-NLS-1$ |
| break; |
| case DefaultCodeFormatterOptions.SPACE: |
| buffer.append("SPACE"); //$NON-NLS-1$ |
| break; |
| default: |
| buffer.append("MIXED"); //$NON-NLS-1$ |
| } |
| buffer |
| .append(") - (tabSize = ").append(tabLength).append(')') //$NON-NLS-1$/ |
| .append(lineSeparator) |
| .append("(line = ").append(line).append(") - (column = ").append(column).append(") - (identationLevel = ").append(indentationLevel).append(')') //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| .append(lineSeparator) |
| .append("(needSpace = ").append(needSpace).append(") - (lastNumberOfNewLines = ").append(lastNumberOfNewLines).append(") - (checkLineWrapping = ").append(checkLineWrapping).append(')') //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| .append(lineSeparator).append( |
| "==================================================================================") //$NON-NLS-1$ |
| .append(lineSeparator); |
| printRuler(buffer); |
| return buffer.toString(); |
| } |
| |
| public void unIndent() { |
| if (shouldSkip(scanner.getCurrentPosition())) { |
| fSkippedIndentations--; |
| return; |
| } |
| indentationLevel-= indentationSize; |
| numberOfIndentations--; |
| } |
| |
| /** |
| */ |
| public boolean printModifiers() { |
| int currentTokenStartPosition= scanner.getCurrentPosition(); |
| if (shouldSkip(currentTokenStartPosition)) { |
| return false; |
| } |
| boolean isFirstModifier= true; |
| boolean hasComment= false; |
| while ((currentToken= scanner.nextToken()) != null) { |
| switch (currentToken.type) { |
| case Token.t_typedef: |
| case Token.t_extern: |
| case Token.t_static: |
| case Token.t_auto: |
| case Token.t_register: |
| case Token.t_const: |
| case Token.t_signed: |
| case Token.t_unsigned: |
| case Token.t_volatile: |
| case Token.t_virtual: |
| case Token.t_mutable: |
| case Token.t_explicit: |
| case Token.t_friend: |
| case Token.t_inline: |
| case Token.t_restrict: |
| print(currentToken.getLength(), !isFirstModifier); |
| isFirstModifier= false; |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| break; |
| case Token.tBLOCKCOMMENT: |
| printBlockComment(false); |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| hasComment= true; |
| break; |
| case Token.tLINECOMMENT: |
| printLineComment(); |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| break; |
| case Token.tWHITESPACE: |
| addDeleteEdit(scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()); |
| int count= 0; |
| char[] whiteSpaces= scanner.getCurrentTokenSource(); |
| for (int i= 0, max= whiteSpaces.length; i < max; i++) { |
| switch (whiteSpaces[i]) { |
| case '\r': |
| if ((i + 1) < max) { |
| if (whiteSpaces[i + 1] == '\n') { |
| i++; |
| } |
| } |
| count++; |
| break; |
| case '\n': |
| count++; |
| } |
| } |
| if (count >= 1 && hasComment) { |
| printNewLine(); |
| } |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| hasComment= false; |
| break; |
| case Token.tPREPROCESSOR: |
| case Token.tPREPROCESSOR_DEFINE: |
| case Token.tPREPROCESSOR_INCLUDE: |
| if (column != 1) |
| printNewLine(scanner.getCurrentTokenStartPosition()); |
| printPreprocessorDirective(); |
| printNewLine(); |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| hasComment= false; |
| break; |
| default: |
| if (currentToken.getType() == Token.tIDENTIFIER) { |
| if (currentToken.getText().startsWith("__")) { //$NON-NLS-1$ |
| // assume this is a declspec modifier |
| print(currentToken.getLength(), !isFirstModifier); |
| isFirstModifier= false; |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| if ((currentToken= scanner.nextToken()) != null) { |
| if (currentToken.getType() == Token.tLPAREN) { |
| if (skipToToken(Token.tRPAREN)) { |
| currentToken= scanner.nextToken(); |
| currentToken= scanner.nextToken(); |
| currentTokenStartPosition= scanner.getCurrentPosition(); |
| } |
| } |
| } |
| break; |
| } |
| } |
| // Step back one token |
| scanner.resetTo(currentTokenStartPosition, scannerEndPosition); |
| return !isFirstModifier; |
| } |
| } |
| return !isFirstModifier; |
| } |
| |
| /** |
| * Skips to the next occurrence of the given token type. |
| * If successful, the next token will be the expected token, |
| * otherwise the scanner position is left unchanged. |
| * |
| * @param expectedTokenType |
| * @return <code>true</code> if a matching token was skipped to |
| */ |
| public boolean skipToToken(int expectedTokenType) { |
| int skipStart= scanner.getCurrentPosition(); |
| if (shouldSkip(skipStart)) { |
| return true; |
| } |
| int tokenStart = findToken(expectedTokenType); |
| if (tokenStart < 0) { |
| return false; |
| } |
| printRaw(skipStart, tokenStart - skipStart); |
| currentToken= scanner.nextToken(); |
| scanner.resetTo(tokenStart, scannerEndPosition); |
| return true; |
| } |
| |
| /** |
| * Searches for the next occurrence of the given token type. |
| * If successful, returns the offset of the found token, otherwise -1. |
| * The scanner position is left unchanged. |
| * |
| * @param tokenType type of the token to look for |
| * @return the position of the matching token, if found, otherwise -1. |
| */ |
| public int findToken(int tokenType) { |
| return findToken(tokenType, scannerEndPosition - 1); |
| } |
| |
| /** |
| * Searches for the next occurrence of the given token type. |
| * If successful, returns the offset of the found token, otherwise -1. |
| * The scanner position is left unchanged. |
| * |
| * @param tokenType type of the token to look for |
| * @param endPosition end position limiting the search |
| * @return the position of the matching token, if found, otherwise -1. |
| */ |
| public int findToken(int tokenType, int endPosition) { |
| int startPosition= scanner.getCurrentPosition(); |
| if (startPosition >= endPosition) { |
| return -1; |
| } |
| try { |
| int braceLevel= 0; |
| int parenLevel= 0; |
| switch (tokenType) { |
| case Token.tRBRACE: |
| ++braceLevel; |
| break; |
| case Token.tRPAREN: |
| ++parenLevel; |
| break; |
| } |
| Token token; |
| while ((token= scanner.nextToken()) != null) { |
| if (scanner.getCurrentTokenEndPosition() > endPosition) |
| return -1; |
| |
| switch (token.type) { |
| case Token.tLBRACE: |
| if (tokenType != Token.tLBRACE) { |
| ++braceLevel; |
| } |
| break; |
| case Token.tRBRACE: |
| --braceLevel; |
| break; |
| case Token.tLPAREN: |
| if (tokenType != Token.tLPAREN) { |
| ++parenLevel; |
| } |
| break; |
| case Token.tRPAREN: |
| --parenLevel; |
| break; |
| case Token.tWHITESPACE: |
| case Token.tLINECOMMENT: |
| case Token.tBLOCKCOMMENT: |
| case Token.tPREPROCESSOR: |
| case Token.tPREPROCESSOR_DEFINE: |
| case Token.tPREPROCESSOR_INCLUDE: |
| continue; |
| } |
| if (braceLevel <= 0 && parenLevel <= 0) { |
| if (token.type == tokenType) { |
| return scanner.getCurrentTokenStartPosition(); |
| } |
| } |
| if (braceLevel < 0 || parenLevel < 0) { |
| break; |
| } |
| } |
| } finally { |
| scanner.resetTo(startPosition, scannerEndPosition); |
| } |
| return -1; |
| } |
| |
| /** |
| * Searches for the next occurrence of the given token type. |
| * If successful, returns the offset of the found token, otherwise -1. |
| * The scanner position is left unchanged. |
| * |
| * @param tokenType type of the token to look for |
| * @param startPosition position where to start the search |
| * @param endPosition end position limiting the search |
| * @return the position of the matching token, if found, otherwise -1. |
| */ |
| public int findToken(int tokenType, int startPosition, int endPosition) { |
| int currentPosition= scanner.getCurrentPosition(); |
| try { |
| scanner.resetTo(startPosition, scannerEndPosition); |
| return findToken(tokenType, endPosition); |
| } finally { |
| scanner.resetTo(currentPosition, scannerEndPosition); |
| } |
| } |
| |
| public boolean printCommentPreservingNewLines() { |
| final boolean savedPreserveNL= preserveNewLines; |
| preserveNewLines= true; |
| try { |
| return printComment(); |
| } finally { |
| preserveNewLines= savedPreserveNL; |
| } |
| } |
| |
| boolean shouldSkip(int offset) { |
| return offset >= fSkipStartOffset && offset < fSkipEndOffset; |
| } |
| |
| void skipRange(int offset, int endOffset) { |
| if (offset == fSkipStartOffset) { |
| return; |
| } |
| final int currentPosition= scanner.getCurrentPosition(); |
| if (offset > currentPosition) { |
| fSkipStartOffset = Integer.MAX_VALUE; |
| printRaw(currentPosition, offset - currentPosition); |
| } |
| fSkipStartOffset= offset; |
| fSkipEndOffset= endOffset; |
| } |
| |
| boolean skipRange() { |
| return fSkipEndOffset > 0; |
| } |
| |
| void restartAtOffset(int offset) { |
| final int currentPosition= scanner.getCurrentPosition(); |
| if (fSkipEndOffset > 0) { |
| fSkipStartOffset= Integer.MAX_VALUE; |
| fSkipEndOffset= 0; |
| while (fSkippedIndentations < 0) { |
| unIndent(); |
| fSkippedIndentations++; |
| } |
| if (offset > currentPosition) { |
| printRaw(currentPosition, offset - currentPosition); |
| scanner.resetTo(offset, scannerEndPosition); |
| } |
| while (fSkippedIndentations > 0) { |
| indent(); |
| fSkippedIndentations--; |
| } |
| } else if (offset > currentPosition) { |
| boolean hasSpace= printComment(); |
| final int nextPosition= scanner.getCurrentPosition(); |
| if (offset > nextPosition) { |
| if (hasSpace) { |
| space(); |
| } |
| printRaw(nextPosition, offset - nextPosition); |
| scanner.resetTo(offset, scannerEndPosition); |
| } |
| } |
| } |
| |
| /* |
| * Returns the tail formatter associated with the current alignment or, if there is no current |
| * alignment, with the scribe itself. |
| * @see #tailFormatter |
| */ |
| public Runnable getTailFormatter() { |
| if (currentAlignment != null) { |
| return currentAlignment.tailFormatter; |
| } else { |
| return this.tailFormatter; |
| } |
| } |
| |
| /* |
| * Returns the tail formatter associated with the current alignment or, if there is no current |
| * alignment, with the scribe itself. The tail formatter associated with the alignment or |
| * the scribe is set to {@code null}. |
| * @see #tailFormatter |
| */ |
| public Runnable takeTailFormatter() { |
| Runnable formatter; |
| if (currentAlignment != null) { |
| formatter = currentAlignment.tailFormatter; |
| currentAlignment.tailFormatter = null; |
| } else { |
| formatter = this.tailFormatter; |
| this.tailFormatter = null; |
| } |
| return formatter; |
| } |
| |
| /* |
| * Sets the tail formatter associated with the current alignment or, if there is no current |
| * alignment, with the scribe itself. |
| * @see #tailFormatter |
| */ |
| public void setTailFormatter(Runnable tailFormatter) { |
| if (currentAlignment != null) { |
| currentAlignment.tailFormatter = tailFormatter; |
| } else { |
| this.tailFormatter = tailFormatter; |
| } |
| } |
| |
| /* |
| * Runs the tail formatter associated with the current alignment or, if there is no current |
| * alignment, with the scribe itself. |
| * @see #tailFormatter |
| */ |
| public void runTailFormatter() { |
| Runnable formatter = getTailFormatter(); |
| if (formatter != null) |
| formatter.run(); |
| } |
| } |