feat(shell): add bash-style history expansions and interactive keybindings

- Replicate bash bang-operations (!^, !*, !string, etc.) via abbreviations
- Add Ctrl+G for previous path head insertion
- Add Ctrl+F for interactive history substitution
- Add Ctrl+Alt+U to quickly replace command tokens
- Update README documentation for all new features
This commit is contained in:
2026-04-30 01:03:13 -04:00
parent 6722deea3c
commit ada58e8818
12 changed files with 234 additions and 2 deletions
@@ -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
+17
View File
@@ -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
+21
View File
@@ -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
+11
View File
@@ -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
+8
View File
@@ -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
+17
View File
@@ -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
+21
View File
@@ -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
+23
View File
@@ -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
+23
View File
@@ -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