#!/usr/bin/env bash # fzf-ueberzogen.sh is a wrapper script which allows to use ueberzug with fzf. # Copyright (C) 2019 Nico Baeurer # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . readonly BASH_BINARY="$(which bash)" readonly REDRAW_COMMAND="toggle-preview+toggle-preview" readonly REDRAW_KEY="ยต" declare -r -x DEFAULT_PREVIEW_POSITION="right" declare -r -x UEBERZUG_FIFO="$(mktemp --dry-run --suffix "fzf-$$-ueberzug")" declare -r -x PREVIEW_ID="preview" function STORE_TERMINAL_SIZE_IN { # Usage: STORE_TERMINAL_SIZE_IN # lines_variable columns_variable [[ ! -v "$1" || ! -v "$2" ]] && return 1 < <( terminal_lines ? terminal_lines :_fzf_height_lines)) fi ((_fzf_height_lines=fzf_min_height > _fzf_height_lines ? fzf_min_height : _fzf_height_lines)) } function STORE_FZF_OFFSET_IN { # Usage: STORE_FZF_OFFSET_IN # fzf_offset_y_variable # terminal_lines fzf_height fzf_start_offset_y [[ $# -ne 4 || ! -v "$1" ]] && return 1 local -n _fzf_offset_y="$1" local terminal_lines="$2" local fzf_height="$3" local fzf_start_offset_y="$4" # Two cases: # 1. There isn't enough space, so fzf will print blank lines. # -> OFFSET_Y = terminal height - required lines # 2. There is enough space -> OFFSET_Y = START_OFFSET_Y ((_fzf_offset_y=terminal_lines - fzf_height)) ((_fzf_offset_y=_fzf_offset_y < fzf_start_offset_y ? _fzf_offset_y : fzf_start_offset_y)) } function STORE_PREVIEW_POSITION_IN { # Usage: STORE_PREVIEW_POSITION_IN # preview_y_variable preview_x_variable # preview_position fzf_offset_y fzf_height # terminal_width preview_height preview_width [[ $# -ne 8 || ! -v "$1" || ! -v "$2" ]] && return 1 local -n _preview_y="$1" local -n _preview_x="$2" local preview_position="$3" local fzf_offset_y="$4" local fzf_height="$5" local terminal_width="$6" local preview_height="$7" local preview_width="$8" case "${preview_position}" in left|up|top) _preview_x=2 _preview_y=$((1 + fzf_offset_y)) ;; right) _preview_x=$((terminal_width - preview_width - 2)) _preview_y=$((1 + fzf_offset_y)) ;; down|bottom) _preview_x=2 _preview_y=$((fzf_offset_y + fzf_height - preview_height - 1)) ;; esac } function DRAW_PREVIEW { local preview_path="" if test -f "${@}/001.jpg"; then preview_path="${@}/001.jpg" elif test -f "${@}/001.png"; then preview_path="${@}/001.png" else return fi # Usage: DRAW_PREVIEW path local -A add_preview_command=( \ [identifier]="${PREVIEW_ID}" \ [scaler]=contain \ [path]="${preview_path}") ADD_PLACEMENT add_preview_command } function CLEAR_PREVIEW { # Usage: CLEAR_PREVIEW REMOVE_PLACEMENT "${PREVIEW_ID}" } function IDENTITY_RECT { # Usage: IDENTITY_RECT # placement_rect_variable [[ $# -ne 1 ]] && return 1 } function ADD_PLACEMENT { # Usage: ADD_PLACEMENT # add_command_variable [adjust_rect_function] # references can't be checked.. -v doesn't seem to support associative arrays.. local terminal_lines= terminal_columns= local fzf_height= fzf_offset_y= local preview_y= preview_x= local preview_height="${LINES}" preview_width="${COLUMNS}" STORE_TERMINAL_SIZE_IN \ terminal_lines terminal_columns STORE_FZF_HEIGHT_IN \ fzf_height \ "$terminal_lines" "${FZF_HEIGHT}" "${FZF_MIN_HEIGHT}" STORE_FZF_OFFSET_IN \ fzf_offset_y \ "$terminal_lines" "${fzf_height}" "${FZF_START_OFFSET_Y}" STORE_PREVIEW_POSITION_IN \ preview_y preview_x \ "${PREVIEW_POSITION:-${DEFAULT_PREVIEW_POSITION}}" \ "${fzf_offset_y}" "${fzf_height}" "${terminal_columns}" \ "${preview_height}" "${preview_width}" local _add_command_nameref="$1" local -n _add_command="${_add_command_nameref}" local adjust_rect_callback="${2:-IDENTITY_RECT}" local -A adjusted_placement_rect=( \ [y]="${preview_y}" [x]="${preview_x}" \ [height]="${preview_height}" [width]="${preview_width}") "${adjust_rect_callback}" adjusted_placement_rect _add_command[action]=add _add_command[x]="${adjusted_placement_rect[x]}" _add_command[y]="${adjusted_placement_rect[y]}" _add_command[width]="${adjusted_placement_rect[width]}" _add_command[height]="${adjusted_placement_rect[height]}" >"${UEBERZUG_FIFO}" \ declare -p "${_add_command_nameref}" } function REMOVE_PLACEMENT { # Usage: REMOVE_PLACEMENT placement-id [[ $# -ne 1 ]] && return 1 >"${UEBERZUG_FIFO}" \ declare -A -p _remove_command=( \ [action]=remove [identifier]="${1}") } function is_option_key [[ "${@}" =~ ^(\-.*|\+.*) ]] function is_key_value [[ "${@}" == *=* ]] function store_options_map_in { # Usage: store_options_map_in # options_map_variable options_variable # references can't be checked.. -v doesn't seem to support associative arrays.. [[ $# -ne 2 || ! -v "$2" ]] && return 1 local -n _options_map="${1}" local -n _options="${2}" for ((i=0; i < ${#_options[@]}; i++)); do local key="${_options[$i]}" next_key="${_options[$((i + 1))]:---}" local value=true is_option_key "${key}" || \ continue if is_key_value "${key}"; then <<<"${key}" \ IFS='=' read key value elif ! is_option_key "${next_key}"; then value="${next_key}" fi _options_map["${key}"]="${value}" done } function process_options { # Usage: process_options command-line-arguments local -a "default_options=(${FZF_DEFAULT_OPTS})" local -a script_options=("${@}") local -A mapped_options store_options_map_in mapped_options default_options store_options_map_in mapped_options script_options local cursor_y= cursor_x= store_cursor_position_in cursor_y cursor_x # If fzf is used as completion tool we will get the position of the prompt. # If it's normally used we get the position the output will be displayed at. # If it's normally used we need to subtract one to get the position of the prompt. ((cursor_y=cursor_x != 1 ? cursor_y : cursor_y - 1)) declare -g -r -x FZF_START_OFFSET_Y="${cursor_y}" declare -g -r -x PREVIEW_POSITION="${mapped_options[--preview-window]%%:[^:]*}" declare -g -r -x FZF_HEIGHT="${mapped_options[--height]:-100%}" declare -g -r -x FZF_MIN_HEIGHT="${mapped_options[--min-height]:-10}" } function store_cursor_position_in { # Usage: store_cursor_pos_in # y_variable x_variable # based on https://github.com/dylanaraps/pure-bash-bible#get-the-current-cursor-position [[ ! -v "$1" || ! -v "$2" ]] && return 1 /dev/tty \ IFS='[;' \ read -p $'\e[6n' -d R -rs _ "${1}" "${2}" _ } function start_ueberzug { # Usage: start_ueberzug mkfifo "${UEBERZUG_FIFO}" <"${UEBERZUG_FIFO}" \ ueberzug layer --parser bash --silent & # prevent EOF 3>"${UEBERZUG_FIFO}" \ exec } function finalise { # Usage: finalise 3>&- \ exec &>/dev/null \ rm "${UEBERZUG_FIFO}" &>/dev/null \ kill $(jobs -p) } function print_on_winch { # Usage: print_on_winch text # print "$@" to stdin on receiving SIGWINCH # use exec as we will only kill direct childs on exiting, # also the additional bash process isn't needed