1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-05-09 09:34:06 +02:00

Remove the root write and read actions

Historically, IdeaVim was starting read or write action based on command type. However, this is incorrect because of several reasons:
- The command of different type may perform a write/read action
- The write lock with a lot of code inside does cause application freezes
- Since a large branching of code happens inside the write action, there is a chance of read action request, resulting in the deadlock

Also, now this prevents from implementing a proper execution of VIM-3376.
This commit is contained in:
Alex Plate 2025-02-21 18:44:51 +02:00
parent 0511ee277f
commit 437fa9a1a0
No known key found for this signature in database
GPG Key ID: 0B97153C8FFEC09F
15 changed files with 45 additions and 95 deletions
src
main/java/com/maddyhome/idea/vim
test/java/org/jetbrains/plugins/ideavim/group
testFixtures/kotlin/org/jetbrains/plugins/ideavim
vim-engine/src/main/kotlin/com/maddyhome/idea/vim

View File

@ -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)

View File

@ -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(

View File

@ -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)
}
}

View File

@ -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))
}

View File

@ -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()

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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))

View File

@ -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)
}

View File

@ -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() {

View File

@ -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")
}

View File

@ -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

View File

@ -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) }