diff --git a/README.md b/README.md index ccc6fdd..9d18616 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # tmux-complete.vim -Vim plugin for insert mode completion of words in adjacent tmux panes +Vim plugin for insert mode completion of words in adjacent tmux panes and +duplicating the contents of a visible tmux pane in a split buffer. ## Motivation @@ -8,13 +9,18 @@ If you're using Vim in tandem with Tmux you might be familiar with this pesky situation: You're happily editing your lovely files in Vim, when you notice you need to -type a word that you can see in a different Tmux pane right next to Vim. This -might be some secret key found in your REPL or the name of a failing test. +type a word, or a chunk of text, that you can see in a different Tmux pane +right next to Vim. This might be some secret key found in your REPL or the name +of a failing test. -Usually the interesting text is too short to warrant switching panes and going -into Tmux' copy mode, so you end typing it out again. +Usually the interesting text is too short, to warrant switching panes and going +into Tmux's copy mode, so you end up typing it out again. -## But fear no longer! +Or maybe You just don't like using Tmux's copy mode, period. You feel like that +chunk of text sitting in a distant Tmux pane ought to be just as accessible to +you as a chunk of text sitting in a Vim buffer. + +## Well, fear no longer! This plugin adds a completion function that puts all words visible in your Tmux panes right under your fingertips. Just enter insert mode, start typing any @@ -32,6 +38,30 @@ MacVim! [example]: https://raw.githubusercontent.com/wellle/images/master/tmux-complete-example.png [gvim]: https://raw.githubusercontent.com/wellle/images/master/gvim-complete.png +There is also a function that copies all the text visible in a particular tmux +pane directly into a new split buffer in your Vim instance! + +Simply call the function like so: + +```vim +:call tmuxcomplete#tmux_pane_to_buffer() +``` + + Or map it to something convienient: + +```vim +nnoremap t :call tmuxcomplete#tmux_pane_to_buffer() +``` + +After the function is called, the tmux pane indices are flashed for a brief moment. Then just type the number to specify the pane you want and hit enter: + +![pane_selection](https://user-images.githubusercontent.com/52209396/148301300-c4b002d6-6362-4e81-b1a0-52277088a51c.jpg) + + +Here I typed '2', ad it's right there, ready to be bent to Vim's will: + +![pane_2](https://user-images.githubusercontent.com/52209396/148301308-2f8db950-498c-442c-84e4-d354f94dbcec.jpg) + ## Third party integration Tmux complete is automatically integrated with the following plugins: @@ -196,3 +226,18 @@ tmux-complete by putting one of these lines into your `.vimrc`: The trigger function itself is named `tmuxcomplete#complete` (in case you want to call it manually). + +- When copying the text in a Tmux pane to a Vim buffer, Tmux is instructed to +briefly flash the pane indexes for 350ms to aid in your choice. To disable this +behavior, put this in your .vimrc: + + ```vim + let g:tmuxcomplete_pane_index_display_duration_ms = 0 + ``` + +- Or set it to a different duration (as a string): + + ```vim + let g:tmuxcomplete_pane_index_display_duration_ms = "1000" + ``` + diff --git a/autoload/tmuxcomplete.vim b/autoload/tmuxcomplete.vim index 09176f8..d094b16 100644 --- a/autoload/tmuxcomplete.vim +++ b/autoload/tmuxcomplete.vim @@ -133,4 +133,26 @@ function! tmuxcomplete#gather_candidates() return tmuxcomplete#completions('', s:capture_args, 'words') endfunction +function! tmuxcomplete#display_tmux_pane_indices(duration) + " bring up the pane numbers as a background job + call job_start(["tmux", "display-pane", "-d", a:duration]) +endfunction + +function! tmuxcomplete#tmux_pane_to_buffer() + if g:tmuxcomplete_pane_index_display_duration_ms > 0 + call tmuxcomplete#display_tmux_pane_indices(g:tmuxcomplete_pane_index_display_duration_ms) + endif + " get the input from user + let targetpane = input("target_pane:") + if targetpane =~ '\d\+' + silent execute 'split .tmux_pane_'.targetpane + silent execute '%!sh '.s:script.' -t '.targetpane.' -s lines -n' + set filetype=bash + setlocal buftype=nofile + setlocal bufhidden=hide + setlocal noswapfile + setlocal nobuflisted + endif +endfunction + call tmuxcomplete#init() diff --git a/plugin/tmuxcomplete.vim b/plugin/tmuxcomplete.vim index fd82dbb..a29ba4d 100644 --- a/plugin/tmuxcomplete.vim +++ b/plugin/tmuxcomplete.vim @@ -24,6 +24,12 @@ function! s:init() if exists('g:loaded_compe') lua require'compe'.register_source('tmux', require'compe_tmux') endif + + if !exists('g:tmuxcomplete_pane_index_display_duration_ms') + " for use with tmuxcomplete#tmux_pane_to_buffer() + let g:tmuxcomplete_pane_index_display_duration_ms = "350" + endif + endfunction call s:init() diff --git a/sh/tmuxcomplete b/sh/tmuxcomplete index ca88bf3..0a8e2ea 100644 --- a/sh/tmuxcomplete +++ b/sh/tmuxcomplete @@ -6,6 +6,10 @@ # Words visible in current window, excluding current pane # sh tmuxcomplete -e # +# Words visible in specified pane in current window +# (can't be used alongside the -e option) +# sh tmuxcomplete -t +# # Words visible in current session # sh tmuxcomplete -l '-s' # @@ -35,21 +39,23 @@ fi EXCLUDE='0' NOSORT='0' +TARGET='-1' PATTERN='' SPLITMODE=words LISTARGS='' CAPTUREARGS='' GREPARGS='' -while getopts enp:s:l:c:g: name +while getopts enp:s:t:l:c:g: name do case $name in e) EXCLUDE="1";; n) NOSORT="1";; # internal/undocumented, don't use, might be changed in the future + t) TARGET="$OPTARG";; p) PATTERN="$OPTARG";; s) SPLITMODE="$OPTARG";; l) LISTARGS="$OPTARG";; c) CAPTUREARGS="$OPTARG";; g) GREPARGS="$OPTARG";; - *) echo "Usage: $0 [-p pattern] [-s splitmode] [-l listargs] [-c captureargs] [-g grepargs]\n" + *) echo "Usage: $0 [-t ] [-p pattern] [-s splitmode] [-l listargs] [-c captureargs] [-g grepargs]\n" exit 2;; esac done @@ -64,6 +70,8 @@ excludecurrent() { # echo 1>&2 'current' "$currentpane" # use -F to match $ in session id grep -v -F "$currentpane" + elif ! [ "$TARGET" = "-1" ]; then + echo $(tmux display-message -p '01-#{session_id} ')$TARGET else cat fi @@ -155,7 +163,7 @@ splitwords() { sortu() { if [ "$NOSORT" = "1" ]; then - uniq + cat else sort -u fi diff --git a/tmuxcomplete b/tmuxcomplete new file mode 100644 index 0000000..0a8e2ea --- /dev/null +++ b/tmuxcomplete @@ -0,0 +1,185 @@ +#!/bin/sh + +# Usage: Get a list of all words visible in current window +# sh tmuxcomplete +# +# Words visible in current window, excluding current pane +# sh tmuxcomplete -e +# +# Words visible in specified pane in current window +# (can't be used alongside the -e option) +# sh tmuxcomplete -t +# +# Words visible in current session +# sh tmuxcomplete -l '-s' +# +# Words visible in all sessions +# sh tmuxcomplete -l '-a' +# +# Words containing 'foo' +# sh tmuxcomplete -p 'foo' +# +# List of lines +# sh tmuxcomplete -s lines +# +# Words containing 'foo', ignoring case +# sh tmuxcomplete -p 'foo' -g '-i' +# +# Words beginning with 'foo' +# sh tmuxcomplete -p '^foo' +# +# Words including 2000 lines of history per pane +# sh tmuxcomplete -c '-S -2000' + +if ! tmux info > /dev/null 2>&1; then + echo "[tmux-complete.vim]" + echo "No tmux found!" + exit 0 +fi + +EXCLUDE='0' +NOSORT='0' +TARGET='-1' +PATTERN='' +SPLITMODE=words +LISTARGS='' +CAPTUREARGS='' +GREPARGS='' +while getopts enp:s:t:l:c:g: name +do case $name in + e) EXCLUDE="1";; + n) NOSORT="1";; # internal/undocumented, don't use, might be changed in the future + t) TARGET="$OPTARG";; + p) PATTERN="$OPTARG";; + s) SPLITMODE="$OPTARG";; + l) LISTARGS="$OPTARG";; + c) CAPTUREARGS="$OPTARG";; + g) GREPARGS="$OPTARG";; + *) echo "Usage: $0 [-t ] [-p pattern] [-s splitmode] [-l listargs] [-c captureargs] [-g grepargs]\n" + exit 2;; +esac +done + +listpanes() { + tmux list-panes $LISTARGS -F '#{pane_active}#{window_active}-#{session_id} #{pane_id}' +} + +excludecurrent() { + if [ "$EXCLUDE" = "1" ]; then + currentpane=$(tmux display-message -p '11-#{session_id} ') + # echo 1>&2 'current' "$currentpane" + # use -F to match $ in session id + grep -v -F "$currentpane" + elif ! [ "$TARGET" = "-1" ]; then + echo $(tmux display-message -p '01-#{session_id} ')$TARGET + else + cat + fi +} + +paneids() { + cut -d' ' -f2 +} + +capturepanes() { + panes=$(cat) + if [ -z "$panes" ]; then + # echo 'no panes' 1>&2 + return + elif tmux capture-pane -p >/dev/null 2>&1; then + # tmux capture-pane understands -p -> use it + echo "$panes" | xargs -n1 tmux capture-pane $CAPTUREARGS -p -t + else + # tmux capture-pane doesn't understand -p (like version 1.6) + # -> capture to paste-buffer, echo it, then delete it + echo "$panes" | xargs -n1 -I{} sh -c "tmux capture-pane $CAPTUREARGS -t {} && tmux show-buffer && tmux delete-buffer" + fi +} + +split() { + if [ "$SPLITMODE" = "ilines,words" ]; then + # this is most reabable, but not posix compliant + # tee >(splitilines) >(splitwords) + + # from https://unix.stackexchange.com/a/43536 + # this has some issues with trailing whitespace sometimes + # tmp_dir=$(mktemp -d) + # mkfifo "$tmp_dir/f1" "$tmp_dir/f2" + # splitilines <"$tmp_dir/f1" & pid1=$! + # splitwords <"$tmp_dir/f2" & pid2=$! + # tee "$tmp_dir/f1" "$tmp_dir/f2" + # rm -rf "$tmp_dir" + # wait $pid1 $pid2 + + splitilinesandwords + elif [ "$SPLITMODE" = "lines" ]; then + splitlines + elif [ "$SPLITMODE" = "ilines" ]; then + splitilines + elif [ "$SPLITMODE" = "words" ]; then + splitwords + fi +} + +splitilinesandwords() { + # print full line to duplicate it + # on the duplicate substitute all spaces with newlines + # duplicate that result again + # in that duplicate replace all non word characters by linebreaks + sed -e 'p;s/[[:space:]]\{1,\}/\ + /g;p;s/[^a-zA-Z0-9_]\{1,\}/\ + /g' | + # remove surrounding non-word characters + grep -o "\\w.*\\w" +} + +splitlines() { + # remove surrounding whitespace + grep -o "\\S.*\\S" +} + +splitilines() { + # starts at first word character + grep -o "\\w.*\\S" +} + +# returns both WORDS and words of each given line +splitwords() { + # use sed like this instead of tr? + # substitute all spaces with newlines + # duplicate that line + # in the duplicate replace all non word characters by linebreaks + # sed -e 's/[[:space:]]\{1,\}/\ + # /g;p;s/[^a-zA-Z0-9_]/ /g;s/[[:space:]]\{1,\}/\ + # /g' | + + # copy lines and split words + sed -e 'p;s/[^a-zA-Z0-9_]/ /g' | + # split on spaces + tr -s '[:space:]' '\n' | + # remove surrounding non-word characters + grep -o "\\w.*\\w" +} + +sortu() { + if [ "$NOSORT" = "1" ]; then + cat + else + sort -u + fi +} + +# list all panes +listpanes | +# filter out current pane +excludecurrent | +# take the pane id +paneids | +# capture panes +capturepanes | +# split words or lines depending on splitmode +split | +# filter out items not matching pattern +grep -e "$PATTERN" $GREPARGS | +# sort and remove duplicates +sortu