From bc0233962a39309242e42d50ec8ea9a626427bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=84min=20Baumeler?= Date: Thu, 3 Jul 2025 11:41:14 +0200 Subject: [PATCH] impr: relaunch fzf only when necessary --- src/awk/list.awk | 2 + src/main.sh | 182 +++++++++++++----------------------------- src/sh/cli.sh | 120 +++++++++++++++++++++------- src/sh/cligit.sh | 25 ------ src/sh/cliinternal.sh | 92 ++++----------------- src/sh/helper.sh | 9 +++ src/sh/icalendar.sh | 128 +++++++++++++++++++++++++++++ 7 files changed, 303 insertions(+), 255 deletions(-) create mode 100644 src/sh/helper.sh create mode 100644 src/sh/icalendar.sh diff --git a/src/awk/list.awk b/src/awk/list.awk index dfc4e38..0e82ad1 100644 --- a/src/awk/list.awk +++ b/src/awk/list.awk @@ -110,6 +110,7 @@ ENDFILE { { print 10, "-", + type, "-", RED "ERROR: file '" fpath "' contains whitespaces!" OFF exit @@ -195,6 +196,7 @@ ENDFILE { print psort, mod, + type, fpath, collection, datecolor d OFF, diff --git a/src/main.sh b/src/main.sh index a8dd268..88b6071 100644 --- a/src/main.sh +++ b/src/main.sh @@ -2,20 +2,14 @@ set -eu -err() { - echo "❌ $1" >/dev/tty -} +# Helper functions +. "sh/helper.sh" -if [ -z "${FZF_VJOUR_USE_EXPORTED:-}" ]; then - # Read configuration - . "sh/config.sh" +# Read configuration +. "sh/config.sh" - # Load awk scripts - . "sh/awkscripts.sh" - - FZF_VJOUR_USE_EXPORTED="yes" - export FZF_VJOUR_USE_EXPORTED -fi +# Load awk scripts +. "sh/awkscripts.sh" __lines() { find "$ROOT" -type f -name '*.ics' -print0 | xargs -0 -P 0 \ @@ -54,126 +48,64 @@ if [ "${1:-}" = "--help" ]; then exit fi -# Git -. "sh/cligit.sh" - -# Command line arguments: Interal use -. "sh/cli.sh" +# iCalendar routines +. "sh/icalendar.sh" # Command line arguments: Interal use . "sh/cliinternal.sh" -while [ -n "${1:-}" ]; do - case "${1:-}" in - "--completed") - shift - cliquery="${cliquery:-} ✅" +# Command line arguments: Interal use +. "sh/cli.sh" + +while true; do + query=$(stripws "$query") + selection=$( + __lines | $FZF --ansi \ + --query="$query " \ + --no-sort \ + --no-hscroll \ + --with-nth=5.. \ + --print-query \ + --accept-nth=4 \ + --preview="$0 --preview {4}" \ + --expect="ctrl-n,ctrl-alt-d" \ + --bind="ctrl-r:reload($0 --reload)" \ + --bind="ctrl-x:reload($0 --reload --toggle-completed {4})" \ + --bind="alt-up:reload($0 --reload --change-priority '+1' {4})" \ + --bind="alt-down:reload($0 --reload --change-priority '-1' {4})" \ + --bind="alt-0:change-query(!✅)" \ + --bind="alt-1:change-query(📘)" \ + --bind="alt-2:change-query(🗒️)" \ + --bind="alt-3:change-query(✅ | 🔲)" \ + --bind='focus:transform:[ {3} = "VTODO" ] && echo "rebind(ctrl-x)+rebind(alt-up)+rebind(alt-down)" || echo "unbind(ctrl-x)+unbind(alt-up)+unbind(alt-down)"' \ + --bind="ctrl-s:execute($SYNC_CMD ; printf 'Press to continue.'; read -r tmp)" + ) + + # Line 1: query + # Line 2: key ("" for enter) + # Line 3: relative file path + query=$(echo "$selection" | head -n 1) + key=$(echo "$selection" | head -n 2 | tail -n 1) + fname=$(echo "$selection" | head -n 3 | tail -n 1) + if [ "$fname" = "$key" ]; then + fname="" + fi + + file="$ROOT/$fname" + if [ ! -f "$file" ]; then + err "File '$file' does not exist!" + return 1 + fi + + case "$key" in + "ctrl-n") + __new ;; - "--no-completed") - shift - cliquery="${cliquery:-} !✅" - ;; - "--open") - shift - cliquery="${cliquery:-} 🔲" - ;; - "--no-open") - shift - cliquery="${cliquery:-} !🔲" - ;; - "--tasks") - shift - cliquery="${cliquery:-} ✅ | 🔲" - ;; - "--no-tasks") - shift - cliquery="${cliquery:-} !✅ !🔲" - ;; - "--notes") - shift - cliquery="${cliquery:-} 🗒️" - ;; - "--no-notes") - shift - cliquery="${cliquery:-} !🗒️" - ;; - "--journal") - shift - cliquery="${cliquery:-} 📘" - ;; - "--no-journal") - shift - cliquery="${cliquery:-} !📘" - ;; - "--filter") - shift - cliquery="${cliquery:-} $1" - shift - ;; - "--no-filter") - shift - cliquery="${cliquery:-} !$1" - shift + "ctrl-alt-d") + __delete "$file" ;; *) - err "Unknown option \"$1\"" - exit 1 + __edit "$file" ;; esac done -query=${cliquery:-${FZF_QUERY:-!✅}} -query=$(echo "$query" | sed "s/^ *//" | sed "s/ *$//") - -selection=$( - __lines | $FZF --ansi \ - --query="$query " \ - --no-sort \ - --no-hscroll \ - --ellipsis='' \ - --with-nth=4.. \ - --accept-nth=3 \ - --preview="$0 --preview {}" \ - --bind="ctrl-r:reload-sync($0 --reload)" \ - --bind="ctrl-alt-d:become($0 --delete {})" \ - --bind="ctrl-x:become($0 --toggle-completed {})" \ - --bind="alt-up:become($0 --increase-priority {})" \ - --bind="alt-down:become($0 --decrease-priority {})" \ - --bind="ctrl-n:become($0 --new)" \ - --bind="alt-0:change-query(!✅)" \ - --bind="alt-1:change-query(📘)" \ - --bind="alt-2:change-query(🗒️)" \ - --bind="alt-3:change-query(✅ | 🔲)" \ - --bind="ctrl-s:execute($SYNC_CMD ; printf 'Press to continue.'; read -r tmp)" -) -if [ -z "$selection" ]; then - return 0 -fi - -file="$ROOT/$selection" - -if [ ! -f "$file" ]; then - echo "ERROR: File '$file' does not exist!" >/dev/tty - return 1 -fi - -# Prepare file to be edited -filetmp=$(mktemp --suffix='.md') -awk "$AWK_EXPORT" "$file" >"$filetmp" -checksum=$(cksum "$filetmp") - -# Open in editor -$EDITOR "$filetmp" >/dev/tty - -# Update only if changes are detected -if [ "$checksum" != "$(cksum "$filetmp")" ]; then - file_new="$filetmp.ics" - awk "$AWK_UPDATE" "$filetmp" "$file" >"$file_new" - mv "$file_new" "$file" - if [ -n "${GIT:-}" ]; then - $GIT add "$file" - $GIT commit -q -m "File modified" -- "$file" - fi -fi -rm "$filetmp" - -exec "$0" diff --git a/src/sh/cli.sh b/src/sh/cli.sh index 6bd41c4..0b1caf6 100644 --- a/src/sh/cli.sh +++ b/src/sh/cli.sh @@ -1,34 +1,94 @@ +# Git +if [ "${1:-}" = "--git-init" ]; then + shift + if [ -n "${GIT:-}" ]; then + err "Git already enabled" + return 1 + fi + if ! command -v "git" >/dev/null; then + err "Git not installed" + return 1 + fi + git -C "$ROOT" init + git -C "$ROOT" add -A + git -C "$ROOT" commit -m 'Initial commit: Start git tracking' + exit +fi + +if [ "${1:-}" = "--git" ]; then + shift + if [ -z "${GIT:-}" ]; then + err "Git not supported, run \`$0 --git-init\` first" + return 1 + fi + $GIT "$@" + exit +fi + # Generate new entry if [ "${1:-}" = "--new" ]; then shift - collection=$(printf "%s" "$COLLECTION_LABELS" | tr ';' '\n' | $FZF --delimiter='=' --with-nth=2 --accept-nth=1) - file="" - while [ -f "$file" ] || [ -z "$file" ]; do - uuid=$($UUIDGEN) - file="$ROOT/$collection/$uuid.ics" - done - tmpmd=$(mktemp --suffix='.md') - { - echo "::: |> " - echo "::: <| " - echo "# " - echo "> " - echo "" - } >"$tmpmd" - checksum=$(cksum "$tmpmd") - - # Open in editor - $EDITOR "$tmpmd" >/dev/tty - - # Update if changes are detected - if [ "$checksum" != "$(cksum "$tmpmd")" ]; then - tmpfile="$tmpmd.ics" - awk -v uid="$uuid" "$AWK_NEW" "$tmpmd" >"$tmpfile" - mv "$tmpfile" "$file" - if [ -n "${GIT:-}" ]; then - $GIT add "$file" - $GIT commit -q -m "File added" -- "$file" - fi - fi - rm "$tmpmd" + __new fi + +# Build query +while [ -n "${1:-}" ]; do + case "${1:-}" in + "--completed") + shift + cliquery="${cliquery:-} ✅" + ;; + "--no-completed") + shift + cliquery="${cliquery:-} !✅" + ;; + "--open") + shift + cliquery="${cliquery:-} 🔲" + ;; + "--no-open") + shift + cliquery="${cliquery:-} !🔲" + ;; + "--tasks") + shift + cliquery="${cliquery:-} ✅ | 🔲" + ;; + "--no-tasks") + shift + cliquery="${cliquery:-} !✅ !🔲" + ;; + "--notes") + shift + cliquery="${cliquery:-} 🗒️" + ;; + "--no-notes") + shift + cliquery="${cliquery:-} !🗒️" + ;; + "--journal") + shift + cliquery="${cliquery:-} 📘" + ;; + "--no-journal") + shift + cliquery="${cliquery:-} !📘" + ;; + "--filter") + shift + cliquery="${cliquery:-} $1" + shift + ;; + "--no-filter") + shift + cliquery="${cliquery:-} !$1" + shift + ;; + *) + err "Unknown option \"$1\"" + exit 1 + ;; + esac +done +query=${cliquery:-!✅} +export query diff --git a/src/sh/cligit.sh b/src/sh/cligit.sh index c910d62..e69de29 100644 --- a/src/sh/cligit.sh +++ b/src/sh/cligit.sh @@ -1,25 +0,0 @@ -if [ "${1:-}" = "--git-init" ]; then - shift - if [ -n "${GIT:-}" ]; then - err "Git already enabled" - return 1 - fi - if ! command -v "git" >/dev/null; then - err "Git not installed" - return 1 - fi - git -C "$ROOT" init - git -C "$ROOT" add -A - git -C "$ROOT" commit -m 'Initial commit: Start git tracking' - exit -fi - -if [ "${1:-}" = "--git" ]; then - shift - if [ -z "${GIT:-}" ]; then - err "Git not supported, run \`$0 --git-init\` first" - return 1 - fi - $GIT "$@" - exit -fi diff --git a/src/sh/cliinternal.sh b/src/sh/cliinternal.sh index 2cd0ae2..8ea9d80 100644 --- a/src/sh/cliinternal.sh +++ b/src/sh/cliinternal.sh @@ -3,7 +3,7 @@ # Generate preview of file from selection if [ "${1:-}" = "--preview" ]; then shift - name=$(echo "$1" | cut -d ' ' -f 3) + name="$1" shift file="$ROOT/$name" awk -v field="DESCRIPTION" "$AWK_GET" "$file" | @@ -11,83 +11,25 @@ if [ "${1:-}" = "--preview" ]; then exit fi -# Delete file from selection -if [ "${1:-}" = "--delete" ]; then - shift - name=$(echo "$1" | cut -d ' ' -f 3) - shift - file="$ROOT/$name" - summary=$(awk -v field="SUMMARY" "$AWK_GET" "$file") - while true; do - printf "Do you want to delete the entry with the title \"%s\"? (yes/no): " "$summary" >/dev/tty - read -r yn - case $yn in - "yes") - rm -v "$file" - if [ -n "${GIT:-}" ]; then - $GIT add "$file" - $GIT commit -q -m "File deleted" -- "$file" - fi - break - ;; - "no") - break - ;; - *) - echo "Please answer \"yes\" or \"no\"." >/dev/tty - ;; - esac - done -fi - -# Toggle completed flag -if [ "${1:-}" = "--toggle-completed" ]; then - shift - name=$(echo "$1" | cut -d ' ' -f 3) - shift - file="$ROOT/$name" - tmpfile=$(mktemp) - awk "$AWK_ALTERTODO" "$file" >"$tmpfile" - mv "$tmpfile" "$file" - if [ -n "${GIT:-}" ]; then - $GIT add "$file" - $GIT commit -q -m "Completed toggle" -- "$file" - fi -fi - -# Increase priority -if [ "${1:-}" = "--increase-priority" ]; then - shift - name=$(echo "$1" | cut -d ' ' -f 3) - shift - file="$ROOT/$name" - tmpfile=$(mktemp) - awk -v delta="1" "$AWK_ALTERTODO" "$file" >"$tmpfile" - mv "$tmpfile" "$file" - if [ -n "${GIT:-}" ]; then - $GIT add "$file" - $GIT commit -q -m "Priority increased" -- "$file" - fi -fi - -# Decrease priority -if [ "${1:-}" = "--decrease-priority" ]; then - shift - name=$(echo "$1" | cut -d ' ' -f 3) - shift - file="$ROOT/$name" - tmpfile=$(mktemp) - awk -v delta="-1" "$AWK_ALTERTODO" "$file" >"$tmpfile" - mv "$tmpfile" "$file" - if [ -n "${GIT:-}" ]; then - $GIT add "$file" - $GIT commit -q -m "Priority decreased" -- "$file" - fi -fi - # Reload view if [ "${1:-}" = "--reload" ]; then shift + case "${1:-}" in + "--toggle-completed") + shift + fname="$1" + shift + __toggle_completed "$fname" >/dev/null + ;; + "--change-priority") + shift + delta=$1 + shift + fname="$1" + shift + __change_priority "$delta" "$fname" >>/tmp/foo + ;; + esac __lines exit fi diff --git a/src/sh/helper.sh b/src/sh/helper.sh new file mode 100644 index 0000000..bcaf77b --- /dev/null +++ b/src/sh/helper.sh @@ -0,0 +1,9 @@ +# Print error message +err() { + echo "❌ $1" >/dev/tty +} + +# Strip whitespaces from argument +stripws() { + echo "$@" | sed "s/^ *//" | sed "s/ *$//" +} diff --git a/src/sh/icalendar.sh b/src/sh/icalendar.sh new file mode 100644 index 0000000..6b1d23e --- /dev/null +++ b/src/sh/icalendar.sh @@ -0,0 +1,128 @@ +# Interface to modify iCalendar files + +# Toggle completed status of VTODO +# +# @input $1: Relative path to iCalendar file +__toggle_completed() { + fname="$1" + shift + file="$ROOT/$fname" + tmpfile=$(mktemp) + awk "$AWK_ALTERTODO" "$file" >"$tmpfile" + mv "$tmpfile" "$file" + if [ -n "${GIT:-}" ]; then + $GIT add "$file" + $GIT commit -q -m "Completed toggle" -- "$file" + fi +} + +# Change priority of VTODO entry +# +# @input $1: Delta, can be any integer +# @input $2: Relative path to iCalendar file +__change_priority() { + delta=$1 + shift + fname="$1" + shift + echo "call to __change_priority with delta=$delta and fname=$fname" >>/tmp/foo + file="$ROOT/$fname" + tmpfile=$(mktemp) + echo " tmpfile=$tmpfile" >>/tmp/foo + awk -v delta="$delta" "$AWK_ALTERTODO" "$file" >"$tmpfile" + echo " lines=$(wc -l tmpfile)" >>/tmp/foo + mv "$tmpfile" "$file" + if [ -n "${GIT:-}" ]; then + $GIT add "$file" + $GIT commit -q -m "Priority changed by $delta" -- "$file" + fi +} + +# Edit file +# +# @input $1: File path +__edit() { + file="$1" + shift + filetmp=$(mktemp --suffix='.md') + awk "$AWK_EXPORT" "$file" >"$filetmp" + checksum=$(cksum "$filetmp") + + # Open in editor + $EDITOR "$filetmp" >/dev/tty + + # Update only if changes are detected + if [ "$checksum" != "$(cksum "$filetmp")" ]; then + file_new="$filetmp.ics" + awk "$AWK_UPDATE" "$filetmp" "$file" >"$file_new" + mv "$file_new" "$file" + if [ -n "${GIT:-}" ]; then + $GIT add "$file" + $GIT commit -q -m "File modified" -- "$file" + fi + fi + rm "$filetmp" +} + +# Delete file +# +# @input $1: File path +__delete() { + file="$1" + shift + summary=$(awk -v field="SUMMARY" "$AWK_GET" "$file") + while true; do + printf "Do you want to delete the entry with the title \"%s\"? (yes/no): " "$summary" >/dev/tty + read -r yn + case $yn in + "yes") + rm -v "$file" + if [ -n "${GIT:-}" ]; then + $GIT add "$file" + $GIT commit -q -m "File deleted" -- "$file" + fi + break + ;; + "no") + break + ;; + *) + echo "Please answer \"yes\" or \"no\"." >/dev/tty + ;; + esac + done +} + +# Add file +__new() { + collection=$(printf "%s" "$COLLECTION_LABELS" | tr ';' '\n' | $FZF --delimiter='=' --with-nth=2 --accept-nth=1) + file="" + while [ -f "$file" ] || [ -z "$file" ]; do + uuid=$($UUIDGEN) + file="$ROOT/$collection/$uuid.ics" + done + tmpmd=$(mktemp --suffix='.md') + { + echo "::: |> " + echo "::: <| " + echo "# " + echo "> " + echo "" + } >"$tmpmd" + checksum=$(cksum "$tmpmd") + + # Open in editor + $EDITOR "$tmpmd" >/dev/tty + + # Update if changes are detected + if [ "$checksum" != "$(cksum "$tmpmd")" ]; then + tmpfile="$tmpmd.ics" + awk -v uid="$uuid" "$AWK_NEW" "$tmpmd" >"$tmpfile" + mv "$tmpfile" "$file" + if [ -n "${GIT:-}" ]; then + $GIT add "$file" + $GIT commit -q -m "File added" -- "$file" + fi + fi + rm "$tmpmd" +}