3a8d88acdbfdd1915e66066057db563efb21edf9
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / FileEditorManagerImpl.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.fileEditor.impl;
17
18 import com.intellij.AppTopics;
19 import com.intellij.ProjectTopics;
20 import com.intellij.ide.IdeBundle;
21 import com.intellij.ide.plugins.PluginManager;
22 import com.intellij.ide.ui.UISettings;
23 import com.intellij.ide.ui.UISettingsListener;
24 import com.intellij.injected.editor.VirtualFileWindow;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.application.ModalityState;
28 import com.intellij.openapi.application.ex.ApplicationManagerEx;
29 import com.intellij.openapi.application.impl.LaterInvocator;
30 import com.intellij.openapi.command.CommandProcessor;
31 import com.intellij.openapi.components.ProjectComponent;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.editor.Editor;
34 import com.intellij.openapi.editor.ex.EditorEx;
35 import com.intellij.openapi.extensions.Extensions;
36 import com.intellij.openapi.fileEditor.*;
37 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
38 import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
39 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
40 import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
41 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
42 import com.intellij.openapi.fileTypes.FileTypeEvent;
43 import com.intellij.openapi.fileTypes.FileTypeListener;
44 import com.intellij.openapi.project.DumbAware;
45 import com.intellij.openapi.project.DumbAwareRunnable;
46 import com.intellij.openapi.project.DumbService;
47 import com.intellij.openapi.project.Project;
48 import com.intellij.openapi.project.impl.ProjectImpl;
49 import com.intellij.openapi.roots.ModuleRootEvent;
50 import com.intellij.openapi.roots.ModuleRootListener;
51 import com.intellij.openapi.startup.StartupManager;
52 import com.intellij.openapi.util.*;
53 import com.intellij.openapi.vcs.FileStatus;
54 import com.intellij.openapi.vcs.FileStatusListener;
55 import com.intellij.openapi.vcs.FileStatusManager;
56 import com.intellij.openapi.vfs.*;
57 import com.intellij.openapi.wm.ToolWindowManager;
58 import com.intellij.openapi.wm.WindowManager;
59 import com.intellij.openapi.wm.ex.StatusBarEx;
60 import com.intellij.openapi.wm.ex.WindowManagerEx;
61 import com.intellij.openapi.wm.impl.FrameTitleBuilder;
62 import com.intellij.openapi.wm.impl.IdeFrameImpl;
63 import com.intellij.util.containers.ContainerUtil;
64 import com.intellij.util.messages.MessageBusConnection;
65 import com.intellij.util.messages.impl.MessageListenerList;
66 import com.intellij.util.ui.update.MergingUpdateQueue;
67 import com.intellij.util.ui.update.Update;
68 import org.jdom.Element;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
71
72 import javax.swing.*;
73 import javax.swing.border.Border;
74 import javax.swing.border.EmptyBorder;
75 import java.awt.*;
76 import java.beans.PropertyChangeEvent;
77 import java.beans.PropertyChangeListener;
78 import java.io.File;
79 import java.util.ArrayList;
80 import java.util.Collection;
81 import java.util.List;
82
83 /**
84  * @author Anton Katilin
85  * @author Eugene Belyaev
86  * @author Vladimir Kondratyev
87  */
88 public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable {
89   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl");
90   private static final Key<LocalFileSystem.WatchRequest> WATCH_REQUEST_KEY = Key.create("WATCH_REQUEST_KEY");
91   private static final Key<Boolean> DUMB_AWARE = Key.create("DUMB_AWARE");
92
93   private static final FileEditor[] EMPTY_EDITOR_ARRAY = {};
94   private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {};
95
96   private volatile JPanel myPanels;
97   private EditorsSplitters mySplitters;
98   private final Project myProject;
99
100   private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true, null);
101
102   /**
103    * Removes invalid myEditor and updates "modified" status.
104    */
105   private final MyEditorPropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener();
106
107   private final List<EditorDataProvider> myDataProviders = new ArrayList<EditorDataProvider>();
108
109   public FileEditorManagerImpl(final Project project) {
110 /*    ApplicationManager.getApplication().assertIsDispatchThread(); */
111     myProject = project;
112     myListenerList = new MessageListenerList<FileEditorManagerListener>(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER);
113   }
114
115   public static boolean isDumbAware(FileEditor editor) {
116     return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE));
117   }
118
119   //-------------------------------------------------------------------------------
120
121   public JComponent getComponent() {
122     initUI();
123     return myPanels;
124   }
125
126   public EditorsSplitters getSplitters() {
127     initUI();
128     return mySplitters;
129   }
130
131   private final Object myInitLock = new Object();
132   private void initUI() {
133     if (myPanels == null) {
134       synchronized (myInitLock) {
135         if (myPanels == null) {
136           myPanels = new JPanel(new BorderLayout());
137           myPanels.setBorder(new MyBorder());
138           mySplitters = new EditorsSplitters(this);
139           myPanels.add(mySplitters, BorderLayout.CENTER);
140         }
141       }
142     }
143   }
144
145   private class MyBorder implements Border {
146     @Override
147     public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
148     }
149
150     @Override
151     public Insets getBorderInsets(Component c) {
152       boolean filesOpen = mySplitters != null && mySplitters.getOpenFiles().length > 0;
153       return new Insets(filesOpen ? 1 : 0, 0, 0, 0);
154     }
155
156     @Override
157     public boolean isBorderOpaque() {
158       return false;
159     }
160   }
161
162   public JComponent getPreferredFocusedComponent() {
163     assertReadAccess();
164     final EditorWindow window = getSplitters().getCurrentWindow();
165     if (window != null) {
166       final EditorWithProviderComposite editor = window.getSelectedEditor();
167       if (editor != null) {
168         return editor.getPreferredFocusedComponent();
169       }
170     }
171     return null;
172   }
173
174   //-------------------------------------------------------
175
176   /**
177    * @return color of the <code>file</code> which corresponds to the
178    *         file's status
179    */
180   public Color getFileColor(@NotNull final VirtualFile file) {
181     final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
182     Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : Color.BLACK;
183     if (statusColor == null) statusColor = Color.BLACK;
184     return statusColor;
185   }
186
187   public boolean isProblem(@NotNull final VirtualFile file) {
188     return false;
189   }
190
191   public String getFileTooltipText(VirtualFile file) {
192     return file.getPresentableUrl();
193   }
194
195   public void updateFilePresentation(VirtualFile file) {
196     if (!isFileOpen(file)) return;
197
198     updateFileColor(file);
199     updateFileIcon(file);
200     updateFileName(file);
201     updateFileBackgroundColor(file);
202   }
203
204   /**
205    * Updates tab color for the specified <code>file</code>. The <code>file</code>
206    * should be opened in the myEditor, otherwise the method throws an assertion.
207    */
208   private void updateFileColor(final VirtualFile file) {
209     getSplitters().updateFileColor(file);
210   }
211
212   private void updateFileBackgroundColor(final VirtualFile file) {
213     getSplitters().updateFileBackgroundColor(file);
214   }
215
216   /**
217    * Updates tab icon for the specified <code>file</code>. The <code>file</code>
218    * should be opened in the myEditor, otherwise the method throws an assertion.
219    */
220   protected void updateFileIcon(final VirtualFile file) {
221     getSplitters().updateFileIcon(file);
222   }
223
224   /**
225    * Updates tab title and tab tool tip for the specified <code>file</code>
226    */
227   void updateFileName(@Nullable final VirtualFile file) {
228     // Queue here is to prevent title flickering when tab is being closed and two events arriving: with component==null and component==next focused tab
229     // only the last event makes sense to handle
230     myQueue.queue(new Update("UpdateFileName "+(file==null?"":file.getPath())) {
231       public boolean isExpired() {
232         return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid());
233       }
234
235       public void run() {
236         final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx();
237         final IdeFrameImpl frame = windowManagerEx.getFrame(myProject);
238         LOG.assertTrue(frame != null);
239         getSplitters().updateFileName(file);
240         File ioFile = file == null ? null : new File(file.getPresentableUrl());
241         frame.setFileTitle(file == null ? null : FrameTitleBuilder.getInstance().getFileTitle(myProject, file), ioFile);
242       }
243     });
244   }
245
246   //-------------------------------------------------------
247
248
249   public VirtualFile getFile(@NotNull final FileEditor editor) {
250     final EditorComposite editorComposite = getEditorComposite(editor);
251     if (editorComposite != null) {
252       return editorComposite.getFile();
253     }
254     return null;
255   }
256
257   public void unsplitWindow() {
258     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
259     if (currentWindow != null) {
260       currentWindow.unsplit(true);
261     }
262   }
263
264   public void unsplitAllWindow() {
265     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
266     if (currentWindow != null) {
267       currentWindow.unsplitAll();
268     }
269   }
270
271   @Override
272   public int getWindowSplitCount() {
273     return getSplitters().getSplitCount();
274   }
275
276   @NotNull
277   public EditorWindow[] getWindows() {
278     return getSplitters().getWindows();
279   }
280
281   public EditorWindow getNextWindow(@NotNull final EditorWindow window) {
282     final EditorWindow[] windows = getSplitters().getOrderedWindows();
283     for (int i = 0; i != windows.length; ++i) {
284       if (windows[i].equals(window)) {
285         return windows[(i + 1) % windows.length];
286       }
287     }
288     LOG.error("Not window found");
289     return null;
290   }
291
292   public EditorWindow getPrevWindow(@NotNull final EditorWindow window) {
293     final EditorWindow[] windows = getSplitters().getOrderedWindows();
294     for (int i = 0; i != windows.length; ++i) {
295       if (windows[i].equals(window)) {
296         return windows[(i + windows.length - 1) % windows.length];
297       }
298     }
299     LOG.error("Not window found");
300     return null;
301   }
302
303   public void createSplitter(final int orientation, @Nullable final EditorWindow window) {
304     // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current'
305     if (window != null) {
306       window.split(orientation, true, null, false);
307     }
308     // otherwise we'll split the current window, if any
309     else {
310       final EditorWindow currentWindow = getSplitters().getCurrentWindow();
311       if (currentWindow != null) {
312         currentWindow.split(orientation, true, null, false);
313       }
314     }
315   }
316
317   public void changeSplitterOrientation() {
318     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
319     if (currentWindow != null) {
320       currentWindow.changeOrientation();
321     }
322   }
323
324
325   public void flipTabs() {
326     /*
327     if (myTabs == null) {
328       myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT);
329       remove (mySplitters);
330       add (myTabs, BorderLayout.CENTER);
331       initTabs ();
332     } else {
333       remove (myTabs);
334       add (mySplitters, BorderLayout.CENTER);
335       myTabs.dispose ();
336       myTabs = null;
337     }
338     */
339     myPanels.revalidate();
340   }
341
342   public boolean tabsMode() {
343     return false;
344   }
345
346   private void setTabsMode(final boolean mode) {
347     if (tabsMode() != mode) {
348       flipTabs();
349     }
350     //LOG.assertTrue (tabsMode () == mode);
351   }
352
353
354   public boolean isInSplitter() {
355     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
356     return currentWindow != null && currentWindow.inSplitter();
357   }
358
359   public boolean hasOpenedFile() {
360     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
361     return currentWindow != null && currentWindow.getSelectedEditor() != null;
362   }
363
364   public VirtualFile getCurrentFile() {
365     return getSplitters().getCurrentFile();
366   }
367
368   public EditorWindow getCurrentWindow() {
369     return getSplitters().getCurrentWindow();
370   }
371
372   public void setCurrentWindow(final EditorWindow window) {
373     getSplitters().setCurrentWindow(window, true);
374   }
375
376   public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) {
377     assertDispatchThread();
378
379     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
380       public void run() {
381         if (window.isFileOpen(file)) {
382           window.closeFile(file);
383           final List<EditorWindow> windows = getSplitters().findWindows(file);
384           if (windows.isEmpty()) { // no more windows containing this file left
385             final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
386             if (request != null) {
387               LocalFileSystem.getInstance().removeWatchedRoot(request);
388             }
389           }
390         }
391       }
392     }, IdeBundle.message("command.close.active.editor"), null);
393   }
394
395   //============================= EditorManager methods ================================
396
397   public void closeFile(@NotNull final VirtualFile file) {
398     closeFile(file, true);
399   }
400
401   public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus) {
402     assertDispatchThread();
403
404     final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY);
405     if (request != null) {
406       LocalFileSystem.getInstance().removeWatchedRoot(request);
407     }
408
409     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
410       public void run() {
411         closeFileImpl(file, moveFocus);
412       }
413     }, "", null);
414   }
415
416
417   private VirtualFile findNextFile(final VirtualFile file) {
418     final EditorWindow [] windows = getWindows(); // TODO: use current file as base
419     for (int i = 0; i != windows.length; ++ i) {
420       final VirtualFile[] files = windows[i].getFiles();
421       for (final VirtualFile fileAt : files) {
422         if (fileAt != file) {
423           return fileAt;
424         }
425       }
426     }
427     return null;
428   }
429
430   private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus) {
431     assertDispatchThread();
432     getSplitters().runChange(new Runnable() {
433       public void run() {
434         final List<EditorWindow> windows = getSplitters().findWindows(file);
435         if (!windows.isEmpty()) {
436           final VirtualFile nextFile = findNextFile(file);
437           for (final EditorWindow window : windows) {
438             LOG.assertTrue(window.getSelectedEditor() != null);
439             window.closeFile(file, false, moveFocus);
440             if (window.getTabCount() == 0 && nextFile != null) {
441               EditorWithProviderComposite newComposite = newEditorComposite(nextFile);
442               window.setEditor(newComposite, moveFocus); // newComposite can be null
443             }
444           }
445           // cleanup windows with no tabs
446           for (final EditorWindow window : windows) {
447             if (window.isDisposed()) {
448               // call to window.unsplit() which might make its sibling disposed
449               continue;
450             }
451             if (window.getTabCount() == 0) {
452               window.unsplit(false);
453             }
454           }
455         }
456       }
457     });
458   }
459
460 //-------------------------------------- Open File ----------------------------------------
461
462   @NotNull public Pair<FileEditor[], FileEditorProvider[]> openFileWithProviders(@NotNull final VirtualFile file, final boolean focusEditor) {
463     if (!file.isValid()) {
464       throw new IllegalArgumentException("file is not valid: " + file);
465     }
466     assertDispatchThread();
467     return openFileImpl2(getSplitters().getOrCreateCurrentWindow(file), file, focusEditor, null);
468   }
469
470   @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl2(final EditorWindow window, final VirtualFile file, final boolean focusEditor,
471                                                                   final HistoryEntry entry) {
472     final Ref<Pair<FileEditor[], FileEditorProvider[]>> resHolder = new Ref<Pair<FileEditor[], FileEditorProvider[]>>();
473     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
474       public void run() {
475         resHolder.set(openFileImpl3(window, file, focusEditor, entry, true));
476       }
477     }, "", null);
478     return resHolder.get();
479   }
480
481   /**
482    * @param file  to be opened. Unlike openFile method, file can be
483    *              invalid. For example, all file were invalidate and they are being
484    *              removed one by one. If we have removed one invalid file, then another
485    *              invalid file become selected. That's why we do not require that
486    *              passed file is valid.
487    * @param entry map between FileEditorProvider and FileEditorState. If this parameter
488    * @param current
489    */
490   @NotNull Pair<FileEditor[], FileEditorProvider[]> openFileImpl3(final EditorWindow window,
491                                                                   @NotNull final VirtualFile file,
492                                                                   final boolean focusEditor,
493                                                                   final HistoryEntry entry,
494                                                                   boolean current) {
495     // Open file
496     FileEditor[] editors;
497     FileEditorProvider[] providers;
498     final EditorWithProviderComposite newSelectedComposite;
499     boolean newEditorCreated = false;
500
501     final boolean open = window.isFileOpen(file);
502     if (open) {
503       // File is already opened. In this case we have to just select existing EditorComposite
504       newSelectedComposite = window.findFileComposite(file);
505       LOG.assertTrue(newSelectedComposite != null);
506
507       editors = newSelectedComposite.getEditors();
508       providers = newSelectedComposite.getProviders();
509     }
510     else {
511       // File is not opened yet. In this case we have to create editors
512       // and select the created EditorComposite.
513       final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
514       providers = editorProviderManager.getProviders(myProject, file);
515       if (DumbService.getInstance(myProject).isDumb()) {
516         final List<FileEditorProvider> dumbAware = ContainerUtil.findAll(providers, new Condition<FileEditorProvider>() {
517           public boolean value(FileEditorProvider fileEditorProvider) {
518             return DumbService.isDumbAware(fileEditorProvider);
519           }
520         });
521         providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]);
522       }
523
524       if (providers.length == 0) {
525         return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
526       }
527       newEditorCreated = true;
528
529       getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(this, file);
530
531       editors = new FileEditor[providers.length];
532       for (int i = 0; i < providers.length; i++) {
533         try {
534           final FileEditorProvider provider = providers[i];
535           LOG.assertTrue(provider != null);
536           LOG.assertTrue(provider.accept(myProject, file));
537           final FileEditor editor = provider.createEditor(myProject, file);
538           LOG.assertTrue(editor != null);
539           LOG.assertTrue(editor.isValid());
540           editors[i] = editor;
541           // Register PropertyChangeListener into editor
542           editor.addPropertyChangeListener(myEditorPropertyChangeListener);
543           editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider));
544
545           if (current && editor instanceof TextEditorImpl) {
546             ((TextEditorImpl)editor).initFolding();
547           }
548        }
549         catch (Exception e) {
550           LOG.error(e);
551         }
552         catch (AssertionError e) {
553           LOG.error(e);
554         }
555       }
556
557       // Now we have to create EditorComposite and insert it into the TabbedEditorComponent.
558       // After that we have to select opened editor.
559       newSelectedComposite = new EditorWithProviderComposite(file, editors, providers, this);
560     }
561
562     window.setEditor(newSelectedComposite, focusEditor);
563
564     final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
565     for (int i = 0; i < editors.length; i++) {
566       final FileEditor editor = editors[i];
567       if (editor instanceof TextEditor) {
568         // hack!!!
569         // This code prevents "jumping" on next repaint.
570         ((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
571       }
572
573       final FileEditorProvider provider = providers[i];//getProvider(editor);
574
575       // Restore editor state
576       FileEditorState state = null;
577       if (entry != null) {
578         state = entry.getState(provider);
579       }
580       if (state == null && !open) {
581         // We have to try to get state from the history only in case
582         // if editor is not opened. Otherwise history enty might have a state
583         // out of sync with the current editor state.
584         state = editorHistoryManager.getState(file, provider);
585       }
586       if (state != null) {
587         editor.setState(state);
588       }
589     }
590
591     // Restore selected editor
592     final FileEditorProvider selectedProvider = editorHistoryManager.getSelectedProvider(file);
593     if (selectedProvider != null) {
594       final FileEditor[] _editors = newSelectedComposite.getEditors();
595       final FileEditorProvider[] _providers = newSelectedComposite.getProviders();
596       for (int i = _editors.length - 1; i >= 0; i--) {
597         final FileEditorProvider provider = _providers[i];//getProvider(_editors[i]);
598         if (provider.equals(selectedProvider)) {
599           newSelectedComposite.setSelectedEditor(i);
600           break;
601         }
602       }
603     }
604
605     // Notify editors about selection changes
606     getSplitters().setCurrentWindow(window, false);
607     newSelectedComposite.getSelectedEditor().selectNotify();
608
609     if (newEditorCreated) {
610       getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER).fileOpened(this, file);
611
612       //Add request to watch this editor's virtual file
613       final VirtualFile parentDir = file.getParent();
614       if (parentDir != null) {
615         final LocalFileSystem.WatchRequest request = LocalFileSystem.getInstance().addRootToWatch(parentDir.getPath(), false);
616         file.putUserData(WATCH_REQUEST_KEY, request);
617       }
618     }
619
620     //[jeka] this is a hack to support back-forward navigation
621     // previously here was incorrect call to fireSelectionChanged() with a side-effect
622     ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged();
623
624     // Transfer focus into editor
625     if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) {
626       if (focusEditor) {
627         //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer);
628         window.setAsCurrentWindow(false);
629         ToolWindowManager.getInstance(myProject).activateEditorComponent();
630       }
631     }
632
633     // Update frame and tab title
634     updateFileName(file);
635
636     // Make back/forward work
637     IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
638
639     return Pair.create(editors, providers);
640   }
641
642   private void setSelectedEditor(VirtualFile file, String fileEditorProviderId) {
643     EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
644     if (composite == null) {
645       final List<EditorWithProviderComposite> composites = getEditorComposites(file);
646
647       if (composites.isEmpty()) return;
648       composite = composites.get(0);
649     }
650
651     final FileEditorProvider[] editorProviders = composite.getProviders();
652     final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond();
653
654     for (int i = 0; i < editorProviders.length; i++) {
655       if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) &&  !selectedProvider.equals(editorProviders[i])) {
656         composite.setSelectedEditor(i);
657         composite.getSelectedEditor().selectNotify();
658       }
659     }
660   }
661
662
663   private EditorWithProviderComposite newEditorComposite(final VirtualFile file) {
664     if (file == null) {
665       return null;
666     }
667
668     final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
669     final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
670     final FileEditor[] editors = new FileEditor[providers.length];
671     for (int i = 0; i < providers.length; i++) {
672       final FileEditorProvider provider = providers[i];
673       LOG.assertTrue(provider != null);
674       LOG.assertTrue(provider.accept(myProject, file));
675       final FileEditor editor = provider.createEditor(myProject, file);
676       editors[i] = editor;
677       LOG.assertTrue(editor.isValid());
678       editor.addPropertyChangeListener(myEditorPropertyChangeListener);
679     }
680
681     final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this);
682     final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject);
683     for (int i = 0; i < editors.length; i++) {
684       final FileEditor editor = editors[i];
685       if (editor instanceof TextEditor) {
686         // hack!!!
687         // This code prevents "jumping" on next repaint.
688         //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling();
689       }
690
691       final FileEditorProvider provider = providers[i];
692
693 // Restore myEditor state
694       FileEditorState state = editorHistoryManager.getState(file, provider);
695       if (state != null) {
696         editor.setState(state);
697       }
698     }
699     return newComposite;
700   }
701
702   @NotNull
703   public List<FileEditor> openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) {
704     assertDispatchThread();
705     if (descriptor.getFile() instanceof VirtualFileWindow) {
706       VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile();
707       int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset());
708       OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset);
709       return openEditor(realDescriptor, focusEditor);
710     }
711
712     final List<FileEditor> result = new ArrayList<FileEditor>();
713     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
714       public void run() {
715         VirtualFile file = descriptor.getFile();
716         final FileEditor[] editors = openFile(file, focusEditor);
717         ContainerUtil.addAll(result, editors);
718
719         boolean navigated = false;
720         for (final FileEditor editor : editors) {
721           if (editor instanceof NavigatableFileEditor &&
722               getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor
723             navigated = navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor);
724             if (navigated) break;
725           }
726         }
727
728         if (!navigated) {
729           for (final FileEditor editor : editors) {
730             if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors
731               if (navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor)) {
732                 break;
733               }
734             }
735           }
736         }
737       }
738     }, "", null);
739
740     return result;
741   }
742
743   private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) {
744     if (editor.canNavigateTo(descriptor)) {
745       setSelectedEditor(editor);
746       editor.navigateTo(descriptor);
747       return true;
748     }
749
750     return false;
751   }
752
753   private void setSelectedEditor(final FileEditor editor) {
754     final EditorWithProviderComposite composite = getEditorComposite(editor);
755     if (composite == null) return;
756
757     final FileEditor[] editors = composite.getEditors();
758     for (int i = 0; i < editors.length; i++) {
759       final FileEditor each = editors[i];
760       if (editor == each) {
761         composite.setSelectedEditor(i);
762         composite.getSelectedEditor().selectNotify();
763         break;
764       }
765     }
766   }
767
768   @NotNull
769   public Project getProject() {
770     return myProject;
771   }
772
773   public void registerExtraEditorDataProvider(@NotNull final EditorDataProvider provider, Disposable parentDisposable) {
774     myDataProviders.add(provider);
775     if (parentDisposable != null) {
776       Disposer.register(parentDisposable, new Disposable() {
777         public void dispose() {
778           myDataProviders.remove(provider);
779         }
780       });
781     }
782   }
783
784   @Nullable
785   public final Object getData(String dataId, Editor editor, final VirtualFile file) {
786     for (final EditorDataProvider dataProvider : myDataProviders) {
787       final Object o = dataProvider.getData(dataId, editor, file);
788       if (o != null) return o;
789     }
790     return null;
791   }
792
793   @Nullable
794   public Editor openTextEditor(final OpenFileDescriptor descriptor, final boolean focusEditor) {
795     final Collection<FileEditor> fileEditors = openEditor(descriptor, focusEditor);
796     for (FileEditor fileEditor : fileEditors) {
797       if (fileEditor instanceof TextEditor) {
798         setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId());
799         Editor editor = ((TextEditor)fileEditor).getEditor();
800         return getOpenedEditor(editor, focusEditor);
801       }
802     }
803
804     return null;
805   }
806
807   protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) {
808     return editor;
809   }
810
811   public Editor getSelectedTextEditor() {
812     assertReadAccess();
813
814     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
815     if (currentWindow != null) {
816       final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor();
817       if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) {
818         return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor();
819       }
820     }
821
822     return null;
823   }
824
825
826   public boolean isFileOpen(@NotNull final VirtualFile file) {
827     return getEditors(file).length != 0;
828   }
829
830   @NotNull
831   public VirtualFile[] getOpenFiles() {
832     return getSplitters().getOpenFiles();
833   }
834
835   @NotNull
836   public VirtualFile[] getSelectedFiles() {
837     return getSplitters().getSelectedFiles();
838   }
839
840   @NotNull
841   public FileEditor[] getSelectedEditors() {
842     return getSplitters().getSelectedEditors();
843   }
844
845   public FileEditor getSelectedEditor(@NotNull final VirtualFile file) {
846     final Pair<FileEditor, FileEditorProvider> selectedEditorWithProvider = getSelectedEditorWithProvider(file);
847     return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst();
848   }
849
850
851   public Pair<FileEditor, FileEditorProvider> getSelectedEditorWithProvider(@NotNull VirtualFile file) {
852     if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
853     final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
854     if (composite != null) {
855       return composite.getSelectedEditorWithProvider();
856     }
857
858     final List<EditorWithProviderComposite> composites = getEditorComposites(file);
859     return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider();
860   }
861
862   @NotNull
863   public Pair<FileEditor[], FileEditorProvider[]> getEditorsWithProviders(@NotNull final VirtualFile file) {
864     assertReadAccess();
865
866     final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
867     if (composite != null) {
868       return Pair.create(composite.getEditors(), composite.getProviders());
869     }
870
871     final List<EditorWithProviderComposite> composites = getEditorComposites(file);
872     if (!composites.isEmpty()) {
873       return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders());
874     }
875     else {
876       return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY);
877     }
878   }
879
880   @NotNull
881   public FileEditor[] getEditors(@NotNull VirtualFile file) {
882     assertReadAccess();
883     if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
884
885     final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file);
886     if (composite != null) {
887       return composite.getEditors();
888     }
889
890     final List<EditorWithProviderComposite> composites = getEditorComposites(file);
891     if (!composites.isEmpty()) {
892       return composites.get(0).getEditors();
893     }
894     else {
895       return EMPTY_EDITOR_ARRAY;
896     }
897   }
898
899   @NotNull
900   @Override
901   public FileEditor[] getAllEditors(@NotNull VirtualFile file) {
902     List<EditorWithProviderComposite> editorComposites = getEditorComposites(file);
903     List<FileEditor> editors = new ArrayList<FileEditor>();
904     for (EditorWithProviderComposite composite : editorComposites) {
905       ContainerUtil.addAll(editors, composite.getEditors());
906     }
907     return editors.toArray(new FileEditor[editors.size()]);
908   }
909
910   @Nullable
911   private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) {
912     final EditorWindow editorWindow = getSplitters().getCurrentWindow();
913     if (editorWindow != null) {
914       return editorWindow.findFileComposite(virtualFile);
915     }
916     return null;
917   }
918
919   @NotNull
920   public List<EditorWithProviderComposite> getEditorComposites(final VirtualFile file) {
921     return getSplitters().findEditorComposites(file);
922   }
923
924   @NotNull
925   public FileEditor[] getAllEditors() {
926     assertReadAccess();
927     final ArrayList<FileEditor> result = new ArrayList<FileEditor>();
928     final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
929     for (EditorWithProviderComposite editorsComposite : editorsComposites) {
930       final FileEditor[] editors = editorsComposite.getEditors();
931       ContainerUtil.addAll(result, editors);
932     }
933     return result.toArray(new FileEditor[result.size()]);
934   }
935
936   public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
937     addTopComponent(editor, annotationComponent);
938   }
939
940   public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) {
941     removeTopComponent(editor, annotationComponent);
942   }
943
944   public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
945     final EditorComposite composite = getEditorComposite(editor);
946     if (composite != null) {
947       composite.addTopComponent(editor, component);
948     }
949   }
950
951   public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
952     final EditorComposite composite = getEditorComposite(editor);
953     if (composite != null) {
954       composite.removeTopComponent(editor, component);
955     }
956   }
957
958   public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
959     final EditorComposite composite = getEditorComposite(editor);
960     if (composite != null) {
961       composite.addBottomComponent(editor, component);
962     }
963   }
964
965   public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) {
966     final EditorComposite composite = getEditorComposite(editor);
967     if (composite != null) {
968       composite.removeBottomComponent(editor, component);
969     }
970   }
971
972   private final MessageListenerList<FileEditorManagerListener> myListenerList;
973
974   public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
975     myListenerList.add(listener);
976   }
977
978   public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, final Disposable parentDisposable) {
979     myListenerList.add(listener, parentDisposable);
980   }
981
982   public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) {
983     myListenerList.remove(listener);
984   }
985
986 // ProjectComponent methods
987
988   public void projectOpened() {
989     //myFocusWatcher.install(myWindows.getComponent ());
990     getSplitters().startListeningFocus();
991
992     MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
993
994     final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
995     if (fileStatusManager != null) {
996       /**
997        * Updates tabs colors
998        */
999       final MyFileStatusListener myFileStatusListener = new MyFileStatusListener();
1000       fileStatusManager.addFileStatusListener(myFileStatusListener, myProject);
1001     }
1002     connection.subscribe(AppTopics.FILE_TYPES, new MyFileTypeListener());
1003     connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootsListener());
1004
1005     /**
1006      * Updates tabs names
1007      */
1008     final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener();
1009     VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject);
1010     /**
1011      * Extends/cuts number of opened tabs. Also updates location of tabs.
1012      */
1013     final MyUISettingsListener myUISettingsListener = new MyUISettingsListener();
1014     UISettings.getInstance().addUISettingsListener(myUISettingsListener, myProject);
1015
1016     StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
1017       public void run() {
1018         ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() {
1019           public void run() {
1020             CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1021               public void run() {
1022                 setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1023                 getSplitters().openFiles();
1024                 LaterInvocator.invokeLater(new Runnable() {
1025                   public void run() {
1026                     long currentTime = System.nanoTime();
1027                     Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME);
1028                     if (startTime != null) {
1029                       LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms");
1030                       PluginManager.dumpPluginClassStatistics();
1031                     }
1032                   }
1033                 });
1034 // group 1
1035               }
1036             }, "", null);
1037           }
1038         });
1039       }
1040     });
1041   }
1042
1043   public void projectClosed() {
1044     //myFocusWatcher.deinstall(myWindows.getComponent ());
1045     getSplitters().dispose();
1046
1047 // Dispose created editors. We do not use use closeEditor method because
1048 // it fires event and changes history.
1049     closeAllFiles();
1050   }
1051
1052 // BaseCompomemnt methods
1053
1054   @NotNull
1055   public String getComponentName() {
1056     return "FileEditorManager";
1057   }
1058
1059   public void initComponent() { /* really do nothing */ }
1060
1061   public void disposeComponent() { /* really do nothing */  }
1062
1063 //JDOMExternalizable methods
1064
1065   public void writeExternal(final Element element) {
1066     getSplitters().writeExternal(element);
1067   }
1068
1069   public void readExternal(final Element element) {
1070     getSplitters().readExternal(element);
1071   }
1072
1073   private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) {
1074     final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites();
1075     for (int i = editorsComposites.length - 1; i >= 0; i--) {
1076       final EditorWithProviderComposite composite = editorsComposites[i];
1077       final FileEditor[] editors = composite.getEditors();
1078       for (int j = editors.length - 1; j >= 0; j--) {
1079         final FileEditor _editor = editors[j];
1080         LOG.assertTrue(_editor != null);
1081         if (editor.equals(_editor)) {
1082           return composite;
1083         }
1084       }
1085     }
1086     return null;
1087   }
1088
1089 //======================= Misc =====================
1090
1091   private static void assertDispatchThread() {
1092     ApplicationManager.getApplication().assertIsDispatchThread();
1093   }
1094   private static void assertReadAccess() {
1095     ApplicationManager.getApplication().assertReadAccessAllowed();
1096   }
1097
1098   public void fireSelectionChanged(final EditorComposite oldSelectedComposite, final EditorComposite newSelectedComposite) {
1099     final VirtualFile oldSelectedFile = oldSelectedComposite != null ? oldSelectedComposite.getFile() : null;
1100     final VirtualFile newSelectedFile = newSelectedComposite != null ? newSelectedComposite.getFile() : null;
1101
1102     final FileEditor oldSelectedEditor = oldSelectedComposite != null && !oldSelectedComposite.isDisposed() ? oldSelectedComposite.getSelectedEditor() : null;
1103     final FileEditor newSelectedEditor = newSelectedComposite != null && !newSelectedComposite.isDisposed() ? newSelectedComposite.getSelectedEditor() : null;
1104
1105     final boolean filesEqual = oldSelectedFile == null ? newSelectedFile == null : oldSelectedFile.equals(newSelectedFile);
1106     final boolean editorsEqual = oldSelectedEditor == null ? newSelectedEditor == null : oldSelectedEditor.equals(newSelectedEditor);
1107     if (!filesEqual || !editorsEqual) {
1108       final FileEditorManagerEvent event =
1109         new FileEditorManagerEvent(this, oldSelectedFile, oldSelectedEditor, newSelectedFile, newSelectedEditor);
1110       final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER);
1111       publisher.selectionChanged(event);
1112     }
1113   }
1114
1115   public boolean isChanged(@NotNull final EditorComposite editor) {
1116     final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
1117     if (fileStatusManager != null) {
1118       if (!fileStatusManager.getStatus(editor.getFile()).equals(FileStatus.NOT_CHANGED)) {
1119         return true;
1120       }
1121     }
1122     return false;
1123   }
1124
1125   public void disposeComposite(EditorWithProviderComposite editor) {
1126     if (getAllEditors().length == 0) {
1127       setCurrentWindow(null);
1128     }
1129
1130     if (editor.equals(getLastSelected())) {
1131       editor.getSelectedEditor().deselectNotify();
1132       getSplitters().setCurrentWindow(null, false);
1133     }
1134
1135     final FileEditor[] editors = editor.getEditors();
1136     final FileEditorProvider[] providers = editor.getProviders();
1137
1138     final FileEditor selectedEditor = editor.getSelectedEditor();
1139     for (int i = editors.length - 1; i >= 0; i--) {
1140       final FileEditor editor1 = editors[i];
1141       final FileEditorProvider provider = providers[i];
1142       if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event)
1143         if (selectedEditor.equals(editor1)) {
1144           editor1.deselectNotify();
1145         }
1146       }
1147       editor1.removePropertyChangeListener(myEditorPropertyChangeListener);
1148       provider.disposeEditor(editor1);
1149     }
1150
1151     Disposer.dispose(editor);
1152   }
1153
1154   EditorComposite getLastSelected() {
1155     final EditorWindow currentWindow = getSplitters().getCurrentWindow();
1156     if (currentWindow != null) {
1157       return currentWindow.getSelectedEditor();
1158     }
1159     return null;
1160   }
1161
1162   public void runChange(Runnable runnable) {
1163     getSplitters().runChange(runnable);
1164   }
1165
1166   //================== Listeners =====================
1167
1168   /**
1169    * Closes deleted files. Closes file which are in the deleted directories.
1170    */
1171   private final class MyVirtualFileListener extends VirtualFileAdapter {
1172     public void beforeFileDeletion(VirtualFileEvent e) {
1173       assertDispatchThread();
1174       final VirtualFile file = e.getFile();
1175       final VirtualFile[] openFiles = getOpenFiles();
1176       for (int i = openFiles.length - 1; i >= 0; i--) {
1177         if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1178           closeFile(openFiles[i]);
1179         }
1180       }
1181     }
1182
1183     public void propertyChanged(VirtualFilePropertyEvent e) {
1184       if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) {
1185         assertDispatchThread();
1186         final VirtualFile file = e.getFile();
1187         if (isFileOpen(file)) {
1188           updateFileName(file);
1189           updateFileIcon(file); // file type can change after renaming
1190           updateFileBackgroundColor(file);
1191         }
1192       }
1193       else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) {
1194         // TODO: message bus?
1195         updateIconAndStatusbar(e);
1196       }
1197     }
1198
1199     private void updateIconAndStatusbar(final VirtualFilePropertyEvent e) {
1200       assertDispatchThread();
1201       final VirtualFile file = e.getFile();
1202       if (isFileOpen(file)) {
1203         updateFileIcon(file);
1204         if (file.equals(getSelectedFiles()[0])) { // update "write" status
1205           final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1206           assert statusBar != null;
1207           statusBar.updateWidgets();
1208         }
1209       }
1210     }
1211
1212     public void fileMoved(VirtualFileMoveEvent e) {
1213       final VirtualFile file = e.getFile();
1214       final VirtualFile[] openFiles = getOpenFiles();
1215       for (final VirtualFile openFile : openFiles) {
1216         if (VfsUtil.isAncestor(file, openFile, false)) {
1217           updateFileName(openFile);
1218           updateFileBackgroundColor(openFile);
1219         }
1220       }
1221     }
1222   }
1223
1224 /*
1225 private final class MyVirtualFileListener extends VirtualFileAdapter {
1226   public void beforeFileDeletion(final VirtualFileEvent e) {
1227     assertDispatchThread();
1228     final VirtualFile file = e.getFile();
1229     final VirtualFile[] openFiles = getOpenFiles();
1230     for (int i = openFiles.length - 1; i >= 0; i--) {
1231       if (VfsUtil.isAncestor(file, openFiles[i], false)) {
1232         closeFile(openFiles[i]);
1233       }
1234     }
1235   }
1236
1237   public void propertyChanged(final VirtualFilePropertyEvent e) {
1238     if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName())) {
1239       assertDispatchThread();
1240       final VirtualFile file = e.getFile();
1241       if (isFileOpen(file)) {
1242         if (file.equals(getSelectedFiles()[0])) { // update "write" status
1243           final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
1244           LOG.assertTrue(statusBar != null);
1245           statusBar.setWriteStatus(!file.isWritable());
1246         }
1247       }
1248     }
1249   }
1250
1251   //public void fileMoved(final VirtualFileMoveEvent e){ }
1252 }
1253 */
1254
1255   public boolean isInsideChange() {
1256     return getSplitters().isInsideChange();
1257   }
1258
1259   private final class MyEditorPropertyChangeListener implements PropertyChangeListener {
1260     public void propertyChange(final PropertyChangeEvent e) {
1261       assertDispatchThread();
1262
1263       final String propertyName = e.getPropertyName();
1264       if (FileEditor.PROP_MODIFIED.equals(propertyName)) {
1265         final FileEditor editor = (FileEditor)e.getSource();
1266         final EditorComposite composite = getEditorComposite(editor);
1267         if (composite != null) {
1268           updateFileIcon(composite.getFile());
1269         }
1270       }
1271       else if (FileEditor.PROP_VALID.equals(propertyName)) {
1272         final boolean valid = ((Boolean)e.getNewValue()).booleanValue();
1273         if (!valid) {
1274           final FileEditor editor = (FileEditor)e.getSource();
1275           LOG.assertTrue(editor != null);
1276           final EditorComposite composite = getEditorComposite(editor);
1277           if (composite != null) {
1278             closeFile(composite.getFile());
1279           }
1280         }
1281       }
1282
1283     }
1284   }
1285
1286
1287
1288   /**
1289    * Gets events from VCS and updates color of myEditor tabs
1290    */
1291   private final class MyFileStatusListener implements FileStatusListener {
1292     public void fileStatusesChanged() { // update color of all open files
1293       assertDispatchThread();
1294       LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()");
1295       final VirtualFile[] openFiles = getOpenFiles();
1296       for (int i = openFiles.length - 1; i >= 0; i--) {
1297         final VirtualFile file = openFiles[i];
1298         LOG.assertTrue(file != null);
1299         ApplicationManager.getApplication().invokeLater(new Runnable() {
1300           public void run() {
1301             if (LOG.isDebugEnabled()) {
1302               LOG.debug("updating file status in tab for " + file.getPath());
1303             }
1304             updateFileStatus(file);
1305           }
1306         }, ModalityState.NON_MODAL, myProject.getDisposed());
1307       }
1308     }
1309
1310     public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary)
1311       assertDispatchThread();
1312       if (isFileOpen(file)) {
1313         updateFileStatus(file);
1314       }
1315     }
1316
1317     private void updateFileStatus(final VirtualFile file) {
1318       updateFileColor(file);
1319       updateFileIcon(file);
1320     }
1321   }
1322
1323   /**
1324    * Gets events from FileTypeManager and updates icons on tabs
1325    */
1326   private final class MyFileTypeListener implements FileTypeListener {
1327     public void beforeFileTypesChanged(FileTypeEvent event) {
1328     }
1329
1330     public void fileTypesChanged(final FileTypeEvent event) {
1331       assertDispatchThread();
1332       final VirtualFile[] openFiles = getOpenFiles();
1333       for (int i = openFiles.length - 1; i >= 0; i--) {
1334         final VirtualFile file = openFiles[i];
1335         LOG.assertTrue(file != null);
1336         updateFileIcon(file);
1337       }
1338     }
1339   }
1340
1341   private class MyRootsListener implements ModuleRootListener {
1342     public void beforeRootsChange(ModuleRootEvent event) {
1343     }
1344
1345     public void rootsChanged(ModuleRootEvent event) {
1346       EditorFileSwapper[] swappers = Extensions.getExtensions(EditorFileSwapper.EP_NAME);
1347
1348       for (EditorWindow eachWindow : getWindows()) {
1349         VirtualFile selected = eachWindow.getSelectedFile();
1350         VirtualFile[] files = eachWindow.getFiles();
1351         for (int i = 0; i < files.length - 1 + 1; i++) {
1352           VirtualFile eachFile = files[i];
1353           VirtualFile newFile = null;
1354           for (EditorFileSwapper each : swappers) {
1355             newFile = each.getFileToSwapTo(myProject, eachFile);
1356           }
1357           if (newFile == null) continue;
1358
1359           // already open
1360           if (eachWindow.findFileIndex(newFile) != -1) continue;
1361
1362           closeFile(eachFile, eachWindow);
1363           try {
1364             newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, i);
1365             openFile(newFile, eachFile == selected);
1366           }
1367           finally {
1368             newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null);
1369           }
1370         }
1371       }
1372     }
1373   }
1374
1375   /**
1376    * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT
1377    * and EDITOR_TAB_LIMIT, etc values.
1378    */
1379   private final class MyUISettingsListener implements UISettingsListener {
1380     public void uiSettingsChanged(final UISettings source) {
1381       assertDispatchThread();
1382       setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE);
1383       getSplitters().setTabsPlacement(source.EDITOR_TAB_PLACEMENT);
1384       getSplitters().trimToSize(source.EDITOR_TAB_LIMIT);
1385
1386       // Tab layout policy
1387       if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) {
1388         getSplitters().setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
1389       }
1390       else {
1391         getSplitters().setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
1392       }
1393
1394       // "Mark modified files with asterisk"
1395       final VirtualFile[] openFiles = getOpenFiles();
1396       for (int i = openFiles.length - 1; i >= 0; i--) {
1397         final VirtualFile file = openFiles[i];
1398         updateFileIcon(file);
1399         updateFileName(file);
1400         updateFileBackgroundColor(file);
1401       }
1402     }
1403   }
1404
1405   public void closeAllFiles() {
1406     final VirtualFile[] openFiles = getSplitters().getOpenFiles();
1407     for (VirtualFile openFile : openFiles) {
1408       closeFile(openFile);
1409     }
1410   }
1411
1412   @NotNull
1413   public VirtualFile[] getSiblings(VirtualFile file) {
1414     return getOpenFiles();
1415   }
1416
1417   protected void queueUpdateFile(final VirtualFile file) {
1418     myQueue.queue(new Update(file) {
1419       public void run() {
1420         if (isFileOpen(file)) {
1421           updateFileIcon(file);
1422           updateFileColor(file);
1423           updateFileBackgroundColor(file);
1424         }
1425
1426       }
1427     });
1428   }
1429 }