]> git.lizzy.rs Git - local-nhentai.git/blob - fzf-previews
Generic stats and select
[local-nhentai.git] / fzf-previews
1 #!/usr/bin/env bash
2 # fzf-ueberzogen.sh is a wrapper script which allows to use ueberzug with fzf.
3 # Copyright (C) 2019  Nico Baeurer
4
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 readonly BASH_BINARY="$(which bash)"
18 readonly REDRAW_COMMAND="toggle-preview+toggle-preview"
19 readonly REDRAW_KEY="ยต"
20 declare -r -x DEFAULT_PREVIEW_POSITION="right"
21 declare -r -x UEBERZUG_FIFO="$(mktemp --dry-run --suffix "fzf-$$-ueberzug")"
22 declare -r -x PREVIEW_ID="preview"
23
24
25 function STORE_TERMINAL_SIZE_IN {
26     # Usage: STORE_TERMINAL_SIZE_IN 
27     #           lines_variable columns_variable
28     [[ ! -v "$1" || ! -v "$2" ]] && return 1
29     < <(</dev/tty stty size) \
30         read "$1" "$2"
31 }
32
33
34 function STORE_FZF_HEIGHT_IN {
35     # Usage: STORE_FZF_HEIGHT_IN
36     #           fzf_height_lines_variable
37     #           terminal_lines fzf_height fzf_min_height
38     [[ $# -ne 4 || ! -v "$1" ]] && return 1
39     local -n _fzf_height_lines="$1"
40     local terminal_lines="$2"
41     local fzf_height_text="$3"
42     local fzf_min_height="$4"
43
44     _fzf_height_lines="${fzf_height_text}"
45
46     if [[ "${fzf_height_text}" == *"%" ]]; then
47         ((_fzf_height_lines=(terminal_lines * ${fzf_height_text%\%}) / 100))
48     else
49         ((_fzf_height_lines=_fzf_height_lines > terminal_lines ? terminal_lines :_fzf_height_lines))
50     fi
51
52     ((_fzf_height_lines=fzf_min_height > _fzf_height_lines
53                         ? fzf_min_height : _fzf_height_lines))
54 }
55
56
57 function STORE_FZF_OFFSET_IN {
58     # Usage: STORE_FZF_OFFSET_IN
59     #           fzf_offset_y_variable
60     #           terminal_lines fzf_height fzf_start_offset_y
61     [[ $# -ne 4 || ! -v "$1" ]] && return 1
62     local -n _fzf_offset_y="$1"
63     local terminal_lines="$2"
64     local fzf_height="$3"
65     local fzf_start_offset_y="$4"
66
67     # Two cases:
68     # 1. There isn't enough space, so fzf will print blank lines.
69     #    -> OFFSET_Y = terminal height - required lines
70     # 2. There is enough space -> OFFSET_Y = START_OFFSET_Y
71     ((_fzf_offset_y=terminal_lines - fzf_height))
72     ((_fzf_offset_y=_fzf_offset_y < fzf_start_offset_y
73                     ? _fzf_offset_y : fzf_start_offset_y))
74 }
75
76
77 function STORE_PREVIEW_POSITION_IN {
78     # Usage: STORE_PREVIEW_POSITION_IN
79     #           preview_y_variable preview_x_variable
80     #           preview_position fzf_offset_y fzf_height
81     #           terminal_width preview_height preview_width
82     [[ $# -ne 8 || ! -v "$1" || ! -v "$2" ]] && return 1
83     local -n _preview_y="$1"
84     local -n _preview_x="$2"
85     local preview_position="$3"
86     local fzf_offset_y="$4"
87     local fzf_height="$5"
88     local terminal_width="$6"
89     local preview_height="$7"
90     local preview_width="$8"
91
92     case "${preview_position}" in
93         left|up|top)
94             _preview_x=2
95             _preview_y=$((1 + fzf_offset_y))
96             ;;
97         right)
98             _preview_x=$((terminal_width - preview_width - 2))
99             _preview_y=$((1 + fzf_offset_y))
100             ;;
101         down|bottom)
102             _preview_x=2
103             _preview_y=$((fzf_offset_y + fzf_height - preview_height - 1))
104             ;;
105     esac
106 }
107
108
109 function DRAW_PREVIEW {
110         local preview_path=""
111
112         if test -f "${@}/001.jpg"; then
113                 preview_path="${@}/001.jpg"
114         elif test -f "${@}/001.png"; then
115                 preview_path="${@}/001.png"
116         else
117                 return
118         fi
119
120     # Usage: DRAW_PREVIEW path
121     local -A add_preview_command=( \
122         [identifier]="${PREVIEW_ID}" \
123         [scaler]=contain \
124         [path]="${preview_path}")
125     ADD_PLACEMENT add_preview_command
126 }
127
128
129 function CLEAR_PREVIEW {
130     # Usage: CLEAR_PREVIEW
131     REMOVE_PLACEMENT "${PREVIEW_ID}"
132 }
133
134
135 function IDENTITY_RECT {
136     # Usage: IDENTITY_RECT
137     #           placement_rect_variable
138     [[ $# -ne 1 ]] && return 1
139 }
140
141
142 function ADD_PLACEMENT {
143     # Usage: ADD_PLACEMENT
144     #           add_command_variable [adjust_rect_function]
145     # references can't be checked.. -v doesn't seem to support associative arrays..
146     local terminal_lines= terminal_columns=
147     local fzf_height= fzf_offset_y=
148     local preview_y= preview_x=
149     local preview_height="${LINES}" preview_width="${COLUMNS}"
150     STORE_TERMINAL_SIZE_IN \
151         terminal_lines terminal_columns
152     STORE_FZF_HEIGHT_IN \
153         fzf_height \
154         "$terminal_lines" "${FZF_HEIGHT}" "${FZF_MIN_HEIGHT}"
155     STORE_FZF_OFFSET_IN \
156         fzf_offset_y \
157         "$terminal_lines" "${fzf_height}" "${FZF_START_OFFSET_Y}"
158     STORE_PREVIEW_POSITION_IN \
159         preview_y preview_x \
160         "${PREVIEW_POSITION:-${DEFAULT_PREVIEW_POSITION}}" \
161         "${fzf_offset_y}" "${fzf_height}" "${terminal_columns}" \
162         "${preview_height}" "${preview_width}"
163
164     local _add_command_nameref="$1"
165     local -n _add_command="${_add_command_nameref}"
166     local adjust_rect_callback="${2:-IDENTITY_RECT}"
167     local -A adjusted_placement_rect=( \
168         [y]="${preview_y}" [x]="${preview_x}" \
169         [height]="${preview_height}" [width]="${preview_width}")
170     "${adjust_rect_callback}" adjusted_placement_rect
171     _add_command[action]=add
172     _add_command[x]="${adjusted_placement_rect[x]}"
173     _add_command[y]="${adjusted_placement_rect[y]}"
174     _add_command[width]="${adjusted_placement_rect[width]}"
175     _add_command[height]="${adjusted_placement_rect[height]}"
176
177     >"${UEBERZUG_FIFO}" \
178         declare -p "${_add_command_nameref}"
179 }
180
181
182 function REMOVE_PLACEMENT {
183     # Usage: REMOVE_PLACEMENT placement-id
184     [[ $# -ne 1 ]] && return 1
185     >"${UEBERZUG_FIFO}" \
186         declare -A -p _remove_command=( \
187         [action]=remove [identifier]="${1}")
188 }
189
190
191 function is_option_key [[ "${@}" =~ ^(\-.*|\+.*) ]]
192 function is_key_value [[ "${@}" == *=* ]]
193
194
195 function store_options_map_in {
196     # Usage: store_options_map_in
197     #           options_map_variable options_variable
198     # references can't be checked.. -v doesn't seem to support associative arrays..
199     [[ $# -ne 2 || ! -v "$2" ]] && return 1
200     local -n _options_map="${1}"
201     local -n _options="${2}"
202
203     for ((i=0; i < ${#_options[@]}; i++)); do
204         local key="${_options[$i]}" next_key="${_options[$((i + 1))]:---}"
205         local value=true
206         is_option_key "${key}" || \
207             continue
208         if is_key_value "${key}"; then
209             <<<"${key}" \
210                 IFS='=' read key value
211         elif ! is_option_key "${next_key}"; then
212             value="${next_key}"
213         fi
214         _options_map["${key}"]="${value}"
215     done
216 }
217
218
219 function process_options {
220     # Usage: process_options command-line-arguments
221     local -a "default_options=(${FZF_DEFAULT_OPTS})"
222     local -a script_options=("${@}")
223     local -A mapped_options
224     store_options_map_in mapped_options default_options
225     store_options_map_in mapped_options script_options 
226
227     local cursor_y= cursor_x=
228     store_cursor_position_in cursor_y cursor_x
229     # If fzf is used as completion tool we will get the position of the prompt.
230     # If it's normally used we get the position the output will be displayed at.
231     # If it's normally used we need to subtract one to get the position of the prompt.
232     ((cursor_y=cursor_x != 1 ? cursor_y : cursor_y - 1))
233     declare -g -r -x FZF_START_OFFSET_Y="${cursor_y}"
234     declare -g -r -x PREVIEW_POSITION="${mapped_options[--preview-window]%%:[^:]*}"
235     declare -g -r -x FZF_HEIGHT="${mapped_options[--height]:-100%}"
236     declare -g -r -x FZF_MIN_HEIGHT="${mapped_options[--min-height]:-10}"
237 }
238
239
240 function store_cursor_position_in {
241     # Usage: store_cursor_pos_in
242     #           y_variable x_variable
243     # based on https://github.com/dylanaraps/pure-bash-bible#get-the-current-cursor-position
244     [[ ! -v "$1" || ! -v "$2" ]] && return 1
245     </dev/tty &>/dev/tty \
246         IFS='[;' \
247         read -p $'\e[6n' -d R -rs _ "${1}" "${2}" _
248 }
249
250
251 function start_ueberzug {
252     # Usage: start_ueberzug
253     mkfifo "${UEBERZUG_FIFO}"
254     <"${UEBERZUG_FIFO}" \
255         ueberzug layer --parser bash --silent &
256     # prevent EOF
257     3>"${UEBERZUG_FIFO}" \
258         exec
259 }
260
261
262 function finalise {
263     # Usage: finalise
264     3>&- \
265         exec
266     &>/dev/null \
267         rm "${UEBERZUG_FIFO}"
268     &>/dev/null \
269         kill $(jobs -p)
270 }
271
272
273 function print_on_winch {
274     # Usage: print_on_winch text
275     # print "$@" to stdin on receiving SIGWINCH
276     # use exec as we will only kill direct childs on exiting,
277     # also the additional bash process isn't needed
278     </dev/tty \
279         exec perl -e '
280             require "sys/ioctl.ph";
281             while (1) {
282                 local $SIG{WINCH} = sub {
283                     ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV;
284                 };
285                 sleep;
286             }' \
287             "${@}" &
288 }
289
290
291 function export_functions {
292     # Usage: export_functions
293     # Exports all functions with a name
294     # which only consists of underscores,
295     # figures, upper case charactars
296     local -a function_names="( $(compgen -A function) )"
297
298     for function_name in "${function_names[@]}"; do
299         [[ "${function_name}" =~ ^[A-Z0-9_]+$ ]] && {
300             export -f "${function_name}"
301         }
302     done
303 }
304
305
306 if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
307     trap finalise EXIT
308     process_options "${@}"
309     # print the redraw key twice as there's a run condition we can't circumvent
310     # (we can't know the time fzf finished redrawing it's layout)
311     print_on_winch "${REDRAW_KEY}${REDRAW_KEY}"
312     start_ueberzug
313
314     export_functions
315     SHELL="${BASH_BINARY}" \
316         fzf --preview "DRAW_PREVIEW {}" \
317             --preview-window "${DEFAULT_PREVIEW_POSITION}" \
318             --bind "${REDRAW_KEY}:${REDRAW_COMMAND}" \
319             "${@}"
320 fi