Refining ksh and fzf

15 March 2019 ยท 3 minute read

Refining some on ksh and fzf from my earlier post on ksh and fzf.

The first step is to define some functions to run fzf queries. I’m borrowing the zsh naming convention of a “widget.” fzf_command_complete_widget does command-name completion (for executables located in $PATH).

# *** fzf command completion widget

function fd_path_executables {
    # list all executables in $PATH
    while read -d ':' p; do
        [[ -d $p ]] && fd --type x . "$p"
    done <<< "$PATH:"

}

function fzf_command_complete_widget {
    fd_path_executables | fzf --height="30%" 
}

I rewrote the file name completion widget to handle multiple cases. If called in the middle of a path specification like /usr/local/ it will pass the path and the remaining characters on to fd and fzf. I also needed to add code to replace part of the existing command line if that’s the case.

# *** fzf file completion widget
function fzf_widget {
    # set up local variables
    # last will be the last command line argument entered so far
    # p will be set to the path (assuming there is one)
    # q will be set to the query string. 
    typeset last
    typeset p
    typeset q

    # ignore the first argument
    if [[ $# -gt 1 ]]; then
        last=${@:$#}
    fi

    # if the last character of the raw edit buffer is a
    #space or equal sign, we're
    # just going to append to the existing line.    
    if [[ ${.sh.edtext} =~ " $" ]] || [[ ${.sh.edtext} =~ "=$" ]]; then
        last=""
    # catch cases with a file path, and separate p and q
    elif [[ $last =~ "/" ]]; then
        q=${last##*/}
        p=${last%/*}
    else
        q=$last
    fi
   


    
    # Hacky, search for directories if the command line starts with cd.
    # Use "." and "." as defaults for p and q to pass to fd. 
    if [[ $1 =~ "^cd" ]]; then
        result=$(fd "${q:=.}" "${p:-.}" --type d | fzf --height="30%")
    else
        result=$(fd "${q:=.}" "${p:=.}" | fzf -e --height="30%")
    fi

    # if we've started fresh, print result
    if [[ $last == "" ]]; then
        printf "%q" "${result}"
    else
    # if we've used the last argument, clear the line
    # print all but the last array element
    # and add the result 
        print $'\ca\ck'"${@:1:$#-1} "$(printf " %q" "${result}")
    fi
    

}

The history widget is the simplest of the three. List the history, pipe it to fzf, and pipe the result to awk to get the command.

# *** fzf history completion widget

###############
# fzh
#
# search the history file.
###############
function fzh_widget {

    hist -l 1 | fzf +s --tac --height="30%" | awk -F '\t' '{print $2}'

}

I borrowed the keybinding code from a code sample from the Learning the Korn Shell text which uses an associative array, Keytable. I’m not entirely happy with the multiple layers of quoting needed for this to work.

typeset -A Keytable
trap 'eval "${Keytable[${.sh.edchar}]}"' KEYBD
function keybind # key [action]
{
    typeset key=$(print -f "%s" "$2")
    case $# in
    2)      Keytable[$1]=' .sh.edchar=${.sh.edmode}'"$key"
            ;;
    1)      unset Keytable[$1]
            ;;
    *)      print -u2 "Usage: $0 key [action]"
            return 2 # usage errors return 2 by default
            ;;
    esac
}

keybind $'\ct' $'$(fzf_widget ${.sh.edtext})'$'\cl'
keybind $'\cr' $'$(fzh_widget ${.sh.edtext})'$'\cl'
keybind $'\Et' $'$(fzf_command_complete_widget ${.sh.edtext})'$'\cl'
Tags: