-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from adammillerio/pr5
keyslib: add tmux plugin, start README
- Loading branch information
Showing
8 changed files
with
304 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,78 @@ | ||
# keyslib | ||
a python library and cli for working with key bindings | ||
|
||
`keyslib` is a Python library for working with key sequences. It has a standard | ||
grammar for expressing them, as well as `pathlib`-like overloads of standard binary | ||
operators for composing key sequences in code: | ||
|
||
```python | ||
from keyslib import KeySequence | ||
|
||
TMUX_PREFIX = KeySequence("(ctrl)b") | ||
TMUX_CREATE_WINDOW = TMUX_PREFIX + "c" | ||
|
||
# (ctrl)b+c | ||
print(TMUX_CREATE_WINDOW) | ||
``` | ||
|
||
In addition to key parsing, `keyslib` also has formatters for other applications and their key sequence formats, as well as a `keys` CLI: | ||
```sh | ||
# For a tmux send-keys command: | ||
keys format tmux "(ctrl)b+c" | ||
C-b c | ||
|
||
# For a Visual Studio Code keybindings.json file: | ||
keys format vscode "(ctrl)b+c" | ||
ctrl+b c | ||
|
||
# For a call to the Hammerspoon mac automation framework: | ||
keys format hammerspoon "(ctrl)b+c" | ||
hs.eventtap.keyStroke({"ctrl"}, "b"); hs.eventtap.keyStroke({}, "c"); | ||
``` | ||
|
||
The `keys` CLI can also directly control applications using the `send` command: | ||
```sh | ||
# tmux send-keys 'C-b' 'c' | ||
keys send tmux "(ctrl)b+c" | ||
|
||
# echo '\x02c' | wezterm cli send-text --no-paste | ||
keys send wezterm "(ctrl)b+c" | ||
|
||
# hs -q -c 'hs.eventtap.keyStroke({"ctrl"}, "b"); hs.eventtap.keyStroke({}, "c");' | ||
keys send hammerspoon "(ctrl)b+c" | ||
``` | ||
|
||
Collections of key bindings can be stored in the form of "keys files", which are | ||
just [dotenv](https://saurabh-kumar.com/python-dotenv/#file-format) files: | ||
|
||
[`example/binds/tmux.env`](example/binds/tmux.env): | ||
```sh | ||
CREATE_WINDOW='(ctrl)b+c #window Create window' | ||
SPLIT_WINDOW_VERTICAL='(ctrl)b+% #pane Create vertical split' | ||
SPLIT_WINDOW_HORIZONTAL='(ctrl)b+<quote> #pane Create horizontal split' | ||
``` | ||
|
||
These files can be written by hand or generated via Python and the `keys build` | ||
command. See [`example/keys/tmux.py`](example/keys/tmux.py) for an example. | ||
|
||
Putting it all together, the [`tools/kcmp.sh`](tools/kcmp.sh) script provides | ||
an interactive key completion menu via the [fzf](https://github.com/junegunn/fzf) | ||
CLI: | ||
|
||
`tools/kcmp.sh tmux nvim`: | ||
![keyslib-kcmp.png](https://raw.githubusercontent.com/adammillerio/i/refs/heads/main/keyslib-kcmp.png) | ||
|
||
In this example, keys are loaded from `~/.config/keyslib/binds/nvim.env` and are | ||
sent via a `tmux send-keys` command. | ||
|
||
Additionally, a tmux plugin is available to bind `kcmp.sh` to a hotkey: | ||
![keyslib-tmux-htop.png](https://raw.githubusercontent.com/adammillerio/i/refs/heads/main/keyslib-tmux-htop.png) | ||
|
||
|
||
When the complete hotkey (`(ctrl)<space>` by default) is pressed, the plugin | ||
will use `tmux list-panes -F '#{pane_current_command}'` to determine the current | ||
running command. If there is a matching keys file at | ||
`~/.config/keyslib/binds/<cmd>.env`, it will be displayed in a tmux window via | ||
`kcmp.sh`. The selected key sequence is then sent to the active pane via tmux. | ||
This provides a context-specific "command palette" like experience for terminal | ||
applications. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
KEYS_COMPLETE='(ctrl)<space> #keys Show completion palette' | ||
RENAME_SESSION='(ctrl)b+$ #session Rename session' | ||
DETACH_SESSION='(ctrl)b+d #session Detach from session' | ||
SHOW_SESSION='(ctrl)b+s #session Show all sessions' | ||
SHOW_PREVIEW='(ctrl)b+w #session Show session and window preview' | ||
PREVIOUS_SESSION='(ctrl)b+<lparen> #session Previous session' | ||
NEXT_SESSION='(ctrl)b+<rparen> #session Next session' | ||
CREATE_WINDOW='(ctrl)b+c #window Create window' | ||
RENAME_WINDOW='(ctrl)b+, #window Rename current window' | ||
CLOSE_WINDOW='(ctrl)b+& #window Close current window' | ||
LIST_WINDOW='(ctrl)b+w #window List windows' | ||
PREVIOUS_WINDOW='(ctrl)b+p #window Previous window' | ||
NEXT_WINDOW='(ctrl)b+n #window Next window' | ||
TOGGLE_WINDOW='(ctrl)b+l #window Toggle last active window' | ||
CLOSE_PANE='(ctrl)b+x #pane Close current pane' | ||
TOGGLE_PANE='(ctrl)b+; #pane Toggle last active pane' | ||
MOVE_PANE_LEFT='(ctrl)b+{ #pane Move the current pane left' | ||
MOVE_PANE_RIGHT='(ctrl)b+} #pane Move the current pane right' | ||
TOGGLE_PANE_LAYOUT='(ctrl)b+<space> #pane Toggle between pane layouts' | ||
NEXT_PANE='(ctrl)b+o #pane Switch to next pane' | ||
SHOW_PANE_NUMBERS='(ctrl)b+q #pane Show pane numbers' | ||
TOGGLE_PANE_ZOOM='(ctrl)b+z #pane Toggle pane zoom' | ||
CONVERT_PANE_WINDOW='(ctrl)b+1 #pane Convert pane into a window' | ||
SPLIT_WINDOW_VERTICAL='(ctrl)b+% #pane Create vertical split' | ||
SPLIT_WINDOW_HORIZONTAL='(ctrl)b+<quote> #pane Create horizontal split' | ||
SHOW_COMMAND_PROMPT='(ctrl)b+: #misc Enter command mode' | ||
LIST_KEYS='(ctrl)b+? #misc List key bindings in tmux' | ||
ENTER_COPY='(ctrl)b+[ #misc Enter copy mode' | ||
ENTER_CLOCK_MODE='(ctrl)b+t #misc Display a large clock' | ||
SELECT_WINDOW_0='(ctrl)b+0 #window_select Select window 0' | ||
SELECT_WINDOW_1='(ctrl)b+1 #window_select Select window 1' | ||
SELECT_WINDOW_2='(ctrl)b+2 #window_select Select window 2' | ||
SELECT_WINDOW_3='(ctrl)b+3 #window_select Select window 3' | ||
SELECT_WINDOW_4='(ctrl)b+4 #window_select Select window 4' | ||
SELECT_WINDOW_5='(ctrl)b+5 #window_select Select window 5' | ||
SELECT_WINDOW_6='(ctrl)b+6 #window_select Select window 6' | ||
SELECT_WINDOW_7='(ctrl)b+7 #window_select Select window 7' | ||
SELECT_WINDOW_8='(ctrl)b+8 #window_select Select window 8' | ||
SELECT_WINDOW_9='(ctrl)b+9 #window_select Select window 9' | ||
SELECT_PANE_UP='(ctrl)b+<up> #pane_select Select pane above' | ||
SELECT_PANE_DOWN='(ctrl)b+<down> #pane_select Select pane below' | ||
SELECT_PANE_LEFT='(ctrl)b+<left> #pane_select Select pane to the left' | ||
SELECT_PANE_RIGHT='(ctrl)b+<right> #pane_select Select pane to the right' | ||
SELECT_PANE_0='(ctrl)b+q+0 #pane_select Select pane 0' | ||
SELECT_PANE_1='(ctrl)b+q+1 #pane_select Select pane 1' | ||
SELECT_PANE_2='(ctrl)b+q+2 #pane_select Select pane 2' | ||
SELECT_PANE_3='(ctrl)b+q+3 #pane_select Select pane 3' | ||
SELECT_PANE_4='(ctrl)b+q+4 #pane_select Select pane 4' | ||
SELECT_PANE_5='(ctrl)b+q+5 #pane_select Select pane 5' | ||
SELECT_PANE_6='(ctrl)b+q+6 #pane_select Select pane 6' | ||
SELECT_PANE_7='(ctrl)b+q+7 #pane_select Select pane 7' | ||
SELECT_PANE_8='(ctrl)b+q+8 #pane_select Select pane 8' | ||
SELECT_PANE_9='(ctrl)b+q+9 #pane_select Select pane 9' | ||
UPDATE_PLUGINS='(ctrl)b+U #tpm Update plugins' | ||
UNINSTALL_PLUGINS='(ctrl)b+(alt)u #tpm Uninstall plugins not in the plugin list' | ||
RELOAD_PLUGINS='(ctrl)b+I #tpm Install any new plugins and refresh tmux environment' | ||
SAVE_SESSION='(ctrl)b+(ctrl)s #resurrect Save current session' | ||
RESTORE_SESSION='(ctrl)b+(ctrl)r #resurrect Restore saved session' | ||
CLEAR_HISTORY='(ctrl)b+(alt)c #logging Clear the history for the current pane' | ||
TOGGLE_LOGGING='(ctrl)b+P #logging Toggle logging of the current pane to file' | ||
PRINT_SCREEN='(ctrl)b+(alt)p #logging Save visible text of the current pane to file' | ||
SAVE_HISTORY='(ctrl)b+(alt)P #logging Save entire history of the current pane to file' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
# ruff: noqa: F401, F811 | ||
|
||
from keys.htop import __name__ | ||
from keys.tmux import __name__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
#!/usr/bin/env python3 | ||
from typing import Dict | ||
|
||
from keyslib import KeySequence | ||
from keyslib.builder import bind_multi, bind | ||
|
||
APP = "tmux" | ||
|
||
PREFIX = KeySequence("(ctrl)b") | ||
|
||
bind(APP, "KEYS_COMPLETE", "(ctrl)<space> #keys Show completion palette") | ||
|
||
SESSION_BINDS: Dict[str, KeySequence] = { | ||
# Session management | ||
"RENAME_SESSION": PREFIX + "$ #session Rename session", | ||
"DETACH_SESSION": PREFIX + "d #session Detach from session", | ||
"SHOW_SESSION": PREFIX + "s #session Show all sessions", | ||
"SHOW_PREVIEW": PREFIX + "w #session Show session and window preview", | ||
"PREVIOUS_SESSION": PREFIX + "<lparen> #session Previous session", | ||
"NEXT_SESSION": PREFIX + "<rparen> #session Next session", | ||
} | ||
bind_multi(APP, SESSION_BINDS) | ||
|
||
WINDOW_BINDS: Dict[str, KeySequence] = { | ||
# https://tmuxcheatsheet.com | ||
# Window management | ||
"CREATE_WINDOW": PREFIX + "c #window Create window", | ||
"RENAME_WINDOW": PREFIX + ", #window Rename current window", | ||
"CLOSE_WINDOW": PREFIX + "& #window Close current window", | ||
"LIST_WINDOW": PREFIX + "w #window List windows", | ||
"PREVIOUS_WINDOW": PREFIX + "p #window Previous window", | ||
"NEXT_WINDOW": PREFIX + "n #window Next window", | ||
"TOGGLE_WINDOW": PREFIX + "l #window Toggle last active window", | ||
# TODO: Add some sort of ability to specify sequence literals ie | ||
# "COPY_CAPTURE_PANE": PREFIX + ":" + "capture-pane", | ||
} | ||
bind_multi(APP, WINDOW_BINDS) | ||
|
||
PANE_BINDS: Dict[str, KeySequence] = { | ||
# Pane management | ||
"CLOSE_PANE": PREFIX + "x #pane Close current pane", | ||
"TOGGLE_PANE": PREFIX + "; #pane Toggle last active pane", | ||
"MOVE_PANE_LEFT": PREFIX + "{ #pane Move the current pane left", | ||
"MOVE_PANE_RIGHT": PREFIX + "} #pane Move the current pane right", | ||
"TOGGLE_PANE_LAYOUT": PREFIX + "<space> #pane Toggle between pane layouts", | ||
"NEXT_PANE": PREFIX + "o #pane Switch to next pane", | ||
"SHOW_PANE_NUMBERS": PREFIX + "q #pane Show pane numbers", | ||
"TOGGLE_PANE_ZOOM": PREFIX + "z #pane Toggle pane zoom", | ||
"CONVERT_PANE_WINDOW": PREFIX + "1 #pane Convert pane into a window", | ||
"SPLIT_WINDOW_VERTICAL": PREFIX + "% #pane Create vertical split", | ||
"SPLIT_WINDOW_HORIZONTAL": PREFIX + "<quote> #pane Create horizontal split", | ||
# TODO: Implement handling of arrow keys | ||
# # Resize current pane height up | ||
# "RESIZE_PANE_UP": "(ctrl)<up>", | ||
# # Resize current pane height down | ||
# "RESIZE_PANE_DOWN": "(ctrl)<down>", | ||
# # Resize current pane left | ||
# "RESIZE_PANE_LEFT": "(ctrl)<left>", | ||
# # Resize current pane right | ||
# "RESIZE_PANE_RIGHT": "(ctrl)<right>", | ||
} | ||
bind_multi(APP, PANE_BINDS) | ||
|
||
MISC_BINDS: Dict[str, KeySequence] = { | ||
"SHOW_COMMAND_PROMPT": PREFIX + ": #misc Enter command mode", | ||
"LIST_KEYS": PREFIX + "? #misc List key bindings in tmux", | ||
"ENTER_COPY": PREFIX + "[ #misc Enter copy mode", | ||
"ENTER_CLOCK_MODE": PREFIX + "t #misc Display a large clock", | ||
# Enter copy mode and scroll one page up | ||
# TODO: Handle <pageup> unicode escape sequence | ||
# "ENTER_COPY_UP": PREFIX + "<pageup>", | ||
} | ||
bind_multi(APP, MISC_BINDS) | ||
|
||
|
||
# Select window <n> | ||
WINDOW_SELECT_BINDS: Dict[str, KeySequence] = { | ||
f"SELECT_WINDOW_{n}": PREFIX + f"{n} #window_select Select window {n}" | ||
for n in range(0, 10) | ||
} | ||
bind_multi(APP, WINDOW_SELECT_BINDS) | ||
|
||
# Select pane <n> | ||
PANE_SELECT_BINDS: Dict[str, KeySequence] = { | ||
"SELECT_PANE_UP": PREFIX + "<up> #pane_select Select pane above", | ||
"SELECT_PANE_DOWN": PREFIX + "<down> #pane_select Select pane below", | ||
"SELECT_PANE_LEFT": PREFIX + "<left> #pane_select Select pane to the left", | ||
"SELECT_PANE_RIGHT": PREFIX + "<right> #pane_select Select pane to the right", | ||
} | ||
PANE_SELECT_BINDS.update( | ||
{ | ||
f"SELECT_PANE_{n}": PREFIX + f"q+{n} #pane_select Select pane {n}" | ||
for n in range(0, 10) | ||
} | ||
) | ||
bind_multi(APP, PANE_SELECT_BINDS) | ||
|
||
# tmux plugin manager | ||
# https://github.com/tmux-plugins/tpm | ||
TPM_BINDS: Dict[str, KeySequence] = { | ||
"UPDATE_PLUGINS": PREFIX + "U #tpm Update plugins", | ||
"UNINSTALL_PLUGINS": PREFIX | ||
+ "(alt)u #tpm Uninstall plugins not in the plugin list", | ||
"RELOAD_PLUGINS": PREFIX | ||
+ "I #tpm Install any new plugins and refresh tmux environment", | ||
} | ||
bind_multi("tmux", TPM_BINDS) | ||
|
||
# tmux-resurrect plugin binds | ||
# https://github.com/tmux-plugins/tmux-resurrect | ||
RESURRECT_BINDS: Dict[str, KeySequence] = { | ||
"SAVE_SESSION": PREFIX + "(ctrl)s #resurrect Save current session", | ||
"RESTORE_SESSION": PREFIX + "(ctrl)r #resurrect Restore saved session", | ||
} | ||
bind_multi("tmux", RESURRECT_BINDS) | ||
|
||
# tmux-logging plugin binds | ||
# https://github.com/tmux-plugins/tmux-logging | ||
LOGGING_BINDS: Dict[str, KeySequence] = { | ||
"CLEAR_HISTORY": PREFIX + "(alt)c #logging Clear the history for the current pane", | ||
"TOGGLE_LOGGING": PREFIX | ||
+ "P #logging Toggle logging of the current pane to file", | ||
"PRINT_SCREEN": PREFIX | ||
+ "(alt)p #logging Save visible text of the current pane to file", | ||
"SAVE_HISTORY": PREFIX | ||
+ "(alt)P #logging Save entire history of the current pane to file", | ||
} | ||
bind_multi("tmux", LOGGING_BINDS) | ||
|
||
# TODO: Figure out what I want to do with these, since they are "contextual" | ||
# keybinds | ||
# COPY_MODE_BINDS = { | ||
# # Quit copy mode | ||
# "COPY_EXIT": "q", | ||
# # Copy: Go to top line | ||
# "COPY_GO_TOP": "g", | ||
# # Copy: Go to bottom line | ||
# "COPY_GO_BOTTOM": "G", | ||
# # Copy: Scroll up | ||
# "COPY_SCROLL_UP": "<up>", | ||
# # Copy: Scroll down | ||
# "COPY_SCROLL_DOWN": "<down>", | ||
# # Copy: Move cursor up | ||
# "COPY_MOVE_UP": "k", | ||
# # Copy: Move cursor down | ||
# "COPY_MOVE_DOWN": "j", | ||
# # Copy: Move cursor left | ||
# "COPY_MOVE_LEFT": "h", | ||
# # Copy: Move cursor right | ||
# "COPY_MOVE_RIGHT": "l", | ||
# } | ||
# | ||
# bind_multi("tmux", COPY_MODE_BINDS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters