blob: 2b1b2f93a163dba4486a32faca3d4e2fd386c1b0 [file] [log] [blame]
/*
* 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);
}
}