feat: add history expansions and interactive keybindings #1
@@ -136,6 +136,18 @@ Full tab completion for the `tailscale` CLI is provided via `conf.d/tailscale.fi
|
||||
|
||||
---
|
||||
|
||||
## Key Bindings
|
||||
|
||||
Beyond standard shell and FZF bindings, these custom interactive shortcuts are available:
|
||||
|
||||
| Binding | Action | Description |
|
||||
|---|---|---|
|
||||
| `Ctrl+G` | Previous Path Head | Behaves like `!$:h` in Bash. Inserts the directory part of the previous command's last argument. |
|
||||
| `Ctrl+F` | Interactive History Sub | Behaves like `!!:s/old/new/` in Bash. Performs substitution on the previous command using `old/new` syntax. |
|
||||
| `Ctrl+Alt+U` | Replace Command Token | Strips the first token (the command) from the current line. **If the line is empty**, it pulls the previous command and strips its first token, placing the cursor at the start for a quick replacement (e.g., changing `mkdir` to `cd` while keeping the paths). |
|
||||
|
||||
---
|
||||
|
||||
## Functions
|
||||
|
||||
### Modern CLI Replacements
|
||||
|
||||
+15
-2
@@ -1,7 +1,12 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
### Abreviations ###
|
||||
#
|
||||
# ╭──────────────────────────────────────────────────────────╮
|
||||
# │ Abreviations │
|
||||
# ╰──────────────────────────────────────────────────────────╯
|
||||
#
|
||||
# This file contains all the abbreviations for the terminal.
|
||||
# It is sourced by Fish on startup.
|
||||
|
||||
# Neovim
|
||||
abbr -a n nvim
|
||||
@@ -232,3 +237,11 @@ abbr -a scr 'systemctl restart'
|
||||
abbr -a ssct 'sudo systemctl status'
|
||||
abbr -a sscs 'sudo systemctl start'
|
||||
abbr -a sscr 'sudo systemctl restart'
|
||||
|
||||
### History Expansions and Substitutions ###
|
||||
abbr -a !^ --position anywhere --function expand_bang_caret
|
||||
abbr -a '!*' --position anywhere --function expand_bang_all
|
||||
abbr -a typo_sub --position anywhere --regex '\^([^^]+)\^([^^]*)' --function expand_typo_sub
|
||||
abbr -a bang_string --position anywhere --regex '![\w.-]+' --function expand_bang_string
|
||||
abbr -a bang_search --position anywhere --regex '!\?[\w.-]+\??' --function expand_bang_search
|
||||
abbr -a bang_minus_n --position anywhere --regex '!-(\d+)' --function expand_bang_minus_n
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# ╭──────────────────────────────────────────────────────────╮
|
||||
# │ Fish User Key Bindings │
|
||||
# ╰──────────────────────────────────────────────────────────╯
|
||||
#
|
||||
# This file defines custom key bindings for the Fish shell.
|
||||
# It is sourced by Fish on startup.
|
||||
|
||||
# ────────────────── Bind Prewious Path Head to Ctrl+G ─────────────────
|
||||
# Bindings to insert the previous path head into the command line
|
||||
# Behaves like `!$:h` does in bash
|
||||
#
|
||||
# Example: If the previous command was `cd /usr/local/bin`
|
||||
# pressing Ctrl+G will insert `/usr/local` into the command line.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ──────────── Bind Interactive History Substitution to Ctrl+F ─────────
|
||||
# Bindings to perform substitution on the previous command in the history
|
||||
# Behaves like `!!:s/old/new/` does in bash
|
||||
#
|
||||
# Example: If the previous command was `echo this is a test`
|
||||
# typing `this is/that was` and pressing Ctrl+F
|
||||
# will insert `echo that was a test` into the command line.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ────────── Bind Replace First Command Token to Ctrl+Alt+U ────────────
|
||||
# Strips the first command token and places cursor at start to retype it
|
||||
#
|
||||
# Example: If the current command text is `mkdir new_folder`
|
||||
# pressing Ctrl+Alt+U will change the command line to ` new_folder`
|
||||
# with the cursor at the start, allowing you to quickly change the command
|
||||
# while keeping the arguments intact.
|
||||
# To produce `cd new_folder` or `rm new_folder` etc.
|
||||
# If the current command text is empty, the previous command's first token will be used instead.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
function fish_user_key_bindings
|
||||
|
||||
# ───────────────────────────── Set Bindings ─────────────────────────────
|
||||
#
|
||||
# Set Emacs mode bindings:
|
||||
bind ctrl-g __insert_previous_path_head
|
||||
bind ctrl-f __interactive_history_sub
|
||||
bind ctrl-alt-u _replace_command_token
|
||||
|
||||
# Set bindings for all Vi modes:
|
||||
# 'default' is Vi-Command, 'insert' is Vi-Insert, 'visual' is Vi-Visual
|
||||
for mode in default insert visual
|
||||
bind --mode $mode ctrl-g __insert_previous_path_head
|
||||
bind --mode $mode ctrl-f __interactive_history_sub
|
||||
bind --mode $mode ctrl-alt-u _replace_command_token
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
function __insert_previous_path_head
|
||||
# Get the last command tokens
|
||||
set -l tokens (string split -n " " -- $history[1])
|
||||
|
||||
# If there are tokens, take the last one and strip the 'tail'
|
||||
if set -q tokens[-1]
|
||||
set -l path_head (dirname -- $tokens[-1])
|
||||
# Insert it into the current command line
|
||||
commandline -i -- $path_head
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
function __interactive_history_sub
|
||||
set -l current_line (commandline -b)
|
||||
set -l last_cmd $history[1]
|
||||
|
||||
if string match -qr '(.+)/(.+)' -- "$current_line"
|
||||
set -l parts (string split '/' -- "$current_line")
|
||||
set -l old $parts[1]
|
||||
set -l new $parts[2]
|
||||
set -l expanded (string replace -a -- "$old" "$new" "$last_cmd")
|
||||
commandline -r "$expanded"
|
||||
else
|
||||
if test -z "$current_line"
|
||||
commandline -r "sudo $last_cmd"
|
||||
end
|
||||
end
|
||||
commandline -f repaint
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
function __substitute_typo
|
||||
set -l cursor_pos (commandline -C)
|
||||
set -l cmd (commandline)
|
||||
|
||||
# Check if the current line matches the ^old^new pattern
|
||||
if string match -qr '\^([^^]+)\^([^^]*)' -- "$cmd"
|
||||
set -l last_cmd $history[1]
|
||||
set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$cmd")
|
||||
set -l old $captured[2]
|
||||
set -l new $captured[3]
|
||||
|
||||
if test -n "$old"
|
||||
set -l expanded (string replace -a -- "$old" "$new" "$last_cmd")
|
||||
commandline -r "$expanded"
|
||||
# No need to move cursor, it's a whole new line
|
||||
end
|
||||
else
|
||||
# If it's just a normal caret (not part of a pattern), just insert it
|
||||
commandline -i '^'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
function expand_bang_all
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"; set token (commandline -t); end
|
||||
|
||||
set -l tokens (string split -n " " -- $history[1])
|
||||
if test (count $tokens) -gt 1
|
||||
echo -- (string join " " -- $tokens[2..-1])
|
||||
else
|
||||
echo -- $token
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
function expand_bang_caret
|
||||
# Split the last history item into a list
|
||||
set -l tokens (string split -n ' ' -- $history[1])
|
||||
# tokens[1] is the command, tokens[2] is the first argument
|
||||
if set -q tokens[2]
|
||||
echo -- $tokens[2]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
function expand_bang_minus_n
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"; set token (commandline -t); end
|
||||
|
||||
# Extract the number from the regex match
|
||||
if string match -qr '!-(\d+)' -- "$token"
|
||||
set -l n (string match -r '!-(\d+)' -- "$token")[2]
|
||||
|
||||
if test (count $history) -ge $n
|
||||
echo -- $history[$n]
|
||||
else
|
||||
echo -- $token
|
||||
end
|
||||
else
|
||||
echo -- $token
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
function expand_bang_search
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
# Extract query: looks for text after !? and before an optional ?
|
||||
set -l query (string match -r '!\?([^?]+)' -- $token)[2]
|
||||
|
||||
if test -n "$query"
|
||||
# Search history for a match anywhere in the command
|
||||
set -l match (builtin history search --contains --max=1 -- $query)
|
||||
|
||||
if test -n "$match"
|
||||
echo -- $match
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
echo -- $token
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
function expand_bang_string
|
||||
# Fish 4.x passes the matched token as argv[1]
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
# Remove the '!' to get the search query
|
||||
set -l query (string sub -s 2 -- $token)
|
||||
|
||||
if test -n "$query"
|
||||
# Search history for a prefix match
|
||||
set -l match (builtin history search --prefix --max=1 -- $query)
|
||||
|
||||
if test -n "$match"
|
||||
echo -- $match
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# If no match or empty query, return the token so it doesn't vanish
|
||||
echo -- $token
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
function expand_typo_sub
|
||||
# In newer Fish, the matched token is often passed as $argv[1]
|
||||
# if the abbr is set up correctly. We'll fallback to commandline just in case.
|
||||
set -l last_cmd $history[1]
|
||||
set -l current_token $argv[1]
|
||||
if test -z "$current_token"
|
||||
set current_token (commandline -t)
|
||||
end
|
||||
|
||||
if string match -qr '\^([^^]+)\^([^^]*)' -- "$current_token"
|
||||
set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$current_token")
|
||||
set -l old $captured[2]
|
||||
set -l new $captured[3]
|
||||
|
||||
if test -n "$old"
|
||||
# Using -- to ensure strings starting with '-' aren't treated as flags
|
||||
echo -- (string replace -a -- "$old" "$new" "$last_cmd")
|
||||
end
|
||||
else
|
||||
# Return the token itself so it doesn't vanish
|
||||
echo -- "$current_token"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user