diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroup.kt
index f744f75fa..36d79169a 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroup.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroup.kt
@@ -16,15 +16,11 @@ interface VimVisualMotionGroup {
   val selectionAdj: Int
 
   /**
-   * This function toggles visual mode according to the logic required for `v`, `V` and `<C-V>`
+   * Enters Visual mode, ensuring that the caret's selection start offset is correctly set
    *
-   * This is the implementation for `v`, `V` and `<C-V>`. If you need to enter Visual mode, use [enterVisualMode].
-   *
-   * * If visual mode is disabled, enable it
-   * * If visual mode is enabled, but [selectionType] differs, update visual according to new [selectionType]
-   * * If visual mode is enabled with the same [selectionType], disable it
+   * Use this to programmatically enter Visual mode. Note that it does not modify the editor's selection.
    */
-  fun toggleVisual(editor: VimEditor, count: Int, rawCount: Int, selectionType: SelectionType, returnTo: Mode? = null): Boolean
+  fun enterVisualMode(editor: VimEditor, selectionType: SelectionType): Boolean
 
   /**
    * Enter Select mode with the given selection type
@@ -39,13 +35,15 @@ interface VimVisualMotionGroup {
   fun enterSelectMode(editor: VimEditor, selectionType: SelectionType): Boolean
 
   /**
-   * Enters Visual mode, ensuring that the caret's selection start offset is correctly set
+   * This function toggles visual mode according to the logic required for `v`, `V` and `<C-V>`
    *
-   * Use this to programmatically enter Visual mode. Note that it does not modify the editor's selection.
+   * This is the implementation for `v`, `V` and `<C-V>`. If you need to enter Visual mode, use [enterVisualMode].
+   *
+   * * If visual mode is disabled, enable it
+   * * If visual mode is enabled, but [selectionType] differs, update visual according to new [selectionType]
+   * * If visual mode is enabled with the same [selectionType], disable it
    */
-  fun enterVisualMode(editor: VimEditor, selectionType: SelectionType): Boolean
-
-  fun detectSelectionType(editor: VimEditor): SelectionType
+  fun toggleVisual(editor: VimEditor, count: Int, rawCount: Int, selectionType: SelectionType, returnTo: Mode? = null): Boolean
 
   /**
    * When in Select mode, enter Visual mode for a single command
@@ -58,4 +56,11 @@ interface VimVisualMotionGroup {
    * See `:help v_CTRL-O`.
    */
   fun processSingleVisualCommand(editor: VimEditor)
+
+  /**
+   * Detect the current selection type based on the editor's current selection state
+   *
+   * If the IDE changes the selection, this function can be used to understand what the current selection type is.
+   */
+  fun detectSelectionType(editor: VimEditor): SelectionType
 }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt
index ace4f570b..572e92b2a 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt
@@ -26,6 +26,25 @@ abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
   override val selectionAdj: Int
     get() = if (exclusiveSelection) 0 else 1
 
+  /**
+   * Enters Visual mode, ensuring that the caret's selection start offset is correctly set
+   *
+   * Use this to programmatically enter Visual mode. Note that it does not modify the editor's selection.
+   */
+  override fun enterVisualMode(editor: VimEditor, selectionType: SelectionType): Boolean {
+    editor.mode = Mode.VISUAL(selectionType)
+
+    // vimLeadSelectionOffset requires read action
+    injector.application.runReadAction {
+      if (selectionType == SelectionType.BLOCK_WISE) {
+        editor.primaryCaret().run { vimSelectionStart = vimLeadSelectionOffset }
+      } else {
+        editor.nativeCarets().forEach { it.vimSelectionStart = it.vimLeadSelectionOffset }
+      }
+    }
+    return true
+  }
+
   override fun enterSelectMode(editor: VimEditor, selectionType: SelectionType): Boolean {
     // If we're already in Select or toggling from Visual, replace the current mode (keep the existing returnTo),
     // otherwise push Select, using the current mode as returnTo.
@@ -100,72 +119,6 @@ abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
     return true
   }
 
-  protected fun seemsLikeBlockMode(editor: VimEditor): Boolean {
-    val selections = editor.nativeCarets().map {
-      val adj = if (editor.offsetToBufferPosition(it.selectionEnd).column == 0) 1 else 0
-      it.selectionStart to (it.selectionEnd - adj).coerceAtLeast(0)
-    }.sortedBy { it.first }
-    val selectionStartColumn = editor.offsetToBufferPosition(selections.first().first).column
-    val selectionStartLine = editor.offsetToBufferPosition(selections.first().first).line
-
-    val maxColumn = selections.maxOfOrNull { editor.offsetToBufferPosition(it.second).column } ?: return false
-    selections.forEachIndexed { i, it ->
-      if (editor.offsetToBufferPosition(it.first).line != editor.offsetToBufferPosition(it.second).line) {
-        return false
-      }
-      if (editor.offsetToBufferPosition(it.first).column != selectionStartColumn) {
-        return false
-      }
-      val lineEnd =
-        editor.offsetToBufferPosition(editor.getLineEndForOffset(it.second)).column
-      if (editor.offsetToBufferPosition(it.second).column != maxColumn.coerceAtMost(lineEnd)) {
-        return false
-      }
-      if (editor.offsetToBufferPosition(it.first).line != selectionStartLine + i) {
-        return false
-      }
-    }
-    return true
-  }
-
-  override fun detectSelectionType(editor: VimEditor): SelectionType {
-    if (editor.carets().size > 1 && seemsLikeBlockMode(editor)) {
-      return SelectionType.BLOCK_WISE
-    }
-    val all = editor.nativeCarets().all { caret ->
-      // Detect if visual mode is character wise or line wise
-      val selectionStart = caret.selectionStart
-      val selectionEnd = caret.selectionEnd
-      val startLine = editor.offsetToBufferPosition(selectionStart).line
-      val endPosition = editor.offsetToBufferPosition(selectionEnd)
-      val endLine = if (endPosition.column == 0) (endPosition.line - 1).coerceAtLeast(0) else endPosition.line
-      val lineStartOfSelectionStart = editor.getLineStartOffset(startLine)
-      val lineEndOfSelectionEnd = editor.getLineEndOffset(endLine, true)
-      lineStartOfSelectionStart == selectionStart && (lineEndOfSelectionEnd + 1 == selectionEnd || lineEndOfSelectionEnd == selectionEnd)
-    }
-    if (all) return SelectionType.LINE_WISE
-    return SelectionType.CHARACTER_WISE
-  }
-
-  /**
-   * Enters Visual mode, ensuring that the caret's selection start offset is correctly set
-   *
-   * Use this to programmatically enter Visual mode. Note that it does not modify the editor's selection.
-   */
-  override fun enterVisualMode(editor: VimEditor, selectionType: SelectionType): Boolean {
-    editor.mode = Mode.VISUAL(selectionType)
-
-    // vimLeadSelectionOffset requires read action
-    injector.application.runReadAction {
-      if (selectionType == SelectionType.BLOCK_WISE) {
-        editor.primaryCaret().run { vimSelectionStart = vimLeadSelectionOffset }
-      } else {
-        editor.nativeCarets().forEach { it.vimSelectionStart = it.vimLeadSelectionOffset }
-      }
-    }
-    return true
-  }
-
   /**
    * When in Select mode, enter Visual mode for a single command
    *
@@ -196,4 +149,51 @@ abstract class VimVisualMotionGroupBase : VimVisualMotionGroup {
       SelectToggleVisualMode.toggleMode(editor)
     }
   }
+
+  override fun detectSelectionType(editor: VimEditor): SelectionType {
+    if (editor.carets().size > 1 && seemsLikeBlockMode(editor)) {
+      return SelectionType.BLOCK_WISE
+    }
+    val all = editor.nativeCarets().all { caret ->
+      // Detect if visual mode is character wise or line wise
+      val selectionStart = caret.selectionStart
+      val selectionEnd = caret.selectionEnd
+      val startLine = editor.offsetToBufferPosition(selectionStart).line
+      val endPosition = editor.offsetToBufferPosition(selectionEnd)
+      val endLine = if (endPosition.column == 0) (endPosition.line - 1).coerceAtLeast(0) else endPosition.line
+      val lineStartOfSelectionStart = editor.getLineStartOffset(startLine)
+      val lineEndOfSelectionEnd = editor.getLineEndOffset(endLine, true)
+      lineStartOfSelectionStart == selectionStart && (lineEndOfSelectionEnd + 1 == selectionEnd || lineEndOfSelectionEnd == selectionEnd)
+    }
+    if (all) return SelectionType.LINE_WISE
+    return SelectionType.CHARACTER_WISE
+  }
+
+  protected fun seemsLikeBlockMode(editor: VimEditor): Boolean {
+    val selections = editor.nativeCarets().map {
+      val adj = if (editor.offsetToBufferPosition(it.selectionEnd).column == 0) 1 else 0
+      it.selectionStart to (it.selectionEnd - adj).coerceAtLeast(0)
+    }.sortedBy { it.first }
+    val selectionStartColumn = editor.offsetToBufferPosition(selections.first().first).column
+    val selectionStartLine = editor.offsetToBufferPosition(selections.first().first).line
+
+    val maxColumn = selections.maxOfOrNull { editor.offsetToBufferPosition(it.second).column } ?: return false
+    selections.forEachIndexed { i, it ->
+      if (editor.offsetToBufferPosition(it.first).line != editor.offsetToBufferPosition(it.second).line) {
+        return false
+      }
+      if (editor.offsetToBufferPosition(it.first).column != selectionStartColumn) {
+        return false
+      }
+      val lineEnd =
+        editor.offsetToBufferPosition(editor.getLineEndForOffset(it.second)).column
+      if (editor.offsetToBufferPosition(it.second).column != maxColumn.coerceAtMost(lineEnd)) {
+        return false
+      }
+      if (editor.offsetToBufferPosition(it.first).line != selectionStartLine + i) {
+        return false
+      }
+    }
+    return true
+  }
 }