From 169e9c87ff2322ddcd8795a34b3df6a151ee9489 Mon Sep 17 00:00:00 2001 From: Kevin W Matthews Date: Sun, 22 Feb 2026 15:07:56 -0500 Subject: [PATCH 1/2] fix(vi-mode): re-render cursor after exisiting visual mode (ohmyzsh#11705) Following the suggestion in #11705, this PR removes the wrapping of the visual-mode widget and instead uses the zli-line-pre-redraw hook ([special widget](https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Special-Widgets)) to check state and change the cursor accordingly. My understanding is that the [REGION_ACTIVE](https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#index-REGION_005fACTIVE) variable indicates which characters/regions are to be highlighted, and it has values of: - 0: no highlight - 1: character/region highlight active - 2: line highlight active and that for visual/visual-line modes, VI_KEYMAP must be updated by the plugin itself. fixes 11705 --- plugins/vi-mode/vi-mode.plugin.zsh | 34 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/plugins/vi-mode/vi-mode.plugin.zsh b/plugins/vi-mode/vi-mode.plugin.zsh index 85208cfc9..1d307be9e 100644 --- a/plugins/vi-mode/vi-mode.plugin.zsh +++ b/plugins/vi-mode/vi-mode.plugin.zsh @@ -31,24 +31,32 @@ function _vi-mode-set-cursor-shape-for-keymap() { # https://vt100.net/docs/vt510-rm/DECSCUSR local _shape=0 case "${1:-${VI_KEYMAP:-main}}" in - main) _shape=$VI_MODE_CURSOR_INSERT ;; # vi insert: line - viins) _shape=$VI_MODE_CURSOR_INSERT ;; # vi insert: line - isearch) _shape=$VI_MODE_CURSOR_INSERT ;; # inc search: line - command) _shape=$VI_MODE_CURSOR_INSERT ;; # read a command name - vicmd) _shape=$VI_MODE_CURSOR_NORMAL ;; # vi cmd: block - visual) _shape=$VI_MODE_CURSOR_VISUAL ;; # vi visual mode: block - viopp) _shape=$VI_MODE_CURSOR_OPPEND ;; # vi operation pending: blinking block - *) _shape=0 ;; + main) _shape=$VI_MODE_CURSOR_INSERT ;; # vi insert: line + viins) _shape=$VI_MODE_CURSOR_INSERT ;; # vi insert: line + isearch) _shape=$VI_MODE_CURSOR_INSERT ;; # inc search: line + command) _shape=$VI_MODE_CURSOR_INSERT ;; # read a command name + vicmd) _shape=$VI_MODE_CURSOR_NORMAL ;; # vi cmd: block + visual) _shape=$VI_MODE_CURSOR_VISUAL ;; # vi visual mode: block + visual-line) _shape=$VI_MODE_CURSOR_VISUAL ;; # vi visual line mode: block + viopp) _shape=$VI_MODE_CURSOR_OPPEND ;; # vi operation pending: blinking block + *) _shape=0 ;; esac printf $'\e[%d q' "${_shape}" } -function _visual-mode { - typeset -g VI_KEYMAP=visual - _vi-mode-set-cursor-shape-for-keymap "$VI_KEYMAP" - zle .visual-mode +function zle-line-pre-redraw() { + if [[ "$REGION_ACTIVE" -eq 0 && ("$VI_KEYMAP" == visual || "$VI_KEYMAP" == visual-line) ]]; then + typeset -g VI_KEYMAP=$KEYMAP + _vi-mode-set-cursor-shape-for-keymap "$VI_KEYMAP" + elif [[ "$REGION_ACTIVE" -eq 1 && "$VI_KEYMAP" != "visual" ]]; then + typeset -g VI_KEYMAP=visual + _vi-mode-set-cursor-shape-for-keymap "$VI_KEYMAP" + elif [[ "$REGION_ACTIVE" -eq 2 && "$VI_KEYMAP" != "visual-line" ]]; then + typeset -g VI_KEYMAP=visual-line + _vi-mode-set-cursor-shape-for-keymap "$VI_KEYMAP" + fi } -zle -N visual-mode _visual-mode +zle -N zle-line-pre-redraw function _vi-mode-should-reset-prompt() { # If $VI_MODE_RESET_PROMPT_ON_MODE_CHANGE is unset (default), dynamically From 287fb4fdc6d0c081ee917560fe4ee567971ac471 Mon Sep 17 00:00:00 2001 From: Kevin W Matthews Date: Sun, 22 Feb 2026 15:40:13 -0500 Subject: [PATCH 2/2] feat(vi-mode): add customizable prompts for visual and visual line modes If unset by the theme, these default to '<<<' similar to normal mode, but with white instead of red. --- plugins/vi-mode/vi-mode.plugin.zsh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/plugins/vi-mode/vi-mode.plugin.zsh b/plugins/vi-mode/vi-mode.plugin.zsh index 1d307be9e..950af6759 100644 --- a/plugins/vi-mode/vi-mode.plugin.zsh +++ b/plugins/vi-mode/vi-mode.plugin.zsh @@ -47,13 +47,16 @@ function _vi-mode-set-cursor-shape-for-keymap() { function zle-line-pre-redraw() { if [[ "$REGION_ACTIVE" -eq 0 && ("$VI_KEYMAP" == visual || "$VI_KEYMAP" == visual-line) ]]; then typeset -g VI_KEYMAP=$KEYMAP - _vi-mode-set-cursor-shape-for-keymap "$VI_KEYMAP" + zle reset-prompt + zle -R elif [[ "$REGION_ACTIVE" -eq 1 && "$VI_KEYMAP" != "visual" ]]; then typeset -g VI_KEYMAP=visual - _vi-mode-set-cursor-shape-for-keymap "$VI_KEYMAP" + zle reset-prompt + zle -R elif [[ "$REGION_ACTIVE" -eq 2 && "$VI_KEYMAP" != "visual-line" ]]; then typeset -g VI_KEYMAP=visual-line - _vi-mode-set-cursor-shape-for-keymap "$VI_KEYMAP" + zle reset-prompt + zle -R fi } zle -N zle-line-pre-redraw @@ -171,9 +174,16 @@ fi # if mode indicator wasn't setup by theme, define default, we'll leave INSERT_MODE_INDICATOR empty by default typeset -g MODE_INDICATOR=${MODE_INDICATOR:='%B%F{red}<%b<<%f'} +typeset -g VISUAL_MODE_INDICATOR=${VISUAL_MODE_INDICATOR:='%B%F{white}<%b<<%f'} +typeset -g VISUAL_LINE_MODE_INDICATOR=${VISUAL_LINE_MODE_INDICATOR:='%B%F{white}<%b<<%f'} function vi_mode_prompt_info() { - echo "${${VI_KEYMAP/vicmd/$MODE_INDICATOR}/(main|viins)/$INSERT_MODE_INDICATOR}" + case "$VI_KEYMAP" in + vicmd) echo "$MODE_INDICATOR" ;; + visual) echo "$VISUAL_MODE_INDICATOR" ;; + visual-line) echo "$VISUAL_LINE_MODE_INDICATOR" ;; + main|viins) echo "$INSERT_MODE_INDICATOR" ;; + esac } # define right prompt, if it wasn't defined by a theme