From 2bbd7156c361586dac4a3cbafe04479392cde1de Mon Sep 17 00:00:00 2001 From: Whisperity Date: Fri, 16 Jan 2026 15:24:08 +0100 Subject: [PATCH] feat(fancy-ctrl-z): Toggle between last two suspended jobs in every case Allows using the fancy-ctrl-z functionality of toggling between the last two suspended jobs (`%+` and `%-`, respectively) even if there are more than two jobs suspended. Made the whole implementation more robust, especially in case Ctrl-Z is pressed with an existing non-empty prompt buffer. Fixed a bug where jobs with multiple different working directories would cause the back-and-forth functionality to not work. --- plugins/fancy-ctrl-z/README.md | 40 +++++++++++++------ plugins/fancy-ctrl-z/fancy-ctrl-z.plugin.zsh | 42 +++++++++++++++----- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/plugins/fancy-ctrl-z/README.md b/plugins/fancy-ctrl-z/README.md index 388e402d0..2bd009ae2 100644 --- a/plugins/fancy-ctrl-z/README.md +++ b/plugins/fancy-ctrl-z/README.md @@ -1,8 +1,8 @@ # fancy-ctrl-z -Allows pressing Ctrl-Z again to switch back to the most recently backgrounded -job. If there are precisely two backgrounded jobs, Ctrl-Z will toggle between -them, bringing the other one back. +Allows pressing Ctrl-Z is an empty prompt to bring to the +foreground the only suspended job, or, if there are more than one such jobs, to +switch between the two most recently suspended ones. To use it, add `fancy-ctrl-z` to the `plugins` array in your `.zshrc` file: @@ -12,15 +12,29 @@ plugins=(... fancy-ctrl-z) ## Motivation -I frequently need to execute random commands in my shell. To achieve it I pause -Vim by pressing Ctrl-Z, type command and press fg to switch back to Vim. -The fg part really hurts me. I just wanted to hit Ctrl-Z once again to get back -to Vim. I could not find a solution, so I developed one on my own that -works wonderfully with ZSH. +I frequently need to execute random commands in my shell. +To achieve it, I often pause Vim by pressing Ctrl-Z, type +a command and then would use `fg`↵ Enter to switch back to Vim. +Having to type in the `fg` part really hurt me. +I just wanted to hit Ctrl-Z once again to get back to Vim. +I could not find a solution, so I developed one on my own that works wonderfully +with Zsh. -Source: http://sheerun.net/2014/03/21/how-to-boost-your-vim-productivity/ +Switching between the last two suspended jobs is motivated by both TV remotes +that had such feature, and tools like `cd -` and `git checkout -` that switch +between the current and the second most recent state (directory, branch, etc.). +Sometimes, you have your Vim where code is changed, and another longer-running +process (e.g., a `tail` on some logs, or a Python interpreter) where you want +to test or observe your changes. +There is no point in time where you would "have the editor open" and "have the +program open" together, and the workflow clearly mandates always switching +back and forth between the two. +That's why the original version of _fancy-ctrl-z_ was extended with this "even +fancier" behaviour, because the original version would've opened Vim back again +and again. -Credits: -- original idea by [@sheerun](https://github.com/sheerun) -- added to OMZ by [@mbologna](https://github.com/mbologna) -- two-job switching added by [@Whisperity](https://github.com/Whisperity) +## Credits + +- Original idea by [@sheerun](https://github.com/sheerun), http://sheerun.net/2014/03/21/how-to-boost-your-vim-productivity +- Added to OMZ by [@mbologna](https://github.com/mbologna) +- Two-job switching added by [@Whisperity](https://github.com/Whisperity) diff --git a/plugins/fancy-ctrl-z/fancy-ctrl-z.plugin.zsh b/plugins/fancy-ctrl-z/fancy-ctrl-z.plugin.zsh index c32512174..bc0a16a54 100644 --- a/plugins/fancy-ctrl-z/fancy-ctrl-z.plugin.zsh +++ b/plugins/fancy-ctrl-z/fancy-ctrl-z.plugin.zsh @@ -1,16 +1,38 @@ fancy-ctrl-z () { - if [[ $#BUFFER -eq 0 ]]; then - BUFFER="fg" + local -i hascmd=$(( ${#BUFFER} > 0 )) + local -a sjobs=("${(@f)$(jobs -s)}") + local -a opts=("${(@f)$(setopt)}") + local -i isverbose=$opts[(Ie)verbose] + # In Zsh, arrays are 1-indexed, and an empty array has size 1, not 0. + # So we must check the first element's length to see whether it describes a + # suspended job. + local -i nsjobs="${#${(@)sjobs}}" + local -i hassjob=$(( ${#sjobs[1]} > 0 )) + local -i hassjobs=$(( nsjobs >= 2 )) + # Whether the ^Z action will result in a side effect to the terminal. + local -i sideeffect=$(( hassjob || hassjobs || isverbose )) - IFS=$'\n' local num_jobs=($(jobs)) - if [[ "${#num_jobs[@]}" -eq 2 ]]; then - BUFFER="fg %-" - fi - - zle accept-line -w - else + if (( hascmd && sideeffect )); then + # Save the current command. + # It will be restored after the side-effect is over, e.g., if the + # foregrounded job is put to the background again. zle push-input -w - zle clear-screen -w + fi + + if (( hassjobs )); then + # Multiple suspended jobs: foreground the second-to-last suspended job. + BUFFER="fg %-" + zle accept-line -w + elif (( hassjob )); then + # Single suspended job: foreground it. + # "fg %-" would result in an error as the only suspended job is the + # last one, which is referenced by "fg" or "fg %+". + BUFFER="fg %+" + zle accept-line -w + elif (( isverbose )); then + # No suspended jobs, but verbose mode, let show the "fg: no current job". + BUFFER="fg" + zle accept-line -w fi }