/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorHistoryManager.java

https://bitbucket.org/nbargnesi/idea · Java · 365 lines · 259 code · 38 blank · 68 comment · 54 complexity · f528a5185846da19768660471c2f07d8 MD5 · raw file

  1. /*
  2. * Copyright 2000-2012 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. import com.intellij.ide.ui.UISettings;
  18. import com.intellij.ide.ui.UISettingsListener;
  19. import com.intellij.openapi.application.ApplicationManager;
  20. import com.intellij.openapi.components.AbstractProjectComponent;
  21. import com.intellij.openapi.diagnostic.Logger;
  22. import com.intellij.openapi.fileEditor.*;
  23. import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
  24. import com.intellij.openapi.progress.ProcessCanceledException;
  25. import com.intellij.openapi.project.DumbAwareRunnable;
  26. import com.intellij.openapi.project.Project;
  27. import com.intellij.openapi.startup.StartupManager;
  28. import com.intellij.openapi.util.Comparing;
  29. import com.intellij.openapi.util.InvalidDataException;
  30. import com.intellij.openapi.util.JDOMExternalizable;
  31. import com.intellij.openapi.util.Pair;
  32. import com.intellij.openapi.vfs.VirtualFile;
  33. import com.intellij.openapi.vfs.VirtualFileManager;
  34. import com.intellij.util.ArrayUtil;
  35. import org.jdom.Element;
  36. import org.jetbrains.annotations.NotNull;
  37. import org.jetbrains.annotations.Nullable;
  38. import java.util.ArrayList;
  39. import java.util.List;
  40. public final class EditorHistoryManager extends AbstractProjectComponent implements JDOMExternalizable {
  41. private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorHistoryManager");
  42. private Element myElement;
  43. public static EditorHistoryManager getInstance(final Project project){
  44. return project.getComponent(EditorHistoryManager.class);
  45. }
  46. /**
  47. * State corresponding to the most recent file is the last
  48. */
  49. private final ArrayList<HistoryEntry> myEntriesList;
  50. /** Invoked by reflection */
  51. EditorHistoryManager(final Project project, FileEditorManager fileEditorManager, final UISettings uiSettings){
  52. super(project);
  53. myEntriesList = new ArrayList<HistoryEntry>();
  54. MyEditorManagerListener editorManagerListener = new MyEditorManagerListener();
  55. /**
  56. * Updates history length
  57. */
  58. final MyUISettingsListener myUISettingsListener = new MyUISettingsListener();
  59. fileEditorManager.addFileEditorManagerListener(editorManagerListener, project);
  60. project.getMessageBus().connect().subscribe(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER, new MyEditorManagerBeforeListener());
  61. uiSettings.addUISettingsListener(myUISettingsListener, project);
  62. }
  63. public void projectOpened(){
  64. StartupManager.getInstance(myProject).registerPostStartupActivity(
  65. new DumbAwareRunnable(){
  66. public void run(){
  67. // myElement may be null if node that correspondes to this manager does not exist
  68. if (myElement != null){
  69. final List children = myElement.getChildren(HistoryEntry.TAG);
  70. myElement = null;
  71. //noinspection unchecked
  72. for (final Element e : (Iterable<Element>)children) {
  73. try {
  74. myEntriesList.add(new HistoryEntry(myProject, e));
  75. }
  76. catch (InvalidDataException e1) {
  77. // OK here
  78. }
  79. catch (ProcessCanceledException e1) {
  80. // OK here
  81. }
  82. catch (Exception anyException) {
  83. LOG.error(anyException);
  84. }
  85. }
  86. trimToSize();
  87. }
  88. }
  89. }
  90. );
  91. }
  92. @NotNull
  93. public String getComponentName(){
  94. return "editorHistoryManager";
  95. }
  96. private void fileOpenedImpl(@NotNull final VirtualFile file) {
  97. fileOpenedImpl(file, null, null);
  98. }
  99. /**
  100. * Makes file most recent one
  101. */
  102. private void fileOpenedImpl(@NotNull final VirtualFile file,
  103. @Nullable final FileEditor fallbackEditor,
  104. @Nullable FileEditorProvider fallbackProvider)
  105. {
  106. ApplicationManager.getApplication().assertIsDispatchThread();
  107. // don't add files that cannot be found via VFM (light & etc.)
  108. if (VirtualFileManager.getInstance().findFileByUrl(file.getUrl()) == null) return;
  109. final FileEditorManagerEx editorManager = FileEditorManagerEx.getInstanceEx(myProject);
  110. final Pair<FileEditor[], FileEditorProvider[]> editorsWithProviders = editorManager.getEditorsWithProviders(file);
  111. FileEditor[] editors = editorsWithProviders.getFirst();
  112. FileEditorProvider[] oldProviders = editorsWithProviders.getSecond();
  113. if (editors.length <= 0 && fallbackEditor != null) {
  114. editors = new FileEditor[] { fallbackEditor };
  115. }
  116. if (oldProviders.length <= 0 && fallbackProvider != null) {
  117. oldProviders = new FileEditorProvider[] { fallbackProvider };
  118. }
  119. if (editors.length <= 0) {
  120. LOG.error("No editors for file " + file.getPresentableUrl());
  121. }
  122. FileEditor selectedEditor = editorManager.getSelectedEditor(file);
  123. if (selectedEditor == null) {
  124. selectedEditor = fallbackEditor;
  125. }
  126. LOG.assertTrue(selectedEditor != null);
  127. final int selectedProviderIndex = ArrayUtil.find(editors, selectedEditor);
  128. LOG.assertTrue(selectedProviderIndex != -1);
  129. final HistoryEntry entry = getEntry(file);
  130. if(entry != null){
  131. myEntriesList.remove(entry);
  132. myEntriesList.add(entry);
  133. }
  134. else {
  135. final FileEditorState[] states=new FileEditorState[editors.length];
  136. final FileEditorProvider[] providers=new FileEditorProvider[editors.length];
  137. for (int i = states.length - 1; i >= 0; i--) {
  138. final FileEditorProvider provider = oldProviders [i];
  139. LOG.assertTrue(provider != null);
  140. providers[i] = provider;
  141. states[i] = editors[i].getState(FileEditorStateLevel.FULL);
  142. }
  143. myEntriesList.add(new HistoryEntry(file, providers, states, providers[selectedProviderIndex]));
  144. trimToSize();
  145. }
  146. }
  147. private void updateHistoryEntry(@Nullable final VirtualFile file, final boolean changeEntryOrderOnly) {
  148. updateHistoryEntry(file, null, null, changeEntryOrderOnly);
  149. }
  150. private void updateHistoryEntry(@Nullable final VirtualFile file,
  151. @Nullable final FileEditor fallbackEditor,
  152. @Nullable FileEditorProvider fallbackProvider,
  153. final boolean changeEntryOrderOnly)
  154. {
  155. if (file == null) {
  156. return;
  157. }
  158. final FileEditorManagerEx editorManager = FileEditorManagerEx.getInstanceEx(myProject);
  159. final Pair<FileEditor[], FileEditorProvider[]> editorsWithProviders = editorManager.getEditorsWithProviders(file);
  160. FileEditor[] editors = editorsWithProviders.getFirst();
  161. FileEditorProvider[] providers = editorsWithProviders.getSecond();
  162. if (editors.length <= 0 && fallbackEditor != null) {
  163. editors = new FileEditor[] {fallbackEditor};
  164. providers = new FileEditorProvider[] {fallbackProvider};
  165. }
  166. if (editors.length == 0) {
  167. // obviously not opened in any editor at the moment,
  168. // makes no sense to put the file in the history
  169. return;
  170. }
  171. final HistoryEntry entry = getEntry(file);
  172. if(entry == null){
  173. // Size of entry list can be less than number of opened editors (some entries can be removed)
  174. if (file.isValid()) {
  175. // the file could have been deleted, so the isValid() check is essential
  176. fileOpenedImpl(file, fallbackEditor, fallbackProvider);
  177. }
  178. return;
  179. }
  180. if (!changeEntryOrderOnly) { // update entry state
  181. //LOG.assertTrue(editors.length > 0);
  182. for (int i = editors.length - 1; i >= 0; i--) {
  183. final FileEditor editor = editors [i];
  184. final FileEditorProvider provider = providers [i];
  185. if (!editor.isValid()) {
  186. // this can happen for example if file extension was changed
  187. // and this method was called during correponding myEditor close up
  188. continue;
  189. }
  190. final FileEditorState oldState = entry.getState(provider);
  191. final FileEditorState newState = editor.getState(FileEditorStateLevel.FULL);
  192. if (!newState.equals(oldState)) {
  193. entry.putState(provider, newState);
  194. }
  195. }
  196. }
  197. final Pair <FileEditor, FileEditorProvider> selectedEditorWithProvider = editorManager.getSelectedEditorWithProvider(file);
  198. if (selectedEditorWithProvider != null) {
  199. //LOG.assertTrue(selectedEditorWithProvider != null);
  200. entry.mySelectedProvider = selectedEditorWithProvider.getSecond ();
  201. LOG.assertTrue(entry.mySelectedProvider != null);
  202. if(changeEntryOrderOnly){
  203. myEntriesList.remove(entry);
  204. myEntriesList.add(entry);
  205. }
  206. }
  207. }
  208. /**
  209. * Removes all entries that correspond to invalid files
  210. */
  211. private void validateEntries(){
  212. for(int i=myEntriesList.size()-1; i>=0; i--){
  213. final HistoryEntry entry = myEntriesList.get(i);
  214. if(!entry.myFile.isValid()){
  215. myEntriesList.remove(i);
  216. }
  217. }
  218. }
  219. /**
  220. * @return array of valid files that are in the history. The greater is index the more recent the file is.
  221. */
  222. public VirtualFile[] getFiles(){
  223. validateEntries();
  224. final VirtualFile[] result = new VirtualFile[myEntriesList.size()];
  225. for(int i=myEntriesList.size()-1; i>=0 ;i--){
  226. result[i] = myEntriesList.get(i).myFile;
  227. }
  228. return result;
  229. }
  230. public boolean hasBeenOpen(@NotNull VirtualFile f) {
  231. for (HistoryEntry each : myEntriesList) {
  232. if (Comparing.equal(each.myFile, f)) return true;
  233. }
  234. return false;
  235. }
  236. /**
  237. * Removes specified <code>file</code> from history. The method does
  238. * nothing if <code>file</code> is not in the history.
  239. *
  240. * @exception java.lang.IllegalArgumentException if <code>file</code>
  241. * is <code>null</code>
  242. */
  243. public void removeFile(@NotNull final VirtualFile file){
  244. final HistoryEntry entry = getEntry(file);
  245. if(entry != null){
  246. myEntriesList.remove(entry);
  247. }
  248. }
  249. public FileEditorState getState(final VirtualFile file, final FileEditorProvider provider) {
  250. validateEntries();
  251. final HistoryEntry entry = getEntry(file);
  252. return entry != null ? entry.getState(provider) : null;
  253. }
  254. /**
  255. * @return may be null
  256. */
  257. public FileEditorProvider getSelectedProvider(final VirtualFile file) {
  258. validateEntries();
  259. final HistoryEntry entry = getEntry(file);
  260. return entry != null ? entry.mySelectedProvider : null;
  261. }
  262. private HistoryEntry getEntry(final VirtualFile file){
  263. validateEntries();
  264. for (int i = myEntriesList.size() - 1; i >= 0; i--) {
  265. final HistoryEntry entry = myEntriesList.get(i);
  266. if(file.equals(entry.myFile)){
  267. return entry;
  268. }
  269. }
  270. return null;
  271. }
  272. /**
  273. * If total number of files in history more then <code>UISettings.RECENT_FILES_LIMIT</code>
  274. * then removes the oldest ones to fit the history to new size.
  275. */
  276. private void trimToSize(){
  277. final int limit = UISettings.getInstance().RECENT_FILES_LIMIT + 1;
  278. while(myEntriesList.size()>limit){
  279. myEntriesList.remove(0);
  280. }
  281. }
  282. public void readExternal(final Element element) {
  283. // we have to delay xml processing because history entries require EditorStates to be created
  284. // which is done via corresponding EditorProviders, those are not accessible before their
  285. // is initComponent() called
  286. myElement = (Element)element.clone();
  287. }
  288. public void writeExternal(final Element element){
  289. // update history before saving
  290. final VirtualFile[] openFiles = FileEditorManager.getInstance(myProject).getOpenFiles();
  291. for (int i = openFiles.length - 1; i >= 0; i--) {
  292. final VirtualFile file = openFiles[i];
  293. if(getEntry(file) != null){ // we have to update only files that are in history
  294. updateHistoryEntry(file, false);
  295. }
  296. }
  297. for (final HistoryEntry entry : myEntriesList) {
  298. entry.writeExternal(element, myProject);
  299. }
  300. }
  301. /**
  302. * Updates history
  303. */
  304. private final class MyEditorManagerListener extends FileEditorManagerAdapter{
  305. public void fileOpened(final FileEditorManager source, final VirtualFile file){
  306. fileOpenedImpl(file);
  307. }
  308. public void selectionChanged(final FileEditorManagerEvent event){
  309. updateHistoryEntry(event.getOldFile(), event.getOldEditor(), event.getOldProvider(), false);
  310. updateHistoryEntry(event.getNewFile(), true);
  311. }
  312. }
  313. private final class MyEditorManagerBeforeListener extends FileEditorManagerListener.Before.Adapter {
  314. @Override
  315. public void beforeFileClosed(FileEditorManager source, VirtualFile file) {
  316. updateHistoryEntry(file, false);
  317. }
  318. }
  319. /**
  320. * Cuts/extends history length
  321. */
  322. private final class MyUISettingsListener implements UISettingsListener{
  323. public void uiSettingsChanged(final UISettings source) {
  324. trimToSize();
  325. }
  326. }
  327. }