/*
* Copyright 2000-2015 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.engine;
import com.intellij.formatting.*;
import com.intellij.openapi.editor.Document;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class ExpandChildrenIndentState extends State {
private final Document myDocument;
private final WrapBlocksState myWrapState;
private IndentAdjuster myIndentAdjuster;
private MultiMap<ExpandableIndent, AbstractBlockWrapper> myExpandableIndents;
private LeafBlockWrapper myCurrentBlock;
private Iterator<ExpandableIndent> myIterator;
private MultiMap<Alignment, LeafBlockWrapper> myBlocksToRealign = new MultiMap<>();
public ExpandChildrenIndentState(Document document, WrapBlocksState state) {
myDocument = document;
myWrapState = state;
}
@Override
public void prepare() {
myExpandableIndents = myWrapState.getExpandableIndent();
myIndentAdjuster = myWrapState.getIndentAdjuster();
myIterator = myExpandableIndents.keySet().iterator();
}
@Override
protected void doIteration() {
if (!myIterator.hasNext()) {
setDone(true);
return;
}
final ExpandableIndent indent = myIterator.next();
Collection<AbstractBlockWrapper> blocksToExpandIndent = myExpandableIndents.get(indent);
if (shouldExpand(blocksToExpandIndent)) {
for (AbstractBlockWrapper block : blocksToExpandIndent) {
indent.setEnforceIndent(true);
reindentNewLineChildren(block);
indent.setEnforceIndent(false);
}
}
restoreAlignments(myBlocksToRealign);
myBlocksToRealign.clear();
}
private void restoreAlignments(MultiMap<Alignment, LeafBlockWrapper> blocks) {
for (Alignment alignment : blocks.keySet()) {
AlignmentImpl alignmentImpl = (AlignmentImpl)alignment;
if (!alignmentImpl.isAllowBackwardShift()) continue;
Set<LeafBlockWrapper> toRealign = alignmentImpl.getOffsetResponsibleBlocks();
arrangeSpaces(toRealign);
LeafBlockWrapper rightMostBlock = getRightMostBlock(toRealign);
int maxSpacesBeforeBlock = rightMostBlock.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
int rightMostBlockLine = myDocument.getLineNumber(rightMostBlock.getStartOffset());
for (LeafBlockWrapper block : toRealign) {
int currentBlockLine = myDocument.getLineNumber(block.getStartOffset());
if (currentBlockLine == rightMostBlockLine) continue;
int blockIndent = block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
int delta = maxSpacesBeforeBlock - blockIndent;
if (delta > 0) {
int newSpaces = block.getWhiteSpace().getTotalSpaces() + delta;
adjustSpacingToKeepAligned(block, newSpaces);
}
}
}
}
private static void adjustSpacingToKeepAligned(LeafBlockWrapper block, int newSpaces) {
WhiteSpace space = block.getWhiteSpace();
SpacingImpl property = block.getSpaceProperty();
if (property == null) return;
space.arrangeSpaces(new SpacingImpl(newSpaces, newSpaces,
property.getMinLineFeeds(),
property.isReadOnly(),
property.isSafe(),
property.shouldKeepLineFeeds(),
property.getKeepBlankLines(),
property.shouldKeepFirstColumn(),
property.getPrefLineFeeds()));
}
private static LeafBlockWrapper getRightMostBlock(Collection<LeafBlockWrapper> toRealign) {
int maxSpacesBeforeBlock = -1;
LeafBlockWrapper rightMostBlock = null;
for (LeafBlockWrapper block : toRealign) {
int spaces = block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
if (spaces > maxSpacesBeforeBlock) {
maxSpacesBeforeBlock = spaces;
rightMostBlock = block;
}
}
return rightMostBlock;
}
private static void arrangeSpaces(Collection<LeafBlockWrapper> toRealign) {
for (LeafBlockWrapper block : toRealign) {
WhiteSpace whiteSpace = block.getWhiteSpace();
SpacingImpl spacing = block.getSpaceProperty();
whiteSpace.arrangeSpaces(spacing);
}
}
private static boolean shouldExpand(Collection<AbstractBlockWrapper> blocksToExpandIndent) {
AbstractBlockWrapper last = null;
for (AbstractBlockWrapper block : blocksToExpandIndent) {
if (block.getWhiteSpace().containsLineFeeds()) {
return true;
}
last = block;
}
if (last != null) {
AbstractBlockWrapper next = getNextBlock(last);
if (next != null && next.getWhiteSpace().containsLineFeeds()) {
int nextNewLineBlockIndent = next.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
if (nextNewLineBlockIndent >= finMinNewLineIndent(blocksToExpandIndent)) {
return true;
}
}
}
return false;
}
private static int finMinNewLineIndent(@NotNull Collection<AbstractBlockWrapper> wrappers) {
int totalMinimum = Integer.MAX_VALUE;
for (AbstractBlockWrapper wrapper : wrappers) {
int minNewLineIndent = findMinNewLineIndent(wrapper);
if (minNewLineIndent < totalMinimum) {
totalMinimum = minNewLineIndent;
}
}
return totalMinimum;
}
private static int findMinNewLineIndent(@NotNull AbstractBlockWrapper block) {
if (block instanceof LeafBlockWrapper && block.getWhiteSpace().containsLineFeeds()) {
return block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
}
else if (block instanceof CompositeBlockWrapper) {
List<AbstractBlockWrapper> children = ((CompositeBlockWrapper)block).getChildren();
int currentMin = Integer.MAX_VALUE;
for (AbstractBlockWrapper child : children) {
int childIndent = findMinNewLineIndent(child);
if (childIndent < currentMin) {
currentMin = childIndent;
}
}
return currentMin;
}
return Integer.MAX_VALUE;
}
private static AbstractBlockWrapper getNextBlock(AbstractBlockWrapper block) {
List<AbstractBlockWrapper> children = block.getParent().getChildren();
int nextBlockIndex = children.indexOf(block) + 1;
if (nextBlockIndex < children.size()) {
return children.get(nextBlockIndex);
}
return null;
}
private void reindentNewLineChildren(final @NotNull AbstractBlockWrapper block) {
if (block instanceof LeafBlockWrapper) {
WhiteSpace space = block.getWhiteSpace();
if (space.containsLineFeeds()) {
myCurrentBlock = (LeafBlockWrapper)block;
myIndentAdjuster.adjustIndent(myCurrentBlock); //since aligned block starts new line, it should not touch any other block
storeAlignmentsAfterCurrentBlock();
}
}
else if (block instanceof CompositeBlockWrapper) {
List<AbstractBlockWrapper> children = ((CompositeBlockWrapper)block).getChildren();
for (AbstractBlockWrapper childBlock : children) {
reindentNewLineChildren(childBlock);
}
}
}
private void storeAlignmentsAfterCurrentBlock() {
LeafBlockWrapper current = myCurrentBlock.getNextBlock();
while (current != null && !current.getWhiteSpace().containsLineFeeds()) {
if (current.getAlignment() != null) {
myBlocksToRealign.putValue(current.getAlignment(), current);
}
current = current.getNextBlock();
}
}
}