| /* |
| * Copyright 2000-2013 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.PluginManager; |
| 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.ex.EditorEx; |
| 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.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<LocalFileSystem.WatchRequest> WATCH_REQUEST_KEY = Key.create("WATCH_REQUEST_KEY"); |
| private static final Key<Boolean> DUMB_AWARE = Key.create("DUMB_AWARE"); |
| |
| private static final FileEditor[] EMPTY_EDITOR_ARRAY = {}; |
| private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {}; |
| public static final Key<Boolean> 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<Pair<VirtualFile, EditorWindow>> mySelectionHistory = new ArrayList<Pair<VirtualFile, EditorWindow>>(); |
| private WeakReference<EditorComposite> myLastSelectedComposite = new WeakReference<EditorComposite>(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; |
| |
| public FileEditorManagerImpl(final Project project, DockManager dockManager) { |
| /* ApplicationManager.getApplication().assertIsDispatchThread(); */ |
| myProject = project; |
| myDockManager = dockManager; |
| myListenerList = |
| new MessageListenerList<FileEditorManagerListener>(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); |
| } |
| |
| 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<EditorsSplitters> getAllSplitters() { |
| HashSet<EditorsSplitters> all = new HashSet<EditorsSplitters>(); |
| all.add(getMainSplitters()); |
| Set<DockContainer> dockContainers = myDockManager.getContainers(); |
| for (DockContainer each : dockContainers) { |
| if (each instanceof DockableEditorTabbedContainer) { |
| all.add(((DockableEditorTabbedContainer)each).getSplitters()); |
| } |
| } |
| |
| return Collections.unmodifiableSet(all); |
| } |
| |
| private AsyncResult<EditorsSplitters> getActiveSplitters(boolean syncUsage) { |
| final boolean async = Registry.is("ide.windowSystem.asyncSplitters") && !syncUsage; |
| |
| final AsyncResult<EditorsSplitters> result = new AsyncResult<EditorsSplitters>(); |
| 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 { |
| run.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 <code>file</code> 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 <code>file</code>. The <code>file</code> |
| * should be opened in the myEditor, otherwise the method throws an assertion. |
| */ |
| private void updateFileColor(final VirtualFile file) { |
| Set<EditorsSplitters> all = getAllSplitters(); |
| for (EditorsSplitters each : all) { |
| each.updateFileColor(file); |
| } |
| } |
| |
| private void updateFileBackgroundColor(final VirtualFile file) { |
| Set<EditorsSplitters> all = getAllSplitters(); |
| for (EditorsSplitters each : all) { |
| each.updateFileBackgroundColor(file); |
| } |
| } |
| |
| /** |
| * Updates tab icon for the specified <code>file</code>. The <code>file</code> |
| * should be opened in the myEditor, otherwise the method throws an assertion. |
| */ |
| protected void updateFileIcon(final VirtualFile file) { |
| Set<EditorsSplitters> all = getAllSplitters(); |
| for (EditorsSplitters each : all) { |
| each.updateFileIcon(file); |
| } |
| } |
| |
| /** |
| * Updates tab title and tab tool tip for the specified <code>file</code> |
| */ |
| 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<EditorsSplitters> 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<EditorsSplitters> splitters = getAllSplitters(); |
| if (splitters.size() > 1) return true; |
| return getWindowSplitCount() > 1; |
| } |
| |
| @Override |
| @NotNull |
| public EditorWindow[] getWindows() { |
| ArrayList<EditorWindow> windows = new ArrayList<EditorWindow>(); |
| Set<EditorsSplitters> 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<EditorWindow> getActiveWindow() { |
| return _getActiveWindow(false); |
| } |
| |
| @NotNull |
| private AsyncResult<EditorWindow> _getActiveWindow(boolean now) { |
| final AsyncResult<EditorWindow> result = new AsyncResult<EditorWindow>(); |
| getActiveSplitters(now).doWhenDone(new AsyncResult.Handler<EditorsSplitters>() { |
| @Override |
| public void run(EditorsSplitters editorsSplitters) { |
| result.setDone(editorsSplitters.getCurrentWindow()); |
| } |
| }); |
| |
| return result; |
| } |
| |
| @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); |
| final List<EditorWindow> windows = window.getOwner().findWindows(file); |
| if (windows.isEmpty()) { // no more windows containing this file left |
| final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY); |
| if (request != null) { |
| LocalFileSystem.getInstance().removeWatchedRoot(request); |
| } |
| } |
| } |
| } |
| }, 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(); |
| |
| final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY); |
| if (request != null) { |
| LocalFileSystem.getInstance().removeWatchedRoot(request); |
| } |
| |
| 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<FileEditor[], FileEditorProvider[]> 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<EditorsSplitters> 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<FileEditor[], FileEditorProvider[]> openFileInNewWindow(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<FileEditor[], FileEditorProvider[]> 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<FileEditor[], FileEditorProvider[]> openFileImpl2(@NotNull final EditorWindow window, |
| @NotNull final VirtualFile file, |
| final boolean focusEditor) { |
| final Ref<Pair<FileEditor[], FileEditorProvider[]>> result = new Ref<Pair<FileEditor[], FileEditorProvider[]>>(); |
| 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<FileEditor[], FileEditorProvider[]> openFileImpl3(@NotNull final EditorWindow window, |
| @NotNull final VirtualFile file, |
| final boolean focusEditor, |
| @Nullable final HistoryEntry entry, |
| boolean current) { |
| return openFileImpl4(window, file, focusEditor, entry, current, -1); |
| } |
| |
| @NotNull |
| Pair<FileEditor[], FileEditorProvider[]> openFileImpl4(@NotNull final EditorWindow window, |
| @NotNull final VirtualFile file, |
| final boolean focusEditor, |
| @Nullable final HistoryEntry entry, |
| boolean current, |
| int index) { |
| // Open file |
| FileEditor[] editors; |
| FileEditorProvider[] providers; |
| final EditorWithProviderComposite newSelectedComposite; |
| boolean newEditorCreated = false; |
| |
| final boolean open = window.isFileOpen(file); |
| if (open) { |
| // File is already opened. In this case we have to just select existing EditorComposite |
| newSelectedComposite = window.findFileComposite(file); |
| LOG.assertTrue(newSelectedComposite != null); |
| |
| editors = newSelectedComposite.getEditors(); |
| providers = newSelectedComposite.getProviders(); |
| } |
| else { |
| if (UISettings.getInstance().EDITOR_TAB_PLACEMENT == UISettings.TABS_NONE || UISettings.getInstance().PRESENTATION_MODE) { |
| for (EditorWithProviderComposite composite : window.getEditors()) { |
| Disposer.dispose(composite); |
| } |
| } |
| |
| // File is not opened yet. In this case we have to create editors |
| // and select the created EditorComposite. |
| final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance(); |
| providers = editorProviderManager.getProviders(myProject, file); |
| if (DumbService.getInstance(myProject).isDumb()) { |
| final List<FileEditorProvider> dumbAware = ContainerUtil.findAll(providers, new Condition<FileEditorProvider>() { |
| @Override |
| public boolean value(FileEditorProvider fileEditorProvider) { |
| return DumbService.isDumbAware(fileEditorProvider); |
| } |
| }); |
| providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]); |
| } |
| |
| if (providers.length == 0) { |
| return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY); |
| } |
| newEditorCreated = true; |
| |
| getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(this, file); |
| |
| editors = new FileEditor[providers.length]; |
| for (int i = 0; i < providers.length; i++) { |
| try { |
| final FileEditorProvider provider = providers[i]; |
| LOG.assertTrue(provider != null); |
| LOG.assertTrue(provider.accept(myProject, file)); |
| final FileEditor editor = provider.createEditor(myProject, file); |
| LOG.assertTrue(editor != null); |
| LOG.assertTrue(editor.isValid()); |
| editors[i] = editor; |
| // Register PropertyChangeListener into editor |
| editor.addPropertyChangeListener(myEditorPropertyChangeListener); |
| editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider)); |
| |
| if (current && editor instanceof TextEditorImpl) { |
| ((TextEditorImpl)editor).initFolding(); |
| } |
| } |
| 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. |
| newSelectedComposite = new EditorWithProviderComposite(file, editors, providers, this); |
| |
| if (index >= 0) { |
| newSelectedComposite.getFile().putUserData(EditorWindow.INITIAL_INDEX_KEY, index); |
| } |
| } |
| |
| window.setEditor(newSelectedComposite, focusEditor); |
| |
| 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];//getProvider(editor); |
| |
| // Restore editor state |
| FileEditorState state = null; |
| if (entry != null) { |
| state = entry.getState(provider); |
| } |
| if (state == null && !open) { |
| // 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 = editorHistoryManager.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); |
| } |
| } |
| } |
| |
| // Restore selected editor |
| final FileEditorProvider[] _providers = newSelectedComposite.getProviders(); |
| |
| final FileEditorProvider selectedProvider; |
| if (entry == null) { |
| selectedProvider = ((FileEditorProviderManagerImpl)FileEditorProviderManager.getInstance()) |
| .getSelectedFileEditorProvider(editorHistoryManager, file, _providers); |
| } |
| else { |
| selectedProvider = entry.mySelectedProvider; |
| } |
| if (selectedProvider != null) { |
| final FileEditor[] _editors = newSelectedComposite.getEditors(); |
| for (int i = _editors.length - 1; i >= 0; i--) { |
| final FileEditorProvider provider = _providers[i];//getProvider(_editors[i]); |
| if (provider.equals(selectedProvider)) { |
| newSelectedComposite.setSelectedEditor(i); |
| break; |
| } |
| } |
| } |
| |
| // Notify editors about selection changes |
| window.getOwner().setCurrentWindow(window, focusEditor); |
| window.getOwner().afterFileOpen(file); |
| |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| newSelectedComposite.getSelectedEditor().selectNotify(); |
| } |
| }); |
| |
| final IdeFocusManager focusManager = IdeFocusManager.getInstance(myProject); |
| if (newEditorCreated) { |
| 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); |
| } |
| } |
| }); |
| |
| //Add request to watch this editor's virtual file |
| final VirtualFile parentDir = file.getParent(); |
| if (parentDir != null) { |
| final LocalFileSystem.WatchRequest request = LocalFileSystem.getInstance().addRootToWatch(parentDir.getPath(), false); |
| file.putUserData(WATCH_REQUEST_KEY, request); |
| } |
| } |
| |
| //[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(); |
| |
| // Transfer focus into editor |
| if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) { |
| if (focusEditor) { |
| //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer); |
| window.setAsCurrentWindow(true); |
| ToolWindowManager.getInstance(myProject).activateEditorComponent(); |
| focusManager.toFront(window.getOwner()); |
| } |
| } |
| |
| // Update frame and tab title |
| updateFileName(file); |
| |
| // Make back/forward work |
| IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation(); |
| |
| return Pair.create(editors, 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<EditorWithProviderComposite> 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<FileEditor> 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<FileEditor> result = new ArrayList<FileEditor>(); |
| 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<FileEditor> 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<VirtualFile> openFiles = new HashSet<VirtualFile>(); |
| for (EditorsSplitters each : getAllSplitters()) { |
| openFiles.addAll(Arrays.asList(each.getOpenFiles())); |
| } |
| |
| return VfsUtilCore.toVirtualFileArray(openFiles); |
| } |
| |
| @Override |
| @NotNull |
| public VirtualFile[] getSelectedFiles() { |
| HashSet<VirtualFile> selectedFiles = new HashSet<VirtualFile>(); |
| for (EditorsSplitters each : getAllSplitters()) { |
| selectedFiles.addAll(Arrays.asList(each.getSelectedFiles())); |
| } |
| |
| return VfsUtilCore.toVirtualFileArray(selectedFiles); |
| } |
| |
| @Override |
| @NotNull |
| public FileEditor[] getSelectedEditors() { |
| HashSet<FileEditor> selectedEditors = new HashSet<FileEditor>(); |
| 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<FileEditor, FileEditorProvider> selectedEditorWithProvider = getSelectedEditorWithProvider(file); |
| return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst(); |
| } |
| |
| |
| @Override |
| @Nullable |
| public Pair<FileEditor, FileEditorProvider> 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<EditorWithProviderComposite> composites = getEditorComposites(file); |
| return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider(); |
| } |
| |
| @Override |
| @NotNull |
| public Pair<FileEditor[], FileEditorProvider[]> getEditorsWithProviders(@NotNull final VirtualFile file) { |
| assertReadAccess(); |
| |
| final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); |
| if (composite != null) { |
| return Pair.create(composite.getEditors(), composite.getProviders()); |
| } |
| |
| final List<EditorWithProviderComposite> 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<EditorWithProviderComposite> 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<EditorWithProviderComposite> editorComposites = getEditorComposites(file); |
| if (editorComposites.isEmpty()) return EMPTY_EDITOR_ARRAY; |
| List<FileEditor> editors = new ArrayList<FileEditor>(); |
| 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<EditorWithProviderComposite> getEditorComposites(final VirtualFile file) { |
| ArrayList<EditorWithProviderComposite> result = new ArrayList<EditorWithProviderComposite>(); |
| Set<EditorsSplitters> all = getAllSplitters(); |
| for (EditorsSplitters each : all) { |
| result.addAll(each.findEditorComposites(file)); |
| } |
| return result; |
| } |
| |
| @Override |
| @NotNull |
| public FileEditor[] getAllEditors() { |
| assertReadAccess(); |
| final ArrayList<FileEditor> result = new ArrayList<FileEditor>(); |
| final Set<EditorsSplitters> 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); |
| } |
| |
| @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); |
| } |
| } |
| |
| @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<FileEditorManagerListener> 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() { |
| |
| 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"); |
| PluginManager.dumpPluginClassStatistics(); |
| } |
| } |
| }); |
| // 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<VirtualFile, FileEditor, FileEditorProvider> oldData = extract(myLastSelectedComposite.get()); |
| final Trinity<VirtualFile, FileEditor, FileEditorProvider> newData = extract(newSelectedComposite); |
| myLastSelectedComposite = new WeakReference<EditorComposite>(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<VirtualFile, FileEditor, FileEditorProvider> 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<FileEditor, FileEditorProvider> pair = composite.getSelectedEditorWithProvider(); |
| editor = pair.first; |
| provider = pair.second; |
| } |
| return new Trinity<VirtualFile, FileEditor, FileEditorProvider>(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<EditorsSplitters> target = new HashSet<EditorsSplitters>(); |
| 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(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(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(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(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<VirtualFile, Integer> 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<FileEditor[], FileEditorProvider[]> 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<Pair<VirtualFile, EditorWindow>> getSelectionHistory() { |
| List<Pair<VirtualFile, EditorWindow>> copy = new ArrayList<Pair<VirtualFile, EditorWindow>>(); |
| for (Pair<VirtualFile, EditorWindow> 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<VirtualFile, EditorWindow> 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<VirtualFile, EditorWindow> 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); |
| } |
| } |