diff --git a/tmux/tmux.conf b/tmux/tmux.conf index b6cac81..3f7824a 100644 --- a/tmux/tmux.conf +++ b/tmux/tmux.conf @@ -133,9 +133,9 @@ set -g visual-activity on # set -g visual-bell on # setw -g bell-action other -# ================================ -# === Copy and scroll === -# ================================ +# ================================================ +# === Copy mode, scroll and clipboard === +# ================================================ # Prefer vi style key table setw -g mode-keys vi @@ -144,6 +144,7 @@ bind C-p choose-buffer # trigger copy mode by bind -n M-Up copy-mode +bind -n PageUp copy-mode # Scroll up/down by 1 line, half screen, whole screen bind -T copy-mode-vi M-Up send-keys -X scroll-up @@ -157,11 +158,38 @@ bind -T copy-mode-vi PageUp send-keys -X page-up bind -T copy-mode-vi WheelUpPane select-pane \; send-keys -X -N 2 scroll-up bind -T copy-mode-vi WheelDownPane select-pane \; send-keys -X -N 2 scroll-down -# Do not copy selection and cancel copy mode on drag end event -# More iTerm style selection: select, then need to mouse click to copy to buffer -unbind -T copy-mode-vi MouseDragEnd1Pane -bind -T copy-mode-vi MouseDown1Pane select-pane \; send-keys -X copy-selection +# wrap default shell in reattach-to-user-namespace if available +# there is some hack with `exec & reattach`, credits to "https://github.com/gpakosz/.tmux" +# don't really understand how it works, but at least window are not renamed to "reattach-to-user-namespace" +if -b "command -v reattach-to-user-namespace > /dev/null 2>&1" \ + "run 'tmux set -g default-command \"exec $(tmux show -gv default-shell) 2>/dev/null & reattach-to-user-namespace -l $(tmux show -gv default-shell)\"'" +yank="~/.tmux/yank.sh" + +# Remap keys which perform copy to pipe copied text to OS clipboard +bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "$yank" +bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "$yank" +bind -T copy-mode-vi Y send-keys -X copy-pipe-and-cancel "$yank; tmux paste-buffer" +bind -T copy-mode-vi C-j send-keys -X copy-pipe-and-cancel "$yank" +bind-key -T copy-mode-vi D send-keys -X copy-end-of-line \;\ + run "tmux save-buffer - | $yank" +bind-key -T copy-mode-vi A send-keys -X append-selection-and-cancel \;\ + run "tmux save-buffer - | $yank" + +# Do not copy selection and cancel copy mode on drag end event +# Prefer iTerm style selection: select, then mouse click to copy to buffer +unbind -T copy-mode-vi MouseDragEnd1Pane +bind -T copy-mode-vi MouseDown1Pane select-pane \;\ + send-keys -X copy-pipe-and-cancel "$yank" + +# iTerm2 works with clipboard out of the box, set-clipboard already set to "external" +# tmux show-options -g -s set-clipboard +# set-clipboard on|external + +# TODO: open new window/pane and retain pwd +# bind '"' split-window -c "#{pane_current_path}" +# bind % split-window -h -c "#{pane_current_path}" +# bind c new-window -c "#{pane_current_path}" # ===================================== diff --git a/tmux/yank.sh b/tmux/yank.sh new file mode 100755 index 0000000..f2a0e86 --- /dev/null +++ b/tmux/yank.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -eu + +is_app_installed() { + type "$1" &>/dev/null +} + +# get data either form stdin or from file +buf=$(cat "$@") + +# Resolve copy backend: pbcopy (OSX), reattach-to-user-namespace (OSX), xclip/xsel (Linux) +copy_backend="" +if is_app_installed pbcopy; then + copy_backend="pbcopy" +elif is_app_installed reattach-to-user-namespace; then + copy_backend="reattach-to-user-namespace pbcopy" +elif [ -n "${DISPLAY-}" ] && is_app_installed xclip; then + copy_backend="xclip -selection clipboard -i" +elif [ -n "${DISPLAY-}" ] && is_app_installed xsel; then + copy_backend="xsel -i --clipboard" +fi + +# if copy backend is resolved, copy and exit +if [ -n "$copy_backend" ]; then + printf "$buf" | "$copy_backend" + exit; +fi + +# TODO: send to local socket which is remote tunneled to "pbcopy|xclip" listener on local machine +# TODO: otherwise fallback to OSC 52 escape sequence + +# Copy via OSC 52 ANSI escape sequence to controlling terminal +buflen=$( printf %s "$buf" | wc -c ) + +# https://sunaku.github.io/tmux-yank-osc52.html +# The maximum length of an OSC 52 escape sequence is 100_000 bytes, of which +# 7 bytes are occupied by a "\033]52;c;" header, 1 byte by a "\a" footer, and +# 99_992 bytes by the base64-encoded result of 74_994 bytes of copyable text +maxlen=74994 + +# warn if exceeds maxlen +if [ "$buflen" -gt "$maxlen" ]; then + printf "input is %d bytes too long" "$(( buflen - maxlen ))" >&2 +fi + +# build up OSC 52 ANSI escape sequence +esc="\033]52;c;$( printf %s "$buf" | head -c $maxlen | base64 | tr -d '\r\n' )\a" +esc="\033Ptmux;\033$esc\033\\" + +# resolve target terminal to send escape sequence +# if we are on remote machine, send directly to SSH_TTY to transport escape sequence +# to terminal on local machine, so data lands in clipboard on our local machine +pane_active_tty=$(tmux list-panes -F "#{pane_active} #{pane_tty}" | awk '$1=="1" { print $2 }') +target_tty="${SSH_TTY:-$pane_active_tty}" + +printf "$esc" > "$target_tty"