sk, rg, fd

Building blocks for common workflows

This post is about combination of skim, ripgrep and fd that generalizes common ways of using terminal. Let me show you some examples.

Notes keeping system

Being bitten a few times by device or vendor lock offs, I formulated for myself this rules:

Thus, my current system consists mostly of FS trees and plain markdown files. Directory name represents major interest category, names of the files are often thought as a cloud of tags. Since it's so simple, the real overall structure of notes keeping place are volatile, and depends on how often new strings appears there and how often renaming occures.

Below you can see bash function m (short for "memory") runs sk changing to the root dir of all notes. Then you'll see list of files narrowed by shell query. Right side is a split preview window rendering content that can be scrolled to better get what is looked for. Since no files matched with xargs, I switched with tab to "grep mode" and prepend query with ' (single quote) to discard default fuzziness of skim. There preview window points attention to the concrete line with matched word. Afterall, file opened with nvim.

 
 

Personal web archive

Slight variation of previous idea. Gif starts from picking up URL to archive page locally. wa is another bash function. It wraps wget to download page fully when called with argumens, or runs same machinery as above to navigate all web archive store. It also shows, that preview can display images as ASCII blocks with help of viu (this is tmux restriction) or, additionally, open them, in my case, with Quick Look.

wa

Code navigation

In addition to two modes—files and grep—switching between each other with tab, vim extends them with outline mode (pipe in current buffer to the ctags and massage its output back with awk), and buffers mode (show loaded files and allow to close'em). This is not something new to those already using fzf or skim as a daily driver. Maybe just good integration with skim previewer and seamless mode switching without the need to close skim window and hitting another binding keys for next function.

 
 

Setup tips

Keep as most options as you can in environment variable

Even though varables values would look extremely cryptic, this lets hold important defaults in a single place. I've found that I can't use the only options for all the use cases, but always trying hard to put sane defaults here.

export SKIM_DEFAULT_COMMAND="fd --type file --follow --hidden --exclude .git --color=always || find ."
export SKIM_DEFAULT_OPTIONS="
--ansi \
--reverse \
--color=16 \
--delimiter=: \
--preview-window='right:65%:hidden:+{2}-/2' \
--preview='
    case \`file -bL --mime-type {1}\` in
      text/html)         w3m {1} ;;
      text/troff)        man {1} ;;
      text/*)            bat --style=numbers --color=always --highlight-line {2}:+0 {1} ;;
      image/*)           viu {1} ;;
      application/pdf)   pdftotext -layout -nopgbrk {1} - ;;
      *)                 hexyl --bytes=4KiB --color=always --border=none {1} ;;
    esac
'
--bind='\
ctrl-d:half-page-down,\
ctrl-u:half-page-up,\
?:toggle-preview,\
alt-/:execute-silent(ql {}),\
alt-space:execute-silent(ql {})+down,\
alt-j:preview-down,\
alt-k:preview-up,\
alt-h:preview-left,\
alt-l:preview-right,\
alt-d:preview-page-down,\
alt-u:preview-page-up,'\
"

Here you can see key bindings modifications --bind= that mimic those in vim. --preview expects string that will be passed to $SHELL -c, so value is basically a map of MIME type to the helper tool.

Wrap sk with shell function

This make possible emulate mode switching from fd to rg and back again. It is also use euristics or conventions when selected item has been picked up.

sk() {
  # stdin is not a tty means data has been piped into skim
  if [ ! -t 0 ]; then
    command sk "$@"
    return $?
  fi

  local qry key sel mod res

  __fd() {
    command sk \
      --prompt="Fls> " \
      --header=" " \
      --no-multi \
      --cmd='fd --color=always --type file --follow' \
      --expect=enter,alt-enter,ctrl-m,alt-m,ctrl-c,esc,tab \
      --print-query \
      --query="$1"
  }

  __rg() {
    command sk \
      --prompt="Rgp> " \
      --header=" " \
      --no-multi \
      --cmd='rg --color=always --ignore-case --line-number "{}"' \
      --delimiter=: \
      --nth="3.." \
      --expect=enter,alt-enter,ctrl-m,alt-m,ctrl-c,esc,tab \
      --print-query \
      --query="$1"
  }

  mod="__fd"
  qry="$@"

  while true; do
    res=$(eval '$mod' '$qry')
    qry=$(echo "$res" | sed -n 1p)
    key=$(echo "$res" | sed -n 2p)
    sel=$(echo "$res" | sed -n 3p)

    [ "$key" != "tab" ] && break

    if [ "$mod" = "__rg" ]; then
      mod="__fd"
    else
      mod="__rg"
    fi
  done

  case "$key" in
    ctrl-m | enter)
      if [ -z "$sel" ]; then
        $EDITOR "${qry#\'}"
      elif [ "$mod" = "__fd" ]; then
        $EDITOR "$sel"
      else
        $EDITOR $(echo "$sel" | cut -f1,2 -d':' | sed 's/:/ \+/g')
      fi
      ;;
    alt-m | alt-enter)
      echo "${sel:-$qry}"
      ;;
    ctrl-c | esc | *)
      return 0
      ;;
  esac
}
Use that wrapped function as a basis for other workflows

Here is my m:

m() {
  trap "cd `pwd`" EXIT
  cd $HOME/mem
  sk "$@"
}

And wa:

wa() {
  trap "cd `pwd`" EXIT
  cd $HOME/warc

  if [ -z "$1" ]; then
    EDITOR="open" sk "$@"
    return $?
  fi

  wget \
    --page-requisites \
    --convert-links \
    --adjust-extension \
    --compression=auto \
    --reject-regex='/search|/rss' \
    --no-if-modified-since \
    --no-check-certificate \
    --user-agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64)' \
    "$@"
}
If you're vim user, write some extra commands

Mad looking? Definitely true. This is an example of outline mode.

command! -nargs=? Otln
\ call skim#run({
\     'source': "ctags -f - --sort=foldcase --file-scope=no --all-kinds=* --fields=Kn " . expand('%:p') .
\               ' | grep -F "/;\""'.
\               " | awk -F $'\t' ".
\               "'".
\               '   BEGIN { OFS=FS }'.
\               '         {'.
\               '           gsub(/implementation/, "impl", $4);'.
\               '           gsub(/\/\^[ ]*/, "", $3);'.
\               '           gsub(/[; {]*\$\/;"$/, "", $3);'.
\               '           gsub(/line:/, "", $5);'.
\               '           printf "%d\t%12s  %-31s\t%s\n", $5, $4, $1, $3;'.
\               "         }'".
\               " | sort -bk2,2",
\     'sink*': function('s:OpenUp', ['Otln']),
\     'options': ['--header= ', '--layout=reverse',
\                 '--no-sort',
\                 '--query=' . <q-args>,
\                 '--expect=tab',
\                 '--print-query',
\                 '--delimiter=\t', '--nth=3',
\                 '--color=16', '--inline-info', '--prompt=Otln> ',
\                 '--cycle',
\                 '--preview-window=right:65%:hidden:+{1}-/2',
\                 '--preview=bat --style=numbers --color=always --highlight-line={1} ' . expand('%:p')] })

More can be found at dotfiles repo.