惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
H
Help Net Security
F
Fortinet All Blogs
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
量子位
N
Netflix TechBlog - Medium
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Recorded Future
Recorded Future
博客园 - 三生石上(FineUI控件)
Vercel News
Vercel News
aimingoo的专栏
aimingoo的专栏
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
Scott Helme
Scott Helme
The Last Watchdog
The Last Watchdog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
AI
AI
WordPress大学
WordPress大学
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
U
Unit 42
V2EX - 技术
V2EX - 技术
MongoDB | Blog
MongoDB | Blog
Schneier on Security
Schneier on Security
博客园 - Franky
H
Heimdal Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
W
WeLiveSecurity
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
B
Blog RSS Feed
N
News | PayPal Newsroom
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
Hacker News - Newest:
Hacker News - Newest: "LLM"
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网

QuarticCat's Blog

No more OOM in C/C++/Rust builds How do I boost difftastic by 4x RVV may not be as good as you think One more nasty design of C++'s name lookup How to read C++ types Blogroll
Some useful Zsh key-bindings
QuarticCat · 2024-03-12 · via QuarticCat's Blog

Built-in

undo & redo

No need to explain. They work as their name suggest. Many Zsh users remain unaware of these two widgets since they are not bound in viins keymap (which is the default for most users). I bind them to [Ctrl+Z] & [Ctrl+Y], respectively.

magic-space

It behaves like normal space except that it can perform history expansion. That is to say, it turns !-1 to <last command> in your buffer. Usually bound to [Space].

push-line

Push the current buffer onto the buffer stack and clear the buffer. Next time the editor starts up, the buffer will be popped off the top of the buffer stack and loaded into the editing buffer.

It allows you to leave halfway to execute one other command and then resume. Usually bound to [Ctrl+Q].

push-line-or-edit

At the top-level (PS1) prompt, equivalent to push-line. At a secondary (PS2) prompt, move the entire current multiline construct into the editor buffer. The latter is equivalent to push-input followed by get-line.

A more powerful version of push-line. Usually bound to [Ctrl+Q].

If you want to learn more, there are a few good places to go:

Mine

Word movement

# Ref: https://github.com/marlonrichert/zsh-edit
qc-word-widgets() {
    if [[ $WIDGET == *-shellword ]] {
        local words=(${(Z:n:)BUFFER}) lwords=(${(Z:n:)LBUFFER})
        if [[ $WIDGET == *-backward-* ]] {
            local tail=$lwords[-1]
            local move=-${(N)LBUFFER%$tail*}
        } else {
            local head=${${words[$#lwords]#$lwords[-1]}:-$words[$#lwords+1]}
            local move=+${(N)RBUFFER#*$head}
        }
    } else {
        local subword='([[:WORD:]]##~*[^[:upper:]]*[[:upper:]]*~*[[:alnum:]]*[^[:alnum:]]*)'
        local word="(${subword}|[^[:WORD:][:space:]]##|[[:space:]]##)"
        if [[ $WIDGET == *-backward-* ]] {
            local move=-${(N)LBUFFER%%${~word}(?|)}
        } else {
            local move=+${(N)RBUFFER##(?|)${~word}}
        }
    }
    if [[ $WIDGET == *-kill-* ]] {
        (( MARK = CURSOR + move ))
        zle -f kill
        zle .kill-region
    } else {
        (( CURSOR += move ))
    }
}
for w in qc-{back,for}ward-{,kill-}{sub,shell}word; zle -N $w qc-word-widgets
bindkey '^[[1;5D' qc-backward-subword         # [Ctrl+Left]
bindkey '^[[1;5C' qc-forward-subword          # [Ctrl+Right]
bindkey '^[[1;3D' qc-backward-shellword       # [Alt+Left]
bindkey '^[[1;3C' qc-forward-shellword        # [Alt+Right]
bindkey '^H'      qc-backward-kill-subword    # [Ctrl+Backspace] (in Konsole)
bindkey '^W'      qc-backward-kill-subword    # [Ctrl+Backspace] (in VSCode)
bindkey '^[[3;5~' qc-forward-kill-subword     # [Ctrl+Delete]
bindkey '^[^?'    qc-backward-kill-shellword  # [Alt+Backspace]
bindkey '^[[3;3~' qc-forward-kill-shellword   # [Alt+Delete]

zsh-edit’s README explains the effect very well:

This is how built-in word widgets ({for,back}ward-word, {,backward-}-kill-word) move:

# Zsh with default WORDCHARS='*?_-.[]~=/&;!#$%^(){}<>' moves/deletes way too much:
#              >       >             >             >          >
% ENV_VAR=value command --option-flag camelCaseWord ~/dir/*.ext
# <             <       <             <             <

# Zsh with  WORDCHARS='' is bit better, but skips punctuation clusters & doesn't find subWords:
#    >   >     >         >      >    >               >     >
% ENV_VAR=value command --option-flag camelCaseWord ~/dir/*.ext
# <   <   <     <         <      <    <               <     <

This is how zsh-edit subword widgets move:

# Zsh Edit with WORDCHARS=''
#   >   >     >       >  >     >    >     >   >   >  >  >      >
% ENV_VAR=value command --option-flag camelCaseWord ~/dir/?*.ext
# <   <   <     <       < <      <    <    <   <    < <      <

# Zsh Edit with WORDCHARS='~*?'
#  > >   >  >   >
% cd ~/dir/?*.ext
# <  < <   <  <

This function also includes shell-word widgets to let you move with greater strides. They are more intuitive than Zsh’s select-word-style in some edge cases.

Accept line

Update 2024-03-12: I find that Roman Perepelitsa has made an excellent plugin called zsh-no-ps2 for this purpose. It’s more thoughtful and rigorous than my widget.

# [Enter] Insert `\n` when accept-line would result in a parse error or PS2
# Ref: https://github.com/romkatv/zsh4humans/blob/v5/fn/z4h-accept-line
qc-accept-line() {
    if [[ $(functions[_qc-test]=$BUFFER 2>&1) == '' ]] {
        zle .accept-line
    } else {
        LBUFFER+=$'\n'
    }
}
zle -N qc-accept-line
bindkey '^M' qc-accept-line

On occasion, we may unintentionally hit a key – like ' – before hitting enter. And that will result in a secondary prompt (PS2), which is poorly supported, and your first line will be recorded in history anyway.

In contrast, my widget gives you a newline instead in that scenario, so you can just delete back to correct your command. Moreover, my widget makes writing multi-line commands easier.

Trim paste

# Trim trailing whitespace from pasted text
# Ref: https://unix.stackexchange.com/questions/693118
qc-trim-paste() {
    zle .bracketed-paste
    LBUFFER=${LBUFFER%%[[:space:]]#}
}
zle -N bracketed-paste qc-trim-paste

Sometimes, we want to copy some command from the browser, say:

We triple-click it to select the whole line and then copy. However, triple-clicking also selects the newline character. This could be annoying when pasting to the terminal. Have a try!

Thankfully, we have bracketed-paste handling all pasted text. We can hook pasting by overwriting this widget. The same trick is used by bracketed-paste-magic and bracketed-paste-url-magic.

Rationalize dot

# Change `...` to `../..`
# Ref: https://grml.org/zsh/zsh-lovers.html#_completion
qc-rationalize-dot() {
    if [[ $LBUFFER == *.. ]] {
        LBUFFER+='/..'
    } else {
        LBUFFER+='.'
    }
}
zle -N qc-rationalize-dot
bindkey '.' qc-rationalize-dot
bindkey '^[.' self-insert-unmeta  # [Alt+.] insert dot

It’s a better alternative to traditional .../..../..... aliases. It’s more readable and supports unlimited levels.

Clear screen

# [Ctrl+L] Clear screen but keep scrollback
# Ref: https://superuser.com/questions/1389834
qc-clear-screen() {
    local prompt_height=$(echo -n ${(%%)PS1} | wc -l)
    local lines=$((LINES - prompt_height))
    printf "$terminfo[cud1]%.0s" {1..$lines}  # cursor down
    printf "$terminfo[cuu1]%.0s" {1..$lines}  # cursor up
    zle .reset-prompt
}
zle -N qc-clear-screen
bindkey '^L' qc-clear-screen

Zsh has a built-in clear-screen widget bound to [Ctrl+L] by default. But it also erases your terminal emulator’s whole history in addition to the screen. My widget can keep the scrollback.

And it’s more flexible. You can customize prompt position by adjusting the number of lines. For example, local lines=$((LINES/2)) moves the prompt to the middle.

Fuck

# [Esc Esc] Correct previous command
# Ref: https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/thefuck
qc-fuck() {
    local fuck=$(THEFUCK_REQUIRE_CONFIRMATION=false thefuck $(fc -ln -1) 2>/dev/null)
    if [[ $fuck != '' ]] {
        compadd -Q $fuck
    } else {
        compadd -x '%F{red}-- no fucks given --%f'
    }
}
zle -C qc-fuck complete-word qc-fuck
bindkey '\e\e' qc-fuck

It’s a powerful replacement of oh-my-zsh’s sudo plugin. It calls thefuck to correct previous command. There are many cases that sudo plugin cannot handle, for example:

echo 1 > /sys/devices/system/cpu/cpufreq/boost

You can’t simply prefix it with sudo as echo is not a program but a shell built-in command. Now, thefuck to the rescue. It can smartly prompt sudo sh -c "echo 1 > /sys/devices/system/cpu/cpufreq/boost".

It’s better than using fuck as well. Because you can edit the command before executing it.

More

Here are some key-bindings that aren’t up my street but are still useful.

fzf

fzf is a fuzzy finder. It can be integrated to many commands to provide a handy selection menu. fzf offers some completions and key-bindings under the shell folder. For Zsh, that includes:

  • [Ctrl+T] - Paste the selected file path(s) into the command line
  • [Alt+C] - cd into the selected directory
  • [Ctrl+R] - Paste the selected command from history into the command line

I also recommend you to take a look at fzf-tab, a fabulous completion plugin.

xplr

xplr is a TUI file explorer. Its document offers a bunch of hacks. I used to enjoy this key-binding:

# [Ctrl+N] Navigate by xplr
# This is not a widget since properly resetting prompt is hard
# See https://github.com/romkatv/powerlevel10k/issues/72
bindkey -s '^N' '^Q cd -- $(xplr --print-pwd-as-result) \n'

I’ve spent enough time on this blog. There are more awesome projects but I’m too lazy to introduce all of them. Some are listed below.