fpf-cli 1.6.34 → 1.6.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/fpf +292 -42
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -98,6 +98,8 @@ Installed packages are marked with `*` in the result list.
98
98
  - `FPF_DYNAMIC_RELOAD`: `always` (default), `single`, or `never`
99
99
  - Live reload uses `change:reload` by default for reliability (`ctrl-r` uses the same reload command).
100
100
  - Set `FPF_DYNAMIC_RELOAD_TRANSPORT=ipc` to opt into `--listen` + IPC query notifications on supported `fzf` builds.
101
+ - Startup now shows a dynamic pre-search loader with per-manager progress + elapsed time.
102
+ - Set `FPF_LOADING_INDICATOR=0` to disable the pre-search loading indicator.
101
103
  - `FPF_RELOAD_MIN_CHARS`: minimum query length before live reload (default `2`)
102
104
  - `FPF_RELOAD_DEBOUNCE`: reload debounce seconds (default `0.12`)
103
105
  - `FPF_DISABLE_INSTALLED_CACHE=1` disables installed-package marker cache
package/fpf CHANGED
@@ -3,13 +3,15 @@
3
3
  set -euo pipefail
4
4
 
5
5
  SCRIPT_NAME="fpf"
6
- SCRIPT_VERSION="1.6.34"
6
+ SCRIPT_VERSION="1.6.36"
7
7
  TMP_ROOT="${TMPDIR:-/tmp}/fpf"
8
8
  SESSION_TMP_ROOT=""
9
9
  HELP_FILE=""
10
10
  KBINDS_FILE=""
11
11
  CACHE_FORMAT_VERSION="1"
12
12
  CACHE_ROOT=""
13
+ LOADING_INDICATOR_PID=""
14
+ LOADING_PROGRESS_DIR=""
13
15
 
14
16
  ACTION="search"
15
17
  MANAGER_OVERRIDE=""
@@ -27,6 +29,208 @@ log() {
27
29
  printf "%s\n" "$*" >&2
28
30
  }
29
31
 
32
+ loading_indicator_enabled() {
33
+ local indicator_setting=""
34
+
35
+ indicator_setting="$(trim_whitespace "${FPF_LOADING_INDICATOR:-1}")"
36
+ indicator_setting="$(printf "%s" "${indicator_setting}" | tr '[:upper:]' '[:lower:]')"
37
+
38
+ case "${indicator_setting}" in
39
+ 0|false|no|off)
40
+ return 1
41
+ ;;
42
+ esac
43
+
44
+ [[ -t 2 ]]
45
+ }
46
+
47
+ loading_progress_begin() {
48
+ local managers=("$@")
49
+ local manager=""
50
+ local progress_file=""
51
+
52
+ loading_progress_end
53
+ loading_indicator_enabled || return 0
54
+ [[ "${#managers[@]}" -gt 0 ]] || return 0
55
+
56
+ LOADING_PROGRESS_DIR="$(mktemp -d "${SESSION_TMP_ROOT}/loading-progress.XXXXXX")"
57
+ for manager in "${managers[@]-}"; do
58
+ progress_file="${LOADING_PROGRESS_DIR}/${manager}.status"
59
+ printf "pending\tqueued\n" >"${progress_file}"
60
+ done
61
+ }
62
+
63
+ loading_progress_end() {
64
+ if [[ -z "${LOADING_PROGRESS_DIR}" ]]; then
65
+ return 0
66
+ fi
67
+
68
+ if [[ -d "${LOADING_PROGRESS_DIR}" ]]; then
69
+ rm -rf "${LOADING_PROGRESS_DIR}" >/dev/null 2>&1 || true
70
+ fi
71
+
72
+ LOADING_PROGRESS_DIR=""
73
+ }
74
+
75
+ loading_progress_mark_state() {
76
+ local manager="$1"
77
+ local state="$2"
78
+ local detail="${3:-}"
79
+ local progress_file=""
80
+
81
+ if [[ -z "${LOADING_PROGRESS_DIR}" || ! -d "${LOADING_PROGRESS_DIR}" ]]; then
82
+ return 0
83
+ fi
84
+
85
+ progress_file="${LOADING_PROGRESS_DIR}/${manager}.status"
86
+ printf "%s\t%s\n" "${state}" "${detail}" >"${progress_file}" 2>/dev/null || true
87
+ }
88
+
89
+ loading_progress_mark_running() {
90
+ local manager="$1"
91
+ local detail="${2:-working}"
92
+ loading_progress_mark_state "${manager}" "running" "${detail}"
93
+ }
94
+
95
+ loading_progress_mark_done() {
96
+ local manager="$1"
97
+ local detail="${2:-done}"
98
+ loading_progress_mark_state "${manager}" "done" "${detail}"
99
+ }
100
+
101
+ loading_progress_snapshot() {
102
+ local status_file=""
103
+ local manager=""
104
+ local status=""
105
+ local detail=""
106
+ local total=0
107
+ local done=0
108
+ local running=0
109
+ local queued=0
110
+ local -a active_details=()
111
+ local active_display=""
112
+ local idx
113
+
114
+ if [[ -z "${LOADING_PROGRESS_DIR}" || ! -d "${LOADING_PROGRESS_DIR}" ]]; then
115
+ return 0
116
+ fi
117
+
118
+ for status_file in "${LOADING_PROGRESS_DIR}"/*.status; do
119
+ [[ -f "${status_file}" ]] || continue
120
+ total=$((total + 1))
121
+ manager="$(basename "${status_file}" .status)"
122
+ IFS=$'\t' read -r status detail <"${status_file}" || true
123
+ case "${status}" in
124
+ done)
125
+ done=$((done + 1))
126
+ ;;
127
+ running)
128
+ running=$((running + 1))
129
+ if [[ "${#active_details[@]}" -lt 2 ]]; then
130
+ active_details+=("$(manager_label "${manager}"): ${detail:-working}")
131
+ fi
132
+ ;;
133
+ *)
134
+ queued=$((queued + 1))
135
+ ;;
136
+ esac
137
+ done
138
+
139
+ [[ "${total}" -gt 0 ]] || return 0
140
+
141
+ active_display=""
142
+ for idx in "${!active_details[@]}"; do
143
+ if [[ -n "${active_display}" ]]; then
144
+ active_display+=", "
145
+ fi
146
+ active_display+="${active_details[$idx]}"
147
+ done
148
+ if [[ "${running}" -gt "${#active_details[@]}" ]]; then
149
+ if [[ -n "${active_display}" ]]; then
150
+ active_display+=", "
151
+ fi
152
+ active_display+="+$((running - ${#active_details[@]})) more"
153
+ fi
154
+
155
+ printf "%s/%s done" "${done}" "${total}"
156
+ if [[ -n "${active_display}" ]]; then
157
+ printf " | active: %s" "${active_display}"
158
+ fi
159
+ if [[ "${queued}" -gt 0 ]]; then
160
+ printf " | queued: %s" "${queued}"
161
+ fi
162
+ }
163
+
164
+ start_loading_indicator() {
165
+ local message="${1:-Loading}"
166
+
167
+ if [[ -n "${LOADING_INDICATOR_PID}" ]]; then
168
+ stop_loading_indicator
169
+ fi
170
+
171
+ loading_indicator_enabled || return 0
172
+
173
+ (
174
+ trap 'exit 0' TERM INT
175
+ local frames='|/-\'
176
+ local frame_index=0
177
+ local started_epoch
178
+ local elapsed_seconds=0
179
+ local snapshot=""
180
+
181
+ started_epoch="$(date +%s)"
182
+ if ! [[ "${started_epoch}" =~ ^[0-9]+$ ]]; then
183
+ started_epoch=0
184
+ fi
185
+
186
+ while true; do
187
+ elapsed_seconds="$(( $(date +%s) - started_epoch ))"
188
+ if [[ "${elapsed_seconds}" -lt 0 ]]; then
189
+ elapsed_seconds=0
190
+ fi
191
+
192
+ snapshot="$(loading_progress_snapshot)"
193
+ if [[ -n "${snapshot}" ]]; then
194
+ printf "\r\033[2K%s [%s] %s | elapsed: %ss" "${message}" "${frames:frame_index:1}" "${snapshot}" "${elapsed_seconds}" >&2
195
+ else
196
+ printf "\r\033[2K%s [%s] elapsed: %ss" "${message}" "${frames:frame_index:1}" "${elapsed_seconds}" >&2
197
+ fi
198
+ frame_index=$(((frame_index + 1) % 4))
199
+ sleep 0.1
200
+ done
201
+ ) &
202
+ LOADING_INDICATOR_PID="$!"
203
+ }
204
+
205
+ stop_loading_indicator() {
206
+ if [[ -z "${LOADING_INDICATOR_PID}" ]]; then
207
+ return 0
208
+ fi
209
+
210
+ kill "${LOADING_INDICATOR_PID}" >/dev/null 2>&1 || true
211
+ wait "${LOADING_INDICATOR_PID}" >/dev/null 2>&1 || true
212
+ LOADING_INDICATOR_PID=""
213
+
214
+ if loading_indicator_enabled; then
215
+ printf "\r\033[2K" >&2
216
+ fi
217
+ }
218
+
219
+ run_with_loading_indicator() {
220
+ local message="$1"
221
+ shift
222
+
223
+ start_loading_indicator "${message}"
224
+ if "$@"; then
225
+ stop_loading_indicator
226
+ return 0
227
+ fi
228
+
229
+ local status="$?"
230
+ stop_loading_indicator
231
+ return "${status}"
232
+ }
233
+
30
234
  die() {
31
235
  log "Error: $*"
32
236
  exit 1
@@ -861,6 +1065,9 @@ run_preview_item_action() {
861
1065
  }
862
1066
 
863
1067
  cleanup_session_tmp_root() {
1068
+ stop_loading_indicator
1069
+ loading_progress_end
1070
+
864
1071
  if [[ -n "${SESSION_TMP_ROOT}" && -d "${SESSION_TMP_ROOT}" ]]; then
865
1072
  rm -rf "${SESSION_TMP_ROOT}"
866
1073
  fi
@@ -2711,6 +2918,10 @@ collect_search_display_rows() {
2711
2918
  part_files+=("${part_file}")
2712
2919
 
2713
2920
  (
2921
+ local local_source_file
2922
+ local result_count=0
2923
+
2924
+ loading_progress_mark_running "${manager}" "searching package index"
2714
2925
  local_source_file="$(mktemp "${SESSION_TMP_ROOT}/source.XXXXXX")"
2715
2926
  manager_search_entries "${manager}" "${query}" >"${local_source_file}" || true
2716
2927
  if [[ -s "${local_source_file}" ]]; then
@@ -2723,6 +2934,11 @@ collect_search_display_rows() {
2723
2934
  ' "${local_source_file}" >"${part_file}"
2724
2935
  fi
2725
2936
  rm -f "${local_source_file}"
2937
+
2938
+ if [[ -s "${part_file}" ]]; then
2939
+ result_count="$(awk 'END { print NR + 0 }' "${part_file}")"
2940
+ fi
2941
+ loading_progress_mark_done "${manager}" "${result_count} results indexed"
2726
2942
  ) &
2727
2943
  gather_pids+=("$!")
2728
2944
  done
@@ -2755,6 +2971,64 @@ collect_search_display_rows() {
2755
2971
  rm -f "${merged_source_file}" "${merged_marked_file}"
2756
2972
  }
2757
2973
 
2974
+ collect_installed_display_rows() {
2975
+ local output_file="$1"
2976
+ shift
2977
+ local managers=("$@")
2978
+ local part_files=()
2979
+ local gather_pids=()
2980
+ local manager
2981
+ local part_file
2982
+ local gather_pid
2983
+
2984
+ : >"${output_file}"
2985
+
2986
+ for manager in "${managers[@]-}"; do
2987
+ part_file="$(mktemp "${SESSION_TMP_ROOT}/part.XXXXXX")"
2988
+ part_files+=("${part_file}")
2989
+
2990
+ (
2991
+ local local_source_file
2992
+ local result_count=0
2993
+
2994
+ loading_progress_mark_running "${manager}" "reading installed packages"
2995
+ local_source_file="$(mktemp "${SESSION_TMP_ROOT}/source.XXXXXX")"
2996
+ manager_installed_entries "${manager}" >"${local_source_file}" || true
2997
+ if [[ -s "${local_source_file}" ]]; then
2998
+ awk -F'\t' -v mgr="${manager}" '
2999
+ NF >= 1 {
3000
+ desc = $2
3001
+ if (desc == "") desc = "-"
3002
+ print mgr "\t" $1 "\t" desc
3003
+ }
3004
+ ' "${local_source_file}" >"${part_file}"
3005
+ fi
3006
+ rm -f "${local_source_file}"
3007
+
3008
+ if [[ -s "${part_file}" ]]; then
3009
+ result_count="$(awk 'END { print NR + 0 }' "${part_file}")"
3010
+ fi
3011
+ loading_progress_mark_done "${manager}" "${result_count} installed entries"
3012
+ ) &
3013
+ gather_pids+=("$!")
3014
+ done
3015
+
3016
+ for gather_pid in "${gather_pids[@]-}"; do
3017
+ wait "${gather_pid}" || true
3018
+ done
3019
+
3020
+ for part_file in "${part_files[@]-}"; do
3021
+ if [[ -s "${part_file}" ]]; then
3022
+ cat "${part_file}" >>"${output_file}"
3023
+ fi
3024
+ rm -f "${part_file}"
3025
+ done
3026
+
3027
+ if [[ -s "${output_file}" ]]; then
3028
+ sort -u "${output_file}" -o "${output_file}"
3029
+ fi
3030
+ }
3031
+
2758
3032
  build_dynamic_reload_command() {
2759
3033
  local manager_override="$1"
2760
3034
  local fallback_file="$2"
@@ -3286,48 +3560,19 @@ main() {
3286
3560
  ensure_fzf "${managers[@]-}"
3287
3561
 
3288
3562
  if [[ "${ACTION}" == "search" ]]; then
3289
- collect_search_display_rows "${query}" "${display_file}" "${managers[@]-}"
3563
+ loading_progress_begin "${managers[@]-}"
3564
+ if ! run_with_loading_indicator "Loading packages from ${manager_display}" collect_search_display_rows "${query}" "${display_file}" "${managers[@]-}"; then
3565
+ loading_progress_end
3566
+ die "Failed to load package search results."
3567
+ fi
3568
+ loading_progress_end
3290
3569
  else
3291
- local part_files=()
3292
- local gather_pids=()
3293
- local part_file
3294
- local gather_pid
3295
-
3296
- for manager in "${managers[@]-}"; do
3297
- part_file="$(mktemp "${SESSION_TMP_ROOT}/part.XXXXXX")"
3298
- part_files+=("${part_file}")
3299
-
3300
- (
3301
- local_source_file="$(mktemp "${SESSION_TMP_ROOT}/source.XXXXXX")"
3302
- manager_installed_entries "${manager}" >"${local_source_file}" || true
3303
- if [[ -s "${local_source_file}" ]]; then
3304
- awk -F'\t' -v mgr="${manager}" '
3305
- NF >= 1 {
3306
- desc = $2
3307
- if (desc == "") desc = "-"
3308
- print mgr "\t" $1 "\t" desc
3309
- }
3310
- ' "${local_source_file}" >"${part_file}"
3311
- fi
3312
- rm -f "${local_source_file}"
3313
- ) &
3314
- gather_pids+=("$!")
3315
- done
3316
-
3317
- for gather_pid in "${gather_pids[@]-}"; do
3318
- wait "${gather_pid}" || true
3319
- done
3320
-
3321
- for part_file in "${part_files[@]-}"; do
3322
- if [[ -s "${part_file}" ]]; then
3323
- cat "${part_file}" >>"${display_file}"
3324
- fi
3325
- rm -f "${part_file}"
3326
- done
3327
-
3328
- if [[ -s "${display_file}" ]]; then
3329
- sort -u "${display_file}" -o "${display_file}"
3570
+ loading_progress_begin "${managers[@]-}"
3571
+ if ! run_with_loading_indicator "Loading installed packages from ${manager_display}" collect_installed_display_rows "${display_file}" "${managers[@]-}"; then
3572
+ loading_progress_end
3573
+ die "Failed to load installed package results."
3330
3574
  fi
3575
+ loading_progress_end
3331
3576
  fi
3332
3577
 
3333
3578
  if [[ ! -s "${display_file}" ]]; then
@@ -3370,7 +3615,12 @@ main() {
3370
3615
  if dynamic_reload_enabled "${#managers[@]}"; then
3371
3616
  if [[ -n "${query}" ]]; then
3372
3617
  reload_fallback_file="$(mktemp "${SESSION_TMP_ROOT}/reload-fallback.XXXXXX")"
3373
- collect_search_display_rows "" "${reload_fallback_file}" "${managers[@]-}"
3618
+ loading_progress_begin "${managers[@]-}"
3619
+ if ! run_with_loading_indicator "Preparing baseline search results" collect_search_display_rows "" "${reload_fallback_file}" "${managers[@]-}"; then
3620
+ loading_progress_end
3621
+ die "Failed to prepare baseline search results."
3622
+ fi
3623
+ loading_progress_end
3374
3624
  if [[ ! -s "${reload_fallback_file}" ]]; then
3375
3625
  cp "${display_file}" "${reload_fallback_file}"
3376
3626
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fpf-cli",
3
- "version": "1.6.34",
3
+ "version": "1.6.36",
4
4
  "description": "Cross-platform fuzzy package finder powered by fzf",
5
5
  "bin": {
6
6
  "fpf": "fpf"