postponed formatting: fix performance regress
[fedora-idea.git] / platform / lang-impl / src / com / intellij / psi / impl / source / PostprocessReformattingAspect.java
bloba462f520b1e75addca10eafbeb9e1824901532e9
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.psi.impl.source;
19 import com.intellij.lang.ASTNode;
20 import com.intellij.openapi.Disposable;
21 import com.intellij.openapi.application.ApplicationAdapter;
22 import com.intellij.openapi.application.ApplicationListener;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.command.CommandProcessor;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.RangeMarker;
28 import com.intellij.openapi.fileTypes.FileType;
29 import com.intellij.openapi.fileTypes.FileTypeManager;
30 import com.intellij.openapi.progress.ProgressManager;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.*;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.pom.PomManager;
35 import com.intellij.pom.PomModelAspect;
36 import com.intellij.pom.event.PomModelEvent;
37 import com.intellij.pom.tree.TreeAspect;
38 import com.intellij.pom.tree.events.ChangeInfo;
39 import com.intellij.pom.tree.events.TreeChange;
40 import com.intellij.pom.tree.events.TreeChangeEvent;
41 import com.intellij.psi.*;
42 import com.intellij.psi.codeStyle.CodeStyleSettings;
43 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
44 import com.intellij.psi.impl.PsiTreeDebugBuilder;
45 import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
46 import com.intellij.psi.impl.source.codeStyle.CodeFormatterFacade;
47 import com.intellij.psi.impl.source.codeStyle.Helper;
48 import com.intellij.psi.impl.source.codeStyle.HelperFactory;
49 import com.intellij.psi.impl.source.tree.*;
50 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
51 import com.intellij.util.LocalTimeCounter;
52 import com.intellij.util.text.CharArrayUtil;
53 import org.jetbrains.annotations.NonNls;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.TestOnly;
57 import java.util.*;
59 public class PostprocessReformattingAspect implements PomModelAspect, Disposable {
60 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PostprocessReformatingAspect");
61 private final Project myProject;
62 private final PsiManager myPsiManager;
63 private final TreeAspect myTreeAspect;
64 private final Map<FileViewProvider, List<ASTNode>> myReformatElements = new HashMap<FileViewProvider, List<ASTNode>>();
65 private volatile int myDisabledCounter = 0;
66 private final Set<FileViewProvider> myUpdatedProviders = new HashSet<FileViewProvider>();
68 private final ApplicationListener myApplicationListener = new ApplicationAdapter() {
69 public void writeActionStarted(final Object action) {
70 final CommandProcessor processor = CommandProcessor.getInstance();
71 if (processor != null) {
72 final Project project = processor.getCurrentCommandProject();
73 if (project == myProject) {
74 myPostponedCounter++;
79 public void writeActionFinished(final Object action) {
80 final CommandProcessor processor = CommandProcessor.getInstance();
81 if (processor != null) {
82 final Project project = processor.getCurrentCommandProject();
83 if (project == myProject) {
84 decrementPostponedCounter();
90 public PostprocessReformattingAspect(Project project, PsiManager psiManager, TreeAspect treeAspect) {
91 myProject = project;
92 myPsiManager = psiManager;
93 myTreeAspect = treeAspect;
94 PomManager.getModel(psiManager.getProject())
95 .registerAspect(PostprocessReformattingAspect.class, this, Collections.singleton((PomModelAspect)treeAspect));
97 ApplicationManager.getApplication().addApplicationListener(myApplicationListener);
98 Disposer.register(project, this);
101 public void dispose() {
102 ApplicationManager.getApplication().removeApplicationListener(myApplicationListener);
105 public void disablePostprocessFormattingInside(final Runnable runnable) {
106 disablePostprocessFormattingInside(new NullableComputable<Object>() {
107 public Object compute() {
108 runnable.run();
109 return null;
114 public <T> T disablePostprocessFormattingInside(Computable<T> computable) {
115 try {
116 myDisabledCounter++;
117 return computable.compute();
119 finally {
120 myDisabledCounter--;
121 LOG.assertTrue(myDisabledCounter > 0 || !isDisabled());
125 private int myPostponedCounter = 0;
127 public void postponeFormattingInside(final Runnable runnable) {
128 postponeFormattingInside(new NullableComputable<Object>() {
129 public Object compute() {
130 runnable.run();
131 return null;
136 public <T> T postponeFormattingInside(Computable<T> computable) {
137 try {
138 //if(myPostponedCounter == 0) myDisabled = false;
139 myPostponedCounter++;
140 return computable.compute();
142 finally {
143 decrementPostponedCounter();
147 private void decrementPostponedCounter() {
148 if (--myPostponedCounter == 0) {
149 if (!ApplicationManager.getApplication().isWriteAccessAllowed()) {
150 ApplicationManager.getApplication().runWriteAction(new Runnable() {
151 public void run() {
152 doPostponedFormatting();
156 else {
157 doPostponedFormatting();
159 //myDisabled = true;
163 private final Object LOCK = new Object();
165 private void atomic(Runnable r) {
166 synchronized (LOCK) {
167 ProgressManager.getInstance().executeNonCancelableSection(r);
171 public void update(final PomModelEvent event) {
172 atomic(new Runnable() {
173 public void run() {
174 if (isDisabled() || myPostponedCounter == 0 && !ApplicationManager.getApplication().isUnitTestMode()) return;
175 final TreeChangeEvent changeSet = (TreeChangeEvent)event.getChangeSet(myTreeAspect);
176 if (changeSet == null) return;
177 final PsiElement psiElement = changeSet.getRootElement().getPsi();
178 if (psiElement == null) return;
179 PsiFile containingFile = InjectedLanguageUtil.getTopLevelFile(psiElement);
180 final FileViewProvider viewProvider = containingFile.getViewProvider();
182 if (!viewProvider.isEventSystemEnabled()) return;
183 myUpdatedProviders.add(viewProvider);
184 for (final ASTNode node : changeSet.getChangedElements()) {
185 final TreeChange treeChange = changeSet.getChangesByElement(node);
186 for (final ASTNode affectedChild : treeChange.getAffectedChildren()) {
187 final ChangeInfo childChange = treeChange.getChangeByChild(affectedChild);
188 switch (childChange.getChangeType()) {
189 case ChangeInfo.ADD:
190 case ChangeInfo.REPLACE:
191 postponeFormatting(viewProvider, affectedChild);
192 break;
193 case ChangeInfo.CONTENTS_CHANGED:
194 if (!CodeEditUtil.isNodeGenerated(affectedChild)) {
195 ((TreeElement)affectedChild).acceptTree(new RecursiveTreeElementWalkingVisitor() {
196 protected void visitNode(TreeElement element) {
197 if (CodeEditUtil.isNodeGenerated(element)) {
198 postponeFormatting(viewProvider, element);
199 return;
201 super.visitNode(element);
205 break;
213 public void doPostponedFormatting() {
214 atomic(new Runnable() {
215 public void run() {
216 if (isDisabled()) return;
217 try {
218 FileViewProvider[] viewProviders = myUpdatedProviders.toArray(new FileViewProvider[myUpdatedProviders.size()]);
219 for (final FileViewProvider viewProvider : viewProviders) {
220 doPostponedFormatting(viewProvider);
223 finally {
224 LOG.assertTrue(myReformatElements.isEmpty());
230 public void doPostponedFormatting(final FileViewProvider viewProvider) {
231 atomic(new Runnable() {
232 public void run() {
233 if (isDisabled() || !myUpdatedProviders.contains(viewProvider)) return;
235 try {
236 disablePostprocessFormattingInside(new Runnable() {
237 public void run() {
238 doPostponedFormattingInner(viewProvider);
242 finally {
243 myUpdatedProviders.remove(viewProvider);
244 myReformatElements.remove(viewProvider);
250 public boolean isViewProviderLocked(final FileViewProvider fileViewProvider) {
251 return myReformatElements.containsKey(fileViewProvider);
254 public static PostprocessReformattingAspect getInstance(Project project) {
255 return project.getComponent(PostprocessReformattingAspect.class);
258 private void postponeFormatting(final FileViewProvider viewProvider, final ASTNode child) {
259 if (!CodeEditUtil.isNodeGenerated(child) && child.getElementType() != TokenType.WHITE_SPACE) {
260 final int oldIndent = CodeEditUtil.getOldIndentation(child);
261 LOG.assertTrue(oldIndent >= 0,
262 "for not generated items old indentation must be defined: element=" + child + ", text=" + child.getText());
264 List<ASTNode> list = myReformatElements.get(viewProvider);
265 if (list == null) {
266 list = new ArrayList<ASTNode>();
267 myReformatElements.put(viewProvider, list);
269 list.add(child);
272 private void doPostponedFormattingInner(final FileViewProvider key) {
275 final List<ASTNode> astNodes = myReformatElements.remove(key);
276 final Document document = key.getDocument();
277 // Sort ranges by end offsets so that we won't need any offset adjustment after reformat or reindent
278 if (document == null /*|| documentManager.isUncommited(document) TODO */) return;
280 final VirtualFile virtualFile = key.getVirtualFile();
281 if (!virtualFile.isValid()) return;
283 final TreeMap<RangeMarker, PostponedAction> rangesToProcess = new TreeMap<RangeMarker, PostponedAction>(new Comparator<RangeMarker>() {
284 public int compare(final RangeMarker o1, final RangeMarker o2) {
285 if (o1.equals(o2)) return 0;
286 final int diff = o2.getEndOffset() - o1.getEndOffset();
287 if (diff == 0) {
288 if (o1.getStartOffset() == o2.getStartOffset()) return 0;
289 if (o1.getStartOffset() == o1.getEndOffset()) return -1; // empty ranges first
290 if (o2.getStartOffset() == o2.getEndOffset()) return 1; // empty ranges first
291 return o1.getStartOffset() - o2.getStartOffset();
293 return diff;
297 // process all roots in viewProvider to find marked for reformat before elements and create appropriate ragge markers
298 handleReformatMarkers(key, rangesToProcess);
300 // then we create ranges by changed nodes. One per node. There ranges can instersect. Ranges are sorted by end offset.
301 if (astNodes != null) createActionsMap(astNodes, key, rangesToProcess);
303 if ("true".equals(System.getProperty("check.psi.is.valid")) && ApplicationManager.getApplication().isUnitTestMode()) {
304 checkPsiIsCorrect(key);
307 while (!rangesToProcess.isEmpty()) {
308 // now we have to normalize actions so that they not intersect and ordered in most appropriate way
309 // (free reformating -> reindent -> formating under reindent)
310 final List<Pair<RangeMarker, ? extends PostponedAction>> normalizedActions =
311 normalizeAndReorderPostponedActions(rangesToProcess, document);
313 // only in following loop real changes in document are made
314 for (final Pair<RangeMarker, ? extends PostponedAction> normalizedAction : normalizedActions) {
315 CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myPsiManager.getProject());
316 boolean old = settings.ENABLE_JAVADOC_FORMATTING;
317 settings.ENABLE_JAVADOC_FORMATTING = false;
318 try {
319 normalizedAction.getSecond().processRange(normalizedAction.getFirst(), key);
321 finally {
322 settings.ENABLE_JAVADOC_FORMATTING = old;
328 private void checkPsiIsCorrect(final FileViewProvider key) {
329 PsiFile actualPsi = key.getPsi(key.getBaseLanguage());
331 PsiTreeDebugBuilder treeDebugBuilder = new PsiTreeDebugBuilder().setShowErrorElements(false).setShowWhiteSpaces(false);
333 String actualPsiTree = treeDebugBuilder.psiToString(actualPsi);
335 String fileName = key.getVirtualFile().getName();
336 PsiFile psi = PsiFileFactory.getInstance(myProject)
337 .createFileFromText(fileName, FileTypeManager.getInstance().getFileTypeByFileName(fileName), actualPsi.getNode().getText(),
338 LocalTimeCounter.currentTime(), false);
340 if (actualPsi.getClass().equals(psi.getClass())) {
341 String expectedPsi = treeDebugBuilder.psiToString(psi);
343 if (!expectedPsi.equals(actualPsiTree)) {
344 myReformatElements.clear();
345 assert expectedPsi.equals(actualPsiTree) : "Refactored psi should be the same as result of parsing";
352 private List<Pair<RangeMarker, ? extends PostponedAction>> normalizeAndReorderPostponedActions(final TreeMap<RangeMarker, PostponedAction> rangesToProcess,
353 Document document) {
354 final List<Pair<RangeMarker, ReformatAction>> freeFormatingActions = new ArrayList<Pair<RangeMarker, ReformatAction>>();
355 final List<Pair<RangeMarker, ReindentAction>> indentActions = new ArrayList<Pair<RangeMarker, ReindentAction>>();
357 RangeMarker accumulatedRange = null;
358 PostponedAction accumulatedRangeAction = null;
359 Iterator<Map.Entry<RangeMarker, PostponedAction>> iterator = rangesToProcess.entrySet().iterator();
360 while (iterator.hasNext()) {
361 final Map.Entry<RangeMarker, PostponedAction> entry = iterator.next();
362 final RangeMarker textRange = entry.getKey();
363 final PostponedAction action = entry.getValue();
364 if (accumulatedRange == null) {
365 accumulatedRange = textRange;
366 accumulatedRangeAction = action;
367 iterator.remove();
369 else if (accumulatedRange.getStartOffset() > textRange.getEndOffset() ||
370 (accumulatedRange.getStartOffset() == textRange.getEndOffset() &&
371 !canStickActionsTogether(accumulatedRangeAction, accumulatedRange, action, textRange))) {
372 // action can be pushed
373 if (accumulatedRangeAction instanceof ReindentAction) {
374 indentActions.add(new Pair<RangeMarker, ReindentAction>(accumulatedRange, (ReindentAction)accumulatedRangeAction));
376 else {
377 freeFormatingActions.add(new Pair<RangeMarker, ReformatAction>(accumulatedRange, (ReformatAction)accumulatedRangeAction));
380 accumulatedRange = textRange;
381 accumulatedRangeAction = action;
382 iterator.remove();
384 else if (accumulatedRangeAction instanceof ReformatAction && action instanceof ReindentAction) {
385 // split accumulated reformat range into two
386 if (accumulatedRange.getStartOffset() < textRange.getStartOffset()) {
387 final RangeMarker endOfRange = document.createRangeMarker(accumulatedRange.getStartOffset(), textRange.getStartOffset());
388 // add heading reformat part
389 rangesToProcess.put(endOfRange, accumulatedRangeAction);
390 // and manage heading whitespace because formatter does not edit it in previous action
391 iterator = rangesToProcess.entrySet().iterator();
392 //noinspection StatementWithEmptyBody
393 while (iterator.next().getKey() != textRange) ;
395 final RangeMarker rangeToProcess = document.createRangeMarker(textRange.getEndOffset(), accumulatedRange.getEndOffset());
396 freeFormatingActions.add(new Pair<RangeMarker, ReformatAction>(rangeToProcess, new ReformatWithHeadingWhitespaceAction()));
397 accumulatedRange = textRange;
398 accumulatedRangeAction = action;
399 iterator.remove();
401 else {
402 if (!(accumulatedRangeAction instanceof ReindentAction)) {
403 iterator.remove();
404 if (accumulatedRangeAction instanceof ReformatAction &&
405 action instanceof ReformatWithHeadingWhitespaceAction &&
406 accumulatedRange.getStartOffset() == textRange.getStartOffset() ||
407 accumulatedRangeAction instanceof ReformatWithHeadingWhitespaceAction &&
408 action instanceof ReformatAction &&
409 accumulatedRange.getStartOffset() < textRange.getStartOffset()) {
410 accumulatedRangeAction = action;
412 accumulatedRange = document.createRangeMarker(Math.min(accumulatedRange.getStartOffset(), textRange.getStartOffset()),
413 Math.max(accumulatedRange.getEndOffset(), textRange.getEndOffset()));
415 else if (action instanceof ReindentAction) {
416 iterator.remove();
417 } // TODO[ik]: need to be fixed to correctly process indent inside indent
420 if (accumulatedRange != null) {
421 if (accumulatedRangeAction instanceof ReindentAction) {
422 indentActions.add(new Pair<RangeMarker, ReindentAction>(accumulatedRange, (ReindentAction)accumulatedRangeAction));
424 else {
425 freeFormatingActions.add(new Pair<RangeMarker, ReformatAction>(accumulatedRange, (ReformatAction)accumulatedRangeAction));
429 final List<Pair<RangeMarker, ? extends PostponedAction>> result =
430 new ArrayList<Pair<RangeMarker, ? extends PostponedAction>>(rangesToProcess.size());
431 Collections.reverse(freeFormatingActions);
432 Collections.reverse(indentActions);
433 result.addAll(freeFormatingActions);
434 result.addAll(indentActions);
435 return result;
438 private boolean canStickActionsTogether(final PostponedAction currentAction,
439 final RangeMarker currentRange,
440 final PostponedAction nextAction,
441 final RangeMarker nextRange) {
442 // empty reformat markers can't sticked together with any action
443 if (nextAction instanceof ReformatWithHeadingWhitespaceAction && nextRange.getStartOffset() == nextRange.getEndOffset()) return false;
444 if (currentAction instanceof ReformatWithHeadingWhitespaceAction && currentRange.getStartOffset() == currentRange.getEndOffset()) {
445 return false;
447 // reindent actions can't be sticked at all
448 return !(currentAction instanceof ReindentAction);
451 private void createActionsMap(final List<ASTNode> astNodes,
452 final FileViewProvider provider,
453 final TreeMap<RangeMarker, PostponedAction> rangesToProcess) {
454 final Set<ASTNode> nodesToProcess = new HashSet<ASTNode>(astNodes);
455 final Document document = provider.getDocument();
456 for (final ASTNode node : astNodes) {
457 nodesToProcess.remove(node);
458 final FileElement fileElement = TreeUtil.getFileElement((TreeElement)node);
459 if (fileElement == null || ((PsiFile)fileElement.getPsi()).getViewProvider() != provider) continue;
460 final boolean isGenerated = CodeEditUtil.isNodeGenerated(node);
462 ((TreeElement)node).acceptTree(new RecursiveTreeElementVisitor() {
463 boolean inGeneratedContext = !isGenerated;
465 protected boolean visitNode(TreeElement element) {
466 if (nodesToProcess.contains(element)) return false;
467 final boolean currentNodeGenerated = CodeEditUtil.isNodeGenerated(element);
468 CodeEditUtil.setNodeGenerated(element, false);
469 if (currentNodeGenerated && !inGeneratedContext) {
470 rangesToProcess.put(document.createRangeMarker(element.getTextRange()), new ReformatAction());
471 inGeneratedContext = true;
473 if (!currentNodeGenerated && inGeneratedContext) {
474 if (element.getElementType() == TokenType.WHITE_SPACE) return false;
475 final int oldIndent = CodeEditUtil.getOldIndentation(element);
476 LOG.assertTrue(oldIndent >= 0, "for not generated items old indentation must be defined");
477 rangesToProcess.put(document.createRangeMarker(element.getTextRange()), new ReindentAction(oldIndent));
478 inGeneratedContext = false;
480 return true;
483 @Override
484 public void visitComposite(CompositeElement composite) {
485 boolean oldGeneratedContext = inGeneratedContext;
486 super.visitComposite(composite);
487 inGeneratedContext = oldGeneratedContext;
490 @Override
491 public void visitLeaf(LeafElement leaf) {
492 boolean oldGeneratedContext = inGeneratedContext;
493 super.visitLeaf(leaf);
494 inGeneratedContext = oldGeneratedContext;
500 private void handleReformatMarkers(final FileViewProvider key, final TreeMap<RangeMarker, PostponedAction> rangesToProcess) {
501 final Document document = key.getDocument();
502 for (final FileElement fileElement : ((SingleRootFileViewProvider)key).getKnownTreeRoots()) {
503 fileElement.acceptTree(new RecursiveTreeElementWalkingVisitor() {
504 protected void visitNode(TreeElement element) {
505 if (CodeEditUtil.isMarkedToReformatBefore(element)) {
506 CodeEditUtil.markToReformatBefore(element, false);
507 rangesToProcess.put(document.createRangeMarker(element.getStartOffset(), element.getStartOffset()),
508 new ReformatWithHeadingWhitespaceAction());
510 super.visitNode(element);
516 private static void adjustIndentationInRange(final PsiFile file,
517 final Document document,
518 final TextRange[] indents,
519 final int indentAdjustment) {
520 final Helper formatHelper = HelperFactory.createHelper(file.getFileType(), file.getProject());
521 final CharSequence charsSequence = document.getCharsSequence();
522 for (final TextRange indent : indents) {
523 final String oldIndentStr = charsSequence.subSequence(indent.getStartOffset() + 1, indent.getEndOffset()).toString();
524 final int oldIndent = formatHelper.getIndent(oldIndentStr, true);
525 final String newIndentStr = formatHelper.fillIndent(Math.max(oldIndent + indentAdjustment, 0));
526 document.replaceString(indent.getStartOffset() + 1, indent.getEndOffset(), newIndentStr);
530 private static int getNewIndent(final PsiFile psiFile, final int firstWhitespace) {
531 final Helper formatHelper = HelperFactory.createHelper(psiFile.getFileType(), psiFile.getProject());
532 final Document document = psiFile.getViewProvider().getDocument();
533 final int startOffset = document.getLineStartOffset(document.getLineNumber(firstWhitespace));
534 int endOffset = startOffset;
535 final CharSequence charsSequence = document.getCharsSequence();
536 while (Character.isWhitespace(charsSequence.charAt(endOffset++))) ;
537 final String newIndentStr = charsSequence.subSequence(startOffset, endOffset - 1).toString();
538 return formatHelper.getIndent(newIndentStr, true);
541 public boolean isDisabled() {
542 return myDisabledCounter > 0;
545 private CodeFormatterFacade getFormatterFacade(final FileViewProvider viewProvider) {
546 final CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(myPsiManager.getProject());
547 final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myPsiManager.getProject());
548 final Document document = viewProvider.getDocument();
549 final FileType fileType = viewProvider.getVirtualFile().getFileType();
550 final Helper helper = HelperFactory.createHelper(fileType, myPsiManager.getProject());
551 final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(styleSettings, helper);
553 documentManager.commitDocument(document);
554 return codeFormatter;
557 private interface PostponedAction {
558 void processRange(RangeMarker marker, final FileViewProvider viewProvider);
561 private class ReformatAction implements PostponedAction {
562 public void processRange(RangeMarker marker, final FileViewProvider viewProvider) {
563 final CodeFormatterFacade codeFormatter = getFormatterFacade(viewProvider);
564 codeFormatter.processTextWithoutHeadWhitespace(viewProvider.getPsi(viewProvider.getBaseLanguage()), marker.getStartOffset(),
565 marker.getEndOffset());
569 private class ReformatWithHeadingWhitespaceAction extends ReformatAction {
570 public void processRange(RangeMarker marker, final FileViewProvider viewProvider) {
571 final CodeFormatterFacade codeFormatter = getFormatterFacade(viewProvider);
572 codeFormatter.processText(viewProvider.getPsi(viewProvider.getBaseLanguage()), marker.getStartOffset(),
573 marker.getStartOffset() == marker.getEndOffset() ? marker.getEndOffset() + 1 : marker.getEndOffset());
579 private static class ReindentAction implements PostponedAction {
580 private final int myOldIndent;
582 public ReindentAction(final int oldIndent) {
583 myOldIndent = oldIndent;
586 public void processRange(RangeMarker marker, final FileViewProvider viewProvider) {
587 final Document document = viewProvider.getDocument();
588 final PsiFile psiFile = viewProvider.getPsi(viewProvider.getBaseLanguage());
589 final CharSequence charsSequence = document.getCharsSequence().subSequence(marker.getStartOffset(), marker.getEndOffset());
590 final int oldIndent = getOldIndent();
591 final TextRange[] whitespaces = CharArrayUtil.getIndents(charsSequence, marker.getStartOffset());
592 final int indentAdjustment = getNewIndent(psiFile, marker.getStartOffset()) - oldIndent;
593 if (indentAdjustment != 0) adjustIndentationInRange(psiFile, document, whitespaces, indentAdjustment);
596 private int getOldIndent() {
597 return myOldIndent;
602 public void projectOpened() {
605 public void projectClosed() {
608 @NotNull
609 @NonNls
610 public String getComponentName() {
611 return "Postponed reformatting model";
614 public void initComponent() {
617 public void disposeComponent() {
620 @TestOnly
621 public void clear() {
622 myReformatElements.clear();