/*
* 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.DependantSpacingImpl;
import com.intellij.formatting.SpacingImpl;
import com.intellij.formatting.WhiteSpace;
import com.intellij.openapi.util.TextRange;
import java.util.*;
/**
* Formatter provides a notion of {@link DependantSpacingImpl dependent spacing}, i.e. spacing that insist on line feed if target
* dependent region contains line feed.
* <p/>
* Example:
* <pre>
* int[] data = {1, 2, 3};
* </pre>
* We want to keep that in one line if possible but place curly braces on separate lines if the width is not enough:
* <pre>
* int[] data = { | < right margin
* 1, 2, 3 |
* } |
* </pre>
* There is a possible case that particular block has dependent spacing property that targets region that lays beyond the
* current block. E.g. consider example above - {@code '1'} block has dependent spacing that targets the whole
* {@code '{1, 2, 3}'} block. So, it's not possible to answer whether line feed should be used during processing block
* {@code '1'}.
* <p/>
* We store such 'forward dependencies' at the current collection where the key is the range of the target 'dependent forward
* region' and value is dependent spacing object.
* <p/>
* Every time we detect that formatter changes 'has line feeds' status of such dependent region, we
* {@link DependantSpacingImpl#setDependentRegionLinefeedStatusChanged() mark} the dependent spacing as changed and schedule one more
* formatting iteration.
*/
public class DependentSpacingEngine {
private final BlockRangesMap myBlockRangesMap;
private SortedMap<TextRange, DependantSpacingImpl> myPreviousDependencies =
new TreeMap<>((o1, o2) -> {
int offsetsDelta = o1.getEndOffset() - o2.getEndOffset();
if (offsetsDelta == 0) {
offsetsDelta = o2.getStartOffset() - o1.getStartOffset(); // starting earlier is greater
}
return offsetsDelta;
});
public DependentSpacingEngine(BlockRangesMap helper) {
myBlockRangesMap = helper;
}
public boolean shouldReformatPreviouslyLocatedDependentSpacing(WhiteSpace space) {
final TextRange changed = space.getTextRange();
final SortedMap<TextRange, DependantSpacingImpl> sortedHeadMap = myPreviousDependencies.tailMap(changed);
for (final Map.Entry<TextRange, DependantSpacingImpl> entry : sortedHeadMap.entrySet()) {
final TextRange textRange = entry.getKey();
if (textRange.contains(changed)) {
final DependantSpacingImpl spacing = entry.getValue();
if (spacing.isDependentRegionLinefeedStatusChanged()) {
continue;
}
final boolean containedLineFeeds = spacing.getMinLineFeeds() > 0;
final boolean containsLineFeeds = myBlockRangesMap.containsLineFeeds(textRange);
if (containedLineFeeds != containsLineFeeds) {
spacing.setDependentRegionLinefeedStatusChanged();
return true;
}
}
}
return false;
}
public void registerUnresolvedDependentSpacingRanges(final SpacingImpl spaceProperty, List<TextRange> unprocessedRanges) {
final DependantSpacingImpl dependantSpaceProperty = (DependantSpacingImpl)spaceProperty;
if (dependantSpaceProperty.isDependentRegionLinefeedStatusChanged()) return;
for (TextRange range: unprocessedRanges) {
myPreviousDependencies.put(range, dependantSpaceProperty);
}
}
public void clear() {
myPreviousDependencies.clear();
}
}