├── LICENSE ├── README.md ├── counter.awk ├── gen_hints.py ├── hint_mode.sh ├── hinter.awk ├── tmux-picker.sh ├── tmux-picker.tmux └── tmux-printer ├── README.md ├── tmux-printer └── tmux-printer.awk /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Paweł Wiejacha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tmux-picker 2 | 3 | **tmux-picker**: Selecting and copy-pasting in terminal using Vimium-like hint mode for tmux. 4 | 5 | ![screencast](https://i.imgur.com/sz0176k.gif) 6 | 7 | This is a slimmed-down, improved and extended fork of [tmux-fingers](https://github.com/Morantron/tmux-fingers). Check [Acknowledgements](#acknowledgements) for comparison. 8 | 9 | # Usage 10 | 11 | Press ( Meta + F ) to enter **[picker]** hint mode, in which relevant stuff (e.g. file paths, git SHAs) in the current 12 | pane will be highlighted along with letter hints. By pressing those letters, the highlighted match will be copied to the system clipboard. 13 | 14 | By default, following items are highlighted: 15 | 16 | * File paths (that contain `/`) 17 | * git SHAs 18 | * numbers (4+ digits) 19 | * hex numbers 20 | * IP addresses 21 | * UUIDs 22 | 23 | You can press: 24 | 25 | * SPACE to highlight additional items (everything that might be a file path, if it's longer than 4 characters). 26 | * ESC to exit **[picker]** hint mode 27 | 28 | # Installation 29 | 30 | * Clone the repo: `git clone https://github.com/pawel-wiejacha/tmux-picker ~/.tmux/tmux-picker` 31 | * Add `run-shell ~/.tmux/tmux-picker/tmux-picker.tmux` to your `~/.tmux.conf` 32 | * Reload tmux config by running: `tmux source-file ~/.tmux.conf` 33 | 34 | # Configuration 35 | 36 | - Edit `~/.tmux/tmux-picker/tmux-picker.tmux`, where you can change: 37 | - `PICKER_KEY` (default Meta + F, without the prefix) 38 | - `PATTERNS_LIST1` - regex patterns highlighted after pressing `PICKER_KEY` 39 | - `PATTERNS_LIST2` - regex patterns highlighted after pressing SPACE in hint mode 40 | - `BLACKLIST` - regex pattern describing items that will not be highlighted 41 | - `PICKER_COPY_COMMAND` - command to execute on highlighted item 42 | - default is: `xclip -f -in -sel primary | xclip -in -sel clipboard`, which copies item to clipboard 43 | - `PICKER_COPY_COMMAND_UPPERCASE` - command to execute on highlighted item, when hint was typed using uppercase letters 44 | - default is `bash -c 'arg=\$(cat -); tmux split-window -h -c \"#{pane_current_path}\" vim \"\$arg\"'`, which executes `vim` in a sidebar 45 | - `PICKER_HINT_FORMAT` - describes hint color/style. 46 | - Default is `#[fg=black,bg=red,bold]`, but `#[fg=color0,bg=color202,dim,bold]%s` is IMO better if your terminal supports 256 colors 47 | - `PICKER_HIGHLIGHT_FORMAT` - describes highlighted item color/style 48 | 49 | # Requirements 50 | 51 | * tmux 2.2+ 52 | * bash 4+ 53 | * gawk 4.1+ (which was released in 2013) 54 | 55 | # Troubleshooting 56 | 57 | - Meta + F does not work in copy mode 58 | - Set `set-option -g mode-keys vi`, adjust your key bindings or change `PICKER_KEY` 59 | 60 | # Acknowledgements 61 | 62 | It started as a fork of [tmux-fingers](https://github.com/Morantron/tmux-fingers). I would like to thank to [Morantron](https://github.com/Morantron) (the tmux-fingers author) for a really good piece of code! 63 | 64 | My main problem with tmux-fingers was that it did not support terminal colors (it strips them down). I have fancy powerline prompt, colored `ls`, zsh syntax highlighting, colored git output, etc. So after entering tmux-fingers hint mode it was like *'WTF? Where are all my colors? Where am I? Where's the item I want to highlight??!'*. I could enable capturing escape sequences for colors in `tmux capture-pane`, but it would break tmux-fingers pattern matching. 65 | 66 | My other problem with tmux-fingers was that it was sluggish. So I started adding color support to `tmux-fingers` and improving its performance. I had to simplify things to make it reliable. I completely rewrote awk part, added Huffman Coding, added second hint mode. I therefore decided to fork and rename project instead of submitting pull requests that turn things upside down. 67 | 68 | ## Comparison 69 | 70 | Comparing to tmux-fingers, tmux-picker: 71 | 72 | - **supports terminal colors** (does not strip color escape codes) 73 | - uses Huffman Coding to generate hints (**shorter hints**, less typing) 74 | - and supports unlimited number of hints 75 | - is **noticeably faster** 76 | - and does not have redraw glitches 77 | - has **better patterns** and **two modes** (with different pattern sets) 78 | - and blacklist pattern 79 | - is self-contained, smaller and easier to hack 80 | 81 | Like tmux-fingers, tmux-picker still supports: 82 | 83 | - hints in copy-mode 84 | - split windows/multiple panes 85 | - zoomed panes 86 | - two different commands 87 | - configurable hint/highlight styles 88 | - configurable patterns 89 | 90 | # How it works? 91 | 92 | The basic idea is: 93 | 94 | - create auxiliary pane with the same width and height as the current pane 95 | - `tmux capture-pane -t $current_pane | gawk -f find-and-highlight-patterns.awk` to auxiliary pane 96 | - swap panes (the easiest way not to break things like copy-mode) 97 | - read typed keys and execute user command on selected item 98 | 99 | # License 100 | 101 | [MIT](https://github.com/pawel-wiejacha/tmux-picker/blob/master/LICENSE) 102 | -------------------------------------------------------------------------------- /counter.awk: -------------------------------------------------------------------------------- 1 | # counts items to highlight so we can choose the best hint set 2 | 3 | BEGIN { 4 | n_matches = 0; 5 | 6 | highlight_patterns = ENVIRON["PICKER_PATTERNS"] 7 | blacklist = "(^\x1b\\[[0-9]{1,5}m|^|[[:space:]])"ENVIRON["PICKER_BLACKLIST_PATTERNS"]"$" 8 | } 9 | 10 | { 11 | line = $0; 12 | while (match(line, highlight_patterns)) { 13 | post_match = substr(line, RSTART + RLENGTH); 14 | line_match = substr(line, RSTART, RLENGTH); 15 | 16 | if (line_match !~ blacklist) { 17 | hint = hint_by_match[line_match] 18 | if (!hint) { 19 | hint_by_match[line_match] = n_matches++ 20 | } 21 | } 22 | line = post_match; 23 | } 24 | } 25 | 26 | END { 27 | print n_matches 28 | } 29 | -------------------------------------------------------------------------------- /gen_hints.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import heapq 4 | 5 | # Generates gawk code. Run this manually and update hinter.awk if you don't like current alphabet or want to increase number of hints. 6 | # 7 | # This script builds prefix code for given number of hints using n-aray Huffman Coding. 8 | # We precompute hints for hinter.awk to make it fast (and to avoid implementing priority queue in awk). 9 | 10 | alphabet = list('sadfjklewcmvpghru') 11 | alphabet_size = len(alphabet) 12 | 13 | def generate_hints(num_hints_needed): 14 | def huffman_build_tree(num_hints_needed): 15 | heap = [(1, [i], []) for i in range(num_hints_needed)] 16 | heapq.heapify(heap) 17 | 18 | if num_hints_needed <= alphabet_size: 19 | first_step_num_children = num_hints_needed 20 | else: 21 | first_step_num_children = [m for m in range(2, num_hints_needed) 22 | if m % (alphabet_size - 1) == num_hints_needed % (alphabet_size - 1)][0] 23 | 24 | while len(heap) > 1: 25 | children = [] 26 | while len(heap) > 0 and len(children) < first_step_num_children: 27 | children.append(heapq.heappop(heap)) 28 | 29 | new_node = (sum(node[0] for node in children), 30 | sum([node[1] for node in children], []), 31 | [node for node in children]) 32 | heapq.heappush(heap, new_node) 33 | first_step_num_children = alphabet_size 34 | return heap[0] 35 | 36 | def generate_codes(tree, code): 37 | if len(tree[1]) == 1: 38 | yield code 39 | 40 | assert len(tree[2]) <= alphabet_size 41 | for child, char in zip(tree[2], alphabet): 42 | yield from generate_codes(child, code + char) 43 | 44 | tree = huffman_build_tree(num_hints_needed) 45 | yield from generate_codes(tree, code='') 46 | 47 | def generate_gawk_hints(): 48 | statement = '\nif' 49 | sizes = [alphabet_size, 30, 50, 80, 110, 150, 200, 300, 500, 1000] 50 | for num_hints_needed in sizes: 51 | hints_string = ' '.join(generate_hints(num_hints_needed)) 52 | if num_hints_needed == sizes[-1]: 53 | print(' else {') 54 | else: 55 | print('%s (num_hints_needed <= %d) {' % (statement, num_hints_needed)) 56 | print(' split("%s", HINTS]);' % (hints_string)) 57 | print('}', end='') 58 | statement = ' else if' 59 | print("") 60 | 61 | generate_gawk_hints() 62 | -------------------------------------------------------------------------------- /hint_mode.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | current_pane_id=$1 6 | picker_pane_id=$2 7 | last_pane_id=$3 8 | picker_window_id=$4 9 | pane_input_temp=$5 10 | 11 | eval "$(tmux show-env -g -s | grep ^PICKER)" 12 | 13 | match_lookup_table=$(mktemp) 14 | 15 | # exporting it so they can be properly deleted inside handle_exit trap 16 | export match_lookup_table 17 | 18 | function lookup_match() { 19 | local input=$1 20 | 21 | input="$(echo "$input" | tr "A-Z" "a-z")" 22 | grep -i "^$input:" $match_lookup_table | sed "s/^$input://" | head -n 1 23 | } 24 | 25 | function get_pane_contents() { 26 | cat $pane_input_temp 27 | } 28 | 29 | function extract_hints() { 30 | clear 31 | export NUM_HINTS_NEEDED=$(get_pane_contents | gawk -f $CURRENT_DIR/counter.awk) 32 | get_pane_contents | gawk -f $CURRENT_DIR/hinter.awk 3> $match_lookup_table 33 | } 34 | 35 | function show_hints_again() { 36 | local picker_pane_id=$1 37 | 38 | tmux swap-pane -s "$current_pane_id" -t "$picker_pane_id" # hide screen clearing glitch 39 | extract_hints 40 | tmux swap-pane -s "$current_pane_id" -t "$picker_pane_id" 41 | } 42 | 43 | function show_hints_and_swap() { 44 | current_pane_id=$1 45 | picker_pane_id=$2 46 | 47 | extract_hints 48 | tmux swap-pane -s "$current_pane_id" -t "$picker_pane_id" 49 | } 50 | 51 | 52 | BACKSPACE=$'\177' 53 | 54 | input='' 55 | result='' 56 | 57 | function is_pane_zoomed() { 58 | local pane_id=$1 59 | 60 | tmux list-panes \ 61 | -F "#{pane_id}:#{?pane_active,active,nope}:#{?window_zoomed_flag,zoomed,nope}" \ 62 | | grep -c "^${pane_id}:active:zoomed$" 63 | } 64 | 65 | function zoom_pane() { 66 | local pane_id=$1 67 | 68 | tmux resize-pane -Z -t "$pane_id" 69 | } 70 | 71 | function revert_to_original_pane() { 72 | tmux swap-pane -s "$current_pane_id" -t "$picker_pane_id" 73 | 74 | if [[ ! -z "$last_pane_id" ]]; then 75 | tmux select-pane -t "$last_pane_id" 76 | tmux select-pane -t "$current_pane_id" 77 | fi 78 | 79 | [[ $pane_was_zoomed == "1" ]] && zoom_pane "$current_pane_id" 80 | } 81 | 82 | function handle_exit() { 83 | rm -rf "$pane_input_temp" "$match_lookup_table" 84 | revert_to_original_pane 85 | 86 | if [[ ! -z "$result" ]]; then 87 | run_picker_copy_command "$result" "$input" 88 | fi 89 | 90 | tmux kill-window -t "$picker_window_id" 91 | } 92 | 93 | 94 | function is_valid_input() { 95 | local input=$1 96 | local is_valid=1 97 | 98 | if [[ $input == "" ]] || [[ $input == "" ]]; then 99 | is_valid=1 100 | else 101 | for (( i=0; i<${#input}; i++ )); do 102 | char=${input:$i:1} 103 | 104 | if [[ ! "$char" =~ ^[a-zA-Z]$ ]]; then 105 | is_valid=0 106 | break 107 | fi 108 | done 109 | fi 110 | 111 | echo $is_valid 112 | } 113 | 114 | function hide_cursor() { 115 | echo -n $(tput civis) 116 | } 117 | 118 | trap "handle_exit" EXIT 119 | 120 | export PICKER_PATTERNS=$PICKER_PATTERNS1 121 | export PICKER_BLACKLIST_PATTERNS=$PICKER_BLACKLIST_PATTERNS 122 | 123 | pane_was_zoomed=$(is_pane_zoomed "$current_pane_id") 124 | show_hints_and_swap $current_pane_id $picker_pane_id 125 | [[ $pane_was_zoomed == "1" ]] && zoom_pane "$picker_pane_id" 126 | 127 | hide_cursor 128 | input='' 129 | 130 | function run_picker_copy_command() { 131 | local result="$1" 132 | local hint="$2" 133 | 134 | is_uppercase=$(echo "$input" | grep -E '^[a-z]+$' &> /dev/null; echo $?) 135 | 136 | if [[ $is_uppercase == "1" ]] && [ ! -z "$PICKER_COPY_COMMAND_UPPERCASE" ]; then 137 | command_to_run="$PICKER_COPY_COMMAND_UPPERCASE" 138 | elif [ ! -z "$PICKER_COPY_COMMAND" ]; then 139 | command_to_run="$PICKER_COPY_COMMAND" 140 | fi 141 | 142 | if [[ ! -z "$command_to_run" ]]; then 143 | tmux run-shell -b "printf '$result' | $command_to_run" 144 | fi 145 | } 146 | 147 | while read -rsn1 char; do 148 | if [[ $char == "$BACKSPACE" ]]; then 149 | input="" 150 | fi 151 | 152 | # Escape sequence, flush input 153 | if [[ "$char" == $'\x1b' ]]; then 154 | read -rsn1 -t 0.1 next_char 155 | 156 | if [[ "$next_char" == "[" ]]; then 157 | read -rsn1 -t 0.1 158 | continue 159 | elif [[ "$next_char" == "" ]]; then 160 | char="" 161 | else 162 | continue 163 | fi 164 | 165 | fi 166 | 167 | if [[ ! $(is_valid_input "$char") == "1" ]]; then 168 | continue 169 | fi 170 | 171 | if [[ $char == "$BACKSPACE" ]]; then 172 | input="" 173 | continue 174 | elif [[ $char == "" ]]; then 175 | exit 176 | elif [[ $char == "" ]]; then 177 | if [ "$PICKER_PATTERNS" = "$PICKER_PATTERNS1" ]; then 178 | export PICKER_PATTERNS=$PICKER_PATTERNS2; 179 | else 180 | export PICKER_PATTERNS=$PICKER_PATTERNS1; 181 | fi 182 | show_hints_again "$picker_pane_id" 183 | continue 184 | else 185 | input="$input$char" 186 | fi 187 | 188 | result=$(lookup_match "$input") 189 | 190 | if [[ -z $result ]]; then 191 | continue 192 | fi 193 | 194 | exit 0 195 | done < /dev/tty 196 | -------------------------------------------------------------------------------- /hinter.awk: -------------------------------------------------------------------------------- 1 | @include "join" # gawk 4.1 was released in 2013 2 | 3 | BEGIN { 4 | n_matches = 0; 5 | 6 | highlight_patterns = ENVIRON["PICKER_PATTERNS"] 7 | num_hints_needed = ENVIRON["NUM_HINTS_NEEDED"] 8 | blacklist = "(^\x1b\\[[0-9;]{1,9}m|^|[[:space:]:<>)(&#'\"])"ENVIRON["PICKER_BLACKLIST_PATTERNS"]"$" 9 | 10 | hint_format = ENVIRON["PICKER_HINT_FORMAT"] 11 | hint_format_nocolor = ENVIRON["PICKER_HINT_FORMAT_NOCOLOR"] 12 | hint_format_len = length(sprintf(hint_format_nocolor, "")) 13 | highlight_format = ENVIRON["PICKER_HIGHLIGHT_FORMAT"] 14 | compound_format = hint_format highlight_format 15 | 16 | # run gen_hints.py to (re-)generate it: 17 | if (num_hints_needed <= 17) { 18 | split("s a d f j k l e w c m v p g h r u", HINTS); 19 | } else if (num_hints_needed <= 30) { 20 | split("s a d f j k l e w c m v p g h r us ua ud uf uj uk ul ue uw uc um uv up ug", HINTS); 21 | } else if (num_hints_needed <= 50) { 22 | split("s a d f j k l e w c m v p g hs ha rs ra rd rf rj rk rl re rw rc rm rv rp rg rh rr ru us ua ud uf uj uk ul ue uw uc um uv up ug uh ur uu", HINTS); 23 | } else if (num_hints_needed <= 80) { 24 | split("s a d f j k l e w c m v p gs ga gd gf gj gk gl ge gw gc gm gv gp gg gh gr hs ha hd hf hj hk hl he hw hc hm hv hp hg hh hr hu rs ra rd rf rj rk rl re rw rc rm rv rp rg rh rr ru us ua ud uf uj uk ul ue uw uc um uv up ug uh ur uu", HINTS); 25 | } else if (num_hints_needed <= 110) { 26 | split("s a d f j k l e w c m vs va vd vf vj vk vl ve vw vc vm vv vp vg ps pa pd pf pj pk pl pe pw pc pm pv pp pg ph pr pu gs ga gd gf gj gk gl ge gw gc gm gv gp gg gh gr gu hs ha hd hf hj hk hl he hw hc hm hv hp hg hh hr hu rs ra rd rf rj rk rl re rw rc rm rv rp rg rh rr ru us ua ud uf uj uk ul ue uw uc um uv up ug uh ur uu", HINTS); 27 | } else if (num_hints_needed <= 150) { 28 | split("s a d f j k l e ws wa wd wf wj wk cs ca cd cf cj ck cl ce cw cc cm cv cp cg ch cr cu ms ma md mf mj mk ml me mw mc mm mv mp mg mh mr mu vs va vd vf vj vk vl ve vw vc vm vv vp vg vh vr vu ps pa pd pf pj pk pl pe pw pc pm pv pp pg ph pr pu gs ga gd gf gj gk gl ge gw gc gm gv gp gg gh gr gu hs ha hd hf hj hk hl he hw hc hm hv hp hg hh hr hu rs ra rd rf rj rk rl re rw rc rm rv rp rg rh rr ru us ua ud uf uj uk ul ue uw uc um uv up ug uh ur uu", HINTS); 29 | } else if (num_hints_needed <= 200) { 30 | split("s a d f j ks ka kd kf kj kk kl ke ls la ld lf lj lk ll le lw lc lm lv lp lg lh lr lu es ea ed ef ej ek el ee ew ec em ev ep eg eh er eu ws wa wd wf wj wk wl we ww wc wm wv wp wg wh wr wu cs ca cd cf cj ck cl ce cw cc cm cv cp cg ch cr cu ms ma md mf mj mk ml me mw mc mm mv mp mg mh mr mu vs va vd vf vj vk vl ve vw vc vm vv vp vg vh vr vu ps pa pd pf pj pk pl pe pw pc pm pv pp pg ph pr pu gs ga gd gf gj gk gl ge gw gc gm gv gp gg gh gr gu hs ha hd hf hj hk hl he hw hc hm hv hp hg hh hr hu rs ra rd rf rj rk rl re rw rc rm rv rp rg rh rr ru us ua ud uf uj uk ul ue uw uc um uv up ug uh ur uu", HINTS); 31 | } else if (num_hints_needed <= 300) { 32 | split("ss sa sd sf sj sk sl se sw sc sm sv sp sg sh sr su as aa ad af aj ak al ae aw ac am av ap ag ah ar au ds da dd df dj dk dl de dw dc dm dv dp dg dh dr du fs fa fd ff fj fk fl fe fw fc fm fv fp fg fh fr fu js ja jd jf jj jk jl je jw jc jm jv jp jg jh jr ju ks ka kd kf kj kk kl ke kw kc km kv kp kg kh kr ku ls la ld lf lj lk ll le lw lc lm lv lp lg lh lr lu es ea ed ef ej ek el ee ew ec em ev ep eg eh er eu ws wa wd wf wj wk wl we ww wc wm wv wp wg wh wr wu cs ca cd cf cj ck cl ce cw cc cm cv cp cg ch cr cu ms ma md mf mj mk ml me mw mc mm mv mp mg mh mr mu vs va vd vf vj vk vl ve vw vc vm vv vp vg vh vr vu ps pa pd pf pj pk pl pe pw pc pm pv pp pg ph pr pu gs ga gd gf gj gk gl ge gw gc gm gv gp gg gh gr gu hs ha hd hf hj hk hl he hw hc hm hv hp hg hh hr hu rs ra rd rf rj rk rl re rw rc rm rv rp rg rh rr ru us ua ud uf uj uk ul ue uw uc um uv up ug uh ur uus uua uud uuf uuj uuk uul uue uuw uuc uum uuv", HINTS); 33 | } else if (num_hints_needed <= 500) { 34 | split("ss sa sd sf sj sk sl se sw sc sm sv sp sg sh sr su as aa ad af aj ak al ae aw ac am av ap ag ah ar au ds da dd df dj dk dl de dw dc dm dv dp dg dh dr du fs fa fd ff fj fk fl fe fw fc fm fv fp fg fh fr fu js ja jd jf jj jk jl je jw jc jm jv jp jg jh jr ju ks ka kd kf kj kk kl ke kw kc km kv kp kg kh kr ku ls la ld lf lj lk ll le lw lc lm lv lp lg lh lr lu es ea ed ef ej ek el ee ew ec em ev ep eg eh er eu ws wa wd wf wj wk wl we ww wc wm wv wp wg wh wr wu cs ca cd cf cj ck cl ce cw cc cm cv cp cg ch cr cu ms ma md mf mj mk ml me mw mc mm mv mp mg mh mr mu vs va vd vf vj vk vl ve vw vc vm vv vp vg vh vr vu ps pa pd pf pj pk pl pe pw pc pm pv pp pg ph pr pu gs ga gd gf gj gk gl ge gw gc gm gv gp gg gh gr gu hs ha hd hf hj hk hl he hw hc hm hv hp hg hh hr hu rs ra rd rf rj rk rl re rw rc rm rv rp rg rh rr ru us ua ud ufs ufa ufd uff ujs uja ujd ujf ujj ujk ujl uje ujw ujc ujm ujv ujp ujg ujh ujr uju uks uka ukd ukf ukj ukk ukl uke ukw ukc ukm ukv ukp ukg ukh ukr uku uls ula uld ulf ulj ulk ull ule ulw ulc ulm ulv ulp ulg ulh ulr ulu ues uea ued uef uej uek uel uee uew uec uem uev uep ueg ueh uer ueu uws uwa uwd uwf uwj uwk uwl uwe uww uwc uwm uwv uwp uwg uwh uwr uwu ucs uca ucd ucf ucj uck ucl uce ucw ucc ucm ucv ucp ucg uch ucr ucu ums uma umd umf umj umk uml ume umw umc umm umv ump umg umh umr umu uvs uva uvd uvf uvj uvk uvl uve uvw uvc uvm uvv uvp uvg uvh uvr uvu ups upa upd upf upj upk upl upe upw upc upm upv upp upg uph upr upu ugs uga ugd ugf ugj ugk ugl uge ugw ugc ugm ugv ugp ugg ugh ugr ugu uhs uha uhd uhf uhj uhk uhl uhe uhw uhc uhm uhv uhp uhg uhh uhr uhu urs ura urd urf urj urk url ure urw urc urm urv urp urg urh urr uru uus uua uud uuf uuj uuk uul uue uuw uuc uum uuv uup uug uuh uur uuu", HINTS); 35 | } else { 36 | split("ss sa sd sf sj sk sl se sw sc sm sv sp sg sh sr su as aa ad af aj ak al ae aw ac am av ap ag ah ar au ds da dd df dj dk dl de dw dc dm dv dp dg dh dr du fs fa fd ff fj fk fl fe fw fc fm fv fp fg fh fr fu js ja jd jf jj jk jl je jw jc jm jv jp jg jh jr ju ks ka kd kf kj kk kl ke kw kc km kv kp kg kh kr ku ls la ld lf lj lk ll le lw lc lm lv lp lg lh lr lu es ea ed ef ej ek el ee ew ec em ev ep eg eh er eu ws wa wd wf wj wk wl we ww wc wm wv wp wg wh wr wu cs ca cd cf cj ck cl ce cw cc cm cv cp cg ch cr cu ms ma md mf mj mk ml me mw mc mm mv mp mg mh mr mu vs va vd vf vj vk vl ve vw vc vm vv vp vg vh vr vu ps pa pd pf pj pk pl pe pw pc pm pv pp pg ph pr pu gs ga gd gf gj gk gl ge gw gc gm gv gp gg gh gr gu hs ha hd hf hj hk hls hla hld hlf hlj hlk hll hle hes hea hed hef hej hek hel hee hew hec hem hev hep heg heh her heu hws hwa hwd hwf hwj hwk hwl hwe hww hwc hwm hwv hwp hwg hwh hwr hwu hcs hca hcd hcf hcj hck hcl hce hcw hcc hcm hcv hcp hcg hch hcr hcu hms hma hmd hmf hmj hmk hml hme hmw hmc hmm hmv hmp hmg hmh hmr hmu hvs hva hvd hvf hvj hvk hvl hve hvw hvc hvm hvv hvp hvg hvh hvr hvu hps hpa hpd hpf hpj hpk hpl hpe hpw hpc hpm hpv hpp hpg hph hpr hpu hgs hga hgd hgf hgj hgk hgl hge hgw hgc hgm hgv hgp hgg hgh hgr hgu hhs hha hhd hhf hhj hhk hhl hhe hhw hhc hhm hhv hhp hhg hhh hhr hhu hrs hra hrd hrf hrj hrk hrl hre hrw hrc hrm hrv hrp hrg hrh hrr hru hus hua hud huf huj huk hul hue huw huc hum huv hup hug huh hur huu rss rsa rsd rsf rsj rsk rsl rse rsw rsc rsm rsv rsp rsg rsh rsr rsu ras raa rad raf raj rak ral rae raw rac ram rav rap rag rah rar rau rds rda rdd rdf rdj rdk rdl rde rdw rdc rdm rdv rdp rdg rdh rdr rdu rfs rfa rfd rff rfj rfk rfl rfe rfw rfc rfm rfv rfp rfg rfh rfr rfu rjs rja rjd rjf rjj rjk rjl rje rjw rjc rjm rjv rjp rjg rjh rjr rju rks rka rkd rkf rkj rkk rkl rke rkw rkc rkm rkv rkp rkg rkh rkr rku rls rla rld rlf rlj rlk rll rle rlw rlc rlm rlv rlp rlg rlh rlr rlu res rea red ref rej rek rel ree rew rec rem rev rep reg reh rer reu rws rwa rwd rwf rwj rwk rwl rwe rww rwc rwm rwv rwp rwg rwh rwr rwu rcs rca rcd rcf rcj rck rcl rce rcw rcc rcm rcv rcp rcg rch rcr rcu rms rma rmd rmf rmj rmk rml rme rmw rmc rmm rmv rmp rmg rmh rmr rmu rvs rva rvd rvf rvj rvk rvl rve rvw rvc rvm rvv rvp rvg rvh rvr rvu rps rpa rpd rpf rpj rpk rpl rpe rpw rpc rpm rpv rpp rpg rph rpr rpu rgs rga rgd rgf rgj rgk rgl rge rgw rgc rgm rgv rgp rgg rgh rgr rgu rhs rha rhd rhf rhj rhk rhl rhe rhw rhc rhm rhv rhp rhg rhh rhr rhu rrs rra rrd rrf rrj rrk rrl rre rrw rrc rrm rrv rrp rrg rrh rrr rru rus rua rud ruf ruj ruk rul rue ruw ruc rum ruv rup rug ruh rur ruu uss usa usd usf usj usk usl use usw usc usm usv usp usg ush usr usu uas uaa uad uaf uaj uak ual uae uaw uac uam uav uap uag uah uar uau uds uda udd udf udj udk udl ude udw udc udm udv udp udg udh udr udu ufs ufa ufd uff ufj ufk ufl ufe ufw ufc ufm ufv ufp ufg ufh ufr ufu ujs uja ujd ujf ujj ujk ujl uje ujw ujc ujm ujv ujp ujg ujh ujr uju uks uka ukd ukf ukj ukk ukl uke ukw ukc ukm ukv ukp ukg ukh ukr uku uls ula uld ulf ulj ulk ull ule ulw ulc ulm ulv ulp ulg ulh ulr ulu ues uea ued uef uej uek uel uee uew uec uem uev uep ueg ueh uer ueu uws uwa uwd uwf uwj uwk uwl uwe uww uwc uwm uwv uwp uwg uwh uwr uwu ucs uca ucd ucf ucj uck ucl uce ucw ucc ucm ucv ucp ucg uch ucr ucu ums uma umd umf umj umk uml ume umw umc umm umv ump umg umh umr umu uvs uva uvd uvf uvj uvk uvl uve uvw uvc uvm uvv uvp uvg uvh uvr uvu ups upa upd upf upj upk upl upe upw upc upm upv upp upg uph upr upu ugs uga ugd ugf ugj ugk ugl uge ugw ugc ugm ugv ugp ugg ugh ugr ugu uhs uha uhd uhf uhj uhk uhl uhe uhw uhc uhm uhv uhp uhg uhh uhr uhu urs ura urd urf urj urk url ure urw urc urm urv urp urg urh urr uru uus uua uud uuf uuj uuk uul uue uuw uuc uum uuv uup uug uuh uur uuu", HINTS); 37 | } 38 | 39 | hint_lookup = "" 40 | } 41 | 42 | { 43 | line = $0; 44 | output_line = ""; 45 | post_match = line; 46 | skipped_prefix = ""; 47 | 48 | # Inserts hints into `output_line` and accumulate hints in `hint_lookup` 49 | while (match(line, highlight_patterns, matches)) { 50 | pre_match = skipped_prefix substr(line, 1, RSTART - 1); 51 | post_match = substr(line, RSTART + RLENGTH); 52 | line_match = matches[0] 53 | 54 | if (line_match !~ blacklist) { 55 | # All sub-patterns start with a prefix group (sometimes empty) that should not be highlighted, e.g. 56 | # ((prefix_a)(item_a))|((prefix_b)(item_a)) 57 | # So matches array is looks like: 58 | # ||||prefix_b item_b|prefix_b|item_b| 59 | # or 60 | # |prefix_a item_a|prefix_a|item_a|||| 61 | # Unfortunately, we don't know the index of first matching group. 62 | num_groups = length(matches) / 3; # array contains: idx, idx-start, idx-length for each group 63 | for (i = 1; i <= num_groups; i++) { 64 | if (matches[i] != "") { 65 | line_match = substr(line_match, 1 + matches[++i, "length"]) 66 | pre_match = pre_match matches[i] 67 | break; 68 | } 69 | } 70 | 71 | hint = hint_by_match[line_match] 72 | if (!hint) { 73 | hint = HINTS[++n_matches] 74 | hint_by_match[line_match] = hint 75 | hint_lookup = hint_lookup hint ":" line_match "\n" 76 | } 77 | 78 | hint_len = length(hint) + hint_format_len; 79 | line_match = substr(line_match, hint_len + 1, length(line_match) - hint_len); 80 | line_match = sprintf(compound_format, hint, line_match); 81 | 82 | # Fix colors broken by the hints highlighting. 83 | # This is mostly needed to keep prompts intact, so fix first ~500 chars only 84 | if (length(output_line) < 500) { 85 | num_colors = split(pre_match, arr, /\x1b\[[0-9;]{1,9}m/, colors); 86 | post_match = join(colors, 1, 1 + num_colors, SUBSEP) post_match; 87 | } 88 | 89 | output_line = output_line pre_match line_match; 90 | skipped_prefix = ""; 91 | } else { 92 | skipped_prefix = pre_match line_match; # we need it only to fix colors 93 | } 94 | line = post_match; 95 | } 96 | 97 | printf "\n%s", (output_line skipped_prefix post_match) 98 | } 99 | 100 | END { 101 | print hint_lookup | "cat 1>&3" 102 | } 103 | -------------------------------------------------------------------------------- /tmux-picker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | function init_picker_pane() { 6 | local picker_ids=$(tmux new-window -F "#{pane_id}:#{window_id}" -P -d -n "[picker]" "/bin/sh") 7 | local picker_pane_id=$(echo "$picker_ids" | cut -f1 -d:) 8 | local picker_window_id=$(echo "$picker_ids" | cut -f2 -d:) 9 | 10 | if [[ ! -z "$last_pane_id" ]]; then # to save precious milliseconds;) 11 | local current_size=$(tmux list-panes -F "#{pane_width}:#{pane_height}:#{?pane_active,active,nope}" | grep active) 12 | local current_width=$(echo "$current_size" | cut -f1 -d:) 13 | local current_height=$(echo "$current_size" | cut -f2 -d:) 14 | 15 | local current_window_size=$(tmux list-windows -F "#{window_width}:#{window_height}:#{?window_active,active,nope}" | grep active) 16 | local current_window_width=$(echo "$current_window_size" | cut -f1 -d:) 17 | local current_window_height=$(echo "$current_window_size" | cut -f2 -d:) 18 | 19 | # this is needed to handle wrapped lines inside split windows: 20 | tmux split-window -d -t "$picker_pane_id" -h -l "$((current_window_width - current_width - 1))" '/bin/sh' 21 | tmux split-window -d -t "$picker_pane_id" -l "$((current_window_height - current_height - 1))" '/bin/sh' 22 | fi 23 | 24 | echo "$picker_pane_id:$picker_window_id" 25 | } 26 | 27 | function capture_pane() { 28 | local pane_id=$1 29 | local out_path=$2 30 | local pane_info=$(tmux list-panes -s -F "#{pane_id}:#{pane_height}:#{scroll_position}:#{?pane_in_mode,1,0}" | grep "^$pane_id") 31 | 32 | local pane_height=$(echo $pane_info | cut -d: -f2) 33 | local pane_scroll_position=$(echo $pane_info | cut -d: -f3) 34 | local pane_in_copy_mode=$(echo $pane_info | cut -d: -f4) 35 | 36 | local start_capture="" 37 | 38 | if [[ "$pane_in_copy_mode" == "1" ]]; then 39 | start_capture=$((-pane_scroll_position)) 40 | end_capture=$((pane_height - pane_scroll_position - 1)) 41 | else 42 | start_capture=0 43 | end_capture="-" 44 | fi 45 | 46 | tmux capture-pane -e -J -p -t $pane_id -E $end_capture -S $start_capture > $out_path 47 | } 48 | 49 | function pane_exec() { 50 | local pane_id=$1 51 | local pane_command=$2 52 | 53 | tmux send-keys -t $pane_id " $pane_command" 54 | tmux send-keys -t $pane_id Enter 55 | } 56 | 57 | function prompt_picker_for_pane() { 58 | local current_pane_id=$1 59 | local last_pane_id=$2 60 | local picker_init_data=$(init_picker_pane "$last_pane_id") 61 | local picker_pane_id=$(echo "$picker_init_data" | cut -f1 -d':') 62 | local picker_window_id=$(echo "$picker_init_data" | cut -f2 -d':') 63 | local tmp_path=$(mktemp) 64 | 65 | capture_pane "$current_pane_id" "$tmp_path" 66 | pane_exec "$picker_pane_id" "$CURRENT_DIR/hint_mode.sh \"$current_pane_id\" \"$picker_pane_id\" \"$last_pane_id\" \"$picker_window_id\" $tmp_path" 67 | 68 | echo $picker_pane_id 69 | } 70 | 71 | last_pane_id=$(tmux display -pt':.{last}' '#{pane_id}' 2>/dev/null) 72 | current_pane_id=$(tmux list-panes -F "#{pane_id}:#{?pane_active,active,nope}" | grep active | cut -d: -f1) 73 | picker_pane_id=$(prompt_picker_for_pane "$current_pane_id" "$last_pane_id") 74 | 75 | -------------------------------------------------------------------------------- /tmux-picker.tmux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # HELPERS 5 | # 6 | 7 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | TMUX_PRINTER="$CURRENT_DIR/tmux-printer/tmux-printer" 9 | 10 | function set_tmux_env() { 11 | local option_name="$1" 12 | local final_value="$2" 13 | 14 | tmux setenv -g "$option_name" "$final_value" 15 | } 16 | 17 | function process_format () { 18 | echo -ne "$($TMUX_PRINTER "$1")" 19 | } 20 | 21 | function array_join() { 22 | local IFS="$1"; shift; echo "$*"; 23 | } 24 | 25 | # 26 | # CONFIG 27 | # 28 | 29 | # Every pattern have be of form ((A)B) where: 30 | # - A is part that will not be highlighted (e.g. escape sequence, whitespace) 31 | # - B is part will be highlighted (can contain subgroups) 32 | # 33 | # Valid examples: 34 | # (( )([a-z]+)) 35 | # (( )[a-z]+) 36 | # (( )(http://)[a-z]+) 37 | # (( )(http://)([a-z]+)) 38 | # (( |^)([a-z]+)) 39 | # (( |^)(([a-z]+)|(bar))) 40 | # ((( )|(^))|(([a-z]+)|(bar))) 41 | # (()([0-9]+)) 42 | # (()[0-9]+) 43 | # 44 | # Invalid examples: 45 | # (([0-9]+)) 46 | # ([0-9]+) 47 | # [0-9]+ 48 | 49 | CS=$'\x1b'"\[[0-9;]{1,9}m" # color escape sequence 50 | FILE_CHARS="[[:alnum:]_.#$%&+=/@~-]" 51 | FILE_START_CHARS="[[:space:]:<>)(&#'\"]" 52 | 53 | # default patterns group 54 | PATTERNS_LIST1=( 55 | "(($CS|^|$FILE_START_CHARS)$FILE_CHARS*/$FILE_CHARS+)" # file paths with / 56 | "(()[0-9]+\.[0-9]{3,}|[0-9]{5,})" # long numbers 57 | "(()[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})" # UUIDs 58 | "(()[0-9a-f]{7,40})" # hex numbers (e.g. git hashes) 59 | "(()(https?://|git@|git://|ssh://|ftp://|file:///)[[:alnum:]?=%/_.:,;~@!#$&)(*+-]*)" # URLs 60 | "(()[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})" # IP adresses 61 | "(()0x[0-9a-fA-F]+)" # hex numbers 62 | ) 63 | 64 | # alternative patterns group (shown after pressing the SPACE key) 65 | PATTERNS_LIST2=( 66 | "(($CS|^|$FILE_START_CHARS)$FILE_CHARS*/$FILE_CHARS+)" # file paths with / 67 | "(($CS|^|$FILE_START_CHARS)$FILE_CHARS{5,})" # anything that looks like file/file path but not too short 68 | "(()(https?://|git@|git://|ssh://|ftp://|file:///)[[:alnum:]?=%/_.:,;~@!#$&)(*+-]*)" # URLs 69 | ) 70 | 71 | # items that will not be hightlighted 72 | BLACKLIST=( 73 | "(deleted|modified|renamed|copied|master|mkdir|[Cc]hanges|update|updated|committed|commit|working|discard|directory|staged|add/rm|checkout)" 74 | ) 75 | 76 | # "-n M-f" for Alt-F without prefix 77 | # "f" for prefix-F 78 | PICKER_KEY="-n M-f" 79 | 80 | set_tmux_env PICKER_PATTERNS1 $(array_join "|" "${PATTERNS_LIST1[@]}") 81 | set_tmux_env PICKER_PATTERNS2 $(array_join "|" "${PATTERNS_LIST2[@]}") 82 | set_tmux_env PICKER_BLACKLIST_PATTERNS $(array_join "|" "${BLACKLIST[@]}") 83 | 84 | set_tmux_env PICKER_COPY_COMMAND "xclip -f -in -sel primary | xclip -in -sel clipboard" 85 | set_tmux_env PICKER_COPY_COMMAND_UPPERCASE "bash -c 'arg=\$(cat -); tmux split-window -h -c \"#{pane_current_path}\" vim \"\$arg\"'" 86 | 87 | #set_tmux_env PICKER_HINT_FORMAT $(process_format "#[fg=color0,bg=color202,dim,bold]%s") 88 | set_tmux_env PICKER_HINT_FORMAT $(process_format "#[fg=black,bg=red,bold]%s") 89 | set_tmux_env PICKER_HINT_FORMAT_NOCOLOR "%s" 90 | 91 | #set_tmux_env PICKER_HIGHLIGHT_FORMAT $(process_format "#[fg=black,bg=color227,normal]%s") 92 | set_tmux_env PICKER_HIGHLIGHT_FORMAT $(process_format "#[fg=black,bg=yellow,bold]%s") 93 | 94 | # 95 | # BIND 96 | # 97 | 98 | tmux bind $(echo "$PICKER_KEY") run-shell "$CURRENT_DIR/tmux-picker.sh" 99 | 100 | -------------------------------------------------------------------------------- /tmux-printer/README.md: -------------------------------------------------------------------------------- 1 | This is a copy of https://github.com/Morantron/tmux-printer 2 | -------------------------------------------------------------------------------- /tmux-printer/tmux-printer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | if [[ $1 == '--debug' ]]; then 6 | echo "$*" | gawk -f $CURRENT_DIR/tmux-printer.awk 7 | else 8 | eval "echo -e \"$(echo "$*" | gawk -f $CURRENT_DIR/tmux-printer.awk)\"" 9 | fi 10 | -------------------------------------------------------------------------------- /tmux-printer/tmux-printer.awk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env gawk 2 | 3 | function tput_color(statement) 4 | { 5 | if (!match(statement, "(bg|fg)=(.*)", color_groups)) 6 | return statement 7 | 8 | layer=color_groups[1] 9 | color=color_groups[2] 10 | 11 | if (layer == "bg") { 12 | applied_styles[current_bg] = "0" 13 | current_bg=statement 14 | applied_styles[current_bg] = "1" 15 | capname="setab" 16 | } else { 17 | applied_styles[current_fg] = "0" 18 | current_fg=statement 19 | applied_styles[current_fg] = "1" 20 | capname="setaf" 21 | } 22 | 23 | if (match(color, "colou?r([0-9]+)", color_code_groups)) { 24 | params = color_code_groups[1] 25 | } 26 | 27 | if (color == "black") 28 | params="0" 29 | if (color == "red") 30 | params="1" 31 | if (color == "green") 32 | params="2" 33 | if (color == "yellow") 34 | params="3" 35 | if (color == "blue") 36 | params="4" 37 | if (color == "magenta") 38 | params="5" 39 | if (color == "cyan") 40 | params="6" 41 | if (color == "white") 42 | params="7" 43 | if (color == "default") { 44 | if (layer == "bg") 45 | applied_styles[current_bg] = "0" 46 | else 47 | applied_styles[current_fg] = "0" 48 | 49 | return reset_to_applied_styles() 50 | } 51 | 52 | return "$(tput " capname " " params ")" 53 | } 54 | 55 | function reset_to_applied_styles() { 56 | style_output = "$(tput sgr0)" 57 | 58 | for (applied_style in applied_styles) { 59 | if (applied_styles[applied_style] == "1") { 60 | style_output = style_output tput(applied_style) 61 | } 62 | } 63 | 64 | return style_output 65 | } 66 | 67 | function unset_applied_styles() { 68 | for (applied_style in applied_styles) { 69 | if (applied_styles[applied_style] == "1") { 70 | applied_styles[applied_style] = "0" 71 | } 72 | } 73 | 74 | return "$(tput sgr0)" 75 | } 76 | 77 | function tput_style(statement) { 78 | match(statement, /(no)?(.*)/, style_groups) 79 | 80 | disable_style = style_groups[1] == "no" 81 | style_to_apply = style_groups[2] 82 | style_output = "" 83 | 84 | if (statement == "none") { 85 | return unset_applied_styles() 86 | } 87 | 88 | if (disable_style) { 89 | applied_styles[style_to_apply]="0" 90 | style_to_apply = "" 91 | style_output = reset_to_applied_styles() 92 | } else { 93 | applied_styles[style_to_apply] = "1" 94 | } 95 | 96 | if (style_to_apply == "bright") 97 | style_output = "$(tput bold)" 98 | if (style_to_apply == "bold") 99 | style_output = "$(tput bold)" 100 | if (style_to_apply == "dim") 101 | style_output = "$(tput dim)" 102 | if (style_to_apply == "underscore") 103 | style_output = "$(tput smul)" 104 | if (style_to_apply == "reverse") 105 | style_output = "$(tput rev)" 106 | if (style_to_apply == "italics") 107 | style_output = "$(tput sitm)" 108 | 109 | return style_output 110 | } 111 | 112 | function tput(statement) 113 | { 114 | if (statement ~ /^(bg|fg)=/) 115 | return tput_color(statement) 116 | else 117 | return tput_style(statement) 118 | } 119 | 120 | BEGIN { 121 | PATTERN_FORMAT="#\\[([^\\]]*)\\]" 122 | } 123 | 124 | { 125 | input = $0 126 | output = "" 127 | 128 | pos = 1 129 | prev_pos = pos 130 | 131 | while (match(input, PATTERN_FORMAT, input_groups)) { 132 | prev_pos = pos 133 | pos += (RSTART + RLENGTH - 1) 134 | rstart = RSTART 135 | 136 | styles_raw = input_groups[1] 137 | split(styles_raw, styles, ",") 138 | 139 | for (i in styles) { 140 | output_styles = output_styles tput(styles[i]) 141 | } 142 | 143 | from = prev_pos 144 | to = rstart - 1 145 | 146 | output = output substr($0, from, to) output_styles 147 | input = substr($0, pos) 148 | } 149 | 150 | output = output substr($0, pos) "$(tput sgr0)" 151 | } 152 | 153 | END { 154 | print output 155 | } 156 | --------------------------------------------------------------------------------