build folding for current file only
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / FileEditorManagerImpl.java
blob1334fb6d399db641064b61107454a8536d819421
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) {
276 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
277 if (currentWindow != null) {
278 currentWindow.split(orientation);
282 public void changeSplitterOrientation() {
283 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
284 if (currentWindow != null) {
285 currentWindow.changeOrientation();
290 public void flipTabs() {
292 if (myTabs == null) {
293 myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT);
294 remove (mySplitters);
295 add (myTabs, BorderLayout.CENTER);
296 initTabs ();
297 } else {
298 remove (myTabs);
299 add (mySplitters, BorderLayout.CENTER);
300 myTabs.dispose ();
301 myTabs = null;
304 myPanels.revalidate();
307 public boolean tabsMode() {
308 return false;
311 private void setTabsMode(final boolean mode) {
312 if (tabsMode() != mode) {
313 flipTabs();
315 //LOG.assertTrue (tabsMode () == mode);
319 public boolean isInSplitter() {
320 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
321 return currentWindow != null && currentWindow.inSplitter();
324 public boolean hasOpenedFile() {
325 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
326 return currentWindow != null && currentWindow.getSelectedEditor() != null;
329 public VirtualFile getCurrentFile() {
330 return getSplitters().getCurrentFile();
333 public EditorWindow getCurrentWindow() {
334 return getSplitters().getCurrentWindow();
337 public void setCurrentWindow(final EditorWindow window) {
338 getSplitters().setCurrentWindow(window, true);
341 public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) {
342 assertDispatchThread();
344 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
345 public void run() {
346 if (window.isFileOpen(file)) {
347 window.closeFile(file);
348 final List<EditorWindow> windows = getSplitters().findWindows(file);
349 if (windows.isEmpty()) { // no more windows containing this file left
350 final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
351 if (request != null) {
352 LocalFileSystem.getInstance().removeWatchedRoot(request);
357 }, IdeBundle.message("command.close.active.editor"), null);
360 //============================= EditorManager methods ================================
362 public void closeFile(@NotNull final VirtualFile file) {
363 closeFile(file, true);
366 public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus) {
367 assertDispatchThread();
369 final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
370 if (request != null) {
371 LocalFileSystem.getInstance().removeWatchedRoot(request);
374 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
375 public void run() {
376 closeFileImpl(file, moveFocus);
378 }, "", null);
382 private VirtualFile findNextFile(final VirtualFile file) {
383 final EditorWindow [] windows = getWindows(); // TODO: use current file as base
384 for (int i = 0; i != windows.length; ++ i) {
385 final VirtualFile[] files = windows[i].getFiles();
386 for (final VirtualFile fileAt : files) {
387 if (fileAt != file) {
388 return fileAt;
392 return null;
395 private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus) {
396 assertDispatchThread();
397 getSplitters().runChange(new Runnable() {
398 public void run() {
399 final List<EditorWindow> windows = getSplitters().findWindows(file);
400 if (!windows.isEmpty()) {
401 final VirtualFile nextFile = findNextFile(file);
402 for (final EditorWindow window : windows) {
403 LOG.assertTrue(window.getSelectedEditor() != null);
404 window.closeFile(file, false, moveFocus);
405 if (window.getTabCount() == 0 && nextFile != null) {
406 EditorWithProviderComposite newComposite = newEditorComposite(nextFile);
407 window.setEditor(newComposite, moveFocus); // newComposite can be null
410 // cleanup windows with no tabs
411 for (final EditorWindow window : windows) {
412 if (window.isDisposed()) {
413 // call to window.unsplit() which might make its sibling disposed
414 continue;
416 if (window.getTabCount() == 0) {
417 window.unsplit(false);
425 //-------------------------------------- Open File ----------------------------------------
427 @NotNull public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull final VirtualFile file, final boolean focusEditor) {
428 if (!file.isValid()) {
429 throw new IllegalArgumentException("file is not valid: " + file);
431 assertDispatchThread();
432 return openFileImpl2(getSplitters().getOrCreateCurrentWindow(file), file, focusEditor, null);
435 @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl2(final EditorWindow window, final VirtualFile file, final boolean focusEditor,
436 final HistoryEntry entry) {
437 final Ref<Pair<FileEditor[], FileEditorProvider[]>> resHolder = new Ref<Pair<FileEditor[], FileEditorProvider[]>>();
438 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
439 public void run() {
440 resHolder.set(openFileImpl3(window, file, focusEditor, entry, true));
442 }, "", null);
443 return resHolder.get();
447 * @param file to be opened. Unlike openFile method, file can be
448 * invalid. For example, all file were invalidate and they are being
449 * removed one by one. If we have removed one invalid file, then another
450 * invalid file become selected. That's why we do not require that
451 * passed file is valid.
452 * @param entry map between FileEditorProvider and FileEditorState. If this parameter
453 * @param current
455 @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl3(final EditorWindow window,
456 @NotNull final VirtualFile file,
457 final boolean focusEditor,
458 final HistoryEntry entry,
459 boolean current) {
460 // Open file
461 FileEditor[] editors;
462 FileEditorProvider[] providers;
463 final EditorWithProviderComposite newSelectedComposite;
464 boolean newEditorCreated = false;
466 final boolean open = window.isFileOpen(file);
467 if (open) {
468 // File is already opened. In this case we have to just select existing EditorComposite
469 newSelectedComposite = window.findFileComposite(file);
470 LOG.assertTrue(newSelectedComposite != null);
472 editors = newSelectedComposite.getEditors();
473 providers = newSelectedComposite.getProviders();
475 else {
476 // File is not opened yet. In this case we have to create editors
477 // and select the created EditorComposite.
478 final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
479 providers = editorProviderManager.getProviders(myProject, file);
480 if (DumbService.getInstance(myProject).isDumb()) {
481 final List<FileEditorProvider> dumbAware = ContainerUtil.findAll(providers, new Condition<FileEditorProvider>() {
482 public boolean value(FileEditorProvider fileEditorProvider) {
483 return fileEditorProvider instanceof DumbAware;
486 providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]);
489 if (providers.length == 0) {
490 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
492 newEditorCreated = true;
494 editors = new FileEditor[providers.length];
495 for (int i = 0; i < providers.length; i++) {
496 try {
497 final FileEditorProvider provider = providers[i];
498 LOG.assertTrue(provider != null);
499 LOG.assertTrue(provider.accept(myProject, file));
500 final FileEditor editor = provider.createEditor(myProject, file);
501 if (current && editor instanceof TextEditorImpl) {
502 ((TextEditorImpl)editor).initFolding();
504 editors[i] = editor;
505 LOG.assertTrue(editor != null);
506 LOG.assertTrue(editor.isValid());
508 // Register PropertyChangeListener into editor
509 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
510 editor.putUserData(DUMB_AWARE, provider instanceof DumbAware);
512 catch (Exception e) {
513 LOG.error(e);
515 catch (AssertionError e) {
516 LOG.error(e);
520 // Now we have to create EditorComposite and insert it into the TabbedEditorComponent.
521 // After that we have to select opened editor.
522 newSelectedComposite = new EditorWithProviderComposite(file, editors, providers, this);
525 window.setEditor(newSelectedComposite, focusEditor);
527 final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
528 for (int i = 0; i < editors.length; i++) {
529 final FileEditor editor = editors[i];
530 if (editor instanceof TextEditor) {
531 // hack!!!
532 // This code prevents "jumping" on next repaint.
533 ((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
536 final FileEditorProvider provider = providers[i];//getProvider(editor);
538 // Restore editor state
539 FileEditorState state = null;
540 if (entry != null) {
541 state = entry.getState(provider);
543 if (state == null && !open) {
544 // We have to try to get state from the history only in case
545 // if editor is not opened. Otherwise history enty might have a state
546 // out of sync with the current editor state.
547 state = editorHistoryManager.getState(file, provider);
549 if (state != null) {
550 editor.setState(state);
554 // Restore selected editor
555 final FileEditorProvider selectedProvider = editorHistoryManager.getSelectedProvider(file);
556 if (selectedProvider != null) {
557 final FileEditor[] _editors = newSelectedComposite.getEditors();
558 final FileEditorProvider[] _providers = newSelectedComposite.getProviders();
559 for (int i = _editors.length - 1; i >= 0; i--) {
560 final FileEditorProvider provider = _providers[i];//getProvider(_editors[i]);
561 if (provider.equals(selectedProvider)) {
562 newSelectedComposite.setSelectedEditor(i);
563 break;
568 // Notify editors about selection changes
569 getSplitters().setCurrentWindow(window, false);
570 newSelectedComposite.getSelectedEditor().selectNotify();
572 if (newEditorCreated) {
573 getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER).fileOpened(this, file);
575 //Add request to watch this editor's virtual file
576 final VirtualFile parentDir = file.getParent();
577 if (parentDir != null) {
578 final LocalFileSystem.WatchRequest request = LocalFileSystem.getInstance().addRootToWatch(parentDir.getPath(), false);
579 file.putUserData(WATCH_REQUEST_KEY, request);
583 //[jeka] this is a hack to support back-forward navigation
584 // previously here was incorrect call to fireSelectionChanged() with a side-effect
585 ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged();
587 // Transfer focus into editor
588 if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) {
589 if (focusEditor) {
590 //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer);
591 window.setAsCurrentWindow(false);
592 ToolWindowManager.getInstance(myProject).activateEditorComponent();
596 // Update frame and tab title
597 updateFileName(file);
599 // Make back/forward work
600 IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
602 return Pair.create(editors, providers);
605 private void setSelectedEditor(VirtualFile file, String fileEditorProviderId) {
606 EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
607 if (composite == null) {
608 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
610 if (composites.isEmpty()) return;
611 composite = composites.get(0);
614 final FileEditorProvider[] editorProviders = composite.getProviders();
615 final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond();
617 for (int i = 0; i < editorProviders.length; i++) {
618 if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) {
619 composite.setSelectedEditor(i);
620 composite.getSelectedEditor().selectNotify();
626 private EditorWithProviderComposite newEditorComposite(final VirtualFile file) {
627 if (file == null) {
628 return null;
631 final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
632 final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
633 final FileEditor[] editors = new FileEditor[providers.length];
634 for (int i = 0; i < providers.length; i++) {
635 final FileEditorProvider provider = providers[i];
636 LOG.assertTrue(provider != null);
637 LOG.assertTrue(provider.accept(myProject, file));
638 final FileEditor editor = provider.createEditor(myProject, file);
639 editors[i] = editor;
640 LOG.assertTrue(editor.isValid());
641 editor.addPropertyChangeListener(myEditorPropertyChangeListener);
644 final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this);
645 final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
646 for (int i = 0; i < editors.length; i++) {
647 final FileEditor editor = editors[i];
648 if (editor instanceof TextEditor) {
649 // hack!!!
650 // This code prevents "jumping" on next repaint.
651 //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
654 final FileEditorProvider provider = providers[i];
656 // Restore myEditor state
657 FileEditorState state = editorHistoryManager.getState(file, provider);
658 if (state != null) {
659 editor.setState(state);
662 return newComposite;
665 @NotNull
666 public List<FileEditor> openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
667 assertDispatchThread();
668 if (descriptor.getFile() instanceof VirtualFileWindow) {
669 VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile();
670 int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset());
671 OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset);
672 return openEditor(realDescriptor, focusEditor);
675 final List<FileEditor> result = new ArrayList<FileEditor>();
676 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
677 public void run() {
678 VirtualFile file = descriptor.getFile();
679 final FileEditor[] editors = openFile(file, focusEditor);
680 result.addAll(Arrays.asList(editors));
682 boolean navigated = false;
683 for (final FileEditor editor : editors) {
684 if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor
685 navigated = navigateAndSelectEditor((NavigatableFileEditor) editor, descriptor);
686 if (navigated) break;
690 if (!navigated) {
691 for (final FileEditor editor : editors) {
692 if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors
693 if (navigateAndSelectEditor((NavigatableFileEditor) editor, descriptor)) {
694 break;
700 }, "", null);
702 return result;
705 private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) {
706 if (editor.canNavigateTo(descriptor)) {
707 setSelectedEditor(editor);
708 editor.navigateTo(descriptor);
709 return true;
712 return false;
715 private void setSelectedEditor(final FileEditor editor) {
716 final EditorWithProviderComposite composite = getEditorComposite(editor);
717 if (composite == null) return;
719 final FileEditor[] editors = composite.getEditors();
720 for (int i = 0; i < editors.length; i++) {
721 final FileEditor each = editors[i];
722 if (editor == each) {
723 composite.setSelectedEditor(i);
724 composite.getSelectedEditor().selectNotify();
725 break;
730 @NotNull
731 public Project getProject() {
732 return myProject;
735 public void registerExtraEditorDataProvider(@NotNull final EditorDataProvider provider, Disposable parentDisposable) {
736 myDataProviders.add(provider);
737 if (parentDisposable != null) {
738 Disposer.register(parentDisposable, new Disposable() {
739 public void dispose() {
740 myDataProviders.remove(provider);
746 @Nullable
747 public final Object getData(String dataId, Editor editor, final VirtualFile file) {
748 for (final EditorDataProvider dataProvider : myDataProviders) {
749 final Object o = dataProvider.getData(dataId, editor, file);
750 if (o != null) return o;
752 return null;
755 @Nullable
756 public Editor openTextEditor(final OpenFileDescriptor descriptor, final boolean focusEditor) {
757 final Collection<FileEditor> fileEditors = openEditor(descriptor, focusEditor);
758 for (FileEditor fileEditor : fileEditors) {
759 if (fileEditor instanceof TextEditor) {
760 setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId());
761 Editor editor = ((TextEditor)fileEditor).getEditor();
762 return getOpenedEditor(editor, focusEditor);
766 return null;
769 protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) {
770 return editor;
773 public Editor getSelectedTextEditor() {
774 assertReadAccess();
776 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
777 if (currentWindow != null) {
778 final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor();
779 if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) {
780 return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor();
784 return null;
788 public boolean isFileOpen(@NotNull final VirtualFile file) {
789 return getEditors(file).length != 0;
792 @NotNull
793 public VirtualFile[] getOpenFiles() {
794 return getSplitters().getOpenFiles();
797 @NotNull
798 public VirtualFile[] getSelectedFiles() {
799 return getSplitters().getSelectedFiles();
802 @NotNull
803 public FileEditor[] getSelectedEditors() {
804 return getSplitters().getSelectedEditors();
807 public FileEditor getSelectedEditor(@NotNull final VirtualFile file) {
808 final Pair<FileEditor, FileEditorProvider> selectedEditorWithProvider = getSelectedEditorWithProvider(file);
809 return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst();
813 public Pair<FileEditor, FileEditorProvider> getSelectedEditorWithProvider(@NotNull VirtualFile file) {
814 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
815 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
816 if (composite != null) {
817 return composite.getSelectedEditorWithProvider();
820 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
821 return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider();
824 @NotNull
825 public Pair<FileEditor[], FileEditorProvider[]> getEditorsWithProviders(@NotNull final VirtualFile file) {
826 assertReadAccess();
828 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
829 if (composite != null) {
830 return Pair.create(composite.getEditors(), composite.getProviders());
833 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
834 if (!composites.isEmpty()) {
835 return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders());
837 else {
838 return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
842 @NotNull
843 public FileEditor[] getEditors(@NotNull VirtualFile file) {
844 assertReadAccess();
845 if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
847 final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
848 if (composite != null) {
849 return composite.getEditors();
852 final List<EditorWithProviderComposite> composites = getEditorComposites(file);
853 if (!composites.isEmpty()) {
854 return composites.get(0).getEditors();
856 else {
857 return EMPTY_EDITOR_ARRAY;
861 @Nullable
862 private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) {
863 final EditorWindow editorWindow = getSplitters().getCurrentWindow();
864 if (editorWindow != null) {
865 return editorWindow.findFileComposite(virtualFile);
867 return null;
870 @NotNull
871 public List<EditorWithProviderComposite> getEditorComposites(final VirtualFile file) {
872 return getSplitters().findEditorComposites(file);
875 @NotNull
876 public FileEditor[] getAllEditors() {
877 assertReadAccess();
878 final ArrayList<FileEditor> result = new ArrayList<FileEditor>();
879 final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
880 for (EditorWithProviderComposite editorsComposite : editorsComposites) {
881 final FileEditor[] editors = editorsComposite.getEditors();
882 result.addAll(Arrays.asList(editors));
884 return result.toArray(new FileEditor[result.size()]);
887 public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
888 final EditorComposite composite = getEditorComposite(editor);
889 if (composite != null) {
890 composite.getPane(editor).addInfo(annotationComponent);
894 public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
895 final EditorComposite composite = getEditorComposite(editor);
896 if (composite != null) {
897 composite.getPane(editor).removeInfo(annotationComponent);
901 public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
902 final EditorComposite composite = getEditorComposite(editor);
903 if (composite != null) {
904 composite.addTopComponent(editor, component);
908 public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
909 final EditorComposite composite = getEditorComposite(editor);
910 if (composite != null) {
911 composite.removeTopComponent(editor, component);
915 public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
916 final EditorComposite composite = getEditorComposite(editor);
917 if (composite != null) {
918 composite.addBottomComponent(editor, component);
922 public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
923 final EditorComposite composite = getEditorComposite(editor);
924 if (composite != null) {
925 composite.removeBottomComponent(editor, component);
929 private final MessageListenerList<FileEditorManagerListener> myListenerList;
931 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
932 myListenerList.add(listener);
935 public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, final Disposable parentDisposable) {
936 myListenerList.add(listener, parentDisposable);
939 public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
940 myListenerList.remove(listener);
943 // ProjectComponent methods
945 public void projectOpened() {
946 //myFocusWatcher.install(myWindows.getComponent ());
947 getSplitters().startListeningFocus();
949 MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
951 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
952 if (fileStatusManager != null) {
954 * Updates tabs colors
956 final MyFileStatusListener myFileStatusListener = new MyFileStatusListener();
957 fileStatusManager.addFileStatusListener(myFileStatusListener, myProject);
959 connection.subscribe(AppTopics.FILE_TYPES, new MyFileTypeListener());
961 * Updates tabs names
963 final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener();
964 VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject);
966 * Extends/cuts number of opened tabs. Also updates location of tabs.
968 final MyUISettingsListener myUISettingsListener = new MyUISettingsListener();
969 UISettings.getInstance().addUISettingsListener(myUISettingsListener);
970 Disposer.register(myProject, new Disposable() {
971 public void dispose() {
972 UISettings.getInstance().removeUISettingsListener(myUISettingsListener);
976 StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
977 public void run() {
978 ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() {
979 public void run() {
980 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
981 public void run() {
982 setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
983 getSplitters().openFiles();
984 LaterInvocator.invokeLater(new Runnable() {
985 public void run() {
986 long currentTime = System.nanoTime();
987 Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME);
988 if (startTime != null) {
989 LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms");
990 PluginManager.dumpPluginClassStatistics();
994 // group 1
996 }, "", null);
1003 public void projectClosed() {
1004 //myFocusWatcher.deinstall(myWindows.getComponent ());
1005 getSplitters().dispose();
1007 // Dispose created editors. We do not use use closeEditor method because
1008 // it fires event and changes history.
1009 closeAllFiles();
1012 // BaseCompomemnt methods
1014 @NotNull
1015 public String getComponentName() {
1016 return "FileEditorManager";
1019 public void initComponent() { /* really do nothing */ }
1021 public void disposeComponent() { /* really do nothing */ }
1023 //JDOMExternalizable methods
1025 public void writeExternal(final Element element) {
1026 getSplitters().writeExternal(element);
1029 public void readExternal(final Element element) {
1030 getSplitters().readExternal(element);
1033 private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) {
1034 final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
1035 for (int i = editorsComposites.length - 1; i >= 0; i--) {
1036 final EditorWithProviderComposite composite = editorsComposites[i];
1037 final FileEditor[] editors = composite.getEditors();
1038 for (int j = editors.length - 1; j >= 0; j--) {
1039 final FileEditor _editor = editors[j];
1040 LOG.assertTrue(_editor != null);
1041 if (editor.equals(_editor)) {
1042 return composite;
1046 return null;
1049 //======================= Misc =====================
1051 private static void assertDispatchThread() {
1052 ApplicationManager.getApplication().assertIsDispatchThread();
1054 private static void assertReadAccess() {
1055 ApplicationManager.getApplication().assertReadAccessAllowed();
1058 public void fireSelectionChanged(final EditorComposite oldSelectedComposite, final EditorComposite newSelectedComposite) {
1059 final VirtualFile oldSelectedFile = oldSelectedComposite != null ? oldSelectedComposite.getFile() : null;
1060 final VirtualFile newSelectedFile = newSelectedComposite != null ? newSelectedComposite.getFile() : null;
1062 final FileEditor oldSelectedEditor = oldSelectedComposite != null && !oldSelectedComposite.isDisposed() ? oldSelectedComposite.getSelectedEditor() : null;
1063 final FileEditor newSelectedEditor = newSelectedComposite != null && !newSelectedComposite.isDisposed() ? newSelectedComposite.getSelectedEditor() : null;
1065 final boolean filesEqual = oldSelectedFile == null ? newSelectedFile == null : oldSelectedFile.equals(newSelectedFile);
1066 final boolean editorsEqual = oldSelectedEditor == null ? newSelectedEditor == null : oldSelectedEditor.equals(newSelectedEditor);
1067 if (!filesEqual || !editorsEqual) {
1068 final FileEditorManagerEvent event =
1069 new FileEditorManagerEvent(this, oldSelectedFile, oldSelectedEditor, newSelectedFile, newSelectedEditor);
1070 final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
1071 publisher.selectionChanged(event);
1075 public boolean isChanged(@NotNull final EditorComposite editor) {
1076 final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1077 if (fileStatusManager != null) {
1078 if (!fileStatusManager.getStatus(editor.getFile()).equals(FileStatus.NOT_CHANGED)) {
1079 return true;
1082 return false;
1085 public void disposeComposite(EditorWithProviderComposite editor) {
1086 if (getAllEditors().length == 0) {
1087 setCurrentWindow(null);
1090 if (editor.equals(getLastSelected())) {
1091 editor.getSelectedEditor().deselectNotify();
1092 getSplitters().setCurrentWindow(null, false);
1095 final FileEditor[] editors = editor.getEditors();
1096 final FileEditorProvider[] providers = editor.getProviders();
1098 final FileEditor selectedEditor = editor.getSelectedEditor();
1099 for (int i = editors.length - 1; i >= 0; i--) {
1100 final FileEditor editor1 = editors[i];
1101 final FileEditorProvider provider = providers[i];
1102 if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event)
1103 if (selectedEditor.equals(editor1)) {
1104 editor1.deselectNotify();
1107 editor1.removePropertyChangeListener(myEditorPropertyChangeListener);
1108 provider.disposeEditor(editor1);
1111 Disposer.dispose(editor);
1114 EditorComposite getLastSelected() {
1115 final EditorWindow currentWindow = getSplitters().getCurrentWindow();
1116 if (currentWindow != null) {
1117 return currentWindow.getSelectedEditor();
1119 return null;
1122 public void runChange(Runnable runnable) {
1123 getSplitters().runChange(runnable);
1126 //================== Listeners =====================
1129 * Closes deleted files. Closes file which are in the deleted directories.
1131 private final class MyVirtualFileListener extends VirtualFileAdapter {
1132 public void beforeFileDeletion(VirtualFileEvent e) {
1133 assertDispatchThread();
1134 final VirtualFile file = e.getFile();
1135 final VirtualFile[] openFiles = getOpenFiles();
1136 for (int i = openFiles.length - 1; i >= 0; i--) {
1137 if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1138 closeFile(openFiles[i]);
1143 public void propertyChanged(VirtualFilePropertyEvent e) {
1144 if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) {
1145 assertDispatchThread();
1146 final VirtualFile file = e.getFile();
1147 if (isFileOpen(file)) {
1148 updateFileName(file);
1149 updateFileIcon(file); // file type can change after renaming
1150 updateFileBackgroundColor(file);
1153 else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) {
1154 updateIconAndStatusbar(e);
1158 private void updateIconAndStatusbar(final VirtualFilePropertyEvent e) {
1159 assertDispatchThread();
1160 final VirtualFile file = e.getFile();
1161 if (isFileOpen(file)) {
1162 updateFileIcon(file);
1163 if (file.equals(getSelectedFiles()[0])) { // update "write" status
1164 final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1165 assert statusBar != null;
1166 statusBar.update(getSelectedTextEditor());
1171 public void fileMoved(VirtualFileMoveEvent e) {
1172 final VirtualFile file = e.getFile();
1173 final VirtualFile[] openFiles = getOpenFiles();
1174 for (final VirtualFile openFile : openFiles) {
1175 if (VfsUtil.isAncestor(file, openFile, false)) {
1176 updateFileName(openFile);
1177 updateFileBackgroundColor(openFile);
1184 private final class MyVirtualFileListener extends VirtualFileAdapter {
1185 public void beforeFileDeletion(final VirtualFileEvent e) {
1186 assertDispatchThread();
1187 final VirtualFile file = e.getFile();
1188 final VirtualFile[] openFiles = getOpenFiles();
1189 for (int i = openFiles.length - 1; i >= 0; i--) {
1190 if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1191 closeFile(openFiles[i]);
1196 public void propertyChanged(final VirtualFilePropertyEvent e) {
1197 if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName())) {
1198 assertDispatchThread();
1199 final VirtualFile file = e.getFile();
1200 if (isFileOpen(file)) {
1201 if (file.equals(getSelectedFiles()[0])) { // update "write" status
1202 final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1203 LOG.assertTrue(statusBar != null);
1204 statusBar.setWriteStatus(!file.isWritable());
1210 //public void fileMoved(final VirtualFileMoveEvent e){ }
1214 public boolean isInsideChange() {
1215 return getSplitters().isInsideChange();
1218 private final class MyEditorPropertyChangeListener implements PropertyChangeListener {
1219 public void propertyChange(final PropertyChangeEvent e) {
1220 assertDispatchThread();
1222 final String propertyName = e.getPropertyName();
1223 if (FileEditor.PROP_MODIFIED.equals(propertyName)) {
1224 final FileEditor editor = (FileEditor)e.getSource();
1225 final EditorComposite composite = getEditorComposite(editor);
1226 if (composite != null) {
1227 updateFileIcon(composite.getFile());
1230 else if (FileEditor.PROP_VALID.equals(propertyName)) {
1231 final boolean valid = ((Boolean)e.getNewValue()).booleanValue();
1232 if (!valid) {
1233 final FileEditor editor = (FileEditor)e.getSource();
1234 LOG.assertTrue(editor != null);
1235 final EditorComposite composite = getEditorComposite(editor);
1236 if (composite != null) {
1237 closeFile(composite.getFile());
1246 * Gets events from VCS and updates color of myEditor tabs
1248 private final class MyFileStatusListener implements FileStatusListener {
1249 public void fileStatusesChanged() { // update color of all open files
1250 assertDispatchThread();
1251 LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()");
1252 final VirtualFile[] openFiles = getOpenFiles();
1253 for (int i = openFiles.length - 1; i >= 0; i--) {
1254 final VirtualFile file = openFiles[i];
1255 LOG.assertTrue(file != null);
1256 ApplicationManager.getApplication().invokeLater(new Runnable() {
1257 public void run() {
1258 if (LOG.isDebugEnabled()) {
1259 LOG.debug("updating file status in tab for " + file.getPath());
1261 updateFileStatus(file);
1263 }, ModalityState.NON_MODAL, myProject.getDisposed());
1267 public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary)
1268 assertDispatchThread();
1269 if (isFileOpen(file)) {
1270 updateFileStatus(file);
1274 private void updateFileStatus(final VirtualFile file) {
1275 updateFileColor(file);
1276 updateFileIcon(file);
1281 * Gets events from FileTypeManager and updates icons on tabs
1283 private final class MyFileTypeListener implements FileTypeListener {
1284 public void beforeFileTypesChanged(FileTypeEvent event) {
1287 public void fileTypesChanged(final FileTypeEvent event) {
1288 assertDispatchThread();
1289 final VirtualFile[] openFiles = getOpenFiles();
1290 for (int i = openFiles.length - 1; i >= 0; i--) {
1291 final VirtualFile file = openFiles[i];
1292 LOG.assertTrue(file != null);
1293 updateFileIcon(file);
1299 * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT
1300 * and EDITOR_TAB_LIMIT, etc values.
1302 private final class MyUISettingsListener implements UISettingsListener {
1303 public void uiSettingsChanged(final UISettings source) {
1304 assertDispatchThread();
1305 setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1306 getSplitters().setTabsPlacement(source.EDITOR_TAB_PLACEMENT);
1307 getSplitters().trimToSize(source.EDITOR_TAB_LIMIT);
1309 // Tab layout policy
1310 if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) {
1311 getSplitters().setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
1313 else {
1314 getSplitters().setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
1317 // "Mark modified files with asterisk"
1318 final VirtualFile[] openFiles = getOpenFiles();
1319 for (int i = openFiles.length - 1; i >= 0; i--) {
1320 final VirtualFile file = openFiles[i];
1321 updateFileIcon(file);
1322 updateFileName(file);
1323 updateFileBackgroundColor(file);
1328 public void closeAllFiles() {
1329 final VirtualFile[] openFiles = getSplitters().getOpenFiles();
1330 for (VirtualFile openFile : openFiles) {
1331 closeFile(openFile);
1335 @NotNull
1336 public VirtualFile[] getSiblings(VirtualFile file) {
1337 return getOpenFiles();
1340 protected void queueUpdateFile(final VirtualFile file) {
1341 myQueue.queue(new Update(file) {
1342 public void run() {
1343 if (isFileOpen(file)) {
1344 updateFileIcon(file);
1345 updateFileColor(file);
1346 updateFileBackgroundColor(file);