2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.fileEditor.impl;
18 import com.intellij.ProjectTopics;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.plugins.PluginManagerCore;
21 import com.intellij.ide.ui.UISettings;
22 import com.intellij.ide.ui.UISettingsListener;
23 import com.intellij.injected.editor.VirtualFileWindow;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.application.ex.ApplicationManagerEx;
28 import com.intellij.openapi.command.CommandProcessor;
29 import com.intellij.openapi.components.ProjectComponent;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.Editor;
32 import com.intellij.openapi.editor.ScrollType;
33 import com.intellij.openapi.editor.impl.EditorComponentImpl;
34 import com.intellij.openapi.extensions.Extensions;
35 import com.intellij.openapi.fileEditor.*;
36 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
37 import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
38 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
39 import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
40 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
41 import com.intellij.openapi.fileTypes.FileTypeEvent;
42 import com.intellij.openapi.fileTypes.FileTypeListener;
43 import com.intellij.openapi.fileTypes.FileTypeManager;
44 import com.intellij.openapi.preview.PreviewManager;
45 import com.intellij.openapi.progress.ProcessCanceledException;
46 import com.intellij.openapi.project.DumbAwareRunnable;
47 import com.intellij.openapi.project.DumbService;
48 import com.intellij.openapi.project.PossiblyDumbAware;
49 import com.intellij.openapi.project.Project;
50 import com.intellij.openapi.project.impl.ProjectImpl;
51 import com.intellij.openapi.roots.ModuleRootAdapter;
52 import com.intellij.openapi.roots.ModuleRootEvent;
53 import com.intellij.openapi.startup.StartupManager;
54 import com.intellij.openapi.util.*;
55 import com.intellij.openapi.util.io.FileUtil;
56 import com.intellij.openapi.util.registry.Registry;
57 import com.intellij.openapi.vcs.FileStatus;
58 import com.intellij.openapi.vcs.FileStatusListener;
59 import com.intellij.openapi.vcs.FileStatusManager;
60 import com.intellij.openapi.vfs.*;
61 import com.intellij.openapi.wm.IdeFocusManager;
62 import com.intellij.openapi.wm.ToolWindowManager;
63 import com.intellij.openapi.wm.WindowManager;
64 import com.intellij.openapi.wm.ex.StatusBarEx;
65 import com.intellij.openapi.wm.impl.IdeFrameImpl;
66 import com.intellij.reference.SoftReference;
67 import com.intellij.ui.FocusTrackback;
68 import com.intellij.ui.docking.DockContainer;
69 import com.intellij.ui.docking.DockManager;
70 import com.intellij.ui.docking.impl.DockManagerImpl;
71 import com.intellij.ui.tabs.impl.JBTabsImpl;
72 import com.intellij.util.Function;
73 import com.intellij.util.SmartList;
74 import com.intellij.util.containers.ContainerUtil;
75 import com.intellij.util.messages.MessageBusConnection;
76 import com.intellij.util.messages.impl.MessageListenerList;
77 import com.intellij.util.ui.JBUI;
78 import com.intellij.util.ui.UIUtil;
79 import com.intellij.util.ui.update.MergingUpdateQueue;
80 import com.intellij.util.ui.update.Update;
81 import org.jdom.Element;
82 import org.jetbrains.annotations.NotNull;
83 import org.jetbrains.annotations.Nullable;
86 import javax.swing.border.Border;
88 import java.awt.event.KeyEvent;
89 import java.awt.event.MouseEvent;
90 import java.beans.PropertyChangeEvent;
91 import java.beans.PropertyChangeListener;
92 import java.lang.ref.Reference;
93 import java.lang.ref.WeakReference;
95 import java.util.List;
96 import java.util.concurrent.atomic.AtomicInteger;
99 * @author Anton Katilin
100 * @author Eugene Belyaev
101 * @author Vladimir Kondratyev
103 public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable {
104 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl");
105 private static final Key<Boolean> DUMB_AWARE = Key.create("DUMB_AWARE");
107 private static final FileEditor[] EMPTY_EDITOR_ARRAY = {};
108 private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {};
109 public static final Key<Boolean> CLOSING_TO_REOPEN = Key.create("CLOSING_TO_REOPEN");
110 public static final String FILE_EDITOR_MANAGER = "FileEditorManager";
112 private volatile JPanel myPanels;
113 private EditorsSplitters mySplitters;
114 private final Project myProject;
115 private final List<Pair<VirtualFile, EditorWindow>> mySelectionHistory = new ArrayList<Pair<VirtualFile, EditorWindow>>();
116 private Reference<EditorComposite> myLastSelectedComposite = new WeakReference<EditorComposite>(null);
118 private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true,
119 MergingUpdateQueue.ANY_COMPONENT);
121 private final BusyObject.Impl.Simple myBusyObject = new BusyObject.Impl.Simple();
124 * Removes invalid myEditor and updates "modified" status.
126 private final PropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener();
127 private final DockManager myDockManager;
128 private DockableEditorContainerFactory myContentFactory;
129 private final EditorHistoryManager myEditorHistoryManager;
130 private static final AtomicInteger ourOpenFilesSetModificationCount = new AtomicInteger();
132 public static final ModificationTracker OPEN_FILE_SET_MODIFICATION_COUNT = new ModificationTracker() {
134 public long getModificationCount() {
135 return ourOpenFilesSetModificationCount.get();
140 public FileEditorManagerImpl(final Project project, DockManager dockManager, EditorHistoryManager editorHistoryManager) {
141 /* ApplicationManager.getApplication().assertIsDispatchThread(); */
143 myDockManager = dockManager;
144 myEditorHistoryManager = editorHistoryManager;
146 new MessageListenerList<FileEditorManagerListener>(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER);
148 if (Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME).length > 0) {
149 myListenerList.add(new FileEditorManagerAdapter() {
151 public void selectionChanged(@NotNull FileEditorManagerEvent event) {
152 EditorsSplitters splitters = getSplitters();
153 openAssociatedFile(event.getNewFile(), splitters.getCurrentWindow(), splitters);
158 myQueue.setTrackUiActivity(true);
160 project.getMessageBus().connect().subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
162 public void enteredDumbMode() {
165 public void exitDumbMode() {
166 // can happen under write action, so postpone to avoid deadlock on FileEditorProviderManager.getProviders()
167 ApplicationManager.getApplication().invokeLater(new Runnable() {
170 if (!project.isDisposed())
171 dumbModeFinished(project);
178 private void dumbModeFinished(Project project) {
179 VirtualFile[] files = getOpenFiles();
180 for (VirtualFile file : files) {
181 Set<FileEditorProvider> providers = new HashSet<FileEditorProvider>();
182 List<EditorWithProviderComposite> composites = getEditorComposites(file);
183 for (EditorWithProviderComposite composite : composites) {
184 providers.addAll(Arrays.asList(composite.getProviders()));
186 FileEditorProvider[] newProviders = FileEditorProviderManager.getInstance().getProviders(project, file);
187 if (newProviders.length > providers.size()) {
188 List<FileEditorProvider> toOpen = new ArrayList<FileEditorProvider>(Arrays.asList(newProviders));
189 toOpen.removeAll(providers);
190 // need to open additional non dumb-aware editors
191 for (EditorWithProviderComposite composite : composites) {
192 for (FileEditorProvider provider : toOpen) {
193 FileEditor editor = provider.createEditor(myProject, file);
194 composite.addEditor(editor, provider);
201 public void initDockableContentFactory() {
202 if (myContentFactory != null) return;
204 myContentFactory = new DockableEditorContainerFactory(myProject, this, myDockManager);
205 myDockManager.register(DockableEditorContainerFactory.TYPE, myContentFactory);
206 Disposer.register(myProject, myContentFactory);
209 public static boolean isDumbAware(@NotNull FileEditor editor) {
210 return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE)) &&
211 (!(editor instanceof PossiblyDumbAware) || ((PossiblyDumbAware)editor).isDumbAware());
214 //-------------------------------------------------------------------------------
217 public JComponent getComponent() {
223 public EditorsSplitters getMainSplitters() {
230 public Set<EditorsSplitters> getAllSplitters() {
231 Set<EditorsSplitters> all = new LinkedHashSet<EditorsSplitters>();
232 all.add(getMainSplitters());
233 Set<DockContainer> dockContainers = myDockManager.getContainers();
234 for (DockContainer each : dockContainers) {
235 if (each instanceof DockableEditorTabbedContainer) {
236 all.add(((DockableEditorTabbedContainer)each).getSplitters());
240 return Collections.unmodifiableSet(all);
244 private AsyncResult<EditorsSplitters> getActiveSplitters(boolean syncUsage) {
245 final boolean async = Registry.is("ide.windowSystem.asyncSplitters") && !syncUsage;
247 final AsyncResult<EditorsSplitters> result = new AsyncResult<EditorsSplitters>();
248 final IdeFocusManager fm = IdeFocusManager.getInstance(myProject);
249 Runnable run = new Runnable() {
252 if (myProject.isDisposed()) {
253 result.setRejected();
257 Component focusOwner = fm.getFocusOwner();
258 if (focusOwner == null && !async) {
259 focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
262 if (focusOwner == null && !async) {
263 focusOwner = fm.getLastFocusedFor(fm.getLastFocusedFrame());
266 DockContainer container = myDockManager.getContainerFor(focusOwner);
267 if (container == null && !async) {
268 focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
269 container = myDockManager.getContainerFor(focusOwner);
272 if (container instanceof DockableEditorTabbedContainer) {
273 result.setDone(((DockableEditorTabbedContainer)container).getSplitters());
276 result.setDone(getMainSplitters());
282 fm.doWhenFocusSettlesDown(run);
285 UIUtil.invokeLaterIfNeeded(run);
291 private final Object myInitLock = new Object();
293 private void initUI() {
294 if (myPanels == null) {
295 synchronized (myInitLock) {
296 if (myPanels == null) {
297 final JPanel panel = new JPanel(new BorderLayout());
298 panel.setOpaque(false);
299 panel.setBorder(new MyBorder());
300 mySplitters = new EditorsSplitters(this, myDockManager, true);
301 Disposer.register(myProject, mySplitters);
302 panel.add(mySplitters, BorderLayout.CENTER);
309 private static class MyBorder implements Border {
311 public void paintBorder(@NotNull Component c, @NotNull Graphics g, int x, int y, int width, int height) {
312 if (UIUtil.isUnderAquaLookAndFeel()) {
313 g.setColor(JBTabsImpl.MAC_AQUA_BG_COLOR);
314 final Insets insets = getBorderInsets(c);
315 if (insets.top > 0) {
316 g.fillRect(x, y, width, height + insets.top);
323 public Insets getBorderInsets(Component c) {
324 return JBUI.emptyInsets();
328 public boolean isBorderOpaque() {
334 public JComponent getPreferredFocusedComponent() {
336 final EditorWindow window = getSplitters().getCurrentWindow();
337 if (window != null) {
338 final EditorWithProviderComposite editor = window.getSelectedEditor();
339 if (editor != null) {
340 return editor.getPreferredFocusedComponent();
346 //-------------------------------------------------------
349 * @return color of the <code>file</code> which corresponds to the
352 public Color getFileColor(@NotNull final VirtualFile file) {
353 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
354 Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : UIUtil.getLabelForeground();
355 if (statusColor == null) statusColor = UIUtil.getLabelForeground();
359 public boolean isProblem(@NotNull final VirtualFile file) {
364 public String getFileTooltipText(@NotNull VirtualFile file) {
365 return FileUtil.getLocationRelativeToUserHome(file.getPresentableUrl());
369 public void updateFilePresentation(@NotNull VirtualFile file) {
370 if (!isFileOpen(file)) return;
372 updateFileColor(file);
373 updateFileIcon(file);
374 updateFileName(file);
375 updateFileBackgroundColor(file);
379 * Updates tab color for the specified <code>file</code>. The <code>file</code>
380 * should be opened in the myEditor, otherwise the method throws an assertion.
382 private void updateFileColor(@NotNull VirtualFile file) {
383 Set<EditorsSplitters> all = getAllSplitters();
384 for (EditorsSplitters each : all) {
385 each.updateFileColor(file);
389 private void updateFileBackgroundColor(@NotNull VirtualFile file) {
390 Set<EditorsSplitters> all = getAllSplitters();
391 for (EditorsSplitters each : all) {
392 each.updateFileBackgroundColor(file);
397 * Updates tab icon for the specified <code>file</code>. The <code>file</code>
398 * should be opened in the myEditor, otherwise the method throws an assertion.
400 protected void updateFileIcon(@NotNull VirtualFile file) {
401 Set<EditorsSplitters> all = getAllSplitters();
402 for (EditorsSplitters each : all) {
403 each.updateFileIcon(file);
408 * Updates tab title and tab tool tip for the specified <code>file</code>
410 void updateFileName(@Nullable final 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())) {
415 public boolean isExpired() {
416 return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid());
421 Set<EditorsSplitters> all = getAllSplitters();
422 for (EditorsSplitters each : all) {
423 each.updateFileName(file);
429 //-------------------------------------------------------
433 public VirtualFile getFile(@NotNull final FileEditor editor) {
434 final EditorComposite editorComposite = getEditorComposite(editor);
435 if (editorComposite != null) {
436 return editorComposite.getFile();
442 public void unsplitWindow() {
443 final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow();
444 if (currentWindow != null) {
445 currentWindow.unsplit(true);
450 public void unsplitAllWindow() {
451 final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow();
452 if (currentWindow != null) {
453 currentWindow.unsplitAll();
458 public int getWindowSplitCount() {
459 return getActiveSplitters(true).getResult().getSplitCount();
463 public boolean hasSplitOrUndockedWindows() {
464 Set<EditorsSplitters> splitters = getAllSplitters();
465 if (splitters.size() > 1) return true;
466 return getWindowSplitCount() > 1;
471 public EditorWindow[] getWindows() {
472 List<EditorWindow> windows = new ArrayList<EditorWindow>();
473 Set<EditorsSplitters> all = getAllSplitters();
474 for (EditorsSplitters each : all) {
475 EditorWindow[] eachList = each.getWindows();
476 windows.addAll(Arrays.asList(eachList));
479 return windows.toArray(new EditorWindow[windows.size()]);
483 public EditorWindow getNextWindow(@NotNull final EditorWindow window) {
484 final EditorWindow[] windows = getSplitters().getOrderedWindows();
485 for (int i = 0; i != windows.length; ++i) {
486 if (windows[i].equals(window)) {
487 return windows[(i + 1) % windows.length];
490 LOG.error("Not window found");
495 public EditorWindow getPrevWindow(@NotNull final EditorWindow window) {
496 final EditorWindow[] windows = getSplitters().getOrderedWindows();
497 for (int i = 0; i != windows.length; ++i) {
498 if (windows[i].equals(window)) {
499 return windows[(i + windows.length - 1) % windows.length];
502 LOG.error("Not window found");
507 public void createSplitter(final int orientation, @Nullable final EditorWindow window) {
508 // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current'
509 if (window != null) {
510 window.split(orientation, true, null, false);
512 // otherwise we'll split the current window, if any
514 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
515 if (currentWindow != null) {
516 currentWindow.split(orientation, true, null, false);
522 public void changeSplitterOrientation() {
523 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
524 if (currentWindow != null) {
525 currentWindow.changeOrientation();
531 public void flipTabs() {
533 if (myTabs == null) {
534 myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT);
535 remove (mySplitters);
536 add (myTabs, BorderLayout.CENTER);
540 add (mySplitters, BorderLayout.CENTER);
545 myPanels.revalidate();
549 public boolean tabsMode() {
553 private void setTabsMode(final boolean mode) {
554 if (tabsMode() != mode) {
557 //LOG.assertTrue (tabsMode () == mode);
562 public boolean isInSplitter() {
563 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
564 return currentWindow != null && currentWindow.inSplitter();
568 public boolean hasOpenedFile() {
569 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
570 return currentWindow != null && currentWindow.getSelectedEditor() != null;
574 public VirtualFile getCurrentFile() {
575 return getActiveSplitters(true).getResult().getCurrentFile();
580 public AsyncResult<EditorWindow> getActiveWindow() {
581 return _getActiveWindow(false);
585 private AsyncResult<EditorWindow> _getActiveWindow(boolean now) {
586 return getActiveSplitters(now).subResult(new Function<EditorsSplitters, EditorWindow>() {
588 public EditorWindow fun(EditorsSplitters splitters) {
589 return splitters.getCurrentWindow();
595 public EditorWindow getCurrentWindow() {
596 return _getActiveWindow(true).getResult();
600 public void setCurrentWindow(final EditorWindow window) {
601 getActiveSplitters(true).getResult().setCurrentWindow(window, true);
604 public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window, final boolean transferFocus) {
605 assertDispatchThread();
606 ourOpenFilesSetModificationCount.incrementAndGet();
608 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
611 if (window.isFileOpen(file)) {
612 window.closeFile(file, true, transferFocus);
615 }, IdeBundle.message("command.close.active.editor"), null);
616 removeSelectionRecord(file, window);
620 public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) {
621 closeFile(file, window, true);
624 //============================= EditorManager methods ================================
627 public void closeFile(@NotNull final VirtualFile file) {
628 closeFile(file, true, false);
631 public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus, final boolean closeAllCopies) {
632 assertDispatchThread();
634 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
637 closeFileImpl(file, moveFocus, closeAllCopies);
642 private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus, boolean closeAllCopies) {
643 assertDispatchThread();
644 ourOpenFilesSetModificationCount.incrementAndGet();
645 runChange(new FileEditorManagerChange() {
647 public void run(EditorsSplitters splitters) {
648 splitters.closeFile(file, moveFocus);
650 }, closeAllCopies ? null : getActiveSplitters(true).getResult());
653 //-------------------------------------- Open File ----------------------------------------
657 public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull final VirtualFile file,
659 final boolean searchForSplitter) {
660 if (!file.isValid()) {
661 throw new IllegalArgumentException("file is not valid: " + file);
663 assertDispatchThread();
665 if (isOpenInNewWindow(EventQueue.getCurrentEvent())) {
666 return openFileInNewWindow(file);
670 EditorWindow wndToOpenIn = null;
671 if (searchForSplitter) {
672 Set<EditorsSplitters> all = getAllSplitters();
673 EditorsSplitters active = getActiveSplitters(true).getResult();
674 if (active.getCurrentWindow() != null && active.getCurrentWindow().isFileOpen(file)) {
675 wndToOpenIn = active.getCurrentWindow();
677 for (EditorsSplitters splitters : all) {
678 final EditorWindow window = splitters.getCurrentWindow();
679 if (window == null) continue;
681 if (window.isFileOpen(file)) {
682 wndToOpenIn = window;
689 wndToOpenIn = getSplitters().getCurrentWindow();
692 if (wndToOpenIn == null || !wndToOpenIn.isFileOpen(file)) {
693 Pair<FileEditor[], FileEditorProvider[]> previewResult =
694 PreviewManager.SERVICE.preview(myProject, FilePreviewPanelProvider.ID, file, focusEditor);
695 if (previewResult != null) {
696 return previewResult;
700 EditorsSplitters splitters = getSplitters();
702 if (wndToOpenIn == null) {
703 wndToOpenIn = splitters.getOrCreateCurrentWindow(file);
706 openAssociatedFile(file, wndToOpenIn, splitters);
707 return openFileImpl2(wndToOpenIn, file, focusEditor);
710 public Pair<FileEditor[], FileEditorProvider[]> openFileInNewWindow(@NotNull VirtualFile file) {
711 return ((DockManagerImpl)DockManager.getInstance(getProject())).createNewDockContainerFor(file, this);
714 private static boolean isOpenInNewWindow(AWTEvent event) {
715 // Shift was used while clicking
716 if (event instanceof MouseEvent && ((MouseEvent)event).isShiftDown()) {
721 return event instanceof KeyEvent
722 && ((KeyEvent)event).getKeyCode() == KeyEvent.VK_ENTER
723 && ((KeyEvent)event).isShiftDown();
726 private void openAssociatedFile(VirtualFile file, EditorWindow wndToOpenIn, @NotNull EditorsSplitters splitters) {
727 EditorWindow[] windows = splitters.getWindows();
729 if (file != null && windows.length == 2) {
730 for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) {
731 VirtualFile associatedFile = finder.getAssociatedFileToOpen(myProject, file);
733 if (associatedFile != null) {
734 EditorWindow currentWindow = splitters.getCurrentWindow();
735 int idx = windows[0] == wndToOpenIn ? 1 : 0;
736 openFileImpl2(windows[idx], associatedFile, false);
738 if (currentWindow != null) {
739 splitters.setCurrentWindow(currentWindow, false);
750 public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull VirtualFile file,
752 @NotNull EditorWindow window) {
753 if (!file.isValid()) {
754 throw new IllegalArgumentException("file is not valid: " + file);
756 assertDispatchThread();
758 return openFileImpl2(window, file, focusEditor);
762 public Pair<FileEditor[], FileEditorProvider[]> openFileImpl2(@NotNull final EditorWindow window,
763 @NotNull final VirtualFile file,
764 final boolean focusEditor) {
765 final Ref<Pair<FileEditor[], FileEditorProvider[]>> result = new Ref<Pair<FileEditor[], FileEditorProvider[]>>();
766 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
769 result.set(openFileImpl3(window, file, focusEditor, null, true));
776 * @param file to be opened. Unlike openFile method, file can be
777 * invalid. For example, all file were invalidate and they are being
778 * removed one by one. If we have removed one invalid file, then another
779 * invalid file become selected. That's why we do not require that
780 * passed file is valid.
781 * @param entry map between FileEditorProvider and FileEditorState. If this parameter
784 Pair<FileEditor[], FileEditorProvider[]> openFileImpl3(@NotNull final EditorWindow window,
785 @NotNull final VirtualFile file,
786 final boolean focusEditor,
787 @Nullable final HistoryEntry entry,
789 return openFileImpl4(window, file, entry, current, focusEditor, null, -1);
793 * This method can be invoked from background thread. Of course, UI for returned editors should be accessed from EDT in any case.
796 Pair<FileEditor[], FileEditorProvider[]> openFileImpl4(@NotNull final EditorWindow window,
797 @NotNull final VirtualFile file,
798 @Nullable final HistoryEntry entry,
799 final boolean current,
800 final boolean focusEditor,
803 assert ApplicationManager.getApplication().isDispatchThread() || !ApplicationManager.getApplication().isReadAccessAllowed() : "must not open files under read action since we are doing a lot of invokeAndWaits here";
805 final Ref<EditorWithProviderComposite> compositeRef = new Ref<EditorWithProviderComposite>();
807 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
810 compositeRef.set(window.findFileComposite(file));
814 final FileEditorProvider[] newProviders;
815 final AsyncFileEditorProvider.Builder[] builders;
816 if (compositeRef.isNull()) {
817 // File is not opened yet. In this case we have to create editors
818 // and select the created EditorComposite.
819 newProviders = FileEditorProviderManager.getInstance().getProviders(myProject, file);
820 if (newProviders.length == 0) {
821 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
824 builders = new AsyncFileEditorProvider.Builder[newProviders.length];
825 for (int i = 0; i < newProviders.length; i++) {
827 final FileEditorProvider provider = newProviders[i];
828 LOG.assertTrue(provider != null, "Provider for file "+file+" is null. All providers: "+Arrays.asList(newProviders));
829 builders[i] = ApplicationManager.getApplication().runReadAction(new Computable<AsyncFileEditorProvider.Builder>() {
831 public AsyncFileEditorProvider.Builder compute() {
832 if (myProject.isDisposed() || !file.isValid()) {
835 LOG.assertTrue(provider.accept(myProject, file), "Provider " + provider + " doesn't accept file " + file);
836 return provider instanceof AsyncFileEditorProvider ? ((AsyncFileEditorProvider)provider).createEditorAsync(myProject, file) : null;
840 catch (ProcessCanceledException e) {
843 catch (Exception e) {
846 catch (AssertionError e) {
855 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
858 if (myProject.isDisposed() || !file.isValid()) {
861 compositeRef.set(window.findFileComposite(file));
862 boolean newEditor = compositeRef.isNull();
864 clearWindowIfNeeded(window);
866 ourOpenFilesSetModificationCount.incrementAndGet();
867 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(FileEditorManagerImpl.this, file);
869 FileEditor[] newEditors = new FileEditor[newProviders.length];
870 for (int i = 0; i < newProviders.length; i++) {
872 final FileEditorProvider provider = newProviders[i];
873 final FileEditor editor = builders[i] == null ? provider.createEditor(myProject, file) : builders[i].build();
874 LOG.assertTrue(editor.isValid(), "Invalid editor created by provider " +
875 (provider == null ? null : provider.getClass().getName()));
876 newEditors[i] = editor;
877 // Register PropertyChangeListener into editor
878 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
879 editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider));
881 catch (Exception e) {
884 catch (AssertionError e) {
889 // Now we have to create EditorComposite and insert it into the TabbedEditorComponent.
890 // After that we have to select opened editor.
891 EditorWithProviderComposite composite = createComposite(file, newEditors, newProviders);
892 if (composite == null) return;
895 composite.getFile().putUserData(EditorWindow.INITIAL_INDEX_KEY, index);
898 compositeRef.set(composite);
901 final EditorWithProviderComposite composite = compositeRef.get();
902 FileEditor[] editors = composite.getEditors();
903 FileEditorProvider[] providers = composite.getProviders();
905 window.setEditor(composite, current, focusEditor);
907 for (int i = 0; i < editors.length; i++) {
908 restoreEditorState(file, providers[i], editors[i], entry, newEditor);
911 // Restore selected editor
912 final FileEditorProvider selectedProvider;
914 selectedProvider = ((FileEditorProviderManagerImpl)FileEditorProviderManager.getInstance())
915 .getSelectedFileEditorProvider(myEditorHistoryManager, file, providers);
918 selectedProvider = entry.getSelectedProvider();
920 if (selectedProvider != null) {
921 for (int i = editors.length - 1; i >= 0; i--) {
922 final FileEditorProvider provider = providers[i];
923 if (provider.equals(selectedProvider)) {
924 composite.setSelectedEditor(i);
930 // Notify editors about selection changes
931 window.getOwner().setCurrentWindow(window, focusEditor);
932 window.getOwner().afterFileOpen(file);
933 addSelectionRecord(file, window);
935 composite.getSelectedEditor().selectNotify();
937 // Transfer focus into editor
938 if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) {
940 //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer);
941 window.setAsCurrentWindow(true);
942 ToolWindowManager.getInstance(myProject).activateEditorComponent();
943 IdeFocusManager.getInstance(myProject).toFront(window.getOwner());
948 notifyPublisher(new Runnable() {
951 if (isFileOpen(file)) {
952 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER)
953 .fileOpened(FileEditorManagerImpl.this, file);
959 //[jeka] this is a hack to support back-forward navigation
960 // previously here was incorrect call to fireSelectionChanged() with a side-effect
961 ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged();
963 // Update frame and tab title
964 updateFileName(file);
966 // Make back/forward work
967 IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
970 window.setFilePinned(file, pin);
974 EditorWithProviderComposite composite = compositeRef.get();
975 return Pair.create(composite == null ? EMPTY_EDITOR_ARRAY : composite.getEditors(),
976 composite == null ? EMPTY_PROVIDER_ARRAY : composite.getProviders());
980 private EditorWithProviderComposite createComposite(@NotNull VirtualFile file,
981 @NotNull FileEditor[] editors, @NotNull FileEditorProvider[] providers) {
982 if (NullUtils.hasNull(editors) || NullUtils.hasNull(providers)) {
983 List<FileEditor> editorList = new ArrayList<FileEditor>(editors.length);
984 List<FileEditorProvider> providerList = new ArrayList<FileEditorProvider>(providers.length);
985 for (int i = 0; i < editors.length; i++) {
986 FileEditor editor = editors[i];
987 FileEditorProvider provider = providers[i];
988 if (editor != null && provider != null) {
989 editorList.add(editor);
990 providerList.add(provider);
993 if (editorList.isEmpty()) return null;
994 editors = editorList.toArray(new FileEditor[editorList.size()]);
995 providers = providerList.toArray(new FileEditorProvider[providerList.size()]);
997 return new EditorWithProviderComposite(file, editors, providers, this);
1000 private static void clearWindowIfNeeded(@NotNull EditorWindow window) {
1001 if (UISettings.getInstance().EDITOR_TAB_PLACEMENT == UISettings.TABS_NONE || UISettings.getInstance().PRESENTATION_MODE) {
1002 for (EditorWithProviderComposite composite : window.getEditors()) {
1003 Disposer.dispose(composite);
1008 private void restoreEditorState(@NotNull VirtualFile file,
1009 @NotNull FileEditorProvider provider,
1010 @NotNull final FileEditor editor,
1012 boolean newEditor) {
1013 FileEditorState state = null;
1014 if (entry != null) {
1015 state = entry.getState(provider);
1017 if (state == null && newEditor) {
1018 // We have to try to get state from the history only in case
1019 // if editor is not opened. Otherwise history entry might have a state
1020 // out of sync with the current editor state.
1021 state = myEditorHistoryManager.getState(file, provider);
1023 if (state != null) {
1024 if (!isDumbAware(editor)) {
1025 final FileEditorState finalState = state;
1026 DumbService.getInstance(getProject()).runWhenSmart(new Runnable() {
1029 editor.setState(finalState);
1034 editor.setState(state);
1041 public ActionCallback notifyPublisher(@NotNull final Runnable runnable) {
1042 final IdeFocusManager focusManager = IdeFocusManager.getInstance(myProject);
1043 final ActionCallback done = new ActionCallback();
1044 return myBusyObject.execute(new ActiveRunnable() {
1047 public ActionCallback run() {
1048 focusManager.doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) {
1061 public void setSelectedEditor(@NotNull VirtualFile file, @NotNull String fileEditorProviderId) {
1062 EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1063 if (composite == null) {
1064 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1066 if (composites.isEmpty()) return;
1067 composite = composites.get(0);
1070 final FileEditorProvider[] editorProviders = composite.getProviders();
1071 final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond();
1073 for (int i = 0; i < editorProviders.length; i++) {
1074 if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) {
1075 composite.setSelectedEditor(i);
1076 composite.getSelectedEditor().selectNotify();
1083 EditorWithProviderComposite newEditorComposite(final VirtualFile file) {
1088 final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
1089 final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
1090 final FileEditor[] editors = new FileEditor[providers.length];
1091 for (int i = 0; i < providers.length; i++) {
1092 final FileEditorProvider provider = providers[i];
1093 LOG.assertTrue(provider != null);
1094 LOG.assertTrue(provider.accept(myProject, file));
1095 final FileEditor editor = provider.createEditor(myProject, file);
1096 editors[i] = editor;
1097 LOG.assertTrue(editor.isValid());
1098 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
1101 final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this);
1102 final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
1103 for (int i = 0; i < editors.length; i++) {
1104 final FileEditor editor = editors[i];
1105 if (editor instanceof TextEditor) {
1107 // This code prevents "jumping" on next repaint.
1108 //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
1111 final FileEditorProvider provider = providers[i];
1113 // Restore myEditor state
1114 FileEditorState state = editorHistoryManager.getState(file, provider);
1115 if (state != null) {
1116 editor.setState(state);
1119 return newComposite;
1124 public List<FileEditor> openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
1125 assertDispatchThread();
1126 if (descriptor.getFile() instanceof VirtualFileWindow) {
1127 VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile();
1128 int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset());
1129 OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset);
1130 realDescriptor.setUseCurrentWindow(descriptor.isUseCurrentWindow());
1131 return openEditor(realDescriptor, focusEditor);
1134 final List<FileEditor> result = new SmartList<FileEditor>();
1135 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1138 VirtualFile file = descriptor.getFile();
1139 final FileEditor[] editors = openFile(file, focusEditor, !descriptor.isUseCurrentWindow());
1140 ContainerUtil.addAll(result, editors);
1142 boolean navigated = false;
1143 for (final FileEditor editor : editors) {
1144 if (editor instanceof NavigatableFileEditor &&
1145 getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor
1146 navigated = navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor);
1147 if (navigated) break;
1152 for (final FileEditor editor : editors) {
1153 if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors
1154 if (navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor)) {
1166 private boolean navigateAndSelectEditor(@NotNull NavigatableFileEditor editor, @NotNull OpenFileDescriptor descriptor) {
1167 if (editor.canNavigateTo(descriptor)) {
1168 setSelectedEditor(editor);
1169 editor.navigateTo(descriptor);
1176 private void setSelectedEditor(@NotNull FileEditor editor) {
1177 final EditorWithProviderComposite composite = getEditorComposite(editor);
1178 if (composite == null) return;
1180 final FileEditor[] editors = composite.getEditors();
1181 for (int i = 0; i < editors.length; i++) {
1182 final FileEditor each = editors[i];
1183 if (editor == each) {
1184 composite.setSelectedEditor(i);
1185 composite.getSelectedEditor().selectNotify();
1193 public Project getProject() {
1199 public Editor openTextEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
1200 final Collection<FileEditor> fileEditors = openEditor(descriptor, focusEditor);
1201 for (FileEditor fileEditor : fileEditors) {
1202 if (fileEditor instanceof TextEditor) {
1203 setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId());
1204 Editor editor = ((TextEditor)fileEditor).getEditor();
1205 return getOpenedEditor(editor, focusEditor);
1212 protected Editor getOpenedEditor(@NotNull Editor editor, final boolean focusEditor) {
1217 public Editor getSelectedTextEditor() {
1218 return getSelectedTextEditor(false);
1221 public Editor getSelectedTextEditor(boolean lockfree) {
1223 assertDispatchThread();
1226 final EditorWindow currentWindow = lockfree ? getMainSplitters().getCurrentWindow() : getSplitters().getCurrentWindow();
1227 if (currentWindow != null) {
1228 final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor();
1229 if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) {
1230 return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor();
1240 public boolean isFileOpen(@NotNull final VirtualFile file) {
1241 return !getEditorComposites(file).isEmpty();
1246 public VirtualFile[] getOpenFiles() {
1247 Set<VirtualFile> openFiles = new HashSet<VirtualFile>();
1248 for (EditorsSplitters each : getAllSplitters()) {
1249 openFiles.addAll(Arrays.asList(each.getOpenFiles()));
1252 return VfsUtilCore.toVirtualFileArray(openFiles);
1257 public VirtualFile[] getSelectedFiles() {
1258 Set<VirtualFile> selectedFiles = new HashSet<VirtualFile>();
1259 for (EditorsSplitters each : getAllSplitters()) {
1260 selectedFiles.addAll(Arrays.asList(each.getSelectedFiles()));
1263 return VfsUtilCore.toVirtualFileArray(selectedFiles);
1268 public FileEditor[] getSelectedEditors() {
1269 Set<FileEditor> selectedEditors = new HashSet<FileEditor>();
1270 for (EditorsSplitters each : getAllSplitters()) {
1271 selectedEditors.addAll(Arrays.asList(each.getSelectedEditors()));
1274 return selectedEditors.toArray(new FileEditor[selectedEditors.size()]);
1279 public EditorsSplitters getSplitters() {
1280 EditorsSplitters active = getActiveSplitters(true).getResult();
1281 return active == null ? getMainSplitters() : active;
1286 public FileEditor getSelectedEditor(@NotNull final VirtualFile file) {
1287 final Pair<FileEditor, FileEditorProvider> selectedEditorWithProvider = getSelectedEditorWithProvider(file);
1288 return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst();
1294 public Pair<FileEditor, FileEditorProvider> getSelectedEditorWithProvider(@NotNull VirtualFile file) {
1295 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
1296 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1297 if (composite != null) {
1298 return composite.getSelectedEditorWithProvider();
1301 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1302 return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider();
1307 public Pair<FileEditor[], FileEditorProvider[]> getEditorsWithProviders(@NotNull final VirtualFile file) {
1310 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1311 if (composite != null) {
1312 return Pair.create(composite.getEditors(), composite.getProviders());
1315 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1316 if (!composites.isEmpty()) {
1317 return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders());
1319 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
1324 public FileEditor[] getEditors(@NotNull VirtualFile file) {
1326 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
1328 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
1329 if (composite != null) {
1330 return composite.getEditors();
1333 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
1334 if (!composites.isEmpty()) {
1335 return composites.get(0).getEditors();
1337 return EMPTY_EDITOR_ARRAY;
1342 public FileEditor[] getAllEditors(@NotNull VirtualFile file) {
1343 List<EditorWithProviderComposite> editorComposites = getEditorComposites(file);
1344 if (editorComposites.isEmpty()) return EMPTY_EDITOR_ARRAY;
1345 List<FileEditor> editors = new ArrayList<FileEditor>();
1346 for (EditorWithProviderComposite composite : editorComposites) {
1347 ContainerUtil.addAll(editors, composite.getEditors());
1349 return editors.toArray(new FileEditor[editors.size()]);
1353 private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) {
1354 final EditorWindow editorWindow = getSplitters().getCurrentWindow();
1355 if (editorWindow != null) {
1356 return editorWindow.findFileComposite(virtualFile);
1362 private List<EditorWithProviderComposite> getEditorComposites(@NotNull VirtualFile file) {
1363 List<EditorWithProviderComposite> result = new ArrayList<EditorWithProviderComposite>();
1364 Set<EditorsSplitters> all = getAllSplitters();
1365 for (EditorsSplitters each : all) {
1366 result.addAll(each.findEditorComposites(file));
1373 public FileEditor[] getAllEditors() {
1375 List<FileEditor> result = new ArrayList<FileEditor>();
1376 final Set<EditorsSplitters> allSplitters = getAllSplitters();
1377 for (EditorsSplitters splitter : allSplitters) {
1378 final EditorWithProviderComposite[] editorsComposites = splitter.getEditorsComposites();
1379 for (EditorWithProviderComposite editorsComposite : editorsComposites) {
1380 final FileEditor[] editors = editorsComposite.getEditors();
1381 ContainerUtil.addAll(result, editors);
1384 return result.toArray(new FileEditor[result.size()]);
1388 public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
1389 addTopComponent(editor, annotationComponent);
1393 public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
1394 removeTopComponent(editor, annotationComponent);
1398 public List<JComponent> getTopComponents(@NotNull FileEditor editor) {
1399 final EditorComposite composite = getEditorComposite(editor);
1400 return composite != null ? composite.getTopComponents(editor) : Collections.<JComponent>emptyList();
1404 public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1405 final EditorComposite composite = getEditorComposite(editor);
1406 if (composite != null) {
1407 composite.addTopComponent(editor, component);
1412 public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1413 final EditorComposite composite = getEditorComposite(editor);
1414 if (composite != null) {
1415 composite.removeTopComponent(editor, component);
1420 public List<JComponent> getBottomComponents(@NotNull FileEditor editor) {
1421 final EditorComposite composite = getEditorComposite(editor);
1422 return composite != null ? composite.getBottomComponents(editor) : Collections.<JComponent>emptyList();
1426 public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1427 final EditorComposite composite = getEditorComposite(editor);
1428 if (composite != null) {
1429 composite.addBottomComponent(editor, component);
1434 public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
1435 final EditorComposite composite = getEditorComposite(editor);
1436 if (composite != null) {
1437 composite.removeBottomComponent(editor, component);
1441 private final MessageListenerList<FileEditorManagerListener> myListenerList;
1444 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
1445 myListenerList.add(listener);
1449 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, @NotNull final Disposable parentDisposable) {
1450 myListenerList.add(listener, parentDisposable);
1454 public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
1455 myListenerList.remove(listener);
1458 // ProjectComponent methods
1461 public void projectOpened() {
1462 //myFocusWatcher.install(myWindows.getComponent ());
1463 getMainSplitters().startListeningFocus();
1465 MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
1467 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1468 if (fileStatusManager != null) {
1470 * Updates tabs colors
1472 final MyFileStatusListener myFileStatusListener = new MyFileStatusListener();
1473 fileStatusManager.addFileStatusListener(myFileStatusListener, myProject);
1475 connection.subscribe(FileTypeManager.TOPIC, new MyFileTypeListener());
1476 connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootsListener());
1479 * Updates tabs names
1481 final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener();
1482 VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject);
1484 * Extends/cuts number of opened tabs. Also updates location of tabs.
1486 final MyUISettingsListener myUISettingsListener = new MyUISettingsListener();
1487 UISettings.getInstance().addUISettingsListener(myUISettingsListener, myProject);
1489 StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
1492 if (myProject.isDisposed()) return;
1493 setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1495 ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() {
1498 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1502 ApplicationManager.getApplication().invokeLater(new Runnable() {
1505 long currentTime = System.nanoTime();
1506 Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME);
1507 if (startTime != null) {
1508 LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms");
1509 PluginManagerCore.dumpPluginClassStatistics();
1512 }, myProject.getDisposed());
1523 public void projectClosed() {
1524 // Dispose created editors. We do not use use closeEditor method because
1525 // it fires event and changes history.
1529 // BaseCompomemnt methods
1533 public String getComponentName() {
1534 return FILE_EDITOR_MANAGER;
1538 public void initComponent() {
1543 public void disposeComponent() { /* really do nothing */ }
1545 //JDOMExternalizable methods
1548 public void writeExternal(final Element element) {
1549 getMainSplitters().writeExternal(element);
1553 public void readExternal(final Element element) {
1554 getMainSplitters().readExternal(element);
1558 private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) {
1559 for (EditorsSplitters splitters : getAllSplitters()) {
1560 final EditorWithProviderComposite[] editorsComposites = splitters.getEditorsComposites();
1561 for (int i = editorsComposites.length - 1; i >= 0; i--) {
1562 final EditorWithProviderComposite composite = editorsComposites[i];
1563 final FileEditor[] editors = composite.getEditors();
1564 for (int j = editors.length - 1; j >= 0; j--) {
1565 final FileEditor _editor = editors[j];
1566 LOG.assertTrue(_editor != null);
1567 if (editor.equals(_editor)) {
1576 //======================= Misc =====================
1578 private static void assertDispatchThread() {
1579 ApplicationManager.getApplication().assertIsDispatchThread();
1582 private static void assertReadAccess() {
1583 ApplicationManager.getApplication().assertReadAccessAllowed();
1586 public void fireSelectionChanged(final EditorComposite newSelectedComposite) {
1587 final Trinity<VirtualFile, FileEditor, FileEditorProvider> oldData = extract(SoftReference.dereference(myLastSelectedComposite));
1588 final Trinity<VirtualFile, FileEditor, FileEditorProvider> newData = extract(newSelectedComposite);
1589 myLastSelectedComposite = newSelectedComposite == null ? null : new WeakReference<EditorComposite>(newSelectedComposite);
1590 final boolean filesEqual = oldData.first == null ? newData.first == null : oldData.first.equals(newData.first);
1591 final boolean editorsEqual = oldData.second == null ? newData.second == null : oldData.second.equals(newData.second);
1592 if (!filesEqual || !editorsEqual) {
1593 if (oldData.first != null && newData.first != null) {
1594 for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) {
1595 VirtualFile associatedFile = finder.getAssociatedFileToOpen(myProject, oldData.first);
1597 if (Comparing.equal(associatedFile, newData.first)) {
1603 final FileEditorManagerEvent event =
1604 new FileEditorManagerEvent(this, oldData.first, oldData.second, oldData.third, newData.first, newData.second, newData.third);
1605 final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
1607 if (newData.first != null) {
1608 final JComponent component = newData.second.getComponent();
1609 final EditorWindowHolder holder = UIUtil.getParentOfType(EditorWindowHolder.class, component);
1610 if (holder != null) {
1611 addSelectionRecord(newData.first, holder.getEditorWindow());
1614 notifyPublisher(new Runnable() {
1617 publisher.selectionChanged(event);
1624 private static Trinity<VirtualFile, FileEditor, FileEditorProvider> extract(@Nullable EditorComposite composite) {
1625 final VirtualFile file;
1626 final FileEditor editor;
1627 final FileEditorProvider provider;
1628 if (composite == null || composite.isDisposed()) {
1634 file = composite.getFile();
1635 final Pair<FileEditor, FileEditorProvider> pair = composite.getSelectedEditorWithProvider();
1636 editor = pair.first;
1637 provider = pair.second;
1639 return new Trinity<VirtualFile, FileEditor, FileEditorProvider>(file, editor, provider);
1643 public boolean isChanged(@NotNull final EditorComposite editor) {
1644 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1645 if (fileStatusManager == null) return false;
1646 FileStatus status = fileStatusManager.getStatus(editor.getFile());
1647 return status != FileStatus.UNKNOWN && status != FileStatus.NOT_CHANGED;
1650 public void disposeComposite(@NotNull EditorWithProviderComposite editor) {
1651 if (getAllEditors().length == 0) {
1652 setCurrentWindow(null);
1655 if (editor.equals(getLastSelected())) {
1656 editor.getSelectedEditor().deselectNotify();
1657 getSplitters().setCurrentWindow(null, false);
1660 final FileEditor[] editors = editor.getEditors();
1661 final FileEditorProvider[] providers = editor.getProviders();
1663 final FileEditor selectedEditor = editor.getSelectedEditor();
1664 for (int i = editors.length - 1; i >= 0; i--) {
1665 final FileEditor editor1 = editors[i];
1666 final FileEditorProvider provider = providers[i];
1667 if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event)
1668 if (selectedEditor.equals(editor1)) {
1669 editor1.deselectNotify();
1672 editor1.removePropertyChangeListener(myEditorPropertyChangeListener);
1673 provider.disposeEditor(editor1);
1676 Disposer.dispose(editor);
1680 EditorComposite getLastSelected() {
1681 final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow();
1682 if (currentWindow != null) {
1683 return currentWindow.getSelectedEditor();
1689 * @param splitters - taken getAllSplitters() value if parameter is null
1691 public void runChange(@NotNull FileEditorManagerChange change, @Nullable EditorsSplitters splitters) {
1692 Set<EditorsSplitters> target = new HashSet<EditorsSplitters>();
1693 if (splitters == null) {
1694 target.addAll(getAllSplitters());
1697 target.add(splitters);
1700 for (EditorsSplitters each : target) {
1701 each.myInsideChange++;
1706 each.myInsideChange--;
1711 //================== Listeners =====================
1714 * Closes deleted files. Closes file which are in the deleted directories.
1716 private final class MyVirtualFileListener extends VirtualFileAdapter {
1718 public void beforeFileDeletion(@NotNull VirtualFileEvent e) {
1719 assertDispatchThread();
1721 boolean moveFocus = moveFocusOnDelete();
1723 final VirtualFile file = e.getFile();
1724 final VirtualFile[] openFiles = getOpenFiles();
1725 for (int i = openFiles.length - 1; i >= 0; i--) {
1726 if (VfsUtilCore.isAncestor(file, openFiles[i], false)) {
1727 closeFile(openFiles[i], moveFocus, true);
1733 public void propertyChanged(@NotNull VirtualFilePropertyEvent e) {
1734 if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) {
1735 assertDispatchThread();
1736 final VirtualFile file = e.getFile();
1737 if (isFileOpen(file)) {
1738 updateFileName(file);
1739 updateFileIcon(file); // file type can change after renaming
1740 updateFileBackgroundColor(file);
1743 else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) {
1744 // TODO: message bus?
1745 updateIconAndStatusBar(e);
1749 private void updateIconAndStatusBar(final VirtualFilePropertyEvent e) {
1750 assertDispatchThread();
1751 final VirtualFile file = e.getFile();
1752 if (isFileOpen(file)) {
1753 updateFileIcon(file);
1754 if (file.equals(getSelectedFiles()[0])) { // update "write" status
1755 final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1756 assert statusBar != null;
1757 statusBar.updateWidgets();
1763 public void fileMoved(@NotNull VirtualFileMoveEvent e) {
1764 final VirtualFile file = e.getFile();
1765 final VirtualFile[] openFiles = getOpenFiles();
1766 for (final VirtualFile openFile : openFiles) {
1767 if (VfsUtilCore.isAncestor(file, openFile, false)) {
1768 updateFileName(openFile);
1769 updateFileBackgroundColor(openFile);
1775 private static boolean moveFocusOnDelete() {
1776 final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
1777 if (window != null) {
1778 final Component component = FocusTrackback.getFocusFor(window);
1779 if (component != null) {
1780 return component instanceof EditorComponentImpl;
1782 return window instanceof IdeFrameImpl;
1788 public boolean isInsideChange() {
1789 return getSplitters().isInsideChange();
1792 private final class MyEditorPropertyChangeListener implements PropertyChangeListener {
1794 public void propertyChange(@NotNull final PropertyChangeEvent e) {
1795 assertDispatchThread();
1797 final String propertyName = e.getPropertyName();
1798 if (FileEditor.PROP_MODIFIED.equals(propertyName)) {
1799 final FileEditor editor = (FileEditor)e.getSource();
1800 final EditorComposite composite = getEditorComposite(editor);
1801 if (composite != null) {
1802 updateFileIcon(composite.getFile());
1805 else if (FileEditor.PROP_VALID.equals(propertyName)) {
1806 final boolean valid = ((Boolean)e.getNewValue()).booleanValue();
1808 final FileEditor editor = (FileEditor)e.getSource();
1809 LOG.assertTrue(editor != null);
1810 final EditorComposite composite = getEditorComposite(editor);
1811 if (composite != null) {
1812 closeFile(composite.getFile());
1822 * Gets events from VCS and updates color of myEditor tabs
1824 private final class MyFileStatusListener implements FileStatusListener {
1826 public void fileStatusesChanged() { // update color of all open files
1827 assertDispatchThread();
1828 LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()");
1829 final VirtualFile[] openFiles = getOpenFiles();
1830 for (int i = openFiles.length - 1; i >= 0; i--) {
1831 final VirtualFile file = openFiles[i];
1832 LOG.assertTrue(file != null);
1833 ApplicationManager.getApplication().invokeLater(new Runnable() {
1836 if (LOG.isDebugEnabled()) {
1837 LOG.debug("updating file status in tab for " + file.getPath());
1839 updateFileStatus(file);
1841 }, ModalityState.NON_MODAL, myProject.getDisposed());
1846 public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary)
1847 assertDispatchThread();
1848 if (isFileOpen(file)) {
1849 updateFileStatus(file);
1853 private void updateFileStatus(final VirtualFile file) {
1854 updateFileColor(file);
1855 updateFileIcon(file);
1860 * Gets events from FileTypeManager and updates icons on tabs
1862 private final class MyFileTypeListener extends FileTypeListener.Adapter {
1864 public void fileTypesChanged(@NotNull final FileTypeEvent event) {
1865 assertDispatchThread();
1866 final VirtualFile[] openFiles = getOpenFiles();
1867 for (int i = openFiles.length - 1; i >= 0; i--) {
1868 final VirtualFile file = openFiles[i];
1869 LOG.assertTrue(file != null);
1870 updateFileIcon(file);
1875 private class MyRootsListener extends ModuleRootAdapter {
1876 private boolean myScheduled;
1879 public void rootsChanged(ModuleRootEvent event) {
1880 if (myScheduled) return;
1882 DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
1885 myScheduled = false;
1891 private void handleRootChange() {
1892 EditorFileSwapper[] swappers = Extensions.getExtensions(EditorFileSwapper.EP_NAME);
1894 for (EditorWindow eachWindow : getWindows()) {
1895 EditorWithProviderComposite selected = eachWindow.getSelectedEditor();
1896 EditorWithProviderComposite[] editors = eachWindow.getEditors();
1897 for (int i = 0; i < editors.length; i++) {
1898 EditorWithProviderComposite editor = editors[i];
1899 VirtualFile file = editor.getFile();
1900 if (!file.isValid()) continue;
1902 Pair<VirtualFile, Integer> newFilePair = null;
1904 for (EditorFileSwapper each : swappers) {
1905 newFilePair = each.getFileToSwapTo(myProject, editor);
1906 if (newFilePair != null) break;
1909 if (newFilePair == null) continue;
1911 VirtualFile newFile = newFilePair.first;
1912 if (newFile == null) continue;
1915 if (eachWindow.findFileIndex(newFile) != -1) continue;
1918 newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, i);
1919 Pair<FileEditor[], FileEditorProvider[]> pair = openFileImpl2(eachWindow, newFile, editor == selected);
1921 if (newFilePair.second != null) {
1922 TextEditorImpl openedEditor = EditorFileSwapper.findSinglePsiAwareEditor(pair.first);
1923 if (openedEditor != null) {
1924 openedEditor.getEditor().getCaretModel().moveToOffset(newFilePair.second);
1925 openedEditor.getEditor().getScrollingModel().scrollToCaret(ScrollType.CENTER);
1930 newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null);
1932 closeFile(file, eachWindow);
1939 * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT
1940 * and EDITOR_TAB_LIMIT, etc values.
1942 private final class MyUISettingsListener implements UISettingsListener {
1944 public void uiSettingsChanged(final UISettings source) {
1945 assertDispatchThread();
1946 setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE);
1948 for (EditorsSplitters each : getAllSplitters()) {
1949 each.setTabsPlacement(source.EDITOR_TAB_PLACEMENT);
1950 each.trimToSize(source.EDITOR_TAB_LIMIT);
1952 // Tab layout policy
1953 if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) {
1954 each.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
1957 each.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
1961 // "Mark modified files with asterisk"
1962 final VirtualFile[] openFiles = getOpenFiles();
1963 for (int i = openFiles.length - 1; i >= 0; i--) {
1964 final VirtualFile file = openFiles[i];
1965 updateFileIcon(file);
1966 updateFileName(file);
1967 updateFileBackgroundColor(file);
1973 public void closeAllFiles() {
1974 final VirtualFile[] openFiles = getSplitters().getOpenFiles();
1975 for (VirtualFile openFile : openFiles) {
1976 closeFile(openFile);
1982 public VirtualFile[] getSiblings(@NotNull VirtualFile file) {
1983 return getOpenFiles();
1986 protected void queueUpdateFile(@NotNull final VirtualFile file) {
1987 myQueue.queue(new Update(file) {
1990 if (isFileOpen(file)) {
1991 updateFileIcon(file);
1992 updateFileColor(file);
1993 updateFileBackgroundColor(file);
2001 public EditorsSplitters getSplittersFor(Component c) {
2002 EditorsSplitters splitters = null;
2003 DockContainer dockContainer = myDockManager.getContainerFor(c);
2004 if (dockContainer instanceof DockableEditorTabbedContainer) {
2005 splitters = ((DockableEditorTabbedContainer)dockContainer).getSplitters();
2008 if (splitters == null) {
2009 splitters = getMainSplitters();
2016 public List<Pair<VirtualFile, EditorWindow>> getSelectionHistory() {
2017 List<Pair<VirtualFile, EditorWindow>> copy = new ArrayList<Pair<VirtualFile, EditorWindow>>();
2018 for (Pair<VirtualFile, EditorWindow> pair : mySelectionHistory) {
2019 if (pair.second.getFiles().length == 0) {
2020 final EditorWindow[] windows = pair.second.getOwner().getWindows();
2021 if (windows.length > 0 && windows[0] != null && windows[0].getFiles().length > 0) {
2022 final Pair<VirtualFile, EditorWindow> p = Pair.create(pair.first, windows[0]);
2023 if (!copy.contains(p)) {
2028 if (!copy.contains(pair)) {
2033 mySelectionHistory.clear();
2034 mySelectionHistory.addAll(copy);
2035 return mySelectionHistory;
2038 public void addSelectionRecord(@NotNull VirtualFile file, @NotNull EditorWindow window) {
2039 final Pair<VirtualFile, EditorWindow> record = Pair.create(file, window);
2040 mySelectionHistory.remove(record);
2041 mySelectionHistory.add(0, record);
2044 public void removeSelectionRecord(@NotNull VirtualFile file, @NotNull EditorWindow window) {
2045 mySelectionHistory.remove(Pair.create(file, window));
2050 public ActionCallback getReady(@NotNull Object requestor) {
2051 return myBusyObject.getReady(requestor);