mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge branch 'formatter-engine-refactoring'
This commit is contained in:
@@ -337,7 +337,7 @@ public abstract class AbstractBlockWrapper {
|
||||
*
|
||||
* @return object that encapsulates information about number of symbols before the current block
|
||||
*/
|
||||
protected abstract IndentData getNumberOfSymbolsBeforeBlock();
|
||||
public abstract IndentData getNumberOfSymbolsBeforeBlock();
|
||||
|
||||
/**
|
||||
* @return previous block for the current block if any; <code>null</code> otherwise
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class AlignmentImpl extends Alignment {
|
||||
public class AlignmentImpl extends Alignment {
|
||||
private static final List<LeafBlockWrapper> EMPTY = Collections.emptyList();
|
||||
private final boolean myAllowBackwardShift;
|
||||
private final Anchor myAnchor;
|
||||
@@ -110,7 +110,7 @@ class AlignmentImpl extends Alignment {
|
||||
* {@link #setParent(Alignment) its parent} using the algorithm above if any; <code>null</code> otherwise
|
||||
*/
|
||||
@Nullable
|
||||
LeafBlockWrapper getOffsetRespBlockBefore(@Nullable final AbstractBlockWrapper block) {
|
||||
public LeafBlockWrapper getOffsetRespBlockBefore(@Nullable final AbstractBlockWrapper block) {
|
||||
if (!continueOffsetResponsibleBlockRetrieval(block)) {
|
||||
return null;
|
||||
}
|
||||
@@ -135,7 +135,7 @@ class AlignmentImpl extends Alignment {
|
||||
*
|
||||
* @param block wrapped block to register within the current alignment object
|
||||
*/
|
||||
void setOffsetRespBlock(final LeafBlockWrapper block) {
|
||||
public void setOffsetRespBlock(final LeafBlockWrapper block) {
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public class CompositeBlockWrapper extends AbstractBlockWrapper{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IndentData getNumberOfSymbolsBeforeBlock() {
|
||||
public IndentData getNumberOfSymbolsBeforeBlock() {
|
||||
if (myChildren == null || myChildren.isEmpty()) {
|
||||
return new IndentData(0, 0);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.formatting.engine.BlockRangesMap;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -88,14 +89,14 @@ public class DependantSpacingImpl extends SpacingImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(FormatProcessor formatter) {
|
||||
public void refresh(BlockRangesMap helper) {
|
||||
if (isDependentRegionLinefeedStatusChanged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean atLeastOneDependencyRangeContainsLf = false;
|
||||
for (TextRange dependency : myDependentRegionRanges) {
|
||||
atLeastOneDependencyRangeContainsLf |= formatter.containsLineFeeds(dependency);
|
||||
atLeastOneDependencyRangeContainsLf |= helper.containsLineFeeds(dependency);
|
||||
}
|
||||
|
||||
if (atLeastOneDependencyRangeContainsLf) myFlags |= DEPENDENCE_CONTAINS_LF_MASK;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.formatting.engine.ExpandableIndent;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
@@ -181,8 +182,6 @@ public class FormatterImpl extends FormatterEx
|
||||
FormatProcessor processor = new FormatProcessor(
|
||||
model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges, FormattingProgressCallback.EMPTY
|
||||
);
|
||||
processor.setJavaIndentOptions(javaIndentOptions);
|
||||
|
||||
processor.format(model);
|
||||
return processor;
|
||||
}
|
||||
@@ -306,21 +305,6 @@ public class FormatterImpl extends FormatterEx
|
||||
execute(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute given sequential formatting task. Two approaches are possible:
|
||||
* <pre>
|
||||
* <ul>
|
||||
* <li>
|
||||
* <b>synchronous</b> - the task is completely executed during the current method processing;
|
||||
* </li>
|
||||
* <li>
|
||||
* <b>asynchronous</b> - the task is executed at background thread under the progress dialog;
|
||||
* </li>
|
||||
* </ul>
|
||||
* </pre>
|
||||
*
|
||||
* @param task task to execute
|
||||
*/
|
||||
private void execute(@NotNull SequentialTask task) {
|
||||
disableFormatting();
|
||||
Application application = ApplicationManager.getApplication();
|
||||
@@ -363,7 +347,7 @@ public class FormatterImpl extends FormatterEx
|
||||
final FormatProcessor processor = buildProcessorAndWrapBlocks(
|
||||
model, block, settings, indentOptions, new FormatTextRanges(affectedRange, true)
|
||||
);
|
||||
final LeafBlockWrapper blockBefore = processor.getBlockAtOrAfter(affectedRange.getStartOffset());
|
||||
final LeafBlockWrapper blockBefore = processor.getBlockRangesMap().getBlockAtOrAfter(affectedRange.getStartOffset());
|
||||
LOG.assertTrue(blockBefore != null);
|
||||
WhiteSpace whiteSpace = blockBefore.getWhiteSpace();
|
||||
LOG.assertTrue(whiteSpace != null);
|
||||
@@ -473,7 +457,7 @@ public class FormatterImpl extends FormatterEx
|
||||
documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
|
||||
);
|
||||
|
||||
final LeafBlockWrapper blockAfterOffset = processor.getBlockAtOrAfter(offset);
|
||||
final LeafBlockWrapper blockAfterOffset = processor.getBlockRangesMap().getBlockAtOrAfter(offset);
|
||||
|
||||
if (blockAfterOffset != null && blockAfterOffset.contains(offset)) {
|
||||
return offset;
|
||||
@@ -492,40 +476,14 @@ public class FormatterImpl extends FormatterEx
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to
|
||||
* {@link #buildProcessorAndWrapBlocks(FormattingDocumentModel, Block, CodeStyleSettings, CommonCodeStyleSettings.IndentOptions, FormatTextRanges, int)}
|
||||
* with '-1' as an interested offset.
|
||||
*
|
||||
* @param docModel
|
||||
* @param rootBlock
|
||||
* @param settings
|
||||
* @param indentOptions
|
||||
* @param affectedRanges
|
||||
* @return
|
||||
*/
|
||||
private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
|
||||
Block rootBlock,
|
||||
CodeStyleSettings settings,
|
||||
CommonCodeStyleSettings.IndentOptions indentOptions,
|
||||
@Nullable FormatTextRanges affectedRanges)
|
||||
{
|
||||
@Nullable FormatTextRanges affectedRanges) {
|
||||
return buildProcessorAndWrapBlocks(docModel, rootBlock, settings, indentOptions, affectedRanges, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds {@link FormatProcessor} instance and asks it to wrap all {@link Block code blocks}
|
||||
* {@link FormattingModel#getRootBlock() derived from the given model}.
|
||||
*
|
||||
* @param docModel target model
|
||||
* @param rootBlock root block to process
|
||||
* @param settings code style settings to use
|
||||
* @param indentOptions indent options to use
|
||||
* @param affectedRanges ranges to reformat
|
||||
* @param interestingOffset interesting offset; <code>'-1'</code> if no particular offset has a special interest
|
||||
* @return format processor instance with wrapped {@link Block code blocks}
|
||||
*/
|
||||
@SuppressWarnings({"StatementWithEmptyBody"})
|
||||
|
||||
private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
|
||||
Block rootBlock,
|
||||
CodeStyleSettings settings,
|
||||
@@ -599,7 +557,7 @@ public class FormatterImpl extends FormatterEx
|
||||
final FormatProcessor processor = buildProcessorAndWrapBlocks(
|
||||
documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
|
||||
);
|
||||
final LeafBlockWrapper blockAfterOffset = processor.getBlockAtOrAfter(offset);
|
||||
final LeafBlockWrapper blockAfterOffset = processor.getBlockRangesMap().getBlockAtOrAfter(offset);
|
||||
|
||||
if (blockAfterOffset != null && !blockAfterOffset.contains(offset)) {
|
||||
final WhiteSpace whiteSpace = blockAfterOffset.getWhiteSpace();
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.intellij.formatting;
|
||||
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
class IndentImpl extends Indent {
|
||||
public class IndentImpl extends Indent {
|
||||
private final boolean myIsAbsolute;
|
||||
private final boolean myRelativeToDirectParent;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class IndentInside {
|
||||
public class IndentInside {
|
||||
public int whiteSpaces = 0;
|
||||
public int tabs = 0;
|
||||
|
||||
@@ -66,7 +66,7 @@ class IndentInside {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static IndentInside createIndentOn(@Nullable final CharSequence lastLine) {
|
||||
public static IndentInside createIndentOn(@Nullable final CharSequence lastLine) {
|
||||
final IndentInside result = new IndentInside();
|
||||
if (lastLine == null) {
|
||||
return result;
|
||||
@@ -79,7 +79,7 @@ class IndentInside {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static CharSequence getLastLine(@NotNull final CharSequence text) {
|
||||
public static CharSequence getLastLine(@NotNull final CharSequence text) {
|
||||
int i = CharArrayUtil.shiftBackwardUntil(text, text.length() - 1, "\n");
|
||||
if (i < 0) {
|
||||
return text;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.diagnostic.LogMessageEx;
|
||||
import com.intellij.formatting.engine.ExpandableIndent;
|
||||
import com.intellij.lang.LanguageFormatting;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -46,7 +47,7 @@ import java.util.Set;
|
||||
* Allows to build {@link AbstractBlockWrapper formatting block wrappers} for the target {@link Block formatting blocks}.
|
||||
* The main idea of block wrapping is to associate information about {@link WhiteSpace white space before block} with the block itself.
|
||||
*/
|
||||
class InitialInfoBuilder {
|
||||
public class InitialInfoBuilder {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.InitialInfoBuilder");
|
||||
|
||||
private final Map<AbstractBlockWrapper, Block> myResult = new THashMap<AbstractBlockWrapper, Block>();
|
||||
@@ -98,15 +99,16 @@ class InitialInfoBuilder {
|
||||
myFormatterTagHandler = new FormatterTagHandler(settings);
|
||||
}
|
||||
|
||||
protected static InitialInfoBuilder prepareToBuildBlocksSequentially(Block root,
|
||||
FormattingDocumentModel model,
|
||||
@Nullable final FormatTextRanges affectedRanges,
|
||||
@NotNull CodeStyleSettings settings,
|
||||
final CommonCodeStyleSettings.IndentOptions options,
|
||||
int interestingOffset,
|
||||
@NotNull FormattingProgressCallback progressCallback)
|
||||
protected static InitialInfoBuilder prepareToBuildBlocksSequentially(
|
||||
Block root,
|
||||
FormattingDocumentModel model,
|
||||
FormatProcessor.FormatOptions formatOptions,
|
||||
CodeStyleSettings settings,
|
||||
CommonCodeStyleSettings.IndentOptions options,
|
||||
@NotNull FormattingProgressCallback progressCallback)
|
||||
{
|
||||
InitialInfoBuilder builder = new InitialInfoBuilder(root, model, affectedRanges, settings, options, interestingOffset, progressCallback);
|
||||
InitialInfoBuilder builder = new InitialInfoBuilder(root, model, formatOptions.myAffectedRanges, settings, options, formatOptions.myInterestingOffset, progressCallback);
|
||||
builder.setCollectAlignmentsInsideFormattingRange(formatOptions.myReformatContext);
|
||||
builder.buildFrom(root, 0, null, null, null, true);
|
||||
return builder;
|
||||
}
|
||||
@@ -120,8 +122,12 @@ class InitialInfoBuilder {
|
||||
}
|
||||
return minOffset;
|
||||
}
|
||||
|
||||
public FormattingDocumentModel getFormattingDocumentModel() {
|
||||
return myModel;
|
||||
}
|
||||
|
||||
int getEndOffset() {
|
||||
public int getEndOffset() {
|
||||
int maxDocOffset = myModel.getTextLength();
|
||||
int maxOffset = myRootBlockWrapper != null ? myRootBlockWrapper.getEndOffset() : 0;
|
||||
if (myAffectedRanges != null) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class LeafBlockWrapper extends AbstractBlockWrapper {
|
||||
public class LeafBlockWrapper extends AbstractBlockWrapper {
|
||||
private static final int CONTAIN_LINE_FEEDS = 4;
|
||||
private static final int READ_ONLY = 8;
|
||||
private static final int LEAF = 16;
|
||||
@@ -126,7 +126,7 @@ class LeafBlockWrapper extends AbstractBlockWrapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IndentData getNumberOfSymbolsBeforeBlock() {
|
||||
public IndentData getNumberOfSymbolsBeforeBlock() {
|
||||
int spaces = getWhiteSpace().getSpaces();
|
||||
int indentSpaces = getWhiteSpace().getIndentSpaces();
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.formatting.engine.BlockRangesMap;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
/**
|
||||
* Extends {@link Spacing} in order to keep number of additional settings like <code>'minSpaces'</code>, <code>'minLineFeeds'</code>,
|
||||
* <code>'prefLineFeeds'</code> etc.
|
||||
*/
|
||||
class SpacingImpl extends Spacing {
|
||||
public class SpacingImpl extends Spacing {
|
||||
private int myMinSpaces;
|
||||
private int myKeepBlankLines;
|
||||
private int myMaxSpaces;
|
||||
@@ -76,7 +77,7 @@ class SpacingImpl extends Spacing {
|
||||
return myMinLineFeeds;
|
||||
}
|
||||
|
||||
final boolean isReadOnly(){
|
||||
public final boolean isReadOnly(){
|
||||
return (myFlags & READ_ONLY_MASK) != 0;
|
||||
}
|
||||
|
||||
@@ -90,10 +91,8 @@ class SpacingImpl extends Spacing {
|
||||
|
||||
/**
|
||||
* Allows to ask to refresh current state using given formatter if necessary.
|
||||
*
|
||||
* @param formatter formatter to use during state refresh
|
||||
*/
|
||||
public void refresh(FormatProcessor formatter) {
|
||||
public void refresh(BlockRangesMap helper) {
|
||||
}
|
||||
|
||||
public final boolean shouldKeepLineFeeds() {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.formatting.engine.BlockRangesMap;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -45,7 +46,7 @@ import java.util.ArrayList;
|
||||
* <p/>
|
||||
* Not thread-safe.
|
||||
*/
|
||||
class WhiteSpace {
|
||||
public class WhiteSpace {
|
||||
|
||||
private static final char LINE_FEED = '\n';
|
||||
|
||||
@@ -392,16 +393,13 @@ class WhiteSpace {
|
||||
/**
|
||||
* Tries to ensure that number of line feeds managed by the current {@link WhiteSpace} is consistent to the settings
|
||||
* defined at the given spacing property.
|
||||
*
|
||||
* @param spaceProperty space settings holder
|
||||
* @param formatProcessor format processor to use for space settings state refreshing
|
||||
*/
|
||||
public void arrangeLineFeeds(final SpacingImpl spaceProperty, final FormatProcessor formatProcessor) {
|
||||
public void arrangeLineFeeds(final SpacingImpl spaceProperty, final BlockRangesMap helper) {
|
||||
performModification(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (spaceProperty != null) {
|
||||
spaceProperty.refresh(formatProcessor);
|
||||
spaceProperty.refresh(helper);
|
||||
|
||||
if (spaceProperty.getMinLineFeeds() >= 0 && getLineFeeds() < spaceProperty.getMinLineFeeds()) {
|
||||
setLineFeeds(spaceProperty.getMinLineFeeds());
|
||||
@@ -541,11 +539,8 @@ class WhiteSpace {
|
||||
* <p/>
|
||||
* This method may be considered a shortcut for calling {@link #arrangeLineFeeds(SpacingImpl, FormatProcessor)} and
|
||||
* {@link #arrangeSpaces(SpacingImpl)}.
|
||||
*
|
||||
* @param spacing spacing settings holder
|
||||
* @param formatProcessor format processor to use to refresh state of the given <code>'spacing'</code> object
|
||||
*/
|
||||
public void removeLineFeeds(final SpacingImpl spacing, final FormatProcessor formatProcessor) {
|
||||
public void removeLineFeeds(final SpacingImpl spacing, final BlockRangesMap helper) {
|
||||
performModification(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -554,7 +549,7 @@ class WhiteSpace {
|
||||
myIndentSpaces = 0;
|
||||
}
|
||||
});
|
||||
arrangeLineFeeds(spacing, formatProcessor);
|
||||
arrangeLineFeeds(spacing, helper);
|
||||
arrangeSpaces(spacing);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
class WrapImpl extends Wrap {
|
||||
public class WrapImpl extends Wrap {
|
||||
/**
|
||||
* The block where the wrap needs to happen if the CHOP wrap mode is used and the chain of blocks exceeds the right margin.
|
||||
*/
|
||||
@@ -124,11 +124,11 @@ class WrapImpl extends Wrap {
|
||||
myIgnoredWraps.get(wrap).add(currentBlock);
|
||||
}
|
||||
|
||||
enum Type{
|
||||
public enum Type{
|
||||
DO_NOT_WRAP, WRAP_AS_NEEDED, CHOP_IF_NEEDED, WRAP_ALWAYS
|
||||
}
|
||||
|
||||
LeafBlockWrapper getChopStartBlock() {
|
||||
public LeafBlockWrapper getChopStartBlock() {
|
||||
return myChopStartBlock;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ class WrapImpl extends Wrap {
|
||||
* <li>'{@link #isActive() isActive}' property value is set (to <code>true</code>)</li>
|
||||
* </ul>
|
||||
*/
|
||||
void setActive() {
|
||||
public void setActive() {
|
||||
myChopStartBlock = null;
|
||||
myFlags |= ACTIVE_MASK;
|
||||
}
|
||||
@@ -150,7 +150,7 @@ class WrapImpl extends Wrap {
|
||||
*
|
||||
* @param startOffset new '{@link #getWrapOffset() firstPosition}' property value to use if current value is undefined (negative)
|
||||
*/
|
||||
void setWrapOffset(final int startOffset) {
|
||||
public void setWrapOffset(final int startOffset) {
|
||||
if (myWrapOffset < 0) {
|
||||
myWrapOffset = startOffset;
|
||||
}
|
||||
@@ -160,7 +160,7 @@ class WrapImpl extends Wrap {
|
||||
* @return '{@link #getWrapOffset() firstPosition}' property value defined previously via {@link #setWrapOffset(int)} if any;
|
||||
* <code>'-1'</code> otherwise
|
||||
*/
|
||||
int getWrapOffset() {
|
||||
public int getWrapOffset() {
|
||||
return myWrapOffset;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ class WrapImpl extends Wrap {
|
||||
myFlags |= (wrapFirstElement ? WRAP_FIRST_ELEMENT_MASK:0) | (myType.ordinal() << TYPE_SHIFT) | (myId << ID_SHIFT);
|
||||
}
|
||||
|
||||
final Type getType() {
|
||||
public final Type getType() {
|
||||
return myTypes[(myFlags & TYPE_MASK) >>> TYPE_SHIFT];
|
||||
}
|
||||
|
||||
@@ -190,17 +190,17 @@ class WrapImpl extends Wrap {
|
||||
*
|
||||
* @return <code>'wrapFirstElement'</code> property value
|
||||
*/
|
||||
final boolean isWrapFirstElement() {
|
||||
public final boolean isWrapFirstElement() {
|
||||
return (myFlags & WRAP_FIRST_ELEMENT_MASK) != 0;
|
||||
}
|
||||
|
||||
void saveChopBlock(LeafBlockWrapper current) {
|
||||
public void saveChopBlock(LeafBlockWrapper current) {
|
||||
if (myChopStartBlock == null) {
|
||||
myChopStartBlock = current;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean isActive() {
|
||||
public final boolean isActive() {
|
||||
return (myFlags & ACTIVE_MASK) != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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<WhiteSpace>();
|
||||
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 AdjustWhiteSpacesState(LeafBlockWrapper firstBlock,
|
||||
DependentSpacingEngine dependentSpacingEngine,
|
||||
WrapProcessor wrapProcessor,
|
||||
IndentAdjuster indentAdjuster,
|
||||
BlockRangesMap blockRangesMap,
|
||||
Set<Alignment> alignmentsInsideRangesToModify,
|
||||
boolean isReformatContext,
|
||||
FormattingProgressCallback progressCallback)
|
||||
{
|
||||
myWrapBlocksState = null;
|
||||
myCurrentBlock = firstBlock;
|
||||
myDependentSpacingEngine = dependentSpacingEngine;
|
||||
myWrapProcessor = wrapProcessor;
|
||||
myIndentAdjuster = indentAdjuster;
|
||||
myBlockRangesMap = blockRangesMap;
|
||||
myAlignmentsInsideRangesToModify = alignmentsInsideRangesToModify;
|
||||
myReformatContext = isReformatContext;
|
||||
myProgressCallback = progressCallback;
|
||||
}
|
||||
|
||||
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(), new Condition<TextRange>() {
|
||||
@Override
|
||||
public boolean value(TextRange dependencyRange) {
|
||||
return 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.diagnostic.LogMessageEx;
|
||||
import com.intellij.formatting.*;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class AlignmentHelper {
|
||||
private static final Logger LOG = Logger.getInstance(AlignmentHelper.class);
|
||||
|
||||
private static final Map<Alignment.Anchor, BlockAlignmentProcessor> ALIGNMENT_PROCESSORS = new EnumMap<Alignment.Anchor, BlockAlignmentProcessor>(Alignment.Anchor.class);
|
||||
static {
|
||||
ALIGNMENT_PROCESSORS.put(Alignment.Anchor.LEFT, new LeftEdgeAlignmentProcessor());
|
||||
ALIGNMENT_PROCESSORS.put(Alignment.Anchor.RIGHT, new RightEdgeAlignmentProcessor());
|
||||
}
|
||||
|
||||
private final Set<Alignment> myAlignmentsToSkip = ContainerUtil.newHashSet();
|
||||
private final Document myDocument;
|
||||
private final BlockIndentOptions myBlockIndentOptions;
|
||||
|
||||
private final AlignmentCyclesDetector myCyclesDetector;
|
||||
|
||||
private final Map<LeafBlockWrapper, Set<LeafBlockWrapper>> myBackwardShiftedAlignedBlocks = ContainerUtil.newHashMap();
|
||||
private final Map<AbstractBlockWrapper, Set<AbstractBlockWrapper>> myAlignmentMappings = ContainerUtil.newHashMap();
|
||||
|
||||
public AlignmentHelper(Document document, MultiMap<Alignment, Block> blocksToAlign, BlockIndentOptions options) {
|
||||
myDocument = document;
|
||||
myBlockIndentOptions = options;
|
||||
int totalBlocks = blocksToAlign.values().size();
|
||||
myCyclesDetector = new AlignmentCyclesDetector(totalBlocks);
|
||||
}
|
||||
|
||||
private static void reportAlignmentProcessingError(BlockAlignmentProcessor.Context context) {
|
||||
ASTNode node = context.targetBlock.getNode();
|
||||
Language language = node != null ? node.getPsi().getLanguage() : null;
|
||||
LogMessageEx.error(LOG,
|
||||
(language != null ? language.getDisplayName() + ": " : "") +
|
||||
"Can't align block " + context.targetBlock, context.document.getText());
|
||||
}
|
||||
|
||||
public LeafBlockWrapper applyAlignment(final AlignmentImpl alignment, final LeafBlockWrapper currentBlock) {
|
||||
BlockAlignmentProcessor alignmentProcessor = ALIGNMENT_PROCESSORS.get(alignment.getAnchor());
|
||||
if (alignmentProcessor == null) {
|
||||
LOG.error(String.format("Can't find alignment processor for alignment anchor %s", alignment.getAnchor()));
|
||||
return null;
|
||||
}
|
||||
|
||||
BlockAlignmentProcessor.Context context = new BlockAlignmentProcessor.Context(
|
||||
myDocument, alignment, currentBlock, myAlignmentMappings, myBackwardShiftedAlignedBlocks,
|
||||
myBlockIndentOptions.getIndentOptions(currentBlock));
|
||||
final LeafBlockWrapper offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(currentBlock);
|
||||
myCyclesDetector.registerOffsetResponsibleBlock(offsetResponsibleBlock);
|
||||
BlockAlignmentProcessor.Result result = alignmentProcessor.applyAlignment(context);
|
||||
switch (result) {
|
||||
case TARGET_BLOCK_PROCESSED_NOT_ALIGNED:
|
||||
return null;
|
||||
case TARGET_BLOCK_ALIGNED:
|
||||
storeAlignmentMapping(currentBlock);
|
||||
return null;
|
||||
case BACKWARD_BLOCK_ALIGNED:
|
||||
if (offsetResponsibleBlock == null) {
|
||||
return null;
|
||||
}
|
||||
Set<LeafBlockWrapper> blocksCausedRealignment = new HashSet<LeafBlockWrapper>();
|
||||
myBackwardShiftedAlignedBlocks.clear();
|
||||
myBackwardShiftedAlignedBlocks.put(offsetResponsibleBlock, blocksCausedRealignment);
|
||||
blocksCausedRealignment.add(currentBlock);
|
||||
storeAlignmentMapping(currentBlock, offsetResponsibleBlock);
|
||||
if (myCyclesDetector.isCycleDetected()) {
|
||||
reportAlignmentProcessingError(context);
|
||||
return null;
|
||||
}
|
||||
myCyclesDetector.registerBlockRollback(currentBlock);
|
||||
return offsetResponsibleBlock.getNextBlock();
|
||||
case RECURSION_DETECTED:
|
||||
myAlignmentsToSkip.add(alignment);
|
||||
return offsetResponsibleBlock; // Fall through to the 'register alignment to skip'.
|
||||
case UNABLE_TO_ALIGN_BACKWARD_BLOCK:
|
||||
myAlignmentsToSkip.add(alignment);
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldSkip(AlignmentImpl alignment) {
|
||||
return myAlignmentsToSkip.contains(alignment);
|
||||
}
|
||||
|
||||
private void storeAlignmentMapping(AbstractBlockWrapper block1, AbstractBlockWrapper block2) {
|
||||
doStoreAlignmentMapping(block1, block2);
|
||||
doStoreAlignmentMapping(block2, block1);
|
||||
}
|
||||
|
||||
private void doStoreAlignmentMapping(AbstractBlockWrapper key, AbstractBlockWrapper value) {
|
||||
Set<AbstractBlockWrapper> wrappers = myAlignmentMappings.get(key);
|
||||
if (wrappers == null) {
|
||||
myAlignmentMappings.put(key, wrappers = new HashSet<AbstractBlockWrapper>());
|
||||
}
|
||||
wrappers.add(value);
|
||||
}
|
||||
|
||||
private void storeAlignmentMapping(LeafBlockWrapper currentBlock) {
|
||||
AlignmentImpl alignment = null;
|
||||
AbstractBlockWrapper block = currentBlock;
|
||||
while (alignment == null && block != null) {
|
||||
alignment = block.getAlignment();
|
||||
block = block.getParent();
|
||||
}
|
||||
if (alignment != null) {
|
||||
block = alignment.getOffsetRespBlockBefore(currentBlock);
|
||||
if (block != null) {
|
||||
storeAlignmentMapping(currentBlock, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
myBackwardShiftedAlignedBlocks.clear();
|
||||
myAlignmentMappings.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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.openapi.editor.TextChange;
|
||||
import com.intellij.openapi.editor.ex.DocumentEx;
|
||||
import com.intellij.openapi.editor.impl.BulkChangesMerger;
|
||||
import com.intellij.openapi.editor.impl.TextChangeImpl;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ApplyChangesState extends State {
|
||||
|
||||
/**
|
||||
* There is a possible case that formatting introduced big number of changes to the underlying document. That number may be
|
||||
* big enough for that their subsequent appliance is much slower than direct replacing of the whole document text.
|
||||
* <p/>
|
||||
* Current constant holds minimum number of changes that should trigger such <code>'replace whole text'</code> optimization.
|
||||
*/
|
||||
private static final int BULK_REPLACE_OPTIMIZATION_CRITERIA = 3000;
|
||||
|
||||
|
||||
private final FormattingModel myModel;
|
||||
|
||||
private final FormattingProgressCallback myProgressCallback;
|
||||
private final WrapBlocksState myWrapState;
|
||||
|
||||
private List<LeafBlockWrapper> myBlocksToModify;
|
||||
private int myShift;
|
||||
private int myIndex;
|
||||
private boolean myResetBulkUpdateState;
|
||||
|
||||
private BlockIndentOptions myBlockIndentOptions;
|
||||
|
||||
public ApplyChangesState(FormattingModel model, WrapBlocksState state, FormattingProgressCallback callback) {
|
||||
myModel = model;
|
||||
myWrapState = state;
|
||||
myProgressCallback = callback;
|
||||
myBlockIndentOptions = state.getBlockIndentOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs formatter changes in a series of blocks, for each block a new contents of document is calculated
|
||||
* and whole document is replaced in one operation.
|
||||
*
|
||||
* @param blocksToModify changes introduced by formatter
|
||||
* @param model current formatting model
|
||||
*/
|
||||
@SuppressWarnings({"deprecation"})
|
||||
private void applyChangesAtRewriteMode(@NotNull final List<LeafBlockWrapper> blocksToModify,
|
||||
@NotNull final FormattingModel model) {
|
||||
FormattingDocumentModel documentModel = model.getDocumentModel();
|
||||
Document document = documentModel.getDocument();
|
||||
CaretOffsetUpdater caretOffsetUpdater = new CaretOffsetUpdater(document);
|
||||
|
||||
if (document instanceof DocumentEx) ((DocumentEx)document).setInBulkUpdate(true);
|
||||
try {
|
||||
List<TextChange> changes = new ArrayList<TextChange>();
|
||||
int shift = 0;
|
||||
int currentIterationShift = 0;
|
||||
for (LeafBlockWrapper block : blocksToModify) {
|
||||
WhiteSpace whiteSpace = block.getWhiteSpace();
|
||||
CharSequence newWs = documentModel.adjustWhiteSpaceIfNecessary(
|
||||
whiteSpace.generateWhiteSpace(myBlockIndentOptions.getIndentOptions(block)), whiteSpace.getStartOffset(),
|
||||
whiteSpace.getEndOffset(), block.getNode(), false
|
||||
);
|
||||
if (changes.size() > 10000) {
|
||||
caretOffsetUpdater.update(changes);
|
||||
CharSequence mergeResult =
|
||||
BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
|
||||
document.replaceString(0, document.getTextLength(), mergeResult);
|
||||
shift += currentIterationShift;
|
||||
currentIterationShift = 0;
|
||||
changes.clear();
|
||||
}
|
||||
TextChangeImpl change = new TextChangeImpl(newWs, whiteSpace.getStartOffset() + shift, whiteSpace.getEndOffset() + shift);
|
||||
currentIterationShift += change.getDiff();
|
||||
changes.add(change);
|
||||
}
|
||||
caretOffsetUpdater.update(changes);
|
||||
CharSequence mergeResult = BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
|
||||
document.replaceString(0, document.getTextLength(), mergeResult);
|
||||
}
|
||||
finally {
|
||||
if (document instanceof DocumentEx) ((DocumentEx)document).setInBulkUpdate(false);
|
||||
}
|
||||
|
||||
caretOffsetUpdater.restoreCaretLocations();
|
||||
cleanupBlocks(blocksToModify);
|
||||
}
|
||||
|
||||
private static void cleanupBlocks(List<LeafBlockWrapper> blocks) {
|
||||
for (LeafBlockWrapper block : blocks) {
|
||||
block.getParent().dispose();
|
||||
block.dispose();
|
||||
}
|
||||
blocks.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static DocumentEx getAffectedDocument(final FormattingModel model) {
|
||||
final Document document = model.getDocumentModel().getDocument();
|
||||
if (document instanceof DocumentEx) {
|
||||
return (DocumentEx)document;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<LeafBlockWrapper> collectBlocksToModify() {
|
||||
List<LeafBlockWrapper> blocksToModify = new ArrayList<LeafBlockWrapper>();
|
||||
LeafBlockWrapper firstBlock = myWrapState.getFirstBlock();
|
||||
for (LeafBlockWrapper block = firstBlock; block != null; block = block.getNextBlock()) {
|
||||
final WhiteSpace whiteSpace = block.getWhiteSpace();
|
||||
if (!whiteSpace.isReadOnly()) {
|
||||
final String newWhiteSpace = whiteSpace.generateWhiteSpace(myBlockIndentOptions.getIndentOptions(block));
|
||||
if (!whiteSpace.equalsToString(newWhiteSpace)) {
|
||||
blocksToModify.add(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
return blocksToModify;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
myBlocksToModify = collectBlocksToModify();
|
||||
// call doModifications static method to ensure no access to state
|
||||
// thus we may clear formatting state
|
||||
|
||||
//reset();
|
||||
//myDisposed = true;
|
||||
|
||||
if (myBlocksToModify.isEmpty()) {
|
||||
setDone(true);
|
||||
return;
|
||||
}
|
||||
|
||||
myProgressCallback.beforeApplyingFormatChanges(myBlocksToModify);
|
||||
|
||||
final int blocksToModifyCount = myBlocksToModify.size();
|
||||
if (blocksToModifyCount > BULK_REPLACE_OPTIMIZATION_CRITERIA) {
|
||||
applyChangesAtRewriteMode(myBlocksToModify, myModel);
|
||||
setDone(true);
|
||||
}
|
||||
else if (blocksToModifyCount > 50) {
|
||||
DocumentEx updatedDocument = getAffectedDocument(myModel);
|
||||
if (updatedDocument != null) {
|
||||
updatedDocument.setInBulkUpdate(true);
|
||||
myResetBulkUpdateState = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doIteration() {
|
||||
LeafBlockWrapper blockWrapper = myBlocksToModify.get(myIndex);
|
||||
myShift = FormatProcessorUtils.replaceWhiteSpace(
|
||||
myModel,
|
||||
blockWrapper,
|
||||
myShift,
|
||||
blockWrapper.getWhiteSpace().generateWhiteSpace(myBlockIndentOptions.getIndentOptions(blockWrapper)),
|
||||
myBlockIndentOptions.getIndentOptions()
|
||||
);
|
||||
myProgressCallback.afterApplyingChange(blockWrapper);
|
||||
// block could be gc'd
|
||||
blockWrapper.getParent().dispose();
|
||||
blockWrapper.dispose();
|
||||
myBlocksToModify.set(myIndex, null);
|
||||
myIndex++;
|
||||
|
||||
if (myIndex >= myBlocksToModify.size()) {
|
||||
setDone(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDone(boolean done) {
|
||||
super.setDone(done);
|
||||
|
||||
if (myResetBulkUpdateState) {
|
||||
DocumentEx document = getAffectedDocument(myModel);
|
||||
if (document != null) {
|
||||
document.setInBulkUpdate(false);
|
||||
myResetBulkUpdateState = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
myModel.commitChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (myIndex > 0) {
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myModel.commitChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.ASTBlock;
|
||||
import com.intellij.formatting.AbstractBlockWrapper;
|
||||
import com.intellij.formatting.Block;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BlockIndentOptions {
|
||||
private final CodeStyleSettings mySettings;
|
||||
private final CommonCodeStyleSettings.IndentOptions myIndentOptions;
|
||||
private final int myRightMargin;
|
||||
|
||||
public BlockIndentOptions(@NotNull CodeStyleSettings settings, @NotNull CommonCodeStyleSettings.IndentOptions indentOptions, Block block) {
|
||||
mySettings = settings;
|
||||
myIndentOptions = indentOptions;
|
||||
myRightMargin = calcRightMargin(block);
|
||||
}
|
||||
|
||||
public CommonCodeStyleSettings.IndentOptions getIndentOptions() {
|
||||
return myIndentOptions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CommonCodeStyleSettings.IndentOptions getIndentOptions(@NotNull AbstractBlockWrapper block) {
|
||||
final Language language = block.getLanguage();
|
||||
if (language == null) {
|
||||
return myIndentOptions;
|
||||
}
|
||||
final CommonCodeStyleSettings commonSettings = mySettings.getCommonSettings(language);
|
||||
if (commonSettings == null) {
|
||||
return myIndentOptions;
|
||||
}
|
||||
final CommonCodeStyleSettings.IndentOptions result = commonSettings.getIndentOptions();
|
||||
return result == null ? myIndentOptions : result;
|
||||
}
|
||||
|
||||
public int getRightMargin() {
|
||||
return myRightMargin;
|
||||
}
|
||||
|
||||
private int calcRightMargin(Block rootBlock) {
|
||||
Language language = null;
|
||||
if (rootBlock instanceof ASTBlock) {
|
||||
ASTNode node = ((ASTBlock)rootBlock).getNode();
|
||||
if (node != null) {
|
||||
PsiElement psiElement = node.getPsi();
|
||||
if (psiElement.isValid()) {
|
||||
PsiFile psiFile = psiElement.getContainingFile();
|
||||
if (psiFile != null) {
|
||||
language = psiFile.getViewProvider().getBaseLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mySettings.getRightMargin(language);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.LeafBlockWrapper;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import gnu.trove.TIntObjectHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BlockRangesMap {
|
||||
private final LeafBlockWrapper myLastBlock;
|
||||
private TIntObjectHashMap<LeafBlockWrapper> myTextRangeToWrapper;
|
||||
|
||||
public BlockRangesMap(LeafBlockWrapper first, LeafBlockWrapper last) {
|
||||
myLastBlock = last;
|
||||
myTextRangeToWrapper = buildTextRangeToInfoMap(first);
|
||||
}
|
||||
|
||||
private static TIntObjectHashMap<LeafBlockWrapper> buildTextRangeToInfoMap(final LeafBlockWrapper first) {
|
||||
final TIntObjectHashMap<LeafBlockWrapper> result = new TIntObjectHashMap<LeafBlockWrapper>();
|
||||
LeafBlockWrapper current = first;
|
||||
while (current != null) {
|
||||
result.put(current.getStartOffset(), current);
|
||||
current = current.getNextBlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean containsLineFeeds(final TextRange dependency) {
|
||||
LeafBlockWrapper child = myTextRangeToWrapper.get(dependency.getStartOffset());
|
||||
if (child == null) return false;
|
||||
if (child.containsLineFeeds()) return true;
|
||||
final int endOffset = dependency.getEndOffset();
|
||||
while (child.getEndOffset() < endOffset) {
|
||||
child = child.getNextBlock();
|
||||
if (child == null) return false;
|
||||
if (child.getWhiteSpace().containsLineFeeds()) return true;
|
||||
if (child.containsLineFeeds()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LeafBlockWrapper getBlockAtOrAfter(final int startOffset) {
|
||||
int current = startOffset;
|
||||
LeafBlockWrapper result = null;
|
||||
while (current < myLastBlock.getEndOffset()) {
|
||||
final LeafBlockWrapper currentValue = myTextRangeToWrapper.get(current);
|
||||
if (currentValue != null) {
|
||||
result = currentValue;
|
||||
break;
|
||||
}
|
||||
current++;
|
||||
}
|
||||
|
||||
LeafBlockWrapper prevBlock = getPrevBlock(result);
|
||||
|
||||
if (prevBlock != null && prevBlock.contains(startOffset)) {
|
||||
return prevBlock;
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LeafBlockWrapper getPrevBlock(@Nullable final LeafBlockWrapper result) {
|
||||
if (result != null) {
|
||||
return result.getPreviousBlock();
|
||||
}
|
||||
else {
|
||||
return myLastBlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.EditorFactory;
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import com.intellij.openapi.editor.impl.BulkChangesMerger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CaretOffsetUpdater {
|
||||
private final Map<Editor, Integer> myCaretOffsets = new HashMap<Editor, Integer>();
|
||||
|
||||
public CaretOffsetUpdater(@NotNull Document document) {
|
||||
Editor[] editors = EditorFactory.getInstance().getEditors(document);
|
||||
for (Editor editor : editors) {
|
||||
myCaretOffsets.put(editor, editor.getCaretModel().getOffset());
|
||||
}
|
||||
}
|
||||
|
||||
public void update(@NotNull List<? extends TextChange> changes) {
|
||||
BulkChangesMerger merger = BulkChangesMerger.INSTANCE;
|
||||
for (Map.Entry<Editor, Integer> entry : myCaretOffsets.entrySet()) {
|
||||
entry.setValue(merger.updateOffset(entry.getValue(), changes));
|
||||
}
|
||||
}
|
||||
|
||||
public void restoreCaretLocations() {
|
||||
for (Map.Entry<Editor, Integer> entry : myCaretOffsets.entrySet()) {
|
||||
entry.getKey().getCaretModel().moveToOffset(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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'</code> block has dependent spacing that targets the whole
|
||||
* <code>'{1, 2, 3}'</code> block. So, it's not possible to answer whether line feed should be used during processing block
|
||||
* <code>'1'</code>.
|
||||
* <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<TextRange, DependantSpacingImpl>(new Comparator<TextRange>() {
|
||||
@Override
|
||||
public int compare(final TextRange o1, final TextRange 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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<Alignment, LeafBlockWrapper>();
|
||||
|
||||
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 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 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 void arrangeSpaces(Collection<LeafBlockWrapper> toRealign) {
|
||||
for (LeafBlockWrapper block : toRealign) {
|
||||
WhiteSpace whiteSpace = block.getWhiteSpace();
|
||||
SpacingImpl spacing = block.getSpaceProperty();
|
||||
whiteSpace.arrangeSpaces(spacing);
|
||||
}
|
||||
}
|
||||
|
||||
private 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 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 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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -13,7 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.formatting;
|
||||
package com.intellij.formatting.engine;
|
||||
|
||||
import com.intellij.formatting.IndentImpl;
|
||||
|
||||
public class ExpandableIndent extends IndentImpl {
|
||||
private boolean myEnforceIndent;
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.util.TextRange;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FormatProcessorUtils {
|
||||
|
||||
|
||||
private static int calcShift(@NotNull final IndentInside oldIndent,
|
||||
@NotNull final IndentInside newIndent,
|
||||
@NotNull final CommonCodeStyleSettings.IndentOptions options)
|
||||
{
|
||||
if (oldIndent.equals(newIndent)) return 0;
|
||||
return newIndent.getSpacesCount(options) - oldIndent.getSpacesCount(options);
|
||||
}
|
||||
|
||||
public static int replaceWhiteSpace(final FormattingModel model,
|
||||
@NotNull final LeafBlockWrapper block,
|
||||
int shift,
|
||||
final CharSequence _newWhiteSpace,
|
||||
final CommonCodeStyleSettings.IndentOptions options
|
||||
) {
|
||||
final WhiteSpace whiteSpace = block.getWhiteSpace();
|
||||
final TextRange textRange = whiteSpace.getTextRange();
|
||||
final TextRange wsRange = textRange.shiftRight(shift);
|
||||
final String newWhiteSpace = _newWhiteSpace.toString();
|
||||
TextRange newWhiteSpaceRange = model instanceof FormattingModelEx
|
||||
? ((FormattingModelEx) model).replaceWhiteSpace(wsRange, block.getNode(), newWhiteSpace)
|
||||
: model.replaceWhiteSpace(wsRange, newWhiteSpace);
|
||||
|
||||
shift += newWhiteSpaceRange.getLength() - textRange.getLength();
|
||||
|
||||
if (block.isLeaf() && whiteSpace.containsLineFeeds() && block.containsLineFeeds()) {
|
||||
final TextRange currentBlockRange = block.getTextRange().shiftRight(shift);
|
||||
|
||||
IndentInside oldBlockIndent = whiteSpace.getInitialLastLineIndent();
|
||||
IndentInside whiteSpaceIndent = IndentInside.createIndentOn(IndentInside.getLastLine(newWhiteSpace));
|
||||
final int shiftInside = calcShift(oldBlockIndent, whiteSpaceIndent, options);
|
||||
|
||||
if (shiftInside != 0 || !oldBlockIndent.equals(whiteSpaceIndent)) {
|
||||
final TextRange newBlockRange = model.shiftIndentInsideRange(block.getNode(), currentBlockRange, shiftInside);
|
||||
shift += newBlockRange.getLength() - block.getLength();
|
||||
}
|
||||
}
|
||||
return shift;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.formatting.FormatProcessor;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class IndentAdjuster {
|
||||
private final AlignmentHelper myAlignmentHelper;
|
||||
private final BlockIndentOptions myBlockIndentOptions;
|
||||
|
||||
public IndentAdjuster(BlockIndentOptions blockIndentOptions, AlignmentHelper alignmentHelper) {
|
||||
myAlignmentHelper = alignmentHelper;
|
||||
myBlockIndentOptions = blockIndentOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes to align block we adjust whitespace of other block before.
|
||||
* In such a case, we rollback to that block restarting formatting from there.
|
||||
*
|
||||
* @return new current block if we need to rollback, null otherwise
|
||||
*/
|
||||
public LeafBlockWrapper adjustIndent(LeafBlockWrapper block) {
|
||||
AlignmentImpl alignment = CoreFormatterUtil.getAlignment(block);
|
||||
WhiteSpace whiteSpace = block.getWhiteSpace();
|
||||
|
||||
if (alignment == null || myAlignmentHelper.shouldSkip(alignment)) {
|
||||
if (whiteSpace.containsLineFeeds()) {
|
||||
adjustSpacingByIndentOffset(block);
|
||||
}
|
||||
else {
|
||||
whiteSpace.arrangeSpaces(block.getSpaceProperty());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return myAlignmentHelper.applyAlignment(alignment, block);
|
||||
}
|
||||
|
||||
private void adjustSpacingByIndentOffset(LeafBlockWrapper block) {
|
||||
CommonCodeStyleSettings.IndentOptions options = myBlockIndentOptions.getIndentOptions(block);
|
||||
IndentData offset = block.calculateOffset(options);
|
||||
block.getWhiteSpace().setSpaces(offset.getSpaces(), offset.getIndentSpaces());
|
||||
}
|
||||
|
||||
public void adjustLineIndent(LeafBlockWrapper myCurrentBlock) {
|
||||
IndentData alignOffset = getAlignOffset(myCurrentBlock);
|
||||
|
||||
if (alignOffset == null) {
|
||||
adjustSpacingByIndentOffset(myCurrentBlock);
|
||||
}
|
||||
else {
|
||||
myCurrentBlock.getWhiteSpace().setSpaces(alignOffset.getSpaces(), alignOffset.getIndentSpaces());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IndentData getAlignOffset(LeafBlockWrapper myCurrentBlock) {
|
||||
AbstractBlockWrapper current = myCurrentBlock;
|
||||
while (true) {
|
||||
final AlignmentImpl alignment = current.getAlignment();
|
||||
LeafBlockWrapper offsetResponsibleBlock;
|
||||
if (alignment != null && (offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(myCurrentBlock)) != null) {
|
||||
final WhiteSpace whiteSpace = offsetResponsibleBlock.getWhiteSpace();
|
||||
if (whiteSpace.containsLineFeeds()) {
|
||||
return new IndentData(whiteSpace.getIndentSpaces(), whiteSpace.getSpaces());
|
||||
}
|
||||
else {
|
||||
final int offsetBeforeBlock = CoreFormatterUtil.getStartColumn(offsetResponsibleBlock);
|
||||
final AbstractBlockWrapper indentedParentBlock = CoreFormatterUtil.getIndentedParentBlock(myCurrentBlock);
|
||||
if (indentedParentBlock == null) {
|
||||
return new IndentData(0, offsetBeforeBlock);
|
||||
}
|
||||
else {
|
||||
final int parentIndent = indentedParentBlock.getWhiteSpace().getIndentOffset();
|
||||
if (parentIndent > offsetBeforeBlock) {
|
||||
return new IndentData(0, offsetBeforeBlock);
|
||||
}
|
||||
else {
|
||||
return new IndentData(parentIndent, offsetBeforeBlock - parentIndent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
current = current.getParent();
|
||||
if (current == null || current.getStartOffset() != myCurrentBlock.getStartOffset()) return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IndentInfo adjustLineIndent(LeafBlockWrapper currentBlock, FormatProcessor.ChildAttributesInfo info) {
|
||||
AbstractBlockWrapper parent = info.parent;
|
||||
ChildAttributes childAttributes = info.attributes;
|
||||
int index = info.index;
|
||||
|
||||
int alignOffset = getAlignOffsetBefore(childAttributes.getAlignment(), null);
|
||||
if (alignOffset == -1) {
|
||||
return parent.calculateChildOffset(myBlockIndentOptions.getIndentOptions(parent), childAttributes, index).createIndentInfo();
|
||||
}
|
||||
else {
|
||||
AbstractBlockWrapper indentedParentBlock = CoreFormatterUtil.getIndentedParentBlock(currentBlock);
|
||||
if (indentedParentBlock == null) {
|
||||
return new IndentInfo(0, 0, alignOffset);
|
||||
}
|
||||
else {
|
||||
int indentOffset = indentedParentBlock.getWhiteSpace().getIndentOffset();
|
||||
if (indentOffset > alignOffset) {
|
||||
return new IndentInfo(0, 0, alignOffset);
|
||||
}
|
||||
else {
|
||||
return new IndentInfo(0, indentOffset, alignOffset - indentOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getAlignOffsetBefore(@Nullable final Alignment alignment, @Nullable final LeafBlockWrapper blockAfter) {
|
||||
if (alignment == null) return -1;
|
||||
final LeafBlockWrapper alignRespBlock = ((AlignmentImpl)alignment).getOffsetRespBlockBefore(blockAfter);
|
||||
if (alignRespBlock != null) {
|
||||
return CoreFormatterUtil.getStartColumn(alignRespBlock);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public abstract class State {
|
||||
|
||||
private boolean myDone;
|
||||
private Runnable myOnDoneAction;
|
||||
|
||||
public void iteration() {
|
||||
if (!isDone()) {
|
||||
doIteration();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return myDone;
|
||||
}
|
||||
|
||||
protected void setDone(boolean done) {
|
||||
if (!myDone && done && myOnDoneAction != null) {
|
||||
myOnDoneAction.run();
|
||||
}
|
||||
myDone = done;
|
||||
}
|
||||
|
||||
public void stop() {}
|
||||
|
||||
protected abstract void doIteration();
|
||||
|
||||
public void prepare() {}
|
||||
|
||||
public void setOnDone(Runnable onDoneAction) {
|
||||
myOnDoneAction = onDoneAction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.util.containers.ContainerUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class StateProcessor {
|
||||
|
||||
private final List<State> myStates = ContainerUtil.newArrayList();
|
||||
private State myCurrentState;
|
||||
|
||||
public StateProcessor(State initial) {
|
||||
myCurrentState = initial;
|
||||
}
|
||||
|
||||
public void setNextState(State state) {
|
||||
myStates.add(state);
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return myStates.isEmpty() && myCurrentState.isDone();
|
||||
}
|
||||
|
||||
public void iteration() {
|
||||
if (!myCurrentState.isDone()) {
|
||||
myCurrentState.iteration();
|
||||
}
|
||||
shiftStateIfNecessary();
|
||||
}
|
||||
|
||||
private void shiftStateIfNecessary() {
|
||||
if (myCurrentState.isDone() && !myStates.isEmpty()) {
|
||||
myCurrentState = myStates.get(0);
|
||||
myStates.remove(0);
|
||||
myCurrentState.prepare();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
myCurrentState.stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class WrapBlocksState extends State {
|
||||
private final InitialInfoBuilder myWrapper;
|
||||
private final BlockIndentOptions myBlockIndentOptions;
|
||||
private WhiteSpace myLastWhiteSpace;
|
||||
private BlockRangesMap myBlockRangesMap;
|
||||
private DependentSpacingEngine myDependentSpacingEngine;
|
||||
private AlignmentHelper myAlignmentHelper;
|
||||
private IndentAdjuster myIndentAdjuster;
|
||||
private WrapProcessor myWrapProcessor;
|
||||
|
||||
public WrapBlocksState(@NotNull InitialInfoBuilder initialInfoBuilder, BlockIndentOptions blockIndentOptions) {
|
||||
myWrapper = initialInfoBuilder;
|
||||
myBlockIndentOptions = blockIndentOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doIteration() {
|
||||
if (isDone()) {
|
||||
return;
|
||||
}
|
||||
setDone(myWrapper.iteration());
|
||||
}
|
||||
|
||||
public Map<AbstractBlockWrapper, Block> getBlockToInfoMap() {
|
||||
return myWrapper.getBlockToInfoMap();
|
||||
}
|
||||
|
||||
public LeafBlockWrapper getFirstBlock() {
|
||||
assertDone();
|
||||
return myWrapper.getFirstTokenBlock();
|
||||
}
|
||||
|
||||
public LeafBlockWrapper getLastBlock() {
|
||||
assertDone();
|
||||
return myWrapper.getLastTokenBlock();
|
||||
}
|
||||
|
||||
public WhiteSpace getLastWhiteSpace() {
|
||||
assertDone();
|
||||
if (myLastWhiteSpace == null) {
|
||||
int lastBlockOffset = getLastBlock().getEndOffset();
|
||||
myLastWhiteSpace = new WhiteSpace(lastBlockOffset, false);
|
||||
FormattingDocumentModel model = myWrapper.getFormattingDocumentModel();
|
||||
CommonCodeStyleSettings.IndentOptions options = myBlockIndentOptions.getIndentOptions();
|
||||
myLastWhiteSpace.append(Math.max(lastBlockOffset, myWrapper.getEndOffset()), model, options);
|
||||
}
|
||||
return myLastWhiteSpace;
|
||||
}
|
||||
|
||||
public BlockRangesMap getBlockRangesMap() {
|
||||
assertDone();
|
||||
if (myBlockRangesMap == null) {
|
||||
myBlockRangesMap = new BlockRangesMap(getFirstBlock(), getLastBlock());
|
||||
}
|
||||
return myBlockRangesMap;
|
||||
}
|
||||
|
||||
public DependentSpacingEngine getDependentSpacingEngine() {
|
||||
assertDone();
|
||||
if (myDependentSpacingEngine == null) {
|
||||
myDependentSpacingEngine = new DependentSpacingEngine(getBlockRangesMap());
|
||||
}
|
||||
return myDependentSpacingEngine;
|
||||
}
|
||||
|
||||
public Set<Alignment> getAlignmentsInsideRangesToModify() {
|
||||
assertDone();
|
||||
return myWrapper.getAlignmentsInsideRangeToModify();
|
||||
}
|
||||
|
||||
public AlignmentHelper getAlignmentHelper() {
|
||||
assertDone();
|
||||
if (myAlignmentHelper == null) {
|
||||
Document document = myWrapper.getFormattingDocumentModel().getDocument();
|
||||
myAlignmentHelper = new AlignmentHelper(document, myWrapper.getBlocksToAlign(), myBlockIndentOptions);
|
||||
}
|
||||
return myAlignmentHelper;
|
||||
}
|
||||
|
||||
public IndentAdjuster getIndentAdjuster() {
|
||||
assertDone();
|
||||
if (myIndentAdjuster == null) {
|
||||
myIndentAdjuster = new IndentAdjuster(myBlockIndentOptions, getAlignmentHelper());
|
||||
}
|
||||
return myIndentAdjuster;
|
||||
}
|
||||
|
||||
public MultiMap<ExpandableIndent, AbstractBlockWrapper> getExpandableIndent() {
|
||||
assertDone();
|
||||
return myWrapper.getExpandableIndentsBlocks();
|
||||
}
|
||||
|
||||
public WrapProcessor getWrapProcessor() {
|
||||
assertDone();
|
||||
if (myWrapProcessor == null) {
|
||||
int rightMargin = myBlockIndentOptions.getRightMargin();
|
||||
myWrapProcessor = new WrapProcessor(myBlockRangesMap, getIndentAdjuster(), rightMargin);
|
||||
}
|
||||
return myWrapProcessor;
|
||||
}
|
||||
|
||||
public BlockIndentOptions getBlockIndentOptions() {
|
||||
return myBlockIndentOptions;
|
||||
}
|
||||
|
||||
private void assertDone() {
|
||||
if (!isDone()) throw new IllegalStateException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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 org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class WrapProcessor {
|
||||
private LeafBlockWrapper myFirstWrappedBlockOnLine = null;
|
||||
private BlockRangesMap myBlockRangesMap;
|
||||
private LeafBlockWrapper myWrapCandidate = null;
|
||||
private IndentAdjuster myIndentAdjuster;
|
||||
private int myRightMargin;
|
||||
|
||||
public WrapProcessor(BlockRangesMap blockHelper, IndentAdjuster indentAdjuster, int rightMargin) {
|
||||
myIndentAdjuster = indentAdjuster;
|
||||
myBlockRangesMap = blockHelper;
|
||||
myRightMargin = rightMargin;
|
||||
}
|
||||
|
||||
private boolean isSuitableInTheCurrentPosition(final WrapImpl wrap, LeafBlockWrapper currentBlock) {
|
||||
if (wrap.getWrapOffset() < currentBlock.getStartOffset()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wrap.isWrapFirstElement()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED) {
|
||||
return positionAfterWrappingIsSuitable(currentBlock);
|
||||
}
|
||||
|
||||
return wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && lineOver(currentBlock) && positionAfterWrappingIsSuitable(currentBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if {@link #myCurrentBlock currently processed wrapped block} doesn't contain line feeds and
|
||||
* exceeds right margin; <code>false</code> otherwise
|
||||
*/
|
||||
private boolean lineOver(LeafBlockWrapper currentBlock) {
|
||||
return !currentBlock.containsLineFeeds() &&
|
||||
CoreFormatterUtil.getStartColumn(currentBlock) + currentBlock.getLength() > myRightMargin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensures that offset of the {@link #myCurrentBlock currently processed block} is not increased if we make a wrap on it.
|
||||
*
|
||||
* @return <code>true</code> if it's ok to wrap at the currently processed block; <code>false</code> otherwise
|
||||
*/
|
||||
private boolean positionAfterWrappingIsSuitable(LeafBlockWrapper currentBlock) {
|
||||
final WhiteSpace whiteSpace = currentBlock.getWhiteSpace();
|
||||
if (whiteSpace.containsLineFeeds()) return true;
|
||||
final int spaces = whiteSpace.getSpaces();
|
||||
int indentSpaces = whiteSpace.getIndentSpaces();
|
||||
try {
|
||||
final int startColumnNow = CoreFormatterUtil.getStartColumn(currentBlock);
|
||||
whiteSpace.ensureLineFeed();
|
||||
myIndentAdjuster.adjustLineIndent(currentBlock);
|
||||
final int startColumnAfterWrap = CoreFormatterUtil.getStartColumn(currentBlock);
|
||||
return startColumnNow > startColumnAfterWrap;
|
||||
}
|
||||
finally {
|
||||
whiteSpace.removeLineFeeds(currentBlock.getSpaceProperty(), myBlockRangesMap);
|
||||
whiteSpace.setSpaces(spaces, indentSpaces);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private WrapImpl getWrapToBeUsed(final ArrayList<WrapImpl> wraps, LeafBlockWrapper currentBlock) {
|
||||
if (wraps.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (myWrapCandidate == currentBlock) return wraps.get(0);
|
||||
|
||||
for (final WrapImpl wrap : wraps) {
|
||||
if (!isSuitableInTheCurrentPosition(wrap, currentBlock)) continue;
|
||||
if (wrap.isActive()) return wrap;
|
||||
|
||||
final WrapImpl.Type type = wrap.getType();
|
||||
if (type == WrapImpl.Type.WRAP_ALWAYS) return wrap;
|
||||
if (type == WrapImpl.Type.WRAP_AS_NEEDED || type == WrapImpl.Type.CHOP_IF_NEEDED) {
|
||||
if (lineOver(currentBlock)) {
|
||||
return wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isCandidateToBeWrapped(final WrapImpl wrap, LeafBlockWrapper currentBlock) {
|
||||
return isSuitableInTheCurrentPosition(wrap, currentBlock) &&
|
||||
(wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED || wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED) &&
|
||||
!currentBlock.getWhiteSpace().isReadOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to answer if wrap of the {@link #myWrapCandidate} object (if any) may be replaced by the given wrap.
|
||||
*
|
||||
* @param wrap wrap candidate to check
|
||||
* @return <code>true</code> if wrap of the {@link #myWrapCandidate} object (if any) may be replaced by the given wrap;
|
||||
* <code>false</code> otherwise
|
||||
*/
|
||||
private boolean canReplaceWrapCandidate(WrapImpl wrap, LeafBlockWrapper currentBlock) {
|
||||
if (myWrapCandidate == null) return true;
|
||||
WrapImpl.Type type = wrap.getType();
|
||||
if (wrap.isActive() && (type == WrapImpl.Type.CHOP_IF_NEEDED || type == WrapImpl.Type.WRAP_ALWAYS)) return true;
|
||||
final WrapImpl currentWrap = myWrapCandidate.getWrap();
|
||||
return wrap == currentWrap || !wrap.isChildOf(currentWrap, currentBlock);
|
||||
}
|
||||
|
||||
public LeafBlockWrapper processWrap(LeafBlockWrapper currentBlock) {
|
||||
final SpacingImpl spacing = currentBlock.getSpaceProperty();
|
||||
final WhiteSpace whiteSpace = currentBlock.getWhiteSpace();
|
||||
|
||||
final boolean wrapWasPresent = whiteSpace.containsLineFeeds();
|
||||
|
||||
if (wrapWasPresent) {
|
||||
myFirstWrappedBlockOnLine = null;
|
||||
|
||||
if (!whiteSpace.containsLineFeedsInitially()) {
|
||||
whiteSpace.removeLineFeeds(spacing, myBlockRangesMap);
|
||||
}
|
||||
}
|
||||
|
||||
final boolean wrapIsPresent = whiteSpace.containsLineFeeds();
|
||||
|
||||
final ArrayList<WrapImpl> wraps = currentBlock.getWraps();
|
||||
for (WrapImpl wrap : wraps) {
|
||||
wrap.setWrapOffset(currentBlock.getStartOffset());
|
||||
}
|
||||
|
||||
final WrapImpl wrap = getWrapToBeUsed(wraps, currentBlock);
|
||||
|
||||
if (wrap != null || wrapIsPresent) {
|
||||
if (!wrapIsPresent && !canReplaceWrapCandidate(wrap, currentBlock)) {
|
||||
return myWrapCandidate;
|
||||
}
|
||||
if (wrap != null && wrap.getChopStartBlock() != null) {
|
||||
// getWrapToBeUsed() returns the block only if it actually exceeds the right margin. In this case, we need to go back to the
|
||||
// first block that has the CHOP_IF_NEEDED wrap type and start wrapping from there.
|
||||
LeafBlockWrapper newCurrentBlock = wrap.getChopStartBlock();
|
||||
wrap.setActive();
|
||||
return newCurrentBlock;
|
||||
}
|
||||
if (wrap != null && isChopNeeded(wrap, currentBlock)) {
|
||||
wrap.setActive();
|
||||
}
|
||||
|
||||
if (!wrapIsPresent) {
|
||||
whiteSpace.ensureLineFeed();
|
||||
if (!wrapWasPresent) {
|
||||
if (myFirstWrappedBlockOnLine != null && wrap.isChildOf(myFirstWrappedBlockOnLine.getWrap(), currentBlock)) {
|
||||
wrap.ignoreParentWrap(myFirstWrappedBlockOnLine.getWrap(), currentBlock);
|
||||
return myFirstWrappedBlockOnLine;
|
||||
}
|
||||
else {
|
||||
myFirstWrappedBlockOnLine = currentBlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myWrapCandidate = null;
|
||||
}
|
||||
else {
|
||||
for (final WrapImpl wrap1 : wraps) {
|
||||
if (isCandidateToBeWrapped(wrap1, currentBlock) && canReplaceWrapCandidate(wrap1, currentBlock)) {
|
||||
myWrapCandidate = currentBlock;
|
||||
}
|
||||
if (isChopNeeded(wrap1, currentBlock)) {
|
||||
wrap1.saveChopBlock(currentBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!whiteSpace.containsLineFeeds() && myWrapCandidate != null && !whiteSpace.isReadOnly() && lineOver(currentBlock)) {
|
||||
return myWrapCandidate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isChopNeeded(final WrapImpl wrap, LeafBlockWrapper currentBlock) {
|
||||
return wrap != null && wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && isSuitableInTheCurrentPosition(wrap, currentBlock);
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
myWrapCandidate = null;
|
||||
}
|
||||
|
||||
public void onCurrentLineChanged() {
|
||||
myWrapCandidate = null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user