Merge branch 'formatter-engine-refactoring'
authorYaroslav Lepenkin <yaroslav.lepenkin@jetbrains.com>
Wed, 20 Apr 2016 10:32:07 +0000 (13:32 +0300)
committerYaroslav Lepenkin <yaroslav.lepenkin@jetbrains.com>
Wed, 20 Apr 2016 10:32:07 +0000 (13:32 +0300)
28 files changed:
platform/lang-impl/src/com/intellij/formatting/AbstractBlockWrapper.java
platform/lang-impl/src/com/intellij/formatting/AlignmentImpl.java
platform/lang-impl/src/com/intellij/formatting/CompositeBlockWrapper.java
platform/lang-impl/src/com/intellij/formatting/DependantSpacingImpl.java
platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java
platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java
platform/lang-impl/src/com/intellij/formatting/IndentImpl.java
platform/lang-impl/src/com/intellij/formatting/IndentInside.java
platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java
platform/lang-impl/src/com/intellij/formatting/LeafBlockWrapper.java
platform/lang-impl/src/com/intellij/formatting/SpacingImpl.java
platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java
platform/lang-impl/src/com/intellij/formatting/WrapImpl.java
platform/lang-impl/src/com/intellij/formatting/engine/AdjustWhiteSpacesState.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/AlignmentHelper.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/ApplyChangesState.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/BlockIndentOptions.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/BlockRangesMap.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/CaretOffsetUpdater.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/DependentSpacingEngine.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/ExpandChildrenIndentState.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/ExpandableIndent.java [moved from platform/lang-impl/src/com/intellij/formatting/ExpandableIndent.java with 92% similarity]
platform/lang-impl/src/com/intellij/formatting/engine/FormatProcessorUtils.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/IndentAdjuster.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/State.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/StateProcessor.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/WrapBlocksState.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/formatting/engine/WrapProcessor.java [new file with mode: 0644]

index 8081b7f4a47edd8d57d664cfedd804baf4472b16..0043dce2f854ae414c5668663167bb65ce6d4724 100644 (file)
@@ -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
index eebf46217aacaee6b9252e11762c94a05da17ae4..76ccf68191076e0ecb472b0b9c614b9d1ac2b353 100644 (file)
@@ -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;
     }
index 97508169a4de874a74001e07f630550180765ccd..fd5c95999d9fd4d4d7e81130dc908722c5767deb 100644 (file)
@@ -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);
     }
index 4b9d8c1023b7f53182c8e402ed2eacedbef7e05c..c56ce5f376f077c73ca562af2fe86b95ba60eea3 100644 (file)
@@ -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;
index ece6987525b16008e9ba7d37886408fa3207e7cc..caf00cbf450eeab75568c73e1107b51fb9fbbecd 100644 (file)
 
 package com.intellij.formatting;
 
-import com.intellij.diagnostic.LogMessageEx;
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.Language;
+import com.intellij.formatting.engine.*;
 import com.intellij.openapi.diagnostic.Logger;
 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.ex.DocumentEx;
-import com.intellij.openapi.editor.impl.BulkChangesMerger;
-import com.intellij.openapi.editor.impl.TextChangeImpl;
-import com.intellij.openapi.fileTypes.StdFileTypes;
-import com.intellij.openapi.util.Condition;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
 import com.intellij.psi.codeStyle.CodeStyleSettings;
 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
-import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.containers.MultiMap;
-import com.intellij.util.ui.UIUtil;
-import gnu.trove.TIntObjectHashMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.*;
+import java.util.List;
 
-import static com.intellij.formatting.AbstractBlockAlignmentProcessor.Context;
+import static com.intellij.formatting.InitialInfoBuilder.prepareToBuildBlocksSequentially;
 
 public class FormatProcessor {
-
-  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());
-  }
-
-  /**
-   * 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 static final Logger LOG = Logger.getInstance("#com.intellij.formatting.FormatProcessor");
-  private Set<Alignment> myAlignmentsInsideRangesToModify = null;
+  
+  private final WrapBlocksState myWrapState;
   private boolean myReformatContext;
-
-  private LeafBlockWrapper myCurrentBlock;
-
-  private Map<AbstractBlockWrapper, Block>    myInfos;
-  private CompositeBlockWrapper               myRootBlockWrapper;
-  private TIntObjectHashMap<LeafBlockWrapper> myTextRangeToWrapper;
-
-  private final CommonCodeStyleSettings.IndentOptions myDefaultIndentOption;
-  private final CodeStyleSettings                     mySettings;
-  private final Document                              myDocument;
-
-  /**
-   * Remembers mappings between backward-shifted aligned block and blocks that cause that shift in order to detect
-   * infinite cycles that may occur when, for example following alignment is specified:
-   * <p/>
-   * <pre>
-   *     int i1     = 1;
-   *     int i2, i3 = 2;
-   * </pre>
-   * <p/>
-   * There is a possible case that <code>'i1'</code>, <code>'i2'</code> and <code>'i3'</code> blocks re-use
-   * the same alignment, hence, <code>'i1'</code> is shifted to right during <code>'i3'</code> processing but
-   * that causes <code>'i2'</code> to be shifted right as wll because it's aligned to <code>'i1'</code> that
-   * increases offset of <code>'i3'</code> that, in turn, causes backward shift of <code>'i1'</code> etc.
-   * <p/>
-   * This map remembers such backward shifts in order to be able to break such infinite cycles.
-   */
-  private final Map<LeafBlockWrapper, Set<LeafBlockWrapper>> myBackwardShiftedAlignedBlocks
-    = new HashMap<LeafBlockWrapper, Set<LeafBlockWrapper>>();
-
-  private final Map<AbstractBlockWrapper, Set<AbstractBlockWrapper>> myAlignmentMappings
-    = new HashMap<AbstractBlockWrapper, Set<AbstractBlockWrapper>>();
-
-  /**
-   * There is a possible case that we detect a 'cycled alignment' rules (see {@link #myBackwardShiftedAlignedBlocks}). We want
-   * just to skip processing for such alignments then.
-   * <p/>
-   * This container holds 'bad alignment' objects that should not be processed.
-   */
-  private final Set<Alignment> myAlignmentsToSkip = new HashSet<Alignment>();
-
-  private LeafBlockWrapper myWrapCandidate           = null;
-  private LeafBlockWrapper myFirstWrappedBlockOnLine = null;
-
-  private LeafBlockWrapper myFirstTokenBlock;
-  private LeafBlockWrapper myLastTokenBlock;
-
-  /**
-   * 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 = {    | &lt; 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.
-   */
-  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;
-      }
-    });
-
-  private final HashSet<WhiteSpace> myAlignAgain = new HashSet<WhiteSpace>();
+  private final Document myDocument;
+  
   @NotNull
   private final FormattingProgressCallback myProgressCallback;
 
-  private WhiteSpace                      myLastWhiteSpace;
-  private boolean                         myDisposed;
-  private CommonCodeStyleSettings.IndentOptions myJavaIndentOptions;
-  private final int myRightMargin;
-
   @NotNull
-  private State myCurrentState;
-  private MultiMap<ExpandableIndent, AbstractBlockWrapper> myExpandableIndents;
-  private int myTotalBlocksWithAlignments;
-  private int myBlockRollbacks;
-  
-  private AlignmentCyclesDetector myCyclesDetector;
+  private StateProcessor myStateProcessor;
 
   public FormatProcessor(final FormattingDocumentModel docModel,
                          Block rootBlock,
@@ -178,855 +51,106 @@ public class FormatProcessor {
     this(docModel, rootBlock, new FormatOptions(settings, indentOptions, affectedRanges, false), progressCallback);
   }
 
-  public FormatProcessor(FormattingDocumentModel model,
+  public FormatProcessor(final FormattingDocumentModel model,
                          Block block,
                          FormatOptions options,
                          @NotNull FormattingProgressCallback callback)
   {
     myProgressCallback = callback;
-    myDefaultIndentOption = options.myIndentOptions;
-    mySettings = options.mySettings;
+    
+    CommonCodeStyleSettings.IndentOptions defaultIndentOption = options.myIndentOptions;
+    CodeStyleSettings settings = options.mySettings;
+    BlockIndentOptions blockIndentOptions = new BlockIndentOptions(settings, defaultIndentOption, block);
+    
     myDocument = model.getDocument();
     myReformatContext = options.myReformatContext;
-    myCurrentState = new WrapBlocksState(block, model, options.myAffectedRanges, options.myInterestingOffset);
-    myRightMargin = getRightMargin(block);
-  }
-
-  private int getRightMargin(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);
-  }
-
-  private LeafBlockWrapper getLastBlock() {
-    LeafBlockWrapper result = myFirstTokenBlock;
-    while (result.getNextBlock() != null) {
-      result = result.getNextBlock();
-    }
-    return result;
+    
+    final InitialInfoBuilder builder = prepareToBuildBlocksSequentially(block, model, options, settings, defaultIndentOption, myProgressCallback);
+    myWrapState = new WrapBlocksState(builder, blockIndentOptions);
+    
+    myStateProcessor = new StateProcessor(myWrapState);
   }
-
-  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 BlockRangesMap getBlockRangesMap() {
+    return myWrapState.getBlockRangesMap();
   }
 
   public void format(FormattingModel model) {
-    format(model, false);
-  }
-
-  /**
-   * Asks current processor to perform formatting.
-   * <p/>
-   * There are two processing approaches at the moment:
-   * <pre>
-   * <ul>
-   *   <li>perform formatting during the current method call;</li>
-   *   <li>
-   *     split the whole formatting process to the set of fine-grained tasks and execute them sequentially during
-   *     subsequent {@link #iteration()} calls;
-   *   </li>
-   * </ul>
-   * </pre>
-   * <p/>
-   * Here is rationale for the second approach - formatting may introduce changes to the underlying document and IntelliJ IDEA
-   * is designed in a way that write access is allowed from EDT only. That means that every time we execute particular action
-   * from EDT we have no chance of performing any other actions from EDT simultaneously (e.g. we may want to show progress bar
-   * that reflects current formatting state but the progress bar can' bet updated if formatting is performed during a single long
-   * method call). So, we can interleave formatting iterations with GUI state updates.
-   *
-   * @param model         target formatting model
-   * @param sequentially  flag that indicates what kind of processing should be used
-   */
-  public void format(FormattingModel model, boolean sequentially) {
-    if (sequentially) {
-      AdjustWhiteSpacesState adjustState = new AdjustWhiteSpacesState();
-      ExpandChildrenIndent expandChildrenIndent = new ExpandChildrenIndent();
-      ApplyChangesState applyChangesState = new ApplyChangesState(model);
-
-      expandChildrenIndent.setNext(applyChangesState);
-      adjustState.setNext(expandChildrenIndent);
-      myCurrentState.setNext(adjustState);
-    }
-    else {
-      formatWithoutRealModifications(false);
-      performModifications(model, false);
-    }
-  }
-
-  /**
-   * Asks current processor to perform processing iteration
-   *
-   * @return    <code>true</code> if the processing is finished; <code>false</code> otherwise
-   * @see #format(FormattingModel, boolean)
-   */
-  public boolean iteration() {
-    if (myCurrentState.isDone()) {
-      return true;
-    }
-    myCurrentState.iteration();
-    return myCurrentState.isDone();
-  }
-
-  /**
-   * Asks current processor to stop any active sequential processing if any.
-   */
-  public void stopSequentialProcessing() {
-    myCurrentState.stop();
-  }
-
-  public void formatWithoutRealModifications() {
-    formatWithoutRealModifications(false);
-  }
-
-  @SuppressWarnings({"WhileLoopSpinsOnField"})
-  public void formatWithoutRealModifications(boolean sequentially) {
-    AdjustWhiteSpacesState adjustSpace = new AdjustWhiteSpacesState();
-    adjustSpace.setNext(new ExpandChildrenIndent());
-    myCurrentState.setNext(adjustSpace);
-
-    if (sequentially) {
-      return;
-    }
-
-    doIterationsSynchronously(FormattingStateId.PROCESSING_BLOCKS);
-  }
-
-  private void reset() {
-    myBackwardShiftedAlignedBlocks.clear();
-    myAlignmentMappings.clear();
-    myPreviousDependencies.clear();
-    myWrapCandidate = null;
-    if (myRootBlockWrapper != null) {
-      myRootBlockWrapper.reset();
-    }
-  }
-
-  public void performModifications(FormattingModel model) {
-    performModifications(model, false);
-  }
-
-  public void performModifications(FormattingModel model, boolean sequentially) {
-    assert !myDisposed;
-    myCurrentState.setNext(new ApplyChangesState(model));
-
-    if (sequentially) {
-      return;
-    }
-
-    doIterationsSynchronously(FormattingStateId.APPLYING_CHANGES);
-  }
-
-  /**
-   * Perform iterations against the {@link #myCurrentState current state} until it's {@link FormattingStateId type}
-   * is {@link FormattingStateId#getPreviousStates() less} or equal to the given state.
-   *
-   * @param state   target state to process
-   */
-  private void doIterationsSynchronously(@NotNull FormattingStateId state) {
-    while ((myCurrentState.getStateId() == state || state.getPreviousStates().contains(myCurrentState.getStateId()))
-           && !myCurrentState.isDone())
-    {
-      myCurrentState.iteration();
-    }
-  }
-
-  public void setJavaIndentOptions(final CommonCodeStyleSettings.IndentOptions javaIndentOptions) {
-    myJavaIndentOptions = javaIndentOptions;
-  }
-
-  /**
-   * 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
-   * @param indentOption          indent options to use
-   */
-  @SuppressWarnings({"deprecation"})
-  private void applyChangesAtRewriteMode(@NotNull final List<LeafBlockWrapper> blocksToModify,
-                                            @NotNull final FormattingModel model,
-                                            @NotNull CommonCodeStyleSettings.IndentOptions indentOption)
-  {
-    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(getIndentOptionsToUse(block, indentOption)), 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 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;
-  }
-
-  @NotNull
-  private List<LeafBlockWrapper> collectBlocksToModify() {
-    List<LeafBlockWrapper> blocksToModify = new ArrayList<LeafBlockWrapper>();
-
-    for (LeafBlockWrapper block = myFirstTokenBlock; block != null; block = block.getNextBlock()) {
-      final WhiteSpace whiteSpace = block.getWhiteSpace();
-      if (!whiteSpace.isReadOnly()) {
-        final String newWhiteSpace = whiteSpace.generateWhiteSpace(getIndentOptionsToUse(block, myDefaultIndentOption));
-        if (!whiteSpace.equalsToString(newWhiteSpace)) {
-          blocksToModify.add(block);
-        }
-      }
-    }
-    return blocksToModify;
-  }
-
-  @NotNull
-  private CommonCodeStyleSettings.IndentOptions getIndentOptionsToUse(@NotNull AbstractBlockWrapper block,
-                                                                      @NotNull CommonCodeStyleSettings.IndentOptions fallbackIndentOptions)
-  {
-    final Language language = block.getLanguage();
-    if (language == null) {
-      return fallbackIndentOptions;
-    }
-    final CommonCodeStyleSettings commonSettings = mySettings.getCommonSettings(language);
-    if (commonSettings == null) {
-      return fallbackIndentOptions;
-    }
-    final CommonCodeStyleSettings.IndentOptions result = commonSettings.getIndentOptions();
-    return result == null ? fallbackIndentOptions : result;
-  }
-
-  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, this);
-
-    if (!whiteSpace.containsLineFeeds()) {
-      whiteSpace.arrangeSpaces(spaceProperty);
-    }
-
-    try {
-      if (processWrap()) {
-        return;
-      }
-    }
-    finally {
-      if (whiteSpace.containsLineFeeds()) {
-        onCurrentLineChanged();
-      }
-    }
-
-    if (!adjustIndent()) {
-      return;
-    }
-
-    defineAlignOffset(myCurrentBlock);
-
-    if (myCurrentBlock.containsLineFeeds()) {
-      onCurrentLineChanged();
-    }
-
-
-    final List<TextRange> ranges = getDependentRegionRangesAfterCurrentWhiteSpace(spaceProperty, whiteSpace);
-    if (!ranges.isEmpty()) {
-      registerUnresolvedDependentSpacingRanges(spaceProperty, ranges);
-    }
-
-    if (!whiteSpace.isIsReadOnly() && shouldReformatPreviouslyLocatedDependentSpacing(whiteSpace)) {
-      myAlignAgain.add(whiteSpace);
-    }
-    else if (!myAlignAgain.isEmpty()) {
-      myAlignAgain.remove(whiteSpace);
-    }
-
-    myCurrentBlock = myCurrentBlock.getNextBlock();
-  }
-
-  private boolean isReformatSelectedRangesContext() {
-    return myReformatContext && !ContainerUtil.isEmpty(myAlignmentsInsideRangesToModify);
-  }
-
-  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 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 = containsLineFeeds(textRange);
-
-        if (containedLineFeeds != containsLineFeeds) {
-          spacing.setDependentRegionLinefeedStatusChanged();
-          return true;
-        }
-      }
-    }
-
-    return false;
-  }
-
-  private 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);
-    }
-  }
-
-  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();
-      }
-    });
-  }
-
-  /**
-   * Processes the wrap of the current block.
-   *
-   * @return true if we have changed myCurrentBlock and need to restart its processing; false if myCurrentBlock is unchanged and we can
-   * continue processing
-   */
-  private boolean processWrap() {
-    final SpacingImpl spacing = myCurrentBlock.getSpaceProperty();
-    final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
-
-    final boolean wrapWasPresent = whiteSpace.containsLineFeeds();
-
-    if (wrapWasPresent) {
-      myFirstWrappedBlockOnLine = null;
-
-      if (!whiteSpace.containsLineFeedsInitially()) {
-        whiteSpace.removeLineFeeds(spacing, this);
-      }
-    }
-
-    final boolean wrapIsPresent = whiteSpace.containsLineFeeds();
-
-    final ArrayList<WrapImpl> wraps = myCurrentBlock.getWraps();
-    for (WrapImpl wrap : wraps) {
-      wrap.setWrapOffset(myCurrentBlock.getStartOffset());
-    }
-
-    final WrapImpl wrap = getWrapToBeUsed(wraps);
-
-    if (wrap != null || wrapIsPresent) {
-      if (!wrapIsPresent && !canReplaceWrapCandidate(wrap)) {
-        myCurrentBlock = myWrapCandidate;
-        return true;
-      }
-      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.
-        myCurrentBlock = wrap.getChopStartBlock();
-        wrap.setActive();
-        return true;
-      }
-      if (wrap != null && isChopNeeded(wrap)) {
-        wrap.setActive();
-      }
-
-      if (!wrapIsPresent) {
-        whiteSpace.ensureLineFeed();
-        if (!wrapWasPresent) {
-          if (myFirstWrappedBlockOnLine != null && wrap.isChildOf(myFirstWrappedBlockOnLine.getWrap(), myCurrentBlock)) {
-            wrap.ignoreParentWrap(myFirstWrappedBlockOnLine.getWrap(), myCurrentBlock);
-            myCurrentBlock = myFirstWrappedBlockOnLine;
-            return true;
-          }
-          else {
-            myFirstWrappedBlockOnLine = myCurrentBlock;
-          }
-        }
-      }
-
-      myWrapCandidate = null;
-    }
-    else {
-      for (final WrapImpl wrap1 : wraps) {
-        if (isCandidateToBeWrapped(wrap1) && canReplaceWrapCandidate(wrap1)) {
-          myWrapCandidate = myCurrentBlock;
-        }
-        if (isChopNeeded(wrap1)) {
-          wrap1.saveChopBlock(myCurrentBlock);
-        }
-      }
-    }
-
-    if (!whiteSpace.containsLineFeeds() && myWrapCandidate != null && !whiteSpace.isReadOnly() && lineOver()) {
-      myCurrentBlock = myWrapCandidate;
-      return true;
-    }
-
-    return false;
-  }
-
-  /**
-   * 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) {
-    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, myCurrentBlock);
-  }
-
-  private boolean isCandidateToBeWrapped(final WrapImpl wrap) {
-    return isSuitableInTheCurrentPosition(wrap) &&
-           (wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED || wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED) &&
-           !myCurrentBlock.getWhiteSpace().isReadOnly();
-  }
-
-  private void onCurrentLineChanged() {
-    myWrapCandidate = null;
-  }
-
-  /**
-   * Adjusts indent of the current block.
-   *
-   * @return <code>true</code> if current formatting iteration should be continued;
-   *         <code>false</code> otherwise (e.g. if previously processed block is shifted inside this method for example
-   *         because of specified alignment options)
-   */
-  private boolean adjustIndent() {
-    AlignmentImpl alignment = CoreFormatterUtil.getAlignment(myCurrentBlock);
-    WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
-
-    if (alignment == null || myAlignmentsToSkip.contains(alignment)) {
-      if (whiteSpace.containsLineFeeds()) {
-        adjustSpacingByIndentOffset();
-      }
-      else {
-        whiteSpace.arrangeSpaces(myCurrentBlock.getSpaceProperty());
-      }
-      return true;
-    }
-
-    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 true;
-    }
-
-    BlockAlignmentProcessor.Context context = new BlockAlignmentProcessor.Context(
-      myDocument, alignment, myCurrentBlock, myAlignmentMappings, myBackwardShiftedAlignedBlocks,
-      getIndentOptionsToUse(myCurrentBlock, myDefaultIndentOption));
-    final LeafBlockWrapper offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(myCurrentBlock);
-    myCyclesDetector.registerOffsetResponsibleBlock(offsetResponsibleBlock);
-    BlockAlignmentProcessor.Result result = alignmentProcessor.applyAlignment(context);
-    switch (result) {
-      case TARGET_BLOCK_PROCESSED_NOT_ALIGNED: return true;
-      case TARGET_BLOCK_ALIGNED: storeAlignmentMapping(); return true;
-      case BACKWARD_BLOCK_ALIGNED:
-        if (offsetResponsibleBlock == null) {
-          return true;
-        }
-        Set<LeafBlockWrapper> blocksCausedRealignment = new HashSet<LeafBlockWrapper>();
-        myBackwardShiftedAlignedBlocks.clear();
-        myBackwardShiftedAlignedBlocks.put(offsetResponsibleBlock, blocksCausedRealignment);
-        blocksCausedRealignment.add(myCurrentBlock);
-        storeAlignmentMapping(myCurrentBlock, offsetResponsibleBlock);
-        
-        if (myCyclesDetector.isCycleDetected()) {
-          reportAlignmentProcessingError(context);
-          return true;
-        }
-        else {
-          myCyclesDetector.registerBlockRollback(myCurrentBlock);
-          myCurrentBlock = offsetResponsibleBlock.getNextBlock();
-        }
-        onCurrentLineChanged();
-        return false;
-      case RECURSION_DETECTED:
-        myCurrentBlock = offsetResponsibleBlock; // Fall through to the 'register alignment to skip'.
-      case UNABLE_TO_ALIGN_BACKWARD_BLOCK:
-        myAlignmentsToSkip.add(alignment);
-        return false;
-      default: return true;
-    }
-  }
-
-  private static void reportAlignmentProcessingError(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());
-  }
-
-  /**
-   * We need to track blocks which white spaces are modified because of alignment rules.
-   * <p/>
-   * This method encapsulates the logic of storing such information.
-   */
-  private void storeAlignmentMapping() {
-    AlignmentImpl alignment = null;
-    AbstractBlockWrapper block = myCurrentBlock;
-    while (alignment == null && block != null) {
-      alignment = block.getAlignment();
-      block = block.getParent();
-    }
-    if (alignment != null) {
-      block = alignment.getOffsetRespBlockBefore(myCurrentBlock);
-      if (block != null) {
-        storeAlignmentMapping(myCurrentBlock, block);
-      }
-    }
-  }
-
-  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);
-  }
-
-  /**
-   * Applies indent to the white space of {@link #myCurrentBlock currently processed wrapped block}. Both indentation
-   * and alignment options are took into consideration here.
-   */
-  private void adjustLineIndent() {
-    IndentData alignOffset = getAlignOffset();
-
-    if (alignOffset == null) {
-      adjustSpacingByIndentOffset();
-    }
-    else {
-      myCurrentBlock.getWhiteSpace().setSpaces(alignOffset.getSpaces(), alignOffset.getIndentSpaces());
-    }
-  }
-
-  private void adjustSpacingByIndentOffset() {
-    IndentData offset = myCurrentBlock.calculateOffset(getIndentOptionsToUse(myCurrentBlock, myDefaultIndentOption));
-    myCurrentBlock.getWhiteSpace().setSpaces(offset.getSpaces(), offset.getIndentSpaces());
-  }
-
-  private boolean isChopNeeded(final WrapImpl wrap) {
-    return wrap != null && wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && isSuitableInTheCurrentPosition(wrap);
-  }
-
-  private boolean isSuitableInTheCurrentPosition(final WrapImpl wrap) {
-    if (wrap.getWrapOffset() < myCurrentBlock.getStartOffset()) {
-      return true;
-    }
-
-    if (wrap.isWrapFirstElement()) {
-      return true;
-    }
-
-    if (wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED) {
-      return positionAfterWrappingIsSuitable();
-    }
-
-    return wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && lineOver() && positionAfterWrappingIsSuitable();
+    format(model, false);
   }
 
-  /**
-   * 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() {
-    final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
-    if (whiteSpace.containsLineFeeds()) return true;
-    final int spaces = whiteSpace.getSpaces();
-    int indentSpaces = whiteSpace.getIndentSpaces();
-    try {
-      final int startColumnNow = CoreFormatterUtil.getStartColumn(myCurrentBlock);
-      whiteSpace.ensureLineFeed();
-      adjustLineIndent();
-      final int startColumnAfterWrap = CoreFormatterUtil.getStartColumn(myCurrentBlock);
-      return startColumnNow > startColumnAfterWrap;
+  public void format(FormattingModel model, boolean sequentially) {
+    if (sequentially) {
+      myStateProcessor.setNextState(new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext));
+      myStateProcessor.setNextState(new ExpandChildrenIndentState(myDocument, myWrapState));
+      myStateProcessor.setNextState(new ApplyChangesState(model, myWrapState, myProgressCallback));
     }
-    finally {
-      whiteSpace.removeLineFeeds(myCurrentBlock.getSpaceProperty(), this);
-      whiteSpace.setSpaces(spaces, indentSpaces);
+    else {
+      formatWithoutRealModifications(false);
+      performModifications(model, false);
     }
   }
 
-  @Nullable
-  private WrapImpl getWrapToBeUsed(final ArrayList<WrapImpl> wraps) {
-    if (wraps.isEmpty()) {
-      return null;
-    }
-    if (myWrapCandidate == myCurrentBlock) return wraps.get(0);
-
-    for (final WrapImpl wrap : wraps) {
-      if (!isSuitableInTheCurrentPosition(wrap)) 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()) {
-          return wrap;
-        }
-      }
+  public boolean iteration() {
+    if (myStateProcessor.isDone()) {
+      return true;
     }
-    return null;
+    myStateProcessor.iteration();
+    return myStateProcessor.isDone();
   }
 
   /**
-   * @return <code>true</code> if {@link #myCurrentBlock currently processed wrapped block} doesn't contain line feeds and
-   *         exceeds right margin; <code>false</code> otherwise
+   * Asks current processor to stop any active sequential processing if any.
    */
-  private boolean lineOver() {
-    return !myCurrentBlock.containsLineFeeds() &&
-           CoreFormatterUtil.getStartColumn(myCurrentBlock) + myCurrentBlock.getLength() > myRightMargin;
+  public void stopSequentialProcessing() {
+    myStateProcessor.stop();
   }
 
-  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;
-
-    }
+  public void formatWithoutRealModifications() {
+    formatWithoutRealModifications(false);
   }
 
-  /**
-   * Tries to get align-implied indent of the current block.
-   *
-   * @return indent of the current block if any; <code>null</code> otherwise
-   */
-  @Nullable
-  private IndentData getAlignOffset() {
-    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 void formatWithoutRealModifications(boolean sequentially) {
+    myStateProcessor.setNextState(new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext));
+    myStateProcessor.setNextState(new ExpandChildrenIndentState(myDocument, myWrapState));
+    if (sequentially) {
+      return;
     }
+    doIterationsSynchronously();
   }
 
-  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;
+  public void performModifications(FormattingModel model) {
+    performModifications(model, false);
   }
 
-  @Nullable
-  public LeafBlockWrapper getBlockAtOrAfter(final int startOffset) {
-    int current = startOffset;
-    LeafBlockWrapper result = null;
-    while (current < myLastWhiteSpace.getStartOffset()) {
-      final LeafBlockWrapper currentValue = myTextRangeToWrapper.get(current);
-      if (currentValue != null) {
-        result = currentValue;
-        break;
-      }
-      current++;
-    }
-
-    LeafBlockWrapper prevBlock = getPrevBlock(result);
+  public void performModifications(FormattingModel model, boolean sequentially) {
+    myStateProcessor.setNextState(new ApplyChangesState(model, myWrapState, myProgressCallback));
 
-    if (prevBlock != null && prevBlock.contains(startOffset)) {
-      return prevBlock;
-    }
-    else {
-      return result;
+    if (sequentially) {
+      return;
     }
+
+    doIterationsSynchronously();
   }
 
-  @Nullable
-  private LeafBlockWrapper getPrevBlock(@Nullable final LeafBlockWrapper result) {
-    if (result != null) {
-      return result.getPreviousBlock();
-    }
-    else {
-      return myLastTokenBlock;
+  private void doIterationsSynchronously() {
+    while (!myStateProcessor.isDone()) {
+      myStateProcessor.iteration();
     }
   }
 
   public void setAllWhiteSpacesAreReadOnly() {
-    LeafBlockWrapper current = myFirstTokenBlock;
+    LeafBlockWrapper current = myWrapState.getFirstBlock();
     while (current != null) {
       current.getWhiteSpace().setReadOnly(true);
       current = current.getNextBlock();
     }
   }
 
-  static class ChildAttributesInfo {
+  public static class ChildAttributesInfo {
     public final AbstractBlockWrapper parent;
-    final        ChildAttributes      attributes;
-    final        int                  index;
+    public final ChildAttributes attributes;
+    public final int index;
 
     public ChildAttributesInfo(final AbstractBlockWrapper parent, final ChildAttributes attributes, final int index) {
       this.parent = parent;
@@ -1036,15 +160,15 @@ public class FormatProcessor {
   }
 
   public IndentInfo getIndentAt(final int offset) {
-    processBlocksBefore(offset);
-    AbstractBlockWrapper parent = getParentFor(offset, myCurrentBlock);
+    LeafBlockWrapper current = processBlocksBefore(offset);
+    AbstractBlockWrapper parent = getParentFor(offset, current);
     if (parent == null) {
-      final LeafBlockWrapper previousBlock = myCurrentBlock.getPreviousBlock();
+      final LeafBlockWrapper previousBlock = current.getPreviousBlock();
       if (previousBlock != null) parent = getParentFor(offset, previousBlock);
       if (parent == null) return new IndentInfo(0, 0, 0);
     }
     int index = getNewChildPosition(parent, offset);
-    final Block block = myInfos.get(parent);
+    final Block block = myWrapState.getBlockToInfoMap().get(parent);
 
     if (block == null) {
       return new IndentInfo(0, 0, 0);
@@ -1055,7 +179,8 @@ public class FormatProcessor {
       return new IndentInfo(0, 0, 0);
     }
 
-    return adjustLineIndent(info.parent, info.attributes, info.index);
+    IndentAdjuster adjuster = myWrapState.getIndentAdjuster();
+    return adjuster.adjustLineIndent(current, info);
   }
 
   @Nullable
@@ -1100,40 +225,7 @@ public class FormatProcessor {
       return new ChildAttributesInfo(parent, childAttributes, index);
     }
   }
-
-  private IndentInfo adjustLineIndent(final AbstractBlockWrapper parent, final ChildAttributes childAttributes, final int index) {
-    int alignOffset = getAlignOffsetBefore(childAttributes.getAlignment(), null);
-    if (alignOffset == -1) {
-      return parent.calculateChildOffset(getIndentOptionsToUse(parent, myDefaultIndentOption), childAttributes, index).createIndentInfo();
-    }
-    else {
-      AbstractBlockWrapper indentedParentBlock = CoreFormatterUtil.getIndentedParentBlock(myCurrentBlock);
-      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;
-    }
-  }
-
+  
   private static int getNewChildPosition(final AbstractBlockWrapper parent, final int offset) {
     AbstractBlockWrapper parentBlockToUse = getLastNestedCompositeBlockForSameRange(parent);
     if (!(parentBlockToUse instanceof CompositeBlockWrapper)) return 0;
@@ -1177,8 +269,9 @@ public class FormatProcessor {
   @Nullable
   private AbstractBlockWrapper getPreviousIncompleteBlock(final LeafBlockWrapper block, final int offset) {
     if (block == null) {
-      if (myLastTokenBlock.isIncomplete()) {
-        return myLastTokenBlock;
+      LeafBlockWrapper lastTokenBlock = myWrapState.getLastBlock();
+      if (lastTokenBlock.isIncomplete()) {
+        return lastTokenBlock;
       }
       else {
         return null;
@@ -1263,343 +356,36 @@ public class FormatProcessor {
     }
     return result;
   }
-
-  private void processBlocksBefore(final int offset) {
-    while (true) {
-      myAlignAgain.clear();
-      myCurrentBlock = myFirstTokenBlock;
-      while (myCurrentBlock != null && myCurrentBlock.getStartOffset() < offset) {
-        processToken();
-        if (myCurrentBlock == null) {
-          myCurrentBlock = myLastTokenBlock;
-          if (myCurrentBlock != null) {
-            myProgressCallback.afterProcessingBlock(myCurrentBlock);
-          }
-          break;
-        }
-      }
-      if (myAlignAgain.isEmpty()) return;
-      reset();
+  
+  private LeafBlockWrapper processBlocksBefore(final int offset) {
+    AdjustWhiteSpacesState state = new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext);
+    state.prepare();
+    
+    LeafBlockWrapper last = null;
+    while (!state.isDone() && state.getCurrentBlock().getStartOffset() < offset) {
+      last = state.getCurrentBlock();
+      state.doIteration();
     }
+
+    return state.getCurrentBlock() != null ? state.getCurrentBlock() : last;
   }
 
   public LeafBlockWrapper getFirstTokenBlock() {
-    return myFirstTokenBlock;
+    return myWrapState.getFirstBlock();
   }
 
   public WhiteSpace getLastWhiteSpace() {
-    return myLastWhiteSpace;
-  }
-
-  /**
-   * Calculates difference in visual columns between the given indents.
-   *
-   * @param oldIndent  old indent
-   * @param newIndent  new indent
-   * @param options    indent options to use
-   * @return           difference in visual columns between the given indents
-   */
-  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);
-  }
-
-  /**
-   * Utility method to use during debugging formatter processing.
-   *
-   * @return    text that contains intermediate formatter-introduced changes (even not committed yet)
-   */
-  @SuppressWarnings("UnusedDeclaration")
-  @NotNull
-  private String getCurrentText() {
-    StringBuilder result = new StringBuilder();
-    for (LeafBlockWrapper block = myFirstTokenBlock; block != null; block = block.getNextBlock()) {
-      result.append(block.getWhiteSpace().generateWhiteSpace(getIndentOptionsToUse(block, myDefaultIndentOption)));
-      result.append(myDocument.getCharsSequence().subSequence(block.getStartOffset(), block.getEndOffset()));
-    }
-    return result.toString();
-  }
-
-  private abstract class State {
-
-    private final FormattingStateId myStateId;
-
-    private State   myNextState;
-    private boolean myDone;
-
-    protected State(FormattingStateId stateId) {
-      myStateId = stateId;
-    }
-
-    public void iteration() {
-      if (!isDone()) {
-        doIteration();
-      }
-      shiftStateIfNecessary();
-    }
-
-    public boolean isDone() {
-      return myDone;
-    }
-
-    protected void setDone(boolean done) {
-      myDone = done;
-    }
-
-    public void setNext(@NotNull State state) {
-      if (getStateId() == state.getStateId() || (myNextState != null && myNextState.getStateId() == state.getStateId())) {
-        return;
-      }
-      myNextState = state;
-      shiftStateIfNecessary();
-    }
-
-    public FormattingStateId getStateId() {
-      return myStateId;
-    }
-
-    public void stop() {
-    }
-
-    protected abstract void doIteration();
-    protected abstract void prepare();
-
-    private void shiftStateIfNecessary() {
-      if (isDone() && myNextState != null) {
-        myCurrentState = myNextState;
-        myNextState = null;
-        myCurrentState.prepare();
-      }
-    }
-  }
-
-  private class WrapBlocksState extends State {
-
-    private final InitialInfoBuilder      myWrapper;
-    private final FormattingDocumentModel myModel;
-
-    WrapBlocksState(@NotNull Block root,
-                    @NotNull FormattingDocumentModel model,
-                    @Nullable final FormatTextRanges affectedRanges,
-                    int interestingOffset)
-    {
-      super(FormattingStateId.WRAPPING_BLOCKS);
-      myModel = model;
-      myWrapper = InitialInfoBuilder.prepareToBuildBlocksSequentially(
-        root, model, affectedRanges, mySettings, myDefaultIndentOption, interestingOffset, myProgressCallback
-      );
-      myWrapper.setCollectAlignmentsInsideFormattingRange(myReformatContext);
-
-      myExpandableIndents = myWrapper.getExpandableIndentsBlocks();
-    }
-
-    @Override
-    protected void prepare() {
-    }
-
-    @Override
-    public void doIteration() {
-      if (isDone()) {
-        return;
-      }
-
-      setDone(myWrapper.iteration());
-      if (!isDone()) {
-        return;
-      }
-
-      myInfos = myWrapper.getBlockToInfoMap();
-      myRootBlockWrapper = myWrapper.getRootBlockWrapper();
-      myFirstTokenBlock = myWrapper.getFirstTokenBlock();
-      myLastTokenBlock = myWrapper.getLastTokenBlock();
-      myCurrentBlock = myFirstTokenBlock;
-      myTextRangeToWrapper = buildTextRangeToInfoMap(myFirstTokenBlock);
-      int lastBlockOffset = getLastBlock().getEndOffset();
-      myLastWhiteSpace = new WhiteSpace(lastBlockOffset, false);
-      myLastWhiteSpace.append(Math.max(lastBlockOffset, myWrapper.getEndOffset()), myModel, myDefaultIndentOption);
-      myAlignmentsInsideRangesToModify = myWrapper.getAlignmentsInsideRangeToModify();
-      myTotalBlocksWithAlignments = myWrapper.getBlocksToAlign().values().size();
-
-      myCyclesDetector = new AlignmentCyclesDetector(myTotalBlocksWithAlignments);
-    }
-  }
-
-  private class AdjustWhiteSpacesState extends State {
-
-    AdjustWhiteSpacesState() {
-      super(FormattingStateId.PROCESSING_BLOCKS);
-    }
-
-    @Override
-    protected void prepare() {
-    }
-
-    @Override
-    protected void doIteration() {
-      LeafBlockWrapper blockToProcess = myCurrentBlock;
-      processToken();
-      if (blockToProcess != null) {
-        myProgressCallback.afterProcessingBlock(blockToProcess);
-      }
-
-      if (myCurrentBlock != null) {
-        return;
-      }
-
-      if (myAlignAgain.isEmpty()) {
-        setDone(true);
-      }
-      else {
-        myAlignAgain.clear();
-        myPreviousDependencies.clear();
-        myCurrentBlock = myFirstTokenBlock;
-      }
-    }
-  }
-
-  private class ApplyChangesState extends State {
-
-    private final FormattingModel        myModel;
-    private       List<LeafBlockWrapper> myBlocksToModify;
-    private       int                    myShift;
-    private       int                    myIndex;
-    private       boolean                myResetBulkUpdateState;
-
-    private ApplyChangesState(FormattingModel model) {
-      super(FormattingStateId.APPLYING_CHANGES);
-      myModel = model;
-    }
-
-    @Override
-    protected void prepare() {
-      myBlocksToModify = collectBlocksToModify();
-      // call doModifications static method to ensure no access to state
-      // thus we may clear formatting state
-      reset();
-
-      myInfos = null;
-      myRootBlockWrapper = null;
-      myTextRangeToWrapper = null;
-      myPreviousDependencies = null;
-      myLastWhiteSpace = null;
-      myFirstTokenBlock = null;
-      myLastTokenBlock = null;
-      myDisposed = true;
-
-      if (myBlocksToModify.isEmpty()) {
-        setDone(true);
-        return;
-      }
-
-      //for GeneralCodeFormatterTest
-      if (myJavaIndentOptions == null) {
-        myJavaIndentOptions = mySettings.getIndentOptions(StdFileTypes.JAVA);
-      }
-
-      myProgressCallback.beforeApplyingFormatChanges(myBlocksToModify);
-
-      final int blocksToModifyCount = myBlocksToModify.size();
-      if (blocksToModifyCount > BULK_REPLACE_OPTIMIZATION_CRITERIA) {
-        applyChangesAtRewriteMode(myBlocksToModify, myModel, myDefaultIndentOption);
-        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 = replaceWhiteSpace(
-        myModel,
-        blockWrapper,
-        myShift,
-        blockWrapper.getWhiteSpace().generateWhiteSpace(getIndentOptionsToUse(blockWrapper, myDefaultIndentOption)),
-        myDefaultIndentOption
-      );
-      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();
-          }
-        });
-      }
-    }
-  }
-
-  private static class CaretOffsetUpdater {
-    private final Map<Editor, Integer> myCaretOffsets = new HashMap<Editor, Integer>();
-
-    private CaretOffsetUpdater(@NotNull Document document) {
-      Editor[] editors = EditorFactory.getInstance().getEditors(document);
-      for (Editor editor : editors) {
-        myCaretOffsets.put(editor, editor.getCaretModel().getOffset());
-      }
-    }
-
-    private 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));
-      }
-    }
-
-    private void restoreCaretLocations() {
-      for (Map.Entry<Editor, Integer> entry : myCaretOffsets.entrySet()) {
-        entry.getKey().getCaretModel().moveToOffset(entry.getValue());
-      }
-    }
+    return myWrapState.getLastWhiteSpace();
   }
-
-
+  
   public static class FormatOptions {
-    private CodeStyleSettings mySettings;
-    private CommonCodeStyleSettings.IndentOptions myIndentOptions;
+    public CodeStyleSettings mySettings;
+    public CommonCodeStyleSettings.IndentOptions myIndentOptions;
 
-    private FormatTextRanges myAffectedRanges;
-    private boolean myReformatContext;
+    public FormatTextRanges myAffectedRanges;
+    public boolean myReformatContext;
 
-    private int myInterestingOffset;
+    public int myInterestingOffset;
 
     public FormatOptions(CodeStyleSettings settings,
                          CommonCodeStyleSettings.IndentOptions options,
@@ -1620,192 +406,4 @@ public class FormatProcessor {
       myInterestingOffset = interestingOffset;
     }
   }
-
-  private class ExpandChildrenIndent extends State {
-    private Iterator<ExpandableIndent> myIterator;
-    private MultiMap<Alignment, LeafBlockWrapper> myBlocksToRealign = new MultiMap<Alignment, LeafBlockWrapper>();
-
-    public ExpandChildrenIndent() {
-      super(FormattingStateId.EXPANDING_CHILDREN_INDENTS);
-    }
-
-    @Override
-    protected void doIteration() {
-      if (myIterator == null) {
-        myIterator = myExpandableIndents.keySet().iterator();
-      }
-      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;
-          adjustIndent();
-          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();
-      }
-    }
-
-    @Override
-    protected void prepare() {
-    }
-  }
 }
index f8dbc4ed53f9f00dc176dcd45a4272085b9ec2fa..2fd4d92f865cbff846748c566a0689b2f94139ed 100644 (file)
@@ -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();
index 314531d5714bf2ebc2d5a4d704ed50b80e59f984..cbed59277f781dac7d04ce68713f5d69b6d9bf57 100644 (file)
@@ -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;
 
index 709a08d7d3eec4fe1d294d0cf30eab0978485aff..b6b126f0812f074b399e2356c9d1edb56aed8457 100644 (file)
@@ -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;
index b9db5823b859d0df6952e9c0946ef3ec9fc28b94..db44a57a373fafed30d9f08f1eb6be9ec92a3464 100644 (file)
@@ -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) {
index 8ba16b4ddddd4e81c49d1c904b6af184437e4eb8..ab3ef322a786a3ed4dae7db7a139aa3e078d7d38 100644 (file)
@@ -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();
 
index 06334cb5866014bcb65cbd8ccbd5821bbb30efb8..d0009f7aeb5e0dc9917f0aaf89edad21baed3879 100644 (file)
 
 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() {
index 63692a8999258f560eb55f3bb199525aca6cff3b..7bee50ca0ea3be448ad8e9fac9168df82cad9c25 100644 (file)
@@ -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);
   }
 
index 7be44a8e6816fdc05d8d76d70676349d78c9c14b..1eb07bf66265897b0a65b550307ea91ef1f5962c 100644 (file)
@@ -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;
   }
 
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/AdjustWhiteSpacesState.java b/platform/lang-impl/src/com/intellij/formatting/engine/AdjustWhiteSpacesState.java
new file mode 100644 (file)
index 0000000..1088d66
--- /dev/null
@@ -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();
+  }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/AlignmentHelper.java b/platform/lang-impl/src/com/intellij/formatting/engine/AlignmentHelper.java
new file mode 100644 (file)
index 0000000..a5a48b2
--- /dev/null
@@ -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();
+  }
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/ApplyChangesState.java b/platform/lang-impl/src/com/intellij/formatting/engine/ApplyChangesState.java
new file mode 100644 (file)
index 0000000..3f6eb09
--- /dev/null
@@ -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();
+        }
+      });
+    }
+  }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/BlockIndentOptions.java b/platform/lang-impl/src/com/intellij/formatting/engine/BlockIndentOptions.java
new file mode 100644 (file)
index 0000000..9cdb278
--- /dev/null
@@ -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);
+  }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/BlockRangesMap.java b/platform/lang-impl/src/com/intellij/formatting/engine/BlockRangesMap.java
new file mode 100644 (file)
index 0000000..422cf75
--- /dev/null
@@ -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;
+    }
+  }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/CaretOffsetUpdater.java b/platform/lang-impl/src/com/intellij/formatting/engine/CaretOffsetUpdater.java
new file mode 100644 (file)
index 0000000..1423dfa
--- /dev/null
@@ -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());
+      }
+    }
+  }
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/DependentSpacingEngine.java b/platform/lang-impl/src/com/intellij/formatting/engine/DependentSpacingEngine.java
new file mode 100644 (file)
index 0000000..c3e1d1a
--- /dev/null
@@ -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 = {    | &lt; 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();
+  }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/ExpandChildrenIndentState.java b/platform/lang-impl/src/com/intellij/formatting/engine/ExpandChildrenIndentState.java
new file mode 100644 (file)
index 0000000..296a436
--- /dev/null
@@ -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();
+    }
+  }
+}
\ No newline at end of file
similarity index 92%
rename from platform/lang-impl/src/com/intellij/formatting/ExpandableIndent.java
rename to platform/lang-impl/src/com/intellij/formatting/engine/ExpandableIndent.java
index 8b60c5fb6ba73adcc7e758ee402a2016769a37c1..fd634ca383d8f4547d296108f9762317dcf62f55 100644 (file)
@@ -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;
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/FormatProcessorUtils.java b/platform/lang-impl/src/com/intellij/formatting/engine/FormatProcessorUtils.java
new file mode 100644 (file)
index 0000000..c6b8066
--- /dev/null
@@ -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;
+  }
+  
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/IndentAdjuster.java b/platform/lang-impl/src/com/intellij/formatting/engine/IndentAdjuster.java
new file mode 100644 (file)
index 0000000..8d42729
--- /dev/null
@@ -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;
+    }
+  }
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/State.java b/platform/lang-impl/src/com/intellij/formatting/engine/State.java
new file mode 100644 (file)
index 0000000..77f1760
--- /dev/null
@@ -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;
+  }
+  
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/StateProcessor.java b/platform/lang-impl/src/com/intellij/formatting/engine/StateProcessor.java
new file mode 100644 (file)
index 0000000..606d96e
--- /dev/null
@@ -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();
+  }
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/WrapBlocksState.java b/platform/lang-impl/src/com/intellij/formatting/engine/WrapBlocksState.java
new file mode 100644 (file)
index 0000000..aeb8097
--- /dev/null
@@ -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();
+  }
+  
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/WrapProcessor.java b/platform/lang-impl/src/com/intellij/formatting/engine/WrapProcessor.java
new file mode 100644 (file)
index 0000000..acce258
--- /dev/null
@@ -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;
+  }
+  
+}