2 * Copyright 2000-2014 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.codeInsight.actions;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.lang.LanguageFormatting;
21 import com.intellij.notification.Notification;
22 import com.intellij.notification.NotificationType;
23 import com.intellij.openapi.application.ApplicationBundle;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ModalityState;
26 import com.intellij.openapi.command.CommandProcessor;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.editor.Document;
29 import com.intellij.openapi.editor.SelectionModel;
30 import com.intellij.openapi.fileEditor.FileDocumentManager;
31 import com.intellij.openapi.module.Module;
32 import com.intellij.openapi.progress.ProcessCanceledException;
33 import com.intellij.openapi.progress.ProgressIndicator;
34 import com.intellij.openapi.progress.ProgressManager;
35 import com.intellij.openapi.progress.util.ProgressWindow;
36 import com.intellij.openapi.project.IndexNotReadyException;
37 import com.intellij.openapi.project.Project;
38 import com.intellij.openapi.project.ProjectCoreUtil;
39 import com.intellij.openapi.project.ProjectUtil;
40 import com.intellij.openapi.roots.GeneratedSourcesFilter;
41 import com.intellij.openapi.ui.Messages;
42 import com.intellij.openapi.ui.ex.MessagesEx;
43 import com.intellij.openapi.util.Ref;
44 import com.intellij.openapi.util.TextRange;
45 import com.intellij.openapi.vfs.VirtualFile;
46 import com.intellij.psi.PsiBundle;
47 import com.intellij.psi.PsiDirectory;
48 import com.intellij.psi.PsiDocumentManager;
49 import com.intellij.psi.PsiFile;
50 import com.intellij.util.IncorrectOperationException;
51 import com.intellij.util.SequentialModalProgressTask;
52 import com.intellij.util.SequentialTask;
53 import com.intellij.util.SmartList;
54 import com.intellij.util.containers.ContainerUtil;
55 import com.intellij.util.diff.FilesTooBigForDiffException;
56 import org.jetbrains.annotations.NotNull;
57 import org.jetbrains.annotations.Nullable;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.List;
62 import java.util.concurrent.Callable;
63 import java.util.concurrent.ExecutionException;
64 import java.util.concurrent.FutureTask;
66 public abstract class AbstractLayoutCodeProcessor {
67 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor");
69 protected final Project myProject;
70 private final Module myModule;
72 private PsiDirectory myDirectory;
73 private PsiFile myFile;
74 private List<PsiFile> myFiles;
75 private boolean myIncludeSubdirs;
77 private final String myProgressText;
78 private final String myCommandName;
79 private Runnable myPostRunnable;
80 private boolean myProcessChangedTextOnly;
82 protected AbstractLayoutCodeProcessor myPreviousCodeProcessor;
83 private List<FileFilter> myFilters = ContainerUtil.newArrayList();
85 private LayoutCodeInfoCollector myInfoCollector;
87 protected AbstractLayoutCodeProcessor(Project project, String commandName, String progressText, boolean processChangedTextOnly) {
88 this(project, (Module)null, commandName, progressText, processChangedTextOnly);
91 protected AbstractLayoutCodeProcessor(@NotNull AbstractLayoutCodeProcessor previous,
92 @NotNull String commandName,
93 @NotNull String progressText)
95 myProject = previous.myProject;
96 myModule = previous.myModule;
97 myDirectory = previous.myDirectory;
98 myFile = previous.myFile;
99 myFiles = previous.myFiles;
100 myIncludeSubdirs = previous.myIncludeSubdirs;
101 myProcessChangedTextOnly = previous.myProcessChangedTextOnly;
103 myPostRunnable = null;
104 myProgressText = progressText;
105 myCommandName = commandName;
106 myPreviousCodeProcessor = previous;
107 myFilters = previous.myFilters;
108 myInfoCollector = previous.myInfoCollector;
111 protected AbstractLayoutCodeProcessor(Project project,
112 @Nullable Module module,
115 boolean processChangedTextOnly)
120 myIncludeSubdirs = true;
121 myCommandName = commandName;
122 myProgressText = progressText;
123 myPostRunnable = null;
124 myProcessChangedTextOnly = processChangedTextOnly;
127 protected AbstractLayoutCodeProcessor(Project project,
128 PsiDirectory directory,
129 boolean includeSubdirs,
132 boolean processChangedTextOnly)
136 myDirectory = directory;
137 myIncludeSubdirs = includeSubdirs;
138 myProgressText = progressText;
139 myCommandName = commandName;
140 myPostRunnable = null;
141 myProcessChangedTextOnly = processChangedTextOnly;
144 protected AbstractLayoutCodeProcessor(Project project,
148 boolean processChangedTextOnly)
153 myProgressText = progressText;
154 myCommandName = commandName;
155 myPostRunnable = null;
156 myProcessChangedTextOnly = processChangedTextOnly;
159 protected AbstractLayoutCodeProcessor(Project project,
163 @Nullable Runnable postRunnable,
164 boolean processChangedTextOnly)
168 myFiles = filterFilesTo(files, new ArrayList<PsiFile>());
169 myProgressText = progressText;
170 myCommandName = commandName;
171 myPostRunnable = postRunnable;
172 myProcessChangedTextOnly = processChangedTextOnly;
175 private static List<PsiFile> filterFilesTo(PsiFile[] files, List<PsiFile> list) {
176 for (PsiFile file : files) {
177 if (canBeFormatted(file)) {
184 public void setPostRunnable(Runnable postRunnable) {
185 myPostRunnable = postRunnable;
189 private FutureTask<Boolean> getPreviousProcessorTask(@NotNull PsiFile file, boolean processChangedTextOnly) {
190 return myPreviousCodeProcessor != null ? myPreviousCodeProcessor.preprocessFile(file, processChangedTextOnly)
194 public void setCollectInfo(boolean isCollectInfo) {
195 myInfoCollector = isCollectInfo ? new LayoutCodeInfoCollector() : null;
197 AbstractLayoutCodeProcessor current = this;
198 while (current.myPreviousCodeProcessor != null) {
199 current = current.myPreviousCodeProcessor;
200 current.myInfoCollector = myInfoCollector;
204 public void addFileFilter(@NotNull FileFilter filter) {
205 myFilters.add(filter);
208 protected void setProcessChangedTextOnly(boolean value) {
209 myProcessChangedTextOnly = value;
212 * Ensures that given file is ready to reformatting and prepares it if necessary.
214 * @param file file to process
215 * @param processChangedTextOnly flag that defines is only the changed text (in terms of VCS change) should be processed
216 * @return task that triggers formatting of the given file. Returns value of that task indicates whether formatting
217 * is finished correctly or not (exception occurred, user cancelled formatting etc)
218 * @throws IncorrectOperationException if unexpected exception occurred during formatting
221 protected abstract FutureTask<Boolean> prepareTask(@NotNull PsiFile file, boolean processChangedTextOnly) throws IncorrectOperationException;
223 public FutureTask<Boolean> preprocessFile(@NotNull PsiFile file, boolean processChangedTextOnly) throws IncorrectOperationException {
224 final FutureTask<Boolean> previousTask = getPreviousProcessorTask(file, processChangedTextOnly);
225 final FutureTask<Boolean> currentTask = prepareTask(file, processChangedTextOnly);
227 return new FutureTask<Boolean>(new Callable<Boolean>() {
229 public Boolean call() throws Exception {
230 if (previousTask != null) {
232 if (!previousTask.get() || previousTask.isCancelled()) return false;
235 ApplicationManager.getApplication().runWriteAction(new Runnable() {
242 return currentTask.get() && !currentTask.isCancelled();
248 if (myFile != null) {
249 runProcessFile(myFile);
253 FileTreeIterator iterator;
254 if (myFiles != null) {
255 iterator = new FileTreeIterator(myFiles);
258 iterator = myProcessChangedTextOnly ? buildChangedFilesIterator()
259 : buildFileTreeIterator();
261 runProcessFiles(iterator);
264 private FileTreeIterator buildFileTreeIterator() {
265 if (myDirectory != null) {
266 return new FileTreeIterator(myDirectory);
268 else if (myFiles != null) {
269 return new FileTreeIterator(myFiles);
271 else if (myModule != null) {
272 return new FileTreeIterator(myModule);
274 else if (myProject != null) {
275 return new FileTreeIterator(myProject);
278 return new FileTreeIterator(Collections.<PsiFile>emptyList());
282 private FileTreeIterator buildChangedFilesIterator() {
283 List<PsiFile> files = getChangedFilesFromContext();
284 return new FileTreeIterator(files);
288 private List<PsiFile> getChangedFilesFromContext() {
289 List<PsiDirectory> dirs = getAllSearchableDirsFromContext();
290 return FormatChangedTextUtil.getChangedFilesFromDirs(myProject, dirs);
293 private List<PsiDirectory> getAllSearchableDirsFromContext() {
294 List<PsiDirectory> dirs = ContainerUtil.newArrayList();
295 if (myDirectory != null) {
296 dirs.add(myDirectory);
298 else if (myModule != null) {
299 List<PsiDirectory> allModuleDirs = FileTreeIterator.collectModuleDirectories(myModule);
300 dirs.addAll(allModuleDirs);
302 else if (myProject != null) {
303 List<PsiDirectory> allProjectDirs = FileTreeIterator.collectProjectDirectories(myProject);
304 dirs.addAll(allProjectDirs);
310 private void runProcessFile(@NotNull final PsiFile file) {
311 Document document = PsiDocumentManager.getInstance(myProject).getDocument(file);
313 if (document == null) {
317 if (!FileDocumentManager.getInstance().requestWriting(document, myProject)) {
318 Messages.showMessageDialog(myProject, PsiBundle.message("cannot.modify.a.read.only.file", file.getName()),
319 CodeInsightBundle.message("error.dialog.readonly.file.title"),
320 Messages.getErrorIcon()
325 final Ref<FutureTask<Boolean>> writeActionRunnable = new Ref<FutureTask<Boolean>>();
326 Runnable readAction = new Runnable() {
329 if (!checkFileWritable(file)) return;
331 FutureTask<Boolean> writeTask = preprocessFile(file, myProcessChangedTextOnly);
332 writeActionRunnable.set(writeTask);
334 catch(IncorrectOperationException e){
339 Runnable writeAction = new Runnable() {
342 if (writeActionRunnable.isNull()) return;
343 FutureTask<Boolean> task = writeActionRunnable.get();
348 catch (Exception e) {
353 runLayoutCodeProcess(readAction, writeAction, false );
356 private boolean checkFileWritable(final PsiFile file){
357 if (!file.isWritable()){
358 MessagesEx.fileIsReadOnly(myProject, file.getVirtualFile())
359 .setTitle(CodeInsightBundle.message("error.dialog.readonly.file.title"))
369 private Runnable createIterativeFileProcessor(@NotNull final FileTreeIterator fileIterator) {
370 return new Runnable() {
373 SequentialModalProgressTask progressTask = new SequentialModalProgressTask(myProject, myCommandName);
374 progressTask.setMinIterationTime(100);
375 ReformatFilesTask reformatFilesTask = new ReformatFilesTask(fileIterator);
376 reformatFilesTask.setCompositeTask(progressTask);
377 progressTask.setTask(reformatFilesTask);
378 ProgressManager.getInstance().run(progressTask);
383 private void runProcessFiles(@NotNull final FileTreeIterator fileIterator) {
384 final Runnable[] resultRunnable = new Runnable[1];
386 Runnable readAction = new Runnable() {
389 resultRunnable[0] = createIterativeFileProcessor(fileIterator);
393 Runnable writeAction = new Runnable() {
396 if (resultRunnable[0] != null) {
397 resultRunnable[0].run();
402 runLayoutCodeProcess(readAction, writeAction, true);
405 private static boolean canBeFormatted(PsiFile file) {
406 if (LanguageFormatting.INSTANCE.forContext(file) == null) {
409 VirtualFile virtualFile = file.getVirtualFile();
410 if (virtualFile == null) return true;
412 if (ProjectCoreUtil.isProjectOrWorkspaceFile(virtualFile)) return false;
414 for (GeneratedSourcesFilter filter : GeneratedSourcesFilter.EP_NAME.getExtensions()) {
415 if (filter.isGeneratedSource(virtualFile, file.getProject())) {
422 private void runLayoutCodeProcess(final Runnable readAction, final Runnable writeAction, final boolean globalAction) {
423 final ProgressWindow progressWindow = new ProgressWindow(true, myProject);
424 progressWindow.setTitle(myCommandName);
425 progressWindow.setText(myProgressText);
427 final ModalityState modalityState = ModalityState.current();
429 final Runnable process = new Runnable() {
432 ApplicationManager.getApplication().runReadAction(readAction);
436 Runnable runnable = new Runnable() {
440 ProgressManager.getInstance().runProcess(process, progressWindow);
442 catch(ProcessCanceledException e) {
445 catch(IndexNotReadyException e) {
449 final Runnable writeRunnable = new Runnable() {
452 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
455 if (globalAction) CommandProcessor.getInstance().markCurrentCommandAsGlobal(myProject);
459 if (myPostRunnable != null) {
460 ApplicationManager.getApplication().invokeLater(myPostRunnable);
463 catch (IndexNotReadyException ignored) {
466 }, myCommandName, null);
470 if (ApplicationManager.getApplication().isUnitTestMode()) {
474 ApplicationManager.getApplication().invokeLater(writeRunnable, modalityState, myProject.getDisposed());
479 if (ApplicationManager.getApplication().isUnitTestMode()) {
483 ApplicationManager.getApplication().executeOnPooledThread(runnable);
487 public void runWithoutProgress() throws IncorrectOperationException {
488 final Runnable runnable = preprocessFile(myFile, myProcessChangedTextOnly);
492 private class ReformatFilesTask implements SequentialTask {
493 private SequentialModalProgressTask myCompositeTask;
495 private final FileTreeIterator myFileTreeIterator;
496 private final FileTreeIterator myCountingIterator;
498 private int myTotalFiles = 0;
499 private int myFilesProcessed = 0;
500 private boolean myStopFormatting;
501 private boolean myFilesCountingFinished;
503 ReformatFilesTask(@NotNull FileTreeIterator fileIterator) {
504 myFileTreeIterator = fileIterator;
505 myCountingIterator = new FileTreeIterator(fileIterator);
509 public void prepare() {
513 public boolean isDone() {
514 return myStopFormatting || !myFileTreeIterator.hasNext();
517 private void countingIteration() {
518 if (myCountingIterator.hasNext()) {
519 myCountingIterator.next();
523 myFilesCountingFinished = true;
528 public boolean iteration() {
529 if (myStopFormatting) {
533 if (!myFilesCountingFinished) {
534 updateIndicatorText(ApplicationBundle.message("bulk.reformat.prepare.progress.text"), "");
539 updateIndicatorFraction(myFilesProcessed);
541 if (myFileTreeIterator.hasNext()) {
542 final PsiFile file = myFileTreeIterator.next();
544 if (file.isWritable() && canBeFormatted(file) && acceptedByFilters(file)) {
545 updateIndicatorText(ApplicationBundle.message("bulk.reformat.process.progress.text"), getPresentablePath(file));
546 ApplicationManager.getApplication().runWriteAction(new Runnable() {
549 performFileProcessing(file);
558 private void performFileProcessing(@NotNull PsiFile file) {
559 FutureTask<Boolean> task = preprocessFile(file, myProcessChangedTextOnly);
562 if (!task.get() || task.isCancelled()) {
563 myStopFormatting = true;
566 catch (InterruptedException e) {
567 LOG.error("Got unexpected exception during formatting", e);
569 catch (ExecutionException e) {
570 LOG.error("Got unexpected exception during formatting", e);
574 private void updateIndicatorText(@NotNull String upperLabel, @NotNull String downLabel) {
575 ProgressIndicator indicator = myCompositeTask.getIndicator();
576 if (indicator != null) {
577 indicator.setText(upperLabel);
578 indicator.setText2(downLabel);
582 private String getPresentablePath(@NotNull PsiFile file) {
583 VirtualFile vFile = file.getVirtualFile();
584 return vFile != null ? ProjectUtil.calcRelativeToProjectPath(vFile, myProject) : file.getName();
587 private void updateIndicatorFraction(int processed) {
588 ProgressIndicator indicator = myCompositeTask.getIndicator();
589 if (indicator != null) {
590 indicator.setFraction((double)processed / myTotalFiles);
596 myStopFormatting = true;
599 public void setCompositeTask(@Nullable SequentialModalProgressTask compositeTask) {
600 myCompositeTask = compositeTask;
604 private boolean acceptedByFilters(@NotNull PsiFile file) {
605 VirtualFile vFile = file.getVirtualFile();
610 for (FileFilter filter : myFilters) {
611 if (!filter.accept(file.getVirtualFile())) {
619 protected static List<TextRange> getSelectedRanges(@NotNull SelectionModel selectionModel) {
620 final List<TextRange> ranges = new SmartList<TextRange>();
621 if (selectionModel.hasSelection()) {
622 TextRange range = TextRange.create(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
625 else if (selectionModel.hasBlockSelection()) {
626 int[] starts = selectionModel.getBlockSelectionStarts();
627 int[] ends = selectionModel.getBlockSelectionEnds();
628 for (int i = 0; i < starts.length; i++) {
629 ranges.add(TextRange.create(starts[i], ends[i]));
636 protected void handleFileTooBigException(Logger logger, FilesTooBigForDiffException e, @NotNull PsiFile file) {
637 logger.info("Error while calculating changed ranges for: " + file.getVirtualFile(), e);
638 if (!ApplicationManager.getApplication().isUnitTestMode()) {
639 Notification notification = new Notification(ApplicationBundle.message("reformat.changed.text.file.too.big.notification.groupId"),
640 ApplicationBundle.message("reformat.changed.text.file.too.big.notification.title"),
641 ApplicationBundle.message("reformat.changed.text.file.too.big.notification.text", file.getName()),
642 NotificationType.INFORMATION);
643 notification.notify(file.getProject());
648 public LayoutCodeInfoCollector getInfoCollector() {
649 return myInfoCollector;