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