| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.formatting; |
| |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.codeStyle.CommonCodeStyleSettings; |
| import org.jetbrains.annotations.Nullable; |
| |
| class LeafBlockWrapper extends AbstractBlockWrapper { |
| private static final int CONTAIN_LINE_FEEDS = 4; |
| private static final int READ_ONLY = 8; |
| private static final int LEAF = 16; |
| |
| private final int mySymbolsAtTheLastLine; |
| private LeafBlockWrapper myPreviousBlock; |
| private LeafBlockWrapper myNextBlock; |
| private SpacingImpl mySpaceProperty; |
| |
| /** |
| * Shortcut for calling |
| * {@link #LeafBlockWrapper(Block, CompositeBlockWrapper, WhiteSpace, FormattingDocumentModel, CommonCodeStyleSettings.IndentOptions, LeafBlockWrapper, boolean, TextRange)} |
| * with {@link Block#getTextRange() text range associated with the given block}. |
| * |
| * @param block block to wrap |
| * @param parent wrapped parent block |
| * @param whiteSpaceBefore white space before the target block to wrap |
| * @param model formatting model to use during current wrapper initialization |
| * @param options code formatting options |
| * @param previousTokenBlock previous token block |
| * @param isReadOnly flag that indicates if target block is read-only |
| */ |
| public LeafBlockWrapper(final Block block, |
| @Nullable CompositeBlockWrapper parent, |
| WhiteSpace whiteSpaceBefore, |
| FormattingDocumentModel model, |
| CommonCodeStyleSettings.IndentOptions options, |
| LeafBlockWrapper previousTokenBlock, |
| boolean isReadOnly) |
| { |
| this(block, parent, whiteSpaceBefore, model, options, previousTokenBlock, isReadOnly, block.getTextRange()); |
| } |
| |
| public LeafBlockWrapper(final Block block, |
| CompositeBlockWrapper parent, |
| WhiteSpace whiteSpaceBefore, |
| FormattingDocumentModel model, |
| CommonCodeStyleSettings.IndentOptions options, |
| LeafBlockWrapper previousTokenBlock, |
| boolean isReadOnly, |
| final TextRange textRange) |
| { |
| super(block, whiteSpaceBefore, parent, textRange); |
| myPreviousBlock = previousTokenBlock; |
| final int lastLineNumber = model.getLineNumber(textRange.getEndOffset()); |
| |
| int flagsValue = myFlags; |
| final boolean containsLineFeeds = model.getLineNumber(textRange.getStartOffset()) != lastLineNumber; |
| flagsValue |= containsLineFeeds ? CONTAIN_LINE_FEEDS:0; |
| |
| // We need to perform such a complex calculation because block construction algorithm is allowed to create 'leaf' blocks |
| // that contain more than one token interleaved white space that contains either tabulations or line breaks. |
| // E.g. consider the following code: |
| // |
| // public |
| // void foo() {} |
| // |
| // 'public void' here is a single 'leaf' token and it's second part 'void' is preceeded by tabulaton symbol. Hence, we need |
| // correctly calculate number of symbols occupied by the current token at last line. |
| int start = containsLineFeeds ? model.getLineStartOffset(lastLineNumber) : textRange.getStartOffset(); |
| int symbols = 0; |
| CharSequence text = model.getDocument().getCharsSequence(); |
| for (int i = start; i < textRange.getEndOffset(); i++) { |
| if (text.charAt(i) == '\t') { |
| symbols += options.TAB_SIZE; |
| } else { |
| symbols++; |
| } |
| } |
| mySymbolsAtTheLastLine = symbols; |
| flagsValue |= isReadOnly ? READ_ONLY:0; |
| final boolean isLeaf = block.isLeaf(); |
| flagsValue |= isLeaf ? LEAF : 0; |
| |
| myFlags = flagsValue; |
| } |
| |
| public final boolean containsLineFeeds() { |
| return (myFlags & CONTAIN_LINE_FEEDS) != 0; |
| } |
| |
| public int getSymbolsAtTheLastLine() { |
| return mySymbolsAtTheLastLine; |
| } |
| |
| @Override |
| public LeafBlockWrapper getPreviousBlock() { |
| return myPreviousBlock; |
| } |
| |
| public LeafBlockWrapper getNextBlock() { |
| return myNextBlock; |
| } |
| |
| public void setNextBlock(final LeafBlockWrapper nextBlock) { |
| myNextBlock = nextBlock; |
| } |
| |
| @Override |
| protected boolean indentAlreadyUsedBefore(final AbstractBlockWrapper child) { |
| return false; |
| } |
| |
| @Override |
| protected IndentData getNumberOfSymbolsBeforeBlock() { |
| int spaces = getWhiteSpace().getSpaces(); |
| int indentSpaces = getWhiteSpace().getIndentSpaces(); |
| |
| if (getWhiteSpace().containsLineFeeds()) { |
| return new IndentData(indentSpaces, spaces); |
| } |
| |
| for (LeafBlockWrapper current = this.getPreviousBlock(); current != null; current = current.getPreviousBlock()) { |
| spaces += current.getWhiteSpace().getSpaces(); |
| spaces += current.getSymbolsAtTheLastLine(); |
| indentSpaces += current.getWhiteSpace().getIndentSpaces(); |
| if (current.getWhiteSpace().containsLineFeeds()) { |
| break; |
| } |
| } |
| return new IndentData(indentSpaces, spaces); |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| myPreviousBlock = null; |
| myNextBlock = null; |
| mySpaceProperty = null; |
| } |
| |
| /** |
| * @return spacing between current block and its left sibling |
| */ |
| public SpacingImpl getSpaceProperty() { |
| return mySpaceProperty; |
| } |
| |
| public IndentData calculateOffset(final CommonCodeStyleSettings.IndentOptions options) { |
| // Calculate result as an indent of current block from parent plus parent block indent. |
| if (myIndentFromParent != null) { |
| final AbstractBlockWrapper firstIndentedParent = findFirstIndentedParent(); |
| final IndentData indentData = new IndentData(myIndentFromParent.getIndentSpaces(), myIndentFromParent.getSpaces()); |
| if (firstIndentedParent == null) { |
| return indentData; |
| } else { |
| final WhiteSpace whiteSpace = firstIndentedParent.getWhiteSpace(); |
| return new IndentData(whiteSpace.getIndentOffset(), whiteSpace.getSpaces()).add(indentData); |
| } |
| } |
| |
| // Consider that current block is not indented if it doesn't have a parent block. |
| if (myParent == null) return new IndentData(0); |
| |
| // Define that current block and all its parents that start at the same offset can't use first child indent as block indent. |
| if (getIndent().isAbsolute()) { |
| setCanUseFirstChildIndentAsBlockIndent(false); |
| AbstractBlockWrapper current = this; |
| while (current != null && current.getStartOffset() == getStartOffset()) { |
| current.setCanUseFirstChildIndentAsBlockIndent(false); |
| current = current.myParent; |
| } |
| } |
| |
| return myParent.getChildOffset(this, options, this.getStartOffset()); |
| } |
| |
| public void setSpaceProperty(@Nullable final SpacingImpl currentSpaceProperty) { |
| mySpaceProperty = currentSpaceProperty; |
| } |
| |
| @Nullable |
| public IndentInfo calcIndentFromParent() { |
| AbstractBlockWrapper firstIndentedParent = findFirstIndentedParent(); |
| final WhiteSpace mySpace = getWhiteSpace(); |
| if (firstIndentedParent != null) { |
| final WhiteSpace parentSpace = firstIndentedParent.getWhiteSpace(); |
| return new IndentInfo(0, |
| mySpace.getIndentOffset() - parentSpace.getIndentOffset(), |
| mySpace.getSpaces() - parentSpace.getSpaces()); |
| } else { |
| return null; |
| } |
| } |
| |
| public final boolean isLeaf() { |
| return (myFlags & LEAF) != 0; |
| } |
| |
| public boolean contains(final int offset) { |
| return myStart < offset && myEnd > offset; |
| } |
| |
| public TextRange getTextRange() { |
| return new TextRange(myStart, myEnd); |
| } |
| } |