mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[undo] IJPL-201535 Support speculative undo for global and multi-project case
GitOrigin-RevId: 09805ee7fdf15ec02117d86ce604345775af5bf0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f887e139ca
commit
46a8fcb887
+1
-1
@@ -29,7 +29,7 @@ object SpeculativeUndoStatCollector : CounterUsagesCollector() {
|
||||
/**
|
||||
* Collects project id, file type and number of successful undo/redo requests before the corruption
|
||||
*/
|
||||
fun logUndoCorruption(project: Project, fileEditor: FileEditor?, undoCount: Int) {
|
||||
fun logUndoCorruption(project: Project?, fileEditor: FileEditor?, undoCount: Int) {
|
||||
UNDO_CORRUPTION.log(project) {
|
||||
val fileType = fileEditor?.file?.fileType
|
||||
add(EventFields.FileType with fileType)
|
||||
|
||||
@@ -97,19 +97,35 @@ public final class CommandSeparator implements CommandListener {
|
||||
}
|
||||
|
||||
private void notifyCommandStarted(@NotNull CommandEvent event) {
|
||||
publisher.onCommandStarted(createCmdEvent(event));
|
||||
notifyCommandStarted(createCmdEvent(event));
|
||||
}
|
||||
|
||||
private void notifyCommandFinished(@NotNull CommandEvent event) {
|
||||
publisher.onCommandFinished(createCmdEvent(event));
|
||||
notifyCommandFinished(createCmdEvent(event));
|
||||
}
|
||||
|
||||
private void notifyTransparentStarted() {
|
||||
publisher.onCommandStarted(createTransparentCmdEvent());
|
||||
notifyCommandStarted(createTransparentCmdEvent());
|
||||
}
|
||||
|
||||
private void notifyTransparentFinished() {
|
||||
publisher.onCommandFinished(createTransparentCmdEvent());
|
||||
notifyCommandFinished(createTransparentCmdEvent());
|
||||
}
|
||||
|
||||
private void notifyCommandStarted(@NotNull CmdEvent cmdEvent) {
|
||||
publisher.onCommandStarted(cmdEvent);
|
||||
UndoSpy undoSpy = UndoSpy.getInstance();
|
||||
if (undoSpy != null) {
|
||||
undoSpy.commandStarted(cmdEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCommandFinished(@NotNull CmdEvent cmdEvent) {
|
||||
publisher.onCommandFinished(cmdEvent);
|
||||
UndoSpy undoSpy = UndoSpy.getInstance();
|
||||
if (undoSpy != null) {
|
||||
undoSpy.commandFinished(cmdEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertInsideCommand() {
|
||||
|
||||
+8
-3
@@ -7,13 +7,18 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ApiStatus.Internal
|
||||
final class SharedUndoRedoStacksHolder extends UndoRedoStacksHolderBase<ImmutableActionChangeRange> {
|
||||
private final SharedAdjustableUndoableActionsHolder myAdjustableUndoableActionsHolder;
|
||||
private final boolean myIsPerClientSupported;
|
||||
private final Supplier<Boolean> myIsPerClientSupported;
|
||||
|
||||
SharedUndoRedoStacksHolder(SharedAdjustableUndoableActionsHolder undoableActionsHolder, boolean isPerClientSupported, boolean isUndo) {
|
||||
SharedUndoRedoStacksHolder(
|
||||
@NotNull SharedAdjustableUndoableActionsHolder undoableActionsHolder,
|
||||
@NotNull Supplier<Boolean> isPerClientSupported,
|
||||
boolean isUndo
|
||||
) {
|
||||
super(isUndo);
|
||||
myAdjustableUndoableActionsHolder = undoableActionsHolder;
|
||||
myIsPerClientSupported = isPerClientSupported;
|
||||
@@ -60,7 +65,7 @@ final class SharedUndoRedoStacksHolder extends UndoRedoStacksHolderBase<Immutabl
|
||||
}
|
||||
|
||||
@NotNull MovementAvailability canMoveToStackTop(@NotNull DocumentReference reference, @NotNull Map<Integer, MutableActionChangeRange> rangesToMove) {
|
||||
if (!myIsPerClientSupported) {
|
||||
if (!myIsPerClientSupported.get()) {
|
||||
return MovementAvailability.ALREADY_MOVED;
|
||||
}
|
||||
UndoRedoList<ImmutableActionChangeRange> stack = getStack(reference);
|
||||
|
||||
@@ -41,7 +41,6 @@ final class UndoClientState implements Disposable {
|
||||
private final @NotNull UndoRedoStacksHolder undoStacksHolder;
|
||||
private final @NotNull UndoRedoStacksHolder redoStacksHolder;
|
||||
|
||||
private final @NotNull UndoSpy undoSpy;
|
||||
private final boolean isConfirmationSupported;
|
||||
private final boolean isCompactSupported;
|
||||
private final boolean isGlobalSplitSupported;
|
||||
@@ -65,7 +64,6 @@ final class UndoClientState implements Disposable {
|
||||
private UndoClientState(@NotNull UndoManagerImpl undoManager, @NotNull ClientId clientId) {
|
||||
this.clientId = clientId;
|
||||
this.project = undoManager.getProject();
|
||||
this.undoSpy = undoManager.getUndoSpy();
|
||||
this.isConfirmationSupported = undoManager.isConfirmationSupported();
|
||||
this.isCompactSupported = undoManager.isCompactSupported();
|
||||
this.isGlobalSplitSupported = undoManager.isGlobalSplitSupported();
|
||||
@@ -162,13 +160,12 @@ final class UndoClientState implements Disposable {
|
||||
|
||||
void commandStarted(@NotNull CmdEvent cmdEvent, @NotNull CurrentEditorProvider editorProvider) {
|
||||
commandBuilder.commandStarted(cmdEvent, editorProvider);
|
||||
undoSpy.commandStarted(cmdEvent);
|
||||
}
|
||||
|
||||
void commandFinished(@NotNull CmdEvent cmdEvent) {
|
||||
PerformedCommand performedCommand = commandBuilder.commandFinished(cmdEvent);
|
||||
commitCommand(performedCommand);
|
||||
notifyUndoSpy(cmdEvent, performedCommand);
|
||||
notifyUndoSpy(performedCommand);
|
||||
}
|
||||
|
||||
private void commitCommand(@NotNull PerformedCommand performedCommand) {
|
||||
@@ -183,14 +180,14 @@ final class UndoClientState implements Disposable {
|
||||
commandMerger.mergeWithPerformedCommand(performedCommand);
|
||||
}
|
||||
|
||||
private void notifyUndoSpy(@NotNull CmdEvent cmdEvent, @NotNull PerformedCommand performedCommand) {
|
||||
private void notifyUndoSpy(@NotNull PerformedCommand performedCommand) {
|
||||
for (UndoableAction action : performedCommand.undoableActions()) {
|
||||
sharedState.addAction(action);
|
||||
if (cmdEvent.project() != null) {
|
||||
undoSpy.undoableActionAdded(cmdEvent.project(), action, UndoableActionType.forAction(action));
|
||||
UndoSpy undoSpy = UndoSpy.getInstance();
|
||||
if (undoSpy != null) {
|
||||
undoSpy.undoableActionAdded(project, action, UndoableActionType.forAction(action));
|
||||
}
|
||||
}
|
||||
undoSpy.commandFinished(cmdEvent);
|
||||
}
|
||||
|
||||
void flushCommandMerger(@NotNull UndoCommandFlushReason flushReason) {
|
||||
@@ -369,7 +366,10 @@ final class UndoClientState implements Disposable {
|
||||
|
||||
private void flushCommandMerger(@NotNull UndoCommandFlushReason flushReason, @Nullable PerformedCommand performedCommand) {
|
||||
if (performedCommand != null && !performedCommand.hasActions() && commandMerger.hasActions() && !isUndoOrRedoInProgress()) {
|
||||
undoSpy.commandMergerFlushed(project);
|
||||
UndoSpy undoSpy = UndoSpy.getInstance();
|
||||
if (undoSpy != null) {
|
||||
undoSpy.commandMergerFlushed(project);
|
||||
}
|
||||
}
|
||||
UndoableGroup group = commandMerger.formGroup(flushReason, nextCommandTimestamp());
|
||||
if (group != null) {
|
||||
@@ -384,7 +384,7 @@ final class UndoClientState implements Disposable {
|
||||
docsOnStacks.removeIf(doc -> UndoDocumentUtil.isDocumentOpened(project, doc));
|
||||
if (docsOnStacks.size() > FREE_QUEUES_LIMIT) {
|
||||
DocumentReference[] docsBackSorted = docsOnStacks.toArray(DocumentReference.EMPTY_ARRAY);
|
||||
Arrays.sort(docsBackSorted, Comparator.comparingInt(doc -> getLastCommandTimestamp(doc)));
|
||||
Arrays.sort(docsBackSorted, Comparator.comparingInt(this::getLastCommandTimestamp));
|
||||
for (int i = 0; i < docsBackSorted.length - FREE_QUEUES_LIMIT; i++) {
|
||||
DocumentReference doc = docsBackSorted[i];
|
||||
if (getLastCommandTimestamp(doc) + COMMANDS_TO_KEEP_LIVE_QUEUES > commandTimestamp) {
|
||||
|
||||
@@ -77,7 +77,7 @@ public class UndoManagerImpl extends UndoManager {
|
||||
@NonInjectable
|
||||
protected UndoManagerImpl(@Nullable ComponentManager componentManager) {
|
||||
myProject = componentManager instanceof Project project ? project : null;
|
||||
myUndoSharedState = new UndoSharedState(isPerClientSupported());
|
||||
myUndoSharedState = new UndoSharedState(this::isPerClientSupported);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -292,8 +292,9 @@ public class UndoManagerImpl extends UndoManager {
|
||||
} finally {
|
||||
Disposer.dispose(disposable);
|
||||
}
|
||||
if (myProject != null) {
|
||||
getUndoSpy().undoRedoPerformed(myProject, editor, isUndo);
|
||||
UndoSpy undoSpy = UndoSpy.getInstance();
|
||||
if (undoSpy != null) {
|
||||
undoSpy.undoRedoPerformed(myProject, editor, isUndo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,11 +307,6 @@ public class UndoManagerImpl extends UndoManager {
|
||||
.undoRedoStarted(myProject, this, editor, isUndo, disposable);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
protected @NotNull UndoSpy getUndoSpy() {
|
||||
return UndoSpy.BLIND;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
protected boolean isTransparentSupported() {
|
||||
return true;
|
||||
@@ -534,11 +530,11 @@ public class UndoManagerImpl extends UndoManager {
|
||||
}
|
||||
|
||||
private @NotNull List<UndoProvider> getUndoProviders() {
|
||||
return ProgressManager.getInstance().computeInNonCancelableSection(() -> {
|
||||
return myProject == null
|
||||
? UndoProvider.EP_NAME.getExtensionList()
|
||||
: UndoProvider.PROJECT_EP_NAME.getExtensionList(myProject);
|
||||
});
|
||||
return ProgressManager.getInstance().computeInNonCancelableSection(
|
||||
() -> myProject == null
|
||||
? UndoProvider.EP_NAME.getExtensionList()
|
||||
: UndoProvider.PROJECT_EP_NAME.getExtensionList(myProject)
|
||||
);
|
||||
}
|
||||
|
||||
private @NotNull ComponentManager getComponentManager() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
final class UndoSharedState {
|
||||
@@ -17,7 +18,7 @@ final class UndoSharedState {
|
||||
private final @NotNull SharedUndoRedoStacksHolder undoStacks;
|
||||
private final @NotNull SharedUndoRedoStacksHolder redoStacks;
|
||||
|
||||
UndoSharedState(boolean isPerClientSupported) {
|
||||
UndoSharedState(@NotNull Supplier<Boolean> isPerClientSupported) {
|
||||
adjustableActions = new SharedAdjustableUndoableActionsHolder();
|
||||
undoStacks = new SharedUndoRedoStacksHolder(adjustableActions, isPerClientSupported, true);
|
||||
redoStacks = new SharedUndoRedoStacksHolder(adjustableActions, isPerClientSupported, false);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.command.impl
|
||||
|
||||
import com.intellij.ide.impl.UndoRemoteBehaviorService
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.undo.UndoableAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@@ -13,33 +17,28 @@ import org.jetbrains.annotations.ApiStatus
|
||||
@ApiStatus.Internal
|
||||
interface UndoSpy {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(): UndoSpy? {
|
||||
return ProgressManager.getInstance().computeInNonCancelableSection<UndoSpy?, Exception> {
|
||||
if (UndoRemoteBehaviorService.isSpeculativeUndoEnabled()) {
|
||||
val application = ApplicationManager.getApplication()
|
||||
application?.service<UndoSpy>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun commandStarted(cmdEvent: CmdEvent)
|
||||
|
||||
fun undoableActionAdded(
|
||||
project: Project?,
|
||||
action: UndoableAction,
|
||||
type: UndoableActionType,
|
||||
)
|
||||
fun undoableActionAdded(undoProject: Project?, action: UndoableAction, type: UndoableActionType)
|
||||
|
||||
fun commandFinished(cmdEvent: CmdEvent)
|
||||
|
||||
fun undoRedoPerformed(
|
||||
project: Project?,
|
||||
editor: FileEditor?,
|
||||
isUndo: Boolean,
|
||||
)
|
||||
fun undoRedoPerformed(project: Project?, editor: FileEditor?, isUndo: Boolean)
|
||||
|
||||
// TODO: sync FE commands instead of flush
|
||||
fun commandMergerFlushed(project: Project?)
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val BLIND: UndoSpy = object : UndoSpy {
|
||||
override fun commandStarted(cmdEvent: CmdEvent) {}
|
||||
override fun undoableActionAdded(project: Project?, action: UndoableAction, type: UndoableActionType) {}
|
||||
override fun commandFinished(cmdEvent: CmdEvent) {}
|
||||
override fun undoRedoPerformed(project: Project?, editor: FileEditor?, isUndo: Boolean) {}
|
||||
override fun commandMergerFlushed(project: Project?) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.command.impl
|
||||
|
||||
import com.intellij.openapi.command.undo.UndoableAction
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@ApiStatus.Internal
|
||||
open class UndoSpyImpl : UndoSpy {
|
||||
|
||||
override fun commandStarted(cmdEvent: CmdEvent) {
|
||||
}
|
||||
|
||||
override fun undoableActionAdded(undoProject: Project?, action: UndoableAction, type: UndoableActionType) {
|
||||
}
|
||||
|
||||
override fun commandFinished(cmdEvent: CmdEvent) {
|
||||
}
|
||||
|
||||
override fun undoRedoPerformed(project: Project?, editor: FileEditor?, isUndo: Boolean) {
|
||||
}
|
||||
|
||||
override fun commandMergerFlushed(project: Project?) {
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,9 @@
|
||||
<projectService serviceInterface="com.intellij.openapi.command.undo.UndoManager"
|
||||
serviceImplementation="com.intellij.openapi.command.impl.UndoManagerImpl" preload="notHeadless"/>
|
||||
|
||||
<applicationService serviceInterface="com.intellij.openapi.command.impl.UndoSpy"
|
||||
serviceImplementation="com.intellij.openapi.command.impl.UndoSpyImpl" preload="notHeadless"/>
|
||||
|
||||
<applicationService serviceInterface="com.intellij.ide.ui.LafManager"
|
||||
serviceImplementation="com.intellij.ide.ui.laf.LafManagerImpl"
|
||||
headlessImplementation="com.intellij.ide.ui.laf.HeadlessLafManagerImpl"/>
|
||||
|
||||
Reference in New Issue
Block a user