diff --git a/notebooks/visualization/src/com/intellij/notebooks/visualization/NotebookIntervalPointerImpl.kt b/notebooks/visualization/src/com/intellij/notebooks/visualization/NotebookIntervalPointerImpl.kt index 0c7c60ff5860..e881f580067d 100644 --- a/notebooks/visualization/src/com/intellij/notebooks/visualization/NotebookIntervalPointerImpl.kt +++ b/notebooks/visualization/src/com/intellij/notebooks/visualization/NotebookIntervalPointerImpl.kt @@ -175,7 +175,6 @@ class NotebookIntervalPointerFactoryImpl( eventChanges: NotebookIntervalPointersEventChanges, shiftChanges: NotebookIntervalPointersEventChanges, ) { - CommandProcessor.getInstance().currentCommand registerUndoableAction(object : BasicUndoableAction(document) { override fun undo() {} diff --git a/platform/analysis-api/src/com/intellij/modcommand/ModCommandExecutor.java b/platform/analysis-api/src/com/intellij/modcommand/ModCommandExecutor.java index e516f1c3b117..588cd9a53c45 100644 --- a/platform/analysis-api/src/com/intellij/modcommand/ModCommandExecutor.java +++ b/platform/analysis-api/src/com/intellij/modcommand/ModCommandExecutor.java @@ -89,7 +89,7 @@ public interface ModCommandExecutor { title, true, context.project()); if (!command.isEmpty()) { CommandProcessor commandProcessor = CommandProcessor.getInstance(); - if (commandProcessor.getCurrentCommand() == null) { + if (!commandProcessor.isCommandInProgress()) { commandProcessor.executeCommand(context.project(), () -> getInstance().executeInteractively(context, command, editor), title, null); } else { diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java b/platform/analysis-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java index 21d60ee2d4a6..23c2709f91b5 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java @@ -106,7 +106,7 @@ public final class TemplateState extends TemplateStateBase implements Disposable myEditorDocumentListener = new DocumentListener() { @Override public void beforeDocumentChange(@NotNull DocumentEvent e) { - if (CommandProcessor.getInstance().getCurrentCommand() != null && !isUndoOrRedoInProgress()) { + if (CommandProcessor.getInstance().isCommandInProgress() && !isUndoOrRedoInProgress()) { myDocumentChanged = true; } } diff --git a/platform/core-api/api-dump-experimental.txt b/platform/core-api/api-dump-experimental.txt index 12efd996c851..6a21cd553da8 100644 --- a/platform/core-api/api-dump-experimental.txt +++ b/platform/core-api/api-dump-experimental.txt @@ -245,6 +245,7 @@ a:com.intellij.openapi.application.ReadAction a:com.intellij.openapi.command.CommandProcessor - *a:allowMergeGlobalCommands(java.lang.Runnable):V - *a:executeCommand(com.intellij.openapi.project.Project,java.lang.Runnable,java.lang.String,java.lang.Object,com.intellij.openapi.command.UndoConfirmationPolicy,Z,com.intellij.openapi.editor.Document):V +- *:isCommandInProgress():Z f:com.intellij.openapi.command.CoroutinesKt - *sf:execute(com.intellij.openapi.command.WriteCommandAction$Builder,kotlin.jvm.functions.Function0,kotlin.coroutines.Continuation):java.lang.Object - *sf:writeCommandAction(com.intellij.openapi.application.ReadAndWriteScope,com.intellij.openapi.project.Project,java.lang.String,kotlin.jvm.functions.Function0):com.intellij.openapi.application.ReadResult diff --git a/platform/core-api/src/com/intellij/openapi/command/CommandProcessor.java b/platform/core-api/src/com/intellij/openapi/command/CommandProcessor.java index e4257bb29a89..90f2be9e21e5 100644 --- a/platform/core-api/src/com/intellij/openapi/command/CommandProcessor.java +++ b/platform/core-api/src/com/intellij/openapi/command/CommandProcessor.java @@ -105,6 +105,11 @@ public abstract class CommandProcessor { @ApiStatus.Experimental public abstract void allowMergeGlobalCommands(@NotNull Runnable action); + @ApiStatus.Experimental + public boolean isCommandInProgress() { + return getCurrentCommand() != null; + } + /** * @deprecated use {@link CommandListener#TOPIC} */ diff --git a/platform/core-impl/api-dump-unreviewed.txt b/platform/core-impl/api-dump-unreviewed.txt index 50d4b52147f6..021cb530744c 100644 --- a/platform/core-impl/api-dump-unreviewed.txt +++ b/platform/core-impl/api-dump-unreviewed.txt @@ -1003,7 +1003,6 @@ com.intellij.openapi.command.CommandToken - a:getProject():com.intellij.openapi.project.Project c:com.intellij.openapi.command.impl.CoreCommandProcessor - com.intellij.openapi.command.CommandProcessorEx -- p:myCurrentCommand:com.intellij.openapi.command.impl.CoreCommandProcessor$CommandDescriptor - ():V - addAffectedDocuments(com.intellij.openapi.project.Project,com.intellij.openapi.editor.Document[]):V - addAffectedFiles(com.intellij.openapi.project.Project,com.intellij.openapi.vfs.VirtualFile[]):V diff --git a/platform/core-impl/exposed-private-api.txt b/platform/core-impl/exposed-private-api.txt index 76560694ac7d..0c6943e01c47 100644 --- a/platform/core-impl/exposed-private-api.txt +++ b/platform/core-impl/exposed-private-api.txt @@ -1,7 +1,6 @@ com/intellij/diagnostic/ActivityCategory com/intellij/lang/impl/PsiBuilderImpl$MyTreeStructure com/intellij/lang/impl/PsiBuilderImpl$StartMarker -com/intellij/openapi/command/impl/CoreCommandProcessor$CommandDescriptor com/intellij/openapi/extensions/impl/ExtensionsAreaImpl com/intellij/psi/impl/DiffLog com/intellij/psi/stubs/StubBuilderType diff --git a/platform/core-impl/src/com/intellij/openapi/command/impl/CommandDescriptor.java b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandDescriptor.java new file mode 100644 index 000000000000..f1e7fc848e0b --- /dev/null +++ b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandDescriptor.java @@ -0,0 +1,140 @@ +// 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.CommandEvent; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.CommandToken; +import com.intellij.openapi.command.UndoConfirmationPolicy; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.NlsContexts.Command; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + + +final class CommandDescriptor implements CommandToken { + private final @NotNull CommandIdentity identity; + private final @NotNull Runnable command; + private final @Nullable Project project; + private final @Nullable @Command String name; + private final @Nullable Object groupId; + private final @Nullable Document document; + private final @NotNull UndoConfirmationPolicy undoConfirmationPolicy; + private final boolean shouldRecordActionForActiveDocument; + + CommandDescriptor( + @NotNull Runnable command, + @Nullable Project project, + @Nullable @Command String name, + @Nullable Object groupId, + @NotNull UndoConfirmationPolicy undoConfirmationPolicy, + boolean shouldRecordActionForActiveDocument, + @Nullable Document document + ) { + this( + new CommandIdentity(), + command, + project, + name, + groupId, + undoConfirmationPolicy, + shouldRecordActionForActiveDocument, + document + ); + } + + private CommandDescriptor( + @NotNull CommandIdentity identity, + @NotNull Runnable command, + @Nullable Project project, + @Nullable @Command String name, + @Nullable Object groupId, + @NotNull UndoConfirmationPolicy undoConfirmationPolicy, + boolean shouldRecordActionForActiveDocument, + @Nullable Document document + ) { + this.identity = identity; + this.command = command; + this.project = project; + this.name = name; + this.groupId = groupId; + this.undoConfirmationPolicy = undoConfirmationPolicy; + this.shouldRecordActionForActiveDocument = shouldRecordActionForActiveDocument; + this.document = document; + } + + @NotNull CommandEvent toCommandEvent(@NotNull CommandProcessor processor) { + return new CommandEvent( + processor, + command, + name, + groupId, + project, + undoConfirmationPolicy, + shouldRecordActionForActiveDocument, + document + ); + } + + @NotNull CommandDescriptor withName(@Nullable @Command String name) { + return new CommandDescriptor( + identity, + command, + project, + name, + groupId, + undoConfirmationPolicy, + shouldRecordActionForActiveDocument, + document + ); + } + + @NotNull CommandDescriptor withGroupId(@Nullable Object groupId) { + return new CommandDescriptor( + identity, + command, + project, + name, + groupId, + undoConfirmationPolicy, + shouldRecordActionForActiveDocument, + document + ); + } + + @Override + public @Nullable Project getProject() { + return project; + } + + @NotNull Runnable getCommand() { + return command; + } + + @Nullable @Command String getName() { + return name; + } + + @Nullable Object getGroupId() { + return groupId; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof CommandDescriptor)) return false; + CommandDescriptor that = (CommandDescriptor)object; + return Objects.equals(identity, that.identity); + } + + @Override + public int hashCode() { + return Objects.hashCode(identity); + } + + @Override + public String toString() { + return "'" + name + "', group: '" + groupId + "'"; + } +} diff --git a/platform/core-impl/src/com/intellij/openapi/command/impl/CommandIdentity.java b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandIdentity.java new file mode 100644 index 000000000000..6f038fc1980c --- /dev/null +++ b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandIdentity.java @@ -0,0 +1,30 @@ +// 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 java.util.concurrent.atomic.AtomicInteger; + + +final class CommandIdentity { + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(0); + + private final int id = ID_GENERATOR.incrementAndGet(); + + @Override + public boolean equals(Object object) { + if (!(object instanceof CommandIdentity)) return false; + CommandIdentity identity = (CommandIdentity) object; + return id == identity.id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "CommandIdentity{" + + "id=" + id + + '}'; + } +} diff --git a/platform/core-impl/src/com/intellij/openapi/command/impl/CommandListeners.java b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandListeners.java new file mode 100644 index 000000000000..47e4db3ccd21 --- /dev/null +++ b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandListeners.java @@ -0,0 +1,90 @@ +// 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.CommandEvent; +import com.intellij.openapi.command.CommandListener; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + + +final class CommandListeners implements CommandListener { + private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + + void addCommandListener(@NotNull CommandListener listener) { + myListeners.add(listener); + } + + @Override + public void commandStarted(@NotNull CommandEvent event) { + for (CommandListener listener : myListeners) { + try { + listener.commandStarted(event); + } + catch (Throwable e) { + CoreCommandProcessor.LOG.error(e); + } + } + } + + @Override + public void beforeCommandFinished(@NotNull CommandEvent event) { + for (CommandListener listener : myListeners) { + try { + listener.beforeCommandFinished(event); + } + catch (Throwable e) { + CoreCommandProcessor.LOG.error(e); + } + } + } + + @Override + public void commandFinished(@NotNull CommandEvent event) { + for (CommandListener listener : myListeners) { + try { + listener.commandFinished(event); + } + catch (Throwable e) { + CoreCommandProcessor.LOG.error(e); + } + } + } + + @Override + public void undoTransparentActionStarted() { + for (CommandListener listener : myListeners) { + try { + listener.undoTransparentActionStarted(); + } + catch (Throwable e) { + CoreCommandProcessor.LOG.error(e); + } + } + } + + @Override + public void beforeUndoTransparentActionFinished() { + for (CommandListener listener : myListeners) { + try { + listener.beforeUndoTransparentActionFinished(); + } + catch (Throwable e) { + CoreCommandProcessor.LOG.error(e); + } + } + } + + @Override + public void undoTransparentActionFinished() { + for (CommandListener listener : myListeners) { + try { + listener.undoTransparentActionFinished(); + } + catch (Throwable e) { + CoreCommandProcessor.LOG.error(e); + } + } + } +} diff --git a/platform/core-impl/src/com/intellij/openapi/command/impl/CommandPublisher.java b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandPublisher.java new file mode 100644 index 000000000000..18ecdab06a62 --- /dev/null +++ b/platform/core-impl/src/com/intellij/openapi/command/impl/CommandPublisher.java @@ -0,0 +1,57 @@ +// 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.application.ApplicationManager; +import com.intellij.openapi.command.CommandEvent; +import com.intellij.openapi.command.CommandListener; +import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NotNull; + + +final class CommandPublisher implements CommandListener { + private final CommandListeners listeners; + private final CommandListener publisher; + + CommandPublisher() { + CommandListeners listeners = new CommandListeners(); + MessageBus messageBus = ApplicationManager.getApplication().getMessageBus(); + messageBus.simpleConnect().subscribe(TOPIC, listeners); + this.listeners = listeners; + this.publisher = messageBus.syncPublisher(TOPIC); + } + + void addCommandListener(@NotNull CommandListener listener) { + listeners.addCommandListener(listener); + } + + @Override + public void commandStarted(@NotNull CommandEvent event) { + publisher.commandStarted(event); + } + + @Override + public void beforeCommandFinished(@NotNull CommandEvent event) { + publisher.beforeCommandFinished(event); + } + + @Override + public void commandFinished(@NotNull CommandEvent event) { + publisher.commandFinished(event); + } + + @Override + public void undoTransparentActionStarted() { + publisher.undoTransparentActionStarted(); + } + + @Override + public void beforeUndoTransparentActionFinished() { + publisher.beforeUndoTransparentActionFinished(); + } + + @Override + public void undoTransparentActionFinished() { + publisher.undoTransparentActionFinished(); + } +} diff --git a/platform/core-impl/src/com/intellij/openapi/command/impl/CoreCommandProcessor.java b/platform/core-impl/src/com/intellij/openapi/command/impl/CoreCommandProcessor.java index d66dfd9ea060..482f22d6561c 100644 --- a/platform/core-impl/src/com/intellij/openapi/command/impl/CoreCommandProcessor.java +++ b/platform/core-impl/src/com/intellij/openapi/command/impl/CoreCommandProcessor.java @@ -15,220 +15,131 @@ import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.concurrency.ThreadingAssertions; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Stack; -import com.intellij.util.messages.MessageBus; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; public class CoreCommandProcessor extends CommandProcessorEx { + @ApiStatus.Internal protected static final Logger LOG = Logger.getInstance("#com.intellij.openapi.command.impl"); - @ApiStatus.Internal - public static final class CommandDescriptor implements CommandToken { - public final @NotNull Runnable myCommand; - public final Project myProject; - public @NlsContexts.Command String myName; - public Object myGroupId; - public final Document myDocument; - final @NotNull UndoConfirmationPolicy myUndoConfirmationPolicy; - final boolean myShouldRecordActionForActiveDocument; - - CommandDescriptor(@NotNull Runnable command, - Project project, - @NlsContexts.Command String name, - Object groupId, - @NotNull UndoConfirmationPolicy undoConfirmationPolicy, - boolean shouldRecordActionForActiveDocument, - Document document) { - myCommand = command; - myProject = project; - myName = name; - myGroupId = groupId; - myUndoConfirmationPolicy = undoConfirmationPolicy; - myShouldRecordActionForActiveDocument = shouldRecordActionForActiveDocument; - myDocument = document; - } - - @Override - public Project getProject() { - return myProject; - } - - @Override - public String toString() { - return "'" + myName + "', group: '" + myGroupId + "'"; - } - } - - protected CommandDescriptor myCurrentCommand; - // Stack is used instead of ConcurrentLinkedDeque because null values are not supported by ConcurrentLinkedDeque - private final Stack myInterruptedCommands = new Stack<>(); - private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); - private int myUndoTransparentCount; - private int myAllowMergeGlobalCommandsCount = 0; - - private final CommandListener eventPublisher; - - public CoreCommandProcessor() { - MessageBus messageBus = ApplicationManager.getApplication().getMessageBus(); - messageBus.simpleConnect().subscribe(CommandListener.TOPIC, new CommandListener() { - @Override - public void commandStarted(@NotNull CommandEvent event) { - for (CommandListener listener : myListeners) { - try { - listener.commandStarted(event); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - @Override - public void beforeCommandFinished(@NotNull CommandEvent event) { - for (CommandListener listener : myListeners) { - try { - listener.beforeCommandFinished(event); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - @Override - public void commandFinished(@NotNull CommandEvent event) { - for (CommandListener listener : myListeners) { - try { - listener.commandFinished(event); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - @Override - public void undoTransparentActionStarted() { - for (CommandListener listener : myListeners) { - try { - listener.undoTransparentActionStarted(); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - @Override - public void beforeUndoTransparentActionFinished() { - for (CommandListener listener : myListeners) { - try { - listener.beforeUndoTransparentActionFinished(); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - @Override - public void undoTransparentActionFinished() { - for (CommandListener listener : myListeners) { - try { - listener.undoTransparentActionFinished(); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - }); - - // will, command events occurred quite often, let's cache publisher - eventPublisher = messageBus.syncPublisher(CommandListener.TOPIC); - } + private final CommandPublisher eventPublisher = new CommandPublisher(); + private final Stack<@Nullable CommandDescriptor> interruptedCommands = new Stack<>(); + private @Nullable CommandDescriptor currentCommand; + private int undoTransparentCount; + private int allowMergeGlobalCommandsCount = 0; @Override - public void executeCommand(Project project, @NotNull Runnable runnable, String name, Object groupId) { + public void executeCommand( + @Nullable Project project, + @NotNull Runnable runnable, + @Nullable String name, + @Nullable Object groupId + ) { executeCommand(project, runnable, name, groupId, UndoConfirmationPolicy.DEFAULT); } @Override - public void executeCommand(Project project, @NotNull Runnable runnable, String name, Object groupId, Document document) { + public void executeCommand( + @Nullable Project project, + @NotNull Runnable runnable, + @Nullable String name, + @Nullable Object groupId, + @Nullable Document document + ) { executeCommand(project, runnable, name, groupId, UndoConfirmationPolicy.DEFAULT, document); } @Override - public void executeCommand(Project project, - final @NotNull Runnable command, - final String name, - final Object groupId, - @NotNull UndoConfirmationPolicy undoConfirmationPolicy) { + public void executeCommand( + @Nullable Project project, + @NotNull Runnable command, + @Nullable String name, + @Nullable Object groupId, + @NotNull UndoConfirmationPolicy undoConfirmationPolicy + ) { executeCommand(project, command, name, groupId, undoConfirmationPolicy, null); } @Override - public void executeCommand(Project project, - final @NotNull Runnable command, - final String name, - final Object groupId, - @NotNull UndoConfirmationPolicy undoConfirmationPolicy, - Document document) { + public void executeCommand( + @Nullable Project project, + @NotNull Runnable command, + @Nullable String name, + @Nullable Object groupId, + @NotNull UndoConfirmationPolicy undoConfirmationPolicy, + @Nullable Document document + ) { executeCommand(project, command, name, groupId, undoConfirmationPolicy, true, document); } @Override - public void executeCommand(@Nullable Project project, - @NotNull Runnable command, - @Nullable String name, - @Nullable Object groupId, - @NotNull UndoConfirmationPolicy undoConfirmationPolicy, - boolean shouldRecordCommandForActiveDocument) { + public void executeCommand( + @Nullable Project project, + @NotNull Runnable command, + @Nullable String name, + @Nullable Object groupId, + @NotNull UndoConfirmationPolicy undoConfirmationPolicy, + boolean shouldRecordCommandForActiveDocument + ) { executeCommand(project, command, name, groupId, undoConfirmationPolicy, shouldRecordCommandForActiveDocument, null); } @Override - public void executeCommand(@Nullable Project project, - @NotNull Runnable command, - @Nullable @NlsContexts.Command String name, - @Nullable Object groupId, - @NotNull UndoConfirmationPolicy undoConfirmationPolicy, - boolean shouldRecordCommandForActiveDocument, - @Nullable Document document) { + public void executeCommand( + @Nullable Project project, + @NotNull Runnable command, + @Nullable @NlsContexts.Command String name, + @Nullable Object groupId, + @NotNull UndoConfirmationPolicy undoConfirmationPolicy, + boolean shouldRecordCommandForActiveDocument, + @Nullable Document document + ) { Application application = ApplicationManager.getApplication(); application.assertIsDispatchThread(); if (LOG.isDebugEnabled()) { - String currentCommandName; - if (myCurrentCommand != null) currentCommandName = myCurrentCommand.myName; - else currentCommandName = ""; - LOG.debug("executeCommand: " + command + ", name = " + name + ", groupId = " + groupId + - ", in command = " + currentCommandName + - ", in transparent action = " + isUndoTransparentActionInProgress()); + CommandDescriptor currentCommand = this.currentCommand; + LOG.debug(String.format( + "executeCommand: %s, name = %s, groupId = %s, in command = %s, in transparent action = %s", + command, + name, + groupId, + currentCommand == null ? "" : currentCommand.getName(), + isUndoTransparentActionInProgress() + )); } if (project != null && project.isDisposed()) { - LOG.error("Project "+project+" already disposed"); + LOG.error("Project " + project + " already disposed"); return; } - if (myCurrentCommand != null) { - application.runWriteIntentReadAction(() -> { command.run(); return null; }); + if (currentCommand != null) { + application.runWriteIntentReadAction(() -> { + command.run(); + return null; + }); return; } - CommandDescriptor descriptor = new CommandDescriptor(command, project, name, groupId, undoConfirmationPolicy, - shouldRecordCommandForActiveDocument, document); - myCurrentCommand = descriptor; + + CommandDescriptor descriptor = new CommandDescriptor( + command, + project, + name, + groupId, + undoConfirmationPolicy, + shouldRecordCommandForActiveDocument, + document + ); + currentCommand = descriptor; application.runWriteIntentReadAction(() -> { Throwable throwable = null; try { - fireCommandStarted(); + fireCommandStarted(descriptor); command.run(); } catch (Throwable th) { @@ -248,152 +159,144 @@ public class CoreCommandProcessor extends CommandProcessorEx { } @Override - public @Nullable CommandToken startCommand(final @Nullable Project project, - final String name, - final @Nullable Object groupId, - final @NotNull UndoConfirmationPolicy undoConfirmationPolicy) { + public @Nullable CommandToken startCommand( + @Nullable Project project, + @Nullable String name, + @Nullable Object groupId, + @NotNull UndoConfirmationPolicy undoConfirmationPolicy + ) { ApplicationManager.getApplication().assertWriteIntentLockAcquired(); - if (project != null && project.isDisposed()) return null; + if (project != null && project.isDisposed()) { + return null; + } if (LOG.isDebugEnabled()) { LOG.debug("startCommand: name = " + name + ", groupId = " + groupId); } - if (myCurrentCommand != null) { + if (currentCommand != null) { return null; } - Document document = groupId instanceof Document - ? (Document)groupId - : groupId instanceof Ref && ((Ref)groupId).get() instanceof Document - ? (Document)((Ref)groupId).get() - : null; - myCurrentCommand = new CommandDescriptor(EmptyRunnable.INSTANCE, project, name, groupId, undoConfirmationPolicy, true, document); - fireCommandStarted(); - return myCurrentCommand; + CommandDescriptor descriptor = new CommandDescriptor( + EmptyRunnable.INSTANCE, + project, + name, + groupId, + undoConfirmationPolicy, + true, + getDocumentFromGroupId(groupId) + ); + currentCommand = descriptor; + fireCommandStarted(descriptor); + return descriptor; } @Override public void finishCommand(@NotNull CommandToken command, @Nullable Throwable throwable) { ApplicationManager.getApplication().assertWriteIntentLockAcquired(); - LOG.assertTrue(myCurrentCommand != null, "no current command in progress"); - fireCommandFinished(); - } - - private void fireCommandFinished() { - CommandDescriptor currentCommand = myCurrentCommand; - CommandEvent event = new CommandEvent(this, currentCommand.myCommand, - currentCommand.myName, - currentCommand.myGroupId, - currentCommand.myProject, - currentCommand.myUndoConfirmationPolicy, - currentCommand.myShouldRecordActionForActiveDocument, - currentCommand.myDocument); - CommandListener publisher = eventPublisher; - try { - publisher.beforeCommandFinished(event); - } - finally { - myCurrentCommand = null; - publisher.commandFinished(event); - } - - LOG.debug("finishCommand: name = " + event.getCommandName() + ", groupId = " + event.getCommandGroupId()); + CommandDescriptor currentCommand = this.currentCommand; + LOG.assertTrue(currentCommand != null, "no current command in progress"); + fireCommandFinished(currentCommand); } @Override public void enterModal() { ThreadingAssertions.assertEventDispatchThread(); - CommandDescriptor currentCommand = myCurrentCommand; - myInterruptedCommands.push(currentCommand); + CommandDescriptor currentCommand = this.currentCommand; + interruptedCommands.push(currentCommand); if (currentCommand != null) { - fireCommandFinished(); + fireCommandFinished(currentCommand); } } @Override public void leaveModal() { ThreadingAssertions.assertEventDispatchThread(); - LOG.assertTrue(myCurrentCommand == null, "Command must not run: " + myCurrentCommand); - - myCurrentCommand = myInterruptedCommands.pop(); - if (myCurrentCommand != null) { - fireCommandStarted(); + LOG.assertTrue(currentCommand == null, "Command must not run: " + currentCommand); + CommandDescriptor descriptor = interruptedCommands.pop(); + currentCommand = descriptor; + if (descriptor != null) { + fireCommandStarted(descriptor); } } @Override public void setCurrentCommandName(String name) { ThreadingAssertions.assertWriteIntentReadAccess(); - CommandDescriptor currentCommand = myCurrentCommand; + CommandDescriptor currentCommand = this.currentCommand; LOG.assertTrue(currentCommand != null); - currentCommand.myName = name; + this.currentCommand = currentCommand.withName(name); } @Override public void setCurrentCommandGroupId(Object groupId) { ThreadingAssertions.assertWriteIntentReadAccess(); - CommandDescriptor currentCommand = myCurrentCommand; + CommandDescriptor currentCommand = this.currentCommand; LOG.assertTrue(currentCommand != null); - currentCommand.myGroupId = groupId; + this.currentCommand = currentCommand.withGroupId(groupId); } @Override public @Nullable Runnable getCurrentCommand() { - CommandDescriptor currentCommand = myCurrentCommand; - return currentCommand != null ? currentCommand.myCommand : null; + CommandDescriptor currentCommand = this.currentCommand; + return currentCommand != null ? currentCommand.getCommand() : null; } @Override public @Nullable String getCurrentCommandName() { - CommandDescriptor currentCommand = myCurrentCommand; - if (currentCommand != null) return currentCommand.myName; - if (!myInterruptedCommands.isEmpty()) { - final CommandDescriptor command = myInterruptedCommands.peek(); - return command != null ? command.myName : null; + CommandDescriptor currentCommand = this.currentCommand; + if (currentCommand != null) { + return currentCommand.getName(); + } + if (!interruptedCommands.isEmpty()) { + CommandDescriptor command = interruptedCommands.peek(); + return command != null ? command.getName() : null; } return null; } @Override public @Nullable Object getCurrentCommandGroupId() { - CommandDescriptor currentCommand = myCurrentCommand; - if (currentCommand != null) return currentCommand.myGroupId; - if (!myInterruptedCommands.isEmpty()) { - final CommandDescriptor command = myInterruptedCommands.peek(); - return command != null ? command.myGroupId : null; + CommandDescriptor currentCommand = this.currentCommand; + if (currentCommand != null) { + return currentCommand.getGroupId(); + } + if (!interruptedCommands.isEmpty()) { + final CommandDescriptor command = interruptedCommands.peek(); + return command != null ? command.getGroupId() : null; } return null; } @Override public @Nullable Project getCurrentCommandProject() { - CommandDescriptor currentCommand = myCurrentCommand; - return currentCommand != null ? currentCommand.myProject : null; + CommandDescriptor currentCommand = this.currentCommand; + return currentCommand != null ? currentCommand.getProject() : null; } @Override public void addCommandListener(@NotNull CommandListener listener) { - myListeners.add(listener); + eventPublisher.addCommandListener(listener); } @Override public void runUndoTransparentAction(@NotNull Runnable action) { if (LOG.isDebugEnabled()) { - LOG.debug("runUndoTransparentAction: " + action + ", in command = " + (myCurrentCommand != null) + - ", in transparent action = " + isUndoTransparentActionInProgress()); + LOG.debug("runUndoTransparentAction: " + action + ", in command = " + (currentCommand != null) + + ", in transparent action = " + isUndoTransparentActionInProgress()); } - if (myUndoTransparentCount++ == 0) { + if (undoTransparentCount++ == 0) { eventPublisher.undoTransparentActionStarted(); } try { action.run(); } finally { - if (myUndoTransparentCount == 1) { + if (undoTransparentCount == 1) { eventPublisher.beforeUndoTransparentActionFinished(); } - if (--myUndoTransparentCount == 0) { + if (--undoTransparentCount == 0) { eventPublisher.undoTransparentActionFinished(); } } @@ -402,17 +305,17 @@ public class CoreCommandProcessor extends CommandProcessorEx { @Override public final AutoCloseable withUndoTransparentAction() { if (LOG.isDebugEnabled()) { - LOG.debug("withUndoTransparentAction in command = " + (myCurrentCommand != null) + + LOG.debug("withUndoTransparentAction in command = " + (currentCommand != null) + ", in transparent action = " + isUndoTransparentActionInProgress()); } - if (myUndoTransparentCount++ == 0) { + if (undoTransparentCount++ == 0) { eventPublisher.undoTransparentActionStarted(); } return () -> { - if (myUndoTransparentCount == 1) { + if (undoTransparentCount == 1) { eventPublisher.beforeUndoTransparentActionFinished(); } - if (--myUndoTransparentCount == 0) { + if (--undoTransparentCount == 0) { eventPublisher.undoTransparentActionFinished(); } }; @@ -420,7 +323,7 @@ public class CoreCommandProcessor extends CommandProcessorEx { @Override public boolean isUndoTransparentActionInProgress() { - return myUndoTransparentCount > 0; + return undoTransparentCount > 0; } @Override @@ -437,8 +340,8 @@ public class CoreCommandProcessor extends CommandProcessorEx { @ApiStatus.Internal @ApiStatus.Experimental - public Boolean isMergeGlobalCommandsAllowed() { - return myAllowMergeGlobalCommandsCount > 0; + public boolean isMergeGlobalCommandsAllowed() { + return allowMergeGlobalCommandsCount > 0; } @Override @@ -446,13 +349,13 @@ public class CoreCommandProcessor extends CommandProcessorEx { @ApiStatus.Experimental public AccessToken allowMergeGlobalCommands() { ThreadingAssertions.assertWriteIntentReadAccess(); - myAllowMergeGlobalCommandsCount++; + allowMergeGlobalCommandsCount++; return new AccessToken() { @Override public void finish() { ThreadingAssertions.assertWriteIntentReadAccess(); - myAllowMergeGlobalCommandsCount--; + allowMergeGlobalCommandsCount--; } }; } @@ -464,16 +367,38 @@ public class CoreCommandProcessor extends CommandProcessorEx { } } - private void fireCommandStarted() { - CommandDescriptor currentCommand = myCurrentCommand; - CommandEvent event = new CommandEvent(this, - currentCommand.myCommand, - currentCommand.myName, - currentCommand.myGroupId, - currentCommand.myProject, - currentCommand.myUndoConfirmationPolicy, - currentCommand.myShouldRecordActionForActiveDocument, - currentCommand.myDocument); + @ApiStatus.Internal + protected boolean isCommandTokenActive(@NotNull CommandToken command) { + return command.equals(currentCommand); + } + + private void fireCommandStarted(@NotNull CommandDescriptor command) { + CommandEvent event = command.toCommandEvent(this); eventPublisher.commandStarted(event); } + + private void fireCommandFinished(@NotNull CommandDescriptor command) { + CommandEvent event = command.toCommandEvent(this); + try { + eventPublisher.beforeCommandFinished(event); + } + finally { + this.currentCommand = null; + eventPublisher.commandFinished(event); + } + LOG.debug("finishCommand: name = " + event.getCommandName() + ", groupId = " + event.getCommandGroupId()); + } + + private static @Nullable Document getDocumentFromGroupId(@Nullable Object groupId) { + if (groupId instanceof Document) { + return (Document) groupId; + } + if (groupId instanceof Ref) { + Object value = ((Ref) groupId).get(); + if (value instanceof Document) { + return (Document) value; + } + } + return null; + } } diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index 5a040b486c6b..06ecd59a3012 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -936,7 +936,7 @@ public final class DocumentImpl extends UserDataHolderBase implements DocumentEx if (!myAssertThreading) return; CommandProcessor commandProcessor = CommandProcessor.getInstance(); if (!commandProcessor.isUndoTransparentActionInProgress() && - commandProcessor.getCurrentCommand() == null) { + !commandProcessor.isCommandInProgress()) { throw new IncorrectOperationException("Must not change document outside command or undo-transparent action. See com.intellij.openapi.command.WriteCommandAction or com.intellij.openapi.command.CommandProcessor"); } } diff --git a/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java b/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java index d753e5ef3cbf..d87de9d431fa 100644 --- a/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java +++ b/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java @@ -270,7 +270,7 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel { throw new IllegalStateException("Attempt to modify PSI for non-committed Document!"); } CommandProcessor commandProcessor = CommandProcessor.getInstance(); - if (physical && !commandProcessor.isUndoTransparentActionInProgress() && commandProcessor.getCurrentCommand() == null) { + if (physical && !commandProcessor.isUndoTransparentActionInProgress() && !commandProcessor.isCommandInProgress()) { throw new IncorrectOperationException("Must not change PSI outside command or undo-transparent action. See com.intellij.openapi.command.WriteCommandAction or com.intellij.openapi.command.CommandProcessor"); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/ActionTracker.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/ActionTracker.kt index 6d1c26696e22..4c710fb0708c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/ActionTracker.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/ActionTracker.kt @@ -61,7 +61,7 @@ internal class ActionTracker( } fun ignoreCurrentDocumentChange() { - if (CommandProcessor.getInstance().currentCommand == null) { + if (!CommandProcessor.getInstance().isCommandInProgress) { return } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java index 445ce50c5f14..569f49586272 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java @@ -676,7 +676,7 @@ public class DocumentationManager extends DockablePopupManager EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_ENTER) .execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext); - if (commandProcessor.getCurrentCommand() == null) { + if (!commandProcessor.isCommandInProgress()) { commandProcessor.executeCommand(project, command, WRAP_LINE_COMMAND_NAME, null); } else { diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandProcessorImpl.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandProcessorImpl.java index 15d29388a1d7..996171273a9e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandProcessorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/CommandProcessorImpl.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.Nullable; public final class CommandProcessorImpl extends CoreCommandProcessor implements Disposable { @Override public void finishCommand(final @NotNull CommandToken command, final @Nullable Throwable throwable) { - if (myCurrentCommand != command) return; + if (!isCommandTokenActive(command)) return; final boolean failed; try { if (throwable != null) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java index f81afded18aa..317aafe721b1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java @@ -1175,7 +1175,7 @@ public final class EditorUtil { * command (so that it becomes part of it), otherwise does nothing. */ public static void performBeforeCommandEnd(@NotNull Runnable task) { - if (CommandProcessor.getInstance().getCurrentCommand() == null) return; + if (!CommandProcessor.getInstance().isCommandInProgress()) return; MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(); connection.subscribe(CommandListener.TOPIC, new CommandListener() { @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java index 8901249d10c5..c873a31ad569 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java @@ -270,7 +270,7 @@ public final class ScrollingModelImpl implements ScrollingModelEx { if (!editor.getSettings().isAnimatedScrolling() || animationDisabled || RemoteDesktopService.isRemoteSession()) { useAnimation = false; } - else if (CommandProcessor.getInstance().getCurrentCommand() == null) { + else if (!CommandProcessor.getInstance().isCommandInProgress()) { useAnimation = editor.getComponent().isShowing(); } else { diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java index 4ad93803b16d..2c18f29fbcb1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java @@ -488,8 +488,8 @@ public class FileDocumentManagerImpl extends FileDocumentManagerBase implements if (lineSeparator == null) { lineSeparator = document.getUserData(LINE_SEPARATOR_KEY); if (lineSeparator == null) { - Runnable currentCommand = CommandProcessor.getInstance().getCurrentCommand(); - Project project = currentCommand == null ? null : CommandProcessor.getInstance().getCurrentCommandProject(); + CommandProcessor commandProcessor = CommandProcessor.getInstance(); + Project project = commandProcessor.isCommandInProgress() ? commandProcessor.getCurrentCommandProject() : null; if (project == null) { project = ProjectUtil.guessProjectForFile(virtualFile); } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/DocumentTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/DocumentTest.java index 66d2f12d332d..e5f598234ebb 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/DocumentTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/DocumentTest.java @@ -62,7 +62,7 @@ public class DocumentTest extends LightPlatformTestCase { public void testModificationInsideCommandAssertion() { CommandProcessor commandProcessor = CommandProcessor.getInstance(); assertTrue(!commandProcessor.isUndoTransparentActionInProgress() && - commandProcessor.getCurrentCommand() == null); + !commandProcessor.isCommandInProgress()); final Document doc = new DocumentImpl("xxx"); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt index a15f985ef73d..b244cb5133cc 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt @@ -354,7 +354,7 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project, if (project.isDisposed) return if (hasUndoInCommand) return if (undoManager.isUndoOrRedoInProgress) return - if (CommandProcessor.getInstance().currentCommand == null) return + if (!CommandProcessor.getInstance().isCommandInProgress) return hasUndoInCommand = true registerUndoAction(true) @@ -375,13 +375,13 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project, } override fun undoTransparentActionStarted() { - if (CommandProcessor.getInstance().currentCommand == null) { + if (!CommandProcessor.getInstance().isCommandInProgress) { hasUndoInCommand = false } } override fun undoTransparentActionFinished() { - if (CommandProcessor.getInstance().currentCommand == null) { + if (!CommandProcessor.getInstance().isCommandInProgress) { hasUndoInCommand = false } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.kt index d171de7851e8..9aa2aa494916 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.kt @@ -911,7 +911,7 @@ class LineStatusTrackerManager( override fun commandFinished(event: CommandEvent) { if (!partialChangeListsEnabled) return - if (CommandProcessor.getInstance().currentCommand == null && + if (!CommandProcessor.getInstance().isCommandInProgress && !filesWithDamagedInactiveRanges.isEmpty()) { showInactiveRangesDamagedNotification() }