From ce9886c2e7dfcf79bbbced95f41d64212916517f Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Thu, 8 Oct 2026 11:29:07 -0400 Subject: [PATCH] chore(config): update terminal, tmux, and starship configurations --- dot_config/fish/conf.d/secrets.fish | 1 + dot_config/ghostty/config | 83 ++++++++----- dot_config/kitty/kitty.conf | 2 +- .../hooks/theme-set.d/executable_tmux-tabs | 7 ++ dot_config/omarchy/themed/tmux.conf.tpl | 7 ++ dot_config/starship.toml | 6 + dot_config/tmux/executable_tab-label.sh | 24 ++++ .../tmux/tmux-nerd-font-window-name.yml | 12 +- dot_config/tmux/tmux.conf | 111 ++++++++++++++++-- dot_zshrc | 33 +++++- 10 files changed, 236 insertions(+), 50 deletions(-) create mode 100755 dot_config/omarchy/hooks/theme-set.d/executable_tmux-tabs create mode 100644 dot_config/omarchy/themed/tmux.conf.tpl create mode 100755 dot_config/tmux/executable_tab-label.sh diff --git a/dot_config/fish/conf.d/secrets.fish b/dot_config/fish/conf.d/secrets.fish index 3a461da..c062951 100644 --- a/dot_config/fish/conf.d/secrets.fish +++ b/dot_config/fish/conf.d/secrets.fish @@ -1,6 +1,7 @@ # Secrets — gitignored. Do NOT commit. Rotate anything that ever was committed. set -gx PGDATABASE 'postgresql://prad:dv@localhost:5432/devdb' +set -gx HF_TOKEN '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' set -gx GITLAB_TOKEN 'glpat-CUmtwRHJl1gkFZKXnOPiEG86MQp1OjEH.01.0w0405m5v' set -gx OPPER_API_KEY 'op-5S93K4PP2AIQXVY1Y1Q5' set -gx OPENAI_API_KEY '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' diff --git a/dot_config/ghostty/config b/dot_config/ghostty/config index 4dd305d..959ca08 100644 --- a/dot_config/ghostty/config +++ b/dot_config/ghostty/config @@ -1,6 +1,7 @@ # Font Configuration -font-size = 13 -font-family = Victor Mono NF +font-size = 11 +font-family = IoskeleyMonoTerm Nerd Font Mono +adjust-cell-height = 15% background-blur = true # Window Configuration @@ -24,46 +25,70 @@ confirm-close-surface = false # Disable annoying copied to clipboard app-notifications = no-clipboard-copy,no-config-reload -# Key bindings for common actions -keybind = cmd+c=copy_to_clipboard -keybind = cmd+v=paste_from_clipboard -keybind = cmd+n=new_window +# 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 +keybind = shift+enter=text:\n + +# 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+e=text:\x02h +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 = ctrl+shift+enter=text:\x02z +keybind = ctrl+shift+z=text:\x02z + +# scrollback, prompt, help +keybind = ctrl+shift+f=text:\x02/ +keybind = ctrl+shift+page_up=text:\x02[ +keybind = ctrl+shift+p=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 and tab attaches to the tmux session "main", creating it -# when missing, so closing Ghostty detaches instead of killing the shells. -command = tmux new-session -A -s main +# 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 -keybind = shift+enter=text:\n # Rando stuff gtk-single-instance = true - - -# ── Herdr sync: Ctrl+Shift chords → prefix (\x02 = ctrl+b) bytes ── -# Later keybind lines overwrite earlier/default ones; keep these LAST. -# reload_config moves off ctrl+shift+, (taken by Herdr settings below) -keybind = ctrl+shift+alt+,=reload_config -keybind = ctrl+shift+n=text:\x02% -keybind = ctrl+shift+t=text:\x02c -keybind = ctrl+tab=text:\x02n -keybind = ctrl+shift+tab=text:\x02p -keybind = ctrl+shift+f=text:\x02s -keybind = ctrl+shift+l=text:\x02. -keybind = ctrl+shift+h=text:\x02, -keybind = ctrl+shift+j=text:\x02j -keybind = ctrl+shift+k=text:\x02k -keybind = super+enter=text:\x02N -keybind = super+r=text:\x02W -keybind = ctrl+shift+r=text:\x02T -keybind = ctrl+shift+,=text:\x02S diff --git a/dot_config/kitty/kitty.conf b/dot_config/kitty/kitty.conf index b9b16f0..dc843c8 100644 --- a/dot_config/kitty/kitty.conf +++ b/dot_config/kitty/kitty.conf @@ -1,5 +1,5 @@ # ── base.conf · shared ── -font_family VictorMono Nerd Font +font_family IoskeleyMono Nerd Font Term font_size 13.0 bold_italic_font auto window_padding_width 8 diff --git a/dot_config/omarchy/hooks/theme-set.d/executable_tmux-tabs b/dot_config/omarchy/hooks/theme-set.d/executable_tmux-tabs new file mode 100755 index 0000000..3e4fde7 --- /dev/null +++ b/dot_config/omarchy/hooks/theme-set.d/executable_tmux-tabs @@ -0,0 +1,7 @@ +#!/bin/bash + +# Recolor tmux's window tabs on theme change, as kitty's tab bar does: re-read +# the @tab-* colors rendered from ~/.config/omarchy/themed/tmux.conf.tpl. +# tmux.conf's status formats read those options live; the bar picks them up +# on its next redraw (status-interval 5). +tmux source-file -q ~/.local/state/omarchy/current/theme/tmux.conf 2>/dev/null || true diff --git a/dot_config/omarchy/themed/tmux.conf.tpl b/dot_config/omarchy/themed/tmux.conf.tpl new file mode 100644 index 0000000..f803248 --- /dev/null +++ b/dot_config/omarchy/themed/tmux.conf.tpl @@ -0,0 +1,7 @@ +# tmux window-tab colors, rendered per theme into +# ~/.local/state/omarchy/current/theme/tmux.conf and sourced by tmux.conf. +# Same derivation as the kitty tab bar in kitty.conf.tpl. +set -g @tab-bg "{{ background }}" +set -g @tab-fg "{{ muted }}" +set -g @tab-active-bg "{{ mix accent background 80% }}" +set -g @tab-active-fg "{{ accent }}" diff --git a/dot_config/starship.toml b/dot_config/starship.toml index 4a6be9f..7d0d9e7 100644 --- a/dot_config/starship.toml +++ b/dot_config/starship.toml @@ -17,6 +17,12 @@ add_newline = true right_format = """$time""" +# zsh transient prompt (~/.zshrc). `starship prompt --profile` emits +# zsh-escaped output; `starship module` doesn't, which breaks RPROMPT width. +[profiles] +transient = "$character" +transient_right = "$time" + [directory] style = "blue" diff --git a/dot_config/tmux/executable_tab-label.sh b/dot_config/tmux/executable_tab-label.sh new file mode 100755 index 0000000..e24888e --- /dev/null +++ b/dot_config/tmux/executable_tab-label.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# ~/.config/tmux/tab-label.sh — the non-editor tab label, kitty tab_bar.py's +# rule: the repo the pane's cwd belongs to (dir name if no repo, ~ at home). +# Editors (the open file) are handled in tmux.conf's automatic-rename-format. +# Args: $1 pane_current_path $2 window_id + +path=$1 window=$2 + +if [[ $path == "$HOME" ]]; then + label='~' +else + dir=$path + while [[ -n $dir && ! -e $dir/.git ]]; do dir=${dir%/*}; done + root=${dir:-$path} + label=${root##*/} +fi +printf '%s' "$label" + +# tmux runs #() jobs async and only re-checks a window's name on pane output, +# so after a `cd` the tab would keep the old label until the next keystroke. +# Re-arming automatic-rename makes tmux re-check now and pick up this output. +if [[ -n $window && $(tmux display -p -t "$window" '#W' 2>/dev/null) != *"${label:0:26}"* ]]; then + tmux set -w -t "$window" automatic-rename on >/dev/null 2>&1 & +fi diff --git a/dot_config/tmux/tmux-nerd-font-window-name.yml b/dot_config/tmux/tmux-nerd-font-window-name.yml index 13e0d59..17ed711 100644 --- a/dot_config/tmux/tmux-nerd-font-window-name.yml +++ b/dot_config/tmux/tmux-nerd-font-window-name.yml @@ -1,14 +1,14 @@ config: - fallback-icon: "\U000F08D9" # show when no definition is found + fallback-icon: "󰣙" # show when no definition is found multi-pane-icon: "" # show when window has multiple panes (blank by default) - show-name: true # show the window name with the icon (defaults to false) + show-name: false # the label comes from automatic-rename-format in tmux.conf (cwd, as in kitty) icon-position: "left" # show the icon to the "left" or "right" of the window name (defaults to left) icons: - mprocs: "\U000F012A" + mprocs: "󰄪" mm: "" macmon: "" make: "" - zsh: "\U000F07B7" + zsh: "󰞷" nvim: "" devbox: "" psql: "" @@ -16,11 +16,11 @@ icons: lazygit: "" gh: "" tv: "" - docker: "\U000F0868" + docker: "󰡨" bun: "" node: "" npm: "" pnpm: "" go: "" - bunx: "\U000F012A" + bunx: "󰄪" claude: "󱜙" diff --git a/dot_config/tmux/tmux.conf b/dot_config/tmux/tmux.conf index 69c82c2..7d369f7 100644 --- a/dot_config/tmux/tmux.conf +++ b/dot_config/tmux/tmux.conf @@ -3,6 +3,10 @@ set -g prefix C-Space set -g prefix2 C-b bind -N "Send prefix" C-Space send-prefix +set-option -g renumber-windows on +set -g base-index 1 +setw -g pane-base-index 1 + # Config and help bind -N "Reload configuration" q source-file ~/.config/tmux/tmux.conf \; display "Configuration reloaded" bind -N "Show Tmux keybindings" ? display-popup -E -w 80% -h 70% -T "Tmux keybindings" "omarchy-menu-tmux-keybindings --print | less -R" @@ -11,6 +15,7 @@ bind -N "Show Tmux keybindings" ? display-popup -E -w 80% -h 70% -T "Tmux keybin setw -g mode-keys vi bind -N "Begin selection" -T copy-mode-vi v send -X begin-selection bind -N "Copy selection" -T copy-mode-vi y send -X copy-selection-and-cancel +bind -N "Search scrollback" / copy-mode \; command-prompt -p "search up:" "send -X search-backward '%%'" # Pane Controls bind -N "Split pane vertically" -n M-Enter split-window -v -c "#{pane_current_path}" @@ -26,6 +31,23 @@ bind -N "Focus pane right" -n C-M-Right select-pane -R bind -N "Focus pane up" -n C-M-Up select-pane -U bind -N "Focus pane down" -n C-M-Down select-pane -D +# ctrl+h/j/k/l: vim-aware pane navigation, the tmux half of nvim's +# config.pane_nav. In an nvim/vim/fzf pane the key is forwarded so nvim moves +# between its own splits and, at its outer edge, hands off with +# `tmux select-pane -t $TMUX_PANE`; elsewhere tmux moves directly. "Is vim" is +# smart-splits' @pane-is-vim pane option or the pane's foreground command, read +# as a format (no ps fork per keypress). Ghostty passes these keys through +# (keybind = clear); prefix + ctrl+l is the clear-screen escape hatch. +bind -N "Focus pane left (vim-aware)" -n C-h if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-h' 'select-pane -L' +bind -N "Focus pane down (vim-aware)" -n C-j if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-j' 'select-pane -D' +bind -N "Focus pane up (vim-aware)" -n C-k if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-k' 'select-pane -U' +bind -N "Focus pane right (vim-aware)" -n C-l if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-l' 'select-pane -R' +bind -N "Focus pane left" -T copy-mode-vi C-h select-pane -L +bind -N "Focus pane down" -T copy-mode-vi C-j select-pane -D +bind -N "Focus pane up" -T copy-mode-vi C-k select-pane -U +bind -N "Focus pane right" -T copy-mode-vi C-l select-pane -R +bind -N "Clear screen" C-l send-keys C-l + bind -N "Resize pane left" -n C-M-S-Left resize-pane -L 5 bind -N "Resize pane down" -n C-M-S-Down resize-pane -D 5 bind -N "Resize pane up" -n C-M-S-Up resize-pane -U 5 @@ -71,10 +93,18 @@ set -g renumber-windows on set -g history-limit 50000 set -g escape-time 0 set -g focus-events on +# Yazi inside tmux: OSC 52 clipboard, image passthrough, and the outer +# TERM/TERM_PROGRAM so yazi detects Ghostty/kitty graphics. Never bind M-[: +# tmux would eat the ESC [ of yazi's terminal-query replies. set -g set-clipboard on set -g allow-passthrough on +set -ga update-environment TERM +set -ga update-environment TERM_PROGRAM setw -g aggressive-resize on -set -g detach-on-destroy off +# Killing a session switches only to a *detached* one, else detaches: with +# one session per Ghostty window, `off` would land on (and mirror) a session +# another window is showing. +set -g detach-on-destroy no-detached set -g extended-keys on set -g extended-keys-format csi-u set -ag terminal-features "xterm-kitty:extkeys" @@ -88,28 +118,83 @@ set -g status-left-length 30 set -g status-right-length 50 set -g window-status-separator "" set -gw automatic-rename on -set -gw automatic-rename-format '#{b:pane_current_path}' +# #{window_icon} is filled in by tmux-nerd-font-window-name (icons from +# tmux-nerd-font-window-name.yml); the label follows kitty's tab_bar.py. An +# editor shows the file from the title nvim sets (" | ", see +# nvim's astrocore custom_title), read straight from #{pane_title} so a buffer +# switch renames the tab at once; otherwise tab-label.sh prints the repo of the +# pane's cwd (an async #() job; it re-arms automatic-rename when the tab is +# stale, so a `cd` relabels without waiting for more pane output). +set -gw automatic-rename-format '#{window_icon} #{=/27/…:#{?#{&&:#{m/r:^(n?vim|vim|nvim-qt|hx|helix)$,#{pane_current_command}},#{m/r:^[^/|]+ \| ,#{pane_title}}},#{s/ \| .*$//:pane_title},#(~/.config/tmux/tab-label.sh #{q:pane_current_path} #{window_id})}}' set -g set-titles on set -g set-titles-string '#h:#W' -# Theme -set -g status-style "bg=default,fg=default" -set -g status-left "#[fg=black,bg=blue,bold] #S #[bg=default] " -set -g status-right "#[fg=blue]#{?pane_in_mode,COPY ,}#{?client_prefix,PREFIX ,}#{?window_zoomed_flag,ZOOM ,}#[fg=brightblack]#h " -set -g window-status-format "#[fg=brightblack] #I:#W " -set -g window-status-current-format "#[fg=blue,bold] #I:#W " +# Theme — kitty's powerline/angled tab bar (kitty.conf + the Omarchy kitty +# template): the current window is an accent-tinted plate with the accent as +# its text, the rest sit flush with the background in muted text. Colors are +# cyberdream defaults, overridden per Omarchy theme by tmux.conf rendered from +# ~/.config/omarchy/themed/tmux.conf.tpl. +set -g @tab-bg "#16181a" +set -g @tab-fg "#7b8496" +set -g @tab-active-bg "#243348" +set -g @tab-active-fg "#5ea1ff" +source-file -q ~/.local/state/omarchy/current/theme/tmux.conf + +set -g status-style "bg=default,fg=#{@tab-fg}" +# bg=default inside a tab format means "the window-status-*-style bg", not the +# terminal's, so these must stay default for the tabs to be transparent. Set +# explicitly: a config reload never clears a value a removed plugin left +# behind (tmux-dotbar painted window-status-style bg=#0B0E14). +set -g window-status-style default +set -g window-status-current-style default +set -g window-status-last-style default +set -g window-status-activity-style default +set -g window-status-bell-style default +# Tabs start right after the session pill (status-justify left; a reload +# would otherwise keep a stale value such as dotbar's absolute-centre), and +# the pill's arrow points into the first tab's plate. +set -g status-justify left +set -g status-left "#[fg=#{@tab-bg},bg=#{@tab-active-fg},bold] #S #[fg=#{@tab-active-fg},nobold]#{?#{==:#{active_window_index},#{base-index}},#[bg=#{@tab-active-bg}],#[bg=default]}" +set -g status-right "#[fg=#{@tab-active-fg}]#{?pane_in_mode,COPY ,}#{?client_prefix,PREFIX ,}#{?window_zoomed_flag,ZOOM ,}#[fg=#{@tab-fg}]#h " +# Inactive tabs are bg=default so the terminal's transparency shows through, +# as in kitty (tab_bar_background none). kitty's separator rule: a solid +#  into a differently colored neighbour, thin  between two inactive +# tabs, none after the last. The arrow into the current tab is painted in +# @tab-bg, the window background; Ghostty's background-opacity-cells gives it +# the same opacity as the window, so it is indistinguishable from it. (Plain +#  is one of Ghostty's built-in sprites and fills the whole cell even +# with adjust-cell-height; inverse glyphs such as  come from the font +# and leave gaps.) +set -g window-status-format "#[fg=#{@tab-fg},bg=default] #I #W #{?next_window_active,#[fg=#{@tab-bg}#,bg=#{@tab-active-bg}],#{?window_end_flag,,}}" +set -g window-status-current-format "#[fg=#{@tab-active-fg},bg=#{@tab-active-bg},bold] #I #W #[fg=#{@tab-active-bg},bg=default,nobold]" set -g pane-border-style "fg=brightblack" -set -g pane-active-border-style "fg=blue" -set -g message-style "bg=default,fg=blue" -set -g message-command-style "bg=default,fg=blue" -set -g mode-style "bg=blue,fg=black" +set -g pane-active-border-style "fg=#{@tab-active-fg}" +set -g message-style "bg=default,fg=#{@tab-active-fg}" +set -g message-command-style "bg=default,fg=#{@tab-active-fg}" +set -g mode-style "bg=#{@tab-active-bg},fg=#{@tab-active-fg}" setw -g clock-mode-colour blue # Plugins (tpm, cloned by .chezmoiexternal.toml). prefix + I installs, # prefix + U updates, prefix + alt + u removes unlisted ones. set -g @plugin 'tmux-plugins/tpm' set -g @plugin 'tmux-plugins/tmux-sensible' -set -g @plugin 'vaaleyard/tmux-dotbar' +set -g @plugin 'joshmedeski/tmux-nerd-font-window-name' +set -g @plugin 'tmux-plugins/tmux-resurrect' +set -g @plugin 'tmux-plugins/tmux-continuum' +set -g @plugin 'omerxx/tmux-sessionx' + +# sessionx: fuzzy session switcher/creator on prefix + o (Ghostty +# ctrl+shift+o). A query with no match goes to zoxide; uses fzf --tmux. +set -g @sessionx-bind 'o' +set -g @sessionx-zoxide-mode 'on' +set -g @sessionx-fzf-builtin-tmux 'on' + +# Persistence: continuum saves every 10 min (resurrect: prefix + C-s save, +# prefix + C-r restore) and restores the last save when the server starts, +# i.e. the first Ghostty window after login or reboot. +set -g @resurrect-capture-pane-contents 'on' +set -g @continuum-save-interval '10' +set -g @continuum-restore 'on' # Keep at the very bottom. run '~/.config/tmux/plugins/tpm/tpm' diff --git a/dot_zshrc b/dot_zshrc index f7c790e..b24fd2f 100644 --- a/dot_zshrc +++ b/dot_zshrc @@ -63,7 +63,38 @@ command -v suv >/dev/null 2>&1 && eval "$(suv init zsh)" [ -s "$HOME/.config/envman/load.sh" ] && . "$HOME/.config/envman/load.sh" # Prompt — starship, consistent with fish -command -v starship >/dev/null 2>&1 && eval "$(starship init zsh)" +if command -v starship >/dev/null 2>&1; then + eval "$(starship init zsh)" + ZLE_RPROMPT_INDENT=0 # right_format ($time) flush with the edge + + # Transient prompt (fish has enable_transience; zsh has no starship + # built-in): on Enter, collapse the prompt to ❯ + time, restore it next + # precmd. Must be registered before zsh-syntax-highlighting below. + # Profiles live in starship.toml; strip add_newline's leading \n. + _starship_prompt=$PROMPT _starship_rprompt=$RPROMPT + _starship_profile() { + local s + s=$(starship prompt --profile "$1" --status="${STARSHIP_CMD_STATUS:-}") + print -rn -- "${s#$'\n'}" + } + _starship_transient() { + PROMPT='$(_starship_profile transient)' RPROMPT='$(_starship_profile transient_right)' + zle .reset-prompt + } + _starship_restore() { PROMPT=$_starship_prompt RPROMPT=$_starship_rprompt; } + autoload -Uz add-zle-hook-widget && add-zle-hook-widget line-finish _starship_transient + add-zsh-hook precmd _starship_restore +fi + +# Window title — port of fish_title.fish (+ tmux window name, as in +# fish's conf.d/50-terminal-title.fish) +_set_win_title() { + local t=${(%):-%~} + [[ -n $SSH_TTY ]] && t="${${(%):-%m}[1,10]}:$t" + printf '\e]0;%s\a' "$t" + [[ -n $TMUX ]] && printf '\ek%s\e\\' "$t" +} +autoload -Uz add-zsh-hook && add-zsh-hook precmd _set_win_title # television: ctrl-t smart autocomplete (its ctrl-r is taken over by atuin # below, as in fish)