blob: b9b8bbf3eb8398c0f6943b7ddf3db30c8738d05f [file] [log] [blame]
/*
* Copyright 2000-2011 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.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Contains utility methods for core formatter processing.
*
* @author Denis Zhdanov
* @since 4/28/11 4:16 PM
*/
public class CoreFormatterUtil {
private CoreFormatterUtil() {
}
@NotNull
public static FormattingModel buildModel(@NotNull FormattingModelBuilder builder,
@NotNull PsiElement element,
@NotNull CodeStyleSettings settings,
@NotNull FormattingMode mode) {
if (builder instanceof FormattingModelBuilderEx) {
return ((FormattingModelBuilderEx)builder).createModel(element, settings, mode);
}
else {
return builder.createModel(element, settings);
}
}
/**
* Checks if there is an {@link AlignmentImpl} object that should be used during adjusting
* {@link AbstractBlockWrapper#getWhiteSpace() white space} of the given block.
*
* @param block target block
* @return alignment object to use during adjusting white space of the given block if any; <code>null</code> otherwise
*/
@Nullable
public static AlignmentImpl getAlignment(final @NotNull AbstractBlockWrapper block) {
AbstractBlockWrapper current = block;
while (true) {
AlignmentImpl alignment = current.getAlignment();
if (alignment == null || alignment.getOffsetRespBlockBefore(block) == null) {
current = current.getParent();
if (current == null || current.getStartOffset() != block.getStartOffset()) {
return null;
}
}
else {
return alignment;
}
}
}
/**
* Calculates number of non-line feed symbols before the given wrapped block.
* <p/>
* <b>Example:</b>
* <pre>
* whitespace<sub>11</sub> block<sub>11</sub> whitespace<sub>12</sub> block<sub>12</sub>
* whitespace<sub>21</sub> block<sub>21</sub> whitespace<sub>22</sub> block<sub>22</sub>
* </pre>
* <p/>
* Suppose this method is called with the wrapped <code>'block<sub>22</sub>'</code> and <code>'whitespace<sub>21</sub>'</code>
* contains line feeds but <code>'whitespace<sub>22</sub>'</code> is not. This method returns number of symbols
* from <code>'whitespace<sub>21</sub>'</code> after its last line feed symbol plus number of symbols at
* <code>block<sub>21</sub></code> plus number of symbols at <code>whitespace<sub>22</sub></code>.
*
* @param block target wrapped block to be used at a boundary during counting non-line feed symbols to the left of it
* @return non-line feed symbols to the left of the given wrapped block
*/
public static int getStartColumn(@Nullable LeafBlockWrapper block) {
if (block != null) {
int result = 0;
while (true) {
final WhiteSpace whiteSpace = block.getWhiteSpace();
result += whiteSpace.getTotalSpaces();
if (whiteSpace.containsLineFeeds()) {
return result;
}
block = block.getPreviousBlock();
if (block == null) return result;
result += block.getSymbolsAtTheLastLine();
if (block.containsLineFeeds()) return result;
}
}
else {
return -1;
}
}
/**
* Tries to find the closest block that starts before the given block and contains line feeds.
*
* @return closest block to the given block that contains line feeds if any; <code>null</code> otherwise
*/
@Nullable
public static AbstractBlockWrapper getIndentedParentBlock(@NotNull AbstractBlockWrapper block) {
AbstractBlockWrapper current = block.getParent();
while (current != null) {
if (current.getStartOffset() != block.getStartOffset() && current.getWhiteSpace().containsLineFeeds()) return current;
if (current.getParent() != null) {
AbstractBlockWrapper prevIndented = current.getParent().getPrevIndentedSibling(current);
if (prevIndented != null) return prevIndented;
}
current = current.getParent();
}
return null;
}
/**
* It's possible to configure alignment in a way to allow
* {@link AlignmentFactory#createAlignment(boolean, Alignment.Anchor)} backward shift}.
* <p/>
* <b>Example:</b>
* <pre>
* class Test {
* int i;
* StringBuilder buffer;
* }
* </pre>
* <p/>
* It's possible that blocks <code>'i'</code> and <code>'buffer'</code> should be aligned. As formatter processes document from
* start to end that means that requirement to shift block <code>'i'</code> to the right is discovered only during
* <code>'buffer'</code> block processing. I.e. formatter returns to the previously processed block (<code>'i'</code>), modifies
* its white space and continues from that location (performs 'backward' shift).
* <p/>
* Here is one very important moment - there is a possible case that formatting blocks are configured in a way that they are
* combined in explicit cyclic graph.
* <p/>
* <b>Example:</b>
* <pre>
* blah(bleh(blih,
* bloh), bluh);
* </pre>
* <p/>
* Consider that pairs of blocks <code>'blih'; 'bloh'</code> and <code>'bleh', 'bluh'</code> should be aligned
* and backward shift is possible for them. Here is how formatter works:
* <ol>
* <li>
* Processing reaches <b>'bloh'</b> block. It's aligned to <code>'blih'</code> block. Current document state:
* <p/>
* <pre>
* blah(bleh(blih,
* bloh), bluh);
* </pre>
* </li>
* <li>
* Processing reaches <b>'bluh'</b> block. It's aligned to <code>'blih'</code> block and backward shift is allowed, hence,
* <code>'blih'</code> block is moved to the right and processing contnues from it. Current document state:
* <pre>
* blah( bleh(blih,
* bloh), bluh);
* </pre>
* </li>
* <li>
* Processing reaches <b>'bloh'</b> block. It's configured to be aligned to <code>'blih'</code> block, hence, it's moved
* to the right:
* <pre>
* blah( bleh(blih,
* bloh), bluh);
* </pre>
* </li>
* <li>We have endless loop then;</li>
* </ol>
* So, that implies that we can't use backward alignment if the blocks are configured in a way that backward alignment
* appliance produces endless loop. This method encapsulates the logic for checking if backward alignment can be applied.
*
* @param first the first aligned block
* @param second the second aligned block
* @param alignmentMappings block aligned mappings info
* @return <code>true</code> if backward alignment is possible; <code>false</code> otherwise
*/
public static boolean allowBackwardAlignment(@NotNull LeafBlockWrapper first, @NotNull LeafBlockWrapper second,
@NotNull Map<AbstractBlockWrapper, Set<AbstractBlockWrapper>> alignmentMappings)
{
Set<AbstractBlockWrapper> blocksBeforeCurrent = new HashSet<AbstractBlockWrapper>();
for (
LeafBlockWrapper previousBlock = second.getPreviousBlock();
previousBlock != null;
previousBlock = previousBlock.getPreviousBlock())
{
Set<AbstractBlockWrapper> blocks = alignmentMappings.get(previousBlock);
if (blocks != null) {
blocksBeforeCurrent.addAll(blocks);
}
if (previousBlock.getWhiteSpace().containsLineFeeds()) {
break;
}
}
for (
LeafBlockWrapper next = first.getNextBlock();
next != null && !next.getWhiteSpace().containsLineFeeds();
next = next.getNextBlock())
{
if (blocksBeforeCurrent.contains(next)) {
return false;
}
}
return true;
}
/**
* Calculates indent for the given block and target start offset according to the given indent options.
*
* @param options indent options to use
* @param block target wrapped block
* @param tokenBlockStartOffset target wrapped block offset
* @return indent to use for the given parameters
*/
public static IndentData getIndent(CommonCodeStyleSettings.IndentOptions options, AbstractBlockWrapper block,
final int tokenBlockStartOffset)
{
final IndentImpl indent = block.getIndent();
if (indent.getType() == Indent.Type.CONTINUATION) {
return new IndentData(options.CONTINUATION_INDENT_SIZE);
}
if (indent.getType() == Indent.Type.CONTINUATION_WITHOUT_FIRST) {
if (block.getStartOffset() != block.getParent().getStartOffset() && block.getStartOffset() == tokenBlockStartOffset) {
return new IndentData(options.CONTINUATION_INDENT_SIZE);
}
else {
return new IndentData(0);
}
}
if (indent.getType() == Indent.Type.LABEL) return new IndentData(options.LABEL_INDENT_SIZE);
if (indent.getType() == Indent.Type.NONE) return new IndentData(0);
if (indent.getType() == Indent.Type.SPACES) return new IndentData(0, indent.getSpaces());
return new IndentData(options.INDENT_SIZE);
}
@NotNull
public static LeafBlockWrapper getFirstLeaf(@NotNull AbstractBlockWrapper block) {
if (block instanceof LeafBlockWrapper) {
return (LeafBlockWrapper)block;
}
else {
return getFirstLeaf(((CompositeBlockWrapper)block).getChildren().get(0));
}
}
}