Use tmux to list keybindings (#6308)
* Use tmux to list keybindings * Annotate tmux keybindings * List unannotated bindings too and let the isolated server exit on its own list-keys -aN falls back to the raw command when a binding has no note, so user-added bindings in an existing tmux.conf still show up. Dropping exit-empty off means the throwaway server exits when the client disconnects instead of relying solely on the trap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
David Heinemeier Hansson
parent
0f2f3f11cb
commit
3754c2c031
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Display Tmux keybindings defined in your configuration using an interactive search menu.
|
||||
# omarchy:summary=Display annotated Tmux keybindings using an interactive search menu.
|
||||
# omarchy:args=[--print|-p] [--config <path>]
|
||||
|
||||
print_only=false
|
||||
@@ -34,197 +34,21 @@ if [[ ! -f $config_file ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
output_keybindings() {
|
||||
awk '
|
||||
function trim(value) {
|
||||
gsub(/^[ \t]+|[ \t]+$/, "", value)
|
||||
return value
|
||||
}
|
||||
|
||||
function key_part_text(part, is_modifier, part_count) {
|
||||
gsub(/^\\/, "", part)
|
||||
|
||||
if (is_modifier && part == "C") return "CTRL"
|
||||
if (is_modifier && part == "M") return "ALT"
|
||||
if (is_modifier && part == "S") return "SHIFT"
|
||||
if (part == "Space") return "SPACE"
|
||||
if (part == "BSpace") return "BACKSPACE"
|
||||
if (part == "BTab") return "SHIFT + TAB"
|
||||
if (part == "PPage") return "PAGE UP"
|
||||
if (part == "NPage") return "PAGE DOWN"
|
||||
if (part == "DC") return "DELETE"
|
||||
if (part == "IC") return "INSERT"
|
||||
if (part_count == 1 && part ~ /^[a-z]$/) return part
|
||||
|
||||
return toupper(part)
|
||||
}
|
||||
|
||||
function key_text(key, parts, count, i, part, text) {
|
||||
gsub(/\\/, "", key)
|
||||
count = split(key, parts, "-")
|
||||
text = ""
|
||||
|
||||
for (i = 1; i <= count; i++) {
|
||||
part = key_part_text(parts[i], i < count, count)
|
||||
text = text (text == "" ? "" : " + ") part
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
function table_text(table) {
|
||||
if (table == "copy-mode-vi") return "COPY MODE"
|
||||
if (table == "copy-mode") return "COPY MODE"
|
||||
if (table == "prefix") return "PREFIX"
|
||||
|
||||
gsub(/-/, " ", table)
|
||||
return toupper(table)
|
||||
}
|
||||
|
||||
function pretty_command(command) {
|
||||
gsub(/\\;/, ";", command)
|
||||
gsub(/[{}]/, "", command)
|
||||
gsub(/^[ \t]+|[ \t]+$/, "", command)
|
||||
gsub(/[ \t]+/, " ", command)
|
||||
return command
|
||||
}
|
||||
|
||||
function command_action(command, normalized, target) {
|
||||
normalized = pretty_command(command)
|
||||
|
||||
if (normalized ~ /omarchy-menu-tmux-keybindings/) return "Show Tmux keybindings"
|
||||
if (normalized ~ /^send(-keys)? -X begin-selection/) return "Begin selection"
|
||||
if (normalized ~ /^send(-keys)? -X copy-selection-and-cancel/) return "Copy selection"
|
||||
if (normalized ~ /^send-prefix/) return "Send prefix"
|
||||
if (normalized ~ /^source-file/) return "Reload config"
|
||||
if (normalized ~ /^split-window -v/) return "Split pane vertically"
|
||||
if (normalized ~ /^split-window -h/) return "Split pane horizontally"
|
||||
if (normalized ~ /^kill-pane/) return "Kill pane"
|
||||
if (normalized ~ /^select-pane -L/) return "Focus pane left"
|
||||
if (normalized ~ /^select-pane -R/) return "Focus pane right"
|
||||
if (normalized ~ /^select-pane -U/) return "Focus pane up"
|
||||
if (normalized ~ /^select-pane -D/) return "Focus pane down"
|
||||
if (normalized ~ /^resize-pane -L/) return "Resize pane left"
|
||||
if (normalized ~ /^resize-pane -R/) return "Resize pane right"
|
||||
if (normalized ~ /^resize-pane -U/) return "Resize pane up"
|
||||
if (normalized ~ /^resize-pane -D/) return "Resize pane down"
|
||||
if (normalized ~ /^swap-pane -t "?left-of"?/) return "Move pane left"
|
||||
if (normalized ~ /^swap-pane -t "?right-of"?/) return "Move pane right"
|
||||
if (normalized ~ /^swap-pane -t "?up-of"?/) return "Move pane up"
|
||||
if (normalized ~ /^swap-pane -t "?down-of"?/) return "Move pane down"
|
||||
if (normalized ~ /^swap-pane -U/) return "Move pane left"
|
||||
if (normalized ~ /^swap-pane -D/) return "Move pane right"
|
||||
if (normalized ~ /rename-window/) return "Rename window"
|
||||
if (normalized ~ /^new-window/) return "New window"
|
||||
if (normalized ~ /^kill-window/) return "Kill window"
|
||||
|
||||
if (match(normalized, /^select-window -t ([^ ;]+)/, target)) {
|
||||
gsub(/^:=?/, "", target[1])
|
||||
|
||||
if (target[1] == "-1") return "Previous window"
|
||||
if (target[1] == "+1") return "Next window"
|
||||
|
||||
return "Switch to window " target[1]
|
||||
}
|
||||
|
||||
if (normalized ~ /^swap-window -t -1/) return "Move window left"
|
||||
if (normalized ~ /^swap-window -t \+1/) return "Move window right"
|
||||
if (normalized ~ /rename-session/) return "Rename session"
|
||||
if (normalized ~ /^new-session/) return "New session"
|
||||
if (normalized ~ /^kill-session/) return "Kill session"
|
||||
if (normalized ~ /^switch-client -p/) return "Previous session"
|
||||
if (normalized ~ /^switch-client -n/) return "Next session"
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
function add_record(combo, action) {
|
||||
records[++record_count] = sprintf("%-32s → %s", combo, action)
|
||||
}
|
||||
|
||||
function parse_bind(line, words, count, i, table, global, key, command, combo, action) {
|
||||
count = split(line, words, /[ \t]+/)
|
||||
i = 2
|
||||
table = "prefix"
|
||||
global = 0
|
||||
|
||||
while (i <= count && words[i] ~ /^-/) {
|
||||
if (words[i] == "-n") {
|
||||
global = 1
|
||||
i++
|
||||
} else if (words[i] == "-T") {
|
||||
table = words[i + 1]
|
||||
i += 2
|
||||
} else if (words[i] == "-r") {
|
||||
i++
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
key = words[i]
|
||||
i++
|
||||
|
||||
command = ""
|
||||
for (; i <= count; i++) {
|
||||
command = command (command == "" ? "" : " ") words[i]
|
||||
}
|
||||
|
||||
if (key == "" || command == "") return
|
||||
|
||||
if (global) {
|
||||
combo = key_text(key)
|
||||
} else if (table == "prefix") {
|
||||
combo = "PREFIX + " key_text(key)
|
||||
} else {
|
||||
combo = table_text(table) " + " key_text(key)
|
||||
}
|
||||
|
||||
action = command_action(command)
|
||||
add_record(combo, action)
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
prefix = "C-b"
|
||||
prefix2 = ""
|
||||
}
|
||||
|
||||
{
|
||||
line = trim($0)
|
||||
|
||||
if (line == "") next
|
||||
if (line ~ /^#/) next
|
||||
|
||||
if (line ~ /^(set|set-option|setw|set-window-option)[ \t]/) {
|
||||
word_count = split(line, words, /[ \t]+/)
|
||||
|
||||
for (i = 2; i <= word_count; i++) {
|
||||
if (words[i] == "prefix") prefix = words[i + 1]
|
||||
if (words[i] == "prefix2") prefix2 = words[i + 1]
|
||||
}
|
||||
|
||||
next
|
||||
}
|
||||
|
||||
if (line ~ /^(bind|bind-key)[ \t]/) parse_bind(line)
|
||||
}
|
||||
|
||||
END {
|
||||
prefix_description = key_text(prefix)
|
||||
|
||||
if (prefix2 != "" && prefix2 != "None") {
|
||||
prefix_description = prefix_description " / " key_text(prefix2)
|
||||
}
|
||||
|
||||
printf "%-32s → %s\n", "PREFIX", prefix_description
|
||||
|
||||
add_record("PREFIX + {", "Move pane left")
|
||||
add_record("PREFIX + }", "Move pane right")
|
||||
|
||||
for (i = 1; i <= record_count; i++) print records[i]
|
||||
}
|
||||
' "$config_file"
|
||||
}
|
||||
output_keybindings() (
|
||||
set -o pipefail
|
||||
socket_dir=$(mktemp -d)
|
||||
socket="$socket_dir/tmux"
|
||||
trap 'tmux -S "$socket" kill-server >/dev/null 2>&1 || true; rm -f "$socket"; rmdir "$socket_dir" 2>/dev/null || true' EXIT
|
||||
tmux -S "$socket" -f /dev/null start-server \; \
|
||||
unbind-key -a -T prefix \; \
|
||||
unbind-key -a -T root \; \
|
||||
unbind-key -a -T copy-mode \; \
|
||||
unbind-key -a -T copy-mode-vi \; \
|
||||
source-file "$config_file" \; \
|
||||
list-keys -aN -T prefix \; \
|
||||
list-keys -aN -T root -P "" \; \
|
||||
list-keys -aN -T copy-mode-vi -P "copy-mode-vi" | sed 's/^ //'
|
||||
)
|
||||
|
||||
if [[ $print_only == "true" ]]; then
|
||||
output_keybindings
|
||||
|
||||
+42
-42
@@ -1,65 +1,65 @@
|
||||
# Prefix
|
||||
set -g prefix C-Space
|
||||
set -g prefix2 C-b
|
||||
bind C-Space send-prefix
|
||||
bind -N "Send prefix" C-Space send-prefix
|
||||
|
||||
# Config and help
|
||||
bind q source-file ~/.config/tmux/tmux.conf \; display "Configuration reloaded"
|
||||
bind ? display-popup -E -w 80% -h 70% -T "Tmux keybindings" "omarchy-menu-tmux-keybindings --print | less -R"
|
||||
bind -N "Reload configuration" q source-file ~/.config/tmux/tmux.conf \; display "Configuration reloaded"
|
||||
bind -N "Show Tmux keybindings" ? display-popup -E -w 80% -h 70% -T "Tmux keybindings" "omarchy-menu-tmux-keybindings --print | less -R"
|
||||
|
||||
# Vi mode for copy
|
||||
setw -g mode-keys vi
|
||||
bind -T copy-mode-vi v send -X begin-selection
|
||||
bind -T copy-mode-vi y send -X copy-selection-and-cancel
|
||||
bind -N "Begin selection" -T copy-mode-vi v send -X begin-selection
|
||||
bind -N "Copy selection" -T copy-mode-vi y send -X copy-selection-and-cancel
|
||||
|
||||
# Pane Controls
|
||||
bind -n M-Enter split-window -v -c "#{pane_current_path}"
|
||||
bind -n M-S-Enter split-window -h -c "#{pane_current_path}"
|
||||
bind -n M-Escape kill-pane
|
||||
bind -N "Split pane vertically" -n M-Enter split-window -v -c "#{pane_current_path}"
|
||||
bind -N "Split pane horizontally" -n M-S-Enter split-window -h -c "#{pane_current_path}"
|
||||
bind -N "Kill pane" -n M-Escape kill-pane
|
||||
|
||||
bind h split-window -v -c "#{pane_current_path}"
|
||||
bind v split-window -h -c "#{pane_current_path}"
|
||||
bind x kill-pane
|
||||
bind -N "Split pane vertically" h split-window -v -c "#{pane_current_path}"
|
||||
bind -N "Split pane horizontally" v split-window -h -c "#{pane_current_path}"
|
||||
bind -N "Kill pane" x kill-pane
|
||||
|
||||
bind -n C-M-Left select-pane -L
|
||||
bind -n C-M-Right select-pane -R
|
||||
bind -n C-M-Up select-pane -U
|
||||
bind -n C-M-Down select-pane -D
|
||||
bind -N "Focus pane left" -n C-M-Left select-pane -L
|
||||
bind -N "Focus pane right" -n C-M-Right select-pane -R
|
||||
bind -N "Focus pane up" -n C-M-Up select-pane -U
|
||||
bind -N "Focus pane down" -n C-M-Down select-pane -D
|
||||
|
||||
bind -n C-M-S-Left resize-pane -L 5
|
||||
bind -n C-M-S-Down resize-pane -D 5
|
||||
bind -n C-M-S-Up resize-pane -U 5
|
||||
bind -n C-M-S-Right resize-pane -R 5
|
||||
bind -N "Resize pane left" -n C-M-S-Left resize-pane -L 5
|
||||
bind -N "Resize pane down" -n C-M-S-Down resize-pane -D 5
|
||||
bind -N "Resize pane up" -n C-M-S-Up resize-pane -U 5
|
||||
bind -N "Resize pane right" -n C-M-S-Right resize-pane -R 5
|
||||
|
||||
# Window navigation
|
||||
bind r command-prompt -I "#W" "rename-window -- '%%'"
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
bind k kill-window
|
||||
bind -N "Rename window" r command-prompt -I "#W" "rename-window -- '%%'"
|
||||
bind -N "Create window" c new-window -c "#{pane_current_path}"
|
||||
bind -N "Kill window" k kill-window
|
||||
|
||||
bind -n M-1 select-window -t 1
|
||||
bind -n M-2 select-window -t 2
|
||||
bind -n M-3 select-window -t 3
|
||||
bind -n M-4 select-window -t 4
|
||||
bind -n M-5 select-window -t 5
|
||||
bind -n M-6 select-window -t 6
|
||||
bind -n M-7 select-window -t 7
|
||||
bind -n M-8 select-window -t 8
|
||||
bind -n M-9 select-window -t 9
|
||||
bind -N "Switch to window 1" -n M-1 select-window -t 1
|
||||
bind -N "Switch to window 2" -n M-2 select-window -t 2
|
||||
bind -N "Switch to window 3" -n M-3 select-window -t 3
|
||||
bind -N "Switch to window 4" -n M-4 select-window -t 4
|
||||
bind -N "Switch to window 5" -n M-5 select-window -t 5
|
||||
bind -N "Switch to window 6" -n M-6 select-window -t 6
|
||||
bind -N "Switch to window 7" -n M-7 select-window -t 7
|
||||
bind -N "Switch to window 8" -n M-8 select-window -t 8
|
||||
bind -N "Switch to window 9" -n M-9 select-window -t 9
|
||||
|
||||
bind -n M-Left select-window -t -1
|
||||
bind -n M-Right select-window -t +1
|
||||
bind -n M-S-Left swap-window -t -1 \; select-window -t -1
|
||||
bind -n M-S-Right swap-window -t +1 \; select-window -t +1
|
||||
bind -N "Previous window" -n M-Left select-window -t -1
|
||||
bind -N "Next window" -n M-Right select-window -t +1
|
||||
bind -N "Move window left" -n M-S-Left swap-window -t -1 \; select-window -t -1
|
||||
bind -N "Move window right" -n M-S-Right swap-window -t +1 \; select-window -t +1
|
||||
|
||||
# Session controls
|
||||
bind R command-prompt -I "#S" "rename-session -- '%%'"
|
||||
bind C new-session -c "#{pane_current_path}"
|
||||
bind K kill-session
|
||||
bind P switch-client -p
|
||||
bind N switch-client -n
|
||||
bind -N "Rename session" R command-prompt -I "#S" "rename-session -- '%%'"
|
||||
bind -N "Create session" C new-session -c "#{pane_current_path}"
|
||||
bind -N "Kill session" K kill-session
|
||||
bind -N "Previous session" P switch-client -p
|
||||
bind -N "Next session" N switch-client -n
|
||||
|
||||
bind -n M-Up switch-client -p
|
||||
bind -n M-Down switch-client -n
|
||||
bind -N "Previous session" -n M-Up switch-client -p
|
||||
bind -N "Next session" -n M-Down switch-client -n
|
||||
|
||||
# General
|
||||
set -g default-terminal "tmux-256color"
|
||||
|
||||
Reference in New Issue
Block a user