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.
This commit is contained in:
Whisperity 2026-01-16 15:24:08 +01:00
commit 2bbd7156c3
2 changed files with 58 additions and 22 deletions

View file

@ -1,8 +1,8 @@
# fancy-ctrl-z # fancy-ctrl-z
Allows pressing Ctrl-Z again to switch back to the most recently backgrounded Allows pressing <kbd>Ctrl</kbd>-<kbd>Z</kbd> is an empty prompt to bring to the
job. If there are precisely two backgrounded jobs, Ctrl-Z will toggle between foreground the only suspended job, or, if there are more than one such jobs, to
them, bringing the other one back. switch between the two most recently suspended ones.
To use it, add `fancy-ctrl-z` to the `plugins` array in your `.zshrc` file: To use it, add `fancy-ctrl-z` to the `plugins` array in your `.zshrc` file:
@ -12,15 +12,29 @@ plugins=(... fancy-ctrl-z)
## Motivation ## Motivation
I frequently need to execute random commands in my shell. To achieve it I pause I frequently need to execute random commands in my shell.
Vim by pressing Ctrl-Z, type command and press fg<Enter> to switch back to Vim. To achieve it, I often pause Vim by pressing <kbd>Ctrl</kbd>-<kbd>Z</kbd>, type
The fg part really hurts me. I just wanted to hit Ctrl-Z once again to get back a command and then would use `fg`<kbd>↵ Enter</kbd> to switch back to Vim.
to Vim. I could not find a solution, so I developed one on my own that Having to type in the `fg` part really hurt me.
works wonderfully with ZSH. I just wanted to hit <kbd>Ctrl</kbd>-<kbd>Z</kbd> 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: ## Credits
- original idea by [@sheerun](https://github.com/sheerun)
- added to OMZ by [@mbologna](https://github.com/mbologna) - Original idea by [@sheerun](https://github.com/sheerun), http://sheerun.net/2014/03/21/how-to-boost-your-vim-productivity
- two-job switching added by [@Whisperity](https://github.com/Whisperity) - Added to OMZ by [@mbologna](https://github.com/mbologna)
- Two-job switching added by [@Whisperity](https://github.com/Whisperity)

View file

@ -1,16 +1,38 @@
fancy-ctrl-z () { fancy-ctrl-z () {
if [[ $#BUFFER -eq 0 ]]; then local -i hascmd=$(( ${#BUFFER} > 0 ))
BUFFER="fg" 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 (( hascmd && sideeffect )); then
if [[ "${#num_jobs[@]}" -eq 2 ]]; then # Save the current command.
BUFFER="fg %-" # 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
fi 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 zle accept-line -w
else
zle push-input -w
zle clear-screen -w
fi fi
} }