ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / FileEditorManagerImpl.java
blob7396fa8fba3bc3b776db7b9669e1076ba8fafc3e
1 /*
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.ide.IdeBundle;
20 import com.intellij.ide.plugins.PluginManager;
21 import com.intellij.ide.ui.UISettings;
22 import com.intellij.ide.ui.UISettingsListener;
23 import com.intellij.injected.editor.VirtualFileWindow;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.application.ex.ApplicationManagerEx;
28 import com.intellij.openapi.application.impl.LaterInvocator;
29 import com.intellij.openapi.command.CommandProcessor;
30 import com.intellij.openapi.components.ProjectComponent;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.editor.ex.EditorEx;
34 import com.intellij.openapi.fileEditor.*;
35 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
36 import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
37 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
38 import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
39 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
40 import com.intellij.openapi.fileTypes.FileTypeEvent;
41 import com.intellij.openapi.fileTypes.FileTypeListener;
42 import com.intellij.openapi.project.DumbAware;
43 import com.intellij.openapi.project.DumbAwareRunnable;
44 import com.intellij.openapi.project.DumbService;
45 import com.intellij.openapi.project.Project;
46 import com.intellij.openapi.project.impl.ProjectImpl;
47 import com.intellij.openapi.startup.StartupManager;
48 import com.intellij.openapi.util.*;
49 import com.intellij.openapi.vcs.FileStatus;
50 import com.intellij.openapi.vcs.FileStatusListener;
51 import com.intellij.openapi.vcs.FileStatusManager;
52 import com.intellij.openapi.vfs.*;
53 import com.intellij.openapi.wm.ToolWindowManager;
54 import com.intellij.openapi.wm.WindowManager;
55 import com.intellij.openapi.wm.ex.StatusBarEx;
56 import com.intellij.openapi.wm.ex.WindowManagerEx;
57 import com.intellij.openapi.wm.impl.FrameTitleBuilder;
58 import com.intellij.openapi.wm.impl.IdeFrameImpl;
59 import com.intellij.util.containers.ContainerUtil;
60 import com.intellij.util.messages.MessageBusConnection;
61 import com.intellij.util.messages.impl.MessageListenerList;
62 import com.intellij.util.ui.update.MergingUpdateQueue;
63 import com.intellij.util.ui.update.Update;
64 import org.jdom.Element;
65 import org.jetbrains.annotations.NotNull;
66 import org.jetbrains.annotations.Nullable;
68 import javax.swing.*;
69 import java.awt.*;
70 import java.beans.PropertyChangeEvent;
71 import java.beans.PropertyChangeListener;
72 import java.io.File;
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.Collection;
76 import java.util.List;
78 /**
79 * @author Anton Katilin
80 * @author Eugene Belyaev
81 * @author Vladimir Kondratyev
83 public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable {
84 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl");
85 private static final Key<LocalFileSystem.WatchRequest> WATCH_REQUEST_KEY = Key.create("WATCH_REQUEST_KEY");
86 private static final Key<Boolean> DUMB_AWARE = Key.create("DUMB_AWARE");
88 private static final FileEditor[] EMPTY_EDITOR_ARRAY = {};
89 private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {};
91 private volatile JPanel myPanels;
92 private EditorsSplitters mySplitters;
93 private final Project myProject;
95 private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true, null);
97 /**
98 * Removes invalid myEditor and updates "modified" status.
100 private final MyEditorPropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener();
102 private final List<EditorDataProvider> myDataProviders = new ArrayList<EditorDataProvider>();
104 public FileEditorManagerImpl(final Project project) {
105 /* ApplicationManager.getApplication().assertIsDispatchThread(); */
106 myProject = project;
107 myListenerList = new MessageListenerList<FileEditorManagerListener>(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER);
110 public static boolean isDumbAware(FileEditor editor) {
111 return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE));
114 //-------------------------------------------------------------------------------
116 public JComponent getComponent() {
117 initUI();
118 return myPanels;
121 public EditorsSplitters getSplitters() {
122 initUI();
123 return mySplitters;
126 private final Object myInitLock = new Object();
127 private void initUI() {
128 if (myPanels == null) {
129 synchronized (myInitLock) {
130 if (myPanels == null) {
131 myPanels = new JPanel(new BorderLayout());
132 mySplitters = new EditorsSplitters(this);
133 myPanels.add(mySplitters, BorderLayout.CENTER);
139 public JComponent getPreferredFocusedComponent() {
140 assertReadAccess();
141 final EditorWindow window = getSplitters().getCurrentWindow();
142 if (window != null) {
143 final EditorWithProviderComposite editor = window.getSelectedEditor();
144 if (editor != null) {
145 return editor.getPreferredFocusedComponent();
148 return null;
151 //-------------------------------------------------------
154 * @return color of the <code>file</code> which corresponds to the
155 * file's status
157 public Color getFileColor(@NotNull final VirtualFile file) {
158 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
159 Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : Color.BLACK;
160 if (statusColor == null) statusColor = Color.BLACK;
161 return statusColor;
164 public boolean isProblem(@NotNull final VirtualFile file) {
165 return false;
168 public String getFileTooltipText(VirtualFile file) {
169 return file.getPresentableUrl();
172 public void updateFilePresentation(VirtualFile file) {
173 if (!isFileOpen(file)) return;
175 updateFileColor(file);
176 updateFileIcon(file);
177 updateFileName(file);
178 updateFileBackgroundColor(file);
182 * Updates tab color for the specified <code>file</code>. The <code>file</code>
183 * should be opened in the myEditor, otherwise the method throws an assertion.
185 private void updateFileColor(final VirtualFile file) {
186 getSplitters().updateFileColor(file);
189 private void updateFileBackgroundColor(final VirtualFile file) {
190 getSplitters().updateFileBackgroundColor(file);
194 * Updates tab icon for the specified <code>file</code>. The <code>file</code>
195 * should be opened in the myEditor, otherwise the method throws an assertion.
197 protected void updateFileIcon(final VirtualFile file) {
198 getSplitters().updateFileIcon(file);
202 * Updates tab title and tab tool tip for the specified <code>file</code>
204 void updateFileName(@Nullable final VirtualFile file) {
205 // Queue here is to prevent title flickering when tab is being closed and two events arriving: with component==null and component==next focused tab
206 // only the last event makes sense to handle
207 myQueue.queue(new Update("UpdateFileName "+(file==null?"":file.getPath())) {
208 public boolean isExpired() {
209 return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid());
212 public void run() {
213 final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx();
214 final IdeFrameImpl frame = windowManagerEx.getFrame(myProject);
215 LOG.assertTrue(frame != null);
216 getSplitters().updateFileName(file);
217 File ioFile = file == null ? null : new File(file.getPresentableUrl());
218 frame.setFileTitle(file == null ? null : FrameTitleBuilder.getInstance().getFileTitle(myProject, file), ioFile);
223 //-------------------------------------------------------
226 public VirtualFile getFile(@NotNull final FileEditor editor) {
227 final EditorComposite editorComposite = getEditorComposite(editor);
228 if (editorComposite != null) {
229 return editorComposite.getFile();
231 return null;
234 public void unsplitWindow() {
235 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
236 if (currentWindow != null) {
237 currentWindow.unsplit(true);
241 public void unsplitAllWindow() {
242 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
243 if (currentWindow != null) {
244 currentWindow.unsplitAll();
248 @NotNull
249 public EditorWindow[] getWindows() {
250 return getSplitters().getWindows();
253 public EditorWindow getNextWindow(@NotNull final EditorWindow window) {
254 final EditorWindow[] windows = getSplitters().getOrderedWindows();
255 for (int i = 0; i != windows.length; ++i) {
256 if (windows[i].equals(window)) {
257 return windows[(i + 1) % windows.length];
260 LOG.error("Not window found");
261 return null;
264 public EditorWindow getPrevWindow(@NotNull final EditorWindow window) {
265 final EditorWindow[] windows = getSplitters().getOrderedWindows();
266 for (int i = 0; i != windows.length; ++i) {
267 if (windows[i].equals(window)) {
268 return windows[(i + windows.length - 1) % windows.length];
271 LOG.error("Not window found");
272 return null;
275 public void createSplitter(final int orientation, @Nullable final EditorWindow window) {
276 // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current'
277 if (window != null) {
278 window.split(orientation);
280 // otherwise we'll split the current window, if any
281 else {
282 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
283 if (currentWindow != null) {
284 currentWindow.split(orientation);
289 public void changeSplitterOrientation() {
290 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
291 if (currentWindow != null) {
292 currentWindow.changeOrientation();
297 public void flipTabs() {
299 if (myTabs == null) {
300 myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT);
301 remove (mySplitters);
302 add (myTabs, BorderLayout.CENTER);
303 initTabs ();
304 } else {
305 remove (myTabs);
306 add (mySplitters, BorderLayout.CENTER);
307 myTabs.dispose ();
308 myTabs = null;
311 myPanels.revalidate();
314 public boolean tabsMode() {
315 return false;
318 private void setTabsMode(final boolean mode) {
319 if (tabsMode() != mode) {
320 flipTabs();
322 //LOG.assertTrue (tabsMode () == mode);
326 public boolean isInSplitter() {
327 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
328 return currentWindow != null && currentWindow.inSplitter();
331 public boolean hasOpenedFile() {
332 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
333 return currentWindow != null && currentWindow.getSelectedEditor() != null;
336 public VirtualFile getCurrentFile() {
337 return getSplitters().getCurrentFile();
340 public EditorWindow getCurrentWindow() {
341 return getSplitters().getCurrentWindow();
344 public void setCurrentWindow(final EditorWindow window) {
345 getSplitters().setCurrentWindow(window, true);
348 public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) {
349 assertDispatchThread();
351 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
352 public void run() {
353 if (window.isFileOpen(file)) {
354 window.closeFile(file);
355 final List<EditorWindow> windows = getSplitters().findWindows(file);
356 if (windows.isEmpty()) { // no more windows containing this file left
357 final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
358 if (request != null) {
359 LocalFileSystem.getInstance().removeWatchedRoot(request);
364 }, IdeBundle.message("command.close.active.editor"), null);
367 //============================= EditorManager methods ================================
369 public void closeFile(@NotNull final VirtualFile file) {
370 closeFile(file, true);
373 public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus) {
374 assertDispatchThread();
376 final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
377 if (request != null) {
378 LocalFileSystem.getInstance().removeWatchedRoot(request);
381 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
382 public void run() {
383 closeFileImpl(file, moveFocus);
385 }, "", null);
389 private VirtualFile findNextFile(final VirtualFile file) {
390 final EditorWindow [] windows = getWindows(); // TODO: use current file as base
391 for (int i = 0; i != windows.length; ++ i) {
392 final VirtualFile[] files = windows[i].getFiles();
393 for (final VirtualFile fileAt : files) {
394 if (fileAt != file) {
395 return fileAt;
399 return null;
402 private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus) {
403 assertDispatchThread();
404 getSplitters().runChange(new Runnable() {
405 public void run() {
406 final List<EditorWindow> windows = getSplitters().findWindows(file);
407 if (!windows.isEmpty()) {
408 final VirtualFile nextFile = findNextFile(file);
409 for (final EditorWindow window : windows) {
410 LOG.assertTrue(window.getSelectedEditor() != null);
411 window.closeFile(file, false, moveFocus);
412 if (window.getTabCount() == 0 && nextFile != null) {
413 EditorWithProviderComposite newComposite = newEditorComposite(nextFile);
414 window.setEditor(newComposite, moveFocus); // newComposite can be null
417 // cleanup windows with no tabs
418 for (final EditorWindow window : windows) {
419 if (window.isDisposed()) {
420 // call to window.unsplit() which might make its sibling disposed
421 continue;
423 if (window.getTabCount() == 0) {
424 window.unsplit(false);
432 //-------------------------------------- Open File ----------------------------------------
434 @NotNull public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull final VirtualFile file, final boolean focusEditor) {
435 if (!file.isValid()) {
436 throw new IllegalArgumentException("file is not valid: " + file);
438 assertDispatchThread();
439 return openFileImpl2(getSplitters().getOrCreateCurrentWindow(file), file, focusEditor, null);
442 @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl2(final EditorWindow window, final VirtualFile file, final boolean focusEditor,
443 final HistoryEntry entry) {
444 final Ref<Pair<FileEditor[], FileEditorProvider[]>> resHolder = new Ref<Pair<FileEditor[], FileEditorProvider[]>>();
445 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
446 public void run() {
447 resHolder.set(openFileImpl3(window, file, focusEditor, entry, true));
449 }, "", null);
450 return resHolder.get();
454 * @param file to be opened. Unlike openFile method, file can be
455 * invalid. For example, all file were invalidate and they are being
456 * removed one by one. If we have removed one invalid file, then another
457 * invalid file become selected. That's why we do not require that
458 * passed file is valid.
459 * @param entry map between FileEditorProvider and FileEditorState. If this parameter
460 * @param current
462 @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl3(final EditorWindow window,
463 @NotNull final VirtualFile file,
464 final boolean focusEditor,
465 final HistoryEntry entry,
466 boolean current) {
467 // Open file
468 FileEditor[] editors;
469 FileEditorProvider[] providers;
470 final EditorWithProviderComposite newSelectedComposite;
471 boolean newEditorCreated = false;
473 final boolean open = window.isFileOpen(file);
474 if (open) {
475 // File is already opened. In this case we have to just select existing EditorComposite
476 newSelectedComposite = window.findFileComposite(file);
477 LOG.assertTrue(newSelectedComposite != null);
479 editors = newSelectedComposite.getEditors();
480 providers = newSelectedComposite.getProviders();
482 else {
483 // File is not opened yet. In this case we have to create editors
484 // and select the created EditorComposite.
485 final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
486 providers = editorProviderManager.getProviders(myProject, file);
487 if (DumbService.getInstance(myProject).isDumb()) {
488 final List<FileEditorProvider> dumbAware = ContainerUtil.findAll(providers, new Condition<FileEditorProvider>() {
489 public boolean value(FileEditorProvider fileEditorProvider) {
490 return fileEditorProvider instanceof DumbAware;
493 providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]);
496 if (providers.length == 0) {
497 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
499 newEditorCreated = true;
501 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(this, file);
503 editors = new FileEditor[providers.length];
504 for (int i = 0; i < providers.length; i++) {
505 try {
506 final FileEditorProvider provider = providers[i];
507 LOG.assertTrue(provider != null);
508 LOG.assertTrue(provider.accept(myProject, file));
509 final FileEditor editor = provider.createEditor(myProject, file);
510 LOG.assertTrue(editor != null);
511 LOG.assertTrue(editor.isValid());
512 editors[i] = editor;
513 // Register PropertyChangeListener into editor
514 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
515 editor.putUserData(DUMB_AWARE, provider instanceof DumbAware);
517 if (current && editor instanceof TextEditorImpl) {
518 ((TextEditorImpl)editor).initFolding();
521 catch (Exception e) {
522 LOG.error(e);
524 catch (AssertionError e) {
525 LOG.error(e);
529 // Now we have to create EditorComposite and insert it into the TabbedEditorComponent.
530 // After that we have to select opened editor.
531 newSelectedComposite = new EditorWithProviderComposite(file, editors, providers, this);
534 window.setEditor(newSelectedComposite, focusEditor);
536 final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
537 for (int i = 0; i < editors.length; i++) {
538 final FileEditor editor = editors[i];
539 if (editor instanceof TextEditor) {
540 // hack!!!
541 // This code prevents "jumping" on next repaint.
542 ((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
545 final FileEditorProvider provider = providers[i];//getProvider(editor);
547 // Restore editor state
548 FileEditorState state = null;
549 if (entry != null) {
550 state = entry.getState(provider);
552 if (state == null && !open) {
553 // We have to try to get state from the history only in case
554 // if editor is not opened. Otherwise history enty might have a state
555 // out of sync with the current editor state.
556 state = editorHistoryManager.getState(file, provider);
558 if (state != null) {
559 editor.setState(state);
563 // Restore selected editor
564 final FileEditorProvider selectedProvider = editorHistoryManager.getSelectedProvider(file);
565 if (selectedProvider != null) {
566 final FileEditor[] _editors = newSelectedComposite.getEditors();
567 final FileEditorProvider[] _providers = newSelectedComposite.getProviders();
568 for (int i = _editors.length - 1; i >= 0; i--) {
569 final FileEditorProvider provider = _providers[i];//getProvider(_editors[i]);
570 if (provider.equals(selectedProvider)) {
571 newSelectedComposite.setSelectedEditor(i);
572 break;
577 // Notify editors about selection changes
578 getSplitters().setCurrentWindow(window, false);
579 newSelectedComposite.getSelectedEditor().selectNotify();
581 if (newEditorCreated) {
582 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER).fileOpened(this, file);
584 //Add request to watch this editor's virtual file
585 final VirtualFile parentDir = file.getParent();
586 if (parentDir != null) {
587 final LocalFileSystem.WatchRequest request = LocalFileSystem.getInstance().addRootToWatch(parentDir.getPath(), false);
588 file.putUserData(WATCH_REQUEST_KEY, request);
592 //[jeka] this is a hack to support back-forward navigation
593 // previously here was incorrect call to fireSelectionChanged() with a side-effect
594 ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged();
596 // Transfer focus into editor
597 if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) {
598 if (focusEditor) {
599 //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer);
600 window.setAsCurrentWindow(false);
601 ToolWindowManager.getInstance(myProject).activateEditorComponent();
605 // Update frame and tab title
606 updateFileName(file);
608 // Make back/forward work
609 IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
611 return Pair.create(editors, providers);
614 private void setSelectedEditor(VirtualFile file, String fileEditorProviderId) {
615 EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
616 if (composite == null) {
617 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
619 if (composites.isEmpty()) return;
620 composite = composites.get(0);
623 final FileEditorProvider[] editorProviders = composite.getProviders();
624 final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond();
626 for (int i = 0; i < editorProviders.length; i++) {
627 if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) {
628 composite.setSelectedEditor(i);
629 composite.getSelectedEditor().selectNotify();
635 private EditorWithProviderComposite newEditorComposite(final VirtualFile file) {
636 if (file == null) {
637 return null;
640 final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
641 final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
642 final FileEditor[] editors = new FileEditor[providers.length];
643 for (int i = 0; i < providers.length; i++) {
644 final FileEditorProvider provider = providers[i];
645 LOG.assertTrue(provider != null);
646 LOG.assertTrue(provider.accept(myProject, file));
647 final FileEditor editor = provider.createEditor(myProject, file);
648 editors[i] = editor;
649 LOG.assertTrue(editor.isValid());
650 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
653 final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this);
654 final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
655 for (int i = 0; i < editors.length; i++) {
656 final FileEditor editor = editors[i];
657 if (editor instanceof TextEditor) {
658 // hack!!!
659 // This code prevents "jumping" on next repaint.
660 //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
663 final FileEditorProvider provider = providers[i];
665 // Restore myEditor state
666 FileEditorState state = editorHistoryManager.getState(file, provider);
667 if (state != null) {
668 editor.setState(state);
671 return newComposite;
674 @NotNull
675 public List<FileEditor> openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
676 assertDispatchThread();
677 if (descriptor.getFile() instanceof VirtualFileWindow) {
678 VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile();
679 int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset());
680 OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset);
681 return openEditor(realDescriptor, focusEditor);
684 final List<FileEditor> result = new ArrayList<FileEditor>();
685 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
686 public void run() {
687 VirtualFile file = descriptor.getFile();
688 final FileEditor[] editors = openFile(file, focusEditor);
689 result.addAll(Arrays.asList(editors));
691 boolean navigated = false;
692 for (final FileEditor editor : editors) {
693 if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor
694 navigated = navigateAndSelectEditor((NavigatableFileEditor) editor, descriptor);
695 if (navigated) break;
699 if (!navigated) {
700 for (final FileEditor editor : editors) {
701 if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors
702 if (navigateAndSelectEditor((NavigatableFileEditor) editor, descriptor)) {
703 break;
709 }, "", null);
711 return result;
714 private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) {
715 if (editor.canNavigateTo(descriptor)) {
716 setSelectedEditor(editor);
717 editor.navigateTo(descriptor);
718 return true;
721 return false;
724 private void setSelectedEditor(final FileEditor editor) {
725 final EditorWithProviderComposite composite = getEditorComposite(editor);
726 if (composite == null) return;
728 final FileEditor[] editors = composite.getEditors();
729 for (int i = 0; i < editors.length; i++) {
730 final FileEditor each = editors[i];
731 if (editor == each) {
732 composite.setSelectedEditor(i);
733 composite.getSelectedEditor().selectNotify();
734 break;
739 @NotNull
740 public Project getProject() {
741 return myProject;
744 public void registerExtraEditorDataProvider(@NotNull final EditorDataProvider provider, Disposable parentDisposable) {
745 myDataProviders.add(provider);
746 if (parentDisposable != null) {
747 Disposer.register(parentDisposable, new Disposable() {
748 public void dispose() {
749 myDataProviders.remove(provider);
755 @Nullable
756 public final Object getData(String dataId, Editor editor, final VirtualFile file) {
757 for (final EditorDataProvider dataProvider : myDataProviders) {
758 final Object o = dataProvider.getData(dataId, editor, file);
759 if (o != null) return o;
761 return null;
764 @Nullable
765 public Editor openTextEditor(final OpenFileDescriptor descriptor, final boolean focusEditor) {
766 final Collection<FileEditor> fileEditors = openEditor(descriptor, focusEditor);
767 for (FileEditor fileEditor : fileEditors) {
768 if (fileEditor instanceof TextEditor) {
769 setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId());
770 Editor editor = ((TextEditor)fileEditor).getEditor();
771 return getOpenedEditor(editor, focusEditor);
775 return null;
778 protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) {
779 return editor;
782 public Editor getSelectedTextEditor() {
783 assertReadAccess();
785 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
786 if (currentWindow != null) {
787 final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor();
788 if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) {
789 return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor();
793 return null;
797 public boolean isFileOpen(@NotNull final VirtualFile file) {
798 return getEditors(file).length != 0;
801 @NotNull
802 public VirtualFile[] getOpenFiles() {
803 return getSplitters().getOpenFiles();
806 @NotNull
807 public VirtualFile[] getSelectedFiles() {
808 return getSplitters().getSelectedFiles();
811 @NotNull
812 public FileEditor[] getSelectedEditors() {
813 return getSplitters().getSelectedEditors();
816 public FileEditor getSelectedEditor(@NotNull final VirtualFile file) {
817 final Pair<FileEditor, FileEditorProvider> selectedEditorWithProvider = getSelectedEditorWithProvider(file);
818 return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst();
822 public Pair<FileEditor, FileEditorProvider> getSelectedEditorWithProvider(@NotNull VirtualFile file) {
823 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
824 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
825 if (composite != null) {
826 return composite.getSelectedEditorWithProvider();
829 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
830 return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider();
833 @NotNull
834 public Pair<FileEditor[], FileEditorProvider[]> getEditorsWithProviders(@NotNull final VirtualFile file) {
835 assertReadAccess();
837 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
838 if (composite != null) {
839 return Pair.create(composite.getEditors(), composite.getProviders());
842 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
843 if (!composites.isEmpty()) {
844 return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders());
846 else {
847 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
851 @NotNull
852 public FileEditor[] getEditors(@NotNull VirtualFile file) {
853 assertReadAccess();
854 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
856 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
857 if (composite != null) {
858 return composite.getEditors();
861 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
862 if (!composites.isEmpty()) {
863 return composites.get(0).getEditors();
865 else {
866 return EMPTY_EDITOR_ARRAY;
870 @NotNull
871 @Override
872 public FileEditor[] getAllEditors(@NotNull VirtualFile file) {
873 List<EditorWithProviderComposite> editorComposites = getEditorComposites(file);
874 List<FileEditor> editors = new ArrayList<FileEditor>();
875 for (EditorWithProviderComposite composite : editorComposites) {
876 editors.addAll(Arrays.asList(composite.getEditors()));
878 return editors.toArray(new FileEditor[editors.size()]);
881 @Nullable
882 private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) {
883 final EditorWindow editorWindow = getSplitters().getCurrentWindow();
884 if (editorWindow != null) {
885 return editorWindow.findFileComposite(virtualFile);
887 return null;
890 @NotNull
891 public List<EditorWithProviderComposite> getEditorComposites(final VirtualFile file) {
892 return getSplitters().findEditorComposites(file);
895 @NotNull
896 public FileEditor[] getAllEditors() {
897 assertReadAccess();
898 final ArrayList<FileEditor> result = new ArrayList<FileEditor>();
899 final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
900 for (EditorWithProviderComposite editorsComposite : editorsComposites) {
901 final FileEditor[] editors = editorsComposite.getEditors();
902 result.addAll(Arrays.asList(editors));
904 return result.toArray(new FileEditor[result.size()]);
907 public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
908 final EditorComposite composite = getEditorComposite(editor);
909 if (composite != null) {
910 composite.getPane(editor).addInfo(annotationComponent);
914 public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
915 final EditorComposite composite = getEditorComposite(editor);
916 if (composite != null) {
917 composite.getPane(editor).removeInfo(annotationComponent);
921 public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
922 final EditorComposite composite = getEditorComposite(editor);
923 if (composite != null) {
924 composite.addTopComponent(editor, component);
928 public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
929 final EditorComposite composite = getEditorComposite(editor);
930 if (composite != null) {
931 composite.removeTopComponent(editor, component);
935 public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
936 final EditorComposite composite = getEditorComposite(editor);
937 if (composite != null) {
938 composite.addBottomComponent(editor, component);
942 public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
943 final EditorComposite composite = getEditorComposite(editor);
944 if (composite != null) {
945 composite.removeBottomComponent(editor, component);
949 private final MessageListenerList<FileEditorManagerListener> myListenerList;
951 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
952 myListenerList.add(listener);
955 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, final Disposable parentDisposable) {
956 myListenerList.add(listener, parentDisposable);
959 public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
960 myListenerList.remove(listener);
963 // ProjectComponent methods
965 public void projectOpened() {
966 //myFocusWatcher.install(myWindows.getComponent ());
967 getSplitters().startListeningFocus();
969 MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
971 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
972 if (fileStatusManager != null) {
974 * Updates tabs colors
976 final MyFileStatusListener myFileStatusListener = new MyFileStatusListener();
977 fileStatusManager.addFileStatusListener(myFileStatusListener, myProject);
979 connection.subscribe(AppTopics.FILE_TYPES, new MyFileTypeListener());
981 * Updates tabs names
983 final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener();
984 VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject);
986 * Extends/cuts number of opened tabs. Also updates location of tabs.
988 final MyUISettingsListener myUISettingsListener = new MyUISettingsListener();
989 UISettings.getInstance().addUISettingsListener(myUISettingsListener);
990 Disposer.register(myProject, new Disposable() {
991 public void dispose() {
992 UISettings.getInstance().removeUISettingsListener(myUISettingsListener);
996 StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
997 public void run() {
998 ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() {
999 public void run() {
1000 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1001 public void run() {
1002 setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1003 getSplitters().openFiles();
1004 LaterInvocator.invokeLater(new Runnable() {
1005 public void run() {
1006 long currentTime = System.nanoTime();
1007 Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME);
1008 if (startTime != null) {
1009 LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms");
1010 PluginManager.dumpPluginClassStatistics();
1014 // group 1
1016 }, "", null);
1023 public void projectClosed() {
1024 //myFocusWatcher.deinstall(myWindows.getComponent ());
1025 getSplitters().dispose();
1027 // Dispose created editors. We do not use use closeEditor method because
1028 // it fires event and changes history.
1029 closeAllFiles();
1032 // BaseCompomemnt methods
1034 @NotNull
1035 public String getComponentName() {
1036 return "FileEditorManager";
1039 public void initComponent() { /* really do nothing */ }
1041 public void disposeComponent() { /* really do nothing */ }
1043 //JDOMExternalizable methods
1045 public void writeExternal(final Element element) {
1046 getSplitters().writeExternal(element);
1049 public void readExternal(final Element element) {
1050 getSplitters().readExternal(element);
1053 private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) {
1054 final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
1055 for (int i = editorsComposites.length - 1; i >= 0; i--) {
1056 final EditorWithProviderComposite composite = editorsComposites[i];
1057 final FileEditor[] editors = composite.getEditors();
1058 for (int j = editors.length - 1; j >= 0; j--) {
1059 final FileEditor _editor = editors[j];
1060 LOG.assertTrue(_editor != null);
1061 if (editor.equals(_editor)) {
1062 return composite;
1066 return null;
1069 //======================= Misc =====================
1071 private static void assertDispatchThread() {
1072 ApplicationManager.getApplication().assertIsDispatchThread();
1074 private static void assertReadAccess() {
1075 ApplicationManager.getApplication().assertReadAccessAllowed();
1078 public void fireSelectionChanged(final EditorComposite oldSelectedComposite, final EditorComposite newSelectedComposite) {
1079 final VirtualFile oldSelectedFile = oldSelectedComposite != null ? oldSelectedComposite.getFile() : null;
1080 final VirtualFile newSelectedFile = newSelectedComposite != null ? newSelectedComposite.getFile() : null;
1082 final FileEditor oldSelectedEditor = oldSelectedComposite != null && !oldSelectedComposite.isDisposed() ? oldSelectedComposite.getSelectedEditor() : null;
1083 final FileEditor newSelectedEditor = newSelectedComposite != null && !newSelectedComposite.isDisposed() ? newSelectedComposite.getSelectedEditor() : null;
1085 final boolean filesEqual = oldSelectedFile == null ? newSelectedFile == null : oldSelectedFile.equals(newSelectedFile);
1086 final boolean editorsEqual = oldSelectedEditor == null ? newSelectedEditor == null : oldSelectedEditor.equals(newSelectedEditor);
1087 if (!filesEqual || !editorsEqual) {
1088 final FileEditorManagerEvent event =
1089 new FileEditorManagerEvent(this, oldSelectedFile, oldSelectedEditor, newSelectedFile, newSelectedEditor);
1090 final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
1091 publisher.selectionChanged(event);
1095 public boolean isChanged(@NotNull final EditorComposite editor) {
1096 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1097 if (fileStatusManager != null) {
1098 if (!fileStatusManager.getStatus(editor.getFile()).equals(FileStatus.NOT_CHANGED)) {
1099 return true;
1102 return false;
1105 public void disposeComposite(EditorWithProviderComposite editor) {
1106 if (getAllEditors().length == 0) {
1107 setCurrentWindow(null);
1110 if (editor.equals(getLastSelected())) {
1111 editor.getSelectedEditor().deselectNotify();
1112 getSplitters().setCurrentWindow(null, false);
1115 final FileEditor[] editors = editor.getEditors();
1116 final FileEditorProvider[] providers = editor.getProviders();
1118 final FileEditor selectedEditor = editor.getSelectedEditor();
1119 for (int i = editors.length - 1; i >= 0; i--) {
1120 final FileEditor editor1 = editors[i];
1121 final FileEditorProvider provider = providers[i];
1122 if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event)
1123 if (selectedEditor.equals(editor1)) {
1124 editor1.deselectNotify();
1127 editor1.removePropertyChangeListener(myEditorPropertyChangeListener);
1128 provider.disposeEditor(editor1);
1131 Disposer.dispose(editor);
1134 EditorComposite getLastSelected() {
1135 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
1136 if (currentWindow != null) {
1137 return currentWindow.getSelectedEditor();
1139 return null;
1142 public void runChange(Runnable runnable) {
1143 getSplitters().runChange(runnable);
1146 //================== Listeners =====================
1149 * Closes deleted files. Closes file which are in the deleted directories.
1151 private final class MyVirtualFileListener extends VirtualFileAdapter {
1152 public void beforeFileDeletion(VirtualFileEvent e) {
1153 assertDispatchThread();
1154 final VirtualFile file = e.getFile();
1155 final VirtualFile[] openFiles = getOpenFiles();
1156 for (int i = openFiles.length - 1; i >= 0; i--) {
1157 if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1158 closeFile(openFiles[i]);
1163 public void propertyChanged(VirtualFilePropertyEvent e) {
1164 if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) {
1165 assertDispatchThread();
1166 final VirtualFile file = e.getFile();
1167 if (isFileOpen(file)) {
1168 updateFileName(file);
1169 updateFileIcon(file); // file type can change after renaming
1170 updateFileBackgroundColor(file);
1173 else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) {
1174 updateIconAndStatusbar(e);
1178 private void updateIconAndStatusbar(final VirtualFilePropertyEvent e) {
1179 assertDispatchThread();
1180 final VirtualFile file = e.getFile();
1181 if (isFileOpen(file)) {
1182 updateFileIcon(file);
1183 if (file.equals(getSelectedFiles()[0])) { // update "write" status
1184 final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1185 assert statusBar != null;
1186 statusBar.update(getSelectedTextEditor());
1191 public void fileMoved(VirtualFileMoveEvent e) {
1192 final VirtualFile file = e.getFile();
1193 final VirtualFile[] openFiles = getOpenFiles();
1194 for (final VirtualFile openFile : openFiles) {
1195 if (VfsUtil.isAncestor(file, openFile, false)) {
1196 updateFileName(openFile);
1197 updateFileBackgroundColor(openFile);
1204 private final class MyVirtualFileListener extends VirtualFileAdapter {
1205 public void beforeFileDeletion(final VirtualFileEvent e) {
1206 assertDispatchThread();
1207 final VirtualFile file = e.getFile();
1208 final VirtualFile[] openFiles = getOpenFiles();
1209 for (int i = openFiles.length - 1; i >= 0; i--) {
1210 if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1211 closeFile(openFiles[i]);
1216 public void propertyChanged(final VirtualFilePropertyEvent e) {
1217 if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName())) {
1218 assertDispatchThread();
1219 final VirtualFile file = e.getFile();
1220 if (isFileOpen(file)) {
1221 if (file.equals(getSelectedFiles()[0])) { // update "write" status
1222 final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1223 LOG.assertTrue(statusBar != null);
1224 statusBar.setWriteStatus(!file.isWritable());
1230 //public void fileMoved(final VirtualFileMoveEvent e){ }
1234 public boolean isInsideChange() {
1235 return getSplitters().isInsideChange();
1238 private final class MyEditorPropertyChangeListener implements PropertyChangeListener {
1239 public void propertyChange(final PropertyChangeEvent e) {
1240 assertDispatchThread();
1242 final String propertyName = e.getPropertyName();
1243 if (FileEditor.PROP_MODIFIED.equals(propertyName)) {
1244 final FileEditor editor = (FileEditor)e.getSource();
1245 final EditorComposite composite = getEditorComposite(editor);
1246 if (composite != null) {
1247 updateFileIcon(composite.getFile());
1250 else if (FileEditor.PROP_VALID.equals(propertyName)) {
1251 final boolean valid = ((Boolean)e.getNewValue()).booleanValue();
1252 if (!valid) {
1253 final FileEditor editor = (FileEditor)e.getSource();
1254 LOG.assertTrue(editor != null);
1255 final EditorComposite composite = getEditorComposite(editor);
1256 if (composite != null) {
1257 closeFile(composite.getFile());
1266 * Gets events from VCS and updates color of myEditor tabs
1268 private final class MyFileStatusListener implements FileStatusListener {
1269 public void fileStatusesChanged() { // update color of all open files
1270 assertDispatchThread();
1271 LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()");
1272 final VirtualFile[] openFiles = getOpenFiles();
1273 for (int i = openFiles.length - 1; i >= 0; i--) {
1274 final VirtualFile file = openFiles[i];
1275 LOG.assertTrue(file != null);
1276 ApplicationManager.getApplication().invokeLater(new Runnable() {
1277 public void run() {
1278 if (LOG.isDebugEnabled()) {
1279 LOG.debug("updating file status in tab for " + file.getPath());
1281 updateFileStatus(file);
1283 }, ModalityState.NON_MODAL, myProject.getDisposed());
1287 public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary)
1288 assertDispatchThread();
1289 if (isFileOpen(file)) {
1290 updateFileStatus(file);
1294 private void updateFileStatus(final VirtualFile file) {
1295 updateFileColor(file);
1296 updateFileIcon(file);
1301 * Gets events from FileTypeManager and updates icons on tabs
1303 private final class MyFileTypeListener implements FileTypeListener {
1304 public void beforeFileTypesChanged(FileTypeEvent event) {
1307 public void fileTypesChanged(final FileTypeEvent event) {
1308 assertDispatchThread();
1309 final VirtualFile[] openFiles = getOpenFiles();
1310 for (int i = openFiles.length - 1; i >= 0; i--) {
1311 final VirtualFile file = openFiles[i];
1312 LOG.assertTrue(file != null);
1313 updateFileIcon(file);
1319 * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT
1320 * and EDITOR_TAB_LIMIT, etc values.
1322 private final class MyUISettingsListener implements UISettingsListener {
1323 public void uiSettingsChanged(final UISettings source) {
1324 assertDispatchThread();
1325 setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1326 getSplitters().setTabsPlacement(source.EDITOR_TAB_PLACEMENT);
1327 getSplitters().trimToSize(source.EDITOR_TAB_LIMIT);
1329 // Tab layout policy
1330 if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) {
1331 getSplitters().setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
1333 else {
1334 getSplitters().setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
1337 // "Mark modified files with asterisk"
1338 final VirtualFile[] openFiles = getOpenFiles();
1339 for (int i = openFiles.length - 1; i >= 0; i--) {
1340 final VirtualFile file = openFiles[i];
1341 updateFileIcon(file);
1342 updateFileName(file);
1343 updateFileBackgroundColor(file);
1348 public void closeAllFiles() {
1349 final VirtualFile[] openFiles = getSplitters().getOpenFiles();
1350 for (VirtualFile openFile : openFiles) {
1351 closeFile(openFile);
1355 @NotNull
1356 public VirtualFile[] getSiblings(VirtualFile file) {
1357 return getOpenFiles();
1360 protected void queueUpdateFile(final VirtualFile file) {
1361 myQueue.queue(new Update(file) {
1362 public void run() {
1363 if (isFileOpen(file)) {
1364 updateFileIcon(file);
1365 updateFileColor(file);
1366 updateFileBackgroundColor(file);