-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrgf
executable file
·346 lines (301 loc) · 12.9 KB
/
rgf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#!/usr/bin/env bash
# adapted from https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interactive-ripgrep-launcher
## Interactive Ripgrep
# on 'Enter' open the file in less
# Switch between Ripgrep mode and fzf filtering mode (Ctrl+T)
# Nested search with (Ctrl+F)
## Usage
# rgf [options] file search_words/regexes...
#
# `rgf` similar to `rg | fzf`, search through all files
# `rgf [filename]` search in a file, similar to `less`
# `CMD | rgf` pipe the output of CMD to `rgf`
## Prerequisite
# rg: https://github.com/BurntSushi/ripgrep
# fzf: https://github.com/junegunn/fzf
# bat: https://github.com/sharkdp/bat
# companion script fzf_previewer
## known issue
# 1. ctrl-F no response, this can happen when the number of records is too large. Try to apply more fileters to reduce the searching pool
# 2. in nested search mode, rg will match filename, while it is possible to manipulate the regex to skip filename:line_num, I decided to
# keep this behaviour to provide the ability to filter over filenames
KEY_BINDING=$(cat << EOF
key-bindings:
F1 Help
Ctrl+Space Toggle between rg and fzf
Ctrl+F Search in current mattching records, could fail if the number of records is too large
F12 Search in file context. You can choose a line in the context and perform a search against the selected line
Ctrl+/ Change preview window position or close it
Ctrl+D Clear query
Enter Use 'less' or Vscode to peek the current record
Alt-Enter Print the selected records to CLI
Alt+Left / Alt+Right Go to the begining / end of query prompt
Alt+Up / Alt+down Scroll preview window
EOF
)
show_help() {
cat << EOF
Interactive Ripgrep / FZF
usage:
rgf [options] file patterns... # both file and pattern are optional
rgf [options] patterns... # if the first positional parameter is not a file, it will be treated as part of the pattern
CMD | rgf [options] # can also use from pipe
In rg mode, you can use '!' to invert the search, e.g., rg> !foo will match anything but foo
options:
-h|--help display this help message
--rg start in rg mode, this is default when searching all files under the directory
--fzf start in fzf mode, this is default when searching in a particular file
--header [n] freeze the first n line in preview window as header, n is default to 1 when not provided,
e.g., 'ps -ef | rgf --header' freezes the first line in preview to provide column information
a convenient shothand for fzf's --header-lines option
-s|--silent useful when you want to pipe the search result to other command,
by default, rgf prints out the searching pattern or peeking file path to provide a trace
Case search sensitivity:
Default: Smart case Searches case insensitively if the pattern is all lowercase. Search case sensitively otherwise.
-s|--case: Search case sensitively
-i|--ignore-case: Search case insensitively
Other options are passed to the underlying rg and fzf
!! IMPORTANT: for key-value options, use opt=value or the script may fail
Note that fzf performs search on top of rg's result, thus --glob / --type will affect the searching pool for both fzf and rg
but some other options such as rg's "--fixed-strings" will have no effect on fzf. Generally, fzf's options will not affect rg
-z-*| -z--* fzf options, e.g., rgf -z+i => fzf +i # fzf case sensitive mode, see man fzf
-*|--* rg options, e.g., rgf --glob='*.c' # glob will be parsed by rg, see man rg
EOF
echo "$KEY_BINDING"
echo
exit 0
}
PASS_THROUGH_OPT=''
SILENT=''
FZF_OPT=""
RG_PREFIX="rg --line-number --no-heading --color=always --smart-case"
NOT_FOUND_MSG="[[ regex:/\$FZF_QUERY/ not found ]]"
PRETTY_BAT='bat --force-colorization --terminal-width $(tput cols) --paging=always'
BAT_STYLE='--style=grid,numbers,header'
PREVIEW_HEADER_LINE_FILE=',~3'
PREVIEW_HEADER_LINE_PIPE=''
PATTERN_SETUP="PATTERN=\$([ -z {q} ] && echo '' || [[ \$FZF_PROMPT =~ 'rg>' ]] && echo \"-p {q}\" | sed -E -e 's+\\\\d+[0-9]+g' || \
echo \"-p \$(echo {q} | sed -E -e 's/\\s+/|/g' | sed -e 's/^|//; s/|$//')\")"
PROMPT_SETUP="PROMPT=\$([[ \$FZF_PROMPT =~ 'rg>' ]] && echo 'rg:' || echo 'fzf:') "
PREFIX_SETUP="$PROMPT_SETUP ; new_prefix=\$([ -z {q} ] && echo '' || echo [{q}]) ; echo \$PROMPT {q} "
TRUNCATE_LINE="awk '{ if (length(\$0) > 201) print substr(\$0, 1, 201) \" [...]\"; else print \$0; }'"
PREVIEW_FILE='{1}'
LINE_NUM='{2}'
N_TH='3..'
HINT='Ctrl-Space: toggle rg/fzf, Ctrl-F: nested search, F1: help'
CTX_PEEK='FALSE'
start_with_fzf()
{
IS_FZF_DISABLED=''
UNBIND_CHANGE='+unbind(change)'
START_PROMPT='fzf'
START_SEARCH="''"
}
start_with_rg()
{
IS_FZF_DISABLED='--disabled'
UNBIND_CHANGE=''
START_PROMPT='rg'
START_SEARCH="{q}"
}
# default, start with fzf
start_with_fzf
# parse options
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
exit 0
;;
--rg)
shift
PASS_THROUGH_OPT="$PASS_THROUGH_OPT --rg"
start_with_rg
;;
--fzf)
shift
PASS_THROUGH_OPT="$PASS_THROUGH_OPT --fzf"
start_with_fzf
;;
-s|--silent)
PASS_THROUGH_OPT="$PASS_THROUGH_OPT -s"
SILENT='>/dev/null'
PREFIX_SETUP="$PREFIX_SETUP $SILENT"
shift
;;
--header)
shift
if [[ $2 =~ ^[0-9]+$ ]]; then
ln=$2
shift
else
ln=1
fi
PREVIEW_HEADER_LINE_PIPE=",~$((ln+1))"
FZF_OPT="$FZF_OPT --header-lines $ln"
;;
-i|--ignore-case)
shift
PASS_THROUGH_OPT="$PASS_THROUGH_OPT -i"
FZF_OPT="$FZF_OPT -i"
RG_PREFIX="$RG_PREFIX -i"
;;
-s|--case)
shift
PASS_THROUGH_OPT="$PASS_THROUGH_OPT --case"
FZF_OPT="$FZF_OPT +i"
RG_PREFIX="$RG_PREFIX -s"
;;
--PASS-THROUGH)
PASS_THROUGH_FILE=$2
shift 2
;;
--PREFIX)
prefix="$2 && "
shift 2
;;
--context-peek)
CTX_LINE=$2
shift 2
second_last=$(( $# - 1 ))
CTX_FILE="${!second_last}"
CTX_PEEK='TRUE'
[[ ! $CTX_FILE =~ "/tmp/rg-fzf-pipe-" ]] && CTX_FILE_LABEL="--border=top --border-label-pos=2 --border-label=File:$CTX_FILE"
# options in CTX_FZF_OPT cannot contains whitespace, e.g, '--change-prompt(fzf> )' won't work
CTX_FZF_OPT="--bind start:+unbind(f12,change,ctrl-space,alt-enter) --bind load:pos($CTX_LINE)+change-preview-window(hidden) --disabled --no-multi $CTX_FILE_LABEL"
;;
-z-*|-z+*)
FZF_OPT="$FZF_OPT ${1:2}" # remove -z part
shift
;;
-*)
RG_PREFIX="$RG_PREFIX $1"
shift
;;
*)
break
;;
esac
done
set -- "$@"
PASS_THROUGH_FILE="${PASS_THROUGH_FILE:='//'}" # provide a default value
main() {
local file_path=''
[ -f "$1" ] && file_path="'$1'" && shift
local context_file="${file_path:-'{1}'}"
local EXECUTE_PEEK_FILE=""
if [[ $TERM_PROGRAM == 'vscode' ]]; then
# use vscode
EXECUTE_PEEK_FILE="execute-silent([[ {} != '$NOT_FOUND_MSG' ]] && code -g $PREVIEW_FILE:$LINE_NUM && echo peek: {} $SILENT | $TRUNCATE_LINE)"
else
EXECUTE_PEEK_FILE="execute([[ {} != '$NOT_FOUND_MSG' ]] && $PATTERN_SETUP ; $PRETTY_BAT $PREVIEW_FILE --highlight-line $LINE_NUM --pager \"less -R +$LINE_NUM \$PATTERN\" 2>/dev/null && echo peek: {} $SILENT | $TRUNCATE_LINE)"
fi
CTRL_F_ACTION="select-all+execute($PREFIX_SETUP ; printf '%s\n' {+} | rgf --PREFIX \"${prefix}\$new_prefix\" --PASS-THROUGH $PASS_THROUGH_FILE $PASS_THROUGH_OPT)+clear-selection"
CTRL_D_ACTION="clear-query"
TAB_ACTION='toggle'
if [[ $CTX_PEEK == 'TRUE' ]]; then
EXECUTE_PEEK_FILE="become(echo {q})"
CTRL_F_ACTION="clear-query+enable-search+change-prompt(Search enabled | fzf> )+change-preview-window(top,50%)+change-header(Ctrl-D: reset, Tab: copy selection)"
CTRL_D_ACTION="clear-query+enable-search+change-prompt(Search enabled | fzf> )+change-preview-window(hidden)+change-header(Ctrl-D: reset, Tab: copy selection)"
TAB_ACTION="transform-query(echo {2..})"
fi
rm -f /tmp/rg-fzf-{r,f}
INITIAL_QUERY="${*:-}"
: | fzf --ansi --query "$INITIAL_QUERY" \
--layout=reverse-list \
--exact --no-sort \
--track \
--pointer='=>' \
--marker='# ' \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--multi \
--delimiter : \
--nth=$N_TH \
\
--prompt "${prefix}${START_PROMPT}> " \
--header "$HINT" \
\
--bind "start:reload($RG_PREFIX $START_SEARCH $file_path || echo $NOT_FOUND_MSG)$UNBIND_CHANGE" \
--bind "change:reload:sleep 0.1; ([[ {q} =~ ^! ]] && $RG_PREFIX -v \"\$(echo {q} | sed -E -e 's/^!//')\" $file_path || $RG_PREFIX {q} $file_path) || echo $NOT_FOUND_MSG" \
\
--bind "home:first" \
--bind "end:last" \
--bind 'ctrl-/:change-preview-window(up,50%,border-bottom|right,50%,border-left|hidden|)' \
--bind "ctrl-space:transform:[[ ! \$FZF_PROMPT =~ 'rg>' ]] &&
echo \"disable-search+rebind(change)+reload($RG_PREFIX \\{q} $file_path || echo $NOT_FOUND_MSG)+change-prompt(${prefix}rg> )\" ||
echo \"unbind(change)+reload($RG_PREFIX '' $file_path )+change-prompt(${prefix}fzf> )+enable-search\" " \
--bind "ctrl-f:$CTRL_F_ACTION" \
--bind "ctrl-d:$CTRL_D_ACTION" \
--bind "f1:execute:echo \"$KEY_BINDING\" | less" \
--bind "f12:execute(CTX_QUERY=\$(rgf --context-peek $LINE_NUM $context_file {$N_TH}) && rgf --PREFIX '((context search))' --rg --PASS-THROUGH $PASS_THROUGH_FILE $PASS_THROUGH_OPT $file_path \$CTX_QUERY)" \
--bind "alt-left:beginning-of-line,alt-right:end-of-line" \
--bind "alt-up:preview-up,alt-down:preview-down" \
--bind "tab:$TAB_ACTION" \
\
--preview "bat $BAT_STYLE --terminal-width \$FZF_PREVIEW_COLUMNS --force-colorization $PREVIEW_FILE --highlight-line $LINE_NUM 2>/dev/null || fzf_previewer {}" \
--preview-window "up,80%,border-bottom,wrap,+$LINE_NUM+3/3$PREVIEW_HEADER_LINE_FILE" \
\
--bind "enter:$EXECUTE_PEEK_FILE" \
--bind "alt-enter:become( [[ $PASS_THROUGH_FILE == '//' ]] && printf '%s\n' {+1..2} || printf '%s\n' {+2..} )" \
$IS_FZF_DISABLED \
$CTX_FZF_OPT \
--bind "focus:$NAV_CHANGE_QUERY" \
$FZF_OPT \
} # end of main()
if [[ $CTX_PEEK == 'TRUE' ]]; then
# run in context peek (first stage of F12)
LINE_NUM='{1}'
N_TH='2..'
START_PROMPT="select context"
HINT="Ctrl-F: turn on search, Ctrl-D: reset"
PREVIEW_FILE=$1
NAV_CHANGE_QUERY="disable-search+transform-query(echo {2..})+change-prompt($START_PROMPT> )+change-header($HINT)"
main "$@"
elif ! [ -p /dev/stdin ]; then
# Run from cli or second stage of context search
if [[ "$PASS_THROUGH_FILE" =~ "//" ]]; then
# top level input is not pipe
if [ -f "$1" ]; then
# search in a pariticular file
RG_PREFIX="$RG_PREFIX --with-filename"
else
# search in all files, start with rg for best performance unless --fzf is used
[[ ! $PASS_THROUGH_OPT =~ '--fzf' ]] && start_with_rg
fi
main "$@"
else
# context search when top level input from pipe
RG_PREFIX="$RG_PREFIX --no-filename"
BAT_STYLE="--style=grid,numbers"
PREVIEW_HEADER_LINE_FILE="$PREVIEW_HEADER_LINE_PIPE"
PREVIEW_FILE="$PASS_THROUGH_FILE"
LINE_NUM='{1}'
N_TH='2..'
main "$@"
fi
else
# Run from pipeline
TEMP_FILE="/tmp/rg-fzf-pipe-$$"
rm -f $TEMP_FILE
cat > $TEMP_FILE
if [[ "$PASS_THROUGH_FILE" == '//' ]]; then
# nested search mode when top level is a file or not specified
RG_PREFIX="$RG_PREFIX --no-filename --no-line-number"
else
if [[ "$PASS_THROUGH_FILE" =~ "/tmp/rg-fzf-pipe-" ]]; then
# nested search mode when top level is pipe input
RG_PREFIX="$RG_PREFIX --no-filename --no-line-number"
else
# top level entry with input from pipe
PASS_THROUGH_FILE="$TEMP_FILE"
fi
# common when top level is pipe
BAT_STYLE="--style=grid,numbers"
PREVIEW_HEADER_LINE_FILE="$PREVIEW_HEADER_LINE_PIPE"
PREVIEW_FILE="$PASS_THROUGH_FILE"
LINE_NUM='{1}'
N_TH='2..'
fi
main $TEMP_FILE "$@"
rm -f $TEMP_FILE
fi