IDEA-80001 remember recent files, that were deleted and restored later

This commit is contained in:
Aleksey Pivovarov
2015-10-26 12:04:24 +03:00
parent 3a74cbb7f5
commit 35aeb7c725
5 changed files with 211 additions and 80 deletions
@@ -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<Element> {
public final class EditorHistoryManager implements PersistentStateComponent<Element>, ProjectComponent {
private static final Logger LOG = Logger.getInstance(EditorHistoryManager.class);
private final Project myProject;
@@ -84,7 +85,13 @@ public final class EditorHistoryManager implements PersistentStateComponent<Elem
}
private synchronized void removeEntry(HistoryEntry entry) {
boolean removed = myEntriesList.remove(entry);
if (removed) entry.destroy();
}
private synchronized void moveOnTop(HistoryEntry entry) {
myEntriesList.remove(entry);
myEntriesList.add(entry);
}
/**
@@ -122,8 +129,7 @@ public final class EditorHistoryManager implements PersistentStateComponent<Elem
final HistoryEntry entry = getEntry(file);
if(entry != null){
removeEntry(entry);
addEntry(entry);
moveOnTop(entry);
}
else {
final FileEditorState[] states=new FileEditorState[editors.length];
@@ -134,7 +140,7 @@ public final class EditorHistoryManager implements PersistentStateComponent<Elem
providers[i] = provider;
states[i] = editors[i].getState(FileEditorStateLevel.FULL);
}
addEntry(new HistoryEntry(file, providers, states, providers[selectedProviderIndex]));
addEntry(HistoryEntry.createHeavy(myProject, file, providers, states, providers[selectedProviderIndex]));
trimToSize();
}
}
@@ -195,24 +201,11 @@ public final class EditorHistoryManager implements PersistentStateComponent<Elem
final Pair <FileEditor, FileEditorProvider> 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<Elem
* @return array of valid files that are in the history, oldest first. May contain duplicates.
*/
public synchronized VirtualFile[] getFiles(){
validateEntries();
final VirtualFile[] result = new VirtualFile[myEntriesList.size()];
for(int i=myEntriesList.size()-1; i>=0 ;i--){
result[i] = myEntriesList.get(i).myFile;
final List<VirtualFile> result = new ArrayList<VirtualFile>(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<Elem
public LinkedHashSet<VirtualFile> getFileSet() {
LinkedHashSet<VirtualFile> 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<Elem
public synchronized boolean hasBeenOpen(@NotNull VirtualFile f) {
for (HistoryEntry each : myEntriesList) {
if (Comparing.equal(each.myFile, f)) return true;
if (Comparing.equal(each.getFile(), f)) return true;
}
return false;
}
@@ -273,14 +266,14 @@ public final class EditorHistoryManager implements PersistentStateComponent<Elem
*/
public FileEditorProvider getSelectedProvider(final VirtualFile file) {
final HistoryEntry entry = getEntry(file);
return entry != null ? entry.mySelectedProvider : null;
return entry != null ? entry.getSelectedProvider() : null;
}
private synchronized HistoryEntry getEntry(@NotNull VirtualFile file){
validateEntries();
private synchronized HistoryEntry getEntry(@NotNull VirtualFile file) {
for (int i = myEntriesList.size() - 1; i >= 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 PersistentStateComponent<Elem
private synchronized void trimToSize(){
final int limit = UISettings.getInstance().RECENT_FILES_LIMIT + 1;
while(myEntriesList.size()>limit){
myEntriesList.remove(0);
HistoryEntry removed = myEntriesList.remove(0);
removed.destroy();
}
}
@@ -309,7 +303,7 @@ public final class EditorHistoryManager implements PersistentStateComponent<Elem
public void run() {
for (Element e : state.getChildren(HistoryEntry.TAG)) {
try {
addEntry(new HistoryEntry(myProject, e));
addEntry(HistoryEntry.createHeavy(myProject, e));
}
catch (InvalidDataException e1) {
// OK here
@@ -345,6 +339,32 @@ public final class EditorHistoryManager implements PersistentStateComponent<Elem
return element;
}
@Override
public void projectOpened() {
}
@Override
public void projectClosed() {
}
@Override
public void initComponent() {
}
@Override
public synchronized void disposeComponent() {
for (HistoryEntry entry : myEntriesList) {
entry.destroy();
}
myEntriesList.clear();
}
@NotNull
@Override
public String getComponentName() {
return "editorHistoryManager";
}
/**
* Updates history
*/
@@ -88,7 +88,7 @@ public class EditorWithProviderComposite extends EditorComposite {
final int selectedProviderIndex = ArrayUtil.find(editors, getSelectedEditor());
LOG.assertTrue(selectedProviderIndex != -1);
final FileEditorProvider[] providers = getProviders();
return new HistoryEntry(getFile(), providers, states, providers[selectedProviderIndex]);
return HistoryEntry.createLight(getFile(), providers, states, providers[selectedProviderIndex]);
}
public void addEditor(@NotNull FileEditor editor, FileEditorProvider provider) {
@@ -862,19 +862,20 @@ public class EditorsSplitters extends IdePanePanel implements UISettingsListener
try {
final FileEditorManagerImpl fileEditorManager = getManager();
Element historyElement = file.getChild(HistoryEntry.TAG);
final VirtualFile virtualFile = HistoryEntry.getVirtualFile(historyElement);
final HistoryEntry entry = HistoryEntry.createLight(fileEditorManager.getProject(), historyElement);
final VirtualFile virtualFile = entry.getFile();
if (virtualFile == null) throw new InvalidDataException("No file exists: " + entry.getFilePointer().getUrl());
Document document = ApplicationManager.getApplication().runReadAction(new Computable<Document>() {
@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
@@ -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--) {
@@ -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<FileEditorProvider, FileEditorState> myProvider2State;
*/
@Nullable private FileEditorProvider mySelectedProvider;
@NotNull private final HashMap<FileEditorProvider, FileEditorState> myProvider2State;
public HistoryEntry(@NotNull VirtualFile file, @NotNull FileEditorProvider[] providers, @NotNull FileEditorState[] states, @NotNull FileEditorProvider selectedProvider){
myFile = file;
myProvider2State = new HashMap<FileEditorProvider, FileEditorState>();
@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<FileEditorProvider, FileEditorState>();
}
public HistoryEntry(@NotNull Project project, @NotNull Element e) throws InvalidDataException {
myFile = getVirtualFile(e);
myProvider2State = new HashMap<FileEditorProvider, FileEditorState>();
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<FileEditorProvider, FileEditorState> 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<FileEditorProvider, FileEditorState> 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 <code>element</code>.
* 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<FileEditorProvider, FileEditorState> 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<Pair<FileEditorProvider, FileEditorState>> providerStates = new ArrayList<Pair<FileEditorProvider, FileEditorState>>();
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<Pair<FileEditorProvider, FileEditorState>> providerStates;
@Nullable public final FileEditorProvider selectedProvider;
public EntryData(@NotNull String url,
@NotNull List<Pair<FileEditorProvider, FileEditorState>> providerStates,
@Nullable FileEditorProvider selectedProvider) {
this.url = url;
this.providerStates = providerStates;
this.selectedProvider = selectedProvider;
}
return file;
}
}