mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 11:32:42 +00:00
chore(config): externalize Ghostty and Kitty configs, add vicinae-bin package and MIME handler
This commit is contained in:
1 parent
09de712152
commit
de55c943c4
27 files changed
+151
-2163
No files matched your search
@@ -1,108 +0,0 @@
|
||||
# Font Configuration
|
||||
font-size = 11
|
||||
font-family = VictorMono Nerd Font
|
||||
background-blur = true
|
||||
|
||||
# Window Configuration
|
||||
window-decoration = false
|
||||
window-padding-x = 0
|
||||
window-padding-y = 0
|
||||
background-opacity = 0.9
|
||||
|
||||
# Cursor Configuration
|
||||
cursor-style = underline
|
||||
cursor-style-blink = true
|
||||
|
||||
# Scrollback
|
||||
scrollback-limit = 3023
|
||||
|
||||
# Terminal features
|
||||
mouse-hide-while-typing = true
|
||||
copy-on-select = false
|
||||
confirm-close-surface = false
|
||||
|
||||
# Disable annoying copied to clipboard
|
||||
app-notifications = no-clipboard-copy,no-config-reload
|
||||
|
||||
# Ghostty is only a tmux client: tmux owns tabs (windows), splits (panes),
|
||||
# sessions and persistence. `clear` drops every Ghostty default (tabs,
|
||||
# splits, alt+N goto_tab, ctrl+alt+arrow goto_split) so those chords reach
|
||||
# tmux; the ctrl+shift chords below drive tmux through its prefix2
|
||||
# (\x02 = ctrl+b). Keep `clear` above every other keybind line.
|
||||
keybind = clear
|
||||
keybind = ctrl+shift+c=copy_to_clipboard
|
||||
keybind = ctrl+shift+v=paste_from_clipboard
|
||||
keybind = ctrl+plus=increase_font_size:1
|
||||
keybind = ctrl+minus=decrease_font_size:1
|
||||
keybind = ctrl+zero=reset_font_size
|
||||
keybind = ctrl+shift+period=reload_config
|
||||
# shift+enter as CSI-u (ESC[13;2u), the newline-in-prompt key coding agents
|
||||
# (omp, claude) read. Never \n: that is ctrl+j, tmux's pane-down key.
|
||||
keybind = shift+enter=text:\x1b[13;2u
|
||||
|
||||
# command palette: ctrl+space is tmux's root-table C-Space (tmux-palette);
|
||||
# sent explicitly as NUL so no keyboard-protocol mode can turn it into a CSI.
|
||||
# ctrl+backspace is a second key for the same palette.
|
||||
keybind = ctrl+space=text:\x00
|
||||
keybind = ctrl+backspace=text:\x00
|
||||
# project tasks palette (pj): tmux prefix t
|
||||
keybind = ctrl+shift+p=text:\x02t
|
||||
keybind = ctrl+shift+space=text:\x02t
|
||||
|
||||
# launchers (kitty's ctrl+shift chords), tmux prefix y/e/g/a
|
||||
keybind = ctrl+shift+f=text:\x02y
|
||||
keybind = ctrl+shift+e=text:\x02e
|
||||
keybind = ctrl+shift+g=text:\x02g
|
||||
keybind = ctrl+shift+a=text:\x02a
|
||||
|
||||
# tabs → tmux windows
|
||||
keybind = ctrl+shift+t=text:\x02c
|
||||
keybind = ctrl+shift+w=text:\x02k
|
||||
keybind = ctrl+shift+r=text:\x02r
|
||||
keybind = ctrl+tab=text:\x02n
|
||||
keybind = ctrl+shift+tab=text:\x02p
|
||||
keybind = ctrl+page_down=text:\x02n
|
||||
keybind = ctrl+page_up=text:\x02p
|
||||
keybind = ctrl+shift+arrow_right=text:\x02n
|
||||
keybind = ctrl+shift+arrow_left=text:\x02p
|
||||
keybind = ctrl+shift+k=text:\x02n
|
||||
keybind = ctrl+shift+h=text:\x02p
|
||||
|
||||
# splits → tmux panes
|
||||
keybind = ctrl+shift+l=text:\x02v
|
||||
keybind = ctrl+shift+j=text:\x02h
|
||||
keybind = ctrl+shift+x=text:\x02x
|
||||
|
||||
# session picker: tmux-sessionx on prefix o
|
||||
keybind = ctrl+shift+o=text:\x02o
|
||||
keybind = super+enter=text:\x02o
|
||||
keybind = ctrl+shift+enter=text:\x02z
|
||||
keybind = ctrl+shift+z=text:\x02z
|
||||
|
||||
# scrollback, help (search scrollback: tmux prefix /)
|
||||
keybind = ctrl+shift+page_up=text:\x02[
|
||||
keybind = ctrl+shift+slash=text:\x02?
|
||||
keybind = ctrl+shift+alt+,=text:\x02q
|
||||
|
||||
# sessions: quitting Ghostty detaches; the session lives on in tmux
|
||||
keybind = ctrl+shift+n=text:\x02C
|
||||
keybind = ctrl+shift+s=text:\x02s
|
||||
keybind = ctrl+shift+q=text:\x02d
|
||||
|
||||
# Tab configuration
|
||||
gtk-titlebar = false
|
||||
|
||||
quit-after-last-window-closed = false
|
||||
quit-after-last-window-closed-delay = 5m
|
||||
# Every window gets its own tmux session (~/.local/bin/ghostty-tmux): it
|
||||
# reclaims the oldest detached session or starts a new one. The first window
|
||||
# after boot starts the tmux server, and tmux-continuum restores the last
|
||||
# saved sessions, so closing Ghostty (or rebooting) never loses the shells.
|
||||
command = shell:$HOME/.local/bin/ghostty-tmux
|
||||
|
||||
# Shell integration
|
||||
shell-integration = detect
|
||||
shell-integration-features = cursor,sudo,title,no-cursor
|
||||
|
||||
# Rando stuff
|
||||
gtk-single-instance = true
|
||||
@@ -1 +0,0 @@
|
||||
theme = cyberdream
|
||||
@@ -1,23 +0,0 @@
|
||||
# cyberdream theme for ghostty
|
||||
palette = 0=#16181a
|
||||
palette = 1=#ff6e5e
|
||||
palette = 2=#5eff6c
|
||||
palette = 3=#f1ff5e
|
||||
palette = 4=#5ea1ff
|
||||
palette = 5=#bd5eff
|
||||
palette = 6=#5ef1ff
|
||||
palette = 7=#ffffff
|
||||
palette = 8=#3c4048
|
||||
palette = 9=#ff6e5e
|
||||
palette = 10=#5eff6c
|
||||
palette = 11=#f1ff5e
|
||||
palette = 12=#5ea1ff
|
||||
palette = 13=#bd5eff
|
||||
palette = 14=#5ef1ff
|
||||
palette = 15=#ffffff
|
||||
|
||||
background = #16181a
|
||||
foreground = #ffffff
|
||||
cursor-color = #ffffff
|
||||
selection-background = #3c4048
|
||||
selection-foreground = #ffffff
|
||||
@@ -1,43 +0,0 @@
|
||||
# Auto-generated by ii wallpaper theming system
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
background = #1D1D1D
|
||||
foreground = #DFE3E3
|
||||
|
||||
cursor-color = #DFE3E3
|
||||
cursor-text = #1D1D1D
|
||||
|
||||
selection-background = #DFE3E3
|
||||
selection-foreground = #1D1D1D
|
||||
|
||||
# Black
|
||||
palette = 0=#1D1D1D
|
||||
palette = 8=#8F9192
|
||||
|
||||
# Red
|
||||
palette = 1=#FF9BAD
|
||||
palette = 9=#FFCFD7
|
||||
|
||||
# Green
|
||||
palette = 2=#00FD98
|
||||
palette = 10=#C1FFD5
|
||||
|
||||
# Yellow
|
||||
palette = 3=#FFF17B
|
||||
palette = 11=#FFF39B
|
||||
|
||||
# Blue
|
||||
palette = 4=#6DD0FF
|
||||
palette = 12=#DAF0FF
|
||||
|
||||
# Magenta
|
||||
palette = 5=#D7B0FF
|
||||
palette = 13=#F0DFFF
|
||||
|
||||
# Cyan
|
||||
palette = 6=#3DF0F5
|
||||
palette = 14=#B8FDFF
|
||||
|
||||
# White
|
||||
palette = 7=#ADAAA2
|
||||
palette = 15=#DFE3E3
|
||||
@@ -1,32 +0,0 @@
|
||||
# Forces bold text to use the bright color palette
|
||||
bold-is-bright = true
|
||||
|
||||
# Background and foreground
|
||||
background = #101010
|
||||
foreground = #ffffff
|
||||
|
||||
# Cursor colors
|
||||
cursor-color = #b0b0b0
|
||||
cursor-text = #101010
|
||||
|
||||
# Selection colors
|
||||
selection-background = #272727
|
||||
selection-foreground = #b0b0b0
|
||||
|
||||
# ANSI color palette (0-15)
|
||||
palette = 0=#101010
|
||||
palette = 1=#ff7676
|
||||
palette = 2=#a3d6a3
|
||||
palette = 3=#ffffff
|
||||
palette = 4=#b3b3b3
|
||||
palette = 5=#f4b8e4
|
||||
palette = 6=#fafafa
|
||||
palette = 7=#a5adce
|
||||
palette = 8=#50585d
|
||||
palette = 9=#ff5733
|
||||
palette = 10=#86cd82
|
||||
palette = 11=#d9ba73
|
||||
palette = 12=#ffffff
|
||||
palette = 13=#f2a4db
|
||||
palette = 14=#5abfb5
|
||||
palette = 15=#b5bfe2
|
||||
@@ -1,32 +0,0 @@
|
||||
# Forces bold text to use the bright color palette
|
||||
bold-is-bright = true
|
||||
|
||||
# Background and foreground
|
||||
background = #faf9f5
|
||||
foreground = #101010
|
||||
|
||||
# Cursor colors
|
||||
cursor-color = #6a6a6a
|
||||
cursor-text = #ebebeb
|
||||
|
||||
# Selection colors
|
||||
selection-background = #ebebeb
|
||||
selection-foreground = #101010
|
||||
|
||||
# ANSI color palette (0-15)
|
||||
palette = 0=#ebebeb
|
||||
palette = 1=#ca0043
|
||||
palette = 2=#407f00
|
||||
palette = 3=#926200
|
||||
palette = 4=#3d3d3d
|
||||
palette = 5=#a200d1
|
||||
palette = 6=#007d7d
|
||||
palette = 7=#6a6a6a
|
||||
palette = 8=#969ba5
|
||||
palette = 9=#f30052
|
||||
palette = 10=#4f9a00
|
||||
palette = 11=#b07700
|
||||
palette = 12=#000000
|
||||
palette = 13=#c301fb
|
||||
palette = 14=#009797
|
||||
palette = 15=#101010
|
||||
@@ -1,246 +0,0 @@
|
||||
"""Daily kitty sessions, archived per week.
|
||||
|
||||
~/.local/share/kitty-sessions/<monday YYYY-MM-DD>/<day YYYY-MM-DD>.kitty-session
|
||||
~/.local/share/kitty-sessions/<monday YYYY-MM-DD>/<day YYYY-MM-DD>.scrollback/<window key>.ansi
|
||||
|
||||
One file, three entry points (kitty runs it under a different __name__ each):
|
||||
- geninclude (__main__): creates today's session file and points
|
||||
startup_session at it whenever no running kitty owns today's session (an
|
||||
flock held for the owner's lifetime). Every launch after kitty was closed
|
||||
reopens the day; a second concurrent instance starts plain instead of
|
||||
duplicating today's tabs. `kitty --session <past day>` (the kitty-days tv
|
||||
cable) opens that day as a read-only snapshot instead.
|
||||
- watcher (__kitty_watcher__): SAVE_DELAY seconds after a window opens,
|
||||
closes, moves, gains focus or finishes a command (and every SAVE_EVERY
|
||||
seconds, for output from long-running programs), rewrites the session this
|
||||
instance started with: tabs/splits/cwds, each shell window's scrollback, and
|
||||
the program running in it. On restore the shell `cat`s the scrollback, then
|
||||
re-runs the program.
|
||||
- kitten `day_session.py flush` (sent over remote control by kitty-day-save
|
||||
before shutdown/reboot): saves right away, then holds further saves for
|
||||
FREEZE seconds so windows dying during shutdown can't shrink the saved day.
|
||||
"""
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import date, timedelta
|
||||
from time import monotonic
|
||||
|
||||
from kittens.tui.handler import result_handler
|
||||
|
||||
ROOT = os.path.expanduser('~/.local/share/kitty-sessions')
|
||||
SAVE_DELAY = 1 # seconds; batches bursts of events, and the most change lost when kitty dies
|
||||
SAVE_EVERY = 30 # seconds; catches output that arrives without any window event
|
||||
FREEZE = 60 # seconds without saves after a flush; long enough for any shutdown, short if it's cancelled
|
||||
SB_VAR = 'day_scrollback' # user var naming a window's scrollback dump; kitty saves/restores it via launch --var
|
||||
UNSERIALIZE = 'kitty-unserialize-data=' # kitty.constants.unserialize_launch_flag
|
||||
# Never replay a command that was mid-run when the session was saved if any word
|
||||
# of it is one of these: re-running them on the next launch can destroy data.
|
||||
NO_REPLAY = {
|
||||
'rm', 'rmdir', 'mv', 'dd', 'shred', 'truncate', 'wipefs', 'fdisk', 'parted',
|
||||
'sudo', 'doas', 'su', 'run0', 'pkexec', 'kill', 'pkill', 'killall',
|
||||
'reboot', 'shutdown', 'poweroff',
|
||||
}
|
||||
SGR = re.compile(r'\x1b\[[0-9;:]*m')
|
||||
PROMPT_MARK = '\x1b]133;A' # shell integration's start-of-prompt mark
|
||||
_schedule_save = None # set by on_load when this instance owns a day session
|
||||
|
||||
|
||||
def today_path() -> str:
|
||||
d = date.today()
|
||||
return os.path.join(ROOT, str(d - timedelta(days=d.weekday())), f'{d}.kitty-session')
|
||||
|
||||
|
||||
def own_day(path: str) -> bool:
|
||||
# Lock a side file, not the session itself: atomic_save replaces that inode.
|
||||
# The fd stays open (and non-inheritable) so the lock dies with this kitty.
|
||||
lock = os.path.join(os.environ.get('XDG_RUNTIME_DIR') or tempfile.gettempdir(),
|
||||
f'kitty-day-{os.path.basename(path)}.lock')
|
||||
fd = os.open(lock, os.O_RDONLY | os.O_CREAT, 0o600)
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError:
|
||||
os.close(fd)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def replay_safe(cmd: str) -> bool:
|
||||
try:
|
||||
words = shlex.split(cmd)
|
||||
except ValueError:
|
||||
return False
|
||||
return not any((b := os.path.basename(w)) in NO_REPLAY or b.startswith('mkfs') for w in words)
|
||||
|
||||
|
||||
def scrollback(window) -> str:
|
||||
from kitty.window import as_text
|
||||
|
||||
s = window.screen
|
||||
# While a full-screen program (nvim) is up, the shell's output lives in the other buffer.
|
||||
text = as_text(s, as_ansi=True, add_history=True, alternate_screen=s.is_using_alternate_linebuf())
|
||||
if window.at_prompt and (i := text.rfind(PROMPT_MARK)) >= 0:
|
||||
text = text[:i] # the idle prompt; the restored shell draws a fresh one
|
||||
lines = text.split('\n')
|
||||
while lines and not SGR.sub('', lines[-1]).strip(): # the unused rows below the cursor
|
||||
lines.pop()
|
||||
return '\n'.join(lines) + '\x1b[m\n' if lines else ''
|
||||
|
||||
|
||||
def on_load(boss, data) -> None:
|
||||
from uuid import uuid4
|
||||
|
||||
from kitty.config import atomic_save
|
||||
from kitty.fast_data_types import add_timer, get_options
|
||||
from kitty.session import parse_save_as_options_spec_args, session_arg_to_name
|
||||
|
||||
global _schedule_save
|
||||
path = get_options().startup_session or ''
|
||||
if not path.startswith(ROOT + os.sep):
|
||||
return
|
||||
name = session_arg_to_name(path)
|
||||
sb_dir = path.rpartition('.')[0] + '.scrollback'
|
||||
ser_opts = parse_save_as_options_spec_args(
|
||||
['--use-foreground-process', f'--match=session:^{re.escape(name)}$'])[0]
|
||||
last = ''
|
||||
dumps: dict[str, str] = {}
|
||||
pending = False
|
||||
|
||||
def launch_parts(line: str) -> tuple[list[str], dict] | None:
|
||||
args = shlex.split(line) if line.startswith('launch ') else []
|
||||
if len(args) < 2 or not args[1].startswith(UNSERIALIZE):
|
||||
return None
|
||||
return args, json.loads(args[1][len(UNSERIALIZE):])
|
||||
|
||||
# Per-window shell startup command (scrollback cat + program), by SB_VAR key.
|
||||
# Seeded from the file so restored windows keep theirs until their shell is up.
|
||||
startup: dict[str, str] = {}
|
||||
with open(path) as f:
|
||||
for args, data in filter(None, map(launch_parts, f.read().splitlines())):
|
||||
for a in args:
|
||||
if a.startswith(f'--var={SB_VAR}=') and data.get('cmd_at_shell_startup'):
|
||||
startup[a.removeprefix(f'--var={SB_VAR}=')] = data['cmd_at_shell_startup']
|
||||
|
||||
def startup_cmd(line: str) -> str:
|
||||
# Rewrite kitty's per-window restore data: cat the scrollback first, then
|
||||
# re-run the foreground program unless it is on the NO_REPLAY list.
|
||||
parts = launch_parts(line)
|
||||
if parts is None:
|
||||
return line
|
||||
args, data = parts
|
||||
w = boss.window_id_map.get(data['id'])
|
||||
key = w.user_vars.get(SB_VAR, '') if w else ''
|
||||
fg = data.pop('cmd_at_shell_startup', None)
|
||||
# Until the shell draws a prompt, kitty's "foreground process" is shell
|
||||
# init (mise/direnv hooks) or the replayed program: keep the previous value.
|
||||
if w and w.screen.last_reported_cwd:
|
||||
steps = []
|
||||
dump = os.path.join(sb_dir, f'{key}.ansi')
|
||||
if os.path.exists(dump):
|
||||
steps.append(f'cat {shlex.quote(dump)}')
|
||||
if fg:
|
||||
fg = fg if isinstance(fg, str) else shlex.join(fg)
|
||||
if replay_safe(fg):
|
||||
steps.append(fg)
|
||||
startup[key] = '; '.join(steps)
|
||||
if startup.get(key):
|
||||
data['cmd_at_shell_startup'] = startup[key]
|
||||
args[1] = UNSERIALIZE + json.dumps(data)
|
||||
return shlex.join(args)
|
||||
|
||||
frozen_until = 0.0
|
||||
|
||||
def save(timer_id: int) -> None:
|
||||
nonlocal last, pending
|
||||
pending = False
|
||||
windows = [w for w in boss.all_windows if w.created_in_session_name == name]
|
||||
# Nothing left in the session means kitty is closing: keep the last good save.
|
||||
if not windows or monotonic() < frozen_until:
|
||||
return
|
||||
os.makedirs(sb_dir, exist_ok=True)
|
||||
keep = set()
|
||||
for w in windows:
|
||||
key = w.user_vars.get(SB_VAR)
|
||||
if not key:
|
||||
key = uuid4().hex
|
||||
w.set_user_var(SB_VAR, key)
|
||||
keep.add(f'{key}.ansi')
|
||||
# Only once the shell has drawn a prompt (and reported its cwd): a
|
||||
# restored window cats its old dump before that, so writing earlier
|
||||
# could replace the history before it was replayed.
|
||||
if w.screen.last_reported_cwd:
|
||||
text = scrollback(w)
|
||||
if dumps.get(key) != text:
|
||||
atomic_save(text.encode(), os.path.join(sb_dir, f'{key}.ansi'))
|
||||
dumps[key] = text
|
||||
for f in set(os.listdir(sb_dir)) - keep: # windows closed since the last save
|
||||
os.remove(os.path.join(sb_dir, f))
|
||||
text = '\n'.join(map(startup_cmd, boss.serialize_state_as_session(path, ser_opts)))
|
||||
if text != last:
|
||||
atomic_save(text.encode(), path)
|
||||
last = text
|
||||
|
||||
def schedule() -> None:
|
||||
nonlocal pending
|
||||
if not pending:
|
||||
pending = True
|
||||
add_timer(save, SAVE_DELAY, False)
|
||||
|
||||
def flush() -> None:
|
||||
nonlocal frozen_until
|
||||
frozen_until = 0.0
|
||||
save(0)
|
||||
frozen_until = monotonic() + FREEZE
|
||||
|
||||
_schedule_save = schedule
|
||||
boss.day_session_flush = flush # the flush kitten runs in its own module copy, so it finds this via boss
|
||||
add_timer(lambda timer_id: schedule(), SAVE_EVERY, True)
|
||||
|
||||
|
||||
def _changed(boss, window, data) -> None:
|
||||
if _schedule_save is not None:
|
||||
_schedule_save()
|
||||
|
||||
|
||||
# on_resize covers windows/tabs being created, closed or re-split; on_cmd_startstop
|
||||
# catches `cd` and finished commands. Title changes are skipped: spinners would
|
||||
# re-save every second.
|
||||
on_resize = on_close = on_focus_change = on_cmd_startstop = _changed
|
||||
|
||||
|
||||
def main(args: list[str]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@result_handler(no_ui=True)
|
||||
def handle_result(args: list[str], answer: str, target_window_id: int, boss) -> None:
|
||||
if 'flush' in args and (flush := getattr(boss, 'day_session_flush', None)):
|
||||
flush()
|
||||
|
||||
|
||||
def session_arg() -> str | None:
|
||||
"""`--session` from kitty's own command line (geninclude runs inside kitty)."""
|
||||
argv = sys.argv[1:]
|
||||
for i, a in enumerate(argv):
|
||||
if a.startswith('--session='):
|
||||
return a.partition('=')[2]
|
||||
if a == '--session' and i + 1 < len(argv):
|
||||
return argv[i + 1]
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
path = today_path()
|
||||
other = session_arg()
|
||||
# An explicit --session for another day must not claim today's lock.
|
||||
if (other is None or os.path.abspath(os.path.expanduser(other)) == path) and own_day(path):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
try:
|
||||
open(path, 'x').close() # empty session = one tab with the default shell
|
||||
except FileExistsError:
|
||||
pass
|
||||
print(f'startup_session {path}')
|
||||
@@ -1,39 +0,0 @@
|
||||
listen_on unix:@kitty-goliath
|
||||
# open straight into goliath's tmux, over mosh: the UDP session survives
|
||||
# suspend, roaming and dead links, and reconnects itself. mosh-server allocates
|
||||
# the remote pty, so there is no `ssh -t` equivalent to pass. Caveat: mosh runs
|
||||
# its bootstrap ssh with `-S none`, so it can never ride a ControlMaster --
|
||||
# non-interactive key auth to goliath must work or every launch prompts for a
|
||||
# password. Needs mosh on both ends and UDP 60000-61000 open on goliath.
|
||||
# mosh also rewrites TERM to xterm-256color (it only forwards a color count),
|
||||
# so xterm-kitty-only capabilities (undercurl, ...) are gone inside the session.
|
||||
# MOSH_TITLE_NOPREFIX drops mosh's "[mosh] " window-title prefix.
|
||||
env MOSH_TITLE_NOPREFIX=1
|
||||
shell mosh goliath -- tmux new-session -A -s main
|
||||
# structural keys forward to tmux (prefix C-b = \x02)
|
||||
map super+w send_text all \x02x
|
||||
map super+t send_text all \x02%
|
||||
map super+n send_text all \x02"
|
||||
map super+enter send_text all \x02t
|
||||
map alt+h send_text all \x02H
|
||||
map alt+l send_text all \x02L
|
||||
map alt+t send_text all \x02t
|
||||
map alt+n send_text all \x02t
|
||||
map alt+q send_text all \x021
|
||||
map alt+w send_text all \x022
|
||||
map alt+e send_text all \x023
|
||||
map alt+r send_text all \x024
|
||||
map alt+t send_text all \x025
|
||||
map alt+y send_text all \x026
|
||||
map alt+u send_text all \x027
|
||||
map alt+i send_text all \x028
|
||||
map alt+o send_text all \x029
|
||||
map super+left send_text all \x02\x1b[1;5D
|
||||
map super+right send_text all \x02\x1b[1;5C
|
||||
map super+up send_text all \x02\x1b[1;5A
|
||||
map super+down send_text all \x02\x1b[1;5B
|
||||
map super+d send_text all \x02d
|
||||
tab_bar_style hidden
|
||||
|
||||
# Colors + transparency from the active Omarchy theme (last include wins)
|
||||
include ~/.local/state/omarchy/current/theme/kitty.conf
|
||||
@@ -1,75 +0,0 @@
|
||||
# ── herdr.conf · kitty as a herdr host ──
|
||||
# Kitty stays a dumb terminal here: herdr owns panes, tabs and workspaces.
|
||||
# Keybinds mirror ~/.config/ghostty/config exactly, so both terminals drive
|
||||
# herdr identically. Prefix ctrl+b == byte \x02; an uppercase letter after the
|
||||
# prefix byte is the shift variant (\x02N == prefix+shift+n).
|
||||
|
||||
# ── base / appearance ──
|
||||
font_family VictorMono Nerd Font
|
||||
bold_italic_font auto
|
||||
window_padding_width 8
|
||||
hide_window_decorations yes
|
||||
background_opacity 0.9
|
||||
copy_on_select auto
|
||||
strip_trailing_spaces smart
|
||||
confirm_os_window_close 0
|
||||
allow_remote_control socket-only
|
||||
cursor_trail 10
|
||||
cursor_trail_start_threshold 0
|
||||
cursor_trail_decay 0.01 0.05
|
||||
cursor_blink_interval 0
|
||||
class kitty-herdr
|
||||
|
||||
# herdr renders its own sidebar/tab strip — no kitty chrome
|
||||
tab_bar_style hidden
|
||||
|
||||
# bare `herdr` launches-or-attaches the persistent session; closing the window
|
||||
# detaches, sessions survive (same as ghostty `command = herdr`)
|
||||
shell herdr
|
||||
|
||||
# ── ghostty parity: plain shortcuts ──
|
||||
map super+c copy_to_clipboard
|
||||
map super+v paste_from_clipboard
|
||||
map ctrl+plus change_font_size current +1
|
||||
map ctrl+minus change_font_size current -1
|
||||
map ctrl+0 change_font_size current 0
|
||||
|
||||
# reload_config moves off ctrl+shift+, (taken by herdr settings below)
|
||||
map ctrl+shift+alt+, load_config_file
|
||||
|
||||
# ── herdr sync: ctrl+shift chords → prefix (\x02 = ctrl+b) bytes ──
|
||||
map ctrl+shift+n send_text all \x02N
|
||||
map super+n send_text all \x02G
|
||||
map super+t send_text all \x02c
|
||||
map ctrl+shift+t send_text all \x02c
|
||||
map ctrl+space send_text all \x02t
|
||||
map super+z send_text all \x02z
|
||||
map ctrl+tab send_text all \x02.
|
||||
map ctrl+shift+tab send_text all \x02,
|
||||
map ctrl+shift+f send_text all \x02K
|
||||
map ctrl+shift+g send_text all \x02L
|
||||
# ctrl+shift+e → prefix+e → herdr-nvim toggle; the editor pane opens on the
|
||||
# left (position = "left" in ~/.config/herdr-nvim/config.toml)
|
||||
map ctrl+shift+e send_text all \x02e
|
||||
map ctrl+shift+o send_text all \x02o
|
||||
map ctrl+shift+h send_text all \x02p
|
||||
map ctrl+shift+l send_text all \x02n
|
||||
map ctrl+shift+j send_text all \x02j
|
||||
map ctrl+shift+k send_text all \x02k
|
||||
map ctrl+shift+w send_text all \x02W
|
||||
map ctrl+shift+r send_text all \x02T
|
||||
map ctrl+shift+, send_text all \x02S
|
||||
map ctrl+shift+q send_text all \x02x
|
||||
map ctrl+shift+p send_text all \x02P
|
||||
|
||||
# ctrl+h/j/k/l are herdr's vim-herdr-navigation bindings — kitty must not grab
|
||||
# them, so no split-navigation maps live in this config.
|
||||
|
||||
# BEGIN_KITTY_FONTS
|
||||
font_family family="VictorMono Nerd Font Mono"
|
||||
bold_font auto
|
||||
italic_font auto
|
||||
bold_italic_font auto
|
||||
# END_KITTY_FONTS
|
||||
# Colors + transparency from the active Omarchy theme (last include wins)
|
||||
include ~/.local/state/omarchy/current/theme/kitty.conf
|
||||
@@ -1,129 +0,0 @@
|
||||
# ── base.conf · shared ──
|
||||
font_family VictorMono Nerd Font
|
||||
font_size 13.0
|
||||
bold_italic_font auto
|
||||
window_padding_width 8
|
||||
hide_window_decorations yes
|
||||
# Transparency: kitty's own alpha, not the compositor's -- Hyprland fades a
|
||||
# whole window (chrome included), kitty only makes the cell background
|
||||
# translucent, so text stays fully opaque on top of it. This alpha is also what
|
||||
# makes kitty the one window Hyprland blurs: hypr/looknfeel.lua enables blur
|
||||
# globally and then opts every window out except the terminal classes, and an
|
||||
# opaque window has no backdrop to blur. kitty's own `background_blur` does
|
||||
# nothing here -- it rides ext-background-effect-v1, which Hyprland ignores
|
||||
# while decoration:blur:enabled is false, and is redundant once it is true.
|
||||
background_opacity 0.9
|
||||
cursor_blink_interval 1
|
||||
copy_on_select auto
|
||||
strip_trailing_spaces smart
|
||||
confirm_os_window_close 0
|
||||
allow_remote_control socket-only
|
||||
# socket-only remote control needs a socket: this also exports KITTY_LISTEN_ON
|
||||
# into every pane, which is how nvim (config.pane_nav) knows kitty owns the
|
||||
# panes and how it hands an at-edge <C-h/j/k/l> back to kitty.
|
||||
listen_on unix:@kitty-{kitty_pid}
|
||||
cursor_trail 10
|
||||
cursor_trail_start_threshold 0
|
||||
cursor_trail_decay 0.01 0.05
|
||||
cursor_blink_interval 0
|
||||
|
||||
enabled_layouts splits:split_axis=vertical,stack
|
||||
|
||||
# launch aliases
|
||||
action_alias launch_mn launch --cwd=current --type=overlay-main
|
||||
action_alias launch_os launch --cwd=current --type=os-window
|
||||
action_alias launch_ov launch --cwd=current --type=overlay
|
||||
action_alias launch_tb launch --cwd=current --type=tab
|
||||
action_alias launch_hs launch --cwd=current --location=hsplit --bias=25
|
||||
action_alias launch_vs launch --cwd=current --location=vsplit --bias=33
|
||||
action_alias launch_vs50 launch --cwd=current --location=vsplit --bias=50
|
||||
|
||||
# panes = kitty windows
|
||||
map super+n launch_vs50
|
||||
map super+z toggle_layout stack
|
||||
map ctrl+shift+z toggle_layout stack
|
||||
map ctrl+shift+t new_tab_with_cwd
|
||||
map super+enter new_tab_with_cwd
|
||||
map ctrl+space launch_hs
|
||||
map ctrl+enter launch_vs
|
||||
map ctrl+shift+space new_os_window
|
||||
|
||||
# Splits
|
||||
map ctrl+shift+j launch_hs
|
||||
map ctrl+shift+l launch_vs
|
||||
map ctrl+shift+h previous_tab
|
||||
map ctrl+shift+k next_tab
|
||||
|
||||
# Shortcuts
|
||||
map ctrl+shift+g launch_ov lazygit
|
||||
map ctrl+shift+f launch_tb yazi
|
||||
# nvim in the current dir, split to the right of the active pane at 50/50
|
||||
map ctrl+shift+e launch_vs50 nvim
|
||||
# herdr host: a *separate* kitty process, own config (launch cannot take --config)
|
||||
# tmux host: same trick — kitty stays dumb, tmux owns panes/windows/sessions
|
||||
map ctrl+shift+p launch_hs atuin-pick
|
||||
|
||||
|
||||
# Misc
|
||||
map ctrl+shift+c copy_to_clipboard
|
||||
map ctrl+shift+v paste_from_clipboard
|
||||
|
||||
# tabs
|
||||
map alt+q goto_tab 1
|
||||
map alt+w goto_tab 2
|
||||
map alt+e goto_tab 3
|
||||
map alt+r goto_tab 4
|
||||
map alt+t goto_tab 5
|
||||
map alt+y goto_tab 6
|
||||
map alt+u goto_tab 7
|
||||
map alt+i goto_tab 8
|
||||
map alt+o goto_tab 9
|
||||
|
||||
# Daily sessions, archived per week (day_session.py):
|
||||
# ~/.local/share/kitty-sessions/<monday>/<day>.kitty-session. Launching kitty
|
||||
# reopens today's session unless another running kitty already owns it: tabs,
|
||||
# splits, cwds, each shell's scrollback and the program running in it. Tabs
|
||||
# made with *_with_cwd / --cwd=current join it and are saved ~1s after each
|
||||
# change, and right before shutdown/reboot (kitty-day-save.service). `tv
|
||||
# kitty-days` opens any earlier day in a new kitty.
|
||||
geninclude day_session.py
|
||||
watcher day_session.py
|
||||
|
||||
# split nav (vim-aware)
|
||||
map ctrl+h kitten pass_keys.py neighboring_window left ctrl+h
|
||||
map ctrl+j kitten pass_keys.py neighboring_window bottom ctrl+j
|
||||
map ctrl+k kitten pass_keys.py neighboring_window top ctrl+k
|
||||
map ctrl+l kitten pass_keys.py neighboring_window right ctrl+l
|
||||
# resize
|
||||
map super+left resize_window narrower
|
||||
map super+right resize_window wider
|
||||
map super+up resize_window taller
|
||||
map super+down resize_window shorter 3
|
||||
|
||||
tab_bar_style powerline
|
||||
tab_powerline_style angled
|
||||
tab_bar_align left
|
||||
tab_bar_edge top
|
||||
# always shown, even with one tab: it carries the stack-layout pane indicator
|
||||
tab_bar_min_tabs 1
|
||||
|
||||
# Titles come from ~/.config/kitty/tab_bar.py (`{custom}`): the repo the tab's
|
||||
# cwd belongs to, or the file nvim is editing when nvim is the active process.
|
||||
# Active and inactive tabs share the text; the powerline colors mark the focus.
|
||||
tab_title_template "{custom}"
|
||||
|
||||
|
||||
|
||||
# BEGIN_KITTY_FONTS
|
||||
font_family family="VictorMono Nerd Font Mono"
|
||||
bold_font auto
|
||||
italic_font auto
|
||||
bold_italic_font auto
|
||||
# END_KITTY_FONTS
|
||||
#
|
||||
# Colors + transparency come from the active Omarchy theme
|
||||
# (~/.config/omarchy/themes/<name>/kitty.conf). Last include wins, so keep this
|
||||
# at the bottom.
|
||||
include ~/.local/state/omarchy/current/theme/kitty.conf
|
||||
|
||||
font_size 9.0
|
||||
@@ -1,16 +0,0 @@
|
||||
from kittens.tui.handler import result_handler
|
||||
from typing import List
|
||||
from kitty.boss import Boss
|
||||
|
||||
|
||||
def main():
|
||||
pass
|
||||
|
||||
|
||||
directions = ["right", "left", "top", "bottom"]
|
||||
|
||||
|
||||
@result_handler(no_ui=True)
|
||||
def handle_result(args: List[str], result: str, target_window_id: int, boss: Boss):
|
||||
if len(args) == 2 and args[1] in directions:
|
||||
boss.active_tab.neighboring_window(args[1])
|
||||
@@ -1,60 +0,0 @@
|
||||
import re
|
||||
|
||||
from kittens.tui.handler import result_handler
|
||||
from kitty.key_encoding import KeyEvent, parse_shortcut
|
||||
|
||||
# Windows whose foreground process owns <C-h/j/k/l> itself: (n)vim and fzf move
|
||||
# their own splits/selection, tv binds ctrl-h to goto_parent_dir, and tmux/herdr
|
||||
# run the same vim-aware handoff one layer down (tmux.conf `is_vim`,
|
||||
# vim-herdr-navigation). Anything else means kitty owns the panes, so kitty
|
||||
# moves its own window.
|
||||
PASSTHROUGH = r'\b(vim|nvim|fzf|tv|tmux|herdr)\b'
|
||||
|
||||
|
||||
# Return True if any foreground process in the window matches PASSTHROUGH
|
||||
def is_window_passthrough(window):
|
||||
|
||||
fp = window.child.foreground_processes
|
||||
|
||||
# Check if any argument in a process's cmdline matches PASSTHROUGH,
|
||||
# including when launched via wrappers or pipelines (e.g., "zoxide query | fzf")
|
||||
def is_process_passthrough(p):
|
||||
return any(re.search(PASSTHROUGH, arg, re.IGNORECASE) for arg in p['cmdline'])
|
||||
|
||||
return any(is_process_passthrough(p) for p in fp)
|
||||
|
||||
|
||||
def encode_key_mapping(window, key_mapping):
|
||||
mods, key = parse_shortcut(key_mapping)
|
||||
event = KeyEvent(
|
||||
mods=mods,
|
||||
key=key,
|
||||
shift=bool(mods & 1),
|
||||
alt=bool(mods & 2),
|
||||
ctrl=bool(mods & 4),
|
||||
super=bool(mods & 8),
|
||||
hyper=bool(mods & 16),
|
||||
meta=bool(mods & 32),
|
||||
).as_window_system_event()
|
||||
|
||||
return window.encoded_key(event)
|
||||
|
||||
|
||||
def main():
|
||||
pass
|
||||
|
||||
|
||||
@result_handler(no_ui=True)
|
||||
def handle_result(args, result, target_window_id, boss):
|
||||
window = boss.window_id_map.get(target_window_id)
|
||||
direction = args[2]
|
||||
key_mapping = args[3]
|
||||
|
||||
if window is None:
|
||||
return
|
||||
if is_window_passthrough(window):
|
||||
for keymap in key_mapping.split('>'):
|
||||
encoded = encode_key_mapping(window, keymap)
|
||||
window.write_to_child(encoded)
|
||||
else:
|
||||
boss.active_tab.neighboring_window(direction)
|
||||
@@ -1,628 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Kitty kitten: select a tab from a list with fuzzy search.
|
||||
|
||||
Usage: map some_key kitten select_tab.py
|
||||
|
||||
Keys:
|
||||
Up/Down, Page Up/Page Down - scroll list
|
||||
Enter - select highlighted tab
|
||||
Any other input - search box (incremental fuzzy filter)
|
||||
Ctrl+Up / Ctrl+Down - search history (previous/next)
|
||||
Ctrl+C / Escape - quit without selecting
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# from kitty.typing_compat import KeyEventType
|
||||
from kitty.key_encoding import EventType
|
||||
from kitty.boss import Boss
|
||||
from kittens.tui.handler import Handler, kitten_ui
|
||||
from kittens.tui.loop import Loop, debug
|
||||
from kittens.tui.line_edit import LineEdit
|
||||
from kittens.tui.operations import styled
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import pprint
|
||||
from typing import Any
|
||||
|
||||
|
||||
def format_cwd_like_zsh(path: str) -> str:
|
||||
"""Format CWD like zsh %(4~|…/%3~|%~): full path, or …/last/3/parts if 4+ segments."""
|
||||
if not path:
|
||||
return ""
|
||||
home = os.environ.get("HOME", "") or os.path.expanduser("~")
|
||||
if home and (path == home or path.startswith(home + "/")):
|
||||
path = "~" + path[len(home) :].rstrip("/") or ""
|
||||
path = path.rstrip("/") or "/"
|
||||
segments = [s for s in path.split("/") if s]
|
||||
if len(segments) >= 4:
|
||||
return "…/" + "/".join(segments[-3:])
|
||||
return path if path else "~"
|
||||
|
||||
|
||||
class TabRow:
|
||||
"""One row of data for a tab (for display and filtering)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tab_id,
|
||||
os_window_id,
|
||||
index_in_os_window,
|
||||
title,
|
||||
cwd,
|
||||
vt,
|
||||
foreground,
|
||||
search_text="",
|
||||
):
|
||||
self.tab_id = tab_id
|
||||
self.os_window_id = os_window_id
|
||||
self.index_in_os_window = index_in_os_window
|
||||
self.title = title
|
||||
self.cwd = cwd
|
||||
self.vt = vt
|
||||
self.foreground = foreground
|
||||
parts = [title, cwd]
|
||||
if vt is not None:
|
||||
parts.append(vt)
|
||||
parts.append(foreground)
|
||||
self.search_text = " ".join(parts).lower() if not search_text else search_text
|
||||
|
||||
|
||||
def fuzzy_match(query: str, text: str) -> bool:
|
||||
"""True if query characters appear in text in order (case-insensitive)."""
|
||||
if not query:
|
||||
return True
|
||||
q = query.lower()
|
||||
t = text.lower()
|
||||
i = 0
|
||||
for c in q:
|
||||
j = t.find(c, i)
|
||||
if j < 0:
|
||||
return False
|
||||
i = j + 1
|
||||
return True
|
||||
|
||||
|
||||
def get_fuzzy_match_indices(query: str, text: str) -> list[int]:
|
||||
"""Indices in text that match each query character in order (case-insensitive)."""
|
||||
if not query:
|
||||
return []
|
||||
q = query.lower()
|
||||
t = text.lower()
|
||||
indices = []
|
||||
i = 0
|
||||
for c in q:
|
||||
j = t.find(c, i)
|
||||
if j < 0:
|
||||
break
|
||||
indices.append(j)
|
||||
i = j + 1
|
||||
return indices
|
||||
|
||||
|
||||
def highlight_matches(
|
||||
segment_str: str,
|
||||
match_indices: list[int],
|
||||
content_len: int,
|
||||
) -> str:
|
||||
"""Wrap matching character runs in segment_str with styled bold cyan (or fallback SGR)."""
|
||||
in_content = {i for i in match_indices if 0 <= i < content_len}
|
||||
if not in_content:
|
||||
return segment_str
|
||||
# Merge consecutive indices into (start, end) runs
|
||||
sorted_i = sorted(in_content)
|
||||
runs = []
|
||||
start = sorted_i[0]
|
||||
end = start + 1
|
||||
for i in sorted_i[1:]:
|
||||
if i == end:
|
||||
end = i + 1
|
||||
else:
|
||||
runs.append((start, end))
|
||||
start, end = i, i + 1
|
||||
runs.append((start, end))
|
||||
# Build string with highlights
|
||||
out = []
|
||||
pos = 0
|
||||
hi_on = "\033[1;36m" # bold cyan
|
||||
hi_off = "\033[22;39m"
|
||||
for s, e in runs:
|
||||
out.append(segment_str[pos:s])
|
||||
chunk = segment_str[s:e]
|
||||
st = styled(chunk, fg="cyan", bold=True)
|
||||
out.append(st if st else hi_on + chunk + hi_off)
|
||||
pos = e
|
||||
out.append(segment_str[pos:])
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def longest_substring_match_length(query: str, text: str) -> int:
|
||||
"""Length of longest contiguous substring of query that appears in text (case-insensitive)."""
|
||||
if not query:
|
||||
return 0
|
||||
q = query.lower()
|
||||
t = text.lower()
|
||||
best = 0
|
||||
for start in range(len(q)):
|
||||
for end in range(start + 1, len(q) + 1):
|
||||
if q[start:end] in t:
|
||||
best = max(best, end - start)
|
||||
return best
|
||||
|
||||
|
||||
def gather_tabs_from_ls(ls_data: list[dict[str, Any]]) -> list[TabRow]:
|
||||
"""Build a flat list of TabRow from kitten @ ls JSON (list of OS windows)."""
|
||||
rows: list[TabRow] = []
|
||||
# Exclude the tab that contains the window we're running in (the overlay's parent)
|
||||
try:
|
||||
self_window_id = int(os.environ.get("KITTY_WINDOW_ID", "") or 0)
|
||||
except (ValueError, TypeError):
|
||||
self_window_id = 0
|
||||
for os_win in ls_data:
|
||||
os_window_id = os_win.get("id", 0)
|
||||
tabs = os_win.get("tabs") or []
|
||||
for tab_idx, tab in enumerate(tabs):
|
||||
windows = tab.get("windows") or []
|
||||
if self_window_id and any(w.get("id") == self_window_id for w in windows):
|
||||
continue
|
||||
tab_id = tab.get("id", 0)
|
||||
title = tab.get("title") or ""
|
||||
# Use active/focused window in tab; fallback to first
|
||||
active = next(
|
||||
(w for w in windows if w.get("is_focused") or w.get("is_active")),
|
||||
windows[0] if windows else None,
|
||||
)
|
||||
cwd = ""
|
||||
vt = None
|
||||
foreground = ""
|
||||
if active:
|
||||
debug(f"Active window in tab {tab_id}: {active}")
|
||||
cwd = active.get("cwd") or ""
|
||||
user_vars = active.get("user_vars")
|
||||
if isinstance(user_vars, dict):
|
||||
vt = user_vars.get("vt")
|
||||
else:
|
||||
vt = None
|
||||
# Foreground process: cmdline or foreground_processes
|
||||
cmdline = active.get("cmdline")
|
||||
if cmdline:
|
||||
foreground = (
|
||||
" ".join(cmdline) if isinstance(cmdline, list) else str(cmdline)
|
||||
)
|
||||
else:
|
||||
fg_procs = active.get("foreground_processes")
|
||||
if fg_procs:
|
||||
parts = []
|
||||
for p in fg_procs:
|
||||
if isinstance(p, dict):
|
||||
pcmd = p.get("cmdline")
|
||||
if pcmd:
|
||||
parts.append(
|
||||
" ".join(pcmd)
|
||||
if isinstance(pcmd, list)
|
||||
else str(pcmd)
|
||||
)
|
||||
else:
|
||||
parts.append(str(p))
|
||||
foreground = " | ".join(parts)
|
||||
rows.append(
|
||||
TabRow(
|
||||
tab_id=tab_id,
|
||||
os_window_id=os_window_id,
|
||||
index_in_os_window=tab_idx,
|
||||
title=title or "(no title)",
|
||||
cwd=cwd,
|
||||
vt=vt,
|
||||
foreground=foreground or "(no process)",
|
||||
search_text="",
|
||||
)
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
class TabSelectHandler(Handler):
|
||||
"""TUI handler: list of tabs, search box, scroll, history."""
|
||||
|
||||
def __init__(self, tab_rows: list[TabRow]) -> None:
|
||||
super().__init__()
|
||||
self.all_rows = tab_rows
|
||||
self.filtered_rows = tab_rows[:]
|
||||
# self.search_history: list[str] = []
|
||||
# self.history_index = -1 # -1 = current (not in history)
|
||||
self.selected_index = 0 # index in filtered list
|
||||
self.scroll_offset = 0
|
||||
self.selected_tab_result: dict[str, Any] | None = None # set on Enter
|
||||
self.line_edit = LineEdit()
|
||||
|
||||
# Minimum size so we always draw something (overlay PTY can report 0x0 at start)
|
||||
MIN_ROWS = 24
|
||||
MIN_COLS = 80
|
||||
|
||||
@property
|
||||
def screen_rows(self) -> int:
|
||||
return max(self.MIN_ROWS, getattr(self.screen_size, "rows", 0) or 0)
|
||||
|
||||
@property
|
||||
def screen_cols(self) -> int:
|
||||
return max(self.MIN_COLS, getattr(self.screen_size, "cols", 0) or 0)
|
||||
|
||||
def initialize(self) -> None:
|
||||
# self.print(f"In TabSelectHandler...")
|
||||
self.cmd.clear_screen()
|
||||
self.cmd.set_cursor_visible(True)
|
||||
self._apply_filter()
|
||||
self._clamp_selection()
|
||||
# Draw immediately and also schedule a draw on next event-loop tick. When run
|
||||
# as an overlay, the PTY size can be 0x0 at first or kitty may only show
|
||||
# content sent after overlay_ready(); the delayed draw ensures something
|
||||
# appears once the overlay is ready.
|
||||
self._draw()
|
||||
try:
|
||||
self.asyncio_loop.call_soon(self._draw)
|
||||
except Exception:
|
||||
pass
|
||||
# self.write(SAVE_CURSOR)
|
||||
# debug("Initialization complete")
|
||||
|
||||
def _apply_filter(self) -> None:
|
||||
# debug("In _apply_filter")
|
||||
q = self.line_edit.current_input.strip()
|
||||
if not q:
|
||||
self.filtered_rows = self.all_rows[:]
|
||||
else:
|
||||
q_lower = q.lower()
|
||||
# Exact VT match: if the search term equals a tab's VT, show only that/those.
|
||||
vt_exact = [
|
||||
r for r in self.all_rows if r.vt is not None and str(r.vt).strip() == q
|
||||
]
|
||||
if vt_exact:
|
||||
self.filtered_rows = vt_exact
|
||||
else:
|
||||
self.filtered_rows = [
|
||||
r for r in self.all_rows if fuzzy_match(q_lower, r.search_text)
|
||||
]
|
||||
# Sort by longest contiguous substring match (best first), then original order
|
||||
self.filtered_rows.sort(
|
||||
key=lambda r: (
|
||||
-longest_substring_match_length(q_lower, r.search_text),
|
||||
self.all_rows.index(r),
|
||||
)
|
||||
)
|
||||
self._clamp_selection()
|
||||
|
||||
def on_resize(self, screen_size: Any) -> None:
|
||||
# debug(f"In on_resize with screen_size: {screen_size}")
|
||||
super().on_resize(screen_size)
|
||||
self._clamp_selection()
|
||||
self._draw()
|
||||
|
||||
def _clamp_selection(self) -> None:
|
||||
# debug("In _clamp_selection")
|
||||
n = len(self.filtered_rows)
|
||||
if n == 0:
|
||||
self.selected_index = 0
|
||||
self.scroll_offset = 0
|
||||
return
|
||||
if self.selected_index >= n:
|
||||
self.selected_index = 0
|
||||
elif self.selected_index < 0:
|
||||
self.selected_index = n - 1
|
||||
# self.selected_index = max(0, min(self.selected_index, n - 1))
|
||||
# Keep selection in view
|
||||
list_top = 1
|
||||
list_bottom = self.screen_rows - 2
|
||||
list_height = list_bottom - list_top + 1
|
||||
if self.selected_index < self.scroll_offset:
|
||||
self.scroll_offset = self.selected_index
|
||||
elif self.selected_index >= self.scroll_offset + list_height:
|
||||
self.scroll_offset = self.selected_index - list_height + 1
|
||||
|
||||
# def _push_search_to_history(self) -> None:
|
||||
# debug("In _push_search_to_history")
|
||||
# if self.search_query.strip() and (
|
||||
# not self.search_history
|
||||
# or self.search_history[-1] != self.search_query.strip()
|
||||
# ):
|
||||
# self.search_history.append(self.search_query.strip())
|
||||
# self.history_index = -1
|
||||
|
||||
def _draw(self) -> None:
|
||||
# debug("In _draw")
|
||||
self.cmd.clear_screen()
|
||||
rows, cols = self.screen_rows, self.screen_cols
|
||||
list_top = 1
|
||||
list_bottom = rows - 2
|
||||
list_height = list_bottom - list_top + 1
|
||||
|
||||
# debug(f"in draw: rows: {rows}, cols: {cols}")
|
||||
|
||||
# Column widths (visible chars only). Four segments, three spaces between.
|
||||
w_vt = 2
|
||||
remaining = cols - w_vt - 3
|
||||
w_title = max(12, int(remaining * 0.45))
|
||||
w_foreground = max(10, int(remaining * 0.35))
|
||||
w_cwd = max(8, remaining - w_title - w_foreground)
|
||||
|
||||
# Header: plain string of exactly cols visible characters, then style once
|
||||
header_plain = (
|
||||
"VT".ljust(w_vt)
|
||||
+ " "
|
||||
+ "Title".ljust(w_title)
|
||||
+ " "
|
||||
+ "Process".ljust(w_foreground)
|
||||
+ " "
|
||||
+ "CWD".ljust(w_cwd)
|
||||
)
|
||||
self.cmd.set_cursor_position(0, 0)
|
||||
self.write(styled(header_plain, bold=True) + "\r\n")
|
||||
|
||||
# List
|
||||
q = self.line_edit.current_input.strip().lower()
|
||||
visible = self.filtered_rows[
|
||||
self.scroll_offset : self.scroll_offset + list_height
|
||||
]
|
||||
|
||||
def segment_plain(content: str, width: int, add_dots: bool) -> tuple[str, int]:
|
||||
"""Return (padded segment string, content_len for highlighting)."""
|
||||
if len(content) > width:
|
||||
if add_dots and width >= 2:
|
||||
content = content[: width - 2] + ".."
|
||||
else:
|
||||
content = content[:width]
|
||||
content_len = len(content)
|
||||
return content.ljust(width), content_len
|
||||
|
||||
for i, row in enumerate(visible):
|
||||
y = list_top + i
|
||||
global_idx = self.scroll_offset + i
|
||||
is_selected = global_idx == self.selected_index
|
||||
|
||||
# Build each segment as plain text with exact visible width (no SGR yet)
|
||||
vt_plain, vt_content_len = segment_plain(
|
||||
row.vt or "-", w_vt, add_dots=False
|
||||
)
|
||||
title_plain, title_content_len = segment_plain(
|
||||
row.title, w_title, add_dots=True
|
||||
)
|
||||
fg_plain, fg_content_len = segment_plain(
|
||||
row.foreground, w_foreground, add_dots=True
|
||||
)
|
||||
cwd_display = format_cwd_like_zsh(row.cwd)
|
||||
cwd_plain, cwd_content_len = segment_plain(
|
||||
cwd_display, w_cwd, add_dots=True
|
||||
)
|
||||
|
||||
# Apply match highlighting (inserts SGR; visible length unchanged)
|
||||
if q:
|
||||
vt_styled = highlight_matches(
|
||||
vt_plain,
|
||||
get_fuzzy_match_indices(q, row.vt or ""),
|
||||
vt_content_len,
|
||||
)
|
||||
title_styled = highlight_matches(
|
||||
title_plain,
|
||||
get_fuzzy_match_indices(q, row.title),
|
||||
title_content_len,
|
||||
)
|
||||
fg_styled = highlight_matches(
|
||||
fg_plain,
|
||||
get_fuzzy_match_indices(q, row.foreground),
|
||||
fg_content_len,
|
||||
)
|
||||
cwd_styled = highlight_matches(
|
||||
cwd_plain,
|
||||
get_fuzzy_match_indices(q, cwd_display),
|
||||
cwd_content_len,
|
||||
)
|
||||
else:
|
||||
vt_styled = vt_plain
|
||||
title_styled = title_plain
|
||||
fg_styled = fg_plain
|
||||
cwd_styled = cwd_plain
|
||||
|
||||
# Concatenate segments with single spaces; total visible length is fixed
|
||||
line_plain = (
|
||||
vt_styled + " " + title_styled + " " + fg_styled + " " + cwd_styled
|
||||
)
|
||||
|
||||
# If selected, wrap entire line in reverse (no slicing of output)
|
||||
if is_selected:
|
||||
rev = styled(line_plain, reverse=True)
|
||||
line_out = rev if rev else "\033[7m" + line_plain + "\033[27m"
|
||||
else:
|
||||
line_out = line_plain
|
||||
|
||||
self.cmd.set_cursor_position(0, y)
|
||||
self.write(line_out)
|
||||
self.cmd.clear_to_eol()
|
||||
self.write("\r\n")
|
||||
|
||||
# Search line
|
||||
self.cmd.set_cursor_position(0, rows - 1)
|
||||
self.line_edit.write(
|
||||
self.write, prompt="Filter: ", screen_cols=self.screen_size.cols
|
||||
)
|
||||
# prompt = "Filter: "
|
||||
# self.write(prompt + self.search_query)
|
||||
# self.cmd.clear_to_eol()
|
||||
# self.cmd.set_cursor_position(len(prompt) + self.search_cursor, rows - 1)
|
||||
# debug(
|
||||
# "At end of draw, flushing, current_input: {self.line_edit.current_input}, cursor_pos: {self.line_edit.cursor_pos}"
|
||||
# )
|
||||
self.flush()
|
||||
|
||||
# def on_key_event(
|
||||
# self, key_event: KeyEventType, in_bracketed_paste: bool = False
|
||||
# ) -> None:
|
||||
# debug(f"Got key event: {key_event}")
|
||||
# if key_event.text:
|
||||
# self._on_search_input(key_event.text)
|
||||
# return
|
||||
# if self.perform_default_key_action(key_event):
|
||||
# return
|
||||
# self.on_key(key_event)
|
||||
#
|
||||
def on_key(self, key_event) -> None:
|
||||
# debug(f"In on_key with key_event: {key_event}")
|
||||
if self.line_edit.on_key(key_event):
|
||||
# debug(f"Handled by line_edit: {self.line_edit}")
|
||||
self.selected_index = 0
|
||||
self._clamp_selection()
|
||||
self._apply_filter()
|
||||
self._draw()
|
||||
return
|
||||
|
||||
if key_event.type is EventType.RELEASE:
|
||||
return
|
||||
match key_event.key:
|
||||
case "ESCAPE":
|
||||
self.on_interrupt()
|
||||
return
|
||||
case "UP":
|
||||
self.selected_index = self.selected_index - 1
|
||||
self._clamp_selection()
|
||||
self._draw()
|
||||
return
|
||||
case "DOWN":
|
||||
self.selected_index = self.selected_index + 1
|
||||
self._clamp_selection()
|
||||
self._draw()
|
||||
return
|
||||
case "PAGE_UP":
|
||||
list_height = self.screen_rows - 3
|
||||
self.selected_index = max(0, self.selected_index - list_height)
|
||||
self._clamp_selection()
|
||||
self._draw()
|
||||
return
|
||||
case "PAGE_DOWN":
|
||||
list_height = self.screen_rows - 3
|
||||
self.selected_index = min(
|
||||
len(self.filtered_rows) - 1, self.selected_index + list_height
|
||||
)
|
||||
self._clamp_selection()
|
||||
self._draw()
|
||||
return
|
||||
case "ENTER":
|
||||
self._select_current()
|
||||
return
|
||||
# if key_event.matches("ctrl+up"):
|
||||
# self._history_prev()
|
||||
# return
|
||||
# if key_event.matches("ctrl+down"):
|
||||
# self._history_next()
|
||||
# return
|
||||
# Line editing in search box
|
||||
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
# debug(f"In on_text with text: {text}, in_bracketed_paste: {in_bracketed_paste}")
|
||||
self.line_edit.on_text(text, in_bracketed_paste)
|
||||
# debug(
|
||||
# f"After line_edit.on_text, current_input: {self.line_edit.current_input}, cursor_pos: {self.line_edit.cursor_pos}"
|
||||
# )
|
||||
self.selected_index = 0
|
||||
self._clamp_selection()
|
||||
self._apply_filter()
|
||||
self._draw()
|
||||
|
||||
# def _history_prev(self) -> None:
|
||||
# debug("In _history_prev")
|
||||
# if not self.search_history:
|
||||
# return
|
||||
# if self.history_index <= 0:
|
||||
# if self.search_query.strip():
|
||||
# self.search_history.append(self.search_query.strip())
|
||||
# self.history_index = len(self.search_history) - 1
|
||||
# else:
|
||||
# self.history_index -= 1
|
||||
# self.search_query = self.search_history[self.history_index]
|
||||
# self.search_cursor = len(self.search_query)
|
||||
# self._apply_filter()
|
||||
# self._draw()
|
||||
#
|
||||
# def _history_next(self) -> None:
|
||||
# debug("In _history_next")
|
||||
# if not self.search_history or self.history_index < 0:
|
||||
# return
|
||||
# self.history_index += 1
|
||||
# if self.history_index >= len(self.search_history):
|
||||
# self.history_index = -1
|
||||
# self.search_query = ""
|
||||
# else:
|
||||
# self.search_query = self.search_history[self.history_index]
|
||||
# self.search_cursor = len(self.search_query)
|
||||
# self._apply_filter()
|
||||
# self._draw()
|
||||
#
|
||||
def _select_current(self) -> None:
|
||||
# debug("In _select_current")
|
||||
if not self.filtered_rows:
|
||||
return
|
||||
row = self.filtered_rows[self.selected_index]
|
||||
self.selected_tab_result = {
|
||||
"tab_id": row.tab_id,
|
||||
"os_window_id": row.os_window_id,
|
||||
"index": row.index_in_os_window,
|
||||
}
|
||||
self.quit_loop(0)
|
||||
|
||||
def on_interrupt(self) -> None:
|
||||
debug("In on_interrupt")
|
||||
self.quit_loop(1)
|
||||
|
||||
on_eot = on_interrupt
|
||||
|
||||
|
||||
@kitten_ui(allow_remote_control=True)
|
||||
def main(args: list[str]) -> str:
|
||||
debug("In select_tab.py main")
|
||||
cp = main.remote_control(["ls"], capture_output=True)
|
||||
if cp.returncode != 0:
|
||||
debug(f"Remote control error {cp.returncode}: {cp.stderr}")
|
||||
sys.stderr.buffer.write(cp.stderr or b"")
|
||||
raise SystemExit(cp.returncode)
|
||||
ls_data = json.loads(cp.stdout.decode())
|
||||
tab_rows = gather_tabs_from_ls(ls_data)
|
||||
if not tab_rows:
|
||||
debug("No tab rows?")
|
||||
raise SystemExit(1)
|
||||
debug(f"Have tab rows {pprint.pformat(tab_rows)}")
|
||||
handler = TabSelectHandler(tab_rows)
|
||||
debug(f"Created handler: {handler}")
|
||||
sys.stdout.flush()
|
||||
loop = Loop()
|
||||
loop.loop(handler)
|
||||
debug(f"Loop exited, selected_tab_result: {handler.selected_tab_result}")
|
||||
|
||||
return json.dumps(handler.selected_tab_result)
|
||||
|
||||
|
||||
# @result_handler(has_ready_notification=True)
|
||||
def handle_result(
|
||||
args: list[str],
|
||||
input: str,
|
||||
target_window_id: int,
|
||||
boss: Boss,
|
||||
) -> None:
|
||||
data = json.loads(input) if input else None
|
||||
print("In handle_result with data:", data)
|
||||
if not data:
|
||||
return
|
||||
tab_id = data.get("tab_id")
|
||||
os_window_id = data.get("os_window_id")
|
||||
if tab_id is None:
|
||||
return
|
||||
# Use Boss API: match tab by id, focus its OS window if needed, then make it active
|
||||
tabs = list(boss.match_tabs(f"id:{tab_id}"))
|
||||
if not tabs:
|
||||
return
|
||||
tab = tabs[0]
|
||||
# If the tab is in a different OS window, focus that window first
|
||||
if os_window_id is not None and hasattr(boss, "focus_os_window"):
|
||||
try:
|
||||
boss.focus_os_window(os_window_id)
|
||||
except Exception:
|
||||
pass
|
||||
tab.make_active()
|
||||
@@ -1,5 +0,0 @@
|
||||
hostname goliath
|
||||
share_connections yes
|
||||
# optional niceties:
|
||||
# copy .config/nvim .config/tmux
|
||||
# interpreter /usr/bin/fish
|
||||
@@ -1,103 +0,0 @@
|
||||
from kittens.tui.handler import result_handler
|
||||
from kittens.tui.handler import kitten_ui
|
||||
|
||||
# from kittens.tui.loop import debug
|
||||
from kitty.boss import Boss
|
||||
from kitty.rc.base import MatchError
|
||||
from kitty.tabs import Tab, TabManager
|
||||
from kitty.fast_data_types import swap_tabs
|
||||
# from pprint import pprint
|
||||
|
||||
|
||||
def get_tab_vt(tab: Tab) -> str | None:
|
||||
"""Get the 'vt' user var from a tab's windows, or None if not set."""
|
||||
for window in tab:
|
||||
vt = window.user_vars.get("vt")
|
||||
if vt is not None:
|
||||
return vt
|
||||
return None
|
||||
|
||||
|
||||
def reorder_tabs_by_vt(tm: TabManager) -> None:
|
||||
"""Reorder tabs so those without 'vt' come first, then those with 'vt' sorted by value."""
|
||||
tabs = tm.tabs
|
||||
if len(tabs) <= 1:
|
||||
return
|
||||
|
||||
# Build list of (tab, vt_value) tuples
|
||||
tab_vt_pairs: list[tuple[Tab, str | None]] = [
|
||||
(tab, get_tab_vt(tab)) for tab in tabs
|
||||
]
|
||||
|
||||
# Sort: tabs without vt first (None sorts before strings with key trick),
|
||||
# then tabs with vt sorted by vt value
|
||||
def sort_key(pair: tuple[Tab, str | None]) -> tuple[int, int, str]:
|
||||
tab, vt = pair
|
||||
if vt is None:
|
||||
return (0, 0, "") # No vt: sort first
|
||||
# Try numeric sort first, fall back to string sort
|
||||
try:
|
||||
return (1, int(vt), "") # Numeric vt
|
||||
except ValueError:
|
||||
return (2, 0, vt) # Non-numeric vt: sort after numeric, by string
|
||||
|
||||
sorted_pairs = sorted(tab_vt_pairs, key=sort_key)
|
||||
sorted_tabs = [pair[0] for pair in sorted_pairs]
|
||||
|
||||
# Check if already in correct order
|
||||
if sorted_tabs == tabs:
|
||||
return
|
||||
|
||||
# Reorder by swapping to target positions
|
||||
# We'll do a simple bubble-sort style swap to get tabs in place
|
||||
for target_idx, target_tab in enumerate(sorted_tabs):
|
||||
current_idx = tabs.index(target_tab)
|
||||
while current_idx > target_idx:
|
||||
# Swap with previous position
|
||||
tabs[current_idx], tabs[current_idx - 1] = (
|
||||
tabs[current_idx - 1],
|
||||
tabs[current_idx],
|
||||
)
|
||||
swap_tabs(tm.os_window_id, current_idx, current_idx - 1)
|
||||
current_idx -= 1
|
||||
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
|
||||
# @kitten_ui(allow_remote_control=True)
|
||||
def main(args: list[str]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@result_handler(no_ui=True)
|
||||
def handle_result(
|
||||
args: list[str], answer: str, target_window_id: int, boss: Boss
|
||||
) -> None:
|
||||
target_id = args[1]
|
||||
# pprint(("target_id: ", target_id))
|
||||
# pprint(("args: ", args))
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
tm = boss.active_tab_manager_with_dispatch
|
||||
if tm is None:
|
||||
raise RuntimeError("No tab manager?")
|
||||
|
||||
tabs = list(boss.match_tabs(f"var:vt=^{target_id}$"))
|
||||
if len(tabs) > 0:
|
||||
tm.set_active_tab(tabs[0])
|
||||
return
|
||||
# pprint(("tm", tm, "tabs", list(tabs)))
|
||||
if w is not None:
|
||||
try:
|
||||
boss.call_remote_control(
|
||||
w, ("focus-tab", "--match", f"var:vt=^{target_id}$")
|
||||
)
|
||||
except MatchError:
|
||||
# pprint("No match, need to create tab")
|
||||
boss.call_remote_control(
|
||||
w, ("launch", "--type", "tab", "--var", f"vt={target_id}", *args[2:])
|
||||
)
|
||||
reorder_tabs_by_vt(tm)
|
||||
tabs = list(boss.match_tabs(f"var:vt=^{target_id}$"))
|
||||
if len(tabs) > 0:
|
||||
tm.set_active_tab(tabs[0])
|
||||
return
|
||||
@@ -1 +0,0 @@
|
||||
themes/cyberdream.conf
|
||||
@@ -1,252 +0,0 @@
|
||||
# ── tab_bar.py · kitty tab titles: program icon + repo dir (or nvim's file) ──
|
||||
#
|
||||
# kitty loads this file from the config dir and exposes `draw_title` as the
|
||||
# `{custom}` field of tab_title_template (kitty/tab_bar.py:
|
||||
# load_custom_draw_title). kitty still draws the powerline chrome; only the text
|
||||
# inside a tab comes from here.
|
||||
#
|
||||
# A title is `<index> <icon> <label>`:
|
||||
# 2 chezmoi repo the tab's cwd belongs to (dir name if no repo)
|
||||
# 3 astrocore.lua an editor: the file it is editing
|
||||
# 4 chezmoi a known program: its icon, repo as the label
|
||||
#
|
||||
# No subprocesses, no timers. The repo root is found by walking up for `.git`;
|
||||
# the icon comes from the foreground process' cmdline; the edited file comes from
|
||||
# the title nvim already sets (~/.config/nvim/lua/plugins/astrocore.lua,
|
||||
# custom_title autocmd: "<file> | <org/repo>" / "<file> | Nvim", and
|
||||
# "<org/repo>" / "Nvim" with no file). kitty repaints the tab bar when a window
|
||||
# title changes, so buffer switches and program changes show up on their own.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
# Nerd Font glyphs; VictorMono Nerd Font Mono covers every codepoint used here
|
||||
# (checked with `fc-list ":charset=<hex>:family=VictorMono Nerd Font Mono"`).
|
||||
FOLDER = '\uf07b' # nf-fa-folder — the fallback: "a shell somewhere"
|
||||
EDITOR = '\ue62b' # nf-custom-neovim
|
||||
STACKED = '\U000f0328' # nf-md-layers — stack layout is hiding the tab's other panes
|
||||
|
||||
ICONS: dict[str, str] = {
|
||||
# editors (label becomes the edited file, see EDITORS below)
|
||||
'nvim': EDITOR,
|
||||
'vim': EDITOR,
|
||||
'nvim-qt': EDITOR,
|
||||
'hx': EDITOR,
|
||||
'helix': EDITOR,
|
||||
# agents / harnesses
|
||||
'claude': '\U000f06a9', # nf-md-robot
|
||||
'omp': '\uf2db', # nf-fa-microchip
|
||||
'opencode': '\uf121', # nf-fa-code
|
||||
'crush': '\uf0e7', # nf-fa-bolt
|
||||
'codex': '\uf120', # nf-fa-terminal
|
||||
'gemini': '\U000f0674', # nf-md-creation
|
||||
'aider': '\U000f0068', # nf-md-auto_fix
|
||||
# git
|
||||
'lazygit': '\uf1d3', # nf-fa-git
|
||||
'git': '\uf1d3',
|
||||
'gitui': '\uf1d3',
|
||||
'tig': '\uf1d3',
|
||||
'gh': '\uf113', # nf-fa-github_alt
|
||||
# containers
|
||||
'lazydocker': '\uf308', # nf-linux-docker
|
||||
'docker': '\uf308',
|
||||
'docker-compose': '\uf308',
|
||||
'podman': '\uf308',
|
||||
'kubectl': '\U000f10fe', # nf-md-kubernetes
|
||||
# multiplexers / terminals-in-terminals
|
||||
'herdr': '\uf009', # nf-fa-th_large — panes
|
||||
'tmux': '\uf00a', # nf-fa-th
|
||||
# files, monitors, history
|
||||
'yazi': '\uf07c', # nf-fa-folder_open
|
||||
'ranger': '\uf07c',
|
||||
'lf': '\uf07c',
|
||||
'btop': '\uf0e4', # nf-fa-dashboard
|
||||
'htop': '\uf0e4',
|
||||
'top': '\uf0e4',
|
||||
'atuin': '\uf1da', # nf-fa-history
|
||||
'atuin-pick': '\uf1da',
|
||||
'ssh': '\uf0ac', # nf-fa-globe
|
||||
'chezmoi': '\uf013', # nf-fa-cog
|
||||
# runtimes / build / data
|
||||
'python': '\ue235', # nf-dev-python
|
||||
'python3': '\ue235',
|
||||
'ipython': '\ue235',
|
||||
'node': '\ue718', # nf-dev-nodejs_small
|
||||
'bun': '\ue718',
|
||||
'deno': '\ue718',
|
||||
'cargo': '\ue7a8', # nf-dev-rust
|
||||
'rustc': '\ue7a8',
|
||||
'make': '\uf085', # nf-fa-gears
|
||||
'just': '\uf085',
|
||||
'psql': '\uf1c0', # nf-fa-database
|
||||
'sqlite3': '\uf1c0',
|
||||
'duckdb': '\uf1c0',
|
||||
'man': '\uf02d', # nf-fa-book
|
||||
'less': '\uf02d',
|
||||
'bat': '\uf02d',
|
||||
'glow': '\uf02d',
|
||||
}
|
||||
|
||||
# These get the edited file as their label instead of the repo.
|
||||
EDITORS = frozenset({'nvim', 'vim', 'nvim-qt', 'hx', 'helix'})
|
||||
|
||||
# Skipped when reading a cmdline, so `node .../claude` is claude and
|
||||
# `uv run python x.py` is python — unless nothing else is left, in which case the
|
||||
# interpreter itself is the program (a bare `python` REPL).
|
||||
INTERPRETERS = frozenset({
|
||||
'sh', 'bash', 'zsh', 'fish', 'env', 'sudo', 'doas', 'nohup', 'setsid',
|
||||
'node', 'bun', 'deno', 'npx', 'uv', 'uvx', 'python', 'python3',
|
||||
})
|
||||
|
||||
# Subcommands of the runners above ("uv run python x.py"), skipped only after a
|
||||
# runner has been skipped so a program genuinely called `run` still resolves.
|
||||
RUNNER_WORDS = frozenset({'run', 'exec', 'x', 'tool', '--'})
|
||||
|
||||
MAX_LEN = 28
|
||||
|
||||
# wd -> repo root (or ''), memoized: a working dir keeps its answer for the life
|
||||
# of the kitty instance, so `git init` in an already-open dir needs a config
|
||||
# reload (ctrl+shift+f5) to show up.
|
||||
_repo: dict[str, str] = {}
|
||||
|
||||
|
||||
def draw_title(data: dict[str, Any]) -> str:
|
||||
return _title(data) + _stacked(data)
|
||||
|
||||
|
||||
def _title(data: dict[str, Any]) -> str:
|
||||
tab = data['tab']
|
||||
program = _program(_cmdlines(data['tab_id']), tab.active_exe)
|
||||
index = data['index']
|
||||
if program in EDITORS:
|
||||
edited = _nvim_file(data['title'])
|
||||
if edited:
|
||||
return f'{index} {ICONS[program]} {_elide(edited)}'
|
||||
icon = ICONS.get(program, FOLDER)
|
||||
wd = tab.active_wd
|
||||
label = ''
|
||||
if wd:
|
||||
label = '~' if wd == os.path.expanduser('~') else os.path.basename(_repo_root(wd) or wd.rstrip('/'))
|
||||
return f'{index} {icon} {_elide(label or data["title"])}'
|
||||
|
||||
|
||||
def _stacked(data: dict[str, Any]) -> str:
|
||||
"""` <layers icon> 3` when the stack layout (super+z) shows one of 3 panes, else ''.
|
||||
|
||||
kitty marks the tab bar dirty on every layout change, so toggling repaints this.
|
||||
"""
|
||||
panes = data['num_window_groups']
|
||||
return f' {STACKED} {panes}' if data['layout_name'] == 'stack' and panes > 1 else ''
|
||||
|
||||
|
||||
def _cmdlines(tab_id: int) -> list[list[str]]:
|
||||
"""argv of every foreground process of the tab's active window.
|
||||
|
||||
All of them, not just `foreground_cmdline`: an editor commonly has LSP/agent
|
||||
children in the same process group, and kitty's single "process for cwd" can
|
||||
be either one of them.
|
||||
"""
|
||||
from kitty.boss import get_boss
|
||||
|
||||
try:
|
||||
tab = get_boss().tab_for_id(tab_id)
|
||||
window = tab.active_window if tab is not None else None
|
||||
if window is None:
|
||||
return []
|
||||
return [list(p['cmdline'] or ()) for p in window.child.foreground_processes]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _program(cmdlines: list[list[str]], fallback_exe: str) -> str:
|
||||
"""Program a tab should be iconified by, across its foreground processes.
|
||||
|
||||
An editor wins over anything else (nvim with a node LSP child is nvim), then
|
||||
the first process with an icon, then whatever the first process is.
|
||||
"""
|
||||
names = [name for cmdline in cmdlines if (name := _resolve(cmdline))]
|
||||
for name in names:
|
||||
if name in EDITORS:
|
||||
return name
|
||||
for name in names:
|
||||
if name in ICONS:
|
||||
return name
|
||||
return names[0] if names else _canonical(os.path.basename(fallback_exe or '').lower())
|
||||
|
||||
|
||||
def _resolve(cmdline: list[str]) -> str:
|
||||
"""Program name of one cmdline.
|
||||
|
||||
Walks past runners and flags: `node …/claude` is claude, an unknown script
|
||||
keeps its runtime's icon (`uv run python train.py` is python, `bun run dev`
|
||||
is bun), and a bare `python` stays python.
|
||||
"""
|
||||
runtime = ''
|
||||
skipped = False
|
||||
for arg in cmdline:
|
||||
if arg.startswith('-'):
|
||||
continue
|
||||
name = os.path.basename(arg).lower()
|
||||
if name.endswith(('.js', '.mjs', '.cjs', '.py', '.ts')):
|
||||
name = name.rsplit('.', 1)[0]
|
||||
if not name:
|
||||
continue
|
||||
name = _canonical(name)
|
||||
if name in INTERPRETERS or (skipped and name in RUNNER_WORDS):
|
||||
skipped = True
|
||||
runtime = runtime or (name if name in ICONS else '')
|
||||
continue
|
||||
return name if name in ICONS or not runtime else runtime
|
||||
return runtime
|
||||
|
||||
|
||||
def _canonical(name: str) -> str:
|
||||
"""`omp-linux-x64` -> `omp`, `docker-compose` stays itself."""
|
||||
if name in ICONS:
|
||||
return name
|
||||
for known in ICONS:
|
||||
if name.startswith(known + '-'):
|
||||
return known
|
||||
return name
|
||||
|
||||
|
||||
def _repo_root(wd: str) -> str:
|
||||
root = _repo.get(wd)
|
||||
if root is None:
|
||||
root = ''
|
||||
path = wd
|
||||
while True:
|
||||
if os.path.exists(os.path.join(path, '.git')):
|
||||
root = path
|
||||
break
|
||||
parent = os.path.dirname(path)
|
||||
if parent == path:
|
||||
break
|
||||
path = parent
|
||||
_repo[wd] = root
|
||||
return root
|
||||
|
||||
|
||||
def _nvim_file(title: str) -> str:
|
||||
"""The file name out of an editor window title, '' when it is editing none.
|
||||
|
||||
Handles this config's "<file> | <org/repo>" / "<file> | Nvim" and nvim's own
|
||||
default "%t - Nvim" / "<file> (<dir>) - NVIM".
|
||||
"""
|
||||
text = title.strip()
|
||||
for suffix in (' - NVIM', ' - Nvim', ' - nvim'):
|
||||
if text.endswith(suffix):
|
||||
text = text[: -len(suffix)]
|
||||
text = text.split(' | ', 1)[0].strip()
|
||||
if text.endswith(')') and ' (' in text:
|
||||
text = text.split(' (', 1)[0].strip()
|
||||
# Left with a bare repo slug ("org/repo") or the placeholder: no file open.
|
||||
if not text or '/' in text or text.lower() == 'nvim':
|
||||
return ''
|
||||
return text
|
||||
|
||||
|
||||
def _elide(label: str) -> str:
|
||||
return label if len(label) <= MAX_LEN else label[: MAX_LEN - 1] + '…'
|
||||
@@ -1,27 +0,0 @@
|
||||
# cyberdream theme for kitty
|
||||
background #16181a
|
||||
foreground #ffffff
|
||||
cursor #ffffff
|
||||
cursor_text_color #16181a
|
||||
selection_background #3c4048
|
||||
color0 #16181a
|
||||
color8 #3c4048
|
||||
color1 #ff6e5e
|
||||
color9 #ff6e5e
|
||||
color2 #5eff6c
|
||||
color10 #5eff6c
|
||||
color3 #f1ff5e
|
||||
color11 #f1ff5e
|
||||
color4 #5ea1ff
|
||||
color12 #5ea1ff
|
||||
color5 #bd5eff
|
||||
color13 #bd5eff
|
||||
color6 #5ef1ff
|
||||
color14 #5ef1ff
|
||||
color7 #ffffff
|
||||
color15 #ffffff
|
||||
selection_foreground #ffffff
|
||||
active_tab_foreground #000000
|
||||
active_tab_background #ffbd5e
|
||||
inactive_tab_foreground #ffffff
|
||||
inactive_tab_background #16181a
|
||||
@@ -1,74 +0,0 @@
|
||||
# vim:ft=kitty
|
||||
|
||||
## name: Koda Dark
|
||||
## author: Koda
|
||||
## license: MIT
|
||||
|
||||
|
||||
|
||||
# The basic colors
|
||||
foreground #ffffff
|
||||
background #101010
|
||||
selection_foreground #b0b0b0
|
||||
selection_background #272727
|
||||
|
||||
# Cursor colors
|
||||
cursor #b0b0b0
|
||||
cursor_text_color #101010
|
||||
|
||||
# Scrollbar colors
|
||||
scrollbar_handle_color #b0b0b0
|
||||
scrollbar_track_color #272727
|
||||
|
||||
# URL color when hovering with mouse
|
||||
url_color #d9ba73
|
||||
|
||||
# Kitty window border colors
|
||||
active_border_color #ffffff
|
||||
inactive_border_color #777777
|
||||
bell_border_color #d9ba73
|
||||
|
||||
# OS Window titlebar colors
|
||||
wayland_titlebar_color system
|
||||
macos_titlebar_color system
|
||||
|
||||
# Tab bar colors
|
||||
active_tab_foreground #ffffff
|
||||
active_tab_background #101010
|
||||
inactive_tab_foreground #777777
|
||||
inactive_tab_background #272727
|
||||
tab_bar_background #272727
|
||||
|
||||
# Colors for marks (marked text in the terminal)
|
||||
mark1_foreground #121212
|
||||
mark1_background #b3b3b3
|
||||
mark2_foreground #121212
|
||||
mark2_background #f4b8e4
|
||||
mark3_foreground #121212
|
||||
mark3_background #5abfb5
|
||||
|
||||
# The 16 terminal colors
|
||||
|
||||
color0 #101010
|
||||
color8 #666666
|
||||
|
||||
color1 #ff5733
|
||||
color9 #ff7676
|
||||
|
||||
color2 #86cd82
|
||||
color10 #a3d6a3
|
||||
|
||||
color3 #d9ba73
|
||||
color11 #ffffff
|
||||
|
||||
color12 #666666
|
||||
color4 #b3b3b3
|
||||
|
||||
color5 #f2a4db
|
||||
color13 #f4b8e4
|
||||
|
||||
color6 #5abfb5
|
||||
color14 #fafafa
|
||||
|
||||
color7 #a5adce
|
||||
color15 #b5bfe2
|
||||
@@ -1,72 +0,0 @@
|
||||
# vim:ft=kitty
|
||||
|
||||
## name: Koda Light
|
||||
## author: Koda
|
||||
## license: MIT
|
||||
|
||||
|
||||
|
||||
# The basic colors
|
||||
foreground #101010
|
||||
background #faf9f5
|
||||
selection_foreground #101010
|
||||
selection_background #e2e2e2
|
||||
|
||||
# Cursor colors
|
||||
cursor #6a6a6a
|
||||
cursor_text_color #ebebeb
|
||||
|
||||
# Scrollbar colors
|
||||
scrollbar_handle_color #101010
|
||||
scrollbar_track_color #e2e2e2
|
||||
|
||||
# URL color when hovering with mouse
|
||||
url_color #b07700
|
||||
|
||||
# Kitty window border colors
|
||||
active_border_color #000000
|
||||
inactive_border_color #6a6a6a
|
||||
bell_border_color #b07700
|
||||
|
||||
# OS Window titlebar colors
|
||||
wayland_titlebar_color system
|
||||
macos_titlebar_color system
|
||||
|
||||
# Tab bar colors
|
||||
active_tab_foreground #101010
|
||||
active_tab_background #faf9f5
|
||||
inactive_tab_foreground #969ba5
|
||||
inactive_tab_background #e2e2e2
|
||||
tab_bar_background #ebebeb
|
||||
|
||||
# Colors for marks (marked text in the terminal)
|
||||
mark1_foreground #faf9f5
|
||||
mark1_background #3d3d3d
|
||||
mark2_foreground #faf9f5
|
||||
mark2_background #a200d1
|
||||
mark3_foreground #faf9f5
|
||||
mark3_background #007d7d
|
||||
|
||||
color0 #e2e2e2
|
||||
color8 #ababab
|
||||
|
||||
color1 #ca0043
|
||||
color9 #f30052
|
||||
|
||||
color2 #407f00
|
||||
color10 #4f9a00
|
||||
|
||||
color3 #926200
|
||||
color11 #b07700
|
||||
|
||||
color4 #3d3d3d
|
||||
color12 #000000
|
||||
|
||||
color5 #a200d1
|
||||
color13 #c301fb
|
||||
|
||||
color6 #007d7d
|
||||
color14 #009797
|
||||
|
||||
color7 #6a6a6a
|
||||
color15 #101010
|
||||
@@ -1,100 +0,0 @@
|
||||
# ── tmux.conf · kitty as a tmux host ──
|
||||
# Kitty is a dumb terminal here: tmux owns panes, windows and sessions.
|
||||
# Every structural chord from kitty.conf is re-sent as a tmux key sequence
|
||||
# (prefix ctrl+b == byte \x02); an uppercase letter after the prefix byte is
|
||||
# the shift variant (\x02H == prefix+shift+h). Actions tmux has no binding
|
||||
# for run as out-of-band `tmux` client commands (ctrl+shift+g/e/p).
|
||||
# Launched from the default config with ctrl+shift+t.
|
||||
|
||||
# ── base / appearance (kitty.conf parity) ──
|
||||
font_family VictorMono Nerd Font
|
||||
bold_italic_font auto
|
||||
window_padding_width 8
|
||||
hide_window_decorations yes
|
||||
background_opacity 0.9
|
||||
copy_on_select auto
|
||||
strip_trailing_spaces smart
|
||||
confirm_os_window_close 0
|
||||
allow_remote_control socket-only
|
||||
listen_on unix:@kitty-tmux
|
||||
cursor_trail 10
|
||||
cursor_trail_start_threshold 0
|
||||
cursor_trail_decay 0.01 0.05
|
||||
cursor_blink_interval 0
|
||||
|
||||
# tmux renders its own status line (tmux-dotbar, top) — no kitty chrome
|
||||
tab_bar_style hidden
|
||||
|
||||
# one tmux session per kitty window: cold start boots the server and runs
|
||||
# tmux-resurrect restore, warm start claims the oldest detached session
|
||||
shell kitty-tmux
|
||||
|
||||
# kitty must not shadow tmux, so every kitty default shortcut is dropped and
|
||||
# only what tmux cannot do is re-added below
|
||||
clear_all_shortcuts yes
|
||||
|
||||
# ── kitty-level keys (no tmux equivalent) ──
|
||||
map ctrl+shift+c copy_to_clipboard
|
||||
map ctrl+shift+v paste_from_clipboard
|
||||
map ctrl+plus change_font_size current +1
|
||||
map ctrl+minus change_font_size current -1
|
||||
map ctrl+0 change_font_size current 0
|
||||
map ctrl+shift+alt+, load_config_file
|
||||
map ctrl+shift+a launch --type=background --cwd=current kitty --config ~/.config/kitty/herdr.conf
|
||||
|
||||
# ── kitty tabs → tmux windows ──
|
||||
map super+t send_text all \x02t
|
||||
map super+enter send_text all \x02t
|
||||
map ctrl+shift+t send_text all \x02t
|
||||
map ctrl+shift+h send_text all \x02H
|
||||
map ctrl+shift+k send_text all \x02L
|
||||
map alt+q send_text all \x021
|
||||
map alt+w send_text all \x022
|
||||
map alt+e send_text all \x023
|
||||
map alt+r send_text all \x024
|
||||
map alt+t send_text all \x025
|
||||
map alt+y send_text all \x026
|
||||
map alt+u send_text all \x027
|
||||
map alt+i send_text all \x028
|
||||
map alt+o send_text all \x029
|
||||
|
||||
# ── kitty splits → tmux panes ──
|
||||
map ctrl+shift+j send_text all \x02"
|
||||
map ctrl+shift+l send_text all \x02%
|
||||
map ctrl+enter send_text all \x02%
|
||||
map ctrl+shift+f send_text all \x02z
|
||||
map ctrl+shift+w send_text all \x02x
|
||||
map super+w send_text all \x02x
|
||||
# resize: prefix + ctrl+arrow == tmux resize-pane (repeatable)
|
||||
map super+left send_text all \x02\x1b[1;5D
|
||||
map super+right send_text all \x02\x1b[1;5C
|
||||
map super+up send_text all \x02\x1b[1;5A
|
||||
map super+down send_text all \x02\x1b[1;5B
|
||||
|
||||
# ctrl+h/j/k/l are tmux's vim-aware pane navigation and ctrl+space is
|
||||
# tmux-palette — both no-prefix tmux binds, so kitty must not grab them.
|
||||
# No maps for those live in this config (splits stay on ctrl+shift+j/l).
|
||||
|
||||
# ── launchers: no tmux bind exists for these ──
|
||||
# Driving the prefix command prompt (\x02: … \r) does NOT work: kitty writes
|
||||
# the whole chord in one pty write and tmux drops the buffered bytes when the
|
||||
# prompt opens. Run the tmux client command out-of-band instead; with no -c
|
||||
# it targets the current client, i.e. this kitty window.
|
||||
map ctrl+shift+g launch --type=background tmux display-popup -w 90% -h 90% -E -d "#{pane_current_path}" lazygit
|
||||
map ctrl+shift+e launch --type=background tmux new-window -c "#{pane_current_path}" nvim
|
||||
map ctrl+shift+p launch --type=background tmux split-window -v -l 25% -c "#{pane_current_path}" atuin-pick
|
||||
|
||||
# ── sessions ──
|
||||
# a new kitty OS window runs kitty-tmux again == its own tmux session
|
||||
map super+n new_os_window
|
||||
map ctrl+shift+space new_os_window
|
||||
map super+d send_text all \x02d
|
||||
|
||||
# BEGIN_KITTY_FONTS
|
||||
font_family family="VictorMono Nerd Font Mono"
|
||||
bold_font auto
|
||||
italic_font auto
|
||||
bold_italic_font auto
|
||||
# END_KITTY_FONTS
|
||||
# Colors + transparency from the active Omarchy theme (last include wins)
|
||||
include ~/.local/state/omarchy/current/theme/kitty.conf
|
||||
@@ -1,97 +0,0 @@
|
||||
# ~/.config/kitty/mywatcher.py
|
||||
from kitty.tabs import TabManager
|
||||
from typing import Any
|
||||
|
||||
from kitty.boss import Boss
|
||||
from kitty.window import Window
|
||||
|
||||
# from pprint import pprint
|
||||
|
||||
# Kitty currently ships with Python 3.12, but os.ptsname was added in 3.13.
|
||||
import ctypes
|
||||
|
||||
libc = ctypes.CDLL("libc.so.6")
|
||||
libc.ptsname.restype = ctypes.c_char_p
|
||||
|
||||
updating = False
|
||||
|
||||
|
||||
def get_title(window: Window, title: str) -> str:
|
||||
vt = "n/a"
|
||||
try:
|
||||
vt = window.user_vars["vt"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
ptsname = libc.ptsname(window.child.child_fd).decode()
|
||||
ptsname = ptsname.removeprefix("/dev/")
|
||||
|
||||
return f"{title} [{vt} {ptsname}]"
|
||||
|
||||
|
||||
def on_title_change(boss: Boss, window: Window, data: dict[str, Any]) -> None:
|
||||
# pprint(("title change", "window", window, "data", data))
|
||||
tm = boss.active_tab_manager_with_dispatch
|
||||
if tm is not None:
|
||||
update_title(boss, window, tm)
|
||||
|
||||
|
||||
def on_tab_bar_dirty(boss: Boss, window: Window, data: dict[str, Any]) -> None:
|
||||
# called when any changes happen to the tab bar, such a new tabs being
|
||||
# created, tab titles changing, tabs moving, etc. Useful to display the
|
||||
# tab bar externally to kitty. This is called even if the tab bar is
|
||||
# hidden. Note that this is called only in *global watchers*, that is
|
||||
# watchers defined in kitty.conf or using the --watcher command line
|
||||
# flag. data contains tab_manager which is the object responsible for
|
||||
# managing all tabs in a single OS Window.
|
||||
# return get_title(window, data["title"])
|
||||
tm = data["tab_manager"]
|
||||
update_title(boss, window, tm)
|
||||
|
||||
|
||||
def update_title(boss: Boss, window: Window, tm: TabManager) -> None:
|
||||
# pprint(("update title", "window", window))
|
||||
global updating
|
||||
|
||||
if updating:
|
||||
# pprint("already updating, skipping")
|
||||
return
|
||||
|
||||
tab = tm.active_tab
|
||||
if tab is None:
|
||||
return
|
||||
|
||||
title = get_title(window, window.child_title)
|
||||
if title != tab.effective_title:
|
||||
updating = True
|
||||
# pprint(
|
||||
# (
|
||||
# "title",
|
||||
# title,
|
||||
# "tab.effective title",
|
||||
# tab.effective_title,
|
||||
# "window.title",
|
||||
# window.title,
|
||||
# "window.child_title",
|
||||
# window.child_title,
|
||||
# )
|
||||
# )
|
||||
window.set_title(title)
|
||||
updating = False
|
||||
else:
|
||||
pass
|
||||
# pprint(
|
||||
# (
|
||||
# "no update",
|
||||
# "title",
|
||||
# title,
|
||||
# "tab.effective title",
|
||||
# tab.effective_title,
|
||||
# "window.title",
|
||||
# window.title,
|
||||
# "window.child_title",
|
||||
# window.child_title,
|
||||
# )
|
||||
# )
|
||||
|
||||
return
|
||||
@@ -39,6 +39,7 @@ inode/directory=io.github.lgse.Strata.desktop
|
||||
x-scheme-handler/fastmail=com.fastmail.Fastmail.desktop
|
||||
x-scheme-handler/grokbot=grok-bot.desktop
|
||||
x-scheme-handler/sand=grok-bot.desktop
|
||||
x-scheme-handler/tern=so.stencil.tern.url.desktop
|
||||
|
||||
[Added Associations]
|
||||
x-scheme-handler/http=brave-origin.desktop;userapp-Floorp-8MTFP3.desktop;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// This configuration is merged with the default vicinae configuration file, which you can obtain by running the `vicinae config default` command.
|
||||
// Every item defined in this file takes precedence over the values defined in the default config or any other imported file.
|
||||
//
|
||||
// You can make manual edits to this file, however you should keep in mind that this file may be written to by vicinae when a configuration change is made through the GUI.
|
||||
// When that happens, any custom comments or formatting will be lost.
|
||||
//
|
||||
// If you want to maintain a configuration file with your own comments and formatting, you should create a separate file and add it to the 'imports' array.
|
||||
//
|
||||
// Learn more about configuration at https://docs.vicinae.com/config
|
||||
|
||||
{
|
||||
"$schema": "https://vicinae.com/schemas/config.json",
|
||||
"close_on_focus_loss": true,
|
||||
"pop_to_root_on_close": true,
|
||||
"escape_key_behavior": "",
|
||||
"keybinding": "vim",
|
||||
"global_shortcuts": {
|
||||
"toggle": "super+SPACE"
|
||||
},
|
||||
"font": {
|
||||
"rendering": "native",
|
||||
"normal": {
|
||||
"family": "VictorMono NFP",
|
||||
"size": 9.5
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"dark": {
|
||||
"icon_theme": "Yaru"
|
||||
}
|
||||
},
|
||||
"launcher_window": {
|
||||
"compact_mode": {
|
||||
"enabled": false
|
||||
},
|
||||
"floating_status_bar": false
|
||||
},
|
||||
"providers": {
|
||||
"@aurelleb/store.vicinae.chromium-bookmarks": {
|
||||
"entrypoints": {
|
||||
"search-bookmarks": {
|
||||
"shortcut": "super+control+shift+O"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@codingcodax/store.vicinae.omarchy-menu": {
|
||||
"entrypoints": {
|
||||
"index": {
|
||||
"shortcut": "alt+SPACE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@fbosch/store.vicinae.nerdfont-search": {
|
||||
"entrypoints": {
|
||||
"nerdfonts-search": {
|
||||
"alias": "nf"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rithvikvibhu/store.vicinae.arch-packages": {
|
||||
"entrypoints": {
|
||||
"search-arch-packages": {
|
||||
"alias": "aur"
|
||||
}
|
||||
}
|
||||
},
|
||||
"applications": {
|
||||
"entrypoints": {
|
||||
"1password": {
|
||||
"alias": "pass"
|
||||
},
|
||||
"photocraft": {
|
||||
"alias": "photoshop"
|
||||
},
|
||||
"vectorcraft": {
|
||||
"alias": "illustrator"
|
||||
}
|
||||
}
|
||||
},
|
||||
"clipboard": {
|
||||
"entrypoints": {
|
||||
"history": {
|
||||
"shortcut": "super+P"
|
||||
}
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"entrypoints": {
|
||||
"search-emojis": {
|
||||
"shortcut": "super+shift+SPACE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"manage-shortcuts": {
|
||||
"entrypoints": {
|
||||
"create": {
|
||||
"alias": "cql"
|
||||
}
|
||||
}
|
||||
},
|
||||
"power": {
|
||||
"entrypoints": {
|
||||
"power-off": {
|
||||
"alias": "shut down"
|
||||
},
|
||||
"reboot": {
|
||||
"alias": "restart"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"entrypoints": {
|
||||
"sct-a5e51f1bbe34": {
|
||||
"alias": "streams"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"entrypoints": {
|
||||
"browse-apps": {
|
||||
"alias": "apps",
|
||||
"enabled": true,
|
||||
"shortcut": "super+A"
|
||||
},
|
||||
"run": {
|
||||
"alias": "sh"
|
||||
},
|
||||
"set-default-browser": {
|
||||
"enabled": false
|
||||
},
|
||||
"set-default-terminal": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user