diff --git a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
index 335c06947..f13128548 100644
--- a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
+++ b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
@@ -176,7 +176,9 @@ internal class VimSurroundExtension : VimExtension {
           val currentSurrounding = getCurrentSurrounding(it.caret, pick(charFrom))
           if (currentSurrounding != null) {
             it.caret.moveToOffset(currentSurrounding.startOffset)
-            editor.deleteString(currentSurrounding)
+            injector.application.runWriteAction {
+              editor.deleteString(currentSurrounding)
+            }
           }
 
           val registerValue = getRegisterForCaret(editor, context, REGISTER, it.caret)
diff --git a/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.kt b/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.kt
index 26380de23..1acf3da41 100644
--- a/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.kt
+++ b/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.kt
@@ -130,7 +130,9 @@ class ChangeGroup : VimChangeGroupBase() {
     val project = (editor as IjVimEditor).editor.project ?: return
     val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return
     val textRange = com.intellij.openapi.util.TextRange.create(start, end)
-    CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange))
+    injector.application.runWriteAction {
+      CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange))
+    }
   }
 
   override fun autoIndentRange(
diff --git a/src/main/java/com/maddyhome/idea/vim/helper/RunnableHelper.kt b/src/main/java/com/maddyhome/idea/vim/helper/RunnableHelper.kt
deleted file mode 100644
index 6299ff091..000000000
--- a/src/main/java/com/maddyhome/idea/vim/helper/RunnableHelper.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2003-2023 The IdeaVim authors
- *
- * Use of this source code is governed by an MIT-style
- * license that can be found in the LICENSE.txt file or at
- * https://opensource.org/licenses/MIT.
- */
-package com.maddyhome.idea.vim.helper
-
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.command.CommandProcessor
-import com.intellij.openapi.diagnostic.debug
-import com.intellij.openapi.diagnostic.logger
-import com.intellij.openapi.project.Project
-
-/**
- * This provides some helper methods to run code as a command and an application write action
- */
-internal object RunnableHelper {
-  private val logger = logger<RunnableHelper>()
-
-  @JvmStatic
-  fun runReadCommand(project: Project?, cmd: Runnable, name: String?, groupId: Any?) {
-    logger.debug { "Run read command: $name" }
-    CommandProcessor.getInstance()
-      .executeCommand(project, { ApplicationManager.getApplication().runReadAction(cmd) }, name, groupId)
-  }
-
-  @JvmStatic
-  fun runWriteCommand(project: Project?, cmd: Runnable, name: String?, groupId: Any?) {
-    logger.debug { "Run write command: $name" }
-    CommandProcessor.getInstance()
-      .executeCommand(project, { ApplicationManager.getApplication().runWriteAction(cmd) }, name, groupId)
-  }
-}
diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimApplication.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimApplication.kt
index aa2805311..7fed24cd4 100644
--- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimApplication.kt
+++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimApplication.kt
@@ -16,7 +16,6 @@ import com.intellij.util.ExceptionUtil
 import com.maddyhome.idea.vim.api.VimApplicationBase
 import com.maddyhome.idea.vim.api.VimEditor
 import com.maddyhome.idea.vim.diagnostic.vimLogger
-import com.maddyhome.idea.vim.helper.RunnableHelper
 import java.awt.Component
 import java.awt.Toolkit
 import java.awt.Window
@@ -58,14 +57,6 @@ internal class IjVimApplication : VimApplicationBase() {
     }
   }
 
-  override fun runWriteCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable) {
-    RunnableHelper.runWriteCommand((editor as IjVimEditor).editor.project, command, name, groupId)
-  }
-
-  override fun runReadCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable) {
-    RunnableHelper.runReadCommand((editor as IjVimEditor).editor.project, command, name, groupId)
-  }
-
   override fun <T> runWriteAction(action: () -> T): T {
     return ApplicationManager.getApplication().runWriteAction(Computable(action))
   }
diff --git a/src/test/java/org/jetbrains/plugins/ideavim/group/SearchGroupTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/group/SearchGroupTest.kt
index 81ea45e7c..7f3a788e1 100644
--- a/src/test/java/org/jetbrains/plugins/ideavim/group/SearchGroupTest.kt
+++ b/src/test/java/org/jetbrains/plugins/ideavim/group/SearchGroupTest.kt
@@ -14,7 +14,6 @@ import com.intellij.openapi.util.Ref
 import com.maddyhome.idea.vim.VimPlugin
 import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 import com.maddyhome.idea.vim.common.Direction
-import com.maddyhome.idea.vim.helper.RunnableHelper
 import com.maddyhome.idea.vim.newapi.vim
 import com.maddyhome.idea.vim.state.mode.Mode
 import com.maddyhome.idea.vim.state.mode.SelectionType
@@ -913,21 +912,13 @@ class SearchGroupTest : VimTestCase() {
   private fun search(pattern: String, input: String): Int {
     configureByText(input)
     val editor = fixture.editor
-    val project = fixture.project
     val searchGroup = VimPlugin.getSearch()
     val ref = Ref.create(-1)
     ApplicationManager.getApplication().invokeAndWait {
       ApplicationManager.getApplication().runWriteIntentReadAction<Any, Throwable> {
-        RunnableHelper.runReadCommand(
-          project,
-          {
-            // Does not move the caret!
-            val n = searchGroup.processSearchCommand(editor.vim, pattern, fixture.caretOffset, 1, Direction.FORWARDS)
-            ref.set(n?.first ?: -1)
-          },
-          null,
-          null,
-        )
+        // Does not move the caret!
+        val n = searchGroup.processSearchCommand(editor.vim, pattern, fixture.caretOffset, 1, Direction.FORWARDS)
+        ref.set(n?.first ?: -1)
       }
     }
     return ref.get()
diff --git a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt
index 1e157ba5a..448553b91 100644
--- a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt
+++ b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt
@@ -70,7 +70,6 @@ import com.maddyhome.idea.vim.group.IjOptions
 import com.maddyhome.idea.vim.group.visual.VimVisualTimer.swingTimer
 import com.maddyhome.idea.vim.handler.isOctopusEnabled
 import com.maddyhome.idea.vim.helper.EditorHelper
-import com.maddyhome.idea.vim.helper.RunnableHelper.runWriteCommand
 import com.maddyhome.idea.vim.helper.TestInputModel
 import com.maddyhome.idea.vim.helper.getGuiCursorMode
 import com.maddyhome.idea.vim.key.MappingOwner
@@ -1063,8 +1062,8 @@ abstract class VimTestCase {
       val keyHandler = KeyHandler.getInstance()
       val dataContext = injector.executionContextManager.getEditorExecutionContext(editor.vim)
       TestInputModel.getInstance(editor).setKeyStrokes(keys.filterNotNull())
-      runWriteCommand(
-        project,
+      injector.actionExecutor.executeCommand(
+        editor.vim,
         Runnable {
           val inputModel = TestInputModel.getInstance(editor)
           var key = inputModel.nextKeyStroke()
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt
index 1f1080f25..0c5f89b9a 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt
@@ -245,13 +245,7 @@ class KeyHandler {
       val action: Runnable = ActionRunner(editor, context, command, keyState, operatorArguments)
       val cmdAction = command.action
       val name = cmdAction.id
-      if (type.isWrite) {
-        injector.application.runWriteCommand(editor, name, action, action)
-      } else if (type.isRead) {
-        injector.application.runReadCommand(editor, name, action, action)
-      } else {
-        injector.actionExecutor.executeCommand(editor, action, name, action)
-      }
+      injector.actionExecutor.executeCommand(editor, action, name, action)
     }
   }
 
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCharacterAroundCursorAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCharacterAroundCursorAction.kt
index 439c41de8..212f364a5 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCharacterAroundCursorAction.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCharacterAroundCursorAction.kt
@@ -79,7 +79,9 @@ private fun insertCharacterAroundCursor(editor: VimEditor, caret: VimCaret, dir:
     val charsSequence = editor.text()
     if (offset < charsSequence.length) {
       val ch = charsSequence[offset]
-      (editor as MutableVimEditor).insertText(caret, caret.offset, ch.toString())
+      injector.application.runWriteAction {
+        (editor as MutableVimEditor).insertText(caret, caret.offset, ch.toString())
+      }
       caret.moveToMotion(injector.motion.getHorizontalMotion(editor, caret, 1, true))
       res = true
     }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimApplication.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimApplication.kt
index 4bf9f4881..147a919ee 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimApplication.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimApplication.kt
@@ -18,9 +18,6 @@ interface VimApplication {
   fun isInternal(): Boolean
   fun postKey(stroke: KeyStroke, editor: VimEditor)
 
-  fun runWriteCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable)
-  fun runReadCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable)
-
   fun <T> runWriteAction(action: () -> T): T
   fun <T> runReadAction(action: () -> T): T
 
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt
index 7a5836450..5781cfa97 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt
@@ -197,7 +197,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
         editor,
         LineDeleteShift.NL_ON_END
       ) ?: continue
-      editor.deleteString(TextRange(newRange.first, newRange.second))
+      injector.application.runWriteAction {
+        editor.deleteString(TextRange(newRange.first, newRange.second))
+      }
     }
     if (type != null) {
       val start = updatedRange.startOffset
@@ -215,7 +217,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
    * @param str    The text to insert
    */
   override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret {
-    (editor as MutableVimEditor).insertText(caret, offset, str)
+    injector.application.runWriteAction {
+      (editor as MutableVimEditor).insertText(caret, offset, str)
+    }
     val newCaret = caret.moveToInlayAwareOffset(offset + str.length)
 
     injector.markService.setMark(newCaret, MARK_CHANGE_POS, offset)
@@ -1003,7 +1007,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
     var processedCaret = editor.findLastVersionOfCaret(caret) ?: caret
     if (removeLastNewLine) {
       val textLength = editor.fileSize().toInt()
-      editor.deleteString(TextRange(textLength - 1, textLength))
+      injector.application.runWriteAction {
+        editor.deleteString(TextRange(textLength - 1, textLength))
+      }
       processedCaret = editor.findLastVersionOfCaret(caret) ?: caret
     }
 
@@ -1189,7 +1195,9 @@ abstract class VimChangeGroupBase : VimChangeGroup {
    * @param str    The new text
    */
   override fun replaceText(editor: VimEditor, caret: VimCaret, start: Int, end: Int, str: String) {
-    (editor as MutableVimEditor).replaceString(start, end, str)
+    injector.application.runWriteAction {
+      (editor as MutableVimEditor).replaceString(start, end, str)
+    }
 
     val newEnd = start + str.length
     injector.markService.setChangeMarks(caret, TextRange(start, newEnd))
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt
index e0da3b899..a0918f44d 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt
@@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.api
 import com.maddyhome.idea.vim.common.LiveRange
 import com.maddyhome.idea.vim.common.TextRange
 import com.maddyhome.idea.vim.common.VimEditorReplaceMask
+import com.maddyhome.idea.vim.helper.RWLockLabel
 import com.maddyhome.idea.vim.state.mode.Mode
 import com.maddyhome.idea.vim.state.mode.SelectionType
 
@@ -152,6 +153,7 @@ interface VimEditor {
   }
 
   fun getVirtualFile(): VirtualFile?
+  @RWLockLabel.Writable
   fun deleteString(range: TextRange)
 
   fun getLineText(line: Int): String {
@@ -241,7 +243,9 @@ interface VimEditor {
 
 interface MutableVimEditor : VimEditor {
   fun addLine(atPosition: Int): Int?
+  @RWLockLabel.Writable
   fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence)
+  @RWLockLabel.Writable
   fun replaceString(start: Int, end: Int, newString: String)
 }
 
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt
index 967d31307..b0b09237c 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt
@@ -903,7 +903,7 @@ abstract class VimSearchGroupBase : VimSearchGroup {
     }
 
     override fun executeInput(input: ReplaceConfirmationChoice, editor: VimEditor, context: ExecutionContext) {
-      injector.application.runWriteCommand(editor, "substitute-with-confirmation", modalInput) {
+      injector.actionExecutor.executeCommand(editor, {
         highlight.remove()
         injector.jumpService.saveJumpLocation(editor)
         val matchRange = nextSubstitute.first.range
@@ -931,7 +931,7 @@ abstract class VimSearchGroupBase : VimSearchGroup {
               options
             )
             closeModalInputPrompt()
-            return@runWriteCommand
+            return@executeCommand
           }
 
           ReplaceConfirmationChoice.QUIT -> {
@@ -965,7 +965,7 @@ abstract class VimSearchGroupBase : VimSearchGroup {
         column = replaceResult.column
 
         goToNextIteration()
-      }
+      }, "substitute-with-confirmation", modalInput)
     }
 
     private fun goToNextIteration() {
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimApplicationStub.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimApplicationStub.kt
index b7187637e..8f26d6db0 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimApplicationStub.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimApplicationStub.kt
@@ -42,14 +42,6 @@ class VimApplicationStub : VimApplicationBase() {
     TODO("Not yet implemented")
   }
 
-  override fun runWriteCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable) {
-    TODO("Not yet implemented")
-  }
-
-  override fun runReadCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable) {
-    TODO("Not yet implemented")
-  }
-
   override fun <T> runWriteAction(action: () -> T): T {
     TODO("Not yet implemented")
   }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/Command.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/Command.kt
index ca13d2539..db709c900 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/Command.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/Command.kt
@@ -96,12 +96,11 @@ data class Command(
     MODE_CHANGE,
     ;
 
-    val isRead: Boolean
-      get() = when (this) {
-        MOTION, COPY, OTHER_READONLY, MODE_CHANGE -> true
-        else -> false
-      }
-
+    /**
+     * Deprecated because not only this set of commands can be writable.
+     * A different way of detecting if a command is going to write something is needed.
+     */
+    @Deprecated("")
     val isWrite: Boolean
       get() = when (this) {
         INSERT, DELETE, CHANGE, PASTE, OTHER_WRITABLE -> true
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt
index f4daccdeb..d5777b219 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt
@@ -93,7 +93,9 @@ data class MoveTextCommand(val range: Range, val modifier: CommandModifier, val
     val dropNewLineInEnd = (line + linesMoved == editor.lineCount() - 1 && text.last() == '\n') ||
       (lineRange.endLine == editor.lineCount() - 1)
 
-    editor.deleteString(range)
+    injector.application.runWriteAction {
+      editor.deleteString(range)
+    }
     val putData = if (line == -1) {
       // Special case. Move text to below the line before the first line
       caret.moveToOffset(0)
@@ -113,7 +115,9 @@ data class MoveTextCommand(val range: Range, val modifier: CommandModifier, val
 
     if (dropNewLineInEnd) {
       assert(editor.text().last() == '\n')
-      editor.deleteString(TextRange(editor.text().length - 1, editor.text().length))
+      injector.application.runWriteAction {
+        editor.deleteString(TextRange(editor.text().length - 1, editor.text().length))
+      }
     }
 
     globalMarks.forEach { shiftGlobalMark(editor, it, shift) }