| /* |
| * 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); |
| } |