From 35aeb7c725d641bdccc6cb0b2cc5aae4ccc85a57 Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Mon, 26 Oct 2015 11:57:25 +0300 Subject: [PATCH] IDEA-80001 remember recent files, that were deleted and restored later --- .../fileEditor/impl/EditorHistoryManager.java | 86 +++++--- .../impl/EditorWithProviderComposite.java | 2 +- .../fileEditor/impl/EditorsSplitters.java | 9 +- .../impl/FileEditorManagerImpl.java | 2 +- .../openapi/fileEditor/impl/HistoryEntry.java | 192 ++++++++++++++---- 5 files changed, 211 insertions(+), 80 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorHistoryManager.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorHistoryManager.java index 34e5417dc0bd..07476c37bd08 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorHistoryManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorHistoryManager.java @@ -29,6 +29,7 @@ import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.psi.PsiDocumentManager; @@ -45,7 +46,7 @@ import java.util.LinkedHashSet; import java.util.List; @State(name = "editorHistoryManager", storages = @Storage(file = StoragePathMacros.WORKSPACE_FILE)) -public final class EditorHistoryManager implements PersistentStateComponent { +public final class EditorHistoryManager implements PersistentStateComponent, ProjectComponent { private static final Logger LOG = Logger.getInstance(EditorHistoryManager.class); private final Project myProject; @@ -84,7 +85,13 @@ public final class EditorHistoryManager implements PersistentStateComponent selectedEditorWithProvider = editorManager.getSelectedEditorWithProvider(file); if (selectedEditorWithProvider != null) { //LOG.assertTrue(selectedEditorWithProvider != null); - entry.mySelectedProvider = selectedEditorWithProvider.getSecond (); - LOG.assertTrue(entry.mySelectedProvider != null); + entry.setSelectedProvider(selectedEditorWithProvider.getSecond()); + LOG.assertTrue(entry.getSelectedProvider() != null); if(changeEntryOrderOnly){ - removeEntry(entry); - addEntry(entry); - } - } - } - - /** - * Removes all entries that correspond to invalid files - */ - private synchronized void validateEntries(){ - for(int i=myEntriesList.size()-1; i>=0; i--){ - final HistoryEntry entry = myEntriesList.get(i); - if(!entry.myFile.isValid()){ - myEntriesList.remove(i); + moveOnTop(entry); } } } @@ -221,12 +214,12 @@ public final class EditorHistoryManager implements PersistentStateComponent=0 ;i--){ - result[i] = myEntriesList.get(i).myFile; + final List result = new ArrayList(myEntriesList.size()); + for (HistoryEntry entry : myEntriesList) { + VirtualFile file = entry.getFile(); + if (file != null) result.add(file); } - return result; + return VfsUtilCore.toVirtualFileArray(result); } /** @@ -235,7 +228,7 @@ public final class EditorHistoryManager implements PersistentStateComponent getFileSet() { LinkedHashSet result = ContainerUtil.newLinkedHashSet(); for (VirtualFile file : getFiles()) { - // if the file occurs several times in the history, only its last occurrence counts + // if the file occurs several times in the history, only its last occurrence counts result.remove(file); result.add(file); } @@ -244,7 +237,7 @@ public final class EditorHistoryManager implements PersistentStateComponent= 0; i--) { final HistoryEntry entry = myEntriesList.get(i); - if(file.equals(entry.myFile)){ + VirtualFile entryFile = entry.getFile(); + if (file.equals(entryFile)) { return entry; } } @@ -294,7 +287,8 @@ public final class EditorHistoryManager implements PersistentStateComponentlimit){ - myEntriesList.remove(0); + HistoryEntry removed = myEntriesList.remove(0); + removed.destroy(); } } @@ -309,7 +303,7 @@ public final class EditorHistoryManager implements PersistentStateComponent() { @Override public Document compute() { return virtualFile.isValid() ? FileDocumentManager.getInstance().getDocument(virtualFile) : null; } }); - final HistoryEntry entry = new HistoryEntry(fileEditorManager.getProject(), historyElement); final boolean isCurrentInTab = Boolean.valueOf(file.getAttributeValue(CURRENT_IN_TAB)).booleanValue(); Boolean pin = Boolean.valueOf(file.getAttributeValue(PINNED)); - fileEditorManager.openFileImpl4(window, entry.myFile, entry, isCurrentInTab, isCurrentInTab, pin, i); + fileEditorManager.openFileImpl4(window, virtualFile, entry, isCurrentInTab, isCurrentInTab, pin, i); if (isCurrentInTab) { - focusedFile = entry.myFile; + focusedFile = virtualFile; } if (document != null) { // This is just to make sure document reference is kept on stack till this point diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java index 4d668eaf0e20..c80a96b6b56f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java @@ -915,7 +915,7 @@ public class FileEditorManagerImpl extends FileEditorManagerEx implements Projec .getSelectedFileEditorProvider(myEditorHistoryManager, file, providers); } else { - selectedProvider = entry.mySelectedProvider; + selectedProvider = entry.getSelectedProvider(); } if (selectedProvider != null) { for (int i = editors.length - 1; i >= 0; i--) { diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/HistoryEntry.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/HistoryEntry.java index 9ec85779ff60..76969085331b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/HistoryEntry.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/HistoryEntry.java @@ -15,21 +15,33 @@ */ package com.intellij.openapi.fileEditor.impl; +import com.intellij.openapi.Disposable; import com.intellij.openapi.fileEditor.FileEditorProvider; import com.intellij.openapi.fileEditor.FileEditorState; import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ui.LightFilePointer; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.pointers.VirtualFilePointer; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.util.containers.HashMap; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -final class HistoryEntry{ +/** + * `Heavy` entries should be disposed with {@link #destroy()} to prevent leak of VirtualFilePointer + */ +final class HistoryEntry { @NonNls static final String TAG = "entry"; private static final String FILE_ATTR = "file"; @NonNls private static final String PROVIDER_ELEMENT = "provider"; @@ -37,43 +49,92 @@ final class HistoryEntry{ @NonNls private static final String SELECTED_ATTR_VALUE = "selected"; @NonNls private static final String STATE_ELEMENT = "state"; - public final VirtualFile myFile; + @NotNull private final VirtualFilePointer myFilePointer; /** * can be null when read from XML - */ - public FileEditorProvider mySelectedProvider; - private final HashMap myProvider2State; + */ + @Nullable private FileEditorProvider mySelectedProvider; + @NotNull private final HashMap myProvider2State; - public HistoryEntry(@NotNull VirtualFile file, @NotNull FileEditorProvider[] providers, @NotNull FileEditorState[] states, @NotNull FileEditorProvider selectedProvider){ - myFile = file; - myProvider2State = new HashMap(); + @Nullable private final Disposable myDisposable; + + private HistoryEntry(@NotNull VirtualFilePointer filePointer, + @Nullable FileEditorProvider selectedProvider, + @Nullable Disposable disposable) { + myFilePointer = filePointer; mySelectedProvider = selectedProvider; - for (int i = 0; i < providers.length; i++) { - putState(providers[i], states[i]); - } + myDisposable = disposable; + myProvider2State = new HashMap(); } - public HistoryEntry(@NotNull Project project, @NotNull Element e) throws InvalidDataException { - myFile = getVirtualFile(e); - myProvider2State = new HashMap(); - - for (Element _e : e.getChildren(PROVIDER_ELEMENT)) { - String typeId = _e.getAttributeValue(EDITOR_TYPE_ID_ATTR); - FileEditorProvider provider = FileEditorProviderManager.getInstance().getProvider(typeId); - if (provider == null) { - continue; - } - if (Boolean.valueOf(_e.getAttributeValue(SELECTED_ATTR_VALUE))) { - mySelectedProvider = provider; - } - - Element stateElement = _e.getChild(STATE_ELEMENT); - if (stateElement == null) { - throw new InvalidDataException(); - } - - putState(provider, provider.readState(stateElement, project, myFile)); + @NotNull + public static HistoryEntry createLight(@NotNull VirtualFile file, + @NotNull FileEditorProvider[] providers, + @NotNull FileEditorState[] states, + @NotNull FileEditorProvider selectedProvider) { + VirtualFilePointer pointer = new LightFilePointer(file); + HistoryEntry entry = new HistoryEntry(pointer, selectedProvider, null); + for (int i = 0; i < providers.length; i++) { + entry.putState(providers[i], states[i]); } + return entry; + } + + @NotNull + public static HistoryEntry createLight(@NotNull Project project, @NotNull Element e) throws InvalidDataException { + EntryData entryData = parseEntry(project, e); + + VirtualFilePointer pointer = new LightFilePointer(entryData.url); + HistoryEntry entry = new HistoryEntry(pointer, entryData.selectedProvider, null); + for (Pair state : entryData.providerStates) { + entry.putState(state.first, state.second); + } + return entry; + } + + @NotNull + public static HistoryEntry createHeavy(@NotNull Project project, + @NotNull VirtualFile file, + @NotNull FileEditorProvider[] providers, + @NotNull FileEditorState[] states, + @NotNull FileEditorProvider selectedProvider) { + if (project.isDisposed()) return createLight(file, providers, states, selectedProvider); + + Disposable disposable = Disposer.newDisposable(); + VirtualFilePointer pointer = VirtualFilePointerManager.getInstance().create(file, disposable, null); + + HistoryEntry entry = new HistoryEntry(pointer, selectedProvider, disposable); + for (int i = 0; i < providers.length; i++) { + entry.putState(providers[i], states[i]); + } + return entry; + } + + @NotNull + public static HistoryEntry createHeavy(@NotNull Project project, @NotNull Element e) throws InvalidDataException { + if (project.isDisposed()) return createLight(project, e); + + EntryData entryData = parseEntry(project, e); + + Disposable disposable = Disposer.newDisposable(); + VirtualFilePointer pointer = VirtualFilePointerManager.getInstance().create(entryData.url, disposable, null); + + HistoryEntry entry = new HistoryEntry(pointer, entryData.selectedProvider, disposable); + for (Pair state : entryData.providerStates) { + entry.putState(state.first, state.second); + } + return entry; + } + + + @NotNull + public VirtualFilePointer getFilePointer() { + return myFilePointer; + } + + @Nullable + public VirtualFile getFile() { + return myFilePointer.getFile(); } public FileEditorState getState(@NotNull FileEditorProvider provider) { @@ -84,6 +145,19 @@ final class HistoryEntry{ myProvider2State.put(provider, state); } + @Nullable + public FileEditorProvider getSelectedProvider() { + return mySelectedProvider; + } + + public void setSelectedProvider(@Nullable FileEditorProvider value) { + mySelectedProvider = value; + } + + public void destroy() { + if (myDisposable != null) Disposer.dispose(myDisposable); + } + /** * @return element that was added to the element. * Returned element has tag {@link #TAG}. Never null. @@ -91,7 +165,7 @@ final class HistoryEntry{ public Element writeExternal(Element element, Project project) { Element e = new Element(TAG); element.addContent(e); - e.setAttribute(FILE_ATTR, myFile.getUrl()); + e.setAttribute(FILE_ATTR, myFilePointer.getUrl()); for (final Map.Entry entry : myProvider2State.entrySet()) { FileEditorProvider provider = entry.getKey(); @@ -112,16 +186,52 @@ final class HistoryEntry{ } @NotNull - public static VirtualFile getVirtualFile(Element historyElement) throws InvalidDataException { - if (!historyElement.getName().equals(TAG)) { - throw new IllegalArgumentException("unexpected tag: " + historyElement); + private static EntryData parseEntry(@NotNull Project project, @NotNull Element e) throws InvalidDataException { + if (!e.getName().equals(TAG)) { + throw new IllegalArgumentException("unexpected tag: " + e); } - String url = historyElement.getAttributeValue(FILE_ATTR); - VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url); - if (file == null){ - throw new InvalidDataException("No file exists: " + url); + String url = e.getAttributeValue(FILE_ATTR); + List> providerStates = new ArrayList>(); + FileEditorProvider selectedProvider = null; + + VirtualFile file = LocalFileSystem.getInstance().findFileByPath(url); + + for (Element _e : e.getChildren(PROVIDER_ELEMENT)) { + String typeId = _e.getAttributeValue(EDITOR_TYPE_ID_ATTR); + FileEditorProvider provider = FileEditorProviderManager.getInstance().getProvider(typeId); + if (provider == null) { + continue; + } + if (Boolean.valueOf(_e.getAttributeValue(SELECTED_ATTR_VALUE))) { + selectedProvider = provider; + } + + Element stateElement = _e.getChild(STATE_ELEMENT); + if (stateElement == null) { + throw new InvalidDataException(); + } + + if (file != null) { + FileEditorState state = provider.readState(stateElement, project, file); + providerStates.add(Pair.create(provider, state)); + } + } + + return new EntryData(url, providerStates, selectedProvider); + } + + private static class EntryData { + @NotNull public final String url; + @NotNull public final List> providerStates; + @Nullable public final FileEditorProvider selectedProvider; + + public EntryData(@NotNull String url, + @NotNull List> providerStates, + @Nullable FileEditorProvider selectedProvider) { + this.url = url; + this.providerStates = providerStates; + this.selectedProvider = selectedProvider; } - return file; } }