From 2a23297b782a2ce682f49b8ee7dd44e0f76ef6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerardo=20Santove=C3=B1a?= Date: Sat, 9 May 2026 22:54:30 -0600 Subject: [PATCH 1/2] Make tmux a reproducible Codex-ready config Track the tmux config and teach the installer to link it, bootstrap TPM, and leave TPM-managed plugin checkouts outside Git. The same change preserves Codex multiline input through Ghostty and tmux by using CSI-u Shift+Enter and a Codex keymap override, while pairing tmux navigation with Neovim mappings. Constraint: TPM installs nested Git repositories under tmux/plugins, so that path must remain ignored rather than committed.\nRejected: Commit TPM plugins as submodules | TPM is already the plugin manager and submodules would duplicate ownership.\nRejected: Rely only on terminal feature negotiation for Shift+Enter | Ghostty restart still did not make Codex see multiline input reliably.\nConfidence: high\nScope-risk: moderate\nDirective: Do not track tmux/plugins; it is an install cache owned by TPM.\nTested: make check\nNot-tested: Live TPM network clone on a fresh machine; Bats covers bootstrap with a fake git clone. Co-authored-by: OmX --- .gitignore | 3 ++ README.md | 18 +++++++++ ghostty/config | 3 ++ nvim/lua/config/keymaps.lua | 5 +++ nvim/lua/plugins/ui.lua | 3 ++ scripts/install-enhanced.sh | 68 ++++++++++++++++++++++++++++--- scripts/security-check.sh | 3 +- scripts/test-install.sh | 21 ++++++++-- tests/test_dotfiles.bats | 79 ++++++++++++++++++++++++++++++++++++- tmux/tmux.conf | 58 +++++++++++++++++++++++++++ 10 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 tmux/tmux.conf diff --git a/.gitignore b/.gitignore index baf1130..7bfc503 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ build/ personal/ private/ local/ + +# Tmux Plugin Manager installs plugin checkouts here; do not track nested repos. +tmux/plugins/ diff --git a/README.md b/README.md index 78c1769..67d1f72 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ make install - **Shell Configuration**: Zsh with oh-my-zsh, custom aliases and functions - **Editor Setup**: Neovim-only configuration with lazy.nvim plugin management +- **Terminal Multiplexer**: Tmux configuration with TPM plugin management - **Git Configuration**: Custom aliases and templates for efficient workflows - **Package Management**: Comprehensive Brewfile for development tools - **Security**: Proper secrets management and validation @@ -61,6 +62,7 @@ dotfiles/ ├── git/ # Git templates and hooks ├── gitconfig # Git configuration ├── nvim/ # Neovim configuration +├── tmux/ # Tmux configuration ├── zshrc # Zsh configuration ├── Brewfile # Package management └── Makefile # Build automation @@ -156,6 +158,19 @@ nvim nvim -c "Lazy install" -c "qa" ``` +## 🧩 Tmux plugin setup + +The installer bootstraps [TPM](https://github.com/tmux-plugins/tpm) into +`~/.config/tmux/plugins/tpm`. Other tmux plugins are declared in `tmux/tmux.conf` +and installed by TPM. + +```bash +# After installation, reload tmux config +tmux source-file ~/.config/tmux/tmux.conf + +# Then press prefix + I inside tmux to install declared plugins +``` + ### Key improvements - Better plugin support and performance - Asynchronous processing @@ -206,6 +221,9 @@ brew bundle --file=Brewfile # Update plugins nvim -c "Lazy update" -c "qa" +# Update tmux plugins +# Press prefix + U inside tmux + # Run health checks make check ``` diff --git a/ghostty/config b/ghostty/config index 4933ffb..138c7ce 100644 --- a/ghostty/config +++ b/ghostty/config @@ -2,3 +2,6 @@ font-size = 18 window-width = 120 window-height = 40 macos-option-as-alt = true + +# Send Shift+Enter as CSI-u so TUIs such as Codex can distinguish it from Enter. +keybind = shift+enter=csi:13;2u diff --git a/nvim/lua/config/keymaps.lua b/nvim/lua/config/keymaps.lua index 12b7734..14f622c 100644 --- a/nvim/lua/config/keymaps.lua +++ b/nvim/lua/config/keymaps.lua @@ -48,3 +48,8 @@ keymap("n", "qc", "cclose") keymap("n", "", "za") keymap("c", "w!!", "execute 'silent! write !sudo tee % >/dev/null' edit!") + +keymap("n", "", "TmuxNavigateLeft") +keymap("n", "", "TmuxNavigateRight") +keymap("n", "", "TmuxNavigateDown") +keymap("n", "", "TmuxNavigateUp") diff --git a/nvim/lua/plugins/ui.lua b/nvim/lua/plugins/ui.lua index 05fee57..5ac34ca 100644 --- a/nvim/lua/plugins/ui.lua +++ b/nvim/lua/plugins/ui.lua @@ -205,6 +205,9 @@ end return { -- UI and appearance + { + "christoomey/vim-tmux-navigator", + }, { "nvim-lualine/lualine.nvim", config = setup_evil_lualine, diff --git a/scripts/install-enhanced.sh b/scripts/install-enhanced.sh index 371a949..c1d1aa5 100755 --- a/scripts/install-enhanced.sh +++ b/scripts/install-enhanced.sh @@ -17,8 +17,10 @@ HISTORY_LOGS=${HOME}/.logs SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" HOME_FILES="bash_profile aliases exports functions git gitconfig zshrc screenrc" -CONFIG_FILES="nvim ghostty" +CONFIG_FILES="nvim ghostty tmux" BACKUP_DIR="${HOME}/.dotfiles-backup-$(date +%Y%m%d_%H%M%S)" +TPM_REPO_URL="${TPM_REPO_URL:-https://github.com/tmux-plugins/tpm.git}" +TPM_INSTALL_DIR="${TPM_INSTALL_DIR:-$HOME/.config/tmux/plugins/tpm}" # Function to print colored output print_status() { @@ -282,7 +284,8 @@ verify_installation() { for file in $HOME_FILES; do local target="$HOME/.$file" if [ -L "$target" ]; then - local link_target=$(readlink "$target") + local link_target + link_target=$(readlink "$target") if [ "$link_target" = "$DOTFILES_DIR/$file" ]; then if [ "$VERBOSE" = true ]; then print_status "$GREEN" "✅ $target correctly linked" @@ -301,7 +304,8 @@ verify_installation() { for file in $CONFIG_FILES; do local target="$HOME/.config/$file" if [ -L "$target" ]; then - local link_target=$(readlink "$target") + local link_target + link_target=$(readlink "$target") if [ "$link_target" = "$DOTFILES_DIR/$file" ]; then if [ "$VERBOSE" = true ]; then print_status "$GREEN" "✅ $target correctly linked" @@ -324,6 +328,53 @@ verify_installation() { fi } +# Bootstrap Tmux Plugin Manager so it can install plugins declared in tmux.conf +install_tpm() { + print_status "$BLUE" "Installing Tmux Plugin Manager..." + + if [ "$DRY_RUN" = true ]; then + if [ -e "$TPM_INSTALL_DIR" ]; then + echo "TPM already exists at: $TPM_INSTALL_DIR" + else + echo "Would install TPM from $TPM_REPO_URL to: $TPM_INSTALL_DIR" + fi + return 0 + fi + + case "${DOTFILES_SKIP_TPM_BOOTSTRAP:-false}" in + 1|true|TRUE|yes|YES) + print_status "$YELLOW" "Skipping TPM bootstrap because DOTFILES_SKIP_TPM_BOOTSTRAP is set" + return 0 + ;; + esac + + if [ -x "$TPM_INSTALL_DIR/tpm" ]; then + print_status "$GREEN" "✅ TPM already installed at: $TPM_INSTALL_DIR" + return 0 + fi + + if [ -e "$TPM_INSTALL_DIR" ]; then + print_status "$RED" "❌ TPM install path exists but does not contain an executable tpm script: $TPM_INSTALL_DIR" + print_status "$YELLOW" " Remove the path or install TPM there manually, then re-run the installer." + return 1 + fi + + mkdir -p "$(dirname "$TPM_INSTALL_DIR")" + + if [ "$VERBOSE" = true ]; then + echo "Cloning TPM: $TPM_REPO_URL --> $TPM_INSTALL_DIR" + fi + + git clone --depth 1 "$TPM_REPO_URL" "$TPM_INSTALL_DIR" + + if [ -x "$TPM_INSTALL_DIR/tpm" ]; then + print_status "$GREEN" "✅ TPM installed successfully" + else + print_status "$RED" "❌ TPM clone completed, but expected executable was not found: $TPM_INSTALL_DIR/tpm" + return 1 + fi +} + # Print next steps print_next_steps() { print_status "$BLUE" "Next steps:" @@ -333,14 +384,18 @@ print_next_steps() { echo " # or run non-interactively:" echo " nvim -c 'Lazy install' -c 'qa'" echo "" - echo "2. Install Homebrew packages:" + echo "2. Install tmux plugins with TPM:" + echo " tmux source-file ~/.config/tmux/tmux.conf" + echo " # Then press prefix + I inside tmux" + echo "" + echo "3. Install Homebrew packages:" echo " brew bundle --file=$DOTFILES_DIR/Brewfile" echo "" - echo "3. Restart your shell or run:" + echo "4. Restart your shell or run:" echo " source ~/.zshrc" echo "" if [ "$CREATE_BACKUP" = true ] && [ -d "$BACKUP_DIR" ]; then - echo "4. Your original files are backed up in:" + echo "5. Your original files are backed up in:" echo " $BACKUP_DIR" echo "" fi @@ -359,6 +414,7 @@ main() { create_backup install_dotfiles verify_installation + install_tpm echo "=================================" diff --git a/scripts/security-check.sh b/scripts/security-check.sh index f15503a..0f81891 100755 --- a/scripts/security-check.sh +++ b/scripts/security-check.sh @@ -97,7 +97,8 @@ check_permissions() { local bad_permissions=() while IFS= read -r -d '' file; do if [ -f "$file" ]; then - local perms=$(stat -c "%a" "$file" 2>/dev/null || stat -f "%Lp" "$file" 2>/dev/null) + local perms + perms=$(stat -c "%a" "$file" 2>/dev/null || stat -f "%Lp" "$file" 2>/dev/null) if [ "${perms: -1}" = "7" ] || [ "${perms: -2:1}" = "7" ]; then bad_permissions+=("$file:$perms") fi diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 6e9e6cf..77739cd 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -44,6 +44,8 @@ setup_test_env() { echo "# Original git metadata" > "$TEST_HOME/.git/config" mkdir -p "$TEST_HOME/.config/nvim" echo '" Original Neovim config' > "$TEST_HOME/.config/nvim/init.vim" + mkdir -p "$TEST_HOME/.config/tmux" + echo "# Original tmux config" > "$TEST_HOME/.config/tmux/tmux.conf" print_status "$GREEN" "Test environment created at: $TEST_HOME" } @@ -63,7 +65,7 @@ test_script_syntax() { test_dry_run() { print_status "$BLUE" "Testing dry-run installation..." - if env HOME="$TEST_HOME" bash "$INSTALL_SCRIPT" --dry-run --verbose; then + if env HOME="$TEST_HOME" DOTFILES_SKIP_TPM_BOOTSTRAP=true bash "$INSTALL_SCRIPT" --dry-run --verbose; then print_status "$GREEN" "✅ Dry-run installation completed successfully" else print_status "$RED" "❌ Dry-run installation failed" @@ -85,7 +87,7 @@ test_actual_install() { mkdir -p "$TEST_HOME/.logs" # Run the actual install script - if bash "$INSTALL_SCRIPT" --backup --verbose; then + if DOTFILES_SKIP_TPM_BOOTSTRAP=true bash "$INSTALL_SCRIPT" --backup --verbose; then print_status "$GREEN" "✅ Installation completed successfully" # Verify some key symlinks were created @@ -136,9 +138,21 @@ test_actual_install() { verification_failed=true fi + if [ -L "$TEST_HOME/.config/tmux" ]; then + print_status "$GREEN" "✅ .config/tmux symlink created" + else + print_status "$RED" "❌ .config/tmux symlink missing" + verification_failed=true + fi + + if [ -L "$TEST_HOME/.config/tmux/tmux" ]; then + print_status "$RED" "❌ Nested .config/tmux/tmux symlink should not be created" + verification_failed=true + fi + local backup_dir backup_dir=$(find "$TEST_HOME" -maxdepth 1 -type d -name ".dotfiles-backup-*" | head -n 1) - if [ -n "$backup_dir" ] && [ -f "$backup_dir/git/config" ] && [ -f "$backup_dir/.config/nvim/init.vim" ]; then + if [ -n "$backup_dir" ] && [ -f "$backup_dir/git/config" ] && [ -f "$backup_dir/.config/nvim/init.vim" ] && [ -f "$backup_dir/.config/tmux/tmux.conf" ]; then print_status "$GREEN" "✅ Existing directories moved to backup before linking" else print_status "$RED" "❌ Existing directory backup missing" @@ -199,6 +213,7 @@ test_config_syntax() { } # Cleanup test environment +# shellcheck disable=SC2329 # Invoked by the EXIT trap in main. cleanup() { print_status "$BLUE" "Cleaning up test environment..." if [ -n "${TEST_HOME:-}" ] && [ -d "$TEST_HOME" ]; then diff --git a/tests/test_dotfiles.bats b/tests/test_dotfiles.bats index 686591b..78e73f1 100644 --- a/tests/test_dotfiles.bats +++ b/tests/test_dotfiles.bats @@ -40,14 +40,91 @@ teardown() { echo "# Original git metadata" > "$TEST_TMPDIR/enhanced-home/.git/config" mkdir -p "$TEST_TMPDIR/enhanced-home/.config/nvim" echo '" Original Neovim config' > "$TEST_TMPDIR/enhanced-home/.config/nvim/init.vim" + mkdir -p "$TEST_TMPDIR/enhanced-home/.config/tmux" + echo "# Original tmux config" > "$TEST_TMPDIR/enhanced-home/.config/tmux/tmux.conf" - run env HOME="$TEST_TMPDIR/enhanced-home" bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --backup + run env HOME="$TEST_TMPDIR/enhanced-home" DOTFILES_SKIP_TPM_BOOTSTRAP=true bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --backup [ "$status" -eq 0 ] [ -L "$TEST_TMPDIR/enhanced-home/.git" ] [ -L "$TEST_TMPDIR/enhanced-home/.config/nvim" ] [ -L "$TEST_TMPDIR/enhanced-home/.config/ghostty" ] + [ -L "$TEST_TMPDIR/enhanced-home/.config/tmux" ] [ ! -L "$TEST_TMPDIR/enhanced-home/.git/git" ] [ ! -L "$TEST_TMPDIR/enhanced-home/.config/nvim/nvim" ] + [ ! -L "$TEST_TMPDIR/enhanced-home/.config/tmux/tmux" ] +} + +@test "installer dry-run announces TPM bootstrap" { + run env HOME="$TEST_TMPDIR/dry-home" bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"Would install TPM from https://github.com/tmux-plugins/tpm.git"* ]] +} + +@test "installer bootstraps TPM into configured plugin directory" { + local fake_bin="$TEST_TMPDIR/fake-bin" + local tpm_dir="$TEST_TMPDIR/tpm" + local real_git + real_git="$(command -v git)" + mkdir -p "$fake_bin" + + cat > "$fake_bin/git" < "\$target/tpm" + chmod +x "\$target/tpm" + printf '%s\n' "\$repo" > "\$target/cloned-from" + exit 0 +fi + +exec "$real_git" "\$@" +EOF + chmod +x "$fake_bin/git" + + run env PATH="$fake_bin:$PATH" HOME="$TEST_TMPDIR/bootstrap-home" TPM_INSTALL_DIR="$tpm_dir" bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --backup + [ "$status" -eq 0 ] + [ -x "$tpm_dir/tpm" ] + [ "$(cat "$tpm_dir/cloned-from")" = "https://github.com/tmux-plugins/tpm.git" ] +} + +@test "tmux config uses TPM and ignores installed plugin checkouts" { + grep -q "@plugin 'tmux-plugins/tpm'" "$DOTFILES_DIR/tmux/tmux.conf" + grep -q "run '~/.config/tmux/plugins/tpm/tpm'" "$DOTFILES_DIR/tmux/tmux.conf" + grep -q '^tmux/plugins/$' "$DOTFILES_DIR/.gitignore" +} + +@test "tmux preserves Shift+Enter for Codex multiline input" { + grep -q 'terminal-features.*,xterm-ghostty:extkeys' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q '^set-option -g extended-keys always$' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q '^set-option -g extended-keys-format csi-u$' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q '^bind-key -n S-Enter send-keys -H 1b 5b 31 33 3b 32 75$' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q '^keybind = shift+enter=csi:13;2u$' "$DOTFILES_DIR/ghostty/config" +} + +@test "tmux config syntax is valid" { + if ! command -v tmux >/dev/null 2>&1; then + skip "tmux not available" + fi + + run tmux -f /dev/null source-file -n "$DOTFILES_DIR/tmux/tmux.conf" + [ "$status" -eq 0 ] +} + +@test "ghostty config syntax is valid when ghostty is available" { + if ! command -v ghostty >/dev/null 2>&1; then + skip "ghostty not available" + fi + + run ghostty +validate-config --config-file="$DOTFILES_DIR/ghostty/config" + [ "$status" -eq 0 ] } @test "required dotfiles exist" { diff --git a/tmux/tmux.conf b/tmux/tmux.conf new file mode 100644 index 0000000..2a37771 --- /dev/null +++ b/tmux/tmux.conf @@ -0,0 +1,58 @@ +set-option -sa terminal-overrides ",xterm*:Tc" +set-option -as terminal-features ",xterm-ghostty:extkeys" +set-option -g extended-keys always +set-option -g extended-keys-format csi-u +set -g mouse on + +# Forward Shift+Enter as CSI-u so Codex can insert a newline instead of submitting. +bind-key -n S-Enter send-keys -H 1b 5b 31 33 3b 32 75 + +unbind C-b +set -g prefix C-Space +bind C-Space send-prefix + +# Vim style pane selection +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R + +# Start windows and panes at 1, not 0 +set -g base-index 1 +set -g pane-base-index 1 +set-window-option -g pane-base-index 1 +set-option -g renumber-windows on + +# Use Alt-arrow keys without prefix key to switch panes +bind -n M-Left select-pane -L +bind -n M-Right select-pane -R +bind -n M-Up select-pane -U +bind -n M-Down select-pane -D + +# Shift arrow to switch windows +bind -n S-Left previous-window +bind -n S-Right next-window + +# Shift Alt vim keys to switch windows +bind -n M-H previous-window +bind -n M-L next-window + +set -g @catppuccin_flavour 'mocha' + +set -g @plugin 'tmux-plugins/tpm' +set -g @plugin 'tmux-plugins/tmux-sensible' +set -g @plugin 'christoomey/vim-tmux-navigator' +set -g @plugin 'dreamsofcode-io/catppuccin-tmux' +set -g @plugin 'tmux-plugins/tmux-yank' + +run '~/.config/tmux/plugins/tpm/tpm' + +# set vi-mode +set-window-option -g mode-keys vi +# keybindings +bind-key -T copy-mode-vi v send-keys -X begin-selection +bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle +bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel + +bind '"' split-window -v -c "#{pane_current_path}" +bind % split-window -h -c "#{pane_current_path}" From 05ea96c1b5566b577fd1eedb5676116a3b0649b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerardo=20Santove=C3=B1a?= Date: Sat, 9 May 2026 23:03:41 -0600 Subject: [PATCH 2/2] Preserve tmux links and shell startup helpers tmux mouse mode was intercepting hyperlink clicks before Ghostty could open them. The config now advertises Ghostty hyperlink support and opens tmux-detected OSC 8 hyperlinks directly while falling back to normal pane mouse behavior, and the same change carries the requested shell startup helpers into this PR. Constraint: Mouse support must remain enabled for pane selection, scrolling, and existing tmux workflows.\nConstraint: User explicitly requested including the existing local bash/zsh edits in this commit.\nRejected: Disable tmux mouse mode | it would restore terminal URL clicking but regress tmux mouse UX.\nRejected: Leave shell helpers as separate local edits | user asked to add them to this commit.\nConfidence: medium\nScope-risk: moderate\nDirective: Keep non-link MouseDown1Pane behavior forwarding to tmux so mouse-enabled TUIs still receive clicks.\nTested: make check\nTested: bash -n bash_profile\nTested: zsh -n zshrc\nTested: tmux source-file tmux/tmux.conf\nTested: ghostty +validate-config --config-file=ghostty/config\nNot-tested: Manual click of an OSC 8 hyperlink after opening a fresh Ghostty window. Co-authored-by: OmX --- bash_profile | 4 ++++ ghostty/config | 1 + tests/test_dotfiles.bats | 10 +++++++++- tmux/tmux.conf | 5 ++++- zshrc | 31 +++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/bash_profile b/bash_profile index 7623f4f..9a3e2d6 100644 --- a/bash_profile +++ b/bash_profile @@ -41,3 +41,7 @@ eval "$(pyenv virtualenv-init -)" complete -C /opt/homebrew/bin/vault vault . "$HOME/.local/bin/env" + +#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!! +export SDKMAN_DIR="/Users/gsantovena/.sdkman" +[[ -s "/Users/gsantovena/.sdkman/bin/sdkman-init.sh" ]] && source "/Users/gsantovena/.sdkman/bin/sdkman-init.sh" diff --git a/ghostty/config b/ghostty/config index 138c7ce..2ab451e 100644 --- a/ghostty/config +++ b/ghostty/config @@ -2,6 +2,7 @@ font-size = 18 window-width = 120 window-height = 40 macos-option-as-alt = true +link-url = true # Send Shift+Enter as CSI-u so TUIs such as Codex can distinguish it from Enter. keybind = shift+enter=csi:13;2u diff --git a/tests/test_dotfiles.bats b/tests/test_dotfiles.bats index 78e73f1..48ca2c6 100644 --- a/tests/test_dotfiles.bats +++ b/tests/test_dotfiles.bats @@ -102,13 +102,21 @@ EOF } @test "tmux preserves Shift+Enter for Codex multiline input" { - grep -q 'terminal-features.*,xterm-ghostty:extkeys' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q 'terminal-features.*,xterm-ghostty:extkeys:hyperlinks' "$DOTFILES_DIR/tmux/tmux.conf" grep -q '^set-option -g extended-keys always$' "$DOTFILES_DIR/tmux/tmux.conf" grep -q '^set-option -g extended-keys-format csi-u$' "$DOTFILES_DIR/tmux/tmux.conf" grep -q '^bind-key -n S-Enter send-keys -H 1b 5b 31 33 3b 32 75$' "$DOTFILES_DIR/tmux/tmux.conf" grep -q '^keybind = shift+enter=csi:13;2u$' "$DOTFILES_DIR/ghostty/config" } +@test "tmux and ghostty preserve clickable URLs" { + grep -q 'terminal-features.*,xterm-ghostty:extkeys:hyperlinks' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q '^bind-key -n MouseDown1Pane if -F "#{mouse_hyperlink}"' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q 'command -v open' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q 'command -v xdg-open' "$DOTFILES_DIR/tmux/tmux.conf" + grep -q '^link-url = true$' "$DOTFILES_DIR/ghostty/config" +} + @test "tmux config syntax is valid" { if ! command -v tmux >/dev/null 2>&1; then skip "tmux not available" diff --git a/tmux/tmux.conf b/tmux/tmux.conf index 2a37771..0eb9bea 100644 --- a/tmux/tmux.conf +++ b/tmux/tmux.conf @@ -1,9 +1,12 @@ set-option -sa terminal-overrides ",xterm*:Tc" -set-option -as terminal-features ",xterm-ghostty:extkeys" +set-option -as terminal-features ",xterm-ghostty:extkeys:hyperlinks" set-option -g extended-keys always set-option -g extended-keys-format csi-u set -g mouse on +# Open OSC 8 hyperlinks directly when clicked, but keep normal tmux mouse behavior elsewhere. +bind-key -n MouseDown1Pane if -F "#{mouse_hyperlink}" { run-shell -b 'url=#{q:mouse_hyperlink}; if command -v open >/dev/null 2>&1; then open "$url"; elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"; fi' } { select-pane -t =; send-keys -M } + # Forward Shift+Enter as CSI-u so Codex can insert a newline instead of submitting. bind-key -n S-Enter send-keys -H 1b 5b 31 33 3b 32 75 diff --git a/zshrc b/zshrc index 76d6d41..d90c304 100644 --- a/zshrc +++ b/zshrc @@ -132,3 +132,34 @@ alias tm='task-master' alias taskmaster='task-master' . "$HOME/.local/bin/env" + +# ZSH Hacks - Dreams of Code + +autoload -Uz edit-command-line +zle -N edit-command-line +bindkey '^X^E' edit-command-line + +# Clear screen but keep current command buffer +function clear-screen-and-scrollback() { + echoti civis >"$TTY" + printf '%b' '\e[H\e[2J\e[3J' >"$TTY" + echoti cnorm >"$TTY" + zle redisplay +} +zle -N clear-screen-and-scrollback +bindkey '^Xl' clear-screen-and-scrollback + +# Copy current command buffer to clipboard (macOS) +function copy-buffer-to-clipboard() { + echo -n "$BUFFER" | pbcopy + zle -M "Copied to clipboard" +} +zle -N copy-buffer-to-clipboard +bindkey '^Xc' copy-buffer-to-clipboard + +# Insert git commit template (Ctrl+X, G, C) +# \C-b moves cursor back one position +bindkey -s '^Xgc' 'git commit -m ""\C-b' +bindkey -s '^Xgp' 'git push origin ' +bindkey -s '^Xgs' 'git status\n' +bindkey -s '^Xgl' 'git log --oneline -n 10\n'