config(kitty,yazi,lazygit,lazydocker,gh): add universal tui tools from live home

kitty full config (theme symlink preserved, VictorMono NF). yazi opener
OS-branched via template (open vs xdg-open). yazi/lazygit refreshed to
live. lazydocker default empty config. gh config.yml as private_ —
hosts.yml excluded (oauth token).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Prad NukalaandClaude Fable 5 committed 2026-08-09 14:53:13 -04:00
1 parent f30802426d
commit 3c691e8dc5
24 files changed
+1426 -8

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
# The current version of the config schema
version: 1
# What protocol to use when performing git operations. Supported values: ssh, https
git_protocol: https
# What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment.
editor:
# When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled
prompt: enabled
# Preference for editor-based interactive prompting. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled
prefer_editor_prompt: disabled
# A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager.
pager:
# Aliases allow you to create nicknames for gh commands
aliases:
co: pr checkout
# The path to a unix socket through which to send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport.
http_unix_socket:
# What web browser gh should use when opening URLs. If blank, will refer to environment.
browser:
# Whether to display labels using their RGB hex color codes in terminals that support truecolor. Supported values: enabled, disabled
color_labels: disabled
# Whether customizable, 4-bit accessible colors should be used. Supported values: enabled, disabled
accessible_colors: disabled
# Whether an accessible prompter should be used. Supported values: enabled, disabled
accessible_prompter: disabled
# Whether to use a animated spinner as a progress indicator. If disabled, a textual progress indicator is used instead. Supported values: enabled, disabled
spinner: enabled
+28
View File
@@ -0,0 +1,28 @@
include ssh.conf
listen_on unix:@kitty-goliath
# open straight into goliath's tmux
shell ssh -t 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
+90
View File
@@ -0,0 +1,90 @@
# ── base.conf · shared ──
font_size 13.5
font_family VictorMono Nerd Font
bold_italic_font auto
window_padding_width 8
hide_window_decorations yes
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
cursor_trail 10
cursor_trail_start_threshold 0
cursor_trail_decay 0.01 0.05
cursor_blink_interval 0
# real socket is @kitty-local-<PID>; scripts must use $KITTY_LISTEN_ON, not this name
listen_on unix:@kitty-local
enabled_layouts splits:split_axis=vertical
# 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=35
# panes = kitty windows
map super+t new_tab
map super+enter new_tab_with_cwd
map super+n new_os_window
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_mn yazi
map ctrl+shift+e launch_tb nvim
map ctrl+shift+a launch_vs omp
# 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
# 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
tab_title_template " {index}"
active_tab_title_template " {title}"
# BEGIN_KITTY_FONTS
font_family family="VictorMono Nerd Font Mono"
bold_font auto
italic_font auto
bold_italic_font auto
# END_KITTY_FONTS
include themes/noctalia.conf
+16
View File
@@ -0,0 +1,16 @@
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])
+55
View File
@@ -0,0 +1,55 @@
import re
from kittens.tui.handler import result_handler
from kitty.key_encoding import KeyEvent, parse_shortcut
PASSTHROUGH = r'\b(vim|nvim|fzf)\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)
+628
View File
@@ -0,0 +1,628 @@
#!/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()
+5
View File
@@ -0,0 +1,5 @@
hostname goliath
share_connections yes
# optional niceties:
# copy .config/nvim .config/tmux
# interpreter /usr/bin/fish
+103
View File
@@ -0,0 +1,103 @@
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
@@ -0,0 +1 @@
themes/noctalia.conf
+74
View File
@@ -0,0 +1,74 @@
# 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
+72
View File
@@ -0,0 +1,72 @@
# 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
+32
View File
@@ -0,0 +1,32 @@
color0 #371b36
color1 #fd4663
color2 #ef5ce9
color3 #ed4592
color4 #ed4c45
color5 #f48bef
color6 #f48bbb
color7 #f3f2f3
color8 #716270
color9 #fd4663
color10 #ef5ce9
color11 #ed4592
color12 #ed4c45
color13 #f48bef
color14 #f48bbb
color15 #f3f2f3
cursor #f3f2f3
cursor_text_color #291428
background #291428
foreground #f3f2f3
selection_foreground #b6afb6
selection_background #371b36
active_border_color #ef5ce9
inactive_border_color #371b36
url_color #ef5ce9
active_tab_foreground #251824
active_tab_background #ef5ce9
inactive_tab_foreground #b6afb6
inactive_tab_background #371b36
cursor_trail_color #b6afb6
+97
View File
@@ -0,0 +1,97 @@
# ~/.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
Whitespace-only changes.
+2 -2
View File
@@ -439,7 +439,7 @@ keybinding:
commitMenu: <c-o>
customCommands:
- key: "<c-i>"
command: "ltui"
command: "gitv {{.Repository.Owner}}/{{.Repository.Name}}"
context: "global"
output: terminal
- key: "<c-w>"
@@ -468,7 +468,7 @@ customCommands:
description: "Smart commit with AI"
context: "files"
command: "gh commit"
output: log
output: terminal
- key: "o"
description: "start ai pull request"
command: "gh pr create --fill -w"
-1
View File
@@ -1 +0,0 @@
/home/prad/.config/dotstate/storage/Vertical-Rogz_AppleStudio/.config/tmux/tmux.conf
+135
View File
@@ -0,0 +1,135 @@
# ── Plugins ──────────────────────────────────────────────────────────────
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'vaaleyard/tmux-dotbar'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'joshmedeski/tmux-nerd-font-window-name'
set -g @plugin 'omerxx/tmux-sessionx'
set -g @plugin 'tmux-plugins/tmux-continuum' # keep last
set -g @plugin 'eduwass/tmux-palette'
# ── Kitty compatibility ──────────────────────────────────────────────────
set-option -g renumber-windows on
set -g base-index 1
setw -g pane-base-index 1
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",xterm-kitty:Tc"
set -as terminal-features ",xterm-kitty:RGB:usstyle:hyperlinks:clipboard:extkeys"
set -g extended-keys always
set -gq extended-keys-format csi-u
set-option -g status-position top
# ── Status bar ─────────────────────────────────────────────────────────────────
# Dotbar
set -g @tmux-dotbar-position top
set -g @tmux-dotbar-session-position "right"
set -g @tmux-dotbar-rounded true
set -g @tmux-dotbar-bg "#0B0E14"
set -g @tmux-dotbar-fg "#475266"
set -g @tmux-dotbar-fg-current "#BFBDB6"
set -g @tmux-dotbar-fg-session "#565B66"
set -g @tmux-dotbar-fg-prefix "#95E6CB"
set -g @tmux-dotbar-justify "absolute-centre"
set -g @tmux-dotbar-left "true"
set -g @tmux-dotbar-right "false"
set -g @tmux-dotbar-session-position "left"
set -g @tmux-dotbar-session-text "  "
set -g @tmux-dotbar-status-right-text " %H:%M "
set -g @tmux-dotbar-window-status-format " #W "
set -g @tmux-dotbar-window-status-separator " • "
set -g @tmux-dotbar-maximized-icon "󰊓"
set -g @tmux-dotbar-show-maximized-icon-for-all-tabs false
set -g @tmux-dotbar-bold-status false
set -g @tmux-dotbar-bold-current-window false
set -g @tmux-dotbar-ssh-enabled true
set -g @tmux-dotbar-ssh-icon '󰌘'
set -g @tmux-dotbar-ssh-icon-only false
# Styling
set -g status-style fg=default,bg=default
set -g popup-style 'bg=default'
set -g popup-border-style 'bg=default,fg=#5c5c66'
set -g popup-border-lines rounded
set -g @sessionx-additional-options "--color 'bg:-1,bg+:-1,gutter:-1,border:-1,preview-bg:-1'"
# Kitty graphics protocol passthrough (yazi / image preview)
set -g allow-passthrough on
set -ga update-environment TERM
set -ga update-environment TERM_PROGRAM
# ── Clipboard (Wayland / Niri) ───────────────────────────────────────────
set -g set-clipboard on # OSC 52 — covers ssh'd sessions
set -g mouse on
setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-pipe-and-cancel "wl-copy"
bind -T copy-mode-vi Y send -X copy-pipe-and-cancel "wl-copy" \; send -X cancel
bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-and-cancel "wl-copy"
# ── Sessions ─────────────────────────────────────────────────────────────
set -g @resurrect-strategy-nvim 'session'
set -g @resurrect-capture-pane-contents 'on'
set -g @resurrect-processes 'ssh psql mysql sqlite3 purple lazygit lazydocker "claude->claude --continue" opencode'
set -g @continuum-save-interval '10'
set -g @continuum-restore 'off' # restore is owned by kitty-tmux launcher
set -g @sessionx-bind 'o'
set -g @sessionx-zoxide-mode 'on'
# ── Titles → Niri active window ──────────────────────────────────────────
set-option -g set-titles on
set-option -g set-titles-string "#T (#S)"
set-option -g status-interval 1
set-window-option -g automatic-rename on
# ── QoL ──────────────────────────────────────────────────────────────────
set -g history-limit 50000
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
# ── Keybinds ─────────────────────────────────────────────────────────────
bind -n S-Enter send-keys Escape '[13;2u'
# New window: on @ssh_host sessions (set by the Vicinae tmux-sessions
# extension) connect to the same remote; otherwise inherit the local cwd.
# The remote host is read at runtime — tmux does NOT format-expand a
# new-window shell-command, so "#{@ssh_host}" there would be taken literally.
bind-key t if-shell -F '#{@ssh_host}' \
'new-window "ssh $(tmux show-options -v @ssh_host)"' \
'new-window -c "#{pane_current_path}"'
bind-key [ previous-window
bind-key ] next-window
bind-key H previous-window
bind-key L next-window
set -g @palette-key 'C-Space' # optional, default: C-Space (no-prefix)
set -g @palette-find-pane-key 'M-f' # optional, no binding by default
set -g @palette-move-pane-key 'M-m' # optional, no binding by default
# Splits inherit cwd (kitty super+t / super+n land here)
# vsplit: new pane right at 35% width · hsplit: new pane below at 25% height
bind % split-window -h -l 30% -c "#{pane_current_path}"
bind '"' split-window -v -l 25% -c "#{pane_current_path}"
# Copy-mode entry (default prefix+[ is rebound above)
bind Escape copy-mode
# ── Pane navigation: ctrl+hjkl, no prefix ────────────────────────────────
# Vim-aware: if the pane runs (n)vim, forward the key so vim handles its
# own window movement (pair with vim-tmux-navigator in AstroNvim).
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|l?n?vim?x?|fzf)(diff)?$'"
bind -n C-h if-shell "$is_vim" 'send-keys C-h' 'select-pane -L'
bind -n C-j if-shell "$is_vim" 'send-keys C-j' 'select-pane -D'
bind -n C-k if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind -n C-l if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
# Same nav from inside copy-mode
bind -T copy-mode-vi C-h select-pane -L
bind -T copy-mode-vi C-j select-pane -D
bind -T copy-mode-vi C-k select-pane -U
bind -T copy-mode-vi C-l select-pane -R
# Clear-screen escape hatch (C-l is consumed above)
bind C-l send-keys C-l
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'
+21
View File
@@ -1 +1,22 @@
require("sshfs"):setup()
require("bunny"):setup({
hops = {
{ key = "/", path = "/", },
{ key = "t", path = "/tmp", },
{ key = "~", path = "~", desc = "Home" },
{ key = "m", path = "~/Music", desc = "Music" },
{ key = "d", path = "~/Desktop", desc = "Desktop" },
{ key = "D", path = "~/Developer", desc = "Developer" },
{ key = "c", path = "~/.config", desc = "Config files" },
{ key = { "l", "s" }, path = "~/.local/share", desc = "Local share" },
{ key = { "l", "b" }, path = "~/.local/bin", desc = "Local bin" },
{ key = { "l", "t" }, path = "~/.local/state", desc = "Local state" },
-- key and path attributes are required, desc is optional
},
desc_strategy = "path", -- If desc isn't present, use "path" or "filename", default is "path"
ephemeral = true, -- Enable ephemeral hops, default is true
tabs = true, -- Enable tab hops, default is true
notify = false, -- Notify after hopping, default is false
fuzzy_cmd = "fzf", -- Fuzzy searching command, default is "fzf"
})
+15
View File
@@ -8,6 +8,21 @@ on = [ "g", "r" ]
run = 'shell -- ya emit cd "$(git rev-parse --show-toplevel)"'
desc = "Go to git root"
[[mgr.prepend_keymap]]
on = [ "g", "C" ]
run = 'shell -- ya emit cd "$HOME/.local/share/chezmoi"'
desc = "Go to chezmoi root"
[[mgr.prepend_keymap]]
desc = "Start bunny.yazi"
on = ";"
run = "plugin bunny"
# Optionally, add another keymap to immediately fuzzy search bookmarks
[[mgr.prepend_keymap]]
desc = "Start bunny.yazi fuzzy search"
on = "'"
run = "plugin bunny fuzzy"
[[mgr.prepend_keymap]]
on = [ "g", "d"]
+10
View File
@@ -33,5 +33,15 @@ use = "cap153/tv"
rev = "b6b1f12"
hash = "4bdf0523d3711d20982ad80b061dedf2"
[[plugin.deps]]
use = "stelcodes/bunny"
rev = "71b14a3"
hash = "18a2829738214fe9bca99a2fef19113d"
[[plugin.deps]]
use = "coder0x6675/osc7"
rev = "ae73046"
hash = "4f73595837992eeefcb525c0a95b92ba"
[flavor]
deps = []
+4 -1
View File
@@ -13,12 +13,15 @@ prepend_dirs = [
{ name = "wltbs", text = "󱃪" },
{ name = "Shared", text = "" },
{ name = "Templates", text = "" },
{name = "env", text = ""},
{name = "go", text = "󰟓"},
{name = "js", text = ""},
{name = "dart", text = ""},
{name = "python", text = ""},
{name = "py", text = ""},
{name = "rs", text = ""},
{name = "tex", text = ""},
{name = "md", text = ""},
{name = "docs", text = ""},
{name = "prad.codes", text = ""}
]
@@ -1,3 +1,6 @@
[mgr]
ratio = [0, 2, 6]
[opener]
play = [
{ run = "mpv %s", orphan = true, for = "unix" },
@@ -6,7 +9,7 @@ edit = [
{ run = "nvim %s", block = true, for = "unix" },
]
open = [
{ run = "xdg-open %s1", desc = "Open" },
{ run = "{{ if eq .chezmoi.os "darwin" }}open{{ else }}xdg-open{{ end }} %s1", desc = "Open" },
]
[open]