Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

I often use bkt to speed up fzf's --preview behavior by caching the output of each preview invocation.

Of course you can just prefix the command you want to run with bkt, like:

... | fzf --preview='bkt --ttl=15m --stale=15s -- your_preview_command {}'

This works, and using a long TTL and short stale duration means - once cached - the preview will be refreshed in the background as the user navigates the fzf selector. But it'd be great if the user didn't have to wait for each preview the first time they scroll over a given element - ideally the cache would be warm(er) so the user can dive right in. Here's a demo directory-picker script that warms bkt's cache before launching fzf so the previews render more responsively. Give it a try!

#!/bin/bash
#
# Interactive directory picker using fzf, with a dynamic preview of each
# directory which is cached by bkt. Pass one or more directories as
# arguments and all immediate subdirectories will be displayed by the
# picker. If no arguments are passed your HOME directory is used by
# default.
#
# To use this as a directory switcher define a shell function like so:
#
# goto() {
#   local dir
#   dir=$(/path/to/dir-picker.sh ~/Workspaces) || return
#   cd "$dir"
# }

# Use a dynamic scope for bkt so we don't surface stale results or collide with other usages
printf -v BKT_SCOPE 'dir-picker-%(%s)T-%s' -1 "$$"
export BKT_SCOPE

# Renders the preview window displayed by fzf
# Currently it simply shows the directory contents or git status
# for git repos but could be expanded to do more complex/slow work.
# You could move this out to a separate script instead if preferred.
preview() {
  cd "${1:?}" || return
  printf '\e[93m%s\e[0m\n\n' "$PWD"
  if [[ -e .git ]]; then
    git -c color.ui=always status
  else
    ls --color=always -oh
  fi
}
# Export the preview function so it can be called by fzf's preview subprocess
export -f preview

# Command to execute the preview function via bkt, so that repeated invocations
# are cached. The directory needs to be specified as an additional argument.
PREVIEW_CMD=(bkt --ttl=15m --stale=15s -- bash -c 'preview "$1"' -)

# Caller-provided set of directories to search
DIRS=("$@")
if (( ${#DIRS[@]} == 0 )); then
  DIRS=("$HOME")
fi

# Enumerate all subdirectories, these will be passed to fzf
readarray -t CHOICES < <(find "${DIRS[@]}" -mindepth 1 -maxdepth 1 -type d)

# But first kick off a background thread invoking bkt on each directory.
# This isn't necessary, but helps warm the cache so fzf is more responsive when it starts.
#
# This could be implemented using bkt's --warm flag, but instead we intentionally
# process each directory in sequence to avoid running potentially dozens of expensive
# preview processes concurrently and needlessly. A user who scrolls up quickly may
# have to wait, but the first few directories should be warm by the time the user
# starts browsing.
(
  for d in "${CHOICES[@]:1}"; do
    "${PREVIEW_CMD[@]}" "$d"
  done &
) &>/dev/null

# Launch fzf! The PREVIEW_CMD is escaped with @Q before being passed to fzf so
# that the command is properly quoted.
printf '%s\n' "${CHOICES[@]}" | fzf --select-1 --exit-0 --preview="${PREVIEW_CMD[*]@Q} {}"

Notes:

  • The preview function demoed here is pretty zippy, especially for non-git directories, so try adding a sleep 2 line to it to observe how a slower command would run.
  • I love exporting shell functions to be used by fzf - it's super convenient for defining complex behavior that doesn't merit being pulled into a standalone script. This works even without bkt, you could just do --preview=preview {}' if you wanted since fzf dispatches the preview command through your $SHELL by default.
  • You could use a fixed scope like BKT_SCOPE=dir-picker. This would allow separate calls to the script to share the same cache, but this might lead to unexpectedly stale data showing up. By using a dynamic scope you ensure all previews are at least as fresh as when the picker launched, even if they subsequently get stale.
  • As noted in the script the warming behavior doesn't rely on bkt's --warm in order to warm the cache more gradually. This might not be the best tradeoff, depending on the specific use case. You could tweak it to warm a few directories at a time to make faster progress or process the options in a different order if you can anticipate which options a user is likely to care about.
  • As noted in the README, a downside of this pattern is that bkt (currently) doesn't stream output as the command is executing, which fzf supports. It may be better not to cache commands that are slow but update frequently while running.
You must be logged in to vote

Replies: 2 comments · 2 replies

This comment was marked as off-topic.

@borestad

This comment was marked as off-topic.

@dimo414
Comment options

dimo414 May 3, 2023
Maintainer Author

Thanks! I've moved this over to a separate thread for clarity's sake, I'll post some more thoughts there.

Comment options

Interesting one. I'm building my own alternative to fzf called sunbeam, I'll probably include bkt in some examples.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.