junegunn/fzf

Custom sort weights

Closed this issue · 3 comments

xeruf commented
  • I have read through the manual page (man fzf)
  • I have the latest version of fzf
  • I have searched through the existing issues

Info

  • OS
    • Linux
    • Mac OS X
    • Windows
    • Etc.
  • Shell
    • bash
    • zsh
    • fish

Problem

I have a fzf script that searches through config files in ~/.config and /etc. Most of the time I want something from .config, but I want to keep etc in there for other cases. So what I'd like to do is some way to assign a weight by matching a substring so the config files are preferred - currently it matches awkward paths in /etc far to often. That could also be useful for cases such as #78.

An idea would be something like fd . -t f --full-path ~/.config /etc | fzf --weight /etc=-5 - which would reduce the weight of paths matching that string (the number is arbitrary, I haven't had a look at the algorithms), or even having a simple config file that can be specified to support more complicated cases.

Making the matching/sorting algorithm configurable is not something I want to do. You might want to preprocess the input so that the more important items are listed first and start fzf with --tiebreak=index.

xeruf commented

In case someone else is interested, here my solution - it is a shell function for config editing, and it caches the last 9 selecions so they are immediately up for selection:

conf() {
	conf_cache="$XDG_CACHE_HOME/conf"
	conf_tmp="${conf_cache}.tmp"
	conf_extra="$XDG_CONFIG_HOME/conf-extra"
	touch "$conf_cache"
	# | xargs file | grep text | cut -d':' -f1 # this would filter out non-text files, but it's ridiculously slow
	result="$({ cat $conf_cache $conf_extra; fd --type file --hidden --maxdepth 1 . ~; fd --type file . --full-path $XDG_CONFIG_HOME /etc } | awk '!a[$0]++' | fzf -1 -0 --tiebreak=end,length --preview 'bat --color=always --style=numbers --line-range :200 {}')"
	test "$result" && ((echo "$result" | cat - "$conf_cache" | head -9 >"$conf_tmp" && mv "$conf_tmp" "$conf_cache") & $EDITOR "$result")
}

@xerus2000 I further streamlined the code into a dash script:

#!/bin/dash

echo() {
    printf -- "%s\n" "$*"
}

mru_count=30
if test -z "$context" ; then
    echo "$0: You need to provide a context" >&2
    exit 1
fi


conf_dir="$HOME/.cache/fzf_mru"
mkdir -p "$conf_dir"
conf_cache="$conf_dir/${context}"
touch "$conf_cache"
conf_tmp="${conf_cache}.tmp"

# Removing duplicate lines: seen is an associative-array that Awk will pass every line of the file to. If a line isn't in the array then seen[$0] will evaluate to false. (The name 'seen' does not matter.)
result="$(cat "$conf_cache" - | awk '!seen[$0]++' | fzf --tiebreak=index "$@")" || return $?
if test -n "$result" ; then
    echo "$result" | cat - "$conf_cache" | awk '!seen[$0]++' | head -n "$mru_count" > "$conf_tmp" && mv "$conf_tmp" "$conf_cache"
    echo "$result"
else
    return 1
fi

Usage:

cat choices | context=some_context fzf_mru.sh

The latest version of this script can be found here.