blob: 4cea2ab53d748088974d849facdf1a580ed99fc7 [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.diagnostic.LogMessageEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/**
* Gof Template Method for {@link BlockAlignmentProcessor}.
*
* @author Denis Zhdanov
* @since 4/29/11 11:52 AM
*/
public abstract class AbstractBlockAlignmentProcessor implements BlockAlignmentProcessor {
private static final Logger LOG = Logger.getInstance("#" + AbstractBlockAlignmentProcessor.class.getName());
@Override
public Result applyAlignment(@NotNull Context context) {
IndentData indent = calculateAlignmentAnchorIndent(context);
if (indent == null) {
return Result.TARGET_BLOCK_PROCESSED_NOT_ALIGNED;
}
WhiteSpace whiteSpace = context.targetBlock.getWhiteSpace();
if (whiteSpace.containsLineFeeds() && applyIndentToTheFirstBlockOnLine(indent, context)) {
return Result.TARGET_BLOCK_ALIGNED;
}
int diff = getAlignmentIndentDiff(indent, context);
if (diff == 0) {
return Result.TARGET_BLOCK_ALIGNED;
}
if (diff > 0) {
whiteSpace.setSpaces(whiteSpace.getSpaces() + diff, whiteSpace.getIndentSpaces());
// Avoid tabulations usage for aligning blocks that are not the first blocks on a line.
if (!whiteSpace.containsLineFeeds()) {
whiteSpace.setForceSkipTabulationsUsage(true);
}
return Result.TARGET_BLOCK_ALIGNED;
}
if (!context.alignment.isAllowBackwardShift()) {
return Result.TARGET_BLOCK_PROCESSED_NOT_ALIGNED;
}
LeafBlockWrapper offsetResponsibleBlock = context.alignment.getOffsetRespBlockBefore(context.targetBlock);
if (offsetResponsibleBlock == null) {
return Result.TARGET_BLOCK_PROCESSED_NOT_ALIGNED;
}
if (offsetResponsibleBlock.getWhiteSpace().isIsReadOnly()) {
// We're unable to perform backward shift because white space for the target element is read-only.
return Result.UNABLE_TO_ALIGN_BACKWARD_BLOCK;
}
if (!CoreFormatterUtil.allowBackwardAlignment(offsetResponsibleBlock, context.targetBlock, context.alignmentMappings)) {
return Result.UNABLE_TO_ALIGN_BACKWARD_BLOCK;
}
// There is a possible case that alignment options are defined incorrectly. Consider the following example:
// int i1;
// int i2, i3;
// There is a problem if all blocks above use the same alignment - block 'i1' is shifted to right in order to align
// to block 'i3' and reformatting starts back after 'i1'. Now 'i2' is shifted to left as well in order to align to the
// new 'i1' position. That changes 'i3' position as well that causes 'i1' to be shifted right one more time.
// Hence, we have endless cycle here. We remember information about blocks that caused indentation change because of
// alignment of blocks located before them and skip alignment every time we detect an endless cycle.
Set<LeafBlockWrapper> blocksCausedRealignment = context.backwardShiftedAlignedBlocks.get(offsetResponsibleBlock);
if (blocksCausedRealignment != null && blocksCausedRealignment.contains(context.targetBlock)) {
return Result.RECURSION_DETECTED;
}
WhiteSpace previousWhiteSpace = offsetResponsibleBlock.getWhiteSpace();
previousWhiteSpace.setSpaces(previousWhiteSpace.getSpaces() - diff, previousWhiteSpace.getIndentOffset());
// Avoid tabulations usage for aligning blocks that are not the first blocks on a line.
if (!previousWhiteSpace.containsLineFeeds()) {
previousWhiteSpace.setForceSkipTabulationsUsage(true);
}
return Result.BACKWARD_BLOCK_ALIGNED;
}
/**
* Asks to calculate indent used for the anchor block of the alignment used by the {@link Context#targetBlock target block}.
*
* @param context current processing context
* @return indent to use for the white space of the given block
*/
@Nullable
protected abstract IndentData calculateAlignmentAnchorIndent(@NotNull Context context);
/**
* Encapsulates logic of applying alignment anchor indent to the target block that starts new line.
*
* @param alignmentAnchorIndent alignment anchor indent
* @param context current processing context
* @return <code>true</code> if desired alignment indent is applied to the current block;
* <code>false</code> otherwise
*/
protected abstract boolean applyIndentToTheFirstBlockOnLine(@NotNull IndentData alignmentAnchorIndent, @NotNull Context context);
/**
* Calculates the difference between alignment anchor indent and current target block indent.
*
* @param alignmentAnchorIndent alignment anchor indent
* @param context current processing context
* @return alignment anchor indent minus current target block indent
*/
protected abstract int getAlignmentIndentDiff(@NotNull IndentData alignmentAnchorIndent, @NotNull Context context);
}