Expandable indent storing additional block, which indent is used as min indent marker...
[idea/community.git] / platform / lang-impl / src / com / intellij / formatting / FormatterImpl.java
1 /*
2  * Copyright 2000-2014 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.lang.ASTNode;
20 import com.intellij.openapi.application.Application;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Document;
24 import com.intellij.openapi.fileTypes.FileType;
25 import com.intellij.openapi.progress.ProgressManager;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.util.Computable;
28 import com.intellij.openapi.util.Couple;
29 import com.intellij.openapi.util.TextRange;
30 import com.intellij.psi.PsiDocumentManager;
31 import com.intellij.psi.PsiElement;
32 import com.intellij.psi.PsiFile;
33 import com.intellij.psi.codeStyle.CodeStyleSettings;
34 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
35 import com.intellij.psi.formatter.FormatterUtil;
36 import com.intellij.psi.formatter.FormattingDocumentModelImpl;
37 import com.intellij.psi.formatter.PsiBasedFormattingModel;
38 import com.intellij.util.IncorrectOperationException;
39 import com.intellij.util.SequentialTask;
40 import com.intellij.util.text.CharArrayUtil;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.concurrent.atomic.AtomicInteger;
48 import java.util.concurrent.atomic.AtomicReference;
49
50 import static com.intellij.formatting.FormatProcessor.FormatOptions;
51
52 public class FormatterImpl extends FormatterEx
53   implements IndentFactory,
54              WrapFactory,
55              AlignmentFactory,
56              SpacingFactory,
57              FormattingModelFactory {
58   private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.FormatterImpl");
59
60   private final AtomicReference<FormattingProgressTask> myProgressTask = new AtomicReference<FormattingProgressTask>();
61
62   private final AtomicInteger myIsDisabledCount = new AtomicInteger();
63   private final IndentImpl NONE_INDENT = new IndentImpl(Indent.Type.NONE, false, false);
64   private final IndentImpl myAbsoluteNoneIndent = new IndentImpl(Indent.Type.NONE, true, false);
65   private final IndentImpl myLabelIndent = new IndentImpl(Indent.Type.LABEL, false, false);
66   private final IndentImpl myContinuationIndentRelativeToDirectParent = new IndentImpl(Indent.Type.CONTINUATION, false, true);
67   private final IndentImpl myContinuationIndentNotRelativeToDirectParent = new IndentImpl(Indent.Type.CONTINUATION, false, false);
68   private final IndentImpl myContinuationWithoutFirstIndentRelativeToDirectParent
69     = new IndentImpl(Indent.Type.CONTINUATION_WITHOUT_FIRST, false, true);
70   private final IndentImpl myContinuationWithoutFirstIndentNotRelativeToDirectParent
71     = new IndentImpl(Indent.Type.CONTINUATION_WITHOUT_FIRST, false, false);
72   private final IndentImpl myAbsoluteLabelIndent = new IndentImpl(Indent.Type.LABEL, true, false);
73   private final IndentImpl myNormalIndentRelativeToDirectParent = new IndentImpl(Indent.Type.NORMAL, false, true);
74   private final IndentImpl myNormalIndentNotRelativeToDirectParent = new IndentImpl(Indent.Type.NORMAL, false, false);
75   private final SpacingImpl myReadOnlySpacing = new SpacingImpl(0, 0, 0, true, false, true, 0, false, 0);
76
77   public FormatterImpl() {
78     Indent.setFactory(this);
79     Wrap.setFactory(this);
80     Alignment.setFactory(this);
81     Spacing.setFactory(this);
82     FormattingModelProvider.setFactory(this);
83   }
84
85   @Override
86   public Alignment createAlignment(boolean applyToNonFirstBlocksOnLine, @NotNull Alignment.Anchor anchor) {
87     return new AlignmentImpl(applyToNonFirstBlocksOnLine, anchor);
88   }
89
90   @Override
91   public Alignment createChildAlignment(final Alignment base) {
92     AlignmentImpl result = new AlignmentImpl();
93     result.setParent(base);
94     return result;
95   }
96
97   @Override
98   public Indent getNormalIndent(boolean relative) {
99     return relative ? myNormalIndentRelativeToDirectParent : myNormalIndentNotRelativeToDirectParent;
100   }
101
102   @Override
103   public Indent getNoneIndent() {
104     return NONE_INDENT;
105   }
106
107   @Override
108   public void setProgressTask(@NotNull FormattingProgressTask progressIndicator) {
109     if (!FormatterUtil.isFormatterCalledExplicitly()) {
110       return;
111     }
112     myProgressTask.set(progressIndicator);
113   }
114
115   @Override
116   public int getSpacingForBlockAtOffset(FormattingModel model, int offset) {
117     Couple<Block> blockWithParent = getBlockAtOffset(null, model.getRootBlock(), offset);
118     if (blockWithParent != null) {
119       Block parentBlock = blockWithParent.first;
120       Block targetBlock = blockWithParent.second;
121       if (parentBlock != null && targetBlock != null) {
122         Block prevBlock = findPreviousSibling(parentBlock, targetBlock);
123         if (prevBlock != null) {
124           SpacingImpl spacing = (SpacingImpl)parentBlock.getSpacing(prevBlock, targetBlock);
125           if (spacing != null) {
126             int minSpaces = spacing.getMinSpaces();
127             if (minSpaces > 0) {
128               return minSpaces;
129             }
130           }
131         }
132       }
133     }
134     return 0;
135   }
136
137   @Nullable
138   private static Couple<Block> getBlockAtOffset(@Nullable Block parent, @NotNull Block block, int offset) {
139     TextRange textRange = block.getTextRange();
140     int startOffset = textRange.getStartOffset();
141     int endOffset = textRange.getEndOffset();
142     if (startOffset == offset) {
143       return Couple.of(parent, block);
144     }
145     if (startOffset > offset || endOffset < offset || block.isLeaf()) {
146       return null;
147     }
148     for (Block subBlock : block.getSubBlocks()) {
149       Couple<Block> result = getBlockAtOffset(block, subBlock, offset);
150       if (result != null) {
151         return result;
152       }
153     }
154     return null;
155   }
156
157   @Nullable
158   private static Block findPreviousSibling(@NotNull Block parent, Block block) {
159     Block result = null;
160     for (Block subBlock : parent.getSubBlocks()) {
161       if (subBlock == block) {
162         return result;
163       }
164       result = subBlock;
165     }
166     return null;
167   }
168
169   @Override
170   public void format(final FormattingModel model, final CodeStyleSettings settings,
171                      final CommonCodeStyleSettings.IndentOptions indentOptions,
172                      final CommonCodeStyleSettings.IndentOptions javaIndentOptions,
173                      final FormatTextRanges affectedRanges) throws IncorrectOperationException
174   {
175     try {
176       validateModel(model);
177       SequentialTask task = new MyFormattingTask() {
178         @NotNull
179         @Override
180         protected FormatProcessor buildProcessor() {
181           FormatProcessor processor = new FormatProcessor(
182             model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges, FormattingProgressCallback.EMPTY
183           );
184           processor.setJavaIndentOptions(javaIndentOptions);
185
186           processor.format(model);
187           return processor;
188         }
189       };
190       execute(task);
191     }
192     catch (FormattingModelInconsistencyException e) {
193       LOG.error(e);
194     }
195   }
196
197   @Override
198   public Wrap createWrap(WrapType type, boolean wrapFirstElement) {
199     return new WrapImpl(type, wrapFirstElement);
200   }
201
202   @Override
203   public Wrap createChildWrap(final Wrap parentWrap, final WrapType wrapType, final boolean wrapFirstElement) {
204     final WrapImpl result = new WrapImpl(wrapType, wrapFirstElement);
205     result.registerParent((WrapImpl)parentWrap);
206     return result;
207   }
208
209   @Override
210   @NotNull
211   public Spacing createSpacing(int minOffset,
212                                int maxOffset,
213                                int minLineFeeds,
214                                final boolean keepLineBreaks,
215                                final int keepBlankLines) {
216     return getSpacingImpl(minOffset, maxOffset, minLineFeeds, false, false, keepLineBreaks, keepBlankLines,false, 0);
217   }
218
219   @Override
220   @NotNull
221   public Spacing getReadOnlySpacing() {
222     return myReadOnlySpacing;
223   }
224
225   @NotNull
226   @Override
227   public Spacing createDependentLFSpacing(int minSpaces,
228                                           int maxSpaces,
229                                           @NotNull TextRange dependencyRange,
230                                           boolean keepLineBreaks,
231                                           int keepBlankLines,
232                                           @NotNull DependentSpacingRule rule)
233   {
234     return new DependantSpacingImpl(minSpaces, maxSpaces, dependencyRange, keepLineBreaks, keepBlankLines, rule);
235   }
236
237   @NotNull
238   @Override
239   public Spacing createDependentLFSpacing(int minSpaces,
240                                           int maxSpaces,
241                                           @NotNull List<TextRange> dependentRegion,
242                                           boolean keepLineBreaks,
243                                           int keepBlankLines,
244                                           @NotNull DependentSpacingRule rule)
245   {
246     return new DependantSpacingImpl(minSpaces, maxSpaces, dependentRegion, keepLineBreaks, keepBlankLines, rule);
247   }
248
249   @NotNull
250   private FormattingProgressCallback getProgressCallback() {
251     FormattingProgressCallback result = myProgressTask.get();
252     return result == null ? FormattingProgressCallback.EMPTY : result;
253   }
254
255   @Override
256   public void format(final FormattingModel model,
257                      final CodeStyleSettings settings,
258                      final CommonCodeStyleSettings.IndentOptions indentOptions,
259                      final FormatTextRanges affectedRanges) throws IncorrectOperationException {
260     format(model, settings, indentOptions, affectedRanges, false);
261   }
262
263   public void format(final FormattingModel model,
264                      final CodeStyleSettings settings,
265                      final CommonCodeStyleSettings.IndentOptions indentOptions,
266                      final FormatTextRanges affectedRanges,
267                      final boolean formatContextAroundRanges) throws IncorrectOperationException {
268     try {
269       validateModel(model);
270       SequentialTask task = new MyFormattingTask() {
271         @NotNull
272         @Override
273         protected FormatProcessor buildProcessor() {
274           FormatOptions options = new FormatOptions(settings, indentOptions, affectedRanges, formatContextAroundRanges);
275           FormatProcessor processor = new FormatProcessor(
276             model.getDocumentModel(), model.getRootBlock(), options, getProgressCallback()
277           );
278           processor.format(model, true);
279           return processor;
280         }
281       };
282       execute(task);
283     }
284     catch (FormattingModelInconsistencyException e) {
285       LOG.error(e);
286     }
287   }
288
289   public void formatWithoutModifications(final FormattingDocumentModel model,
290                                          final Block rootBlock,
291                                          final CodeStyleSettings settings,
292                                          final CommonCodeStyleSettings.IndentOptions indentOptions,
293                                          final TextRange affectedRange) throws IncorrectOperationException
294   {
295     SequentialTask task = new MyFormattingTask() {
296       @NotNull
297       @Override
298       protected FormatProcessor buildProcessor() {
299         FormatProcessor result = new FormatProcessor(
300           model, rootBlock, settings, indentOptions, new FormatTextRanges(affectedRange, true), FormattingProgressCallback.EMPTY
301         );
302         result.formatWithoutRealModifications();
303         return result;
304       }
305     };
306     execute(task);
307   }
308
309   /**
310    * Execute given sequential formatting task. Two approaches are possible:
311    * <pre>
312    * <ul>
313    *   <li>
314    *      <b>synchronous</b> - the task is completely executed during the current method processing;
315    *   </li>
316    *   <li>
317    *       <b>asynchronous</b> - the task is executed at background thread under the progress dialog;
318    *   </li>
319    * </ul>
320    * </pre>
321    *
322    * @param task    task to execute
323    */
324   private void execute(@NotNull SequentialTask task) {
325     disableFormatting();
326     Application application = ApplicationManager.getApplication();
327     FormattingProgressTask progressTask = myProgressTask.getAndSet(null);
328     if (progressTask == null || !application.isDispatchThread() || application.isUnitTestMode()) {
329       try {
330         task.prepare();
331         while (!task.isDone()) {
332           task.iteration();
333         }
334       }
335       finally {
336         enableFormatting();
337       }
338     }
339     else {
340       progressTask.setTask(task);
341       Runnable callback = new Runnable() {
342         @Override
343         public void run() {
344           enableFormatting();
345         }
346       };
347       for (FormattingProgressCallback.EventType eventType : FormattingProgressCallback.EventType.values()) {
348         progressTask.addCallback(eventType, callback);
349       }
350       ProgressManager.getInstance().run(progressTask);
351     }
352   }
353
354   @Override
355   public IndentInfo getWhiteSpaceBefore(final FormattingDocumentModel model,
356                                         final Block block,
357                                         final CodeStyleSettings settings,
358                                         final CommonCodeStyleSettings.IndentOptions indentOptions,
359                                         final TextRange affectedRange, final boolean mayChangeLineFeeds)
360   {
361     disableFormatting();
362     try {
363       final FormatProcessor processor = buildProcessorAndWrapBlocks(
364         model, block, settings, indentOptions, new FormatTextRanges(affectedRange, true)
365       );
366       final LeafBlockWrapper blockBefore = processor.getBlockAtOrAfter(affectedRange.getStartOffset());
367       LOG.assertTrue(blockBefore != null);
368       WhiteSpace whiteSpace = blockBefore.getWhiteSpace();
369       LOG.assertTrue(whiteSpace != null);
370       if (!mayChangeLineFeeds) {
371         whiteSpace.setLineFeedsAreReadOnly();
372       }
373       processor.setAllWhiteSpacesAreReadOnly();
374       whiteSpace.setReadOnly(false);
375       processor.formatWithoutRealModifications();
376       return new IndentInfo(whiteSpace.getLineFeeds(), whiteSpace.getIndentOffset(), whiteSpace.getSpaces());
377     }
378     finally {
379       enableFormatting();
380     }
381   }
382
383   @Override
384   public void adjustLineIndentsForRange(final FormattingModel model,
385                                         final CodeStyleSettings settings,
386                                         final CommonCodeStyleSettings.IndentOptions indentOptions,
387                                         final TextRange rangeToAdjust) {
388     disableFormatting();
389     try {
390       validateModel(model);
391       final FormattingDocumentModel documentModel = model.getDocumentModel();
392       final Block block = model.getRootBlock();
393       final FormatProcessor processor = buildProcessorAndWrapBlocks(
394         documentModel, block, settings, indentOptions, new FormatTextRanges(rangeToAdjust, true)
395       );
396       LeafBlockWrapper tokenBlock = processor.getFirstTokenBlock();
397       while (tokenBlock != null) {
398         final WhiteSpace whiteSpace = tokenBlock.getWhiteSpace();
399         whiteSpace.setLineFeedsAreReadOnly(true);
400         if (!whiteSpace.containsLineFeeds()) {
401           whiteSpace.setIsReadOnly(true);
402         }
403         tokenBlock = tokenBlock.getNextBlock();
404       }
405       processor.formatWithoutRealModifications();
406       processor.performModifications(model);
407     }
408     catch (FormattingModelInconsistencyException e) {
409       LOG.error(e);
410     }
411     finally {
412       enableFormatting();
413     }
414   }
415
416   @Override
417   public void formatAroundRange(final FormattingModel model,
418                                 final CodeStyleSettings settings,
419                                 final TextRange textRange,
420                                 final FileType fileType) {
421     disableFormatting();
422     try {
423       validateModel(model);
424       final FormattingDocumentModel documentModel = model.getDocumentModel();
425       final Block block = model.getRootBlock();
426       final FormatProcessor processor = buildProcessorAndWrapBlocks(
427         documentModel, block, settings, settings.getIndentOptions(fileType), null
428       );
429       LeafBlockWrapper tokenBlock = processor.getFirstTokenBlock();
430       while (tokenBlock != null) {
431         final WhiteSpace whiteSpace = tokenBlock.getWhiteSpace();
432
433         if (whiteSpace.getEndOffset() < textRange.getStartOffset() || whiteSpace.getEndOffset() > textRange.getEndOffset() + 1) {
434           whiteSpace.setIsReadOnly(true);
435         } else if (whiteSpace.getStartOffset() > textRange.getStartOffset() &&
436                    whiteSpace.getEndOffset() < textRange.getEndOffset())
437         {
438           if (whiteSpace.containsLineFeeds()) {
439             whiteSpace.setLineFeedsAreReadOnly(true);
440           } else {
441             whiteSpace.setIsReadOnly(true);
442           }
443         }
444
445         tokenBlock = tokenBlock.getNextBlock();
446       }
447       processor.formatWithoutRealModifications();
448       processor.performModifications(model);
449     }
450     catch (FormattingModelInconsistencyException e) {
451       LOG.error(e);
452     }
453     finally{
454       enableFormatting();
455     }
456   }
457
458   @Override
459   public int adjustLineIndent(final FormattingModel model,
460                               final CodeStyleSettings settings,
461                               final CommonCodeStyleSettings.IndentOptions indentOptions,
462                               final int offset,
463                               final TextRange affectedRange) throws IncorrectOperationException {
464     disableFormatting();
465     try {
466       validateModel(model);
467       if (model instanceof PsiBasedFormattingModel) {
468         ((PsiBasedFormattingModel)model).canModifyAllWhiteSpaces();
469       }
470       final FormattingDocumentModel documentModel = model.getDocumentModel();
471       final Block block = model.getRootBlock();
472       final FormatProcessor processor = buildProcessorAndWrapBlocks(
473         documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
474       );
475
476       final LeafBlockWrapper blockAfterOffset = processor.getBlockAtOrAfter(offset);
477
478       if (blockAfterOffset != null && blockAfterOffset.contains(offset)) {
479         return offset;
480       }
481
482       WhiteSpace whiteSpace = blockAfterOffset != null ? blockAfterOffset.getWhiteSpace() : processor.getLastWhiteSpace();
483       return adjustLineIndent(offset, documentModel, processor, indentOptions, model, whiteSpace,
484                               blockAfterOffset != null ? blockAfterOffset.getNode() : null);
485     }
486     catch (FormattingModelInconsistencyException e) {
487       LOG.error(e);
488     }
489     finally {
490       enableFormatting();
491     }
492     return offset;
493   }
494
495   /**
496    * Delegates to
497    * {@link #buildProcessorAndWrapBlocks(FormattingDocumentModel, Block, CodeStyleSettings, CommonCodeStyleSettings.IndentOptions, FormatTextRanges, int)}
498    * with '-1' as an interested offset.
499    *
500    * @param docModel
501    * @param rootBlock
502    * @param settings
503    * @param indentOptions
504    * @param affectedRanges
505    * @return
506    */
507   private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
508                                                              Block rootBlock,
509                                                              CodeStyleSettings settings,
510                                                              CommonCodeStyleSettings.IndentOptions indentOptions,
511                                                              @Nullable FormatTextRanges affectedRanges)
512   {
513     return buildProcessorAndWrapBlocks(docModel, rootBlock, settings, indentOptions, affectedRanges, -1);
514   }
515
516   /**
517    * Builds {@link FormatProcessor} instance and asks it to wrap all {@link Block code blocks}
518    * {@link FormattingModel#getRootBlock() derived from the given model}.
519    *
520    * @param docModel            target model
521    * @param rootBlock           root block to process
522    * @param settings            code style settings to use
523    * @param indentOptions       indent options to use
524    * @param affectedRanges      ranges to reformat
525    * @param interestingOffset   interesting offset; <code>'-1'</code> if no particular offset has a special interest
526    * @return                    format processor instance with wrapped {@link Block code blocks}
527    */
528   @SuppressWarnings({"StatementWithEmptyBody"})
529   private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
530                                                              Block rootBlock,
531                                                              CodeStyleSettings settings,
532                                                              CommonCodeStyleSettings.IndentOptions indentOptions,
533                                                              @Nullable FormatTextRanges affectedRanges,
534                                                              int interestingOffset)
535   {
536     FormatOptions options = new FormatOptions(settings, indentOptions, affectedRanges, false, interestingOffset);
537     FormatProcessor processor = new FormatProcessor(
538       docModel, rootBlock, options, FormattingProgressCallback.EMPTY
539     );
540     while (!processor.iteration()) ;
541     return processor;
542   }
543
544   private static int adjustLineIndent(
545     final int offset,
546     final FormattingDocumentModel documentModel,
547     final FormatProcessor processor,
548     final CommonCodeStyleSettings.IndentOptions indentOptions,
549     final FormattingModel model,
550     final WhiteSpace whiteSpace,
551     ASTNode nodeAfter)
552   {
553     boolean wsContainsCaret = whiteSpace.getStartOffset() <= offset && offset < whiteSpace.getEndOffset();
554
555     int lineStartOffset = getLineStartOffset(offset, whiteSpace, documentModel);
556
557     final IndentInfo indent = calcIndent(offset, documentModel, processor, whiteSpace);
558
559     final String newWS = whiteSpace.generateWhiteSpace(indentOptions, lineStartOffset, indent).toString();
560     if (!whiteSpace.equalsToString(newWS)) {
561       try {
562         if (model instanceof FormattingModelEx) {
563           ((FormattingModelEx) model).replaceWhiteSpace(whiteSpace.getTextRange(), nodeAfter, newWS);
564         }
565         else {
566           model.replaceWhiteSpace(whiteSpace.getTextRange(), newWS);
567         }
568       }
569       finally {
570         model.commitChanges();
571       }
572     }
573
574     final int defaultOffset = offset - whiteSpace.getLength() + newWS.length();
575
576     if (wsContainsCaret) {
577       final int ws = whiteSpace.getStartOffset()
578                      + CharArrayUtil.shiftForward(newWS, Math.max(0, lineStartOffset - whiteSpace.getStartOffset()), " \t");
579       return Math.max(defaultOffset, ws);
580     } else {
581       return defaultOffset;
582     }
583   }
584
585   private static boolean hasContentAfterLineBreak(final FormattingDocumentModel documentModel, final int offset, final WhiteSpace whiteSpace) {
586     return documentModel.getLineNumber(offset) == documentModel.getLineNumber(whiteSpace.getEndOffset()) &&
587            documentModel.getTextLength() != offset;
588   }
589
590   @Override
591   public String getLineIndent(final FormattingModel model,
592                               final CodeStyleSettings settings,
593                               final CommonCodeStyleSettings.IndentOptions indentOptions,
594                               final int offset,
595                               final TextRange affectedRange) {
596     final FormattingDocumentModel documentModel = model.getDocumentModel();
597     final Block block = model.getRootBlock();
598     if (block.getTextRange().isEmpty()) return null; // handing empty document case
599     final FormatProcessor processor = buildProcessorAndWrapBlocks(
600       documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
601     );
602     final LeafBlockWrapper blockAfterOffset = processor.getBlockAtOrAfter(offset);
603
604     if (blockAfterOffset != null && !blockAfterOffset.contains(offset)) {
605       final WhiteSpace whiteSpace = blockAfterOffset.getWhiteSpace();
606       final IndentInfo indent = calcIndent(offset, documentModel, processor, whiteSpace);
607
608       return indent.generateNewWhiteSpace(indentOptions);
609     }
610     return null;
611   }
612
613   private static IndentInfo calcIndent(int offset, FormattingDocumentModel documentModel, FormatProcessor processor, WhiteSpace whiteSpace) {
614     processor.setAllWhiteSpacesAreReadOnly();
615     whiteSpace.setLineFeedsAreReadOnly(true);
616     final IndentInfo indent;
617     if (hasContentAfterLineBreak(documentModel, offset, whiteSpace)) {
618       whiteSpace.setReadOnly(false);
619       processor.formatWithoutRealModifications();
620       indent = new IndentInfo(0, whiteSpace.getIndentOffset(), whiteSpace.getSpaces());
621     }
622     else {
623       indent = processor.getIndentAt(offset);
624     }
625     return indent;
626   }
627
628   public static String getText(final FormattingDocumentModel documentModel) {
629     return getCharSequence(documentModel).toString();
630   }
631
632   private static CharSequence getCharSequence(final FormattingDocumentModel documentModel) {
633     return documentModel.getText(new TextRange(0, documentModel.getTextLength()));
634   }
635
636   private static int getLineStartOffset(final int offset,
637                                         final WhiteSpace whiteSpace,
638                                         final FormattingDocumentModel documentModel) {
639     int lineStartOffset = offset;
640
641     CharSequence text = getCharSequence(documentModel);
642     lineStartOffset = CharArrayUtil.shiftBackwardUntil(text, lineStartOffset, " \t\n");
643     if (lineStartOffset > whiteSpace.getStartOffset()) {
644       if (lineStartOffset >= text.length()) lineStartOffset = text.length() - 1;
645       final int wsStart = whiteSpace.getStartOffset();
646       int prevEnd;
647
648       if (text.charAt(lineStartOffset) == '\n'
649           && wsStart <= (prevEnd = documentModel.getLineStartOffset(documentModel.getLineNumber(lineStartOffset - 1))) &&
650           documentModel.getText(new TextRange(prevEnd, lineStartOffset)).toString().trim().length() == 0 // ws consists of space only, it is not true for <![CDATA[
651          ) {
652         lineStartOffset--;
653       }
654       lineStartOffset = CharArrayUtil.shiftBackward(text, lineStartOffset, "\t ");
655       if (lineStartOffset < 0) lineStartOffset = 0;
656       if (lineStartOffset != offset && text.charAt(lineStartOffset) == '\n') {
657         lineStartOffset++;
658       }
659     }
660     return lineStartOffset;
661   }
662
663   @Override
664   public void adjustTextRange(final FormattingModel model,
665                               final CodeStyleSettings settings,
666                               final CommonCodeStyleSettings.IndentOptions indentOptions,
667                               final TextRange affectedRange,
668                               final boolean keepBlankLines,
669                               final boolean keepLineBreaks,
670                               final boolean changeWSBeforeFirstElement,
671                               final boolean changeLineFeedsBeforeFirstElement,
672                               @Nullable final IndentInfoStorage indentInfoStorage) {
673     disableFormatting();
674     try {
675       validateModel(model);
676       final FormatProcessor processor = buildProcessorAndWrapBlocks(
677         model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, new FormatTextRanges(affectedRange, true)
678       );
679       LeafBlockWrapper current = processor.getFirstTokenBlock();
680       while (current != null) {
681         WhiteSpace whiteSpace = current.getWhiteSpace();
682
683         if (!whiteSpace.isReadOnly()) {
684           if (whiteSpace.getStartOffset() > affectedRange.getStartOffset()) {
685             if (whiteSpace.containsLineFeeds() && indentInfoStorage != null) {
686               whiteSpace.setLineFeedsAreReadOnly(true);
687               current.setIndentFromParent(indentInfoStorage.getIndentInfo(current.getStartOffset()));
688             }
689             else {
690               whiteSpace.setReadOnly(true);
691             }
692           }
693           else {
694             if (!changeWSBeforeFirstElement) {
695               whiteSpace.setReadOnly(true);
696             }
697             else {
698               if (!changeLineFeedsBeforeFirstElement) {
699                 whiteSpace.setLineFeedsAreReadOnly(true);
700               }
701               final SpacingImpl spaceProperty = current.getSpaceProperty();
702               if (spaceProperty != null) {
703                 boolean needChange = false;
704                 int newKeepLineBreaks = spaceProperty.getKeepBlankLines();
705                 boolean newKeepLineBreaksFlag = spaceProperty.shouldKeepLineFeeds();
706
707                 if (!keepLineBreaks) {
708                   needChange = true;
709                   newKeepLineBreaksFlag = false;
710                 }
711                 if (!keepBlankLines) {
712                   needChange = true;
713                   newKeepLineBreaks = 0;
714                 }
715
716                 if (needChange) {
717                   assert !(spaceProperty instanceof DependantSpacingImpl);
718                   current.setSpaceProperty(
719                     getSpacingImpl(
720                       spaceProperty.getMinSpaces(), spaceProperty.getMaxSpaces(), spaceProperty.getMinLineFeeds(),
721                       spaceProperty.isReadOnly(),
722                       spaceProperty.isSafe(), newKeepLineBreaksFlag, newKeepLineBreaks, false, spaceProperty.getPrefLineFeeds()
723                     )
724                   );
725                 }
726               }
727             }
728           }
729         }
730         current = current.getNextBlock();
731       }
732       processor.format(model);
733     }
734     catch (FormattingModelInconsistencyException e) {
735       LOG.error(e);
736     }
737     finally {
738       enableFormatting();
739     }
740   }
741
742   @Override
743   public void adjustTextRange(final FormattingModel model,
744                               final CodeStyleSettings settings,
745                               final CommonCodeStyleSettings.IndentOptions indentOptions,
746                               final TextRange affectedRange) {
747     disableFormatting();
748     try {
749       validateModel(model);
750       final FormatProcessor processor = buildProcessorAndWrapBlocks(
751         model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, new FormatTextRanges(affectedRange, true)
752       );
753       LeafBlockWrapper current = processor.getFirstTokenBlock();
754       while (current != null) {
755         WhiteSpace whiteSpace = current.getWhiteSpace();
756
757         if (!whiteSpace.isReadOnly()) {
758           if (whiteSpace.getStartOffset() > affectedRange.getStartOffset()) {
759             whiteSpace.setReadOnly(true);
760           }
761           else {
762             whiteSpace.setReadOnly(false);
763           }
764         }
765         current = current.getNextBlock();
766       }
767       processor.format(model);
768     }
769     catch (FormattingModelInconsistencyException e) {
770       LOG.error(e);
771     }
772     finally {
773       enableFormatting();
774     }
775   }
776
777   @Override
778   public void saveIndents(final FormattingModel model, final TextRange affectedRange,
779                           IndentInfoStorage storage,
780                           final CodeStyleSettings settings,
781                           final CommonCodeStyleSettings.IndentOptions indentOptions) {
782     try {
783       validateModel(model);
784       final Block block = model.getRootBlock();
785
786       final FormatProcessor processor = buildProcessorAndWrapBlocks(
787         model.getDocumentModel(), block, settings, indentOptions, new FormatTextRanges(affectedRange, true)
788       );
789       LeafBlockWrapper current = processor.getFirstTokenBlock();
790       while (current != null) {
791         WhiteSpace whiteSpace = current.getWhiteSpace();
792
793         if (!whiteSpace.isReadOnly() && whiteSpace.containsLineFeeds()) {
794           storage.saveIndentInfo(current.calcIndentFromParent(), current.getStartOffset());
795         }
796         current = current.getNextBlock();
797       }
798     }
799     catch (FormattingModelInconsistencyException e) {
800       LOG.error(e);
801     }
802   }
803
804   @Override
805   public FormattingModel createFormattingModelForPsiFile(final PsiFile file,
806                                                          @NotNull final Block rootBlock,
807                                                          final CodeStyleSettings settings) {
808     return new PsiBasedFormattingModel(file, rootBlock, FormattingDocumentModelImpl.createOn(file));
809   }
810
811   @Override
812   public Indent getSpaceIndent(final int spaces, final boolean relative) {
813     return getIndent(Indent.Type.SPACES, spaces, relative, false);
814   }
815
816   @Override
817   public Indent getIndent(@NotNull Indent.Type type, boolean relativeToDirectParent, boolean enforceIndentToChildren) {
818     return getIndent(type, 0, relativeToDirectParent, enforceIndentToChildren);
819   }
820
821   @Override
822   public Indent getSmartIndent(@NotNull Indent.Type type) {
823     return new ExpandableIndent(type);
824   }
825
826   @Override
827   public Indent getIndent(@NotNull Indent.Type type, int spaces, boolean relativeToDirectParent, boolean enforceIndentToChildren) {
828     return new IndentImpl(type, false, spaces, relativeToDirectParent, enforceIndentToChildren);
829   }
830
831   @Override
832   public Indent getAbsoluteLabelIndent() {
833     return myAbsoluteLabelIndent;
834   }
835
836   @Override
837   @NotNull
838   public Spacing createSafeSpacing(final boolean shouldKeepLineBreaks, final int keepBlankLines) {
839     return getSpacingImpl(0, 0, 0, false, true, shouldKeepLineBreaks, keepBlankLines, false, 0);
840   }
841
842   @Override
843   @NotNull
844   public Spacing createKeepingFirstColumnSpacing(final int minSpace,
845                                                  final int maxSpace,
846                                                  final boolean keepLineBreaks,
847                                                  final int keepBlankLines) {
848     return getSpacingImpl(minSpace, maxSpace, -1, false, false, keepLineBreaks, keepBlankLines, true, 0);
849   }
850
851   @Override
852   @NotNull
853   public Spacing createSpacing(final int minSpaces, final int maxSpaces, final int minLineFeeds, final boolean keepLineBreaks, final int keepBlankLines,
854                                final int prefLineFeeds) {
855     return getSpacingImpl(minSpaces, maxSpaces, minLineFeeds, false, false, keepLineBreaks, keepBlankLines, false, prefLineFeeds);
856   }
857
858   private final Map<SpacingImpl,SpacingImpl> ourSharedProperties = new HashMap<SpacingImpl,SpacingImpl>();
859   private final SpacingImpl ourSharedSpacing = new SpacingImpl(-1,-1,-1,false,false,false,-1,false,0);
860
861   private SpacingImpl getSpacingImpl(final int minSpaces,
862                                      final int maxSpaces,
863                                      final int minLineFeeds,
864                                      final boolean readOnly,
865                                      final boolean safe,
866                                      final boolean keepLineBreaksFlag,
867                                      final int keepLineBreaks,
868                                      final boolean keepFirstColumn,
869                                      int prefLineFeeds)
870   {
871     synchronized(ourSharedSpacing) {
872       ourSharedSpacing.init(minSpaces, maxSpaces, minLineFeeds, readOnly, safe, keepLineBreaksFlag, keepLineBreaks, keepFirstColumn, prefLineFeeds);
873       SpacingImpl spacing = ourSharedProperties.get(ourSharedSpacing);
874
875       if (spacing == null) {
876         spacing = new SpacingImpl(minSpaces, maxSpaces, minLineFeeds, readOnly, safe, keepLineBreaksFlag, keepLineBreaks, keepFirstColumn, prefLineFeeds);
877         ourSharedProperties.put(spacing, spacing);
878       }
879       return spacing;
880     }
881   }
882
883   @Override
884   public Indent getAbsoluteNoneIndent() {
885     return myAbsoluteNoneIndent;
886   }
887
888   @Override
889   public Indent getLabelIndent() {
890     return myLabelIndent;
891   }
892
893   @Override
894   public Indent getContinuationIndent(boolean relative) {
895     return relative ? myContinuationIndentRelativeToDirectParent : myContinuationIndentNotRelativeToDirectParent;
896   }
897
898   //is default
899   @Override
900   public Indent getContinuationWithoutFirstIndent(boolean relative) {
901     return relative ? myContinuationWithoutFirstIndentRelativeToDirectParent : myContinuationWithoutFirstIndentNotRelativeToDirectParent;
902   }
903
904   @Override
905   public boolean isDisabled() {
906     return myIsDisabledCount.get() > 0;
907   }
908
909   private void disableFormatting() {
910     myIsDisabledCount.incrementAndGet();
911   }
912
913   private void enableFormatting() {
914     int old = myIsDisabledCount.getAndDecrement();
915     if (old <= 0) {
916       LOG.error("enableFormatting()/disableFormatting() not paired. DisabledLevel = " + old);
917     }
918   }
919
920   @Nullable
921   public <T> T runWithFormattingDisabled(@NotNull Computable<T> runnable) {
922     disableFormatting();
923     try {
924       return runnable.compute();
925     }
926     finally {
927       enableFormatting();
928     }
929   }
930
931   private abstract static class MyFormattingTask implements SequentialTask {
932     private FormatProcessor myProcessor;
933     private boolean         myDone;
934
935     @Override
936     public void prepare() {
937       myProcessor = buildProcessor();
938     }
939
940     @Override
941     public boolean isDone() {
942       return myDone;
943     }
944
945     @Override
946     public boolean iteration() {
947       return myDone = myProcessor.iteration();
948     }
949
950     @Override
951     public void stop() {
952       myProcessor.stopSequentialProcessing();
953       myDone = true;
954     }
955
956     @NotNull
957     protected abstract FormatProcessor buildProcessor();
958   }
959
960   private static void validateModel(FormattingModel model) throws FormattingModelInconsistencyException {
961     FormattingDocumentModel documentModel = model.getDocumentModel();
962     Document document = documentModel.getDocument();
963     Block rootBlock = model.getRootBlock();
964     if (rootBlock instanceof ASTBlock) {
965       PsiElement rootElement = ((ASTBlock)rootBlock).getNode().getPsi();
966       if (!rootElement.isValid()) {
967         throw new FormattingModelInconsistencyException("Invalid root block PSI element");
968       }
969       PsiFile file = rootElement.getContainingFile();
970       Project project = file.getProject();
971       PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
972       if (documentManager.isUncommited(document)) {
973         throw new FormattingModelInconsistencyException("Uncommitted document");
974       }
975       if (document.getTextLength() != file.getTextLength()) {
976         throw new FormattingModelInconsistencyException(
977           "Document length " + document.getTextLength() +
978           " doesn't match PSI file length " + file.getTextLength() + ", language: " + file.getLanguage()
979         );
980       }
981     }
982   }
983
984   private static class FormattingModelInconsistencyException extends Exception {
985     public FormattingModelInconsistencyException(String message) {
986       super(message);
987     }
988   }
989 }