2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.fileEditor.impl;
18 import com.intellij.AppTopics;
19 import com.intellij.ProjectTopics;
20 import com.intellij.ide.IdeBundle;
21 import com.intellij.ide.plugins.PluginManager;
22 import com.intellij.ide.ui.UISettings;
23 import com.intellij.ide.ui.UISettingsListener;
24 import com.intellij.injected.editor.VirtualFileWindow;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.application.ModalityState;
28 import com.intellij.openapi.application.ex.ApplicationManagerEx;
29 import com.intellij.openapi.application.impl.LaterInvocator;
30 import com.intellij.openapi.command.CommandProcessor;
31 import com.intellij.openapi.components.ProjectComponent;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.editor.Editor;
34 import com.intellij.openapi.editor.ex.EditorEx;
35 import com.intellij.openapi.extensions.Extensions;
36 import com.intellij.openapi.fileEditor.*;
37 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
38 import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
39 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
40 import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
41 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
42 import com.intellij.openapi.fileTypes.FileTypeEvent;
43 import com.intellij.openapi.fileTypes.FileTypeListener;
44 import com.intellij.openapi.project.DumbAware;
45 import com.intellij.openapi.project.DumbAwareRunnable;
46 import com.intellij.openapi.project.DumbService;
47 import com.intellij.openapi.project.Project;
48 import com.intellij.openapi.project.impl.ProjectImpl;
49 import com.intellij.openapi.roots.ModuleRootEvent;
50 import com.intellij.openapi.roots.ModuleRootListener;
51 import com.intellij.openapi.startup.StartupManager;
52 import com.intellij.openapi.util.*;
53 import com.intellij.openapi.vcs.FileStatus;
54 import com.intellij.openapi.vcs.FileStatusListener;
55 import com.intellij.openapi.vcs.FileStatusManager;
56 import com.intellij.openapi.vfs.*;
57 import com.intellij.openapi.wm.ToolWindowManager;
58 import com.intellij.openapi.wm.WindowManager;
59 import com.intellij.openapi.wm.ex.StatusBarEx;
60 import com.intellij.openapi.wm.ex.WindowManagerEx;
61 import com.intellij.openapi.wm.impl.FrameTitleBuilder;
62 import com.intellij.openapi.wm.impl.IdeFrameImpl;
63 import com.intellij.util.containers.ContainerUtil;
64 import com.intellij.util.messages.MessageBusConnection;
65 import com.intellij.util.messages.impl.MessageListenerList;
66 import com.intellij.util.ui.update.MergingUpdateQueue;
67 import com.intellij.util.ui.update.Update;
68 import org.jdom.Element;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
73 import javax.swing.border.Border;
74 import javax.swing.border.EmptyBorder;
76 import java.beans.PropertyChangeEvent;
77 import java.beans.PropertyChangeListener;
79 import java.util.ArrayList;
80 import java.util.Collection;
81 import java.util.List;
84 * @author Anton Katilin
85 * @author Eugene Belyaev
86 * @author Vladimir Kondratyev
88 public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable {
89 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl");
90 private static final Key<LocalFileSystem.WatchRequest> WATCH_REQUEST_KEY = Key.create("WATCH_REQUEST_KEY");
91 private static final Key<Boolean> DUMB_AWARE = Key.create("DUMB_AWARE");
93 private static final FileEditor[] EMPTY_EDITOR_ARRAY = {};
94 private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {};
96 private volatile JPanel myPanels;
97 private EditorsSplitters mySplitters;
98 private final Project myProject;
100 private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true, null);
103 * Removes invalid myEditor and updates "modified" status.
105 private final MyEditorPropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener();
107 private final List<EditorDataProvider> myDataProviders = new ArrayList<EditorDataProvider>();
109 public FileEditorManagerImpl(final Project project) {
110 /* ApplicationManager.getApplication().assertIsDispatchThread(); */
112 myListenerList = new MessageListenerList<FileEditorManagerListener>(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER);
115 public static boolean isDumbAware(FileEditor editor) {
116 return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE));
119 //-------------------------------------------------------------------------------
121 public JComponent getComponent() {
126 public EditorsSplitters getSplitters() {
131 private final Object myInitLock = new Object();
132 private void initUI() {
133 if (myPanels == null) {
134 synchronized (myInitLock) {
135 if (myPanels == null) {
136 myPanels = new JPanel(new BorderLayout());
137 myPanels.setBorder(new MyBorder());
138 mySplitters = new EditorsSplitters(this);
139 myPanels.add(mySplitters, BorderLayout.CENTER);
145 private class MyBorder implements Border {
147 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
151 public Insets getBorderInsets(Component c) {
152 boolean filesOpen = mySplitters != null && mySplitters.getOpenFiles().length > 0;
153 return new Insets(filesOpen ? 1 : 0, 0, 0, 0);
157 public boolean isBorderOpaque() {
162 public JComponent getPreferredFocusedComponent() {
164 final EditorWindow window = getSplitters().getCurrentWindow();
165 if (window != null) {
166 final EditorWithProviderComposite editor = window.getSelectedEditor();
167 if (editor != null) {
168 return editor.getPreferredFocusedComponent();
174 //-------------------------------------------------------
177 * @return color of the <code>file</code> which corresponds to the
180 public Color getFileColor(@NotNull final VirtualFile file) {
181 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
182 Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : Color.BLACK;
183 if (statusColor == null) statusColor = Color.BLACK;
187 public boolean isProblem(@NotNull final VirtualFile file) {
191 public String getFileTooltipText(VirtualFile file) {
192 return file.getPresentableUrl();
195 public void updateFilePresentation(VirtualFile file) {
196 if (!isFileOpen(file)) return;
198 updateFileColor(file);
199 updateFileIcon(file);
200 updateFileName(file);
201 updateFileBackgroundColor(file);
205 * Updates tab color for the specified <code>file</code>. The <code>file</code>
206 * should be opened in the myEditor, otherwise the method throws an assertion.
208 private void updateFileColor(final VirtualFile file) {
209 getSplitters().updateFileColor(file);
212 private void updateFileBackgroundColor(final VirtualFile file) {
213 getSplitters().updateFileBackgroundColor(file);
217 * Updates tab icon for the specified <code>file</code>. The <code>file</code>
218 * should be opened in the myEditor, otherwise the method throws an assertion.
220 protected void updateFileIcon(final VirtualFile file) {
221 getSplitters().updateFileIcon(file);
225 * Updates tab title and tab tool tip for the specified <code>file</code>
227 void updateFileName(@Nullable final VirtualFile file) {
228 // Queue here is to prevent title flickering when tab is being closed and two events arriving: with component==null and component==next focused tab
229 // only the last event makes sense to handle
230 myQueue.queue(new Update("UpdateFileName "+(file==null?"":file.getPath())) {
231 public boolean isExpired() {
232 return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid());
236 final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx();
237 final IdeFrameImpl frame = windowManagerEx.getFrame(myProject);
238 LOG.assertTrue(frame != null);
239 getSplitters().updateFileName(file);
240 File ioFile = file == null ? null : new File(file.getPresentableUrl());
241 frame.setFileTitle(file == null ? null : FrameTitleBuilder.getInstance().getFileTitle(myProject, file), ioFile);
246 //-------------------------------------------------------
249 public VirtualFile getFile(@NotNull final FileEditor editor) {
250 final EditorComposite editorComposite = getEditorComposite(editor);
251 if (editorComposite != null) {
252 return editorComposite.getFile();
257 public void unsplitWindow() {
258 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
259 if (currentWindow != null) {
260 currentWindow.unsplit(true);
264 public void unsplitAllWindow() {
265 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
266 if (currentWindow != null) {
267 currentWindow.unsplitAll();
272 public int getWindowSplitCount() {
273 return getSplitters().getSplitCount();
277 public EditorWindow[] getWindows() {
278 return getSplitters().getWindows();
281 public EditorWindow getNextWindow(@NotNull final EditorWindow window) {
282 final EditorWindow[] windows = getSplitters().getOrderedWindows();
283 for (int i = 0; i != windows.length; ++i) {
284 if (windows[i].equals(window)) {
285 return windows[(i + 1) % windows.length];
288 LOG.error("Not window found");
292 public EditorWindow getPrevWindow(@NotNull final EditorWindow window) {
293 final EditorWindow[] windows = getSplitters().getOrderedWindows();
294 for (int i = 0; i != windows.length; ++i) {
295 if (windows[i].equals(window)) {
296 return windows[(i + windows.length - 1) % windows.length];
299 LOG.error("Not window found");
303 public void createSplitter(final int orientation, @Nullable final EditorWindow window) {
304 // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current'
305 if (window != null) {
306 window.split(orientation, true, null, false);
308 // otherwise we'll split the current window, if any
310 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
311 if (currentWindow != null) {
312 currentWindow.split(orientation, true, null, false);
317 public void changeSplitterOrientation() {
318 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
319 if (currentWindow != null) {
320 currentWindow.changeOrientation();
325 public void flipTabs() {
327 if (myTabs == null) {
328 myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT);
329 remove (mySplitters);
330 add (myTabs, BorderLayout.CENTER);
334 add (mySplitters, BorderLayout.CENTER);
339 myPanels.revalidate();
342 public boolean tabsMode() {
346 private void setTabsMode(final boolean mode) {
347 if (tabsMode() != mode) {
350 //LOG.assertTrue (tabsMode () == mode);
354 public boolean isInSplitter() {
355 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
356 return currentWindow != null && currentWindow.inSplitter();
359 public boolean hasOpenedFile() {
360 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
361 return currentWindow != null && currentWindow.getSelectedEditor() != null;
364 public VirtualFile getCurrentFile() {
365 return getSplitters().getCurrentFile();
368 public EditorWindow getCurrentWindow() {
369 return getSplitters().getCurrentWindow();
372 public void setCurrentWindow(final EditorWindow window) {
373 getSplitters().setCurrentWindow(window, true);
376 public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) {
377 assertDispatchThread();
379 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
381 if (window.isFileOpen(file)) {
382 window.closeFile(file);
383 final List<EditorWindow> windows = getSplitters().findWindows(file);
384 if (windows.isEmpty()) { // no more windows containing this file left
385 final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
386 if (request != null) {
387 LocalFileSystem.getInstance().removeWatchedRoot(request);
392 }, IdeBundle.message("command.close.active.editor"), null);
395 //============================= EditorManager methods ================================
397 public void closeFile(@NotNull final VirtualFile file) {
398 closeFile(file, true);
401 public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus) {
402 assertDispatchThread();
404 final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
405 if (request != null) {
406 LocalFileSystem.getInstance().removeWatchedRoot(request);
409 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
411 closeFileImpl(file, moveFocus);
417 private VirtualFile findNextFile(final VirtualFile file) {
418 final EditorWindow [] windows = getWindows(); // TODO: use current file as base
419 for (int i = 0; i != windows.length; ++ i) {
420 final VirtualFile[] files = windows[i].getFiles();
421 for (final VirtualFile fileAt : files) {
422 if (fileAt != file) {
430 private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus) {
431 assertDispatchThread();
432 getSplitters().runChange(new Runnable() {
434 final List<EditorWindow> windows = getSplitters().findWindows(file);
435 if (!windows.isEmpty()) {
436 final VirtualFile nextFile = findNextFile(file);
437 for (final EditorWindow window : windows) {
438 LOG.assertTrue(window.getSelectedEditor() != null);
439 window.closeFile(file, false, moveFocus);
440 if (window.getTabCount() == 0 && nextFile != null) {
441 EditorWithProviderComposite newComposite = newEditorComposite(nextFile);
442 window.setEditor(newComposite, moveFocus); // newComposite can be null
445 // cleanup windows with no tabs
446 for (final EditorWindow window : windows) {
447 if (window.isDisposed()) {
448 // call to window.unsplit() which might make its sibling disposed
451 if (window.getTabCount() == 0) {
452 window.unsplit(false);
460 //-------------------------------------- Open File ----------------------------------------
462 @NotNull public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull final VirtualFile file, final boolean focusEditor) {
463 if (!file.isValid()) {
464 throw new IllegalArgumentException("file is not valid: " + file);
466 assertDispatchThread();
467 return openFileImpl2(getSplitters().getOrCreateCurrentWindow(file), file, focusEditor, null);
470 @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl2(final EditorWindow window, final VirtualFile file, final boolean focusEditor,
471 final HistoryEntry entry) {
472 final Ref<Pair<FileEditor[], FileEditorProvider[]>> resHolder = new Ref<Pair<FileEditor[], FileEditorProvider[]>>();
473 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
475 resHolder.set(openFileImpl3(window, file, focusEditor, entry, true));
478 return resHolder.get();
482 * @param file to be opened. Unlike openFile method, file can be
483 * invalid. For example, all file were invalidate and they are being
484 * removed one by one. If we have removed one invalid file, then another
485 * invalid file become selected. That's why we do not require that
486 * passed file is valid.
487 * @param entry map between FileEditorProvider and FileEditorState. If this parameter
490 @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl3(final EditorWindow window,
491 @NotNull final VirtualFile file,
492 final boolean focusEditor,
493 final HistoryEntry entry,
496 FileEditor[] editors;
497 FileEditorProvider[] providers;
498 final EditorWithProviderComposite newSelectedComposite;
499 boolean newEditorCreated = false;
501 final boolean open = window.isFileOpen(file);
503 // File is already opened. In this case we have to just select existing EditorComposite
504 newSelectedComposite = window.findFileComposite(file);
505 LOG.assertTrue(newSelectedComposite != null);
507 editors = newSelectedComposite.getEditors();
508 providers = newSelectedComposite.getProviders();
511 // File is not opened yet. In this case we have to create editors
512 // and select the created EditorComposite.
513 final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
514 providers = editorProviderManager.getProviders(myProject, file);
515 if (DumbService.getInstance(myProject).isDumb()) {
516 final List<FileEditorProvider> dumbAware = ContainerUtil.findAll(providers, new Condition<FileEditorProvider>() {
517 public boolean value(FileEditorProvider fileEditorProvider) {
518 return DumbService.isDumbAware(fileEditorProvider);
521 providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]);
524 if (providers.length == 0) {
525 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
527 newEditorCreated = true;
529 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(this, file);
531 editors = new FileEditor[providers.length];
532 for (int i = 0; i < providers.length; i++) {
534 final FileEditorProvider provider = providers[i];
535 LOG.assertTrue(provider != null);
536 LOG.assertTrue(provider.accept(myProject, file));
537 final FileEditor editor = provider.createEditor(myProject, file);
538 LOG.assertTrue(editor != null);
539 LOG.assertTrue(editor.isValid());
541 // Register PropertyChangeListener into editor
542 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
543 editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider));
545 if (current && editor instanceof TextEditorImpl) {
546 ((TextEditorImpl)editor).initFolding();
549 catch (Exception e) {
552 catch (AssertionError e) {
557 // Now we have to create EditorComposite and insert it into the TabbedEditorComponent.
558 // After that we have to select opened editor.
559 newSelectedComposite = new EditorWithProviderComposite(file, editors, providers, this);
562 window.setEditor(newSelectedComposite, focusEditor);
564 final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
565 for (int i = 0; i < editors.length; i++) {
566 final FileEditor editor = editors[i];
567 if (editor instanceof TextEditor) {
569 // This code prevents "jumping" on next repaint.
570 ((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
573 final FileEditorProvider provider = providers[i];//getProvider(editor);
575 // Restore editor state
576 FileEditorState state = null;
578 state = entry.getState(provider);
580 if (state == null && !open) {
581 // We have to try to get state from the history only in case
582 // if editor is not opened. Otherwise history enty might have a state
583 // out of sync with the current editor state.
584 state = editorHistoryManager.getState(file, provider);
587 editor.setState(state);
591 // Restore selected editor
592 final FileEditorProvider selectedProvider = editorHistoryManager.getSelectedProvider(file);
593 if (selectedProvider != null) {
594 final FileEditor[] _editors = newSelectedComposite.getEditors();
595 final FileEditorProvider[] _providers = newSelectedComposite.getProviders();
596 for (int i = _editors.length - 1; i >= 0; i--) {
597 final FileEditorProvider provider = _providers[i];//getProvider(_editors[i]);
598 if (provider.equals(selectedProvider)) {
599 newSelectedComposite.setSelectedEditor(i);
605 // Notify editors about selection changes
606 getSplitters().setCurrentWindow(window, false);
607 newSelectedComposite.getSelectedEditor().selectNotify();
609 if (newEditorCreated) {
610 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER).fileOpened(this, file);
612 //Add request to watch this editor's virtual file
613 final VirtualFile parentDir = file.getParent();
614 if (parentDir != null) {
615 final LocalFileSystem.WatchRequest request = LocalFileSystem.getInstance().addRootToWatch(parentDir.getPath(), false);
616 file.putUserData(WATCH_REQUEST_KEY, request);
620 //[jeka] this is a hack to support back-forward navigation
621 // previously here was incorrect call to fireSelectionChanged() with a side-effect
622 ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged();
624 // Transfer focus into editor
625 if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) {
627 //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer);
628 window.setAsCurrentWindow(false);
629 ToolWindowManager.getInstance(myProject).activateEditorComponent();
633 // Update frame and tab title
634 updateFileName(file);
636 // Make back/forward work
637 IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
639 return Pair.create(editors, providers);
642 private void setSelectedEditor(VirtualFile file, String fileEditorProviderId) {
643 EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
644 if (composite == null) {
645 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
647 if (composites.isEmpty()) return;
648 composite = composites.get(0);
651 final FileEditorProvider[] editorProviders = composite.getProviders();
652 final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond();
654 for (int i = 0; i < editorProviders.length; i++) {
655 if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) {
656 composite.setSelectedEditor(i);
657 composite.getSelectedEditor().selectNotify();
663 private EditorWithProviderComposite newEditorComposite(final VirtualFile file) {
668 final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
669 final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
670 final FileEditor[] editors = new FileEditor[providers.length];
671 for (int i = 0; i < providers.length; i++) {
672 final FileEditorProvider provider = providers[i];
673 LOG.assertTrue(provider != null);
674 LOG.assertTrue(provider.accept(myProject, file));
675 final FileEditor editor = provider.createEditor(myProject, file);
677 LOG.assertTrue(editor.isValid());
678 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
681 final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this);
682 final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
683 for (int i = 0; i < editors.length; i++) {
684 final FileEditor editor = editors[i];
685 if (editor instanceof TextEditor) {
687 // This code prevents "jumping" on next repaint.
688 //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
691 final FileEditorProvider provider = providers[i];
693 // Restore myEditor state
694 FileEditorState state = editorHistoryManager.getState(file, provider);
696 editor.setState(state);
703 public List<FileEditor> openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
704 assertDispatchThread();
705 if (descriptor.getFile() instanceof VirtualFileWindow) {
706 VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile();
707 int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset());
708 OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset);
709 return openEditor(realDescriptor, focusEditor);
712 final List<FileEditor> result = new ArrayList<FileEditor>();
713 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
715 VirtualFile file = descriptor.getFile();
716 final FileEditor[] editors = openFile(file, focusEditor);
717 ContainerUtil.addAll(result, editors);
719 boolean navigated = false;
720 for (final FileEditor editor : editors) {
721 if (editor instanceof NavigatableFileEditor &&
722 getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor
723 navigated = navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor);
724 if (navigated) break;
729 for (final FileEditor editor : editors) {
730 if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors
731 if (navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor)) {
743 private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) {
744 if (editor.canNavigateTo(descriptor)) {
745 setSelectedEditor(editor);
746 editor.navigateTo(descriptor);
753 private void setSelectedEditor(final FileEditor editor) {
754 final EditorWithProviderComposite composite = getEditorComposite(editor);
755 if (composite == null) return;
757 final FileEditor[] editors = composite.getEditors();
758 for (int i = 0; i < editors.length; i++) {
759 final FileEditor each = editors[i];
760 if (editor == each) {
761 composite.setSelectedEditor(i);
762 composite.getSelectedEditor().selectNotify();
769 public Project getProject() {
773 public void registerExtraEditorDataProvider(@NotNull final EditorDataProvider provider, Disposable parentDisposable) {
774 myDataProviders.add(provider);
775 if (parentDisposable != null) {
776 Disposer.register(parentDisposable, new Disposable() {
777 public void dispose() {
778 myDataProviders.remove(provider);
785 public final Object getData(String dataId, Editor editor, final VirtualFile file) {
786 for (final EditorDataProvider dataProvider : myDataProviders) {
787 final Object o = dataProvider.getData(dataId, editor, file);
788 if (o != null) return o;
794 public Editor openTextEditor(final OpenFileDescriptor descriptor, final boolean focusEditor) {
795 final Collection<FileEditor> fileEditors = openEditor(descriptor, focusEditor);
796 for (FileEditor fileEditor : fileEditors) {
797 if (fileEditor instanceof TextEditor) {
798 setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId());
799 Editor editor = ((TextEditor)fileEditor).getEditor();
800 return getOpenedEditor(editor, focusEditor);
807 protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) {
811 public Editor getSelectedTextEditor() {
814 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
815 if (currentWindow != null) {
816 final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor();
817 if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) {
818 return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor();
826 public boolean isFileOpen(@NotNull final VirtualFile file) {
827 return getEditors(file).length != 0;
831 public VirtualFile[] getOpenFiles() {
832 return getSplitters().getOpenFiles();
836 public VirtualFile[] getSelectedFiles() {
837 return getSplitters().getSelectedFiles();
841 public FileEditor[] getSelectedEditors() {
842 return getSplitters().getSelectedEditors();
845 public FileEditor getSelectedEditor(@NotNull final VirtualFile file) {
846 final Pair<FileEditor, FileEditorProvider> selectedEditorWithProvider = getSelectedEditorWithProvider(file);
847 return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst();
851 public Pair<FileEditor, FileEditorProvider> getSelectedEditorWithProvider(@NotNull VirtualFile file) {
852 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
853 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
854 if (composite != null) {
855 return composite.getSelectedEditorWithProvider();
858 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
859 return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider();
863 public Pair<FileEditor[], FileEditorProvider[]> getEditorsWithProviders(@NotNull final VirtualFile file) {
866 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
867 if (composite != null) {
868 return Pair.create(composite.getEditors(), composite.getProviders());
871 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
872 if (!composites.isEmpty()) {
873 return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders());
876 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
881 public FileEditor[] getEditors(@NotNull VirtualFile file) {
883 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
885 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
886 if (composite != null) {
887 return composite.getEditors();
890 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
891 if (!composites.isEmpty()) {
892 return composites.get(0).getEditors();
895 return EMPTY_EDITOR_ARRAY;
901 public FileEditor[] getAllEditors(@NotNull VirtualFile file) {
902 List<EditorWithProviderComposite> editorComposites = getEditorComposites(file);
903 List<FileEditor> editors = new ArrayList<FileEditor>();
904 for (EditorWithProviderComposite composite : editorComposites) {
905 ContainerUtil.addAll(editors, composite.getEditors());
907 return editors.toArray(new FileEditor[editors.size()]);
911 private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) {
912 final EditorWindow editorWindow = getSplitters().getCurrentWindow();
913 if (editorWindow != null) {
914 return editorWindow.findFileComposite(virtualFile);
920 public List<EditorWithProviderComposite> getEditorComposites(final VirtualFile file) {
921 return getSplitters().findEditorComposites(file);
925 public FileEditor[] getAllEditors() {
927 final ArrayList<FileEditor> result = new ArrayList<FileEditor>();
928 final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
929 for (EditorWithProviderComposite editorsComposite : editorsComposites) {
930 final FileEditor[] editors = editorsComposite.getEditors();
931 ContainerUtil.addAll(result, editors);
933 return result.toArray(new FileEditor[result.size()]);
936 public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
937 addTopComponent(editor, annotationComponent);
940 public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
941 removeTopComponent(editor, annotationComponent);
944 public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
945 final EditorComposite composite = getEditorComposite(editor);
946 if (composite != null) {
947 composite.addTopComponent(editor, component);
951 public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
952 final EditorComposite composite = getEditorComposite(editor);
953 if (composite != null) {
954 composite.removeTopComponent(editor, component);
958 public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
959 final EditorComposite composite = getEditorComposite(editor);
960 if (composite != null) {
961 composite.addBottomComponent(editor, component);
965 public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
966 final EditorComposite composite = getEditorComposite(editor);
967 if (composite != null) {
968 composite.removeBottomComponent(editor, component);
972 private final MessageListenerList<FileEditorManagerListener> myListenerList;
974 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
975 myListenerList.add(listener);
978 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, final Disposable parentDisposable) {
979 myListenerList.add(listener, parentDisposable);
982 public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
983 myListenerList.remove(listener);
986 // ProjectComponent methods
988 public void projectOpened() {
989 //myFocusWatcher.install(myWindows.getComponent ());
990 getSplitters().startListeningFocus();
992 MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
994 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
995 if (fileStatusManager != null) {
997 * Updates tabs colors
999 final MyFileStatusListener myFileStatusListener = new MyFileStatusListener();
1000 fileStatusManager.addFileStatusListener(myFileStatusListener, myProject);
1002 connection.subscribe(AppTopics.FILE_TYPES, new MyFileTypeListener());
1003 connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootsListener());
1006 * Updates tabs names
1008 final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener();
1009 VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject);
1011 * Extends/cuts number of opened tabs. Also updates location of tabs.
1013 final MyUISettingsListener myUISettingsListener = new MyUISettingsListener();
1014 UISettings.getInstance().addUISettingsListener(myUISettingsListener, myProject);
1016 StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
1018 ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() {
1020 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1022 setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1023 getSplitters().openFiles();
1024 LaterInvocator.invokeLater(new Runnable() {
1026 long currentTime = System.nanoTime();
1027 Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME);
1028 if (startTime != null) {
1029 LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms");
1030 PluginManager.dumpPluginClassStatistics();
1043 public void projectClosed() {
1044 //myFocusWatcher.deinstall(myWindows.getComponent ());
1045 getSplitters().dispose();
1047 // Dispose created editors. We do not use use closeEditor method because
1048 // it fires event and changes history.
1052 // BaseCompomemnt methods
1055 public String getComponentName() {
1056 return "FileEditorManager";
1059 public void initComponent() { /* really do nothing */ }
1061 public void disposeComponent() { /* really do nothing */ }
1063 //JDOMExternalizable methods
1065 public void writeExternal(final Element element) {
1066 getSplitters().writeExternal(element);
1069 public void readExternal(final Element element) {
1070 getSplitters().readExternal(element);
1073 private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) {
1074 final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
1075 for (int i = editorsComposites.length - 1; i >= 0; i--) {
1076 final EditorWithProviderComposite composite = editorsComposites[i];
1077 final FileEditor[] editors = composite.getEditors();
1078 for (int j = editors.length - 1; j >= 0; j--) {
1079 final FileEditor _editor = editors[j];
1080 LOG.assertTrue(_editor != null);
1081 if (editor.equals(_editor)) {
1089 //======================= Misc =====================
1091 private static void assertDispatchThread() {
1092 ApplicationManager.getApplication().assertIsDispatchThread();
1094 private static void assertReadAccess() {
1095 ApplicationManager.getApplication().assertReadAccessAllowed();
1098 public void fireSelectionChanged(final EditorComposite oldSelectedComposite, final EditorComposite newSelectedComposite) {
1099 final VirtualFile oldSelectedFile = oldSelectedComposite != null ? oldSelectedComposite.getFile() : null;
1100 final VirtualFile newSelectedFile = newSelectedComposite != null ? newSelectedComposite.getFile() : null;
1102 final FileEditor oldSelectedEditor = oldSelectedComposite != null && !oldSelectedComposite.isDisposed() ? oldSelectedComposite.getSelectedEditor() : null;
1103 final FileEditor newSelectedEditor = newSelectedComposite != null && !newSelectedComposite.isDisposed() ? newSelectedComposite.getSelectedEditor() : null;
1105 final boolean filesEqual = oldSelectedFile == null ? newSelectedFile == null : oldSelectedFile.equals(newSelectedFile);
1106 final boolean editorsEqual = oldSelectedEditor == null ? newSelectedEditor == null : oldSelectedEditor.equals(newSelectedEditor);
1107 if (!filesEqual || !editorsEqual) {
1108 final FileEditorManagerEvent event =
1109 new FileEditorManagerEvent(this, oldSelectedFile, oldSelectedEditor, newSelectedFile, newSelectedEditor);
1110 final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
1111 publisher.selectionChanged(event);
1115 public boolean isChanged(@NotNull final EditorComposite editor) {
1116 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1117 if (fileStatusManager != null) {
1118 if (!fileStatusManager.getStatus(editor.getFile()).equals(FileStatus.NOT_CHANGED)) {
1125 public void disposeComposite(EditorWithProviderComposite editor) {
1126 if (getAllEditors().length == 0) {
1127 setCurrentWindow(null);
1130 if (editor.equals(getLastSelected())) {
1131 editor.getSelectedEditor().deselectNotify();
1132 getSplitters().setCurrentWindow(null, false);
1135 final FileEditor[] editors = editor.getEditors();
1136 final FileEditorProvider[] providers = editor.getProviders();
1138 final FileEditor selectedEditor = editor.getSelectedEditor();
1139 for (int i = editors.length - 1; i >= 0; i--) {
1140 final FileEditor editor1 = editors[i];
1141 final FileEditorProvider provider = providers[i];
1142 if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event)
1143 if (selectedEditor.equals(editor1)) {
1144 editor1.deselectNotify();
1147 editor1.removePropertyChangeListener(myEditorPropertyChangeListener);
1148 provider.disposeEditor(editor1);
1151 Disposer.dispose(editor);
1154 EditorComposite getLastSelected() {
1155 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
1156 if (currentWindow != null) {
1157 return currentWindow.getSelectedEditor();
1162 public void runChange(Runnable runnable) {
1163 getSplitters().runChange(runnable);
1166 //================== Listeners =====================
1169 * Closes deleted files. Closes file which are in the deleted directories.
1171 private final class MyVirtualFileListener extends VirtualFileAdapter {
1172 public void beforeFileDeletion(VirtualFileEvent e) {
1173 assertDispatchThread();
1174 final VirtualFile file = e.getFile();
1175 final VirtualFile[] openFiles = getOpenFiles();
1176 for (int i = openFiles.length - 1; i >= 0; i--) {
1177 if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1178 closeFile(openFiles[i]);
1183 public void propertyChanged(VirtualFilePropertyEvent e) {
1184 if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) {
1185 assertDispatchThread();
1186 final VirtualFile file = e.getFile();
1187 if (isFileOpen(file)) {
1188 updateFileName(file);
1189 updateFileIcon(file); // file type can change after renaming
1190 updateFileBackgroundColor(file);
1193 else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) {
1194 // TODO: message bus?
1195 updateIconAndStatusbar(e);
1199 private void updateIconAndStatusbar(final VirtualFilePropertyEvent e) {
1200 assertDispatchThread();
1201 final VirtualFile file = e.getFile();
1202 if (isFileOpen(file)) {
1203 updateFileIcon(file);
1204 if (file.equals(getSelectedFiles()[0])) { // update "write" status
1205 final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1206 assert statusBar != null;
1207 statusBar.updateWidgets();
1212 public void fileMoved(VirtualFileMoveEvent e) {
1213 final VirtualFile file = e.getFile();
1214 final VirtualFile[] openFiles = getOpenFiles();
1215 for (final VirtualFile openFile : openFiles) {
1216 if (VfsUtil.isAncestor(file, openFile, false)) {
1217 updateFileName(openFile);
1218 updateFileBackgroundColor(openFile);
1225 private final class MyVirtualFileListener extends VirtualFileAdapter {
1226 public void beforeFileDeletion(final VirtualFileEvent e) {
1227 assertDispatchThread();
1228 final VirtualFile file = e.getFile();
1229 final VirtualFile[] openFiles = getOpenFiles();
1230 for (int i = openFiles.length - 1; i >= 0; i--) {
1231 if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1232 closeFile(openFiles[i]);
1237 public void propertyChanged(final VirtualFilePropertyEvent e) {
1238 if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName())) {
1239 assertDispatchThread();
1240 final VirtualFile file = e.getFile();
1241 if (isFileOpen(file)) {
1242 if (file.equals(getSelectedFiles()[0])) { // update "write" status
1243 final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1244 LOG.assertTrue(statusBar != null);
1245 statusBar.setWriteStatus(!file.isWritable());
1251 //public void fileMoved(final VirtualFileMoveEvent e){ }
1255 public boolean isInsideChange() {
1256 return getSplitters().isInsideChange();
1259 private final class MyEditorPropertyChangeListener implements PropertyChangeListener {
1260 public void propertyChange(final PropertyChangeEvent e) {
1261 assertDispatchThread();
1263 final String propertyName = e.getPropertyName();
1264 if (FileEditor.PROP_MODIFIED.equals(propertyName)) {
1265 final FileEditor editor = (FileEditor)e.getSource();
1266 final EditorComposite composite = getEditorComposite(editor);
1267 if (composite != null) {
1268 updateFileIcon(composite.getFile());
1271 else if (FileEditor.PROP_VALID.equals(propertyName)) {
1272 final boolean valid = ((Boolean)e.getNewValue()).booleanValue();
1274 final FileEditor editor = (FileEditor)e.getSource();
1275 LOG.assertTrue(editor != null);
1276 final EditorComposite composite = getEditorComposite(editor);
1277 if (composite != null) {
1278 closeFile(composite.getFile());
1289 * Gets events from VCS and updates color of myEditor tabs
1291 private final class MyFileStatusListener implements FileStatusListener {
1292 public void fileStatusesChanged() { // update color of all open files
1293 assertDispatchThread();
1294 LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()");
1295 final VirtualFile[] openFiles = getOpenFiles();
1296 for (int i = openFiles.length - 1; i >= 0; i--) {
1297 final VirtualFile file = openFiles[i];
1298 LOG.assertTrue(file != null);
1299 ApplicationManager.getApplication().invokeLater(new Runnable() {
1301 if (LOG.isDebugEnabled()) {
1302 LOG.debug("updating file status in tab for " + file.getPath());
1304 updateFileStatus(file);
1306 }, ModalityState.NON_MODAL, myProject.getDisposed());
1310 public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary)
1311 assertDispatchThread();
1312 if (isFileOpen(file)) {
1313 updateFileStatus(file);
1317 private void updateFileStatus(final VirtualFile file) {
1318 updateFileColor(file);
1319 updateFileIcon(file);
1324 * Gets events from FileTypeManager and updates icons on tabs
1326 private final class MyFileTypeListener implements FileTypeListener {
1327 public void beforeFileTypesChanged(FileTypeEvent event) {
1330 public void fileTypesChanged(final FileTypeEvent event) {
1331 assertDispatchThread();
1332 final VirtualFile[] openFiles = getOpenFiles();
1333 for (int i = openFiles.length - 1; i >= 0; i--) {
1334 final VirtualFile file = openFiles[i];
1335 LOG.assertTrue(file != null);
1336 updateFileIcon(file);
1341 private class MyRootsListener implements ModuleRootListener {
1342 public void beforeRootsChange(ModuleRootEvent event) {
1345 public void rootsChanged(ModuleRootEvent event) {
1346 EditorFileSwapper[] swappers = Extensions.getExtensions(EditorFileSwapper.EP_NAME);
1348 for (EditorWindow eachWindow : getWindows()) {
1349 VirtualFile selected = eachWindow.getSelectedFile();
1350 VirtualFile[] files = eachWindow.getFiles();
1351 for (int i = 0; i < files.length - 1 + 1; i++) {
1352 VirtualFile eachFile = files[i];
1353 VirtualFile newFile = null;
1354 for (EditorFileSwapper each : swappers) {
1355 newFile = each.getFileToSwapTo(myProject, eachFile);
1357 if (newFile == null) continue;
1360 if (eachWindow.findFileIndex(newFile) != -1) continue;
1362 closeFile(eachFile, eachWindow);
1364 newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, i);
1365 openFile(newFile, eachFile == selected);
1368 newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null);
1376 * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT
1377 * and EDITOR_TAB_LIMIT, etc values.
1379 private final class MyUISettingsListener implements UISettingsListener {
1380 public void uiSettingsChanged(final UISettings source) {
1381 assertDispatchThread();
1382 setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1383 getSplitters().setTabsPlacement(source.EDITOR_TAB_PLACEMENT);
1384 getSplitters().trimToSize(source.EDITOR_TAB_LIMIT);
1386 // Tab layout policy
1387 if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) {
1388 getSplitters().setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
1391 getSplitters().setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
1394 // "Mark modified files with asterisk"
1395 final VirtualFile[] openFiles = getOpenFiles();
1396 for (int i = openFiles.length - 1; i >= 0; i--) {
1397 final VirtualFile file = openFiles[i];
1398 updateFileIcon(file);
1399 updateFileName(file);
1400 updateFileBackgroundColor(file);
1405 public void closeAllFiles() {
1406 final VirtualFile[] openFiles = getSplitters().getOpenFiles();
1407 for (VirtualFile openFile : openFiles) {
1408 closeFile(openFile);
1413 public VirtualFile[] getSiblings(VirtualFile file) {
1414 return getOpenFiles();
1417 protected void queueUpdateFile(final VirtualFile file) {
1418 myQueue.queue(new Update(file) {
1420 if (isFileOpen(file)) {
1421 updateFileIcon(file);
1422 updateFileColor(file);
1423 updateFileBackgroundColor(file);