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