com.intellij.openapi.command.impl.UndoRedoStacksHolder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of platform-impl Show documentation
Show all versions of platform-impl Show documentation
A packaging of the IntelliJ Community Edition platform-impl library.
This is release number 1 of trunk branch 142.
The newest version!
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.command.impl;
import com.intellij.openapi.command.undo.DocumentReference;
import com.intellij.openapi.command.undo.DocumentReferenceManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.WeakList;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import java.util.*;
class UndoRedoStacksHolder {
private final Key> STACK_IN_DOCUMENT_KEY = Key.create("STACK_IN_DOCUMENT_KEY");
private final boolean myUndo;
private final LinkedList myGlobalStack = new LinkedList();
// strongly reference local files for which we can undo file removal
// document without files and nonlocal files are stored without strong reference
private final Map> myDocumentStacks = new HashMap>();
private final WeakList myDocumentsWithStacks = new WeakList();
private final WeakList myNonlocalVirtualFilesWithStacks = new WeakList();
public UndoRedoStacksHolder(boolean isUndo) {
myUndo = isUndo;
}
@NotNull
LinkedList getStack(@NotNull DocumentReference r) {
return r.getFile() != null ? doGetStackForFile(r) : doGetStackForDocument(r);
}
@NotNull
private LinkedList doGetStackForFile(@NotNull DocumentReference r) {
LinkedList result;
VirtualFile file = r.getFile();
if (!file.isInLocalFileSystem()) {
result = addWeaklyTrackedEmptyStack(file, myNonlocalVirtualFilesWithStacks);
}
else {
result = myDocumentStacks.get(r);
if (result == null) {
result = new LinkedList();
myDocumentStacks.put(r, result);
}
}
return result;
}
@NotNull
private LinkedList doGetStackForDocument(@NotNull DocumentReference r) {
// If document is not associated with file, we have to store its stack in document
// itself to avoid memory leaks caused by holding stacks of all documents, ever created, here.
// And to know, what documents do exist now, we have to maintain weak reference list of them.
return addWeaklyTrackedEmptyStack(r.getDocument(), myDocumentsWithStacks);
}
private LinkedList addWeaklyTrackedEmptyStack(T holder, WeakList allHolders) {
LinkedList result;
result = holder.getUserData(STACK_IN_DOCUMENT_KEY);
if (result == null) {
holder.putUserData(STACK_IN_DOCUMENT_KEY, result = new LinkedList());
allHolders.add(holder);
}
return result;
}
public boolean canBeUndoneOrRedone(@NotNull Collection refs) {
if (refs.isEmpty()) return !myGlobalStack.isEmpty() && myGlobalStack.getLast().isValid();
for (DocumentReference each : refs) {
if (!getStack(each).isEmpty() && getStack(each).getLast().isValid()) return true;
}
return false;
}
@NotNull
public UndoableGroup getLastAction(Collection refs) {
if (refs.isEmpty()) return myGlobalStack.getLast();
UndoableGroup mostRecentAction = null;
int mostRecentDocTimestamp = myUndo ? -1 : Integer.MAX_VALUE;
for (DocumentReference each : refs) {
LinkedList stack = getStack(each);
// the stack for a document can be empty in case of compound editors with several documents
if (stack.isEmpty()) continue;
UndoableGroup lastAction = stack.getLast();
int timestamp = lastAction.getCommandTimestamp();
if (myUndo ? timestamp > mostRecentDocTimestamp : timestamp < mostRecentDocTimestamp) {
mostRecentAction = lastAction;
mostRecentDocTimestamp = timestamp;
}
}
// result must not be null
return mostRecentAction;
}
@NotNull
public Set collectClashingActions(@NotNull UndoableGroup group) {
Set result = new THashSet();
for (DocumentReference each : group.getAffectedDocuments()) {
UndoableGroup last = getStack(each).getLast();
if (last != group) {
result.addAll(last.getAffectedDocuments());
}
}
if (group.isGlobal()) {
UndoableGroup last = myGlobalStack.getLast();
if (last != group) {
result.addAll(last.getAffectedDocuments());
}
}
return result;
}
public void addToStacks(@NotNull UndoableGroup group) {
for (LinkedList each : getAffectedStacks(group)) {
doAddToStack(each, group, each == myGlobalStack ? UndoManagerImpl.getGlobalUndoLimit() : UndoManagerImpl.getDocumentUndoLimit());
}
}
private void doAddToStack(@NotNull LinkedList stack, @NotNull UndoableGroup group, int limit) {
if (!group.isUndoable() && stack.isEmpty()) return;
stack.addLast(group);
while (stack.size() > limit) {
clearStacksFrom(stack.getFirst());
}
}
public void removeFromStacks(@NotNull UndoableGroup group) {
for (LinkedList each : getAffectedStacks(group)) {
assert each.getLast() == group;
each.removeLast();
}
}
public void clearStacks(boolean clearGlobal, @NotNull Set refs) {
for (LinkedList each : getAffectedStacks(clearGlobal, refs)) {
while(!each.isEmpty()) {
clearStacksFrom(each.getLast());
}
}
Set stacksToDrop = new THashSet();
for (Map.Entry> each : myDocumentStacks.entrySet()) {
if (each.getValue().isEmpty()) stacksToDrop.add(each.getKey());
}
for (DocumentReference each : stacksToDrop) {
myDocumentStacks.remove(each);
}
cleanWeaklyTrackedEmptyStacks(myDocumentsWithStacks);
cleanWeaklyTrackedEmptyStacks(myNonlocalVirtualFilesWithStacks);
}
private void cleanWeaklyTrackedEmptyStacks(WeakList stackHolders) {
Set holdersToDrop = new THashSet();
for (T holder : stackHolders) {
LinkedList stack = holder.getUserData(STACK_IN_DOCUMENT_KEY);
if (stack != null && stack.isEmpty()) {
holder.putUserData(STACK_IN_DOCUMENT_KEY, null);
holdersToDrop.add(holder);
}
}
stackHolders.removeAll(holdersToDrop);
}
private void clearStacksFrom(@NotNull UndoableGroup from) {
for (LinkedList each : getAffectedStacks(from)) {
int pos = each.indexOf(from);
if (pos == -1) continue;
if (pos > 0) {
int top = each.size() - pos;
clearStacksFrom(each.get(pos - 1));
assert each.size() == top && each.indexOf(from) == 0;
}
each.removeFirst();
}
}
@NotNull
private List> getAffectedStacks(@NotNull UndoableGroup group) {
return getAffectedStacks(group.isGlobal(), group.getAffectedDocuments());
}
@NotNull
private List> getAffectedStacks(boolean global, @NotNull Collection refs) {
List> result = new ArrayList>(refs.size() + 1);
if (global) result.add(myGlobalStack);
for (DocumentReference each : refs) {
result.add(getStack(each));
}
return result;
}
public void clearAllStacksInTests() {
clearStacks(true, getAffectedDocuments());
}
public void collectAllAffectedDocuments(@NotNull Collection result) {
for (UndoableGroup each : myGlobalStack) {
result.addAll(each.getAffectedDocuments());
}
collectLocalAffectedDocuments(result);
}
private void collectLocalAffectedDocuments(@NotNull Collection result) {
result.addAll(myDocumentStacks.keySet());
DocumentReferenceManager documentReferenceManager = DocumentReferenceManager.getInstance();
for (Document each : myDocumentsWithStacks) {
result.add(documentReferenceManager.create(each));
}
for (VirtualFile each : myNonlocalVirtualFilesWithStacks) {
result.add(documentReferenceManager.create(each));
}
}
@NotNull
private Set getAffectedDocuments() {
Set result = new THashSet();
collectAllAffectedDocuments(result);
return result;
}
public int getLastCommandTimestamp(@NotNull DocumentReference r) {
LinkedList stack = getStack(r);
if (stack.isEmpty()) return 0;
return Math.max(stack.getFirst().getCommandTimestamp(), stack.getLast().getCommandTimestamp());
}
public void invalidateActionsFor(@NotNull DocumentReference ref) {
for (LinkedList eachStack : getAffectedStacks(true, Collections.singleton(ref))) {
for (UndoableGroup eachGroup : eachStack) {
eachGroup.invalidateActionsFor(ref);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy