/*
* Copyright 2000-2016 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.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.util.containers.ContainerUtil;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AdjustWhiteSpacesState extends State {
private final FormattingProgressCallback myProgressCallback;
private WrapBlocksState myWrapBlocksState;
private LeafBlockWrapper myCurrentBlock;
private DependentSpacingEngine myDependentSpacingEngine;
private WrapProcessor myWrapProcessor;
private BlockRangesMap myBlockRangesMap;
private IndentAdjuster myIndentAdjuster;
private boolean myReformatContext;
private Set<Alignment> myAlignmentsInsideRangesToModify = null;
private final HashSet<WhiteSpace> myAlignAgain = new HashSet<>();
private LeafBlockWrapper myFirstBlock;
public AdjustWhiteSpacesState(WrapBlocksState state,
FormattingProgressCallback progressCallback,
boolean isReformatContext) {
myWrapBlocksState = state;
myProgressCallback = progressCallback;
myReformatContext = isReformatContext;
}
@Override
public void prepare() {
if (myWrapBlocksState != null) {
myFirstBlock = myWrapBlocksState.getFirstBlock();
myCurrentBlock = myFirstBlock;
myDependentSpacingEngine = myWrapBlocksState.getDependentSpacingEngine();
myWrapProcessor = myWrapBlocksState.getWrapProcessor();
myIndentAdjuster = myWrapBlocksState.getIndentAdjuster();
myBlockRangesMap = myWrapBlocksState.getBlockRangesMap();
myAlignmentsInsideRangesToModify = myWrapBlocksState.getAlignmentsInsideRangesToModify();
}
}
public LeafBlockWrapper getCurrentBlock() {
return myCurrentBlock;
}
@Override
public void doIteration() {
LeafBlockWrapper blockToProcess = myCurrentBlock;
processToken();
if (blockToProcess != null) {
myProgressCallback.afterProcessingBlock(blockToProcess);
}
if (myCurrentBlock != null) {
return;
}
if (myAlignAgain.isEmpty()) {
setDone(true);
}
else {
myAlignAgain.clear();
myDependentSpacingEngine.clear();
myCurrentBlock = myFirstBlock;
}
}
private boolean isReformatSelectedRangesContext() {
return myReformatContext && !ContainerUtil.isEmpty(myAlignmentsInsideRangesToModify);
}
private void defineAlignOffset(final LeafBlockWrapper block) {
AbstractBlockWrapper current = myCurrentBlock;
while (true) {
final AlignmentImpl alignment = current.getAlignment();
if (alignment != null) {
alignment.setOffsetRespBlock(block);
}
current = current.getParent();
if (current == null) return;
if (current.getStartOffset() != myCurrentBlock.getStartOffset()) return;
}
}
private void onCurrentLineChanged() {
myWrapProcessor.onCurrentLineChanged();
}
private boolean isCurrentBlockAlignmentUsedInRangesToModify() {
AbstractBlockWrapper block = myCurrentBlock;
AlignmentImpl alignment = myCurrentBlock.getAlignment();
while (alignment == null) {
block = block.getParent();
if (block == null || block.getStartOffset() != myCurrentBlock.getStartOffset()) {
return false;
}
alignment = block.getAlignment();
}
return myAlignmentsInsideRangesToModify.contains(alignment);
}
private static List<TextRange> getDependentRegionRangesAfterCurrentWhiteSpace(final SpacingImpl spaceProperty,
final WhiteSpace whiteSpace) {
if (!(spaceProperty instanceof DependantSpacingImpl)) return ContainerUtil.emptyList();
if (whiteSpace.isReadOnly() || whiteSpace.isLineFeedsAreReadOnly()) return ContainerUtil.emptyList();
DependantSpacingImpl spacing = (DependantSpacingImpl)spaceProperty;
return ContainerUtil.filter(spacing.getDependentRegionRanges(),
dependencyRange -> whiteSpace.getStartOffset() < dependencyRange.getEndOffset());
}
private void processToken() {
final SpacingImpl spaceProperty = myCurrentBlock.getSpaceProperty();
final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
if (isReformatSelectedRangesContext()) {
if (isCurrentBlockAlignmentUsedInRangesToModify() &&
whiteSpace.isReadOnly() &&
spaceProperty != null &&
!spaceProperty.isReadOnly()) {
whiteSpace.setReadOnly(false);
whiteSpace.setLineFeedsAreReadOnly(true);
}
}
whiteSpace.arrangeLineFeeds(spaceProperty, myBlockRangesMap);
if (!whiteSpace.containsLineFeeds()) {
whiteSpace.arrangeSpaces(spaceProperty);
}
try {
LeafBlockWrapper newBlock = myWrapProcessor.processWrap(myCurrentBlock);
if (newBlock != null) {
myCurrentBlock = newBlock;
return;
}
}
finally {
if (whiteSpace.containsLineFeeds()) {
onCurrentLineChanged();
}
}
LeafBlockWrapper newCurrentBlock = myIndentAdjuster.adjustIndent(myCurrentBlock);
if (newCurrentBlock != null) {
myCurrentBlock = newCurrentBlock;
onCurrentLineChanged();
return;
}
defineAlignOffset(myCurrentBlock);
if (myCurrentBlock.containsLineFeeds()) {
onCurrentLineChanged();
}
final List<TextRange> ranges = getDependentRegionRangesAfterCurrentWhiteSpace(spaceProperty, whiteSpace);
if (!ranges.isEmpty()) {
myDependentSpacingEngine.registerUnresolvedDependentSpacingRanges(spaceProperty, ranges);
}
if (!whiteSpace.isIsReadOnly() && myDependentSpacingEngine.shouldReformatPreviouslyLocatedDependentSpacing(whiteSpace)) {
myAlignAgain.add(whiteSpace);
}
else if (!myAlignAgain.isEmpty()) {
myAlignAgain.remove(whiteSpace);
}
myCurrentBlock = myCurrentBlock.getNextBlock();
}
}