mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 19:32:43 +00:00
feat(config): add program icons and improved title logic to kitty tab bar
This commit is contained in:
1 parent
745c18852c
commit
703730a4b4
2 files changed
+152
-25
No files matched your search
Binary file not shown.
+152
-25
@@ -1,52 +1,179 @@
|
|||||||
# ── tab_bar.py · kitty tab titles: repo dir, or the file nvim is editing ──
|
# ── 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
|
# kitty loads this file from the config dir and exposes `draw_title` as the
|
||||||
# `{custom}` field of tab_title_template (kitty/tab_bar.py:
|
# `{custom}` field of tab_title_template (kitty/tab_bar.py:
|
||||||
# load_custom_draw_title). kitty still draws the powerline chrome; only the text
|
# load_custom_draw_title). kitty still draws the powerline chrome; only the text
|
||||||
# inside a tab comes from here.
|
# inside a tab comes from here.
|
||||||
#
|
#
|
||||||
# A title is one of:
|
# A title is `<index> <icon> <label>`:
|
||||||
# 2 chezmoi <- repo the tab's cwd belongs to (dir name if no repo)
|
# 2 chezmoi repo the tab's cwd belongs to (dir name if no repo)
|
||||||
# 3 astrocore.lua <- nvim is the active process: the file it is editing
|
# 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`,
|
# No subprocesses, no timers. The repo root is found by walking up for `.git`;
|
||||||
# and the edited file comes from the window title nvim already sets
|
# the icon comes from the foreground process' cmdline; the edited file comes from
|
||||||
# (~/.config/nvim/lua/plugins/astrocore.lua, custom_title autocmd:
|
# the title nvim already sets (~/.config/nvim/lua/plugins/astrocore.lua,
|
||||||
# "<file> | <org/repo>" or "<file> | Nvim", and "<org/repo>" / "Nvim" with no
|
# custom_title autocmd: "<file> | <org/repo>" / "<file> | Nvim", and
|
||||||
# file). kitty repaints the tab bar whenever that title changes, so the tab
|
# "<org/repo>" / "Nvim" with no file). kitty repaints the tab bar when a window
|
||||||
# tracks buffer switches for free.
|
# title changes, so buffer switches and program changes show up on their own.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
# Nerd Font glyphs; VictorMono Nerd Font Mono covers both.
|
# Nerd Font glyphs; VictorMono Nerd Font Mono covers every codepoint used here
|
||||||
FOLDER = '\uf07b' # nf-fa-folder
|
# (checked with `fc-list ":charset=<hex>:family=VictorMono Nerd Font Mono"`).
|
||||||
NVIM = '\ue62b' # nf-custom-neovim
|
FOLDER = '\uf07b' # nf-fa-folder — the fallback: "a shell somewhere"
|
||||||
|
EDITOR = '\ue62b' # nf-custom-neovim
|
||||||
|
|
||||||
|
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': '\U000f09da', # nf-md-brain
|
||||||
|
'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': '\ue7b2', # nf-dev-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', '--'})
|
||||||
|
|
||||||
EDITORS = frozenset({'nvim', 'vim', 'nvim-qt'})
|
|
||||||
MAX_LEN = 28
|
MAX_LEN = 28
|
||||||
|
|
||||||
# wd -> repo root (or ''), memoized: a working dir keeps its answer for the life
|
# 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
|
# of the kitty instance, so `git init` in an already-open dir needs a config
|
||||||
# reload (ctrl+shift+alt+, in the herdr host, ctrl+shift+f5 here) to show up.
|
# reload (ctrl+shift+f5) to show up.
|
||||||
_repo: dict[str, str] = {}
|
_repo: dict[str, str] = {}
|
||||||
|
|
||||||
|
|
||||||
def draw_title(data: dict[str, Any]) -> str:
|
def draw_title(data: dict[str, Any]) -> str:
|
||||||
tab = data['tab']
|
tab = data['tab']
|
||||||
label = ''
|
program = _program(_cmdline(data['tab_id']), tab.active_exe)
|
||||||
if tab.active_exe in EDITORS:
|
index = data['index']
|
||||||
label = _nvim_file(data['title'])
|
if program in EDITORS:
|
||||||
if label:
|
edited = _nvim_file(data['title'])
|
||||||
return f'{data["index"]} {NVIM} {_elide(label)}'
|
if edited:
|
||||||
|
return f'{index} {ICONS[program]} {_elide(edited)}'
|
||||||
|
icon = ICONS.get(program, FOLDER)
|
||||||
wd = tab.active_wd
|
wd = tab.active_wd
|
||||||
|
label = ''
|
||||||
if wd:
|
if wd:
|
||||||
label = os.path.basename(_repo_root(wd) or wd.rstrip('/'))
|
label = '~' if wd == os.path.expanduser('~') else os.path.basename(_repo_root(wd) or wd.rstrip('/'))
|
||||||
if wd == os.path.expanduser('~'):
|
return f'{index} {icon} {_elide(label or data["title"])}'
|
||||||
label = '~'
|
|
||||||
return f'{data["index"]} {FOLDER} {_elide(label or data["title"])}'
|
|
||||||
|
def _cmdline(tab_id: int) -> list[str]:
|
||||||
|
"""argv of the tab's active window's foreground process, [] if unavailable."""
|
||||||
|
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
|
||||||
|
return list(window.child.foreground_cmdline) if window is not None else []
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _program(cmdline: list[str], fallback_exe: str) -> str:
|
||||||
|
"""Program name a tab should be iconified by.
|
||||||
|
|
||||||
|
Walks the cmdline 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. Falls back
|
||||||
|
to kitty's own exe for the active window when the cmdline is unreadable.
|
||||||
|
"""
|
||||||
|
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 or _canonical(os.path.basename(fallback_exe or '').lower())
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
def _repo_root(wd: str) -> str:
|
||||||
@@ -67,7 +194,7 @@ def _repo_root(wd: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _nvim_file(title: str) -> str:
|
def _nvim_file(title: str) -> str:
|
||||||
"""The file name out of an nvim window title, '' when it is editing none.
|
"""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
|
Handles this config's "<file> | <org/repo>" / "<file> | Nvim" and nvim's own
|
||||||
default "%t - Nvim" / "<file> (<dir>) - NVIM".
|
default "%t - Nvim" / "<file> (<dir>) - NVIM".
|
||||||
|
|||||||
Reference in new issue
Block a user