diff --git a/platform/platform-impl/internal/src/com/intellij/internal/statistic/collectors/fus/SpeculativeUndoStatCollector.kt b/platform/platform-impl/internal/src/com/intellij/internal/statistic/collectors/fus/SpeculativeUndoStatCollector.kt index 90d47905ea81..06d0366152a7 100644 --- a/platform/platform-impl/internal/src/com/intellij/internal/statistic/collectors/fus/SpeculativeUndoStatCollector.kt +++ b/platform/platform-impl/internal/src/com/intellij/internal/statistic/collectors/fus/SpeculativeUndoStatCollector.kt @@ -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) diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandSeparator.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandSeparator.java index b8685ba6c8c6..1dca0e5e8da5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandSeparator.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandSeparator.java @@ -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() { diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/SharedUndoRedoStacksHolder.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/SharedUndoRedoStacksHolder.java index 8a91e6e29516..d2e054ade011 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/SharedUndoRedoStacksHolder.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/SharedUndoRedoStacksHolder.java @@ -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 { private final SharedAdjustableUndoableActionsHolder myAdjustableUndoableActionsHolder; - private final boolean myIsPerClientSupported; + private final Supplier myIsPerClientSupported; - SharedUndoRedoStacksHolder(SharedAdjustableUndoableActionsHolder undoableActionsHolder, boolean isPerClientSupported, boolean isUndo) { + SharedUndoRedoStacksHolder( + @NotNull SharedAdjustableUndoableActionsHolder undoableActionsHolder, + @NotNull Supplier isPerClientSupported, + boolean isUndo + ) { super(isUndo); myAdjustableUndoableActionsHolder = undoableActionsHolder; myIsPerClientSupported = isPerClientSupported; @@ -60,7 +65,7 @@ final class SharedUndoRedoStacksHolder extends UndoRedoStacksHolderBase rangesToMove) { - if (!myIsPerClientSupported) { + if (!myIsPerClientSupported.get()) { return MovementAvailability.ALREADY_MOVED; } UndoRedoList stack = getStack(reference); diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoClientState.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoClientState.java index a620906e0ba1..9c6f5be6300a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoClientState.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoClientState.java @@ -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) { diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoManagerImpl.java index 441e0f71cb6e..3a917b573c91 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoManagerImpl.java @@ -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 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() { diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSharedState.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSharedState.java index 3ed6b85d704a..091744feb7d0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSharedState.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSharedState.java @@ -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 isPerClientSupported) { adjustableActions = new SharedAdjustableUndoableActionsHolder(); undoStacks = new SharedUndoRedoStacksHolder(adjustableActions, isPerClientSupported, true); redoStacks = new SharedUndoRedoStacksHolder(adjustableActions, isPerClientSupported, false); diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSpy.kt b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSpy.kt index 0941ae34dfd3..347e441a46f4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSpy.kt +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSpy.kt @@ -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 { + if (UndoRemoteBehaviorService.isSpeculativeUndoEnabled()) { + val application = ApplicationManager.getApplication() + application?.service() + } 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?) {} - } - } } diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSpyImpl.kt b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSpyImpl.kt new file mode 100644 index 000000000000..cd00082156fb --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoSpyImpl.kt @@ -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?) { + } +} diff --git a/platform/platform-resources/src/META-INF/PlatformLangComponents.xml b/platform/platform-resources/src/META-INF/PlatformLangComponents.xml index fc22dae73876..5cc48701c838 100644 --- a/platform/platform-resources/src/META-INF/PlatformLangComponents.xml +++ b/platform/platform-resources/src/META-INF/PlatformLangComponents.xml @@ -45,6 +45,9 @@ + +