From 4b60c502029c554c6d75e57923baecb55e7f8e8c Mon Sep 17 00:00:00 2001 From: pika Date: Fri, 16 Aug 2024 14:22:19 +0200 Subject: [PATCH] addet fzf completion --- .bashrc | 467 +++++++++++++++++++++++++++++++ fzf-bash-completion.sh | 609 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1076 insertions(+) create mode 100644 .bashrc create mode 100644 fzf-bash-completion.sh diff --git a/.bashrc b/.bashrc new file mode 100644 index 0000000..d51b233 --- /dev/null +++ b/.bashrc @@ -0,0 +1,467 @@ +#!/bin/bash +# ─< Helper functions >───────────────────────────────────────────────────────────────── +function echo_error() { echo -e "\033[0;1;31merror:\033[0;31m\t${*}\033[0m"; } +function echo_binfo() { echo -e "\033[0;1;34mInfo:\033[0;34m\t${*}\033[0m"; } +function echo_info() { echo -e "\033[0;35m${*}\033[0m"; } + +# ~/.bashrc: executed by bash(1) for non-login shells. +# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) +# for examples + +# If not running interactively, don't do anything +case $- in +*i*) ;; +*) return ;; +esac + +_defaults_() { + # don't put duplicate lines or lines starting with space in the history. + # See bash(1) for more options + HISTCONTROL=ignoreboth + + # append to the history file, don't overwrite it + shopt -s histappend + + # for setting history length see HISTSIZE and HISTFILESIZE in bash(1) + HISTSIZE=1000 + HISTFILESIZE=2000 + + # check the window size after each command and, if necessary, + # update the values of LINES and COLUMNS. + shopt -s checkwinsize + + # If set, the pattern "**" used in a pathname expansion context will + # match all files and zero or more directories and subdirectories. + #shopt -s globstar + + # make less more friendly for non-text input files, see lesspipe(1) + [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + + # set variable identifying the chroot you work in (used in the prompt below) + if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(cat /etc/debian_chroot) + fi + + _color_prompt_() { + # set a fancy prompt (non-color, unless we know we "want" color) + case "$TERM" in + xterm-color | *-256color) color_prompt=yes ;; + esac + + # uncomment for a colored prompt, if the terminal has the capability; turned + # off by default to not distract the user: the focus in a terminal window + # should be on the output of commands, not on the prompt + # force_color_prompt=yes + + if [ -n "$force_color_prompt" ]; then + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi + fi + + if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' + else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' + fi + unset color_prompt force_color_prompt + } + + # If this is an xterm set the title to user@host:dir + case "$TERM" in + xterm* | rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; + *) ;; + esac + + # Add an "alert" alias for long running commands. Use like so: + # sleep 10; alert + alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' + + # Alias definitions. + # You may want to put all your additions into a separate file like + # ~/.bash_aliases, instead of adding them here directly. + # See /usr/share/doc/bash-doc/examples in the bash-doc package. + + if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases + fi + + # enable programmable completion features (you don't need to enable + # this, if it's already enabled in /etc/bash.bashrc and /etc/profile + # sources /etc/bash.bashrc). + if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi + fi +} + +# ─< check if command exists >──────────────────────────────────────────────────────────── +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# ─< Silent execution >───────────────────────────────────────────────────────────────── +silentexec() { + "$@" >/dev/null 2>&1 +} + +check_root() { + if [[ "${EUID}" -ne 0 ]]; then + if command_exists sudo; then + _sudo="sudo" + else + _sudo="" + echo_info "-- sudo was not found on this system ! --" + fi + else + _sudo="" + fi +} + +_zoxide_() { + if command_exists zoxide; then + eval "$(zoxide init bash)" + fi +} + +_fancy_ls_() { + # ─< colorized ls >───────────────────────────────────────────────────────────────────────── + if command_exists exa; then + alias ls="exa --icons --long --git" + alias l="exa --icons -l" + alias ll="exa --icons -laa" + alias tree="exa --icons -l --tree" + elif command_exists lsd; then + alias ls="lsd -l -1 -h1 --almost-all --git" + alias l="lsd -1" + alias ll="lsd -1 --almost-all" + alias clearl="command clear && l" + alias tree="lsd --tree" + elif command_exists eza; then + alias ls="eza --icons --long --git" + alias l="eza --icons -l" + alias ll="eza --icons -laa" + alias tree="eza --icons -l --tree" + else + alias ls="ls --color=always -lAph" + alias l="ls --color=always -lph -w1" + alias ll="ls --color=always -lph" + fi +} + +_tmux_() { + # ─< t stands for tmux >──────────────────────────────────────────────────────────────────── + if command_exists tmux; then + tmux_y="-- tmux-session active! | connecting to active session --" + tmux_n="-- no tmux-session found! | creating one --" + ta() { + command tmux list-sessions >/dev/null 2>&1 + if [ $? -eq 0 ]; then + if command_exists notify-send; then + notify-send "$tmux_y" + sleep 0.5 + tmux attach + else + echo_info "$tmux_y" + sleep 0.5 + tmux attach + fi + else + if command_exists notify-send; then + notify-send "$tmux_n" + sleep 0.5 + tmux + else + echo_info "$tmux_n" + sleep 0.5 + tmux + fi + fi + } + alias ts="tmux source $HOME/.tmux.conf" + fi +} + +_cli_qol_() { + # ─< colored everything >─────────────────────────────────────────────────────────────────── + alias ip="ip --color=always" + + # ─< easier dir up >──────────────────────────────────────────────────────────────────────── + alias ..="cd .." + + # ─< weather >────────────────────────────────────────────────────────────────────────────── + alias www="curl wttr.in/Ulm" + + # ─< check for rg >───────────────────────────────────────────────────────────────────────── + if command_exists rg; then + alias grep="rg --color=always" + else + alias grep="grep --color=always" + fi + + # ─< telnet (starwars) >──────────────────────────────────────────────────────────────────── + if command_exists telnet; then + alias starwars="telnet -a telehack.com" + fi + + # ─< oh-my-posh initialization >──────────────────────────────────────────────────────────── + if command_exists oh-my-posh; then + eval "$(oh-my-posh init bash --config 'https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/amro.omp.json')" + fi +} + +# ─< t stands for trash(-cli) >─────────────────────────────────────────────────────────────── +_trash() { + if command_exists trash; then + alias rm="trash" + alias t="trash" + elif command_exists trash-cli; then + alias rm="trash-cli" + alias t="trash-cli" + else + echo_error "-- You do not have trash or trash-cli installed! Your 'rm' will be permanent! --" + fi +} + +# ─< rsync >──────────────────────────────────────────────────────────────────────────────── +_rsync_() { + _rrsync() { + if command_exists find; then + numbers=0 + for source in "${@:1:$#-1}"; do + numbers=$(($numbers + $(find "$source" -type f | wc -l))) + done + destination="${@: -1}" + + rsync -avP --info=progress2 "${@:1:$#-1}" "$destination" | pv -lpes $numbers + else + echo_error "-- We could not find 'find'. Please install 'find' to enable the progress bar. (hit 'CTRL + C' to exit now) --" + sleep 5 + rsync -avP --info=progress2 "$@" + fi + } + if command_exists rsync; then + alias cp="_rrsync" + alias scp="rsync -avP" + fi +} + +_cat_() { + # ─< bat alias >──────────────────────────────────────────────────────────────────────────── + if command_exists batcat; then + alias cat="batcat --color=always -p --paging=never" + alias less="batcat --paging always --color=always" + alias gd="batcat --diff" + elif command_exists bat; then + alias cat="bat --color=always -p" + alias less="bat --paging always --color=always" + alias gd="bat --diff" + fi +} + +_fzf_() { + if command_exists fzf; then + # Set up fzf key bindings and fuzzy completion + eval "$(fzf --bash)" + source ./fzf-bash-completion.sh + bind -x '"\t": fzf_bash_completion' + fi +} + +_fetches_() { + # ─< fastfetch >──────────────────────────────────────────────────────────────────────────── + if command_exists fastfetch; then + alias ff="fastfetch" + alias clearff="command clear & fastfetch" + if fastfetch --config os >/dev/null 2>&1; then + alias f="fastfetch --config os" + else + git clone https://git.k4li.de/mirrors/fastfetch.git $HOME/.local/share/fastfetch >/dev/null 2>&1 + exec $SHELL + fi + clear & + f + alias clear="clear & f" + fi +} + +_nmap_() { + # ─< set nmap-alias >─────────────────────────────────────────────────────────────────────── + if command_exists nmap; then + alias scanvuln="sudo nmap --script vuln -vvv" + alias sv="scanvuln" + alias portscan="sudo nmap -sT" + alias ps="portscan" + fi +} + +_docker_() { + # ─< d stands for docker >────────────────────────────────────────────────────────────────── + if command_exists docker; then + alias up="docker compose up" + alias down="docker compose down" + alias pull="docker compose pull" + alias d="docker" + alias dr="docker run --rm -it" + alias drs="docker compose down && docker compose up -d --remove-orphans --force-recreate" + alias ds="docker ps -a" + alias dc="docker compose" + alias appupdate="docker compose pull && docker compose up -d --force-recreate" + fi +} + +_git_() { + # ─< g stands for GIT >───────────────────────────────────────────────────────────────────── + if command_exists git; then + alias g="git" + alias gs="git status" + alias gm='git checkout main && git merge' + alias gc="git clone --recurse-submodule" + alias ga="git add" + alias gp="git pull --recurse-submodule" + alias gms='git maintenance start' + alias gsu="git pull --recurse-submodule && git submodule foreach git pull && git add . && git commit -m ' updated 📌submodules' && echo '-- Committed changes, pushing now..' && sleep 1 && git push" + alias gcm="git commit -m" + alias gpu="git push --recurse-submodule=on-demand" + if command_exists lazygit; then + alias lg="lazygit" + fi + fi +} + +# ╭────────╮ +# │ CODING │ +# ╰────────╯ +_coding_() { + # ─< h stands for HUGO >────────────────────────────────────────────────────────────────── + if command_exists hugo; then + alias h='hugo' + alias hs='hugo server -D --noHTTPCache --disableFastRender' + fi + # ─< c stands for bin/cake >────────────────────────────────────────────────────────────── + alias cake='bin/cake' + alias c='cake' + alias cs='c server -p 1313' + # ─< VSCodium >───────────────────────────────────────────────────────────────────────────── + if command_exists codium; then + alias code="codium" + export EDITOR="codium" + fi + # ─< neovide, the best frontend for any neovim-config >─────────────────────────────────── + if [ -d "$HOME/.local/share/neovide/" ]; then + if command_exists neovide; then + alias nvim='neovide --fork' + else + neovide_path=$(find "$HOME/" -name "*neovide*.appimage" 2>/dev/null) + if [ -n "$neovide_path" ]; then + alias nvim="$neovide_path --fork" + fi + fi + fi +} + +get_packager() { + PKG="" + install="" + update="" + refresh="" + remove="" + source /etc/os-release + case "$ID" in + ubuntu | debian) + if command_exists nala; then + PKG="nala" + install="nala install --assume-yes" + upgrade="nala upgrade" + refresh="nala update" + remove="nala remove" + purge="nala purge" + alias update="$_sudo $refresh && $_sudo $upgrade" + alias install="$_sudo $refresh && $_sudo $install" + alias remove="$_sudo $remove && $_sudo $purge && $_sudo $clean" + elif command_exists apt-get; then + PKG="apt-get" + install="apt-get install --yes" + upgrade="apt-get upgrade" + refresh="apt-get update" + clean="apt-get autoremove" + remove="apt-get remove" + purge="apt-get purge" + alias update="$_sudo $refresh && $_sudo $upgrade" + alias install="$_sudo $refresh && $_sudo $install" + alias remove="$_sudo $remove && $_sudo $purge && $_sudo $clean" + fi + ;; + arch | manjaro | endevouros) + if command_exists yay; then + PKG="yay" + alias install="yay -S --noconfirm" + alias update="yay -Syu" + alias remove="yay -R" + elif command_exists paru; then + PKG="paru" + alias install="paru -S --noconfirm" + alias update="paru -Syu" + alias remove="paru -R" + elif command_exists pacman; then + PKG="pacman" + alias install="$_sudo pacman -S --noconfirm" + alias update="$_sudo pacman -Syu" + alias remove="$_sudo pacman -R" + fi + ;; + fedora | centos) + PKG="dnf" + alias install="dnf install --yes" + alias update="dnf update" + alias remove="dnf remove" + ;; + alpine) + PKG="apk" + install="apk add" + update="apk update" + upgrade="apk upgrade" + remove="apk del" + + alias install="$_sudo $install" + alias update="$_sudo $update && $_sudo $upgrade" + alias remove="$_sudo $remove" + ;; + esac +} + +get_alias() { + _defaults_ + _color_prompt_ + _cli_qol_ + _cat_ + # _fzf_ + _trash + _nmap_ + _tmux_ + _docker_ + _git_ + _rsync_ + _zoxide_ + _fancy_ls_ + _coding_ + _fetches_ +} + +main() { + check_root + get_packager + get_alias +} + +main diff --git a/fzf-bash-completion.sh b/fzf-bash-completion.sh new file mode 100644 index 0000000..7e03e8c --- /dev/null +++ b/fzf-bash-completion.sh @@ -0,0 +1,609 @@ +_FZF_COMPLETION_SEP=$'\x01' + +# shell parsing stuff +_fzf_bash_completion_awk="$( builtin command -v gawk &>/dev/null && echo gawk || echo awk )" +_fzf_bash_completion_sed="$( builtin command -v gsed &>/dev/null && echo gsed || echo sed )" +_fzf_bash_completion_grep="$( builtin command -v ggrep &>/dev/null && echo ggrep || echo builtin command grep )" + +_fzf_bash_completion_awk_escape() { + "$_fzf_bash_completion_sed" 's/\\/\\\\\\\\/g; s/[[*^$.]/\\\\&/g' <<<"$1" +} + +_fzf_bash_completion_shell_split() { + $_fzf_bash_completion_grep -E -o \ + -e '\|+|&+|<+|>+' \ + -e '[;(){}&\|]' \ + -e '(\\.|\$[-[:alnum:]_*@#?$!]|(\$\{[^}]*(\}|$))|[^$\|"[:space:];(){}&<>'"'${wordbreaks}])+" \ + -e "\\\$'(\\\\.|[^'])*('|$)" \ + -e "'[^']*('|$)" \ + -e '"(\\.|\$($|[^(])|[^"$])*("|$)' \ + -e '".*' \ + -e '[[:space:]]+' \ + -e . +} + +_fzf_bash_completion_unbuffered_awk() { + # need to get awk to be unbuffered either by using -W interactive or system("") + "$_fzf_bash_completion_awk" -W interactive "${@:3}" "$1 { $2; print \$0; system(\"\") }" 2>/dev/null +} + +_fzf_bash_completion_flatten_subshells() { + ( + local count=0 buffer= + while IFS= read -r line; do + case "$line" in + \(|\{) (( count -- )) ;; + \)|\}) (( count ++ )) ;; + esac + + if (( count < 0 )); then + return + elif (( count > 0 )); then + buffer="$line$buffer" + else + printf '%s\n' "$line$buffer" + buffer= + fi + done < <(tac) + printf '%s\n' "$buffer" + ) | tac +} + +_fzf_bash_completion_find_matching_bracket() { + local count=0 + while IFS=: read -r num bracket; do + if [ "$bracket" = "$1" ]; then + (( count++ )) + if (( count > 0 )); then + printf '%s\n' "$num" + return 0 + fi + else + (( count -- )) + fi + done < <($_fzf_bash_completion_grep -F -e '(' -e ')' -n) + return 1 +} + +_fzf_bash_completion_parse_dq() { + local words="$(cat)" + local last="$(<<<"$words" tail -n1)" + + if [[ "$last" == \"* ]]; then + local line="${last:1}" shell_start string_end joined num + local word= + while true; do + # we are in a double quoted string + + shell_start="$(<<<"$line" $_fzf_bash_completion_grep -E -o '^(\\.|\$[^(]|[^$])*\$\(')" + string_end="$(<<<"$line" $_fzf_bash_completion_grep -E -o '^(\\.|[^"])*"')" + + if (( ${#string_end} && ( ! ${#shell_start} || ${#string_end} < ${#shell_start} ) )); then + # found end of string + line="${line:${#string_end}}" + if (( ${#line} )); then + printf '%s\n' "${words:0:-${#line}}" + _fzf_bash_completion_parse_line <<<"$line" + else + printf '%s\n' "$words" + fi + return + + elif (( ${#shell_start} && ( ! ${#string_end} || ${#shell_start} < ${#string_end} ) )); then + # found a subshell + + word+="${shell_start:0:-2}" + line="${line:${#shell_start}}" + + split="$(<<<"$line" _fzf_bash_completion_shell_split)" + if ! split="$(_fzf_bash_completion_parse_dq <<<"$split")"; then + # bubble up + printf '%s\n' "$split" + return 1 + fi + if ! num="$(_fzf_bash_completion_find_matching_bracket ')' <<<"$split")"; then + # subshell not closed, this is it + printf '%s\n' "$split" + return 1 + fi + # subshell closed + joined="$(<<<"$split" head -n "$num" | tr -d \\n)" + word+=$'\n$('"$joined"$'\n' + line="${line:${#joined}}" + + else + # the whole line is an incomplete string + break + fi + done + fi + printf '%s\n' "$words" +} + +_fzf_bash_completion_unquote_strings() { + local line + while IFS= read -r line; do + if [[ "$line" =~ ^\'[^\']*\'?$ ]]; then + # single quoted with no single quotes inside + line="${line%%\'}" + printf '%s\n' "${line:1}" + elif [[ "$line" =~ ^\"(\\.|[^\"$])*\"?$ ]]; then + # double quoted with all special characters quoted + "$_fzf_bash_completion_sed" -r 's/\\(.)/\1/g' <<<"${line:1-1}" + elif [[ "$line" == *\\* && "$line" =~ ^(\\.|[a-zA-Z0-9_])*$ ]]; then + # all special characters are quoted + "$_fzf_bash_completion_sed" -r 's/\\(.)/\1/g' <<<"$line" + else + # this string is either boring or too complicated to parse + # print as is + printf '%s\n' "$line" + fi + done +} + +_fzf_bash_completion_parse_line() { + _fzf_bash_completion_shell_split \ + | _fzf_bash_completion_parse_dq \ + | _fzf_bash_completion_flatten_subshells \ + | tr \\n \\0 \ + | "$_fzf_bash_completion_sed" -r "$(cat <<'EOF' +# collapse newlines +s/\x00\x00/\x00/g; +# leave trailing space +s/\x00(\s*)$/\n\1/; +# A & B -> (A, &, B) +s/([^&\n\x00])&([^&\n\x00])/\1\n\&\n\2/g; +# > B -> (>, B) +s/([\n\x00\z])([<>]+)([^\n\x00])/\1\2\n\3/g; +s/([<>][\n\x00])$/\1\n/; +# clear up until the a keyword starting a new command +# except the last line isn't a keyword, it may be the start of a command +s/^(.*[\x00\n])?(\[\[|case|do|done|elif|else|esac|fi|for|function|if|in|select|then|time|until|while|&|;|&&|\|[|&]?)\x00//; +# remove ENVVAR=VALUE +s/^(\s*[\n\x00]|\w+=[^\n\x00]*[\n\x00])*// +EOF +)" \ + | tr \\0 \\n +} + +_fzf_bash_completion_compspec() { + if [[ "$2" =~ .*\$(\{?)([A-Za-z0-9_]*)$ ]]; then + printf '%s\n' 'complete -F _fzf_bash_completion_complete_variables' + elif [[ "$COMP_CWORD" == 0 && -z "$2" ]]; then + # If the command word is the empty string (completion attempted at the beginning of an empty line), any compspec defined with the -E option to complete is used. + complete -p -E || { ! shopt -q no_empty_cmd_completion && printf '%s\n' 'complete -F _fzf_bash_completion_complete_commands -E'; } + elif [[ "$COMP_CWORD" == 0 ]]; then + complete -p -I || printf '%s\n' 'complete -F _fzf_bash_completion_complete_commands -I' + else + # If the command word is a full pathname, a compspec for the full pathname is searched for first. If no compspec is found for the full pathname, an attempt is made to find a compspec for the portion following the final slash. If those searches do not result in a compspec, any compspec defined with the -D option to complete is used as the default + complete -p -- "$1" || complete -p -- "${1##*/}" || complete -p -D || printf '%s\n' 'complete -o filenames -F _fzf_bash_completion_fallback_completer' + fi +} + +_fzf_bash_completion_fallback_completer() { + # fallback completion in case no compspecs loaded + if [[ "$1" == \~* && "$1" != */* ]]; then + # complete ~user directories + readarray -t COMPREPLY < <(compgen -P '~' -u -- "${1#\~}") + else + # complete files + readarray -t COMPREPLY < <(compgen -f -- "$1") + fi +} + +_fzf_bash_completion_complete_commands() { + # commands + compopt -o filenames + readarray -t COMPREPLY < <(compgen -abc -- "$2") +} + +_fzf_bash_completion_complete_variables() { + if [[ "$2" =~ .*\$(\{?)([A-Za-z0-9_]*)$ ]]; then + # environment variables + local brace="${BASH_REMATCH[1]}" + local filter="${BASH_REMATCH[2]}" + if [ -n "$filter" ]; then + local prefix="${2:: -${#filter}}" + else + local prefix="$2" + fi + readarray -t COMPREPLY < <(compgen -v -P "$prefix" -S "${brace:+\}}" -- "$filter") + fi +} + +_fzf_bash_completion_loading_msg() { + echo 'Loading matches ...' +} + +fzf_bash_completion() { + # bail early if no_empty_cmd_completion + if ! [[ "$READLINE_LINE" =~ [^[:space:]] ]] && shopt -q no_empty_cmd_completion; then + return 1 + fi + + printf '\r' + command tput sc 2>/dev/null || echo -ne "\0337" + printf '%s' "$(_fzf_bash_completion_loading_msg)" + command tput rc 2>/dev/null || echo -ne "\0338" + + local raw_comp_words=() + local COMP_WORDS=() COMP_CWORD COMP_POINT COMP_LINE + local COMP_TYPE=37 # % == indicates menu completion + local line="${READLINE_LINE:0:READLINE_POINT}" + local wordbreaks="$COMP_WORDBREAKS" + wordbreaks="${wordbreaks//[]^]/\\&}" + wordbreaks="${wordbreaks//[[:space:]]/}" + if [[ "$line" =~ [^[:space:]] ]]; then + readarray -t raw_comp_words < <(_fzf_bash_completion_parse_line <<<"$line") + fi + + if [[ ${#raw_comp_words[@]} -gt 1 ]]; then + _fzf_bash_completion_expand_alias "${raw_comp_words[@]}" + fi + readarray -t COMP_WORDS < <(printf '%s\n' "${raw_comp_words[@]}" | _fzf_bash_completion_unquote_strings) + + printf -v COMP_LINE '%s' "${COMP_WORDS[@]}" + COMP_POINT="${#COMP_LINE}" + # remove the ones that just spaces + local i + # iterate in reverse + for (( i = ${#COMP_WORDS[@]}-2; i >= 0; i --)); do + if ! [[ "${COMP_WORDS[i]}" =~ [^[:space:]] ]]; then + COMP_WORDS=( "${COMP_WORDS[@]:0:i}" "${COMP_WORDS[@]:i+1}" ) + fi + done + # add an extra blank word if last word is just space + if [[ "${#COMP_WORDS[@]}" = 0 ]]; then + COMP_WORDS+=( '' ) + elif ! [[ "${COMP_WORDS[${#COMP_WORDS[@]}-1]}" =~ [^[:space:]] ]]; then + COMP_WORDS[${#COMP_WORDS[@]}-1]='' + fi + COMP_CWORD="${#COMP_WORDS[@]}" + (( COMP_CWORD-- )) + + local cmd="${COMP_WORDS[0]}" + local prev + if [ "$COMP_CWORD" = 0 ]; then + prev= + else + prev="${COMP_WORDS[COMP_CWORD-1]}" + fi + local cur="${COMP_WORDS[COMP_CWORD]}" + if [[ "$cur" =~ ^[$wordbreaks]$ ]]; then + cur= + fi + local raw_cur="${cur:+${raw_comp_words[-1]}}" + + local COMPREPLY= + fzf_bash_completer "$cmd" "$cur" "$prev" + if [ -n "$COMPREPLY" ]; then + if [ -n "$raw_cur" ]; then + line="${line::-${#raw_cur}}" + fi + READLINE_LINE="${line}${COMPREPLY}${READLINE_LINE:$READLINE_POINT}" + (( READLINE_POINT+=${#COMPREPLY} - ${#raw_cur} )) + fi + + printf '\r' + command tput el 2>/dev/null || echo -ne "\033[K" +} + +_fzf_bash_completion_selector() { + FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" \ + $(__fzfcmd 2>/dev/null || echo fzf) -1 -0 --prompt "${FZF_TAB_COMPLETION_PROMPT:-> }$line" --nth 2 -d "$_FZF_COMPLETION_SEP" --ansi \ + | tr -d "$_FZF_COMPLETION_SEP" +} + +_fzf_bash_completion_expand_alias() { + if alias "$1" &>/dev/null; then + value=( ${BASH_ALIASES[$1]} ) + if [ -n "${value[*]}" -a "${value[0]}" != "$1" ]; then + raw_comp_words=( "${value[@]}" "${raw_comp_words[@]:1}" ) + fi + fi +} + +_fzf_bash_completion_auto_common_prefix() { + if [ "$FZF_COMPLETION_AUTO_COMMON_PREFIX" = true ]; then + local prefix item items prefix_len prefix_is_full input_len i + IFS= read -r prefix && items=("$prefix") || return + prefix_len="${#prefix}" + prefix_is_full=1 # prefix == item + + input_len="$(( ${#1} ))" + + while [ "$prefix_len" != "$input_len" ] && IFS= read -r item && items+=("$item"); do + for ((i=$input_len; i<$prefix_len; i++)); do + if [[ "${item:i:1}" != "${prefix:i:1}" ]]; then + prefix_len="$i" + unset prefix_is_full + break + fi + done + + if [ -z "$prefix_is_full" ] && [ -z "${item:i:1}" ]; then + prefix_is_full=1 + fi + done + + if [ "$prefix_len" != "$input_len" ]; then + if [ "$FZF_COMPLETION_AUTO_COMMON_PREFIX_PART" == true ] || [ "$prefix_is_full" == 1 ]; then + [ "${items[1]}" ] && printf 'compl_nospace=1\n'>&"${__evaled}" # no space if not only one + printf %s\\n "${prefix:0:prefix_len}" + return + fi + fi + + printf %s\\n "${items[@]}" + fi + + cat +} + +fzf_bash_completer() { + local value code + local compl_bashdefault compl_default compl_dirnames compl_filenames compl_noquote compl_nosort compl_nospace compl_plusdirs + + # preload completions in top shell + { complete -p -- "$1" || __load_completion "$1"; } &>/dev/null + local compspec + if ! compspec="$(_fzf_bash_completion_compspec "$@" 2>/dev/null)"; then + return + fi + + eval "$( + local _fzf_sentinel1=b5a0da60-3378-4afd-ba00-bc1c269bef68 + local _fzf_sentinel2=257539ae-7100-4cd8-b822-a1ef35335e88 + ( + set -o pipefail + + # hack: hijack compopt + compopt() { _fzf_bash_completion_compopt "$@"; } + + exec {__evaled}>&1 + coproc ( + ( + # input from tty in case one of the completions wants fzf using $FZF_DEFAULT_COMMAND + exec 32 )); then + echo "$1: possible retry loop" >/dev/tty + break + fi + _fzf_bash_completion_complete "$@" + done + printf '%s\n' "$_FZF_COMPLETION_SEP$_fzf_sentinel1$_fzf_sentinel2" + ) | $_fzf_bash_completion_sed -un "/$_fzf_sentinel1$_fzf_sentinel2/q; p" \ + | _fzf_bash_completion_auto_common_prefix "$raw_cur" \ + | _fzf_bash_completion_unbuffered_awk '$0!="" && !x[$0]++' '$0 = "\x1b[37m" substr($0, 1, len) "\x1b[0m" sep substr($0, len+1)' -vlen="${#raw_cur}" -vsep="$_FZF_COMPLETION_SEP" + ) + local coproc_pid="$COPROC_PID" + value="$(_fzf_bash_completion_selector "$1" "$raw_cur" "$3" <&"${COPROC[0]}")" + code="$?" + value="$(<<<"$value" tr \\n \ )" + value="${value% }" + + printf 'COMPREPLY=%q\n' "$value" + printf 'code=%q\n' "$code" + + # kill descendant processes of coproc + descend_process () { + printf '%s\n' "$1" + for pid in $(ps -ef | "$_fzf_bash_completion_awk" -v ppid="$1" '$3 == ppid { print $2 }'); do + descend_process "$pid" + done + } + kill -- $(descend_process "$coproc_pid") 2>/dev/null + + printf '%s\n' ": $_fzf_sentinel1$_fzf_sentinel2" + ) | $_fzf_bash_completion_sed -un "/$_fzf_sentinel1$_fzf_sentinel2/q; p" + )" 2>/dev/null + + if [ "$code" = 0 ]; then + COMPREPLY="${COMPREPLY[*]}" + [ "$compl_nospace" != 1 ] && COMPREPLY="$COMPREPLY " + [[ "$compl_filenames" == *1* ]] && COMPREPLY="${COMPREPLY/%\/ //}" + fi +} + +_fzf_bash_completion_complete() { + local compgen_actions=() compspec= + if ! compspec="$(_fzf_bash_completion_compspec "$@" 2>/dev/null)"; then + return + fi + + local args=( "$@" ) + eval "compspec=( $compspec )" + set -- "${compspec[@]}" + shift # remove the complete command + while (( $# > 1 )); do + case "$1" in + -F) + local compl_function="$2" + shift ;; + -C) + local compl_command="$2" + shift ;; + -G) + local compl_globpat="$2" + shift ;; + -W) + local compl_wordlist="$2" + shift ;; + -X) + local compl_xfilter="$2" + shift ;; + -o) + _fzf_bash_completion_compopt -o "$2" + shift ;; + -A) + local compgen_opts+=( "$1" "$2" ) + shift ;; + -P) + local compl_prefix="$(_fzf_bash_completion_awk_escape "$2")" + shift ;; + -S) + local compl_suffix="$(_fzf_bash_completion_awk_escape "$2")" + shift ;; + -[a-z]) + compgen_actions+=( "$1" ) + ;; + esac + shift + done + set -- "${args[@]}" + + COMPREPLY=() + if [ -n "$compl_function" ]; then + "$compl_function" "$@" >/dev/null + if [ "$?" = 124 ]; then + local newcompspec + if ! newcompspec="$(_fzf_bash_completion_compspec "$@" 2>/dev/null)"; then + return + elif [ "$newcompspec" != "$compspec" ]; then + return 124 + fi + "$compl_function" "$@" >/dev/null + fi + fi + + if [[ "$compl_filenames" == 1 ]]; then + local dir_marker=_fzf_bash_completion_dir_marker + else + local dir_marker=cat + fi + + printf 'compl_filenames=%q\n' "$compl_filenames" >&"${__evaled}" + printf 'compl_noquote=%q\n' "$compl_noquote" >&"${__evaled}" + printf 'compl_nospace=%q\n' "$compl_nospace" >&"${__evaled}" + + ( + ( + if [ -n "${compgen_actions[*]}" ]; then + compgen "${compgen_actions[@]}" -- "$2" + fi + + if [ -n "$compl_globpat" ]; then + printf %s\\n "$compl_globpat" + fi + + if [ -n "$compl_wordlist" ]; then + eval "printf '%s\\n' $compl_wordlist" + fi + + if [ -n "${COMPREPLY[*]}" ]; then + printf %s\\n "${COMPREPLY[@]}" + fi + + if [ -n "$compl_command" ]; then + ( + unset COMP_WORDS COMP_CWORD + export COMP_LINE="$COMP_LINE" COMP_POINT="$COMP_POINT" COMP_KEY="$COMP_KEY" COMP_TYPE="$COMP_TYPE" + eval "$compl_command" + ) + fi + + printf '\n' + ) | _fzf_bash_completion_apply_xfilter "$compl_xfilter" \ + | _fzf_bash_completion_unbuffered_awk '$0!=""' 'sub(find, replace)' -vfind='.*' -vreplace="$(printf %s "$compl_prefix" | "$_fzf_bash_completion_sed" 's/[&\]/\\&/g')&$(printf %s "$compl_suffix" | "$_fzf_bash_completion_sed" 's/[&\]/\\&/g')" \ + | if IFS= read -r line || (( ${#COMPREPLY[@]} )); then + ([[ -z "$line" ]] || printf '%s\n' "$line"; cat) | _fzf_bash_completion_quote_filenames "$@" + else + # got no results + local compgen_opts=() + [ "$compl_bashdefault" = 1 ] && compgen_opts+=( -o bashdefault ) + [ "$compl_default" = 1 ] && compgen_opts+=( -o default ) + [ "$compl_dirnames" = 1 ] && compgen_opts+=( -o dirnames ) + # don't double invoke fzf + if [ -n "${compgen_opts[*]}" ]; then + # these are all filenames + printf 'compl_filenames=1\n'>&"${__evaled}" + compgen "${compgen_opts[@]}" -- "$2" \ + | compl_filenames=1 _fzf_bash_completion_quote_filenames "$@" \ + | _fzf_bash_completion_dir_marker + fi + fi + + if [ "$compl_plusdirs" = 1 ]; then + compgen -o dirnames -- "$2" \ + | compl_filenames=1 _fzf_bash_completion_quote_filenames "$@" \ + | _fzf_bash_completion_dir_marker + fi + ) \ + | "$dir_marker" +} + +_fzf_bash_completion_apply_xfilter() { + if [ -z "$1" ]; then + cat + return + fi + + local pattern line word="$cur" + word="${word//\//\\/}" + word="${word//&/\\&}" + # replace any unescaped & with the word being completed + pattern="$("$_fzf_bash_completion_sed" 's/\(\(^\|[^\]\)\(\\\\\)*\)&/\1'"$word"'/g' <<<"${1:1}")" + + if [ "${1::1}" = ! ]; then + while IFS= read -r line; do [[ "$line" == $pattern ]] && printf '%s\n' "$line"; done + elif [ -n "$1" ]; then + while IFS= read -r line; do [[ "$line" != $pattern ]] && printf '%s\n' "$line"; done + fi +} + +_fzf_bash_completion_dir_marker() { + local line expanded + while IFS= read -r line; do + expanded="$line" + + # adapted from __expand_tilde_by_ref + if [[ "$expanded" == \~* ]]; then + eval "$(printf expanded=~%q "${expanded:1}")" + fi + + if [[ "$compl_noquote" != 1 && "$expanded" == *\\* ]]; then + expanded="$("$_fzf_bash_completion_sed" -r 's/\\(.)/\1/g' <<<"$expanded")" + fi + + [ -d "$expanded" ] && line="${line%/}/" + printf '%s\n' "$line" + done +} + +_fzf_bash_completion_quote_filenames() { + if [ "$compl_noquote" != 1 -a "$compl_filenames" = 1 ]; then + local IFS line + while IFS= read -r line; do + if [ "${line::1}" = '~' ]; then + printf '~%q\n' "${line:1}" + else + printf '%q\n' "$line" + fi + done + else + cat + fi +} + +_fzf_bash_completion_compopt() { + while [ "$#" -gt 0 ]; do + local val + if [ "$1" = -o ]; then + val=1 + elif [ "$1" = +o ]; then + val=0 + else + break + fi + + if [[ "$2" =~ bashdefault|default|dirnames|filenames|noquote|nosort|nospace|plusdirs ]]; then + eval "compl_$2=$val" + fi + shift 2 + done +}