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