Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions config/tmux-ccb.conf
Original file line number Diff line number Diff line change
Expand Up @@ -83,38 +83,45 @@ if-shell 'command -v pbcopy' {
bind-key -T copy-mode-vi Enter send -X copy-pipe-and-cancel 'pbcopy'
bind-key -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-and-cancel 'pbcopy'
}
# WSL: Use PowerShell for clipboard (clip.exe doesn't support UTF-8)
if-shell '[ -n "$WSL_DISTRO_NAME" ] && command -v powershell.exe' {
bind-key -T copy-mode-vi 'y' send -X copy-pipe "powershell.exe -NoProfile -Command 'Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
bind-key -T copy-mode-vi Enter send -X copy-pipe-and-cancel "powershell.exe -NoProfile -Command 'Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
bind-key -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-and-cancel "powershell.exe -NoProfile -Command 'Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
# Prefer PowerShell clipboard providers when available (more reliable UTF-8 handling on WSL/Windows).
if-shell 'command -v powershell.exe >/dev/null 2>&1' {
bind-key -T copy-mode-vi 'y' send -X copy-pipe "powershell.exe -NoProfile -Command '[Console]::InputEncoding=[System.Text.UTF8Encoding]::new(); Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
bind-key -T copy-mode-vi Enter send -X copy-pipe-and-cancel "powershell.exe -NoProfile -Command '[Console]::InputEncoding=[System.Text.UTF8Encoding]::new(); Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
bind-key -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-and-cancel "powershell.exe -NoProfile -Command '[Console]::InputEncoding=[System.Text.UTF8Encoding]::new(); Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
}
if-shell 'command -v pwsh >/dev/null 2>&1' {
bind-key -T copy-mode-vi 'y' send -X copy-pipe "pwsh -NoLogo -NoProfile -Command '[Console]::InputEncoding=[System.Text.UTF8Encoding]::new(); Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
bind-key -T copy-mode-vi Enter send -X copy-pipe-and-cancel "pwsh -NoLogo -NoProfile -Command '[Console]::InputEncoding=[System.Text.UTF8Encoding]::new(); Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
bind-key -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-and-cancel "pwsh -NoLogo -NoProfile -Command '[Console]::InputEncoding=[System.Text.UTF8Encoding]::new(); Set-Clipboard -Value ([Console]::In.ReadToEnd())'"
}

# Paste from system clipboard (try providers in order; avoids X11/Wayland mis-detection).
# Prefer tmux's default paste key (`prefix + ]`) for compatibility with common tmux configs.
# Also bind `prefix + p` because many users expect it (and some configs remap `]`).
bind-key ] run-shell "sh -lc '\
out=\"\"; \
if [ -z \"\$out\" ] && command -v pwsh >/dev/null 2>&1; then out=$(pwsh -NoLogo -NoProfile -Command '\''[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new(); Get-Clipboard'\'' 2>/dev/null | tr -d \"\\r\" || true); fi; \
if [ -z \"\$out\" ] && command -v powershell.exe >/dev/null 2>&1; then out=$(powershell.exe -NoProfile -Command '\''[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new(); Get-Clipboard'\'' 2>/dev/null | tr -d \"\\r\" || true); fi; \
if [ -z \"\$out\" ] && command -v wl-paste >/dev/null 2>&1; then out=$(wl-paste -n 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v wl-paste >/dev/null 2>&1; then out=$(wl-paste -n -p 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xclip >/dev/null 2>&1; then out=$(xclip -selection clipboard -o 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xclip >/dev/null 2>&1; then out=$(xclip -selection primary -o 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xsel >/dev/null 2>&1; then out=$(xsel --clipboard --output 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xsel >/dev/null 2>&1; then out=$(xsel --primary --output 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v pbpaste >/dev/null 2>&1; then out=$(pbpaste 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v powershell.exe >/dev/null 2>&1; then out=$(powershell.exe -NoProfile -Command Get-Clipboard 2>/dev/null | tr -d \"\\r\" || true); fi; \
if [ -z \"\$out\" ]; then tmux display-message \"No clipboard content/helper found (install wl-clipboard or xclip/xsel)\"; exit 0; fi; \
printf \"%s\" \"\$out\"' | tmux load-buffer - && tmux paste-buffer"
bind-key p run-shell "sh -lc '\
out=\"\"; \
if [ -z \"\$out\" ] && command -v pwsh >/dev/null 2>&1; then out=$(pwsh -NoLogo -NoProfile -Command '\''[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new(); Get-Clipboard'\'' 2>/dev/null | tr -d \"\\r\" || true); fi; \
if [ -z \"\$out\" ] && command -v powershell.exe >/dev/null 2>&1; then out=$(powershell.exe -NoProfile -Command '\''[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new(); Get-Clipboard'\'' 2>/dev/null | tr -d \"\\r\" || true); fi; \
if [ -z \"\$out\" ] && command -v wl-paste >/dev/null 2>&1; then out=$(wl-paste -n 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v wl-paste >/dev/null 2>&1; then out=$(wl-paste -n -p 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xclip >/dev/null 2>&1; then out=$(xclip -selection clipboard -o 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xclip >/dev/null 2>&1; then out=$(xclip -selection primary -o 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xsel >/dev/null 2>&1; then out=$(xsel --clipboard --output 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v xsel >/dev/null 2>&1; then out=$(xsel --primary --output 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v pbpaste >/dev/null 2>&1; then out=$(pbpaste 2>/dev/null || true); fi; \
if [ -z \"\$out\" ] && command -v powershell.exe >/dev/null 2>&1; then out=$(powershell.exe -NoProfile -Command Get-Clipboard 2>/dev/null | tr -d \"\\r\" || true); fi; \
if [ -z \"\$out\" ]; then tmux display-message \"No clipboard content/helper found (install wl-clipboard or xclip/xsel)\"; exit 0; fi; \
printf \"%s\" \"\$out\"' | tmux load-buffer - && tmux paste-buffer"

Expand Down
125 changes: 76 additions & 49 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1069,11 +1069,45 @@ except Exception as e:
fi
}

CCB_TMUX_MARKER="# ============================================================================="
CCB_TMUX_MARKER="# CCB (Claude Code Bridge) tmux configuration"
CCB_TMUX_MARKER_LEGACY="# CCB tmux configuration"

remove_ccb_tmux_block_from_file() {
local target_conf="$1"

if [[ ! -f "$target_conf" ]]; then
return 0
fi

if ! grep -q "$CCB_TMUX_MARKER" "$target_conf" 2>/dev/null && \
! grep -q "$CCB_TMUX_MARKER_LEGACY" "$target_conf" 2>/dev/null; then
return 0
fi

if ! pick_any_python_bin; then
return 1
fi

"$PYTHON_BIN" -c "
import re
path = '$target_conf'
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
# Remove CCB tmux config block (both new and legacy markers)
pattern = r'\n*# =+\n# CCB \(Claude Code Bridge\) tmux configuration.*?# =+\n# End of CCB tmux configuration\n# =+'
content = re.sub(pattern, '', content, flags=re.DOTALL)
pattern = r'\n*# CCB tmux configuration.*'
content = re.sub(pattern, '', content, flags=re.DOTALL)
with open(path, 'w', encoding='utf-8') as f:
f.write(content.strip() + '\n' if content.strip() else '')
"
}

install_tmux_config() {
local tmux_conf="$HOME/.tmux.conf"
local tmux_conf_main="$HOME/.tmux.conf"
local tmux_conf_local="$HOME/.tmux.conf.local"
local tmux_conf="$tmux_conf_main"
local reload_conf="$tmux_conf_main"
local ccb_tmux_conf="$REPO_ROOT/config/tmux-ccb.conf"
local ccb_status_script="$REPO_ROOT/config/ccb-status.sh"
local status_install_path="$BIN_DIR/ccb-status.sh"
Expand Down Expand Up @@ -1123,32 +1157,34 @@ install_tmux_config() {
echo "Installed: $BIN_DIR/ccb-tmux-off.sh"
fi

# Check if already configured (new or legacy marker)
# Oh-My-Tmux keeps user customizations in ~/.tmux.conf.local.
# Appending to ~/.tmux.conf can break its internal _apply_configuration script.
if [[ -f "$tmux_conf_main" ]] && grep -q 'TMUX_CONF_LOCAL' "$tmux_conf_main" 2>/dev/null; then
tmux_conf="$tmux_conf_local"
reload_conf="$tmux_conf_main"
if [[ ! -f "$tmux_conf_local" ]]; then
touch "$tmux_conf_local"
fi
else
reload_conf="$tmux_conf"
fi

# Check if already configured (new or legacy marker) in either main/local config.
local already_configured=false
if [[ -f "$tmux_conf" ]]; then
if grep -q "$CCB_TMUX_MARKER" "$tmux_conf" 2>/dev/null || \
grep -q "$CCB_TMUX_MARKER_LEGACY" "$tmux_conf" 2>/dev/null; then
for conf in "$tmux_conf_main" "$tmux_conf_local"; do
if [[ -f "$conf" ]] && \
(grep -q "$CCB_TMUX_MARKER" "$conf" 2>/dev/null || \
grep -q "$CCB_TMUX_MARKER_LEGACY" "$conf" 2>/dev/null); then
already_configured=true
break
fi
fi
done

if $already_configured; then
# Update existing config: remove old CCB block and re-add
# Update existing config: remove old CCB block(s) and re-add at target location.
echo "Updating CCB tmux configuration..."
if pick_any_python_bin; then
"$PYTHON_BIN" -c "
import re
with open('$tmux_conf', 'r', encoding='utf-8') as f:
content = f.read()
# Remove old CCB tmux config block (both new and legacy markers)
pattern = r'\n*# =+\n# CCB \(Claude Code Bridge\) tmux configuration.*?# =+\n# End of CCB tmux configuration\n# =+'
content = re.sub(pattern, '', content, flags=re.DOTALL)
pattern = r'\n*# CCB tmux configuration.*'
content = re.sub(pattern, '', content, flags=re.DOTALL)
with open('$tmux_conf', 'w', encoding='utf-8') as f:
f.write(content.strip() + '\n' if content.strip() else '')
"
fi
remove_ccb_tmux_block_from_file "$tmux_conf_main" || true
remove_ccb_tmux_block_from_file "$tmux_conf_local" || true
else
# Backup existing config if present
if [[ -f "$tmux_conf" ]]; then
Expand Down Expand Up @@ -1179,23 +1215,24 @@ sys.stdout.write(content.replace('@CCB_BIN_DIR@', bin_dir))
echo " - CCB theme is enabled only while CCB is running (auto restore on exit)"
echo " - Vi-style pane management with h/j/k/l"
echo " - Mouse support and better copy mode"
echo " - Run 'tmux source ~/.tmux.conf' to apply (or restart tmux)"
echo " - Run 'tmux source $reload_conf' to apply (or restart tmux)"

# Best-effort: if a tmux server is already running, reload config automatically.
# (Avoid spawning a new server when tmux isn't running.)
if command -v tmux >/dev/null 2>&1; then
if tmux list-sessions >/dev/null 2>&1; then
if tmux source-file "$tmux_conf" >/dev/null 2>&1; then
if tmux source-file "$reload_conf" >/dev/null 2>&1; then
echo "Reloaded tmux configuration in running server."
else
echo "WARN: Failed to reload tmux configuration automatically; run: tmux source ~/.tmux.conf"
echo "WARN: Failed to reload tmux configuration automatically; run: tmux source $reload_conf"
fi
fi
fi
}

uninstall_tmux_config() {
local tmux_conf="$HOME/.tmux.conf"
local tmux_conf_main="$HOME/.tmux.conf"
local tmux_conf_local="$HOME/.tmux.conf.local"
local status_script="$BIN_DIR/ccb-status.sh"
local border_script="$BIN_DIR/ccb-border.sh"
local tmux_on_script="$BIN_DIR/ccb-tmux-on.sh"
Expand Down Expand Up @@ -1223,32 +1260,22 @@ uninstall_tmux_config() {
echo "Removed: $tmux_off_script"
fi

if [[ ! -f "$tmux_conf" ]]; then
return
fi
local removed_any=false
for conf in "$tmux_conf_main" "$tmux_conf_local"; do
if [[ -f "$conf" ]] && \
(grep -q "$CCB_TMUX_MARKER" "$conf" 2>/dev/null || \
grep -q "$CCB_TMUX_MARKER_LEGACY" "$conf" 2>/dev/null); then
echo "Removing CCB tmux configuration from $conf..."
if remove_ccb_tmux_block_from_file "$conf"; then
echo "Removed CCB tmux configuration from $conf"
removed_any=true
fi
fi
done

# Check for both new and legacy markers
if ! grep -q "$CCB_TMUX_MARKER" "$tmux_conf" 2>/dev/null && \
! grep -q "$CCB_TMUX_MARKER_LEGACY" "$tmux_conf" 2>/dev/null; then
if ! $removed_any; then
return
fi

echo "Removing CCB tmux configuration..."
if pick_any_python_bin; then
"$PYTHON_BIN" -c "
import re
with open('$tmux_conf', 'r', encoding='utf-8') as f:
content = f.read()
# Remove CCB tmux config block (both new and legacy markers)
pattern = r'\n*# =+\n# CCB \(Claude Code Bridge\) tmux configuration.*?# =+\n# End of CCB tmux configuration\n# =+'
content = re.sub(pattern, '', content, flags=re.DOTALL)
pattern = r'\n*# CCB tmux configuration.*'
content = re.sub(pattern, '', content, flags=re.DOTALL)
with open('$tmux_conf', 'w', encoding='utf-8') as f:
f.write(content.strip() + '\n' if content.strip() else '')
"
echo "Removed CCB tmux configuration from $tmux_conf"
fi
}

install_requirements() {
Expand Down
Loading