Expandable indent storing additional block, which indent is used as min indent marker...
[idea/community.git] / platform / lang-impl / src / com / intellij / formatting / InitialInfoBuilder.java
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.formatting;
18
19 import com.intellij.diagnostic.LogMessageEx;
20 import com.intellij.lang.LanguageFormatting;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.util.TextRange;
24 import com.intellij.openapi.util.UnfairTextRange;
25 import com.intellij.psi.PsiFile;
26 import com.intellij.psi.codeStyle.CodeStyleSettings;
27 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
28 import com.intellij.psi.formatter.FormattingDocumentModelImpl;
29 import com.intellij.psi.formatter.ReadOnlyBlockInformationProvider;
30 import com.intellij.psi.impl.DebugUtil;
31 import com.intellij.util.containers.ContainerUtil;
32 import com.intellij.util.containers.LinkedMultiMap;
33 import com.intellij.util.containers.MultiMap;
34 import com.intellij.util.containers.Stack;
35 import gnu.trove.THashMap;
36 import org.jetbrains.annotations.NonNls;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44
45 /**
46  * Allows to build {@link AbstractBlockWrapper formatting block wrappers} for the target {@link Block formatting blocks}.
47  * The main idea of block wrapping is to associate information about {@link WhiteSpace white space before block} with the block itself.
48  */
49 class InitialInfoBuilder {
50   private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.InitialInfoBuilder");
51
52   private final Map<AbstractBlockWrapper, Block> myResult = new THashMap<AbstractBlockWrapper, Block>();
53
54   private final FormattingDocumentModel               myModel;
55   private final FormatTextRanges                      myAffectedRanges;
56   private final int                                   myPositionOfInterest;
57   @NotNull
58   private final FormattingProgressCallback            myProgressCallback;
59   private final FormatterTagHandler                   myFormatterTagHandler;
60
61   private final CommonCodeStyleSettings.IndentOptions myOptions;
62
63   private final Stack<State> myStates = new Stack<State>();
64   private WhiteSpace                       myCurrentWhiteSpace;
65   private CompositeBlockWrapper            myRootBlockWrapper;
66   private LeafBlockWrapper                 myPreviousBlock;
67   private LeafBlockWrapper                 myFirstTokenBlock;
68   private LeafBlockWrapper                 myLastTokenBlock;
69   private SpacingImpl                      myCurrentSpaceProperty;
70   private ReadOnlyBlockInformationProvider myReadOnlyBlockInformationProvider;
71   private boolean                          myInsideFormatRestrictingTag;
72
73   private static final boolean INLINE_TABS_ENABLED = "true".equalsIgnoreCase(System.getProperty("inline.tabs.enabled"));
74
75   private final List<TextRange> myExtendedAffectedRanges;
76   private Set<Alignment> myAlignmentsInsideRangeToModify = ContainerUtil.newHashSet();
77   private boolean myCollectAlignmentsInsideFormattingRange = false;
78
79   private Set<Block> myStrictMinOffsetBlocks = ContainerUtil.newHashSet();
80   private MultiMap<ExpandableIndent, AbstractBlockWrapper> myBlocksToForceChildrenIndent = new LinkedMultiMap<ExpandableIndent, AbstractBlockWrapper>();
81   private Map<Block, AbstractBlockWrapper> myMarkerIndentToBlock = ContainerUtil.newHashMap();
82
83   private InitialInfoBuilder(final Block rootBlock,
84                              final FormattingDocumentModel model,
85                              @Nullable final FormatTextRanges affectedRanges,
86                              @NotNull CodeStyleSettings settings,
87                              final CommonCodeStyleSettings.IndentOptions options,
88                              final int positionOfInterest,
89                              @NotNull FormattingProgressCallback progressCallback)
90   {
91     myModel = model;
92     myAffectedRanges = affectedRanges;
93     myExtendedAffectedRanges = getExtendedAffectedRanges(affectedRanges);
94     myProgressCallback = progressCallback;
95     myCurrentWhiteSpace = new WhiteSpace(getStartOffset(rootBlock), true);
96     myOptions = options;
97     myPositionOfInterest = positionOfInterest;
98     myInsideFormatRestrictingTag = false;
99     myFormatterTagHandler = new FormatterTagHandler(settings);
100   }
101
102   protected static InitialInfoBuilder prepareToBuildBlocksSequentially(Block root,
103                                                                     FormattingDocumentModel model,
104                                                                     @Nullable final FormatTextRanges affectedRanges,
105                                                                     @NotNull CodeStyleSettings settings,
106                                                                     final CommonCodeStyleSettings.IndentOptions options,
107                                                                     int interestingOffset,
108                                                                     @NotNull FormattingProgressCallback progressCallback)
109   {
110     InitialInfoBuilder builder = new InitialInfoBuilder(root, model, affectedRanges, settings, options, interestingOffset, progressCallback);
111     builder.buildFrom(root, 0, null, null, null, true);
112     return builder;
113   }
114
115   private int getStartOffset(@NotNull Block rootBlock) {
116     int minOffset = rootBlock.getTextRange().getStartOffset();
117     if (myAffectedRanges != null) {
118       for (FormatTextRanges.FormatTextRange range : myAffectedRanges.getRanges()) {
119         if (range.getStartOffset() < minOffset) minOffset = range.getStartOffset();
120       }
121     }
122     return minOffset;
123   }
124
125   int getEndOffset() {
126     int maxDocOffset = myModel.getTextLength();
127     int maxOffset = myRootBlockWrapper != null ? myRootBlockWrapper.getEndOffset() : 0;
128     if (myAffectedRanges != null) {
129       for (FormatTextRanges.FormatTextRange range : myAffectedRanges.getRanges()) {
130         if (range.getTextRange().getEndOffset() > maxOffset) maxOffset = range.getTextRange().getEndOffset();
131       }
132     }
133     return   maxOffset < maxDocOffset ? maxOffset : maxDocOffset;
134   }
135
136   /**
137    * Asks current builder to wrap one more remaining {@link Block code block} (if any).
138    *
139    * @return    <code>true</code> if all blocks are wrapped; <code>false</code> otherwise
140    */
141   public boolean iteration() {
142     if (myStates.isEmpty()) {
143       return true;
144     }
145
146     State state = myStates.peek();
147     doIteration(state);
148     return myStates.isEmpty();
149   }
150   
151   /**
152    * Wraps given root block and all of its descendants and returns root block wrapper.
153    * <p/>
154    * This method performs necessary infrastructure actions and delegates actual processing to
155    * {@link #buildCompositeBlock(Block, CompositeBlockWrapper, int, WrapImpl, boolean)} and
156    * {@link #processSimpleBlock(Block, CompositeBlockWrapper, boolean, int, Block)}.
157    *
158    * @param rootBlock             block to wrap
159    * @param index                 index of the current block at its parent block. <code>-1</code> may be used here if we don't
160    *                              have information about parent block
161    * @param parent                parent block wrapper. <code>null</code> may be used here we no parent block wrapper exists
162    * @param currentWrapParent     parent wrap if any; <code>null</code> otherwise
163    * @param parentBlock           parent block of the block to wrap
164    * @param rootBlockIsRightBlock flag that shows if target block is the right-most block
165    * @return wrapper for the given <code>'rootBlock'</code>
166    */
167   private AbstractBlockWrapper buildFrom(final Block rootBlock,
168                                          final int index,
169                                          @Nullable final CompositeBlockWrapper parent,
170                                          @Nullable WrapImpl currentWrapParent,
171                                          @Nullable final Block parentBlock,
172                                          boolean rootBlockIsRightBlock)
173   {
174     final WrapImpl wrap = (WrapImpl)rootBlock.getWrap();
175     if (wrap != null) {
176       wrap.registerParent(currentWrapParent);
177       currentWrapParent = wrap;
178     }
179     TextRange textRange = rootBlock.getTextRange();
180     final int blockStartOffset = textRange.getStartOffset();
181
182     if (parent != null) {
183       if (textRange.getStartOffset() < parent.getStartOffset()) {
184         assertInvalidRanges(
185           textRange.getStartOffset(),
186           parent.getStartOffset(),
187           myModel,
188           "child block start is less than parent block start"
189         );
190       }
191
192       if (textRange.getEndOffset() > parent.getEndOffset()) {
193         assertInvalidRanges(
194           textRange.getEndOffset(),
195           parent.getEndOffset(),
196           myModel,
197           "child block end is after parent block end"
198         );
199       }
200     }
201
202     myCurrentWhiteSpace.append(blockStartOffset, myModel, myOptions);
203
204     if (myCollectAlignmentsInsideFormattingRange && rootBlock.getAlignment() != null
205         && isAffectedByFormatting(rootBlock) && !myInsideFormatRestrictingTag)
206     {
207       myAlignmentsInsideRangeToModify.add(rootBlock.getAlignment());
208     }
209
210     ReadOnlyBlockInformationProvider previousProvider = myReadOnlyBlockInformationProvider;
211     try {
212       if (rootBlock instanceof ReadOnlyBlockInformationProvider) {
213         myReadOnlyBlockInformationProvider = (ReadOnlyBlockInformationProvider)rootBlock;
214       }
215       if (isInsideFormattingRanges(rootBlock, rootBlockIsRightBlock)
216           || myCollectAlignmentsInsideFormattingRange && isInsideExtendedAffectedRange(rootBlock))
217       {
218         final List<Block> subBlocks = rootBlock.getSubBlocks();
219         if (subBlocks.isEmpty() || myReadOnlyBlockInformationProvider != null && myReadOnlyBlockInformationProvider.isReadOnly(rootBlock)) {
220           final AbstractBlockWrapper wrapper = processSimpleBlock(rootBlock, parent, false, index, parentBlock);
221           if (!subBlocks.isEmpty()) {
222             wrapper.setIndent((IndentImpl)subBlocks.get(0).getIndent());
223           }
224           return wrapper;
225         }
226         return buildCompositeBlock(rootBlock, parent, index, currentWrapParent, rootBlockIsRightBlock);
227       }
228       else {
229         //block building is skipped
230         return processSimpleBlock(rootBlock, parent, true, index, parentBlock);
231       }
232     }
233     finally {
234       myReadOnlyBlockInformationProvider = previousProvider;
235     }
236   }
237
238   private boolean isInsideExtendedAffectedRange(Block rootBlock) {
239     if (myExtendedAffectedRanges == null) return false;
240
241     TextRange blockRange = rootBlock.getTextRange();
242     for (TextRange affectedRange : myExtendedAffectedRanges) {
243       if (affectedRange.intersects(blockRange)) return true;
244     }
245
246     return false;
247   }
248
249   @Nullable
250   private static List<TextRange> getExtendedAffectedRanges(FormatTextRanges formatTextRanges) {
251     if (formatTextRanges == null) return null;
252
253     List<FormatTextRanges.FormatTextRange> ranges = formatTextRanges.getRanges();
254     List<TextRange> extended = ContainerUtil.newArrayList();
255
256     final int extendOffset = 500;
257     for (FormatTextRanges.FormatTextRange textRange : ranges) {
258       TextRange range = textRange.getTextRange();
259       extended.add(new UnfairTextRange(range.getStartOffset() - extendOffset, range.getEndOffset() + extendOffset));
260     }
261
262     return extended;
263   }
264
265   private CompositeBlockWrapper buildCompositeBlock(final Block rootBlock,
266                                    @Nullable final CompositeBlockWrapper parent,
267                                    final int index,
268                                    @Nullable final WrapImpl currentWrapParent,
269                                    boolean rootBlockIsRightBlock)
270   {
271     final CompositeBlockWrapper wrappedRootBlock = new CompositeBlockWrapper(rootBlock, myCurrentWhiteSpace, parent);
272     if (index == 0) {
273       wrappedRootBlock.arrangeParentTextRange();
274     }
275
276     if (myRootBlockWrapper == null) {
277       myRootBlockWrapper = wrappedRootBlock;
278       myRootBlockWrapper.setIndent((IndentImpl)Indent.getNoneIndent());
279     }
280     boolean blocksMayBeOfInterest = false;
281
282     if (myPositionOfInterest != -1) {
283       myResult.put(wrappedRootBlock, rootBlock);
284       blocksMayBeOfInterest = true;
285     }
286     
287     final boolean blocksAreReadOnly = rootBlock instanceof ReadOnlyBlockContainer || blocksMayBeOfInterest;
288     
289     State state = new State(rootBlock, wrappedRootBlock, currentWrapParent, blocksAreReadOnly, rootBlockIsRightBlock);
290     myStates.push(state);
291     return wrappedRootBlock;
292   }
293
294   public MultiMap<ExpandableIndent, AbstractBlockWrapper> getExpandableIndentsBlocks() {
295     return myBlocksToForceChildrenIndent;
296   }
297
298   public Map<Block, AbstractBlockWrapper> getMarkerBlocks() {
299     return myMarkerIndentToBlock;
300   }
301
302   private void doIteration(@NotNull State state) {
303     List<Block> subBlocks = state.parentBlock.getSubBlocks();
304     final int subBlocksCount = subBlocks.size();
305     int childBlockIndex = state.getIndexOfChildBlockToProcess();
306     final Block block = subBlocks.get(childBlockIndex);
307     if (state.previousBlock != null || (myCurrentWhiteSpace != null && myCurrentWhiteSpace.isIsFirstWhiteSpace())) {
308       myCurrentSpaceProperty = (SpacingImpl)state.parentBlock.getSpacing(state.previousBlock, block);
309     }
310
311     boolean childBlockIsRightBlock = false;
312
313     if (childBlockIndex == subBlocksCount - 1 && state.parentBlockIsRightBlock) {
314       childBlockIsRightBlock = true;
315     }
316
317     final AbstractBlockWrapper wrapper = buildFrom(
318       block, childBlockIndex, state.wrappedBlock, state.parentBlockWrap, state.parentBlock, childBlockIsRightBlock
319     );
320     registerExpandableIndents(block, wrapper);
321
322     if (wrapper.getIndent() == null) {
323       wrapper.setIndent((IndentImpl)block.getIndent());
324     }
325     if (!state.readOnly) {
326       try {
327         subBlocks.set(childBlockIndex, null); // to prevent extra strong refs during model building
328       } catch (Throwable ex) {
329         // read-only blocks
330       }
331     }
332     
333     if (state.childBlockProcessed(block, wrapper)) {
334       while (!myStates.isEmpty() && myStates.peek().isProcessed()) {
335         myStates.pop();
336       }
337     }
338   }
339
340   private void registerExpandableIndents(@NotNull Block block, @NotNull AbstractBlockWrapper wrapper) {
341     ExpandableIndent expandableIndent = block.getIndent() instanceof ExpandableIndent ? ((ExpandableIndent)block.getIndent()) : null;
342     if (expandableIndent != null) {
343       myBlocksToForceChildrenIndent.putValue(expandableIndent, wrapper);
344       Block markerBlock = expandableIndent.getStrictMinOffsetBlock();
345       if (markerBlock != null) {
346         myStrictMinOffsetBlocks.add(markerBlock);
347       }
348     }
349
350     if (myStrictMinOffsetBlocks.contains(block)) {
351       myMarkerIndentToBlock.put(block, wrapper);
352     }
353   }
354
355   private void setDefaultIndents(final List<AbstractBlockWrapper> list) {
356     if (!list.isEmpty()) {
357       for (AbstractBlockWrapper wrapper : list) {
358         if (wrapper.getIndent() == null) {
359           wrapper.setIndent((IndentImpl)Indent.getContinuationWithoutFirstIndent(myOptions.USE_RELATIVE_INDENTS));
360         }
361       }
362     }
363   }
364
365   private AbstractBlockWrapper processSimpleBlock(final Block rootBlock,
366                                                   @Nullable final CompositeBlockWrapper parent,
367                                                   final boolean readOnly,
368                                                   final int index,
369                                                   @Nullable Block parentBlock) 
370   {
371     LeafBlockWrapper result = doProcessSimpleBlock(rootBlock, parent, readOnly, index, parentBlock);
372     myProgressCallback.afterWrappingBlock(result);
373     return result;
374   }
375
376   private LeafBlockWrapper doProcessSimpleBlock(final Block rootBlock,
377                                                 @Nullable final CompositeBlockWrapper parent,
378                                                 final boolean readOnly,
379                                                 final int index,
380                                                 @Nullable Block parentBlock)
381   {
382     if (!INLINE_TABS_ENABLED && !myCurrentWhiteSpace.containsLineFeeds()) {
383       myCurrentWhiteSpace.setForceSkipTabulationsUsage(true);
384     }
385     final LeafBlockWrapper info =
386       new LeafBlockWrapper(rootBlock, parent, myCurrentWhiteSpace, myModel, myOptions, myPreviousBlock, readOnly);
387     if (index == 0) {
388       info.arrangeParentTextRange();
389     }
390
391     switch (myFormatterTagHandler.getFormatterTag(rootBlock)) {
392       case ON:
393         myInsideFormatRestrictingTag = false;
394         break;
395       case OFF:
396         myInsideFormatRestrictingTag = true;
397         break;
398       case NONE:
399         break;
400     }
401
402     TextRange textRange = rootBlock.getTextRange();
403     if (textRange.getLength() == 0) {
404       assertInvalidRanges(
405         textRange.getStartOffset(),
406         textRange.getEndOffset(),
407         myModel,
408         "empty block"
409       );
410     }
411     if (myPreviousBlock != null) {
412       myPreviousBlock.setNextBlock(info);
413     }
414     if (myFirstTokenBlock == null) {
415       myFirstTokenBlock = info;
416     }
417     myLastTokenBlock = info;
418     if (currentWhiteSpaceIsReadOnly()) {
419       myCurrentWhiteSpace.setReadOnly(true);
420     }
421     if (myCurrentSpaceProperty != null) {
422       myCurrentWhiteSpace.setIsSafe(myCurrentSpaceProperty.isSafe());
423       myCurrentWhiteSpace.setKeepFirstColumn(myCurrentSpaceProperty.shouldKeepFirstColumn());
424     }
425
426     if (info.isEndOfCodeBlock()) {
427       myCurrentWhiteSpace.setBeforeCodeBlockEnd(true);
428     }
429
430     info.setSpaceProperty(myCurrentSpaceProperty);
431     myCurrentWhiteSpace = new WhiteSpace(textRange.getEndOffset(), false);
432     if (myInsideFormatRestrictingTag) myCurrentWhiteSpace.setReadOnly(true);
433     myPreviousBlock = info;
434
435     if (myPositionOfInterest != -1 && (textRange.contains(myPositionOfInterest) || textRange.getEndOffset() == myPositionOfInterest)) {
436       myResult.put(info, rootBlock);
437       if (parent != null) myResult.put(parent, parentBlock);
438     }
439     return info;
440   }
441
442   private boolean currentWhiteSpaceIsReadOnly() {
443     if (myCurrentSpaceProperty != null && myCurrentSpaceProperty.isReadOnly()) {
444       return true;
445     }
446     else {
447       if (myAffectedRanges == null) return false;
448       return myAffectedRanges.isWhitespaceReadOnly(myCurrentWhiteSpace.getTextRange());
449     }
450   }
451
452   private boolean isAffectedByFormatting(final Block block) {
453     if (myAffectedRanges == null) return true;
454
455     List<FormatTextRanges.FormatTextRange> allRanges = myAffectedRanges.getRanges();
456     Document document = myModel.getDocument();
457     int docLength = document.getTextLength();
458     
459     for (FormatTextRanges.FormatTextRange range : allRanges) {
460       int startOffset = range.getStartOffset();
461       if (startOffset >= docLength) continue;
462       
463       int lineNumber = document.getLineNumber(startOffset);
464       int lineEndOffset = document.getLineEndOffset(lineNumber);
465
466       int blockStartOffset = block.getTextRange().getStartOffset();
467       if (blockStartOffset >= startOffset && blockStartOffset < lineEndOffset) {
468         return true;
469       }
470     }
471     
472     return false;
473   }
474
475   private boolean isInsideFormattingRanges(final Block block, boolean rootIsRightBlock) {
476     if (myAffectedRanges == null) return true;
477     return !myAffectedRanges.isReadOnly(block.getTextRange(), rootIsRightBlock);
478   }
479
480   public Map<AbstractBlockWrapper, Block> getBlockToInfoMap() {
481     return myResult;
482   }
483
484   public CompositeBlockWrapper getRootBlockWrapper() {
485     return myRootBlockWrapper;
486   }
487
488   public LeafBlockWrapper getFirstTokenBlock() {
489     return myFirstTokenBlock;
490   }
491
492   public LeafBlockWrapper getLastTokenBlock() {
493     return myLastTokenBlock;
494   }
495
496   public static void assertInvalidRanges(final int startOffset, final int newEndOffset, FormattingDocumentModel model, String message) {
497     @NonNls final StringBuilder buffer = new StringBuilder();
498     buffer.append("Invalid formatting blocks:").append(message).append("\n");
499     buffer.append("Start offset:");
500     buffer.append(startOffset);
501     buffer.append(" end offset:");
502     buffer.append(newEndOffset);
503     buffer.append("\n");
504
505     int minOffset = Math.max(Math.min(startOffset, newEndOffset) - 20, 0);
506     int maxOffset = Math.min(Math.max(startOffset, newEndOffset) + 20, model.getTextLength());
507
508     buffer.append("Affected text fragment:[").append(minOffset).append(",").append(maxOffset).append("] - '")
509       .append(model.getText(new TextRange(minOffset, maxOffset))).append("'\n");
510
511     final StringBuilder messageBuffer =  new StringBuilder();
512     messageBuffer.append("Invalid ranges during formatting");
513     if (model instanceof FormattingDocumentModelImpl) {
514       messageBuffer.append(" in ").append(((FormattingDocumentModelImpl)model).getFile().getLanguage());
515     }
516
517     buffer.append("File text:(").append(model.getTextLength()).append(")\n'");
518     buffer.append(model.getText(new TextRange(0, model.getTextLength())).toString());
519     buffer.append("'\n");
520     buffer.append("model (").append(model.getClass()).append("): ").append(model);
521
522     Throwable currentThrowable = new Throwable();
523     if (model instanceof FormattingDocumentModelImpl) {
524       final FormattingDocumentModelImpl modelImpl = (FormattingDocumentModelImpl)model;
525       buffer.append("Psi Tree:\n");
526       final PsiFile file = modelImpl.getFile();
527       final List<PsiFile> roots = file.getViewProvider().getAllFiles();
528       for (PsiFile root : roots) {
529         buffer.append("Root ");
530         DebugUtil.treeToBuffer(buffer, root.getNode(), 0, false, true, true, true);
531       }
532       buffer.append('\n');
533       currentThrowable = makeLanguageStackTrace(currentThrowable, file);
534     }
535
536     LogMessageEx.error(LOG, messageBuffer.toString(), currentThrowable, buffer.toString());
537   }
538   
539   private static Throwable makeLanguageStackTrace(@NotNull Throwable currentThrowable, @NotNull PsiFile file) {
540     Throwable langThrowable = new Throwable();
541     FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file);
542     if (builder == null) return currentThrowable;
543     Class builderClass = builder.getClass();
544     Class declaringClass = builderClass.getDeclaringClass();
545     String guessedFileName = (declaringClass == null ? builderClass.getSimpleName() : declaringClass.getSimpleName())  + ".java";
546     StackTraceElement ste = new StackTraceElement(builder.getClass().getName(), "createModel", guessedFileName, 1);
547     StackTraceElement[] originalStackTrace = currentThrowable.getStackTrace();
548     StackTraceElement[] modifiedStackTrace = new StackTraceElement[originalStackTrace.length + 1];
549     System.arraycopy(originalStackTrace, 0, modifiedStackTrace, 1, originalStackTrace.length);
550     modifiedStackTrace[0] = ste;
551     langThrowable.setStackTrace(modifiedStackTrace);
552     return langThrowable;
553   }
554
555   public Set<Alignment> getAlignmentsInsideRangeToModify() {
556     return myAlignmentsInsideRangeToModify;
557   }
558
559   public void setCollectAlignmentsInsideFormattingRange(boolean value) {
560     myCollectAlignmentsInsideFormattingRange = value;
561   }
562
563   /**
564    * We want to wrap {@link Block code blocks} sequentially, hence, need to store a processing state and continue from the point
565    * where we stopped the processing last time.
566    * <p/>
567    * Current class defines common contract for the state required for such a processing.
568    */
569   private class State {
570
571     public final Block                 parentBlock;
572     public final WrapImpl              parentBlockWrap;
573     public final CompositeBlockWrapper wrappedBlock;
574     public final boolean               readOnly;
575     public final boolean               parentBlockIsRightBlock;
576     
577     public Block previousBlock;
578     
579     private final List<AbstractBlockWrapper> myWrappedChildren = new ArrayList<AbstractBlockWrapper>();
580
581     State(@NotNull Block parentBlock, @NotNull CompositeBlockWrapper wrappedBlock, @Nullable WrapImpl parentBlockWrap,
582           boolean readOnly, boolean parentBlockIsRightBlock)
583     {
584       this.parentBlock = parentBlock;
585       this.wrappedBlock = wrappedBlock;
586       this.parentBlockWrap = parentBlockWrap;
587       this.readOnly = readOnly;
588       this.parentBlockIsRightBlock = parentBlockIsRightBlock;
589     }
590
591     /**
592      * @return    index of the first non-processed {@link Block#getSubBlocks() child block} of the {@link #parentBlock target block}
593      */
594     public int getIndexOfChildBlockToProcess() {
595       return myWrappedChildren.size();
596     }
597     
598     /**
599      * Notifies current state that child block is processed.
600      * 
601      * @return    <code>true</code> if all child blocks of the block denoted by the current state are processed;
602      *            <code>false</code> otherwise
603      */
604     public boolean childBlockProcessed(@NotNull Block child, @NotNull AbstractBlockWrapper wrappedChild) {
605       myWrappedChildren.add(wrappedChild);
606       previousBlock = child;
607       
608       int subBlocksNumber = parentBlock.getSubBlocks().size();
609       if (myWrappedChildren.size() > subBlocksNumber) {
610         return true;
611       }
612       else if (myWrappedChildren.size() == subBlocksNumber) {
613         setDefaultIndents(myWrappedChildren);
614         wrappedBlock.setChildren(myWrappedChildren);
615         return true;
616       }
617       return false;
618     }
619
620     /**
621      * @return    <code>true</code> if current state is processed (basically, if all {@link Block#getSubBlocks() child blocks})
622      *            of the {@link #parentBlock target block} are processed; <code>false</code> otherwise
623      */
624     public boolean isProcessed() {
625       return myWrappedChildren.size() == parentBlock.getSubBlocks().size();
626     }
627   }
628 }