1
0
Fork 0
dotfiles/.local/bin/filedialog

511 lines
13 KiB
Bash
Executable File

#!/bin/bash
set -e
ENABLE_HIDDEN_FILES=""
ENABLE_DEEP_CHOOSER=""
ENABLE_DEEP_CHOOSER_DIRECTORIES=""
MENU_BACKEND="auto"
FILE_OPEN_MODE="xdg"
CAN_OPEN_DIRECTORIES="y"
CAN_OPEN_FILES="y"
EXIT_AFTER_OPEN="y"
USE_GREP_FOR_SEARCHING=""
FZF_PREVIEW_COMMAND="slatefm-preview {2}"
HOOK_ON_CD=""
HOOK_ON_OPEN_PATH=""
HOOK_ON_EXIT=""
HOOK_ON_ENTER=""
# FILE_TAG="🗒️"
# DIRECTORY_TAG="📁"
# SYMLINK_TAG="↪️"
# COMMAND_TAG="⚙️"
# PWD_TAG="📍"
# UNKNOWN_TAG="❔"
FILE_TAG=""
DIRECTORY_TAG=""
SYMLINK_FILE_TAG=""
SYMLINK_DIRECTORY_TAG=""
HOME_TAG=""
COMMAND_TAG=""
PWD_TAG=""
BACK_TAG=""
UNKNOWN_TAG=""
# Setting depends on menu, see after cli parser
TAG_SEPERATOR=""
COMMAND="interactive"
MODE="${MODE:-normal}"
SEARCH_QUERY="${SEARCH_QUERY:-}"
QUERY=""
SUGGEST_HOME_FOLDER=""
SUGGEST_PWD_HINT="y"
SUGGEST_BACK_HINT="y"
COMMAND_SUGGESTION=""
show_help() {
cat <<EOF
Usage: filedialog <<options>>
OPTIONS:
--pwd <dir> - the directory the filedialog will open in
--menu (bemenu|fzf)
--[no]-show-hidden - enable/disable showing hidden files by default
--[no]-deep-chooser - enable/disable deep chooser mode by default
--open-mode <mode> - how to behave when opening something
xdg - use xdg-open to open files
print - print the opened file
print0 - like print but with null-seperator
--no-open-files - disables opening regular files
--no-open-directories - disables opening directories
--no-exit-after-open - allow opeing multiple files, quit manually
--grep-search - make sure that grep is always used for searching,
even when fzf is present.
EOF
}
while [[ "$#" -gt 0 ]]; do
case "$1" in
--pwd) cd "$(realpath -s "$2")"; shift 2;;
--show-hidden) ENABLE_HIDDEN_FILES="y"; shift 1 ;;
--no-show-hidden) ENABLE_HIDDEN_FILES=""; shift 1 ;;
--menu) MENU_BACKEND="$2"; shift 2 ;;
--deep-chooser) ENABLE_DEEP_CHOOSER="y"; shift 1 ;;
--no-deep-chooser) ENABLE_DEEP_CHOOSER=""; shift 1 ;;
--enable-deep-chooser-directories) ENABLE_DEEP_CHOOSER_DIRECTORIES="y"; shift 1;;
--open-mode) FILE_OPEN_MODE="$2"; shift 2;;
--no-open-files) CAN_OPEN_FILES=""; shift 1;;
--no-open-directories) CAN_OPEN_DIRECTORIES=""; shift 1;;
--no-exit-after-open) EXIT_AFTER_OPEN=""; shift 1;;
--grep-search) USE_GREP_FOR_SEARCHING="y"; shift 1;;
--hook-on-enter) HOOK_ON_ENTER="$2"; shift 2;;
--hook-on-cd) HOOK_ON_CD="$2"; shift 2;;
--hook-on-open-path) HOOK_ON_OPEN_PATH="$2"; shift 2;;
--hook-on-exit) HOOK_ON_EXIT="$2"; shift 2;;
--mode) MODE="$2"; shift 2;;
--search-query) SEARCH_QUERY="$2"; shift 2;;
--query) QUERY="$2"; shift 2;;
--search)
MODE="search"
ENABLE_DEEP_CHOOSER=y
ENABLE_DEEP_CHOOSER_DIRECTORIES=y
SEARCH_QUERY="$2"
shift 2;;
--help) show_help; exit 0;;
-*) printf "Unknown option: %s\n" "$1"; exit 1;;
*) COMMAND="$1" ; shift 1 ;;
esac
done
am_i_in_a_terminal() {
[ -t 0 ] && [ -t 1 ] || return 1
if [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ] ; then
[ "_$TERM" != "_linux" ] || return 1
fi
}
if [ "_$MENU_BACKEND" = "_auto" ] ; then
if am_i_in_a_terminal && command -v fzf >&- 2>&- ; then
MENU_BACKEND=fzf
elif command -v bemenu >&- 2>&- ; then
MENU_BACKEND=bemenu
elif command -v dmenu >&- 2>&- ; then
MENU_BACKEND=dmenu
fi
fi
if [ -z "$TAG_SEPERATOR" ] ; then
case "$MENU_BACKEND" in
dmenu)
TAG_SEPERATOR=" " ;;
*)
TAG_SEPERATOR="$(printf "\t")" ;;
esac
fi
tag_paths() {
cd "$1"
while read -r item ; do
TAG="$UNKNOWN_TAG"
if [ -L "$item" ] ; then
if [ -d "$item" ] ; then
TAG="$SYMLINK_DIRECTORY_TAG"
else
TAG="$SYMLINK_FILE_TAG"
fi
elif [ -d "$item" ] ; then
TAG="$DIRECTORY_TAG"
item="$item/"
elif [ -f "$item" ] ; then
TAG="$FILE_TAG"
fi
printf "%s%s%s\n" "$TAG" "$TAG_SEPERATOR" "$item"
done
}
show_fzf_menu() {
fzf --prompt="$1> " \
--print-query \
--keep-right \
--layout=reverse \
--delimiter "$TAG_SEPERATOR" \
--disabled \
--bind "change:reload:$0 menu_entries --query {q}" \
--preview "$FZF_PREVIEW_COMMAND"
}
show_menu() {
case "$MENU_BACKEND" in
fzf)
FZF_OUTPUT="$(show_fzf_menu "$1")"
if [[ "$(printf "%s\n" "$FZF_OUTPUT" | wc -l )" -gt 1 ]] ; then
printf "%s" "$FZF_OUTPUT" | tail -n 1
else
printf "%s" "$FZF_OUTPUT"
fi
;;
dmenu)
dmenu $DMENU_OPTS -l 25 ;;
bemenu|*)
bemenu -l 25 -p "$1" -i
;;
esac
}
# query
searcher() {
if [ -z "$1" ] ; then
cat
elif [ -z "$USE_GREP_FOR_SEARCHING" ] && command -v fzf > /dev/null 2> /dev/null ; then
fzf --filter="$1"
else
grep -i -- "$1"
fi
}
wrapped_rg() {
if [ "_$(git rev-parse --show-toplevel 2>&-)" = "_$HOME" ] ; then
timeout 3 rg --no-ignore "$@" 2>&-
else
timeout 3 rg "$@" 2>&-
fi
}
# pwd, prefilter
list_files() {
if [ -n "$ENABLE_DEEP_CHOOSER" ] ; then
searchtype=f
[ -z "$ENABLE_DEEP_CHOOSER_DIRECTORIES" ] || searchtype="f,d"
prefilter="*${2:-}*"
prefilter_opt=-iname
if grep -q / <<< "$2" ; then prefilter_opt=-ipath ; fi
{
cd "${1:-.}"
if [ -n "$ENABLE_HIDDEN_FILES" ] ; then
timeout 3 find -type "$searchtype" \
$prefilter_opt "$prefilter" 2>&-
elif [ -z "$ENABLE_DEEP_CHOOSER_DIRECTORIES" ] && command -v rg >&- 2>&- ; then
wrapped_rg --files
else
timeout 3 find "${1:-.}" -type "$searchtype" \
$prefilter_opt "$prefilter" ! -path '*/.*' 2>&-
fi
}
elif [ -n "$ENABLE_HIDDEN_FILES" ] ; then
ls "${1:-.}" -A
else
ls "${1:-.}"
fi
}
# pwd, search, filter
generate_menu_entries() {
[ -z "$SUGGEST_HOME_FOLDER" ] || printf "%s\n" "$HOME_TAG$TAG_SEPERATOR$HOME"
if [ -n "$SUGGEST_PWD_HINT" ] ; then
CHOOSER_PATH="$(realpath -s "$1")"
HOME_REP="~"
printf "%s%s%s\n" "$PWD_TAG" "$TAG_SEPERATOR" "${CHOOSER_PATH/#$HOME/$HOME_REP}"
fi
[ -z "$SUGGEST_BACK_HINT" ] || [ "_$CHOOSER_PATH" = "_/" ] ||
printf "%s%s%s\n" "$BACK_TAG" "$TAG_SEPERATOR" ".."
[ -z "$COMMAND_SUGGESTION" ] || printf "%s\n" "$COMMAND_TAG$TAG_SEPERATOR$COMMAND_SUGGESTION"
case "$MODE" in
rg)
if command -v rg >&- 2>&- ; then
wrapped_rg --files "$1" -- "$2"
else
grep -rl -- "$2" "$1"
fi | grep -v "/__pycache__/" | searcher "$3" | head -n 1000 | tag_paths "$1" ;;
*)
prefilter="$( grep -oP "[a-zA-Z0-9_/-]*" <<< "$2" | head -n 1 )"
list_files "$1" "$prefilter" | searcher "$2" | searcher "$3" | head -n 1000 | tag_paths "$1" ;;
esac
}
# pwd, search
show_chooser() {
SEARCH_QUERY="$2"
export SEARCH_QUERY
PROMPT="open"
case "$MODE" in
search)
PROMPT="search: $2"
ENABLE_DEEP_CHOOSER="y"
ENABLE_DEEP_CHOOSER_DIRECTORIES=y
;;
rg)
PROMPT="rg: $2" ;;
esac
generate_menu_entries "${1:-.}" "$2" | show_menu "$PROMPT"
}
show_help() {
{
cat <<EOF
$BACK_TAG$TAG_SEPERATOR:go-back
┗━ NOTE: The commands below run when activated, this doubles as a menu
┏━ INTERACTION ━━━┅
$COMMAND_TAG$TAG_SEPERATOR:open - open the current directory
$COMMAND_TAG$TAG_SEPERATOR:run - run a command in the current directory
$COMMAND_TAG$TAG_SEPERATOR:reload - reload the current directory (:)
┏━ INVISIBLE ━━━━━┅
┃ Invisible options are not explicitly listed, but they should be intuitive.
┃ When you enter a path to a directory or file, it will be openedi.
┃ When the entered text is neither a valid path nor command it will be treated as
┃ a query for the search mode.
┏━ NAVIGATION ━━━━┅
$COMMAND_TAG$TAG_SEPERATOR:.. - go up a directory (..)
$COMMAND_TAG$TAG_SEPERATOR:~ - go to home directory (~)
$COMMAND_TAG$TAG_SEPERATOR:/ - go to filesystem root (/)
$COMMAND_TAG$TAG_SEPERATOR:- - go back one step (-)
$COMMAND_TAG$TAG_SEPERATOR:-- - go back to initial directory (--)
┏━ SETTINGS ━━━━━━┅
$COMMAND_TAG$TAG_SEPERATOR:hidden - toggle hidden files (:h)
$COMMAND_TAG$TAG_SEPERATOR:deep - enable deeep search (:::)
$COMMAND_TAG$TAG_SEPERATOR:flat - back to directory mode (..)
$COMMAND_TAG$TAG_SEPERATOR:find - prefiltered search mode (:f)
$COMMAND_TAG$TAG_SEPERATOR:search - alias for find
┏━ APPLICATION ━━━┅
$COMMAND_TAG$TAG_SEPERATOR:quit
┏━ NOTE ━━━━━━━━━━┅
┃ You can always use // in place pf the : prefix
┃ This is in case you have some funny filenames to avoid matching
EOF
} | show_menu "command" | sed -e 's/ * - .*//' -e 's/^[┃┏┗].*//' -e "s/---.*//"
}
byebye() {
export RETURNCODE="$1"
run_hook "$HOOK_ON_EXIT"
exit "$1"
}
handle_open() {
[ -d "$1" ] && [ -z "$CAN_OPEN_DIRECTORIES" ] && return
[ -f "$1" ] && [ -z "$CAN_OPEN_FILES" ] && return
run_hook "$HOOK_ON_OPEN_PATH" || case "$FILE_OPEN_MODE" in
xdg) xdg-open "$1" ;;
print) printf "%s\n" "$1" ;;
print0) printf "%s\0" "$1" ;;
*) true ;;
esac
[ -n "$EXIT_AFTER_OPEN" ] && byebye
}
export INITIAL_PWD="$PWD"
# on fail do default action
run_hook() {
[ -n "$1" ] && bash -c "$1"
}
do_cd() {
[ -r "$1" ] || return
export NEW_PATH="$1"
export OLD_PATH="$PWD"
run_hook "$HOOK_ON_CD" || cd "$1"
}
resolve_path() {
if grep -q "^~$" <<< "$1" || grep -q "^~/" <<< "$1" ; then
printf "%s" "$HOME$(sed 's/^~//' <<< "$1")"
else
printf "%s" "$1"
fi
}
interactive() {
while true; do
export MODE
case "$MODE" in
"help")
ENTRY="$(show_help)" ;;
normal|search|*)
ENTRY="$(show_chooser "$PWD" "$SEARCH_QUERY" | sed -e 's|^//|:|' )"
[ -z "$ENTRY" ] && byebye 1
;;
esac
ENTRY="$(printf "%s" "$ENTRY" | sed -e "s|^//|:|" -e "s/$COMMAND_TAG$TAG_SEPERATOR:/:/")"
export ENTRY
#notify-send "entry: $ENTRY"
run_hook "$HOOK_ON_ENTER" || case "$ENTRY" in
:q|:quit)
byebye 1 ;;
:help)
MODE="help" ;;
:h|:hidden|.|:.)
[ -n "$ENABLE_HIDDEN_FILES" ] && ENABLE_HIDDEN_FILES="" || ENABLE_HIDDEN_FILES="y" ;;
:deep|:::)
ENABLE_DEEP_CHOOSER="y"
MODE=normal
;;
:flat)
echo deep_chooser_disabled
ENABLE_DEEP_CHOOSER="" ;;
:f|:find|:search)
MODE="search" ;;
::f\ *|:find\ *|:search\ *)
MODE="search"
SEARCH_QUERY="$(printf "%s" "$ENTRY" | sed 's/^[^ ]* *//' )"
;;
:rg)
MODE="rg" ;;
:rg\ *)
MODE="rg"
SEARCH_QUERY="$(printf "%s" "$ENTRY" | sed 's/^[^ ]* *//' )"
;;
:open)
handle_open "$PWD" ;;
:run\ *)
sh -c "$(printf "%s" "$ENTRY" | sed 's/^:run *//' )" &
# we want fast commands to finish immedeately
sleep .5;;
:|:reload) true ;;
:\~|\~)
ENABLE_DEEP_CHOOSER=""
SEARCH_QUERY=""
MODE=normal
do_cd ~ ;;
:/|/)
ENABLE_DEEP_CHOOSER=""
SEARCH_QUERY=""
MODE=normal
do_cd / ;;
:-|-)
ENABLE_DEEP_CHOOSER=""
SEARCH_QUERY=""
MODE=normal
do_cd - ;;
:--|--)
ENABLE_DEEP_CHOOSER=""
SEARCH_QUERY=""
MODE=normal
do_cd "$INITIAL_PWD" ;;
:..|..|:go-back|$BACK_TAG$TAG_SEPERATOR*)
if [ "_$MODE" != "_normal" ] ; then
MODE="normal"
SEARCH_QUERY=""
elif [ -n "$ENABLE_DEEP_CHOOSER" ] ; then
ENABLE_DEEP_CHOOSER=""
else
do_cd ..
fi ;;
:*)
notify-send "Unknown Command" ;;
$HOME_TAG*)
do_cd "$HOME" ;;
$PWD_TAG$TAG_SEPERATOR*|$DIRECTORY_TAG$TAG_SEPERATOR*|$FILE_TAG$TAG_SEPERATOR*|$SYMLINK_TAG$TAG_SEPERATOR*)
ENTRY_PATH="$(resolve_path "$(printf "%s" "$ENTRY" | sed "s/^.*$TAG_SEPERATOR//" )")"
if [ -d "$ENTRY_PATH" ] ; then
do_cd "$ENTRY_PATH"
elif [ -f "$ENTRY_PATH" ] ; then
handle_open "$ENTRY_PATH"
fi ;;
*)
if [ "_$MODE" = "_search" ] || [ "_$MODE" = "_rg" ] ; then
SEARCH_QUERY="$ENTRY"
elif [ -d "$ENTRY" ] ; then
do_cd "$ENTRY"
elif [ -f "$ENTRY" ] ; then
handle_open "$ENTRY"
else
MODE="search"
SEARCH_QUERY="$ENTRY"
fi ;;
esac
[ "_$MODE" = "_help" ] && [ -n "$ENTRY" ] && MODE="normal"
done
}
case "$COMMAND" in
interactive)
interactive ;;
ls)
list_files . | searcher "$SEARCH_QUERY" ;;
tag)
tag_paths . ;;
menu_entries)
[ "_$QUERY" != "_~" ] || SUGGEST_HOME_FOLDER="y"
[ -z "$QUERY" ] || SUGGEST_PWD_HINT=""
grep -Pq "^\.?\.?$" <<< "$QUERY" || SUGGEST_BACK_HINT=""
case "$QUERY" in
:f|:s|:find|:search)
COMMAND_SUGGESTION=":search $SEARCH_QUERY" ;;
:rg)
COMMAND_SUGGESTION=":rg $SEARCH_QUERY" ;;
:rg\ *|rg\ *)
MODE=rg
SEARCH_QUERY="$(sed "s/^[^ ]* *//" <<< "$QUERY")"
COMMAND_SUGGESTION=":rg $SEARCH_QUERY"
generate_menu_entries . "$SEARCH_QUERY"
exit
;;
esac
FILTER_QUERY="$(sed "s/.*\///" <<< "$QUERY")"
grep -qv "^\." <<< "$FILTER_QUERY" || ENABLE_HIDDEN_FILES="y"
FILEPATH="$(resolve_path "$(sed "s/[^/]*$//" <<< "$QUERY")")"
if ! [ -d "$FILEPATH" ] ; then
FILEPATH="."
FILTER_QUERY="$QUERY"
fi
{
bytes="$(generate_menu_entries "$FILEPATH" "$SEARCH_QUERY" "$FILTER_QUERY" | dd 3>&2 2>&1 1>&3 | tail -n 1 | cut -d" " -f1)"
if [[ "$bytes" -lt 3 ]] ; then
MODE=search
ENABLE_DEEP_CHOOSER=y
ENABLE_DEEP_CHOOSER_DIRECTORIES=y
COMMAND_SUGGESTION=":search $FILTER_QUERY"
generate_menu_entries "$FILEPATH" "$FILTER_QUERY" 2>/dev/null
fi
} 2>&1
;;
*)
printf "Unknown command: '%s'\n" "$COMMAND"
exit 1 ;;
esac