/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.fileEditor.impl; import com.intellij.ProjectTopics; import com.intellij.ide.IdeBundle; import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.ide.ui.UISettings; import com.intellij.ide.ui.UISettingsListener; import com.intellij.injected.editor.VirtualFileWindow; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.impl.EditorComponentImpl; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.*; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager; import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl; import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.fileTypes.FileTypeEvent; import com.intellij.openapi.fileTypes.FileTypeListener; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.PossiblyDumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.roots.ModuleRootAdapter; import com.intellij.openapi.roots.ModuleRootEvent; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.FileStatusListener; import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vfs.*; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.ex.StatusBarEx; import com.intellij.openapi.wm.impl.IdeFrameImpl; import com.intellij.ui.FocusTrackback; import com.intellij.ui.docking.DockContainer; import com.intellij.ui.docking.DockManager; import com.intellij.ui.docking.impl.DockManagerImpl; import com.intellij.ui.tabs.impl.JBTabsImpl; import com.intellij.util.Function; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.impl.MessageListenerList; import com.intellij.util.ui.JBInsets; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.update.MergingUpdateQueue; import com.intellij.util.ui.update.Update; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.Border; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.util.*; import java.util.List; /** * @author Anton Katilin * @author Eugene Belyaev * @author Vladimir Kondratyev */ public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl"); private static final Key DUMB_AWARE = Key.create("DUMB_AWARE"); private static final FileEditor[] EMPTY_EDITOR_ARRAY = {}; private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {}; public static final Key CLOSING_TO_REOPEN = Key.create("CLOSING_TO_REOPEN"); public static final String FILE_EDITOR_MANAGER = "FileEditorManager"; private volatile JPanel myPanels; private EditorsSplitters mySplitters; private final Project myProject; private final List> mySelectionHistory = new ArrayList>(); private WeakReference myLastSelectedComposite = new WeakReference(null); private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true, null); private final BusyObject.Impl.Simple myBusyObject = new BusyObject.Impl.Simple(); /** * Removes invalid myEditor and updates "modified" status. */ private final MyEditorPropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener(); private final DockManager myDockManager; private DockableEditorContainerFactory myContentFactory; private EditorHistoryManager myEditorHistoryManager; public FileEditorManagerImpl(final Project project, DockManager dockManager, EditorHistoryManager editorHistoryManager) { /* ApplicationManager.getApplication().assertIsDispatchThread(); */ myProject = project; myDockManager = dockManager; myEditorHistoryManager = editorHistoryManager; myListenerList = new MessageListenerList(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER); if (Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME).length > 0) { myListenerList.add(new FileEditorManagerAdapter() { @Override public void selectionChanged(@NotNull FileEditorManagerEvent event) { EditorsSplitters splitters = getSplitters(); openAssociatedFile(event.getNewFile(), splitters.getCurrentWindow(), splitters); } }); } myQueue.setTrackUiActivity(true); } public void initDockableContentFactory() { if (myContentFactory != null) return; myContentFactory = new DockableEditorContainerFactory(myProject, this, myDockManager); myDockManager.register(DockableEditorContainerFactory.TYPE, myContentFactory); Disposer.register(myProject, myContentFactory); } public static boolean isDumbAware(FileEditor editor) { return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE)) && (!(editor instanceof PossiblyDumbAware) || ((PossiblyDumbAware)editor).isDumbAware()); } //------------------------------------------------------------------------------- @Override public JComponent getComponent() { initUI(); return myPanels; } @NotNull public EditorsSplitters getMainSplitters() { initUI(); return mySplitters; } public Set getAllSplitters() { HashSet all = new HashSet(); all.add(getMainSplitters()); Set dockContainers = myDockManager.getContainers(); for (DockContainer each : dockContainers) { if (each instanceof DockableEditorTabbedContainer) { all.add(((DockableEditorTabbedContainer)each).getSplitters()); } } return Collections.unmodifiableSet(all); } private AsyncResult getActiveSplitters(boolean syncUsage) { final boolean async = Registry.is("ide.windowSystem.asyncSplitters") && !syncUsage; final AsyncResult result = new AsyncResult(); final IdeFocusManager fm = IdeFocusManager.getInstance(myProject); Runnable run = new Runnable() { @Override public void run() { if (myProject.isDisposed()) { result.setRejected(); return; } Component focusOwner = fm.getFocusOwner(); if (focusOwner == null && !async) { focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); } if (focusOwner == null && !async) { focusOwner = fm.getLastFocusedFor(fm.getLastFocusedFrame()); } DockContainer container = myDockManager.getContainerFor(focusOwner); if (container == null && !async) { focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); container = myDockManager.getContainerFor(focusOwner); } if (container instanceof DockableEditorTabbedContainer) { result.setDone(((DockableEditorTabbedContainer)container).getSplitters()); } else { result.setDone(getMainSplitters()); } } }; if (async) { fm.doWhenFocusSettlesDown(run); } else { UIUtil.invokeAndWaitIfNeeded(run); } return result; } private final Object myInitLock = new Object(); private void initUI() { if (myPanels == null) { synchronized (myInitLock) { if (myPanels == null) { myPanels = new JPanel(new BorderLayout()); myPanels.setOpaque(false); myPanels.setBorder(new MyBorder()); mySplitters = new EditorsSplitters(this, myDockManager, true); myPanels.add(mySplitters, BorderLayout.CENTER); } } } } private static class MyBorder implements Border { @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { if (UIUtil.isUnderAquaLookAndFeel()) { g.setColor(JBTabsImpl.MAC_AQUA_BG_COLOR); final Insets insets = getBorderInsets(c); if (insets.top > 0) { g.fillRect(x, y, width, height + insets.top); } } } @Override public Insets getBorderInsets(Component c) { return JBInsets.NONE; } @Override public boolean isBorderOpaque() { return false; } } @Override public JComponent getPreferredFocusedComponent() { assertReadAccess(); final EditorWindow window = getSplitters().getCurrentWindow(); if (window != null) { final EditorWithProviderComposite editor = window.getSelectedEditor(); if (editor != null) { return editor.getPreferredFocusedComponent(); } } return null; } //------------------------------------------------------- /** * @return color of the file which corresponds to the * file's status */ public Color getFileColor(@NotNull final VirtualFile file) { final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : UIUtil.getLabelForeground(); if (statusColor == null) statusColor = UIUtil.getLabelForeground(); return statusColor; } public boolean isProblem(@NotNull final VirtualFile file) { return false; } public String getFileTooltipText(VirtualFile file) { return FileUtil.getLocationRelativeToUserHome(file.getPresentableUrl()); } @Override public void updateFilePresentation(@NotNull VirtualFile file) { if (!isFileOpen(file)) return; updateFileColor(file); updateFileIcon(file); updateFileName(file); updateFileBackgroundColor(file); } /** * Updates tab color for the specified file. The file * should be opened in the myEditor, otherwise the method throws an assertion. */ private void updateFileColor(final VirtualFile file) { Set all = getAllSplitters(); for (EditorsSplitters each : all) { each.updateFileColor(file); } } private void updateFileBackgroundColor(final VirtualFile file) { Set all = getAllSplitters(); for (EditorsSplitters each : all) { each.updateFileBackgroundColor(file); } } /** * Updates tab icon for the specified file. The file * should be opened in the myEditor, otherwise the method throws an assertion. */ protected void updateFileIcon(final VirtualFile file) { Set all = getAllSplitters(); for (EditorsSplitters each : all) { each.updateFileIcon(file); } } /** * Updates tab title and tab tool tip for the specified file */ void updateFileName(@Nullable final VirtualFile file) { // Queue here is to prevent title flickering when tab is being closed and two events arriving: with component==null and component==next focused tab // only the last event makes sense to handle myQueue.queue(new Update("UpdateFileName " + (file == null ? "" : file.getPath())) { @Override public boolean isExpired() { return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid()); } @Override public void run() { Set all = getAllSplitters(); for (EditorsSplitters each : all) { each.updateFileName(file); } } }); } //------------------------------------------------------- @Override public VirtualFile getFile(@NotNull final FileEditor editor) { final EditorComposite editorComposite = getEditorComposite(editor); if (editorComposite != null) { return editorComposite.getFile(); } return null; } @Override public void unsplitWindow() { final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow(); if (currentWindow != null) { currentWindow.unsplit(true); } } @Override public void unsplitAllWindow() { final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow(); if (currentWindow != null) { currentWindow.unsplitAll(); } } @Override public int getWindowSplitCount() { return getActiveSplitters(true).getResult().getSplitCount(); } @Override public boolean hasSplitOrUndockedWindows() { Set splitters = getAllSplitters(); if (splitters.size() > 1) return true; return getWindowSplitCount() > 1; } @Override @NotNull public EditorWindow[] getWindows() { ArrayList windows = new ArrayList(); Set all = getAllSplitters(); for (EditorsSplitters each : all) { EditorWindow[] eachList = each.getWindows(); windows.addAll(Arrays.asList(eachList)); } return windows.toArray(new EditorWindow[windows.size()]); } @Override public EditorWindow getNextWindow(@NotNull final EditorWindow window) { final EditorWindow[] windows = getSplitters().getOrderedWindows(); for (int i = 0; i != windows.length; ++i) { if (windows[i].equals(window)) { return windows[(i + 1) % windows.length]; } } LOG.error("Not window found"); return null; } @Override public EditorWindow getPrevWindow(@NotNull final EditorWindow window) { final EditorWindow[] windows = getSplitters().getOrderedWindows(); for (int i = 0; i != windows.length; ++i) { if (windows[i].equals(window)) { return windows[(i + windows.length - 1) % windows.length]; } } LOG.error("Not window found"); return null; } @Override public void createSplitter(final int orientation, @Nullable final EditorWindow window) { // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current' if (window != null) { window.split(orientation, true, null, false); } // otherwise we'll split the current window, if any else { final EditorWindow currentWindow = getSplitters().getCurrentWindow(); if (currentWindow != null) { currentWindow.split(orientation, true, null, false); } } } @Override public void changeSplitterOrientation() { final EditorWindow currentWindow = getSplitters().getCurrentWindow(); if (currentWindow != null) { currentWindow.changeOrientation(); } } @Override public void flipTabs() { /* if (myTabs == null) { myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT); remove (mySplitters); add (myTabs, BorderLayout.CENTER); initTabs (); } else { remove (myTabs); add (mySplitters, BorderLayout.CENTER); myTabs.dispose (); myTabs = null; } */ myPanels.revalidate(); } @Override public boolean tabsMode() { return false; } private void setTabsMode(final boolean mode) { if (tabsMode() != mode) { flipTabs(); } //LOG.assertTrue (tabsMode () == mode); } @Override public boolean isInSplitter() { final EditorWindow currentWindow = getSplitters().getCurrentWindow(); return currentWindow != null && currentWindow.inSplitter(); } @Override public boolean hasOpenedFile() { final EditorWindow currentWindow = getSplitters().getCurrentWindow(); return currentWindow != null && currentWindow.getSelectedEditor() != null; } @Override public VirtualFile getCurrentFile() { return getActiveSplitters(true).getResult().getCurrentFile(); } @Override @NotNull public AsyncResult getActiveWindow() { return _getActiveWindow(false); } @NotNull private AsyncResult _getActiveWindow(boolean now) { return getActiveSplitters(now).subResult(new Function() { @Override public EditorWindow fun(EditorsSplitters splitters) { return splitters.getCurrentWindow(); } }); } @Override public EditorWindow getCurrentWindow() { return _getActiveWindow(true).getResult(); } @Override public void setCurrentWindow(final EditorWindow window) { getActiveSplitters(true).getResult().setCurrentWindow(window, true); } public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window, final boolean transferFocus) { assertDispatchThread(); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { if (window.isFileOpen(file)) { window.closeFile(file, true, transferFocus); } } }, IdeBundle.message("command.close.active.editor"), null); removeSelectionRecord(file, window); } @Override public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) { closeFile(file, window, true); } //============================= EditorManager methods ================================ @Override public void closeFile(@NotNull final VirtualFile file) { closeFile(file, true, false); } public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus, final boolean closeAllCopies) { assertDispatchThread(); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { closeFileImpl(file, moveFocus, closeAllCopies); } }, "", null); } private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus, boolean closeAllCopies) { assertDispatchThread(); runChange(new FileEditorManagerChange() { @Override public void run(EditorsSplitters splitters) { splitters.closeFile(file, moveFocus); } }, closeAllCopies ? null : getActiveSplitters(true).getResult()); } //-------------------------------------- Open File ---------------------------------------- @Override @NotNull public Pair openFileWithProviders(@NotNull final VirtualFile file, final boolean focusEditor, boolean searchForSplitter) { if (!file.isValid()) { throw new IllegalArgumentException("file is not valid: " + file); } assertDispatchThread(); if (isOpenInNewWindow(EventQueue.getCurrentEvent())) { return openFileInNewWindow(file); } EditorWindow wndToOpenIn = null; if (searchForSplitter) { Set all = getAllSplitters(); EditorsSplitters active = getActiveSplitters(true).getResult(); if (active.getCurrentWindow() != null && active.getCurrentWindow().isFileOpen(file)) { wndToOpenIn = active.getCurrentWindow(); } else { for (EditorsSplitters splitters : all) { final EditorWindow window = splitters.getCurrentWindow(); if (window == null) continue; if (window.isFileOpen(file)) { wndToOpenIn = window; break; } } } } else { wndToOpenIn = getSplitters().getCurrentWindow(); } EditorsSplitters splitters = getSplitters(); if (wndToOpenIn == null) { wndToOpenIn = splitters.getOrCreateCurrentWindow(file); } openAssociatedFile(file, wndToOpenIn, splitters); return openFileImpl2(wndToOpenIn, file, focusEditor); } public Pair openFileInNewWindow(@NotNull VirtualFile file) { return ((DockManagerImpl)DockManager.getInstance(getProject())).createNewDockContainerFor(file, this); } private static boolean isOpenInNewWindow(AWTEvent event) { // Shift was used while clicking if (event instanceof MouseEvent && ((MouseEvent)event).isShiftDown()) { return true; } // Shift + Enter if (event instanceof KeyEvent && ((KeyEvent)event).getKeyCode() == KeyEvent.VK_ENTER && ((KeyEvent)event).isShiftDown()) { return true; } return false; } private void openAssociatedFile(VirtualFile file, EditorWindow wndToOpenIn, EditorsSplitters splitters) { EditorWindow[] windows = splitters.getWindows(); if (file != null && windows.length == 2) { for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) { VirtualFile associatedFile = finder.getAssociatedFileToOpen(myProject, file); if (associatedFile != null) { EditorWindow currentWindow = splitters.getCurrentWindow(); int idx = windows[0] == wndToOpenIn ? 1 : 0; openFileImpl2(windows[idx], associatedFile, false); if (currentWindow != null) { splitters.setCurrentWindow(currentWindow, false); } break; } } } } @NotNull @Override public Pair openFileWithProviders(@NotNull VirtualFile file, boolean focusEditor, @NotNull EditorWindow window) { if (!file.isValid()) { throw new IllegalArgumentException("file is not valid: " + file); } assertDispatchThread(); return openFileImpl2(window, file, focusEditor); } @NotNull public Pair openFileImpl2(@NotNull final EditorWindow window, @NotNull final VirtualFile file, final boolean focusEditor) { final Ref> result = new Ref>(); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { result.set(openFileImpl3(window, file, focusEditor, null, true)); } }, "", null); return result.get(); } /** * @param file to be opened. Unlike openFile method, file can be * invalid. For example, all file were invalidate and they are being * removed one by one. If we have removed one invalid file, then another * invalid file become selected. That's why we do not require that * passed file is valid. * @param entry map between FileEditorProvider and FileEditorState. If this parameter */ @NotNull Pair openFileImpl3(@NotNull final EditorWindow window, @NotNull final VirtualFile file, final boolean focusEditor, @Nullable final HistoryEntry entry, boolean current) { return openFileImpl4(window, file, entry, current, focusEditor, null, -1); } /** * This method can be invoked from background thread. Of course, UI for returned editors should be accessed from EDT in any case. */ @NotNull Pair openFileImpl4(@NotNull final EditorWindow window, @NotNull final VirtualFile file, @Nullable final HistoryEntry entry, final boolean current, final boolean focusEditor, final Boolean pin, final int index) { ApplicationManager.getApplication().assertReadAccessAllowed(); final Ref compositeRef = new Ref(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { compositeRef.set(window.findFileComposite(file)); } }); final FileEditorProvider[] newProviders; final AsyncFileEditorProvider.Builder[] builders; if (compositeRef.isNull()) { // File is not opened yet. In this case we have to create editors // and select the created EditorComposite. newProviders = getAvailableProviders(file); if (newProviders.length == 0) { return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY); } builders = new AsyncFileEditorProvider.Builder[newProviders.length]; for (int i = 0; i < newProviders.length; i++) { try { final FileEditorProvider provider = newProviders[i]; LOG.assertTrue(provider != null, "Provider for file "+file+" is null. All providers: "+Arrays.asList(newProviders)); LOG.assertTrue(provider.accept(myProject, file), "Provider " + provider + " doesn't accept file " + file); if ((provider instanceof AsyncFileEditorProvider)) { builders[i] = ((AsyncFileEditorProvider)provider).createEditorAsync(myProject, file); } } catch (Exception e) { LOG.error(e); } catch (AssertionError e) { LOG.error(e); } } } else { newProviders = null; builders = null; } UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { if (myProject.isDisposed() || !file.isValid()) { return; } compositeRef.set(window.findFileComposite(file)); boolean newEditor = compositeRef.isNull(); if (newEditor) { clearWindowIfNeeded(window); getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(FileEditorManagerImpl.this, file); FileEditor[] newEditors = new FileEditor[newProviders.length]; for (int i = 0; i < newProviders.length; i++) { try { final FileEditorProvider provider = newProviders[i]; final FileEditor editor = builders[i] == null ? provider.createEditor(myProject, file) : builders[i].build(); LOG.assertTrue(editor.isValid()); newEditors[i] = editor; // Register PropertyChangeListener into editor editor.addPropertyChangeListener(myEditorPropertyChangeListener); editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider)); } catch (Exception e) { LOG.error(e); } catch (AssertionError e) { LOG.error(e); } } // Now we have to create EditorComposite and insert it into the TabbedEditorComponent. // After that we have to select opened editor. EditorWithProviderComposite composite = new EditorWithProviderComposite(file, newEditors, newProviders, FileEditorManagerImpl.this); if (index >= 0) { composite.getFile().putUserData(EditorWindow.INITIAL_INDEX_KEY, index); } compositeRef.set(composite); } final EditorWithProviderComposite composite = compositeRef.get(); FileEditor[] editors = composite.getEditors(); FileEditorProvider[] providers = composite.getProviders(); window.setEditor(composite, current, focusEditor); for (int i = 0; i < editors.length; i++) { restoreEditorState(file, providers[i], editors[i], entry, newEditor); } // Restore selected editor final FileEditorProvider selectedProvider; if (entry == null) { selectedProvider = ((FileEditorProviderManagerImpl)FileEditorProviderManager.getInstance()) .getSelectedFileEditorProvider(myEditorHistoryManager, file, providers); } else { selectedProvider = entry.mySelectedProvider; } if (selectedProvider != null) { for (int i = editors.length - 1; i >= 0; i--) { final FileEditorProvider provider = providers[i]; if (provider.equals(selectedProvider)) { composite.setSelectedEditor(i); break; } } } // Notify editors about selection changes window.getOwner().setCurrentWindow(window, focusEditor); window.getOwner().afterFileOpen(file); addSelectionRecord(file, window); composite.getSelectedEditor().selectNotify(); // Transfer focus into editor if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) { if (focusEditor) { //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer); window.setAsCurrentWindow(true); ToolWindowManager.getInstance(myProject).activateEditorComponent(); IdeFocusManager.getInstance(myProject).toFront(window.getOwner()); } } if (newEditor) { if (window.isShowing()) { window.setPaintBlocked(true); } notifyPublisher(new Runnable() { @Override public void run() { window.setPaintBlocked(false); if (isFileOpen(file)) { getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER) .fileOpened(FileEditorManagerImpl.this, file); } } }); } //[jeka] this is a hack to support back-forward navigation // previously here was incorrect call to fireSelectionChanged() with a side-effect ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged(); // Update frame and tab title updateFileName(file); // Make back/forward work IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation(); if (pin != null) { window.setFilePinned(file, pin); } } }); return Pair.create(compositeRef.get().getEditors(), compositeRef.get().getProviders()); } private void clearWindowIfNeeded(EditorWindow window) { if (UISettings.getInstance().EDITOR_TAB_PLACEMENT == UISettings.TABS_NONE || UISettings.getInstance().PRESENTATION_MODE) { for (EditorWithProviderComposite composite : window.getEditors()) { Disposer.dispose(composite); } } } private void restoreEditorState(VirtualFile file, FileEditorProvider provider, final FileEditor editor, HistoryEntry entry, boolean newEditor) { FileEditorState state = null; if (entry != null) { state = entry.getState(provider); } if (state == null && newEditor) { // We have to try to get state from the history only in case // if editor is not opened. Otherwise history entry might have a state // out of sync with the current editor state. state = myEditorHistoryManager.getState(file, provider); } if (state != null) { if (!isDumbAware(editor)) { final FileEditorState finalState = state; DumbService.getInstance(getProject()).runWhenSmart(new Runnable() { @Override public void run() { editor.setState(finalState); } }); } else { editor.setState(state); } } } private FileEditorProvider[] getAvailableProviders(VirtualFile file) { final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance(); FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file); if (DumbService.getInstance(myProject).isDumb()) { final List dumbAware = ContainerUtil.findAll(providers, new Condition() { @Override public boolean value(FileEditorProvider fileEditorProvider) { return DumbService.isDumbAware(fileEditorProvider); } }); providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]); } return providers; } @NotNull @Override public ActionCallback notifyPublisher(@NotNull final Runnable runnable) { final IdeFocusManager focusManager = IdeFocusManager.getInstance(myProject); final ActionCallback done = new ActionCallback(); return myBusyObject.execute(new ActiveRunnable() { @NotNull @Override public ActionCallback run() { focusManager.doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) { @Override public void run() { runnable.run(); done.setDone(); } }); return done; } }); } @Override public void setSelectedEditor(@NotNull VirtualFile file, String fileEditorProviderId) { EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); if (composite == null) { final List composites = getEditorComposites(file); if (composites.isEmpty()) return; composite = composites.get(0); } final FileEditorProvider[] editorProviders = composite.getProviders(); final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond(); for (int i = 0; i < editorProviders.length; i++) { if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) { composite.setSelectedEditor(i); composite.getSelectedEditor().selectNotify(); } } } @Nullable EditorWithProviderComposite newEditorComposite(final VirtualFile file) { if (file == null) { return null; } final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance(); final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file); final FileEditor[] editors = new FileEditor[providers.length]; for (int i = 0; i < providers.length; i++) { final FileEditorProvider provider = providers[i]; LOG.assertTrue(provider != null); LOG.assertTrue(provider.accept(myProject, file)); final FileEditor editor = provider.createEditor(myProject, file); editors[i] = editor; LOG.assertTrue(editor.isValid()); editor.addPropertyChangeListener(myEditorPropertyChangeListener); } final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this); final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject); for (int i = 0; i < editors.length; i++) { final FileEditor editor = editors[i]; if (editor instanceof TextEditor) { // hack!!! // This code prevents "jumping" on next repaint. //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling(); } final FileEditorProvider provider = providers[i]; // Restore myEditor state FileEditorState state = editorHistoryManager.getState(file, provider); if (state != null) { editor.setState(state); } } return newComposite; } @Override @NotNull public List openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) { assertDispatchThread(); if (descriptor.getFile() instanceof VirtualFileWindow) { VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile(); int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset()); OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset); realDescriptor.setUseCurrentWindow(descriptor.isUseCurrentWindow()); return openEditor(realDescriptor, focusEditor); } final List result = new SmartList(); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { VirtualFile file = descriptor.getFile(); final FileEditor[] editors = openFile(file, focusEditor, !descriptor.isUseCurrentWindow()); ContainerUtil.addAll(result, editors); boolean navigated = false; for (final FileEditor editor : editors) { if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor navigated = navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor); if (navigated) break; } } if (!navigated) { for (final FileEditor editor : editors) { if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors if (navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor)) { break; } } } } } }, "", null); return result; } private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) { if (editor.canNavigateTo(descriptor)) { setSelectedEditor(editor); editor.navigateTo(descriptor); return true; } return false; } private void setSelectedEditor(final FileEditor editor) { final EditorWithProviderComposite composite = getEditorComposite(editor); if (composite == null) return; final FileEditor[] editors = composite.getEditors(); for (int i = 0; i < editors.length; i++) { final FileEditor each = editors[i]; if (editor == each) { composite.setSelectedEditor(i); composite.getSelectedEditor().selectNotify(); break; } } } @Override @NotNull public Project getProject() { return myProject; } @Override @Nullable public Editor openTextEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) { final Collection fileEditors = openEditor(descriptor, focusEditor); for (FileEditor fileEditor : fileEditors) { if (fileEditor instanceof TextEditor) { setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId()); Editor editor = ((TextEditor)fileEditor).getEditor(); return getOpenedEditor(editor, focusEditor); } } return null; } protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) { return editor; } @Override public Editor getSelectedTextEditor() { assertReadAccess(); final EditorWindow currentWindow = getSplitters().getCurrentWindow(); if (currentWindow != null) { final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor(); if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) { return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor(); } } return null; } @Override public boolean isFileOpen(@NotNull final VirtualFile file) { return !getEditorComposites(file).isEmpty(); } @Override @NotNull public VirtualFile[] getOpenFiles() { HashSet openFiles = new HashSet(); for (EditorsSplitters each : getAllSplitters()) { openFiles.addAll(Arrays.asList(each.getOpenFiles())); } return VfsUtilCore.toVirtualFileArray(openFiles); } @Override @NotNull public VirtualFile[] getSelectedFiles() { HashSet selectedFiles = new HashSet(); for (EditorsSplitters each : getAllSplitters()) { selectedFiles.addAll(Arrays.asList(each.getSelectedFiles())); } return VfsUtilCore.toVirtualFileArray(selectedFiles); } @Override @NotNull public FileEditor[] getSelectedEditors() { HashSet selectedEditors = new HashSet(); for (EditorsSplitters each : getAllSplitters()) { selectedEditors.addAll(Arrays.asList(each.getSelectedEditors())); } return selectedEditors.toArray(new FileEditor[selectedEditors.size()]); } @Override @NotNull public EditorsSplitters getSplitters() { EditorsSplitters active = getActiveSplitters(true).getResult(); return active == null ? getMainSplitters() : active; } @Override @Nullable public FileEditor getSelectedEditor(@NotNull final VirtualFile file) { final Pair selectedEditorWithProvider = getSelectedEditorWithProvider(file); return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst(); } @Override @Nullable public Pair getSelectedEditorWithProvider(@NotNull VirtualFile file) { if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); if (composite != null) { return composite.getSelectedEditorWithProvider(); } final List composites = getEditorComposites(file); return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider(); } @Override @NotNull public Pair getEditorsWithProviders(@NotNull final VirtualFile file) { assertReadAccess(); final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); if (composite != null) { return Pair.create(composite.getEditors(), composite.getProviders()); } final List composites = getEditorComposites(file); if (!composites.isEmpty()) { return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders()); } else { return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY); } } @Override @NotNull public FileEditor[] getEditors(@NotNull VirtualFile file) { assertReadAccess(); if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); if (composite != null) { return composite.getEditors(); } final List composites = getEditorComposites(file); if (!composites.isEmpty()) { return composites.get(0).getEditors(); } else { return EMPTY_EDITOR_ARRAY; } } @NotNull @Override public FileEditor[] getAllEditors(@NotNull VirtualFile file) { List editorComposites = getEditorComposites(file); if (editorComposites.isEmpty()) return EMPTY_EDITOR_ARRAY; List editors = new ArrayList(); for (EditorWithProviderComposite composite : editorComposites) { ContainerUtil.addAll(editors, composite.getEditors()); } return editors.toArray(new FileEditor[editors.size()]); } @Nullable private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) { final EditorWindow editorWindow = getSplitters().getCurrentWindow(); if (editorWindow != null) { return editorWindow.findFileComposite(virtualFile); } return null; } @NotNull public List getEditorComposites(final VirtualFile file) { ArrayList result = new ArrayList(); Set all = getAllSplitters(); for (EditorsSplitters each : all) { result.addAll(each.findEditorComposites(file)); } return result; } @Override @NotNull public FileEditor[] getAllEditors() { assertReadAccess(); final ArrayList result = new ArrayList(); final Set allSplitters = getAllSplitters(); for (EditorsSplitters splitter : allSplitters) { final EditorWithProviderComposite[] editorsComposites = splitter.getEditorsComposites(); for (EditorWithProviderComposite editorsComposite : editorsComposites) { final FileEditor[] editors = editorsComposite.getEditors(); ContainerUtil.addAll(result, editors); } } return result.toArray(new FileEditor[result.size()]); } @Override public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) { addTopComponent(editor, annotationComponent); } @Override public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) { removeTopComponent(editor, annotationComponent); } @NotNull public List getTopComponents(@NotNull FileEditor editor) { final EditorComposite composite = getEditorComposite(editor); return composite != null ? composite.getTopComponents(editor) : Collections.emptyList(); } @Override public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { final EditorComposite composite = getEditorComposite(editor); if (composite != null) { composite.addTopComponent(editor, component); } } @Override public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { final EditorComposite composite = getEditorComposite(editor); if (composite != null) { composite.removeTopComponent(editor, component); } } @NotNull public List getBottomComponents(@NotNull FileEditor editor) { final EditorComposite composite = getEditorComposite(editor); return composite != null ? composite.getBottomComponents(editor) : Collections.emptyList(); } @Override public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { final EditorComposite composite = getEditorComposite(editor); if (composite != null) { composite.addBottomComponent(editor, component); } } @Override public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { final EditorComposite composite = getEditorComposite(editor); if (composite != null) { composite.removeBottomComponent(editor, component); } } private final MessageListenerList myListenerList; @Override public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) { myListenerList.add(listener); } @Override public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, @NotNull final Disposable parentDisposable) { myListenerList.add(listener, parentDisposable); } @Override public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) { myListenerList.remove(listener); } // ProjectComponent methods @Override public void projectOpened() { //myFocusWatcher.install(myWindows.getComponent ()); getMainSplitters().startListeningFocus(); MessageBusConnection connection = myProject.getMessageBus().connect(myProject); final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); if (fileStatusManager != null) { /** * Updates tabs colors */ final MyFileStatusListener myFileStatusListener = new MyFileStatusListener(); fileStatusManager.addFileStatusListener(myFileStatusListener, myProject); } connection.subscribe(FileTypeManager.TOPIC, new MyFileTypeListener()); connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootsListener()); /** * Updates tabs names */ final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener(); VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject); /** * Extends/cuts number of opened tabs. Also updates location of tabs. */ final MyUISettingsListener myUISettingsListener = new MyUISettingsListener(); UISettings.getInstance().addUISettingsListener(myUISettingsListener, myProject); StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() { @Override public void run() { if (myProject.isDisposed()) return; setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE); ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() { @Override public void run() { CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { LaterInvocator.invokeLater(new Runnable() { @Override public void run() { long currentTime = System.nanoTime(); Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME); if (startTime != null) { LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms"); PluginManagerCore.dumpPluginClassStatistics(); } } }, myProject.getDisposed()); // group 1 } }, "", null); } }); } }); } @Override public void projectClosed() { //myFocusWatcher.deinstall(myWindows.getComponent ()); getMainSplitters().dispose(); // Dispose created editors. We do not use use closeEditor method because // it fires event and changes history. closeAllFiles(); } // BaseCompomemnt methods @Override @NotNull public String getComponentName() { return FILE_EDITOR_MANAGER; } @Override public void initComponent() { } @Override public void disposeComponent() { /* really do nothing */ } //JDOMExternalizable methods @Override public void writeExternal(final Element element) { getMainSplitters().writeExternal(element); } @Override public void readExternal(final Element element) { getMainSplitters().readExternal(element); } @Nullable private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) { for (EditorsSplitters splitters : getAllSplitters()) { final EditorWithProviderComposite[] editorsComposites = splitters.getEditorsComposites(); for (int i = editorsComposites.length - 1; i >= 0; i--) { final EditorWithProviderComposite composite = editorsComposites[i]; final FileEditor[] editors = composite.getEditors(); for (int j = editors.length - 1; j >= 0; j--) { final FileEditor _editor = editors[j]; LOG.assertTrue(_editor != null); if (editor.equals(_editor)) { return composite; } } } } return null; } //======================= Misc ===================== private static void assertDispatchThread() { ApplicationManager.getApplication().assertIsDispatchThread(); } private static void assertReadAccess() { ApplicationManager.getApplication().assertReadAccessAllowed(); } public void fireSelectionChanged(final EditorComposite newSelectedComposite) { final Trinity oldData = extract(myLastSelectedComposite.get()); final Trinity newData = extract(newSelectedComposite); myLastSelectedComposite = new WeakReference(newSelectedComposite); final boolean filesEqual = oldData.first == null ? newData.first == null : oldData.first.equals(newData.first); final boolean editorsEqual = oldData.second == null ? newData.second == null : oldData.second.equals(newData.second); if (!filesEqual || !editorsEqual) { if (oldData.first != null && newData.first != null) { for (FileEditorAssociateFinder finder : Extensions.getExtensions(FileEditorAssociateFinder.EP_NAME)) { VirtualFile associatedFile = finder.getAssociatedFileToOpen(myProject, oldData.first); if (Comparing.equal(associatedFile, newData.first)) { return; } } } final FileEditorManagerEvent event = new FileEditorManagerEvent(this, oldData.first, oldData.second, oldData.third, newData.first, newData.second, newData.third); final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); if (newData.first != null) { final JComponent component = newData.second.getComponent(); final EditorWindowHolder holder = UIUtil.getParentOfType(EditorWindowHolder.class, component); if (holder != null) { addSelectionRecord(newData.first, holder.getEditorWindow()); } } notifyPublisher(new Runnable() { @Override public void run() { publisher.selectionChanged(event); } }); } } @NotNull private static Trinity extract(@Nullable EditorComposite composite) { final VirtualFile file; final FileEditor editor; final FileEditorProvider provider; if (composite == null || composite.isDisposed()) { file = null; editor = null; provider = null; } else { file = composite.getFile(); final Pair pair = composite.getSelectedEditorWithProvider(); editor = pair.first; provider = pair.second; } return new Trinity(file, editor, provider); } @Override public boolean isChanged(@NotNull final EditorComposite editor) { final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); if (fileStatusManager != null) { VirtualFile file = editor.getFile(); FileStatus status = fileStatusManager.getStatus(file); if (status == FileStatus.UNKNOWN && !file.isWritable()) { return false; } if (!status.equals(FileStatus.NOT_CHANGED)) { return true; } } return false; } public void disposeComposite(@NotNull EditorWithProviderComposite editor) { if (getAllEditors().length == 0) { setCurrentWindow(null); } if (editor.equals(getLastSelected())) { editor.getSelectedEditor().deselectNotify(); getSplitters().setCurrentWindow(null, false); } final FileEditor[] editors = editor.getEditors(); final FileEditorProvider[] providers = editor.getProviders(); final FileEditor selectedEditor = editor.getSelectedEditor(); for (int i = editors.length - 1; i >= 0; i--) { final FileEditor editor1 = editors[i]; final FileEditorProvider provider = providers[i]; if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event) if (selectedEditor.equals(editor1)) { editor1.deselectNotify(); } } editor1.removePropertyChangeListener(myEditorPropertyChangeListener); provider.disposeEditor(editor1); } Disposer.dispose(editor); } @Nullable EditorComposite getLastSelected() { final EditorWindow currentWindow = getActiveSplitters(true).getResult().getCurrentWindow(); if (currentWindow != null) { return currentWindow.getSelectedEditor(); } return null; } public void runChange(FileEditorManagerChange change, EditorsSplitters splitters) { Set target = new HashSet(); if (splitters == null) { target.addAll(getAllSplitters()); } else { target.add(splitters); } for (EditorsSplitters each : target) { each.myInsideChange++; try { change.run(each); } finally { each.myInsideChange--; } } } //================== Listeners ===================== /** * Closes deleted files. Closes file which are in the deleted directories. */ private final class MyVirtualFileListener extends VirtualFileAdapter { @Override public void beforeFileDeletion(@NotNull VirtualFileEvent e) { assertDispatchThread(); boolean moveFocus = moveFocusOnDelete(); final VirtualFile file = e.getFile(); final VirtualFile[] openFiles = getOpenFiles(); for (int i = openFiles.length - 1; i >= 0; i--) { if (VfsUtilCore.isAncestor(file, openFiles[i], false)) { closeFile(openFiles[i], moveFocus, true); } } } @Override public void propertyChanged(@NotNull VirtualFilePropertyEvent e) { if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) { assertDispatchThread(); final VirtualFile file = e.getFile(); if (isFileOpen(file)) { updateFileName(file); updateFileIcon(file); // file type can change after renaming updateFileBackgroundColor(file); } } else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) { // TODO: message bus? updateIconAndStatusBar(e); } } private void updateIconAndStatusBar(final VirtualFilePropertyEvent e) { assertDispatchThread(); final VirtualFile file = e.getFile(); if (isFileOpen(file)) { updateFileIcon(file); if (file.equals(getSelectedFiles()[0])) { // update "write" status final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject); assert statusBar != null; statusBar.updateWidgets(); } } } @Override public void fileMoved(@NotNull VirtualFileMoveEvent e) { final VirtualFile file = e.getFile(); final VirtualFile[] openFiles = getOpenFiles(); for (final VirtualFile openFile : openFiles) { if (VfsUtilCore.isAncestor(file, openFile, false)) { updateFileName(openFile); updateFileBackgroundColor(openFile); } } } } private static boolean moveFocusOnDelete() { final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow(); if (window != null) { final Component component = FocusTrackback.getFocusFor(window); if (component != null) { return component instanceof EditorComponentImpl; } return window instanceof IdeFrameImpl; } return true; } @Override public boolean isInsideChange() { return getSplitters().isInsideChange(); } private final class MyEditorPropertyChangeListener implements PropertyChangeListener { @Override public void propertyChange(final PropertyChangeEvent e) { assertDispatchThread(); final String propertyName = e.getPropertyName(); if (FileEditor.PROP_MODIFIED.equals(propertyName)) { final FileEditor editor = (FileEditor)e.getSource(); final EditorComposite composite = getEditorComposite(editor); if (composite != null) { updateFileIcon(composite.getFile()); } } else if (FileEditor.PROP_VALID.equals(propertyName)) { final boolean valid = ((Boolean)e.getNewValue()).booleanValue(); if (!valid) { final FileEditor editor = (FileEditor)e.getSource(); LOG.assertTrue(editor != null); final EditorComposite composite = getEditorComposite(editor); if (composite != null) { closeFile(composite.getFile()); } } } } } /** * Gets events from VCS and updates color of myEditor tabs */ private final class MyFileStatusListener implements FileStatusListener { @Override public void fileStatusesChanged() { // update color of all open files assertDispatchThread(); LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()"); final VirtualFile[] openFiles = getOpenFiles(); for (int i = openFiles.length - 1; i >= 0; i--) { final VirtualFile file = openFiles[i]; LOG.assertTrue(file != null); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { if (LOG.isDebugEnabled()) { LOG.debug("updating file status in tab for " + file.getPath()); } updateFileStatus(file); } }, ModalityState.NON_MODAL, myProject.getDisposed()); } } @Override public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary) assertDispatchThread(); if (isFileOpen(file)) { updateFileStatus(file); } } private void updateFileStatus(final VirtualFile file) { updateFileColor(file); updateFileIcon(file); } } /** * Gets events from FileTypeManager and updates icons on tabs */ private final class MyFileTypeListener extends FileTypeListener.Adapter { @Override public void fileTypesChanged(@NotNull final FileTypeEvent event) { assertDispatchThread(); final VirtualFile[] openFiles = getOpenFiles(); for (int i = openFiles.length - 1; i >= 0; i--) { final VirtualFile file = openFiles[i]; LOG.assertTrue(file != null); updateFileIcon(file); } } } private class MyRootsListener extends ModuleRootAdapter { @Override public void rootsChanged(ModuleRootEvent event) { EditorFileSwapper[] swappers = Extensions.getExtensions(EditorFileSwapper.EP_NAME); for (EditorWindow eachWindow : getWindows()) { EditorWithProviderComposite selected = eachWindow.getSelectedEditor(); EditorWithProviderComposite[] editors = eachWindow.getEditors(); for (int i = 0; i < editors.length; i++) { EditorWithProviderComposite editor = editors[i]; VirtualFile file = editor.getFile(); if (!file.isValid()) continue; Pair newFilePair = null; for (EditorFileSwapper each : swappers) { newFilePair = each.getFileToSwapTo(myProject, editor); if (newFilePair != null) break; } if (newFilePair == null) continue; VirtualFile newFile = newFilePair.first; if (newFile == null) continue; // already open if (eachWindow.findFileIndex(newFile) != -1) continue; try { newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, i); Pair pair = openFileImpl2(eachWindow, newFile, editor == selected); if (newFilePair.second != null) { TextEditorImpl openedEditor = EditorFileSwapper.findSinglePsiAwareEditor(pair.first); if (openedEditor != null) { openedEditor.getEditor().getCaretModel().moveToOffset(newFilePair.second); openedEditor.getEditor().getScrollingModel().scrollToCaret(ScrollType.CENTER); } } } finally { newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null); } closeFile(file, eachWindow); } } } } /** * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT * and EDITOR_TAB_LIMIT, etc values. */ private final class MyUISettingsListener implements UISettingsListener { @Override public void uiSettingsChanged(final UISettings source) { assertDispatchThread(); setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE && !UISettings.getInstance().PRESENTATION_MODE); for (EditorsSplitters each : getAllSplitters()) { each.setTabsPlacement(source.EDITOR_TAB_PLACEMENT); each.trimToSize(source.EDITOR_TAB_LIMIT); // Tab layout policy if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) { each.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); } else { each.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); } } // "Mark modified files with asterisk" final VirtualFile[] openFiles = getOpenFiles(); for (int i = openFiles.length - 1; i >= 0; i--) { final VirtualFile file = openFiles[i]; updateFileIcon(file); updateFileName(file); updateFileBackgroundColor(file); } } } @Override public void closeAllFiles() { final VirtualFile[] openFiles = getSplitters().getOpenFiles(); for (VirtualFile openFile : openFiles) { closeFile(openFile); } } @Override @NotNull public VirtualFile[] getSiblings(@NotNull VirtualFile file) { return getOpenFiles(); } protected void queueUpdateFile(final VirtualFile file) { myQueue.queue(new Update(file) { @Override public void run() { if (isFileOpen(file)) { updateFileIcon(file); updateFileColor(file); updateFileBackgroundColor(file); } } }); } @Override public EditorsSplitters getSplittersFor(Component c) { EditorsSplitters splitters = null; DockContainer dockContainer = myDockManager.getContainerFor(c); if (dockContainer instanceof DockableEditorTabbedContainer) { splitters = ((DockableEditorTabbedContainer)dockContainer).getSplitters(); } if (splitters == null) { splitters = getMainSplitters(); } return splitters; } public List> getSelectionHistory() { List> copy = new ArrayList>(); for (Pair pair : mySelectionHistory) { if (pair.second.getFiles().length == 0) { final EditorWindow[] windows = pair.second.getOwner().getWindows(); if (windows.length > 0 && windows[0] != null && windows[0].getFiles().length > 0) { final Pair p = Pair.create(pair.first, windows[0]); if (!copy.contains(p)) { copy.add(p); } } } else { if (!copy.contains(pair)) { copy.add(pair); } } } mySelectionHistory.clear(); mySelectionHistory.addAll(copy); return mySelectionHistory; } public void addSelectionRecord(VirtualFile file, EditorWindow window) { final Pair record = Pair.create(file, window); mySelectionHistory.remove(record); mySelectionHistory.add(0, record); } public void removeSelectionRecord(VirtualFile file, EditorWindow window) { mySelectionHistory.remove(Pair.create(file, window)); } @Override public ActionCallback getReady(@NotNull Object requestor) { return myBusyObject.getReady(requestor); } }