IDEA-81134 'Untracked Files Preventing Checkout': add action to delete files
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / AbstractVcsHelperImpl.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 package com.intellij.openapi.vcs.impl;
17
18 import com.intellij.ide.actions.CloseTabToolbarAction;
19 import com.intellij.ide.errorTreeView.ErrorTreeElementKind;
20 import com.intellij.ide.errorTreeView.HotfixData;
21 import com.intellij.ide.errorTreeView.SimpleErrorData;
22 import com.intellij.openapi.actionSystem.AnActionEvent;
23 import com.intellij.openapi.actionSystem.DefaultActionGroup;
24 import com.intellij.openapi.application.Application;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.command.CommandProcessor;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.diff.*;
30 import com.intellij.openapi.editor.Editor;
31 import com.intellij.openapi.fileEditor.FileDocumentManager;
32 import com.intellij.openapi.fileEditor.FileEditorManager;
33 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
34 import com.intellij.openapi.fileTypes.FileType;
35 import com.intellij.openapi.fileTypes.FileTypeManager;
36 import com.intellij.openapi.progress.ProgressIndicator;
37 import com.intellij.openapi.progress.ProgressManager;
38 import com.intellij.openapi.progress.Task;
39 import com.intellij.openapi.project.Project;
40 import com.intellij.openapi.ui.DialogWrapper;
41 import com.intellij.openapi.ui.Messages;
42 import com.intellij.openapi.util.Comparing;
43 import com.intellij.openapi.util.Disposer;
44 import com.intellij.openapi.util.Getter;
45 import com.intellij.openapi.util.text.StringUtil;
46 import com.intellij.openapi.vcs.*;
47 import com.intellij.openapi.vcs.actions.AnnotateToggleAction;
48 import com.intellij.openapi.vcs.annotate.AnnotationProvider;
49 import com.intellij.openapi.vcs.annotate.FileAnnotation;
50 import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
51 import com.intellij.openapi.vcs.changes.Change;
52 import com.intellij.openapi.vcs.changes.CommitExecutor;
53 import com.intellij.openapi.vcs.changes.LocalChangeList;
54 import com.intellij.openapi.vcs.changes.committed.*;
55 import com.intellij.openapi.vcs.changes.ui.*;
56 import com.intellij.openapi.vcs.history.*;
57 import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
58 import com.intellij.openapi.vcs.merge.MergeProvider;
59 import com.intellij.openapi.vcs.merge.MultipleFileMergeDialog;
60 import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
61 import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor;
62 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
63 import com.intellij.openapi.vfs.LocalFileSystem;
64 import com.intellij.openapi.vfs.VirtualFile;
65 import com.intellij.openapi.wm.ToolWindow;
66 import com.intellij.openapi.wm.ToolWindowId;
67 import com.intellij.openapi.wm.ToolWindowManager;
68 import com.intellij.openapi.wm.WindowManager;
69 import com.intellij.ui.content.Content;
70 import com.intellij.ui.content.ContentFactory;
71 import com.intellij.ui.content.MessageView;
72 import com.intellij.util.AsynchConsumer;
73 import com.intellij.util.BufferedListConsumer;
74 import com.intellij.util.Consumer;
75 import com.intellij.util.ui.ConfirmationDialog;
76 import com.intellij.util.ui.ErrorTreeView;
77 import com.intellij.util.ui.MessageCategory;
78 import org.jetbrains.annotations.Nls;
79 import org.jetbrains.annotations.NotNull;
80 import org.jetbrains.annotations.Nullable;
81
82 import java.awt.*;
83 import java.io.File;
84 import java.io.IOException;
85 import java.text.MessageFormat;
86 import java.util.*;
87 import java.util.List;
88
89 public class AbstractVcsHelperImpl extends AbstractVcsHelper {
90   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.AbstractVcsHelperImpl");
91
92   private final Project myProject;
93
94   public AbstractVcsHelperImpl(Project project) {
95     myProject = project;
96   }
97
98   public void openMessagesView(final VcsErrorViewPanel errorTreeView, final String tabDisplayName) {
99     CommandProcessor commandProcessor = CommandProcessor.getInstance();
100     commandProcessor.executeCommand(myProject, new Runnable() {
101       public void run() {
102         final MessageView messageView = MessageView.SERVICE.getInstance(myProject);
103         messageView.runWhenInitialized(new Runnable() {
104           public void run() {
105             final Content content =
106               ContentFactory.SERVICE.getInstance().createContent(errorTreeView, tabDisplayName, true);
107             messageView.getContentManager().addContent(content);
108             Disposer.register(content, errorTreeView);
109             messageView.getContentManager().setSelectedContent(content);
110             removeContents(content, tabDisplayName);
111
112             ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW).activate(null);
113           }
114         });
115       }
116     }, VcsBundle.message("command.name.open.error.message.view"), null);
117   }
118
119   public void showFileHistory(final VcsHistoryProvider vcsHistoryProvider, final FilePath path, final AbstractVcs vcs,
120                               final String repositoryPath) {
121     showFileHistory(vcsHistoryProvider, null, path, repositoryPath, vcs);
122   }
123
124   public void showFileHistory(final VcsHistoryProvider vcsHistoryProvider, final AnnotationProvider annotationProvider, final FilePath path,
125                               final String repositoryPath, final AbstractVcs vcs) {
126     final FileHistoryRefresherI refresherI = new FileHistoryRefresher(vcsHistoryProvider, annotationProvider, path, repositoryPath, vcs);
127     refresherI.run(false);
128   }
129
130   public void showRollbackChangesDialog(List<Change> changes) {
131     RollbackChangesDialog.rollbackChanges(myProject, changes);
132   }
133
134   @Nullable
135   public Collection<VirtualFile> selectFilesToProcess(final List<VirtualFile> files,
136                                                       final String title,
137                                                       @Nullable final String prompt,
138                                                       final String singleFileTitle,
139                                                       final String singleFilePromptTemplate,
140                                                       final VcsShowConfirmationOption confirmationOption) {
141     if (files == null || files.isEmpty()) {
142       return null;
143     }
144
145     if (files.size() == 1 && singleFilePromptTemplate != null) {
146       String filePrompt = MessageFormat.format(singleFilePromptTemplate, files.get(0).getPresentableUrl());
147       if (ConfirmationDialog
148         .requestForConfirmation(confirmationOption, myProject, filePrompt, singleFileTitle, Messages.getQuestionIcon())) {
149         return files;
150       }
151       return null;
152     }
153
154     SelectFilesDialog dlg = new SelectFilesDialog(myProject, files, prompt, confirmationOption, true, true, false);
155     dlg.setTitle(title);
156     if (! confirmationOption.isPersistent()) {
157       dlg.setDoNotAskOption(null);
158     }
159     dlg.show();
160     if (dlg.isOK()) {
161       final Collection<VirtualFile> selection = dlg.getSelectedFiles();
162       // return items in the same order as they were passed to us
163       final List<VirtualFile> result = new ArrayList<VirtualFile>();
164       for(VirtualFile file: files) {
165         if (selection.contains(file)) {
166           result.add(file);
167         }
168       }
169       return result;
170     }
171     return null;
172   }
173
174   @Nullable
175   public Collection<FilePath> selectFilePathsToProcess(List<FilePath> files,
176                                                        String title,
177                                                        @Nullable String prompt,
178                                                        String singleFileTitle,
179                                                        String singleFilePromptTemplate,
180                                                        VcsShowConfirmationOption confirmationOption,
181                                                        @Nullable String okActionName,
182                                                        @Nullable String cancelActionName) {
183     if (files.size() == 1 && singleFilePromptTemplate != null) {
184       final String filePrompt = MessageFormat.format(singleFilePromptTemplate, files.get(0).getPresentableUrl());
185       if (ConfirmationDialog.requestForConfirmation(confirmationOption, myProject, filePrompt, singleFileTitle,
186                                                     Messages.getQuestionIcon(), okActionName, cancelActionName)) {
187         return files;
188       }
189       return null;
190     }
191
192     final SelectFilePathsDialog dlg =
193       new SelectFilePathsDialog(myProject, files, prompt, confirmationOption, okActionName, cancelActionName);
194     dlg.setTitle(title);
195     if (! confirmationOption.isPersistent()) {
196       dlg.setDoNotAskOption(null);
197     }
198     dlg.show();
199     return dlg.isOK() ? dlg.getSelectedFiles() : null;
200   }
201
202   @Nullable
203   public Collection<FilePath> selectFilePathsToProcess(final List<FilePath> files,
204                                                        final String title,
205                                                        @Nullable final String prompt,
206                                                        final String singleFileTitle,
207                                                        final String singleFilePromptTemplate,
208                                                        final VcsShowConfirmationOption confirmationOption) {
209     return selectFilePathsToProcess(files, title, prompt, singleFileTitle, singleFilePromptTemplate, confirmationOption, null, null);
210   }
211
212   public void showErrors(final List<VcsException> abstractVcsExceptions, @NotNull final String tabDisplayName) {
213     showErrorsImpl(abstractVcsExceptions.isEmpty(), new Getter<VcsException>() {
214       public VcsException get() {
215         return abstractVcsExceptions.get(0);
216       }
217     }, tabDisplayName, new Consumer<VcsErrorViewPanel>() {
218       public void consume(VcsErrorViewPanel vcsErrorViewPanel) {
219         addDirectMessages(vcsErrorViewPanel, abstractVcsExceptions);
220       }
221     });
222   }
223   
224   @Override
225   public boolean commitChanges(@NotNull List<Change> changes, @NotNull LocalChangeList initialChangeList,
226                                @NotNull String commitMessage, @Nullable CommitExecutor executor) {
227     return CommitChangeListDialog.commitChanges(myProject, changes, initialChangeList, executor, commitMessage);
228   }
229
230   private void addDirectMessages(VcsErrorViewPanel vcsErrorViewPanel, List<VcsException> abstractVcsExceptions) {
231     for (final VcsException exception : abstractVcsExceptions) {
232       String[] messages = getExceptionMessages(exception);
233       vcsErrorViewPanel.addMessage(getErrorCategory(exception), messages, exception.getVirtualFile(), -1, -1, null);
234     }
235   }
236
237   private String[] getExceptionMessages(VcsException exception) {
238     String[] messages = exception.getMessages();
239     if (messages.length == 0) messages = new String[]{VcsBundle.message("exception.text.unknown.error")};
240     final List<String> list = new ArrayList<String>();
241     for (String message : messages) {
242       list.addAll(StringUtil.split(StringUtil.convertLineSeparators(message), "\n"));
243     }
244     return list.toArray(new String[list.size()]);
245   }
246
247   private void showErrorsImpl(final boolean isEmpty, final Getter<VcsException> firstGetter, @NotNull final String tabDisplayName,
248                               final Consumer<VcsErrorViewPanel> viewFiller) {
249     if (ApplicationManager.getApplication().isUnitTestMode() && !isEmpty) {
250       throw new RuntimeException(firstGetter.get());
251     } else if (ApplicationManager.getApplication().isUnitTestMode()) {
252       return;
253     }
254     ApplicationManager.getApplication().invokeLater(new Runnable() {
255       public void run() {
256         if (myProject.isDisposed()) return;
257         if (isEmpty) {
258           removeContents(null, tabDisplayName);
259           return;
260         }
261
262         final VcsErrorViewPanel errorTreeView = new VcsErrorViewPanel(myProject);
263         openMessagesView(errorTreeView, tabDisplayName);
264
265         viewFiller.consume(errorTreeView);
266       }
267     });
268   }
269
270   @Override
271   public void showErrors(final Map<HotfixData, List<VcsException>> exceptionGroups, @NotNull final String tabDisplayName) {
272     showErrorsImpl(exceptionGroups.isEmpty(), new Getter<VcsException>() {
273       public VcsException get() {
274         final List<VcsException> exceptionList = exceptionGroups.values().iterator().next();
275         return exceptionList == null ? null : (exceptionList.isEmpty() ? null : exceptionList.get(0));
276       }
277     }, tabDisplayName, new Consumer<VcsErrorViewPanel>() {
278       public void consume(VcsErrorViewPanel vcsErrorViewPanel) {
279         for (Map.Entry<HotfixData, List<VcsException>> entry : exceptionGroups.entrySet()) {
280           if (entry.getKey() == null) {
281             addDirectMessages(vcsErrorViewPanel, entry.getValue());
282           } else {
283             final List<VcsException> exceptionList = entry.getValue();
284             final List<SimpleErrorData> list = new ArrayList<SimpleErrorData>(exceptionList.size());
285             for (VcsException exception : exceptionList) {
286               final String[] messages = getExceptionMessages(exception);
287               list.add(new SimpleErrorData(
288                 ErrorTreeElementKind.convertMessageFromCompilerErrorType(getErrorCategory(exception)), messages, exception.getVirtualFile()));
289             }
290
291             vcsErrorViewPanel.addHotfixGroup(entry.getKey(), list);
292           }
293         }
294       }
295     });
296   }
297
298   private static int getErrorCategory(VcsException exception) {
299     if (exception.isWarning()) {
300       return MessageCategory.WARNING;
301     }
302     else {
303       return MessageCategory.ERROR;
304     }
305   }
306
307   protected void removeContents(Content notToRemove, final String tabDisplayName) {
308     MessageView messageView = MessageView.SERVICE.getInstance(myProject);
309     Content[] contents = messageView.getContentManager().getContents();
310     for (Content content : contents) {
311       LOG.assertTrue(content != null);
312       if (content.isPinned()) continue;
313       if (tabDisplayName.equals(content.getDisplayName()) && content != notToRemove) {
314         ErrorTreeView listErrorView = (ErrorTreeView)content.getComponent();
315         if (listErrorView != null) {
316           if (messageView.getContentManager().removeContent(content, true)) {
317             content.release();
318           }
319         }
320       }
321     }
322   }
323
324   public List<VcsException> runTransactionRunnable(AbstractVcs vcs, TransactionRunnable runnable, Object vcsParameters) {
325     List<VcsException> exceptions = new ArrayList<VcsException>();
326
327     TransactionProvider transactionProvider = vcs.getTransactionProvider();
328     boolean transactionSupported = transactionProvider != null;
329
330     if (transactionSupported) {
331       try {
332         transactionProvider.startTransaction(vcsParameters);
333       }
334       catch (VcsException e) {
335         return Collections.singletonList(e);
336       }
337     }
338
339     runnable.run(exceptions);
340
341     if (transactionSupported) {
342       if (exceptions.isEmpty()) {
343         try {
344           transactionProvider.commitTransaction(vcsParameters);
345         }
346         catch (VcsException e) {
347           exceptions.add(e);
348           transactionProvider.rollbackTransaction(vcsParameters);
349         }
350       }
351       else {
352         transactionProvider.rollbackTransaction(vcsParameters);
353       }
354     }
355
356     return exceptions;
357   }
358
359   public void showAnnotation(FileAnnotation annotation, VirtualFile file, AbstractVcs vcs) {
360     OpenFileDescriptor openFileDescriptor = new OpenFileDescriptor(myProject, file);
361     Editor editor = FileEditorManager.getInstance(myProject).openTextEditor(openFileDescriptor, true);
362     if (editor == null) {
363       Messages.showMessageDialog(VcsBundle.message("message.text.cannot.open.editor", file.getPresentableUrl()),
364                                  VcsBundle.message("message.title.cannot.open.editor"), Messages.getInformationIcon());
365       return;
366     }
367
368     AnnotateToggleAction.doAnnotate(editor, myProject, file, annotation, vcs);
369   }
370
371   public void showDifferences(final VcsFileRevision version1, final VcsFileRevision version2, final File file) {
372     try {
373       final byte[] byteContent1 = VcsHistoryUtil.loadRevisionContent(version1);
374       final byte[] byteContent2 = VcsHistoryUtil.loadRevisionContent(version2);
375
376       if (Comparing.equal(byteContent1, byteContent2)) {
377         Messages.showInfoMessage(VcsBundle.message("message.text.versions.are.identical"), VcsBundle.message("message.title.diff"));
378       }
379
380       final SimpleDiffRequest request = new SimpleDiffRequest(myProject, file.getAbsolutePath());
381
382       final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(file.getName());
383       if (fileType.isBinary()) {
384         Messages.showInfoMessage(VcsBundle.message("message.text.binary.versions.differ"), VcsBundle.message("message.title.diff"));
385
386         return;
387       }
388
389       final DiffContent content1 = getContentForVersion(version1, file);
390       final DiffContent content2 = getContentForVersion(version2, file);
391
392       if (version2.getRevisionNumber().compareTo(version1.getRevisionNumber()) > 0) {
393         request.setContents(content2, content1);
394         request.setContentTitles(version2.getRevisionNumber().asString(), version1.getRevisionNumber().asString());
395       }
396       else {
397         request.setContents(content1, content2);
398         request.setContentTitles(version1.getRevisionNumber().asString(), version2.getRevisionNumber().asString());
399       }
400
401       DiffManager.getInstance().getDiffTool().show(request);
402     }
403     catch (VcsException e) {
404       showError(e, VcsBundle.message("message.title.diff"));
405     }
406     catch (IOException e) {
407       showError(new VcsException(e), VcsBundle.message("message.title.diff"));
408     }
409
410   }
411
412   public void showChangesBrowser(List<CommittedChangeList> changelists) {
413     showChangesBrowser(changelists, null);
414   }
415
416   public void showChangesBrowser(List<CommittedChangeList> changelists, @Nls String title) {
417     showChangesBrowser(new CommittedChangesTableModel(changelists, false), title, false, null);
418   }
419
420   private ChangesBrowserDialog createChangesBrowserDialog(CommittedChangesTableModel changelists,
421                                                           String title,
422                                                           boolean showSearchAgain,
423                                                           @Nullable final Component parent, Consumer<ChangesBrowserDialog> initRunnable) {
424     final ChangesBrowserDialog.Mode mode = showSearchAgain ? ChangesBrowserDialog.Mode.Browse : ChangesBrowserDialog.Mode.Simple;
425     final ChangesBrowserDialog dlg = parent != null
426                                      ? new ChangesBrowserDialog(myProject, parent, changelists, mode, initRunnable)
427                                      : new ChangesBrowserDialog(myProject, changelists, mode, initRunnable);
428     if (title != null) {
429       dlg.setTitle(title);
430     }
431     return dlg;
432   }
433
434   private void showChangesBrowser(CommittedChangesTableModel changelists,
435                                   String title,
436                                   boolean showSearchAgain,
437                                   @Nullable final Component parent) {
438     final ChangesBrowserDialog.Mode mode = showSearchAgain ? ChangesBrowserDialog.Mode.Browse : ChangesBrowserDialog.Mode.Simple;
439     final ChangesBrowserDialog dlg = parent != null
440                                      ? new ChangesBrowserDialog(myProject, parent, changelists, mode, null)
441                                      : new ChangesBrowserDialog(myProject, changelists, mode, null);
442     if (title != null) {
443       dlg.setTitle(title);
444     }
445     dlg.show();
446   }
447
448   @Override
449   public void showChangesListBrowser(CommittedChangeList changelist, @Nullable VirtualFile toSelect, @Nls String title) {
450     final ChangeListViewerDialog dlg = new ChangeListViewerDialog(myProject, changelist, toSelect);
451     if (title != null) {
452       dlg.setTitle(title);
453     }
454     dlg.show();
455   }
456
457   public void showChangesListBrowser(CommittedChangeList changelist, @Nls String title) {
458     showChangesListBrowser(changelist, null, title);
459   }
460
461   public void showWhatDiffersBrowser(final Component parent, final Collection<Change> changes, @Nls final String title) {
462     final ChangeListViewerDialog dlg;
463     if (parent != null) {
464       dlg = new ChangeListViewerDialog(parent, myProject, changes, false);
465     }
466     else {
467       dlg = new ChangeListViewerDialog(myProject, changes, false);
468     }
469     if (title != null) {
470       dlg.setTitle(title);
471     }
472     dlg.show();
473   }
474
475   public void showChangesBrowser(final CommittedChangesProvider provider,
476                                  final RepositoryLocation location,
477                                  @Nls String title,
478                                  Component parent) {
479     final ChangesBrowserSettingsEditor filterUI = provider.createFilterUI(true);
480     ChangeBrowserSettings settings = provider.createDefaultSettings();
481     boolean ok;
482     if (filterUI != null) {
483       final CommittedChangesFilterDialog dlg = new CommittedChangesFilterDialog(myProject, filterUI, settings);
484       dlg.show();
485       ok = dlg.getExitCode() == DialogWrapper.OK_EXIT_CODE;
486       settings = dlg.getSettings();
487     }
488     else {
489       ok = true;
490     }
491
492     if (ok) {
493       if (myProject.isDefault() || (ProjectLevelVcsManager.getInstance(myProject).getAllActiveVcss().length == 0) ||
494           (! ModalityState.NON_MODAL.equals(ModalityState.current()))) {
495         final List<CommittedChangeList> versions = new ArrayList<CommittedChangeList>();
496
497         if (parent == null || !parent.isValid()) {
498           parent = WindowManager.getInstance().suggestParentWindow(myProject);
499         }
500         final CommittedChangesTableModel model = new CommittedChangesTableModel(versions, true);
501         final AsynchronousListsLoader[] task = new AsynchronousListsLoader[1];
502         final ChangeBrowserSettings finalSettings = settings;
503         final ChangesBrowserDialog dlg = createChangesBrowserDialog(model, title, filterUI != null, parent, new Consumer<ChangesBrowserDialog>() {
504           @Override
505           public void consume(ChangesBrowserDialog changesBrowserDialog) {
506             task[0] = new AsynchronousListsLoader(myProject, provider, location, finalSettings, changesBrowserDialog);
507             ProgressManager.getInstance().run(task[0]);
508           }
509         });
510
511         dlg.startLoading();
512         dlg.show();
513         if (task[0] != null) {
514           task[0].cancel();
515           final List<VcsException> exceptions = task[0].getExceptions();
516           if (! exceptions.isEmpty()) {
517             Messages.showErrorDialog(myProject, VcsBundle.message("browse.changes.error.message", exceptions.get(0).getMessage()),
518                                      VcsBundle.message("browse.changes.error.title"));
519             return;
520           }
521
522           if (! task[0].isRevisionsReturned()) {
523             Messages.showInfoMessage(myProject, VcsBundle.message("browse.changes.nothing.found"),
524                                      VcsBundle.message("browse.changes.nothing.found.title"));
525           }
526         }
527       }
528       else {
529         openCommittedChangesTab(provider, location, settings, 0, title);
530       }
531     }
532   }
533
534   @Nullable
535   public <T extends CommittedChangeList, U extends ChangeBrowserSettings> T chooseCommittedChangeList(@NotNull CommittedChangesProvider<T, U> provider,
536                                                                                                       RepositoryLocation location) {
537     final List<T> changes;
538     try {
539       changes = provider.getCommittedChanges(provider.createDefaultSettings(), location, 0);
540     }
541     catch (VcsException e) {
542       return null;
543     }
544     final ChangesBrowserDialog dlg = new ChangesBrowserDialog(myProject, new CommittedChangesTableModel((List<CommittedChangeList>)changes,
545                                                                                                         provider.getColumns(), false),
546                                                                          ChangesBrowserDialog.Mode.Choose, null);
547     dlg.show();
548     if (dlg.isOK()) {
549       return (T)dlg.getSelectedChangeList();
550     }
551     else {
552       return null;
553     }
554   }
555
556   @Override
557   @NotNull
558   public List<VirtualFile> showMergeDialog(List<VirtualFile> files, MergeProvider provider) {
559     return showMergeDialog(files, provider, new MergeDialogCustomizer());
560   }
561
562   @Override
563   @NotNull
564   public List<VirtualFile> showMergeDialog(List<VirtualFile> files, MergeProvider provider, @NotNull MergeDialogCustomizer mergeDialogCustomizer) {
565     if (files.isEmpty()) return Collections.emptyList();
566     final MultipleFileMergeDialog fileMergeDialog = new MultipleFileMergeDialog(myProject, files, provider, mergeDialogCustomizer);
567     fileMergeDialog.show();
568     return fileMergeDialog.getProcessedFiles();
569   }
570
571   @NotNull
572   public List<VirtualFile> showMergeDialog(final List<VirtualFile> files) {
573     if (files.isEmpty()) return Collections.emptyList();
574     MergeProvider provider = null;
575     for (VirtualFile virtualFile : files) {
576       final AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(virtualFile);
577       if (vcs != null) {
578         provider = vcs.getMergeProvider();
579         if (provider != null) break;
580       }
581     }
582     if (provider == null) return Collections.emptyList();
583     return showMergeDialog(files, provider);
584   }
585
586   private static DiffContent getContentForVersion(final VcsFileRevision version, final File file) throws IOException, VcsException {
587     VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
588     if (vFile != null && (version instanceof CurrentRevision) && !vFile.getFileType().isBinary()) {
589       return new DocumentContent(FileDocumentManager.getInstance().getDocument(vFile), vFile.getFileType());
590     }
591     else {
592       return new SimpleContent(VcsHistoryUtil.loadRevisionContentGuessEncoding(version, vFile, null),
593                                FileTypeManager.getInstance().getFileTypeByFileName(file.getName()));
594     }
595   }
596
597   public void openCommittedChangesTab(final AbstractVcs vcs,
598                                       final VirtualFile root,
599                                       final ChangeBrowserSettings settings,
600                                       final int maxCount,
601                                       String title) {
602     final RepositoryLocation location = CommittedChangesCache.getInstance(myProject).getLocationCache().getLocation(vcs, new FilePathImpl(root),
603                                                                                                                     false);
604     openCommittedChangesTab(vcs.getCommittedChangesProvider(), location, settings, maxCount, title);
605   }
606
607   public void openCommittedChangesTab(final CommittedChangesProvider provider,
608                                       final RepositoryLocation location,
609                                       final ChangeBrowserSettings settings,
610                                       final int maxCount,
611                                       String title) {
612     DefaultActionGroup extraActions = new DefaultActionGroup();
613     CommittedChangesPanel panel = new CommittedChangesPanel(myProject, provider, settings, location, extraActions);
614     panel.setMaxCount(maxCount);
615     panel.refreshChanges(false);
616     final ContentFactory factory = ContentFactory.SERVICE.getInstance();
617     if (title == null && location != null) {
618       title = VcsBundle.message("browse.changes.content.title", location.toPresentableString());
619     }
620     final Content content = factory.createContent(panel, title, false);
621     final ChangesViewContentI contentManager = ChangesViewContentManager.getInstance(myProject);
622     contentManager.addContent(content);
623     contentManager.setSelectedContent(content);
624
625     extraActions.add(new CloseTabToolbarAction() {
626       public void actionPerformed(final AnActionEvent e) {
627         contentManager.removeContent(content);
628       }
629     });
630
631     ToolWindow window = ToolWindowManager.getInstance(myProject).getToolWindow(ChangesViewContentManager.TOOLWINDOW_ID);
632     if (!window.isVisible()) {
633       window.activate(null);
634     }
635   }
636
637   private static class AsynchronousListsLoader extends Task.Backgroundable {
638     private final CommittedChangesProvider myProvider;
639     private final RepositoryLocation myLocation;
640     private final ChangeBrowserSettings mySettings;
641     private final ChangesBrowserDialog myDlg;
642     private final List<VcsException> myExceptions;
643     private volatile boolean myCanceled;
644     private boolean myRevisionsReturned;
645
646     private AsynchronousListsLoader(@Nullable Project project, final CommittedChangesProvider provider,
647                                     final RepositoryLocation location, final ChangeBrowserSettings settings, final ChangesBrowserDialog dlg) {
648       super(project, VcsBundle.message("browse.changes.progress.title"), true, BackgroundFromStartOption.getInstance());
649       myProvider = provider;
650       myLocation = location;
651       mySettings = settings;
652       myDlg = dlg;
653       myExceptions = new LinkedList<VcsException>();
654     }
655
656     public void cancel() {
657       myCanceled = true;
658     }
659
660     public void run(@NotNull final ProgressIndicator indicator) {
661       final AsynchConsumer<List<CommittedChangeList>> appender = myDlg.getAppender();
662       final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(10, appender, -1);
663
664       final Application application = ApplicationManager.getApplication();
665       try {
666         myProvider.loadCommittedChanges(mySettings, myLocation, 0, new AsynchConsumer<CommittedChangeList>() {
667           public void consume(CommittedChangeList committedChangeList) {
668             myRevisionsReturned = true;
669             bufferedListConsumer.consumeOne(committedChangeList);
670             if (myCanceled) {
671               indicator.cancel();
672             }
673           }
674
675           public void finished() {
676             bufferedListConsumer.flush();
677             appender.finished();
678
679             if (! myRevisionsReturned) {
680               application.invokeLater(new Runnable() {
681                 public void run() {
682                   myDlg.close(-1);
683                 }
684               }, ModalityState.stateForComponent(myDlg.getWindow()));
685             }
686           }
687         });
688       }
689       catch (VcsException e) {
690         myExceptions.add(e);
691         application.invokeLater(new Runnable() {
692           public void run() {
693             myDlg.close(-1);
694           }
695         }, ModalityState.stateForComponent(myDlg.getWindow()));
696       }
697     }
698
699     public List<VcsException> getExceptions() {
700       return myExceptions;
701     }
702
703     public boolean isRevisionsReturned() {
704       return myRevisionsReturned;
705     }
706   }
707 }