├── 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 | 
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 |
--------------------------------------------------------------------------------