511 lines
13 KiB
Bash
Executable File
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
|