Merge branch 'python-fixes'
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / FileEditorManagerImpl.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 package com.intellij.openapi.fileEditor.impl;
17
18 import com.intellij.ProjectTopics;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.plugins.PluginManagerCore;
21 import com.intellij.ide.ui.UISettings;
22 import com.intellij.ide.ui.UISettingsListener;
23 import com.intellij.injected.editor.VirtualFileWindow;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.application.ex.ApplicationManagerEx;
28 import com.intellij.openapi.application.impl.LaterInvocator;
29 import com.intellij.openapi.command.CommandProcessor;
30 import com.intellij.openapi.components.ProjectComponent;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.editor.ScrollType;
34 import com.intellij.openapi.editor.impl.EditorComponentImpl;
35 import com.intellij.openapi.extensions.Extensions;
36 import com.intellij.openapi.fileEditor.*;
37 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
38 import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
39 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
40 import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
41 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
42 import com.intellij.openapi.fileTypes.FileTypeEvent;
43 import com.intellij.openapi.fileTypes.FileTypeListener;
44 import com.intellij.openapi.fileTypes.FileTypeManager;
45 import com.intellij.openapi.project.DumbAwareRunnable;
46 import com.intellij.openapi.project.DumbService;
47 import com.intellij.openapi.project.PossiblyDumbAware;
48 import com.intellij.openapi.project.Project;
49 import com.intellij.openapi.project.impl.ProjectImpl;
50 import com.intellij.openapi.roots.ModuleRootAdapter;
51 import com.intellij.openapi.roots.ModuleRootEvent;
52 import com.intellij.openapi.startup.StartupManager;
53 import com.intellij.openapi.util.*;
54 import com.intellij.openapi.util.io.FileUtil;
55 import com.intellij.openapi.util.registry.Registry;
56 import com.intellij.openapi.vcs.FileStatus;
57 import com.intellij.openapi.vcs.FileStatusListener;
58 import com.intellij.openapi.vcs.FileStatusManager;
59 import com.intellij.openapi.vfs.*;
60 import com.intellij.openapi.wm.IdeFocusManager;
61 import com.intellij.openapi.wm.ToolWindowManager;
62 import com.intellij.openapi.wm.WindowManager;
63 import com.intellij.openapi.wm.ex.StatusBarEx;
64 import com.intellij.openapi.wm.impl.IdeFrameImpl;
65 import com.intellij.ui.FocusTrackback;
66 import com.intellij.ui.docking.DockContainer;
67 import com.intellij.ui.docking.DockManager;
68 import com.intellij.ui.docking.impl.DockManagerImpl;
69 import com.intellij.ui.tabs.impl.JBTabsImpl;
70 import com.intellij.util.Function;
71 import com.intellij.util.SmartList;
72 import com.intellij.util.containers.ContainerUtil;
73 import com.intellij.util.messages.MessageBusConnection;
74 import com.intellij.util.messages.impl.MessageListenerList;
75 import com.intellij.util.ui.JBInsets;
76 import com.intellij.util.ui.UIUtil;
77 import com.intellij.util.ui.update.MergingUpdateQueue;
78 import com.intellij.util.ui.update.Update;
79 import org.jdom.Element;
80 import org.jetbrains.annotations.NotNull;
81 import org.jetbrains.annotations.Nullable;
82
83 import javax.swing.*;
84 import javax.swing.border.Border;
85 import java.awt.*;
86 import java.awt.event.KeyEvent;
87 import java.awt.event.MouseEvent;
88 import java.beans.PropertyChangeEvent;
89 import java.beans.PropertyChangeListener;
90 import java.lang.ref.WeakReference;
91 import java.util.*;
92 import java.util.List;
93
94 /**
95  * @author Anton Katilin
96  * @author Eugene Belyaev
97  * @author Vladimir Kondratyev
98  */
99 public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable {
100   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl");
101   private static final Key<Boolean> DUMB_AWARE = Key.create("DUMB_AWARE");
102
103   private static final FileEditor[] EMPTY_EDITOR_ARRAY = {};
104   private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {};
105   public static final Key<Boolean> CLOSING_TO_REOPEN = Key.create("CLOSING_TO_REOPEN");
106   public static final String FILE_EDITOR_MANAGER = "FileEditorManager";
107
108   private volatile JPanel myPanels;
109   private PreviewPanel myPreviewPanel;
110   private EditorsSplitters mySplitters;
111   private final Project myProject;
112   private final List<Pair<VirtualFile, EditorWindow>> mySelectionHistory = new ArrayList<Pair<VirtualFile, EditorWindow>>();
113   private WeakReference<EditorComposite> myLastSelectedComposite = new WeakReference<EditorComposite>(null);
114
115
116   private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true, null);
117
118   private final BusyObject.Impl.Simple myBusyObject = new BusyObject.Impl.Simple();
119
120   /**
121    * Removes invalid myEditor and updates "modified" status.
122    */
123   private final MyEditorPropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener();
124   private final DockManager myDockManager;
125   private DockableEditorContainerFactory myContentFactory;
126   private EditorHistoryManager myEditorHistoryManager;
127
128   public FileEditorManagerImpl(final Project project, DockManager dockManager, EditorHistoryManager editorHistoryManager) {
129 /*    ApplicationManager.getApplication().assertIsDispatchThread(); */
130     myProject = project;
131     myDockManager = dockManager;
132     myEditorHistoryManager = editorHistoryManager;
133     myListenerList =
134       new MessageListenerList<FileEditorManagerListener>(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER);
135
136     if (Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME).length > 0) {
137       myListenerList.add(new FileEditorManagerAdapter() {
138         @Override
139         public void selectionChanged(@NotNull FileEditorManagerEvent event) {
140           EditorsSplitters splitters = getSplitters();
141           openAssociatedFile(event.getNewFile(), splitters.getCurrentWindow(), splitters);
142         }
143       });
144     }
145
146     myQueue.setTrackUiActivity(true);
147   }
148
149   public void initDockableContentFactory() {
150     if (myContentFactory != null) return;
151
152     myContentFactory = new DockableEditorContainerFactory(myProject, this, myDockManager);
153     myDockManager.register(DockableEditorContainerFactory.TYPE, myContentFactory);
154     Disposer.register(myProject, myContentFactory);
155   }
156
157   public static boolean isDumbAware(FileEditor editor) {
158     return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE)) &&
159            (!(editor instanceof PossiblyDumbAware) || ((PossiblyDumbAware)editor).isDumbAware());
160   }
161
162   //-------------------------------------------------------------------------------
163
164   @Override
165   public JComponent getComponent() {
166     initUI();
167     return myPanels;
168   }
169
170   @NotNull
171   public EditorsSplitters getMainSplitters() {
172     initUI();
173
174     return mySplitters;
175   }
176
177   public Set<EditorsSplitters> getAllSplitters() {
178     HashSet<EditorsSplitters> all = new LinkedHashSet<EditorsSplitters>();
179     if (PreviewPanel.isAvailable()) {
180       initUI();
181       EditorWindow window = myPreviewPanel.getWindow();
182       if (window != null) {
183         all.add(window.getOwner());
184       }
185     }
186     all.add(getMainSplitters());
187     Set<DockContainer> dockContainers = myDockManager.getContainers();
188     for (DockContainer each : dockContainers) {
189       if (each instanceof DockableEditorTabbedContainer) {
190         all.add(((DockableEditorTabbedContainer)each).getSplitters());
191       }
192     }
193
194     return Collections.unmodifiableSet(all);
195   }
196
197   private AsyncResult<EditorsSplitters> getActiveSplitters(boolean syncUsage) {
198     final boolean async = Registry.is("ide.windowSystem.asyncSplitters") && !syncUsage;
199
200     final AsyncResult<EditorsSplitters> result = new AsyncResult<EditorsSplitters>();
201     final IdeFocusManager fm = IdeFocusManager.getInstance(myProject);
202     Runnable run = new Runnable() {
203       @Override
204       public void run() {
205         if (myProject.isDisposed()) {
206           result.setRejected();
207           return;
208         }
209
210         Component focusOwner = fm.getFocusOwner();
211         if (focusOwner == null && !async) {
212           focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
213         }
214
215         if (focusOwner == null && !async) {
216           focusOwner = fm.getLastFocusedFor(fm.getLastFocusedFrame());
217         }
218
219         DockContainer container = myDockManager.getContainerFor(focusOwner);
220         if (container == null && !async) {
221           focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
222           container = myDockManager.getContainerFor(focusOwner);
223         }
224
225         if (container instanceof DockableEditorTabbedContainer) {
226           result.setDone(((DockableEditorTabbedContainer)container).getSplitters());
227         }
228         else {
229           result.setDone(getMainSplitters());
230         }
231       }
232     };
233
234     if (async) {
235       fm.doWhenFocusSettlesDown(run);
236     }
237     else {
238       UIUtil.invokeAndWaitIfNeeded(run);
239     }
240
241     return result;
242   }
243
244   private final Object myInitLock = new Object();
245
246   private void initUI() {
247     if (myPanels == null) {
248       synchronized (myInitLock) {
249         if (myPanels == null) {
250           myPanels = new JPanel(new BorderLayout());
251           myPanels.setOpaque(false);
252           myPanels.setBorder(new MyBorder());
253           mySplitters = new EditorsSplitters(this, myDockManager, true);
254           myPanels.add(mySplitters, BorderLayout.CENTER);
255         }
256       }
257     }
258     if (myPreviewPanel == null && PreviewPanel.isAvailable()) {
259       synchronized (myInitLock) {
260         myPreviewPanel = new PreviewPanel(myProject, this, myDockManager);
261       }
262     }
263   }
264
265   private static class MyBorder implements Border {
266     @Override
267     public void paintBorder(@NotNull Component c, @NotNull Graphics g, int x, int y, int width, int height) {
268       if (UIUtil.isUnderAquaLookAndFeel()) {
269         g.setColor(JBTabsImpl.MAC_AQUA_BG_COLOR);
270         final Insets insets = getBorderInsets(c);
271         if (insets.top > 0) {
272           g.fillRect(x, y, width, height + insets.top);
273         }
274       }
275     }
276
277     @NotNull
278     @Override
279     public Insets getBorderInsets(Component c) {
280       return JBInsets.NONE;
281     }
282
283     @Override
284     public boolean isBorderOpaque() {
285       return false;
286     }
287   }
288
289   @Override
290   public JComponent getPreferredFocusedComponent() {
291     assertReadAccess();
292     final EditorWindow window = getSplitters().getCurrentWindow();
293     if (window != null) {
294       final EditorWithProviderComposite editor = window.getSelectedEditor();
295       if (editor != null) {
296         return editor.getPreferredFocusedComponent();
297       }
298     }
299     return null;
300   }
301
302   //-------------------------------------------------------
303
304   /**
305    * @return color of the <code>file</code> which corresponds to the
306    *         file's status
307    */
308   public Color getFileColor(@NotNull final VirtualFile file) {
309     final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
310     Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : UIUtil.getLabelForeground();
311     if (statusColor == null) statusColor = UIUtil.getLabelForeground();
312     return statusColor;
313   }
314
315   public boolean isProblem(@NotNull final VirtualFile file) {
316     return false;
317   }
318
319   public String getFileTooltipText(VirtualFile file) {
320     return FileUtil.getLocationRelativeToUserHome(file.getPresentableUrl());
321   }
322
323   @Override
324   public void updateFilePresentation(@NotNull VirtualFile file) {
325     if (!isFileOpen(file)) return;
326
327     updateFileColor(file);
328     updateFileIcon(file);
329     updateFileName(file);
330     updateFileBackgroundColor(file);
331   }
332
333   /**
334    * Updates tab color for the specified <code>file</code>. The <code>file</code>
335    * should be opened in the myEditor, otherwise the method throws an assertion.
336    */
337   private void updateFileColor(final VirtualFile file) {
338     Set<EditorsSplitters> all = getAllSplitters();
339     for (EditorsSplitters each : all) {
340       each.updateFileColor(file);
341     }
342   }
343
344   private void updateFileBackgroundColor(final VirtualFile file) {
345     Set<EditorsSplitters> all = getAllSplitters();
346     for (EditorsSplitters each : all) {
347       each.updateFileBackgroundColor(file);
348     }
349   }
350
351   /**
352    * Updates tab icon for the specified <code>file</code>. The <code>file</code>
353    * should be opened in the myEditor, otherwise the method throws an assertion.
354    */
355   protected void updateFileIcon(final VirtualFile file) {
356     Set<EditorsSplitters> all = getAllSplitters();
357     for (EditorsSplitters each : all) {
358       each.updateFileIcon(file);
359     }
360   }
361
362   /**
363    * Updates tab title and tab tool tip for the specified <code>file</code>
364    */
365   void updateFileName(@Nullable final VirtualFile file) {
366     // Queue here is to prevent title flickering when tab is being closed and two events arriving: with component==null and component==next focused tab
367     // only the last event makes sense to handle
368     myQueue.queue(new Update("UpdateFileName " + (file == null ? "" : file.getPath())) {
369       @Override
370       public boolean isExpired() {
371         return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid());
372       }
373
374       @Override
375       public void run() {
376         Set<EditorsSplitters> all = getAllSplitters();
377         for (EditorsSplitters each : all) {
378           each.updateFileName(file);
379         }
380       }
381     });
382   }
383
384   //-------------------------------------------------------
385
386
387   @Override
388   public VirtualFile getFile(@NotNull final FileEditor editor) {
389     final EditorComposite editorComposite = getEditorComposite(editor);
390     if (editorComposite != null) {
391       return editorComposite.getFile();
392     }
393     return null;
394   }
395
396   @Override
397   public void unsplitWindow() {
398     final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow();
399     if (currentWindow != null) {
400       currentWindow.unsplit(true);
401     }
402   }
403
404   @Override
405   public void unsplitAllWindow() {
406     final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow();
407     if (currentWindow != null) {
408       currentWindow.unsplitAll();
409     }
410   }
411
412   @Override
413   public int getWindowSplitCount() {
414     return getActiveSplitters(true).getResult().getSplitCount();
415   }
416
417   @Override
418   public boolean hasSplitOrUndockedWindows() {
419     Set<EditorsSplitters> splitters = getAllSplitters();
420     if (splitters.size() > 1) return true;
421     return getWindowSplitCount() > 1;
422   }
423
424   @Override
425   @NotNull
426   public EditorWindow[] getWindows() {
427     ArrayList<EditorWindow> windows = new ArrayList<EditorWindow>();
428     Set<EditorsSplitters> all = getAllSplitters();
429     for (EditorsSplitters each : all) {
430       EditorWindow[] eachList = each.getWindows();
431       windows.addAll(Arrays.asList(eachList));
432     }
433
434     return windows.toArray(new EditorWindow[windows.size()]);
435   }
436
437   @Override
438   public EditorWindow getNextWindow(@NotNull final EditorWindow window) {
439     final EditorWindow[] windows = getSplitters().getOrderedWindows();
440     for (int i = 0; i != windows.length; ++i) {
441       if (windows[i].equals(window)) {
442         return windows[(i + 1) % windows.length];
443       }
444     }
445     LOG.error("Not window found");
446     return null;
447   }
448
449   @Override
450   public EditorWindow getPrevWindow(@NotNull final EditorWindow window) {
451     final EditorWindow[] windows = getSplitters().getOrderedWindows();
452     for (int i = 0; i != windows.length; ++i) {
453       if (windows[i].equals(window)) {
454         return windows[(i + windows.length - 1) % windows.length];
455       }
456     }
457     LOG.error("Not window found");
458     return null;
459   }
460
461   @Override
462   public void createSplitter(final int orientation, @Nullable final EditorWindow window) {
463     // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current'
464     if (window != null) {
465       window.split(orientation, true, null, false);
466     }
467     // otherwise we'll split the current window, if any
468     else {
469       final EditorWindow currentWindow = getSplitters().getCurrentWindow();
470       if (currentWindow != null) {
471         currentWindow.split(orientation, true, null, false);
472       }
473     }
474   }
475
476   @Override
477   public void changeSplitterOrientation() {
478     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
479     if (currentWindow != null) {
480       currentWindow.changeOrientation();
481     }
482   }
483
484
485   @Override
486   public void flipTabs() {
487     /*
488     if (myTabs == null) {
489       myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT);
490       remove (mySplitters);
491       add (myTabs, BorderLayout.CENTER);
492       initTabs ();
493     } else {
494       remove (myTabs);
495       add (mySplitters, BorderLayout.CENTER);
496       myTabs.dispose ();
497       myTabs = null;
498     }
499     */
500     myPanels.revalidate();
501   }
502
503   @Override
504   public boolean tabsMode() {
505     return false;
506   }
507
508   private void setTabsMode(final boolean mode) {
509     if (tabsMode() != mode) {
510       flipTabs();
511     }
512     //LOG.assertTrue (tabsMode () == mode);
513   }
514
515
516   @Override
517   public boolean isInSplitter() {
518     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
519     return currentWindow != null && currentWindow.inSplitter();
520   }
521
522   @Override
523   public boolean hasOpenedFile() {
524     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
525     return currentWindow != null && currentWindow.getSelectedEditor() != null;
526   }
527
528   @Override
529   public VirtualFile getCurrentFile() {
530     return getActiveSplitters(true).getResult().getCurrentFile();
531   }
532
533   @Override
534   @NotNull
535   public AsyncResult<EditorWindow> getActiveWindow() {
536     return _getActiveWindow(false);
537   }
538
539   @NotNull
540   private AsyncResult<EditorWindow> _getActiveWindow(boolean now) {
541     return getActiveSplitters(now).subResult(new Function<EditorsSplitters, EditorWindow>() {
542       @Override
543       public EditorWindow fun(EditorsSplitters splitters) {
544         return splitters.getCurrentWindow();
545       }
546     });
547   }
548
549   @Override
550   public EditorWindow getCurrentWindow() {
551     return _getActiveWindow(true).getResult();
552   }
553
554   @Override
555   public void setCurrentWindow(final EditorWindow window) {
556     getActiveSplitters(true).getResult().setCurrentWindow(window, true);
557   }
558
559   public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window, final boolean transferFocus) {
560     assertDispatchThread();
561
562     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
563       @Override
564       public void run() {
565         if (window.isFileOpen(file)) {
566           window.closeFile(file, true, transferFocus);
567         }
568       }
569     }, IdeBundle.message("command.close.active.editor"), null);
570     removeSelectionRecord(file, window);
571   }
572
573   @Override
574   public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) {
575     closeFile(file, window, true);
576   }
577
578   //============================= EditorManager methods ================================
579
580   @Override
581   public void closeFile(@NotNull final VirtualFile file) {
582     closeFile(file, true, false);
583   }
584
585   public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus, final boolean closeAllCopies) {
586     assertDispatchThread();
587
588     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
589       @Override
590       public void run() {
591         closeFileImpl(file, moveFocus, closeAllCopies);
592       }
593     }, "", null);
594   }
595
596   private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus, boolean closeAllCopies) {
597     assertDispatchThread();
598     runChange(new FileEditorManagerChange() {
599       @Override
600       public void run(EditorsSplitters splitters) {
601         splitters.closeFile(file, moveFocus);
602       }
603     }, closeAllCopies ? null : getActiveSplitters(true).getResult());
604   }
605
606   //-------------------------------------- Open File ----------------------------------------
607
608   @Override
609   @NotNull
610   public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull final VirtualFile file,
611                                                                         final boolean focusEditor,
612                                                                         final boolean searchForSplitter) {
613     if (!file.isValid()) {
614       throw new IllegalArgumentException("file is not valid: " + file);
615     }
616     assertDispatchThread();
617
618     if (isOpenInNewWindow(EventQueue.getCurrentEvent())) {
619       return openFileInNewWindow(file);
620     }
621
622
623     EditorWindow wndToOpenIn = null;
624     if (searchForSplitter) {
625       Set<EditorsSplitters> all = getAllSplitters();
626       EditorsSplitters active = getActiveSplitters(true).getResult();
627       if (active.getCurrentWindow() != null && active.getCurrentWindow().isFileOpen(file)) {
628         wndToOpenIn = active.getCurrentWindow();
629       } else {
630         for (EditorsSplitters splitters : all) {
631           final EditorWindow window = splitters.getCurrentWindow();
632           if (window == null) continue;
633
634           if (window.isFileOpen(file)) {
635             wndToOpenIn = window;
636             break;
637           }
638         }
639       }
640     }
641     else {
642       wndToOpenIn = getSplitters().getCurrentWindow();
643     }
644
645     if (wndToOpenIn == null || !wndToOpenIn.isFileOpen(file)) {
646       initUI();
647       EditorWindow previewWindow = PreviewPanel.isAvailable() && myPreviewPanel != null ? myPreviewPanel.getWindow() : null;
648       if (previewWindow != null) {
649         wndToOpenIn = previewWindow;
650       }
651     }
652
653     EditorsSplitters splitters = getSplitters();
654
655     if (wndToOpenIn == null) {
656       wndToOpenIn = splitters.getOrCreateCurrentWindow(file);
657     }
658
659     openAssociatedFile(file, wndToOpenIn, splitters);
660     return openFileImpl2(wndToOpenIn, file, focusEditor);
661   }
662
663   public Pair<FileEditor[], FileEditorProvider[]> openFileInNewWindow(@NotNull VirtualFile file) {
664     return ((DockManagerImpl)DockManager.getInstance(getProject())).createNewDockContainerFor(file, this);
665   }
666
667   private static boolean isOpenInNewWindow(AWTEvent event) {
668     // Shift was used while clicking
669     if (event instanceof MouseEvent && ((MouseEvent)event).isShiftDown()) {
670       return true;
671     }
672
673     // Shift + Enter
674     if (event instanceof KeyEvent
675         && ((KeyEvent)event).getKeyCode() == KeyEvent.VK_ENTER
676         && ((KeyEvent)event).isShiftDown()) {
677       return true;
678     }
679
680     return false;
681   }
682
683   private void openAssociatedFile(VirtualFile file, EditorWindow wndToOpenIn, EditorsSplitters splitters) {
684     EditorWindow[] windows = splitters.getWindows();
685
686     if (file != null && windows.length == 2) {
687       for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) {
688         VirtualFile associatedFile = finder.getAssociatedFileToOpen(myProject, file);
689
690         if (associatedFile != null) {
691           EditorWindow currentWindow = splitters.getCurrentWindow();
692           int idx = windows[0] == wndToOpenIn ? 1 : 0;
693           openFileImpl2(windows[idx], associatedFile, false);
694
695           if (currentWindow != null) {
696             splitters.setCurrentWindow(currentWindow, false);
697           }
698
699           break;
700         }
701       }
702     }
703   }
704
705   @NotNull
706   @Override
707   public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull VirtualFile file,
708                                                                         boolean focusEditor,
709                                                                         @NotNull EditorWindow window) {
710     if (!file.isValid()) {
711       throw new IllegalArgumentException("file is not valid: " + file);
712     }
713     assertDispatchThread();
714
715     return openFileImpl2(window, file, focusEditor);
716   }
717
718   @NotNull
719   public Pair<FileEditor[], FileEditorProvider[]> openFileImpl2(@NotNull final EditorWindow window,
720                                                                 @NotNull final VirtualFile file,
721                                                                 final boolean focusEditor) {
722     final Ref<Pair<FileEditor[], FileEditorProvider[]>> result = new Ref<Pair<FileEditor[], FileEditorProvider[]>>();
723     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
724       @Override
725       public void run() {
726         result.set(openFileImpl3(window, file, focusEditor, null, true));
727       }
728     }, "", null);
729     return result.get();
730   }
731
732   /**
733    * @param file    to be opened. Unlike openFile method, file can be
734    *                invalid. For example, all file were invalidate and they are being
735    *                removed one by one. If we have removed one invalid file, then another
736    *                invalid file become selected. That's why we do not require that
737    *                passed file is valid.
738    * @param entry   map between FileEditorProvider and FileEditorState. If this parameter
739    */
740   @NotNull
741   Pair<FileEditor[], FileEditorProvider[]> openFileImpl3(@NotNull final EditorWindow window,
742                                                          @NotNull final VirtualFile file,
743                                                          final boolean focusEditor,
744                                                          @Nullable final HistoryEntry entry,
745                                                          boolean current) {
746     return openFileImpl4(window, file, entry, current, focusEditor, null, -1);
747   }
748
749   /**
750    * This method can be invoked from background thread. Of course, UI for returned editors should be accessed from EDT in any case.
751    */
752   @NotNull
753   Pair<FileEditor[], FileEditorProvider[]> openFileImpl4(@NotNull final EditorWindow window,
754                                                          @NotNull final VirtualFile file,
755                                                          @Nullable final HistoryEntry entry,
756                                                          final boolean current, final boolean focusEditor, final Boolean pin,
757                                                          final int index) {
758     ApplicationManager.getApplication().assertReadAccessAllowed();
759
760     final Ref<EditorWithProviderComposite> compositeRef = new Ref<EditorWithProviderComposite>();
761
762     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
763       @Override
764       public void run() {
765         compositeRef.set(window.findFileComposite(file));
766       }
767     });
768
769     final FileEditorProvider[] newProviders;
770     final AsyncFileEditorProvider.Builder[] builders;
771     if (compositeRef.isNull()) {
772       // File is not opened yet. In this case we have to create editors
773       // and select the created EditorComposite.
774       newProviders = getAvailableProviders(file);
775       if (newProviders.length == 0) {
776         return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
777       }
778
779       builders = new AsyncFileEditorProvider.Builder[newProviders.length];
780       for (int i = 0; i < newProviders.length; i++) {
781         try {
782           final FileEditorProvider provider = newProviders[i];
783           LOG.assertTrue(provider != null, "Provider for file "+file+" is null. All providers: "+Arrays.asList(newProviders));
784           LOG.assertTrue(provider.accept(myProject, file), "Provider " + provider + " doesn't accept file " + file);
785           if (provider instanceof AsyncFileEditorProvider) {
786             builders[i] = ((AsyncFileEditorProvider)provider).createEditorAsync(myProject, file);
787           }
788         }
789         catch (Exception e) {
790           LOG.error(e);
791         }
792         catch (AssertionError e) {
793           LOG.error(e);
794         }
795       }
796     }
797     else {
798       newProviders = null;
799       builders = null;
800     }
801     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
802       @Override
803       public void run() {
804         if (myProject.isDisposed() || !file.isValid()) {
805           return;
806         }
807         compositeRef.set(window.findFileComposite(file));
808         boolean newEditor = compositeRef.isNull();
809         if (newEditor) {
810           clearWindowIfNeeded(window);
811
812           getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(FileEditorManagerImpl.this, file);
813
814           FileEditor[] newEditors = new FileEditor[newProviders.length];
815           for (int i = 0; i < newProviders.length; i++) {
816             try {
817               final FileEditorProvider provider = newProviders[i];
818               final FileEditor editor = builders[i] == null ? provider.createEditor(myProject, file) : builders[i].build();
819               LOG.assertTrue(editor.isValid());
820               newEditors[i] = editor;
821               // Register PropertyChangeListener into editor
822               editor.addPropertyChangeListener(myEditorPropertyChangeListener);
823               editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider));
824             }
825             catch (Exception e) {
826               LOG.error(e);
827             }
828             catch (AssertionError e) {
829               LOG.error(e);
830             }
831           }
832
833           // Now we have to create EditorComposite and insert it into the TabbedEditorComponent.
834           // After that we have to select opened editor.
835           EditorWithProviderComposite composite = new EditorWithProviderComposite(file, newEditors, newProviders, FileEditorManagerImpl.this);
836
837           if (index >= 0) {
838             composite.getFile().putUserData(EditorWindow.INITIAL_INDEX_KEY, index);
839           }
840
841           compositeRef.set(composite);
842         }
843
844         final EditorWithProviderComposite composite = compositeRef.get();
845         FileEditor[] editors = composite.getEditors();
846         FileEditorProvider[] providers = composite.getProviders();
847
848         window.setEditor(composite, current, focusEditor);
849
850         for (int i = 0; i < editors.length; i++) {
851           restoreEditorState(file, providers[i], editors[i], entry, newEditor);
852         }
853
854         // Restore selected editor
855         final FileEditorProvider selectedProvider;
856         if (entry == null) {
857           selectedProvider = ((FileEditorProviderManagerImpl)FileEditorProviderManager.getInstance())
858             .getSelectedFileEditorProvider(myEditorHistoryManager, file, providers);
859         }
860         else {
861           selectedProvider = entry.mySelectedProvider;
862         }
863         if (selectedProvider != null) {
864           for (int i = editors.length - 1; i >= 0; i--) {
865             final FileEditorProvider provider = providers[i];
866             if (provider.equals(selectedProvider)) {
867               composite.setSelectedEditor(i);
868               break;
869             }
870           }
871         }
872
873         // Notify editors about selection changes
874         window.getOwner().setCurrentWindow(window, focusEditor);
875         window.getOwner().afterFileOpen(file);
876         addSelectionRecord(file, window);
877
878         composite.getSelectedEditor().selectNotify();
879
880         // Transfer focus into editor
881         if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) {
882           if (focusEditor) {
883             //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer);
884             window.setAsCurrentWindow(true);
885             ToolWindowManager.getInstance(myProject).activateEditorComponent();
886             IdeFocusManager.getInstance(myProject).toFront(window.getOwner());
887           }
888         }
889
890         if (newEditor) {
891           if (window.isShowing()) {
892             window.setPaintBlocked(true);
893           }
894           notifyPublisher(new Runnable() {
895             @Override
896             public void run() {
897               window.setPaintBlocked(false);
898               if (isFileOpen(file)) {
899                 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER)
900                   .fileOpened(FileEditorManagerImpl.this, file);
901               }
902             }
903           });
904         }
905
906         //[jeka] this is a hack to support back-forward navigation
907         // previously here was incorrect call to fireSelectionChanged() with a side-effect
908         ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged();
909
910         // Update frame and tab title
911         updateFileName(file);
912
913         // Make back/forward work
914         IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
915
916         if (pin != null) {
917           window.setFilePinned(file, pin);
918         }
919       }
920     });
921     return Pair.create(compositeRef.get().getEditors(), compositeRef.get().getProviders());
922   }
923
924   private static void clearWindowIfNeeded(EditorWindow window) {
925     if (UISettings.getInstance().EDITOR_TAB_PLACEMENT == UISettings.TABS_NONE || UISettings.getInstance().PRESENTATION_MODE) {
926       for (EditorWithProviderComposite composite : window.getEditors()) {
927         Disposer.dispose(composite);
928       }
929     }
930   }
931
932   private void restoreEditorState(VirtualFile file,
933                                   FileEditorProvider provider,
934                                   final FileEditor editor,
935                                   HistoryEntry entry,
936                                   boolean newEditor) {
937     FileEditorState state = null;
938     if (entry != null) {
939       state = entry.getState(provider);
940     }
941     if (state == null && newEditor) {
942       // We have to try to get state from the history only in case
943       // if editor is not opened. Otherwise history entry might have a state
944       // out of sync with the current editor state.
945       state = myEditorHistoryManager.getState(file, provider);
946     }
947     if (state != null) {
948       if (!isDumbAware(editor)) {
949         final FileEditorState finalState = state;
950         DumbService.getInstance(getProject()).runWhenSmart(new Runnable() {
951           @Override
952           public void run() {
953             editor.setState(finalState);
954           }
955         });
956       }
957       else {
958         editor.setState(state);
959       }
960     }
961   }
962
963   private FileEditorProvider[] getAvailableProviders(VirtualFile file) {
964     final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
965     FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
966     if (DumbService.getInstance(myProject).isDumb()) {
967       final List<FileEditorProvider> dumbAware = ContainerUtil.findAll(providers, new Condition<FileEditorProvider>() {
968         @Override
969         public boolean value(FileEditorProvider fileEditorProvider) {
970           return DumbService.isDumbAware(fileEditorProvider);
971         }
972       });
973       providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]);
974     }
975     return providers;
976   }
977
978   @NotNull
979   @Override
980   public ActionCallback notifyPublisher(@NotNull final Runnable runnable) {
981     final IdeFocusManager focusManager = IdeFocusManager.getInstance(myProject);
982     final ActionCallback done = new ActionCallback();
983     return myBusyObject.execute(new ActiveRunnable() {
984       @NotNull
985       @Override
986       public ActionCallback run() {
987         focusManager.doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) {
988           @Override
989           public void run() {
990             runnable.run();
991             done.setDone();
992           }
993         });
994         return done;
995       }
996     });
997   }
998
999   @Override
1000   public void setSelectedEditor(@NotNull VirtualFile file, String fileEditorProviderId) {
1001     EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1002     if (composite == null) {
1003       final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1004
1005       if (composites.isEmpty()) return;
1006       composite = composites.get(0);
1007     }
1008
1009     final FileEditorProvider[] editorProviders = composite.getProviders();
1010     final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond();
1011
1012     for (int i = 0; i < editorProviders.length; i++) {
1013       if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) {
1014         composite.setSelectedEditor(i);
1015         composite.getSelectedEditor().selectNotify();
1016       }
1017     }
1018   }
1019
1020
1021   @Nullable
1022   EditorWithProviderComposite newEditorComposite(final VirtualFile file) {
1023     if (file == null) {
1024       return null;
1025     }
1026
1027     final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
1028     final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
1029     final FileEditor[] editors = new FileEditor[providers.length];
1030     for (int i = 0; i < providers.length; i++) {
1031       final FileEditorProvider provider = providers[i];
1032       LOG.assertTrue(provider != null);
1033       LOG.assertTrue(provider.accept(myProject, file));
1034       final FileEditor editor = provider.createEditor(myProject, file);
1035       editors[i] = editor;
1036       LOG.assertTrue(editor.isValid());
1037       editor.addPropertyChangeListener(myEditorPropertyChangeListener);
1038     }
1039
1040     final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this);
1041     final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
1042     for (int i = 0; i < editors.length; i++) {
1043       final FileEditor editor = editors[i];
1044       if (editor instanceof TextEditor) {
1045         // hack!!!
1046         // This code prevents "jumping" on next repaint.
1047         //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
1048       }
1049
1050       final FileEditorProvider provider = providers[i];
1051
1052 // Restore myEditor state
1053       FileEditorState state = editorHistoryManager.getState(file, provider);
1054       if (state != null) {
1055         editor.setState(state);
1056       }
1057     }
1058     return newComposite;
1059   }
1060
1061   @Override
1062   @NotNull
1063   public List<FileEditor> openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
1064     assertDispatchThread();
1065     if (descriptor.getFile() instanceof VirtualFileWindow) {
1066       VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile();
1067       int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset());
1068       OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset);
1069       realDescriptor.setUseCurrentWindow(descriptor.isUseCurrentWindow());
1070       return openEditor(realDescriptor, focusEditor);
1071     }
1072
1073     final List<FileEditor> result = new SmartList<FileEditor>();
1074     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1075       @Override
1076       public void run() {
1077         VirtualFile file = descriptor.getFile();
1078         final FileEditor[] editors = openFile(file, focusEditor, !descriptor.isUseCurrentWindow());
1079         ContainerUtil.addAll(result, editors);
1080
1081         boolean navigated = false;
1082         for (final FileEditor editor : editors) {
1083           if (editor instanceof NavigatableFileEditor &&
1084               getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor
1085             navigated = navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor);
1086             if (navigated) break;
1087           }
1088         }
1089
1090         if (!navigated) {
1091           for (final FileEditor editor : editors) {
1092             if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors
1093               if (navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor)) {
1094                 break;
1095               }
1096             }
1097           }
1098         }
1099       }
1100     }, "", null);
1101
1102     return result;
1103   }
1104
1105   private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) {
1106     if (editor.canNavigateTo(descriptor)) {
1107       setSelectedEditor(editor);
1108       editor.navigateTo(descriptor);
1109       return true;
1110     }
1111
1112     return false;
1113   }
1114
1115   private void setSelectedEditor(final FileEditor editor) {
1116     final EditorWithProviderComposite composite = getEditorComposite(editor);
1117     if (composite == null) return;
1118
1119     final FileEditor[] editors = composite.getEditors();
1120     for (int i = 0; i < editors.length; i++) {
1121       final FileEditor each = editors[i];
1122       if (editor == each) {
1123         composite.setSelectedEditor(i);
1124         composite.getSelectedEditor().selectNotify();
1125         break;
1126       }
1127     }
1128   }
1129
1130   @Override
1131   @NotNull
1132   public Project getProject() {
1133     return myProject;
1134   }
1135
1136   @Override
1137   @Nullable
1138   public Editor openTextEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
1139     final Collection<FileEditor> fileEditors = openEditor(descriptor, focusEditor);
1140     for (FileEditor fileEditor : fileEditors) {
1141       if (fileEditor instanceof TextEditor) {
1142         setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId());
1143         Editor editor = ((TextEditor)fileEditor).getEditor();
1144         return getOpenedEditor(editor, focusEditor);
1145       }
1146     }
1147
1148     return null;
1149   }
1150
1151   protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) {
1152     return editor;
1153   }
1154
1155   @Override
1156   public Editor getSelectedTextEditor() {
1157     return getSelectedTextEditor(false);
1158   }
1159
1160   public Editor getSelectedTextEditor(boolean lockfree) {
1161     if (!lockfree) {
1162       assertDispatchThread();
1163     }
1164
1165     final EditorWindow currentWindow = lockfree ? getMainSplitters().getCurrentWindow() : getSplitters().getCurrentWindow();
1166     if (currentWindow != null) {
1167       final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor();
1168       if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) {
1169         return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor();
1170       }
1171     }
1172
1173     return null;
1174   }
1175
1176
1177
1178   @Override
1179   public boolean isFileOpen(@NotNull final VirtualFile file) {
1180     return !getEditorComposites(file).isEmpty();
1181   }
1182
1183   @Override
1184   @NotNull
1185   public VirtualFile[] getOpenFiles() {
1186     HashSet<VirtualFile> openFiles = new HashSet<VirtualFile>();
1187     for (EditorsSplitters each : getAllSplitters()) {
1188       openFiles.addAll(Arrays.asList(each.getOpenFiles()));
1189     }
1190
1191     return VfsUtilCore.toVirtualFileArray(openFiles);
1192   }
1193
1194   @Override
1195   @NotNull
1196   public VirtualFile[] getSelectedFiles() {
1197     HashSet<VirtualFile> selectedFiles = new HashSet<VirtualFile>();
1198     for (EditorsSplitters each : getAllSplitters()) {
1199       selectedFiles.addAll(Arrays.asList(each.getSelectedFiles()));
1200     }
1201
1202     return VfsUtilCore.toVirtualFileArray(selectedFiles);
1203   }
1204
1205   @Override
1206   @NotNull
1207   public FileEditor[] getSelectedEditors() {
1208     HashSet<FileEditor> selectedEditors = new HashSet<FileEditor>();
1209     for (EditorsSplitters each : getAllSplitters()) {
1210       selectedEditors.addAll(Arrays.asList(each.getSelectedEditors()));
1211     }
1212
1213     return selectedEditors.toArray(new FileEditor[selectedEditors.size()]);
1214   }
1215
1216   @Override
1217   @NotNull
1218   public EditorsSplitters getSplitters() {
1219     EditorsSplitters active = getActiveSplitters(true).getResult();
1220     return active == null ? getMainSplitters() : active;
1221   }
1222
1223   @Override
1224   @Nullable
1225   public FileEditor getSelectedEditor(@NotNull final VirtualFile file) {
1226     final Pair<FileEditor, FileEditorProvider> selectedEditorWithProvider = getSelectedEditorWithProvider(file);
1227     return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst();
1228   }
1229
1230
1231   @Override
1232   @Nullable
1233   public Pair<FileEditor, FileEditorProvider> getSelectedEditorWithProvider(@NotNull VirtualFile file) {
1234     if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
1235     final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1236     if (composite != null) {
1237       return composite.getSelectedEditorWithProvider();
1238     }
1239
1240     final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1241     return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider();
1242   }
1243
1244   @Override
1245   @NotNull
1246   public Pair<FileEditor[], FileEditorProvider[]> getEditorsWithProviders(@NotNull final VirtualFile file) {
1247     assertReadAccess();
1248
1249     final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1250     if (composite != null) {
1251       return Pair.create(composite.getEditors(), composite.getProviders());
1252     }
1253
1254     final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1255     if (!composites.isEmpty()) {
1256       return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders());
1257     }
1258     else {
1259       return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
1260     }
1261   }
1262
1263   @Override
1264   @NotNull
1265   public FileEditor[] getEditors(@NotNull VirtualFile file) {
1266     assertReadAccess();
1267     if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
1268
1269     final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1270     if (composite != null) {
1271       return composite.getEditors();
1272     }
1273
1274     final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1275     if (!composites.isEmpty()) {
1276       return composites.get(0).getEditors();
1277     }
1278     else {
1279       return EMPTY_EDITOR_ARRAY;
1280     }
1281   }
1282
1283   @NotNull
1284   @Override
1285   public FileEditor[] getAllEditors(@NotNull VirtualFile file) {
1286     List<EditorWithProviderComposite> editorComposites = getEditorComposites(file);
1287     if (editorComposites.isEmpty()) return EMPTY_EDITOR_ARRAY;
1288     List<FileEditor> editors = new ArrayList<FileEditor>();
1289     for (EditorWithProviderComposite composite : editorComposites) {
1290       ContainerUtil.addAll(editors, composite.getEditors());
1291     }
1292     return editors.toArray(new FileEditor[editors.size()]);
1293   }
1294
1295   @Nullable
1296   private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) {
1297     final EditorWindow editorWindow = getSplitters().getCurrentWindow();
1298     if (editorWindow != null) {
1299       return editorWindow.findFileComposite(virtualFile);
1300     }
1301     return null;
1302   }
1303
1304   @NotNull
1305   public List<EditorWithProviderComposite> getEditorComposites(final VirtualFile file) {
1306     ArrayList<EditorWithProviderComposite> result = new ArrayList<EditorWithProviderComposite>();
1307     Set<EditorsSplitters> all = getAllSplitters();
1308     for (EditorsSplitters each : all) {
1309       result.addAll(each.findEditorComposites(file));
1310     }
1311     return result;
1312   }
1313
1314   @Override
1315   @NotNull
1316   public FileEditor[] getAllEditors() {
1317     assertReadAccess();
1318     final ArrayList<FileEditor> result = new ArrayList<FileEditor>();
1319     final Set<EditorsSplitters> allSplitters = getAllSplitters();
1320     for (EditorsSplitters splitter : allSplitters) {
1321       final EditorWithProviderComposite[] editorsComposites = splitter.getEditorsComposites();
1322       for (EditorWithProviderComposite editorsComposite : editorsComposites) {
1323         final FileEditor[] editors = editorsComposite.getEditors();
1324         ContainerUtil.addAll(result, editors);
1325       }
1326     }
1327     return result.toArray(new FileEditor[result.size()]);
1328   }
1329
1330   @Override
1331   public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
1332     addTopComponent(editor, annotationComponent);
1333   }
1334
1335   @Override
1336   public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
1337     removeTopComponent(editor, annotationComponent);
1338   }
1339   
1340   @NotNull
1341   public List<JComponent> getTopComponents(@NotNull FileEditor editor) {
1342     final EditorComposite composite = getEditorComposite(editor);
1343     return composite != null ? composite.getTopComponents(editor) : Collections.<JComponent>emptyList();
1344   }
1345
1346   @Override
1347   public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1348     final EditorComposite composite = getEditorComposite(editor);
1349     if (composite != null) {
1350       composite.addTopComponent(editor, component);
1351     }
1352   }
1353
1354   @Override
1355   public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1356     final EditorComposite composite = getEditorComposite(editor);
1357     if (composite != null) {
1358       composite.removeTopComponent(editor, component);
1359     }
1360   }
1361
1362   @NotNull
1363   public List<JComponent> getBottomComponents(@NotNull FileEditor editor) {
1364     final EditorComposite composite = getEditorComposite(editor);
1365     return composite != null ? composite.getBottomComponents(editor) : Collections.<JComponent>emptyList();
1366   }
1367
1368   @Override
1369   public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1370     final EditorComposite composite = getEditorComposite(editor);
1371     if (composite != null) {
1372       composite.addBottomComponent(editor, component);
1373     }
1374   }
1375
1376   @Override
1377   public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1378     final EditorComposite composite = getEditorComposite(editor);
1379     if (composite != null) {
1380       composite.removeBottomComponent(editor, component);
1381     }
1382   }
1383
1384   private final MessageListenerList<FileEditorManagerListener> myListenerList;
1385
1386   @Override
1387   public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
1388     myListenerList.add(listener);
1389   }
1390
1391   @Override
1392   public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, @NotNull final Disposable parentDisposable) {
1393     myListenerList.add(listener, parentDisposable);
1394   }
1395
1396   @Override
1397   public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
1398     myListenerList.remove(listener);
1399   }
1400
1401 // ProjectComponent methods
1402
1403   @Override
1404   public void projectOpened() {
1405     //myFocusWatcher.install(myWindows.getComponent ());
1406     getMainSplitters().startListeningFocus();
1407
1408     MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
1409
1410     final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1411     if (fileStatusManager != null) {
1412       /**
1413        * Updates tabs colors
1414        */
1415       final MyFileStatusListener myFileStatusListener = new MyFileStatusListener();
1416       fileStatusManager.addFileStatusListener(myFileStatusListener, myProject);
1417     }
1418     connection.subscribe(FileTypeManager.TOPIC, new MyFileTypeListener());
1419     connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootsListener());
1420
1421     /**
1422      * Updates tabs names
1423      */
1424     final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener();
1425     VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject);
1426     /**
1427      * Extends/cuts number of opened tabs. Also updates location of tabs.
1428      */
1429     final MyUISettingsListener myUISettingsListener = new MyUISettingsListener();
1430     UISettings.getInstance().addUISettingsListener(myUISettingsListener, myProject);
1431
1432     StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
1433       @Override
1434       public void run() {
1435         if (myProject.isDisposed()) return;
1436         setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1437
1438         ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() {
1439           @Override
1440           public void run() {
1441             CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1442               @Override
1443               public void run() {
1444
1445                 LaterInvocator.invokeLater(new Runnable() {
1446                   @Override
1447                   public void run() {
1448                     long currentTime = System.nanoTime();
1449                     Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME);
1450                     if (startTime != null) {
1451                       LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms");
1452                       PluginManagerCore.dumpPluginClassStatistics();
1453                     }
1454                   }
1455                 }, myProject.getDisposed());
1456 // group 1
1457               }
1458             }, "", null);
1459           }
1460         });
1461       }
1462     });
1463   }
1464
1465   @Override
1466   public void projectClosed() {
1467     //myFocusWatcher.deinstall(myWindows.getComponent ());
1468     getMainSplitters().dispose();
1469
1470 // Dispose created editors. We do not use use closeEditor method because
1471 // it fires event and changes history.
1472     closeAllFiles();
1473   }
1474
1475 // BaseCompomemnt methods
1476
1477   @Override
1478   @NotNull
1479   public String getComponentName() {
1480     return FILE_EDITOR_MANAGER;
1481   }
1482
1483   @Override
1484   public void initComponent() {
1485
1486   }
1487
1488   @Override
1489   public void disposeComponent() { /* really do nothing */ }
1490
1491 //JDOMExternalizable methods
1492
1493   @Override
1494   public void writeExternal(final Element element) {
1495     getMainSplitters().writeExternal(element);
1496   }
1497
1498   @Override
1499   public void readExternal(final Element element) {
1500     getMainSplitters().readExternal(element);
1501   }
1502
1503   @Nullable
1504   private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) {
1505     for (EditorsSplitters splitters : getAllSplitters()) {
1506       final EditorWithProviderComposite[] editorsComposites = splitters.getEditorsComposites();
1507       for (int i = editorsComposites.length - 1; i >= 0; i--) {
1508         final EditorWithProviderComposite composite = editorsComposites[i];
1509         final FileEditor[] editors = composite.getEditors();
1510         for (int j = editors.length - 1; j >= 0; j--) {
1511           final FileEditor _editor = editors[j];
1512           LOG.assertTrue(_editor != null);
1513           if (editor.equals(_editor)) {
1514             return composite;
1515           }
1516         }
1517       }
1518     }
1519     return null;
1520   }
1521
1522 //======================= Misc =====================
1523
1524   private static void assertDispatchThread() {
1525     ApplicationManager.getApplication().assertIsDispatchThread();
1526   }
1527
1528   private static void assertReadAccess() {
1529     ApplicationManager.getApplication().assertReadAccessAllowed();
1530   }
1531
1532   public void fireSelectionChanged(final EditorComposite newSelectedComposite) {
1533     final Trinity<VirtualFile, FileEditor, FileEditorProvider> oldData = extract(myLastSelectedComposite.get());
1534     final Trinity<VirtualFile, FileEditor, FileEditorProvider> newData = extract(newSelectedComposite);
1535     myLastSelectedComposite = new WeakReference<EditorComposite>(newSelectedComposite);
1536     final boolean filesEqual = oldData.first == null ? newData.first == null : oldData.first.equals(newData.first);
1537     final boolean editorsEqual = oldData.second == null ? newData.second == null : oldData.second.equals(newData.second);
1538     if (!filesEqual || !editorsEqual) {
1539       if (oldData.first != null && newData.first != null) {
1540         for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) {
1541           VirtualFile associatedFile = finder.getAssociatedFileToOpen(myProject, oldData.first);
1542
1543           if (Comparing.equal(associatedFile, newData.first)) {
1544             return;
1545           }
1546         }
1547       }
1548
1549       final FileEditorManagerEvent event =
1550         new FileEditorManagerEvent(this, oldData.first, oldData.second, oldData.third, newData.first, newData.second, newData.third);
1551       final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
1552
1553       if (newData.first != null) {
1554         final JComponent component = newData.second.getComponent();
1555         final EditorWindowHolder holder = UIUtil.getParentOfType(EditorWindowHolder.class, component);
1556         if (holder != null) {
1557           addSelectionRecord(newData.first, holder.getEditorWindow());
1558         }
1559       }
1560       notifyPublisher(new Runnable() {
1561         @Override
1562         public void run() {
1563           publisher.selectionChanged(event);
1564         }
1565       });
1566     }
1567   }
1568
1569   @NotNull
1570   private static Trinity<VirtualFile, FileEditor, FileEditorProvider> extract(@Nullable EditorComposite composite) {
1571     final VirtualFile file;
1572     final FileEditor editor;
1573     final FileEditorProvider provider;
1574     if (composite == null || composite.isDisposed()) {
1575       file = null;
1576       editor = null;
1577       provider = null;
1578     }
1579     else {
1580       file = composite.getFile();
1581       final Pair<FileEditor, FileEditorProvider> pair = composite.getSelectedEditorWithProvider();
1582       editor = pair.first;
1583       provider = pair.second;
1584     }
1585     return new Trinity<VirtualFile, FileEditor, FileEditorProvider>(file, editor, provider);
1586   }
1587
1588   @Override
1589   public boolean isChanged(@NotNull final EditorComposite editor) {
1590     final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1591     if (fileStatusManager != null) {
1592       VirtualFile file = editor.getFile();
1593       FileStatus status = fileStatusManager.getStatus(file);
1594       if (status == FileStatus.UNKNOWN && !file.isWritable()) {
1595         return false;
1596       }
1597       if (!status.equals(FileStatus.NOT_CHANGED)) {
1598         return true;
1599       }
1600     }
1601     return false;
1602   }
1603
1604   public void disposeComposite(@NotNull EditorWithProviderComposite editor) {
1605     if (getAllEditors().length == 0) {
1606       setCurrentWindow(null);
1607     }
1608
1609     if (editor.equals(getLastSelected())) {
1610       editor.getSelectedEditor().deselectNotify();
1611       getSplitters().setCurrentWindow(null, false);
1612     }
1613
1614     final FileEditor[] editors = editor.getEditors();
1615     final FileEditorProvider[] providers = editor.getProviders();
1616
1617     final FileEditor selectedEditor = editor.getSelectedEditor();
1618     for (int i = editors.length - 1; i >= 0; i--) {
1619       final FileEditor editor1 = editors[i];
1620       final FileEditorProvider provider = providers[i];
1621       if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event)
1622         if (selectedEditor.equals(editor1)) {
1623           editor1.deselectNotify();
1624         }
1625       }
1626       editor1.removePropertyChangeListener(myEditorPropertyChangeListener);
1627       provider.disposeEditor(editor1);
1628     }
1629
1630     Disposer.dispose(editor);
1631   }
1632
1633   @Nullable
1634   EditorComposite getLastSelected() {
1635     final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow();
1636     if (currentWindow != null) {
1637       return currentWindow.getSelectedEditor();
1638     }
1639     return null;
1640   }
1641
1642   /**
1643    * @param splitters - taken getAllSplitters() value if parameter is null
1644    */
1645   public void runChange(@NotNull FileEditorManagerChange change, @Nullable EditorsSplitters splitters) {
1646     Set<EditorsSplitters> target = new HashSet<EditorsSplitters>();
1647     if (splitters == null) {
1648       target.addAll(getAllSplitters());
1649     } else {
1650       target.add(splitters);
1651     }
1652
1653     for (EditorsSplitters each : target) {
1654       each.myInsideChange++;
1655       try {
1656         change.run(each);
1657       }
1658       finally {
1659         each.myInsideChange--;
1660       }
1661     }
1662   }
1663
1664   //================== Listeners =====================
1665
1666   /**
1667    * Closes deleted files. Closes file which are in the deleted directories.
1668    */
1669   private final class MyVirtualFileListener extends VirtualFileAdapter {
1670     @Override
1671     public void beforeFileDeletion(@NotNull VirtualFileEvent e) {
1672       assertDispatchThread();
1673
1674       boolean moveFocus = moveFocusOnDelete();
1675
1676       final VirtualFile file = e.getFile();
1677       final VirtualFile[] openFiles = getOpenFiles();
1678       for (int i = openFiles.length - 1; i >= 0; i--) {
1679         if (VfsUtilCore.isAncestor(file, openFiles[i], false)) {
1680           closeFile(openFiles[i], moveFocus, true);
1681         }
1682       }
1683     }
1684
1685     @Override
1686     public void propertyChanged(@NotNull VirtualFilePropertyEvent e) {
1687       if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) {
1688         assertDispatchThread();
1689         final VirtualFile file = e.getFile();
1690         if (isFileOpen(file)) {
1691           updateFileName(file);
1692           updateFileIcon(file); // file type can change after renaming
1693           updateFileBackgroundColor(file);
1694         }
1695       }
1696       else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) {
1697         // TODO: message bus?
1698         updateIconAndStatusBar(e);
1699       }
1700     }
1701
1702     private void updateIconAndStatusBar(final VirtualFilePropertyEvent e) {
1703       assertDispatchThread();
1704       final VirtualFile file = e.getFile();
1705       if (isFileOpen(file)) {
1706         updateFileIcon(file);
1707         if (file.equals(getSelectedFiles()[0])) { // update "write" status
1708           final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1709           assert statusBar != null;
1710           statusBar.updateWidgets();
1711         }
1712       }
1713     }
1714
1715     @Override
1716     public void fileMoved(@NotNull VirtualFileMoveEvent e) {
1717       final VirtualFile file = e.getFile();
1718       final VirtualFile[] openFiles = getOpenFiles();
1719       for (final VirtualFile openFile : openFiles) {
1720         if (VfsUtilCore.isAncestor(file, openFile, false)) {
1721           updateFileName(openFile);
1722           updateFileBackgroundColor(openFile);
1723         }
1724       }
1725     }
1726   }
1727
1728   private static boolean moveFocusOnDelete() {
1729     final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
1730     if (window != null) {
1731       final Component component = FocusTrackback.getFocusFor(window);
1732       if (component != null) {
1733         return component instanceof EditorComponentImpl;
1734       }
1735       return window instanceof IdeFrameImpl;
1736     }
1737     return true;
1738   }
1739
1740   @Override
1741   public boolean isInsideChange() {
1742     return getSplitters().isInsideChange();
1743   }
1744
1745   private final class MyEditorPropertyChangeListener implements PropertyChangeListener {
1746     @Override
1747     public void propertyChange(@NotNull final PropertyChangeEvent e) {
1748       assertDispatchThread();
1749
1750       final String propertyName = e.getPropertyName();
1751       if (FileEditor.PROP_MODIFIED.equals(propertyName)) {
1752         final FileEditor editor = (FileEditor)e.getSource();
1753         final EditorComposite composite = getEditorComposite(editor);
1754         if (composite != null) {
1755           updateFileIcon(composite.getFile());
1756         }
1757       }
1758       else if (FileEditor.PROP_VALID.equals(propertyName)) {
1759         final boolean valid = ((Boolean)e.getNewValue()).booleanValue();
1760         if (!valid) {
1761           final FileEditor editor = (FileEditor)e.getSource();
1762           LOG.assertTrue(editor != null);
1763           final EditorComposite composite = getEditorComposite(editor);
1764           if (composite != null) {
1765             closeFile(composite.getFile());
1766           }
1767         }
1768       }
1769
1770     }
1771   }
1772
1773
1774   /**
1775    * Gets events from VCS and updates color of myEditor tabs
1776    */
1777   private final class MyFileStatusListener implements FileStatusListener {
1778     @Override
1779     public void fileStatusesChanged() { // update color of all open files
1780       assertDispatchThread();
1781       LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()");
1782       final VirtualFile[] openFiles = getOpenFiles();
1783       for (int i = openFiles.length - 1; i >= 0; i--) {
1784         final VirtualFile file = openFiles[i];
1785         LOG.assertTrue(file != null);
1786         ApplicationManager.getApplication().invokeLater(new Runnable() {
1787           @Override
1788           public void run() {
1789             if (LOG.isDebugEnabled()) {
1790               LOG.debug("updating file status in tab for " + file.getPath());
1791             }
1792             updateFileStatus(file);
1793           }
1794         }, ModalityState.NON_MODAL, myProject.getDisposed());
1795       }
1796     }
1797
1798     @Override
1799     public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary)
1800       assertDispatchThread();
1801       if (isFileOpen(file)) {
1802         updateFileStatus(file);
1803       }
1804     }
1805
1806     private void updateFileStatus(final VirtualFile file) {
1807       updateFileColor(file);
1808       updateFileIcon(file);
1809     }
1810   }
1811
1812   /**
1813    * Gets events from FileTypeManager and updates icons on tabs
1814    */
1815   private final class MyFileTypeListener extends FileTypeListener.Adapter {
1816     @Override
1817     public void fileTypesChanged(@NotNull final FileTypeEvent event) {
1818       assertDispatchThread();
1819       final VirtualFile[] openFiles = getOpenFiles();
1820       for (int i = openFiles.length - 1; i >= 0; i--) {
1821         final VirtualFile file = openFiles[i];
1822         LOG.assertTrue(file != null);
1823         updateFileIcon(file);
1824       }
1825     }
1826   }
1827
1828   private class MyRootsListener extends ModuleRootAdapter {
1829     @Override
1830     public void rootsChanged(ModuleRootEvent event) {
1831       EditorFileSwapper[] swappers = Extensions.getExtensions(EditorFileSwapper.EP_NAME);
1832
1833       for (EditorWindow eachWindow : getWindows()) {
1834         EditorWithProviderComposite selected = eachWindow.getSelectedEditor();
1835         EditorWithProviderComposite[] editors = eachWindow.getEditors();
1836         for (int i = 0; i < editors.length; i++) {
1837           EditorWithProviderComposite editor = editors[i];
1838           VirtualFile file = editor.getFile();
1839           if (!file.isValid()) continue;
1840
1841           Pair<VirtualFile, Integer> newFilePair = null;
1842
1843           for (EditorFileSwapper each : swappers) {
1844             newFilePair = each.getFileToSwapTo(myProject, editor);
1845             if (newFilePair != null) break;
1846           }
1847
1848           if (newFilePair == null) continue;
1849
1850           VirtualFile newFile = newFilePair.first;
1851           if (newFile == null) continue;
1852
1853           // already open
1854           if (eachWindow.findFileIndex(newFile) != -1) continue;
1855
1856           try {
1857             newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, i);
1858             Pair<FileEditor[], FileEditorProvider[]> pair = openFileImpl2(eachWindow, newFile, editor == selected);
1859
1860             if (newFilePair.second != null) {
1861               TextEditorImpl openedEditor = EditorFileSwapper.findSinglePsiAwareEditor(pair.first);
1862               if (openedEditor != null) {
1863                 openedEditor.getEditor().getCaretModel().moveToOffset(newFilePair.second);
1864                 openedEditor.getEditor().getScrollingModel().scrollToCaret(ScrollType.CENTER);
1865               }
1866             }
1867           }
1868           finally {
1869             newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null);
1870           }
1871           closeFile(file, eachWindow);
1872         }
1873       }
1874     }
1875   }
1876
1877   /**
1878    * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT
1879    * and EDITOR_TAB_LIMIT, etc values.
1880    */
1881   private final class MyUISettingsListener implements UISettingsListener {
1882     @Override
1883     public void uiSettingsChanged(final UISettings source) {
1884       assertDispatchThread();
1885       setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE);
1886
1887       for (EditorsSplitters each : getAllSplitters()) {
1888         each.setTabsPlacement(source.EDITOR_TAB_PLACEMENT);
1889         each.trimToSize(source.EDITOR_TAB_LIMIT);
1890
1891         // Tab layout policy
1892         if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) {
1893           each.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
1894         }
1895         else {
1896           each.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
1897         }
1898       }
1899
1900       // "Mark modified files with asterisk"
1901       final VirtualFile[] openFiles = getOpenFiles();
1902       for (int i = openFiles.length - 1; i >= 0; i--) {
1903         final VirtualFile file = openFiles[i];
1904         updateFileIcon(file);
1905         updateFileName(file);
1906         updateFileBackgroundColor(file);
1907       }
1908     }
1909   }
1910
1911   @Override
1912   public void closeAllFiles() {
1913     final VirtualFile[] openFiles = getSplitters().getOpenFiles();
1914     for (VirtualFile openFile : openFiles) {
1915       closeFile(openFile);
1916     }
1917   }
1918
1919   @Override
1920   @NotNull
1921   public VirtualFile[] getSiblings(@NotNull VirtualFile file) {
1922     return getOpenFiles();
1923   }
1924
1925   protected void queueUpdateFile(final VirtualFile file) {
1926     myQueue.queue(new Update(file) {
1927       @Override
1928       public void run() {
1929         if (isFileOpen(file)) {
1930           updateFileIcon(file);
1931           updateFileColor(file);
1932           updateFileBackgroundColor(file);
1933         }
1934
1935       }
1936     });
1937   }
1938
1939   @Override
1940   public EditorsSplitters getSplittersFor(Component c) {
1941     EditorsSplitters splitters = null;
1942     DockContainer dockContainer = myDockManager.getContainerFor(c);
1943     if (dockContainer instanceof DockableEditorTabbedContainer) {
1944       splitters = ((DockableEditorTabbedContainer)dockContainer).getSplitters();
1945     }
1946
1947     if (splitters == null) {
1948       splitters = getMainSplitters();
1949     }
1950
1951     return splitters;
1952   }
1953
1954   public List<Pair<VirtualFile, EditorWindow>> getSelectionHistory() {
1955     List<Pair<VirtualFile, EditorWindow>> copy = new ArrayList<Pair<VirtualFile, EditorWindow>>();
1956     for (Pair<VirtualFile, EditorWindow> pair : mySelectionHistory) {
1957       if (pair.second.getFiles().length == 0) {
1958         final EditorWindow[] windows = pair.second.getOwner().getWindows();
1959         if (windows.length > 0 && windows[0] != null && windows[0].getFiles().length > 0) {
1960           final Pair<VirtualFile, EditorWindow> p = Pair.create(pair.first, windows[0]);
1961           if (!copy.contains(p)) {
1962             copy.add(p);
1963           }
1964         }
1965       } else {
1966         if (!copy.contains(pair)) {
1967           copy.add(pair);
1968         }
1969       }
1970     }
1971     mySelectionHistory.clear();
1972     mySelectionHistory.addAll(copy);
1973     return mySelectionHistory;
1974   }
1975
1976   public void addSelectionRecord(VirtualFile file, EditorWindow window) {
1977     final Pair<VirtualFile, EditorWindow> record = Pair.create(file, window);
1978     mySelectionHistory.remove(record);
1979     mySelectionHistory.add(0, record);
1980   }
1981
1982   public void removeSelectionRecord(VirtualFile file, EditorWindow window) {
1983     mySelectionHistory.remove(Pair.create(file, window));
1984   }
1985
1986   @NotNull
1987   @Override
1988   public ActionCallback getReady(@NotNull Object requestor) {
1989     return myBusyObject.getReady(requestor);
1990   }
1991 }