fpf-cli 1.6.57 → 1.6.59

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.
package/fpf CHANGED
@@ -2,11 +2,26 @@
2
2
 
3
3
  set -euo pipefail
4
4
 
5
- try_exec_packaged_go_binary() {
6
- if [[ "${FPF_SKIP_GO_BOOTSTRAP:-0}" == "1" ]]; then
7
- return
8
- fi
5
+ resolve_script_path() {
6
+ local source_path="$1"
7
+ local source_dir=""
8
+ local link_target=""
9
+
10
+ while [[ -h "${source_path}" ]]; do
11
+ source_dir="$(cd -P "$(dirname "${source_path}")" && pwd)"
12
+ link_target="$(readlink "${source_path}")"
13
+ if [[ "${link_target}" == /* ]]; then
14
+ source_path="${link_target}"
15
+ else
16
+ source_path="${source_dir}/${link_target}"
17
+ fi
18
+ done
9
19
 
20
+ source_dir="$(cd -P "$(dirname "${source_path}")" && pwd)"
21
+ printf "%s/%s" "${source_dir}" "$(basename "${source_path}")"
22
+ }
23
+
24
+ main() {
10
25
  local script_path=""
11
26
  local script_dir=""
12
27
  local uname_s=""
@@ -17,8 +32,37 @@ try_exec_packaged_go_binary() {
17
32
 
18
33
  script_path="$(resolve_script_path "${BASH_SOURCE[0]}")"
19
34
  script_dir="$(cd -P "$(dirname "${script_path}")" && pwd)"
20
- uname_s="$(uname -s 2>/dev/null || true)"
21
- uname_m="$(uname -m 2>/dev/null || true)"
35
+ if [[ "${FPF_SKIP_GO_BOOTSTRAP:-0}" == "1" ]]; then
36
+ case "${OSTYPE:-}" in
37
+ linux*)
38
+ uname_s="Linux"
39
+ ;;
40
+ darwin*)
41
+ uname_s="Darwin"
42
+ ;;
43
+ msys*|cygwin*|win32*)
44
+ uname_s="MINGW"
45
+ ;;
46
+ *)
47
+ uname_s="$(uname -s 2>/dev/null || true)"
48
+ ;;
49
+ esac
50
+
51
+ case "${HOSTTYPE:-${MACHTYPE:-}}" in
52
+ *x86_64*|*amd64*)
53
+ uname_m="x86_64"
54
+ ;;
55
+ *aarch64*|*arm64*)
56
+ uname_m="arm64"
57
+ ;;
58
+ *)
59
+ uname_m="$(uname -m 2>/dev/null || true)"
60
+ ;;
61
+ esac
62
+ else
63
+ uname_s="$(uname -s 2>/dev/null || true)"
64
+ uname_m="$(uname -m 2>/dev/null || true)"
65
+ fi
22
66
 
23
67
  case "${uname_s}" in
24
68
  Linux)
@@ -31,7 +75,8 @@ try_exec_packaged_go_binary() {
31
75
  goos="windows"
32
76
  ;;
33
77
  *)
34
- return
78
+ printf "fpf: unsupported OS '%s'\n" "${uname_s}" >&2
79
+ exit 1
35
80
  ;;
36
81
  esac
37
82
 
@@ -43,4239 +88,42 @@ try_exec_packaged_go_binary() {
43
88
  goarch="arm64"
44
89
  ;;
45
90
  *)
46
- return
47
- ;;
48
- esac
49
-
50
- candidate="${script_dir}/bin/fpf-go-${goos}-${goarch}"
51
- if [[ "${goos}" == "windows" ]]; then
52
- candidate="${candidate}.exe"
53
- fi
54
-
55
- if [[ -x "${candidate}" ]]; then
56
- export FPF_SKIP_GO_BOOTSTRAP=1
57
- export FPF_LEGACY_SCRIPT="${script_dir}/fpf"
58
- export FPF_PACKAGE_JSON="${script_dir}/package.json"
59
- exec "${candidate}" "$@"
60
- fi
61
- }
62
-
63
- resolve_script_path() {
64
- local source_path="$1"
65
- local source_dir=""
66
- local link_target=""
67
-
68
- while [[ -h "${source_path}" ]]; do
69
- source_dir="$(cd -P "$(dirname "${source_path}")" && pwd)"
70
- link_target="$(readlink "${source_path}")"
71
- if [[ "${link_target}" == /* ]]; then
72
- source_path="${link_target}"
73
- else
74
- source_path="${source_dir}/${link_target}"
75
- fi
76
- done
77
-
78
- source_dir="$(cd -P "$(dirname "${source_path}")" && pwd)"
79
- printf "%s/%s" "${source_dir}" "$(basename "${source_path}")"
80
- }
81
-
82
- try_exec_packaged_go_binary "$@"
83
-
84
- SCRIPT_NAME="fpf"
85
- SCRIPT_VERSION="1.6.57"
86
- TMP_ROOT="${TMPDIR:-/tmp}/fpf"
87
- SESSION_TMP_ROOT=""
88
- HELP_FILE=""
89
- KBINDS_FILE=""
90
- CACHE_FORMAT_VERSION="1"
91
- CACHE_ROOT=""
92
- LOADING_INDICATOR_PID=""
93
- LOADING_PROGRESS_DIR=""
94
-
95
- ACTION="search"
96
- MANAGER_OVERRIDE=""
97
- IPC_MANAGER_OVERRIDE=""
98
- IPC_FALLBACK_FILE=""
99
- ASSUME_YES="0"
100
- declare -a QUERY_PARTS=()
101
- FPF_LIBRARIES_LOADED="0"
102
-
103
- query_cache_flags() {
104
- printf "%s" "query_limit=${FPF_QUERY_RESULT_LIMIT:-0};per_manager_limit=${FPF_QUERY_PER_MANAGER_LIMIT:-40};no_query_limit=${FPF_NO_QUERY_RESULT_LIMIT:-120};no_query_npm_limit=${FPF_NO_QUERY_NPM_LIMIT:-120}"
105
- }
106
-
107
- query_cache_default_enabled_for_manager() {
108
- local manager="$1"
109
-
110
- case "${manager}" in
111
- apt|brew|pacman|bun)
112
- return 0
113
- ;;
114
- *)
115
- return 1
116
- ;;
117
- esac
118
- }
119
-
120
- query_cache_ttl_seconds_for_manager() {
121
- local manager="$1"
122
- local default_ttl="0"
123
- local global_ttl="${FPF_QUERY_CACHE_TTL:-}"
124
- local manager_ttl="0"
125
-
126
- case "${manager}" in
127
- apt)
128
- default_ttl="180"
129
- ;;
130
- brew)
131
- default_ttl="120"
132
- ;;
133
- pacman)
134
- default_ttl="180"
135
- ;;
136
- bun)
137
- default_ttl="300"
91
+ goarch=""
138
92
  ;;
139
93
  esac
140
94
 
141
- if [[ -n "${global_ttl}" ]]; then
142
- if [[ "${global_ttl}" =~ ^[0-9]+$ ]]; then
143
- default_ttl="${global_ttl}"
95
+ if [[ -n "${goarch}" ]]; then
96
+ candidate="${script_dir}/bin/fpf-go-${goos}-${goarch}"
97
+ if [[ "${goos}" == "windows" ]]; then
98
+ candidate="${candidate}.exe"
144
99
  fi
145
- fi
146
-
147
- case "${manager}" in
148
- bun)
149
- manager_ttl="${FPF_BUN_QUERY_CACHE_TTL:-${default_ttl}}"
150
- if ! [[ "${manager_ttl}" =~ ^[0-9]+$ ]]; then
151
- manager_ttl="${default_ttl}"
152
- fi
153
- ;;
154
- apt)
155
- manager_ttl="${FPF_APT_QUERY_CACHE_TTL:-${default_ttl}}"
156
- if ! [[ "${manager_ttl}" =~ ^[0-9]+$ ]]; then
157
- manager_ttl="${default_ttl}"
158
- fi
159
- ;;
160
- brew)
161
- manager_ttl="${FPF_BREW_QUERY_CACHE_TTL:-${default_ttl}}"
162
- if ! [[ "${manager_ttl}" =~ ^[0-9]+$ ]]; then
163
- manager_ttl="${default_ttl}"
164
- fi
165
- ;;
166
- pacman)
167
- manager_ttl="${FPF_PACMAN_QUERY_CACHE_TTL:-${default_ttl}}"
168
- if ! [[ "${manager_ttl}" =~ ^[0-9]+$ ]]; then
169
- manager_ttl="${default_ttl}"
170
- fi
171
- ;;
172
- *)
173
- manager_ttl=0
174
- ;;
175
- esac
176
-
177
- printf "%s" "${manager_ttl}"
178
- }
179
-
180
- dynamic_reload_query_cache_bypass_value() {
181
- local bypass_setting="${FPF_DYNAMIC_RELOAD_BYPASS_QUERY_CACHE:-1}"
182
-
183
- bypass_setting="$(trim_whitespace "${bypass_setting}")"
184
- bypass_setting="${bypass_setting,,}"
185
-
186
- case "${bypass_setting}" in
187
- 1|true|yes|on)
188
- printf "1"
189
- ;;
190
- *)
191
- printf "0"
192
- ;;
193
- esac
194
- }
195
-
196
- log() {
197
- printf "%s\n" "$*" >&2
198
- }
199
-
200
- loading_indicator_enabled() {
201
- local indicator_setting=""
202
-
203
- indicator_setting="$(trim_whitespace "${FPF_LOADING_INDICATOR:-1}")"
204
- indicator_setting="${indicator_setting,,}"
205
-
206
- case "${indicator_setting}" in
207
- 0|false|no|off)
208
- return 1
209
- ;;
210
- esac
211
-
212
- [[ -t 2 ]]
213
- }
214
-
215
- loading_progress_begin() {
216
- local managers=("$@")
217
- local manager=""
218
- local progress_file=""
219
-
220
- loading_progress_end
221
- loading_indicator_enabled || return 0
222
- [[ "${#managers[@]}" -gt 0 ]] || return 0
223
-
224
- LOADING_PROGRESS_DIR="$(mktemp -d "${SESSION_TMP_ROOT}/loading-progress.XXXXXX")"
225
- for manager in "${managers[@]-}"; do
226
- progress_file="${LOADING_PROGRESS_DIR}/${manager}.status"
227
- printf "pending\tqueued\n" >"${progress_file}"
228
- done
229
- }
230
-
231
- loading_progress_end() {
232
- if [[ -z "${LOADING_PROGRESS_DIR}" ]]; then
233
- return 0
234
- fi
235
-
236
- if [[ -d "${LOADING_PROGRESS_DIR}" ]]; then
237
- rm -rf "${LOADING_PROGRESS_DIR}" >/dev/null 2>&1 || true
238
- fi
239
-
240
- LOADING_PROGRESS_DIR=""
241
- }
242
-
243
- loading_progress_mark_state() {
244
- local manager="$1"
245
- local state="$2"
246
- local detail="${3:-}"
247
- local progress_file=""
248
-
249
- if [[ -z "${LOADING_PROGRESS_DIR}" || ! -d "${LOADING_PROGRESS_DIR}" ]]; then
250
- return 0
251
- fi
252
-
253
- progress_file="${LOADING_PROGRESS_DIR}/${manager}.status"
254
- printf "%s\t%s\n" "${state}" "${detail}" >"${progress_file}" 2>/dev/null || true
255
- }
256
-
257
- loading_progress_mark_running() {
258
- local manager="$1"
259
- local detail="${2:-working}"
260
- loading_progress_mark_state "${manager}" "running" "${detail}"
261
- }
262
-
263
- loading_progress_mark_done() {
264
- local manager="$1"
265
- local detail="${2:-done}"
266
- loading_progress_mark_state "${manager}" "done" "${detail}"
267
- }
268
-
269
- loading_indicator_terminal_cols() {
270
- local cols="${COLUMNS:-}"
271
-
272
- if ! [[ "${cols}" =~ ^[0-9]+$ ]] || [[ "${cols}" -lt 40 ]]; then
273
- if command_exists tput; then
274
- cols="$(tput cols 2>/dev/null || true)"
100
+ if [[ -x "${candidate}" ]]; then
101
+ export FPF_PACKAGE_JSON="${script_dir}/package.json"
102
+ exec "${candidate}" "$@"
275
103
  fi
276
104
  fi
277
105
 
278
- if ! [[ "${cols}" =~ ^[0-9]+$ ]] || [[ "${cols}" -lt 40 ]]; then
279
- cols=120
280
- fi
281
-
282
- printf "%s" "${cols}"
283
- }
284
-
285
- loading_indicator_fit_line() {
286
- local line="$1"
287
- local cols
288
- local max_len
289
-
290
- cols="$(loading_indicator_terminal_cols)"
291
- max_len=$((cols - 1))
292
- if [[ "${max_len}" -lt 20 ]]; then
293
- max_len=20
294
- fi
295
-
296
- if [[ "${#line}" -gt "${max_len}" ]]; then
297
- printf "%s..." "${line:0:$((max_len - 3))}"
298
- return
299
- fi
300
-
301
- printf "%s" "${line}"
302
- }
303
-
304
- loading_progress_snapshot() {
305
- local status_file=""
306
- local manager=""
307
- local status=""
308
- local detail=""
309
- local total=0
310
- local done=0
311
- local running=0
312
- local queued=0
313
- local progress_percent=0
314
- local -a active_details=()
315
- local active_text=""
316
- local phase_text=""
317
- local active_total=0
318
- local max_active_items=3
319
- local idx
320
-
321
- if [[ -z "${LOADING_PROGRESS_DIR}" || ! -d "${LOADING_PROGRESS_DIR}" ]]; then
322
- return 0
323
- fi
324
-
325
- for status_file in "${LOADING_PROGRESS_DIR}"/*.status; do
326
- [[ -f "${status_file}" ]] || continue
327
- total=$((total + 1))
328
- manager="$(basename "${status_file}" .status)"
329
- IFS=$'\t' read -r status detail <"${status_file}" || true
330
- case "${status}" in
331
- done)
332
- done=$((done + 1))
333
- ;;
334
- running)
335
- running=$((running + 1))
336
- active_total=$((active_total + 1))
337
- if [[ "${#active_details[@]}" -lt "${max_active_items}" ]]; then
338
- active_details+=("${manager}: ${detail:-work}")
339
- fi
340
- ;;
341
- *)
342
- queued=$((queued + 1))
343
- ;;
344
- esac
345
- done
346
-
347
- [[ "${total}" -gt 0 ]] || return 0
348
-
349
- progress_percent=$((done * 100 / total))
350
-
351
- if [[ "${#active_details[@]}" -gt 0 ]]; then
352
- active_text=""
353
- for idx in "${!active_details[@]}"; do
354
- if [[ -n "${active_text}" ]]; then
355
- active_text+=", "
356
- fi
357
- active_text+="${active_details[$idx]}"
358
- done
359
- if [[ "${active_total}" -gt "${#active_details[@]}" ]]; then
360
- active_text+=", +$((active_total - ${#active_details[@]}))"
106
+ for goarch in amd64 arm64; do
107
+ candidate="${script_dir}/bin/fpf-go-${goos}-${goarch}"
108
+ if [[ "${goos}" == "windows" ]]; then
109
+ candidate="${candidate}.exe"
361
110
  fi
362
- phase_text="active: ${active_text}"
363
- elif [[ "${queued}" -gt 0 ]]; then
364
- phase_text="starting queued managers (${queued} remaining)"
365
- else
366
- phase_text="finalizing combined results"
367
- fi
368
-
369
- printf "%3s%% (%s/%s) | %s" "${progress_percent}" "${done}" "${total}" "${phase_text}"
370
- }
371
-
372
- start_loading_indicator() {
373
- local message="${1:-Loading}"
374
-
375
- if [[ -n "${LOADING_INDICATOR_PID}" ]]; then
376
- stop_loading_indicator
377
- fi
378
-
379
- loading_indicator_enabled || return 0
380
-
381
- (
382
- trap 'exit 0' TERM INT
383
- local started_epoch
384
- local elapsed_seconds=0
385
- local snapshot=""
386
- local line_output=""
387
-
388
- started_epoch="$(date +%s)"
389
- if ! [[ "${started_epoch}" =~ ^[0-9]+$ ]]; then
390
- started_epoch=0
111
+ if [[ -x "${candidate}" ]]; then
112
+ export FPF_PACKAGE_JSON="${script_dir}/package.json"
113
+ exec "${candidate}" "$@"
391
114
  fi
115
+ done
392
116
 
393
- while true; do
394
- elapsed_seconds="$(( $(date +%s) - started_epoch ))"
395
- if [[ "${elapsed_seconds}" -lt 0 ]]; then
396
- elapsed_seconds=0
397
- fi
398
-
399
- snapshot="$(loading_progress_snapshot)"
400
- if [[ -n "${snapshot}" ]]; then
401
- line_output="${message} ${snapshot} | ${elapsed_seconds}s"
402
- else
403
- line_output="${message} working | ${elapsed_seconds}s"
404
- fi
405
- line_output="$(loading_indicator_fit_line "${line_output}")"
406
- printf "\r\033[2K%s" "${line_output}" >&2
407
- sleep 0.15
408
- done
409
- ) &
410
- LOADING_INDICATOR_PID="$!"
411
- }
412
-
413
- stop_loading_indicator() {
414
- if [[ -z "${LOADING_INDICATOR_PID}" ]]; then
415
- return 0
416
- fi
417
-
418
- kill "${LOADING_INDICATOR_PID}" >/dev/null 2>&1 || true
419
- wait "${LOADING_INDICATOR_PID}" >/dev/null 2>&1 || true
420
- LOADING_INDICATOR_PID=""
421
-
422
- if loading_indicator_enabled; then
423
- printf "\r\033[2K" >&2
424
- fi
425
- }
426
-
427
- run_with_loading_indicator() {
428
- local message="$1"
429
- shift
430
-
431
- start_loading_indicator "${message}"
432
- if "$@"; then
433
- stop_loading_indicator
434
- return 0
117
+ printf "fpf: missing packaged Go binary for %s/%s\n" "${goos}" "${uname_m}" >&2
118
+ printf "fpf: expected one of:\n" >&2
119
+ printf " %s/bin/fpf-go-%s-amd64\n" "${script_dir}" "${goos}" >&2
120
+ printf " %s/bin/fpf-go-%s-arm64\n" "${script_dir}" "${goos}" >&2
121
+ if [[ "${goos}" == "windows" ]]; then
122
+ printf " (with .exe suffix on Windows binaries)\n" >&2
435
123
  fi
436
-
437
- local status="$?"
438
- stop_loading_indicator
439
- return "${status}"
440
- }
441
-
442
- die() {
443
- log "Error: $*"
124
+ printf "fpf: run 'bash scripts/build-go-binaries.sh' to build local binaries.\n" >&2
444
125
  exit 1
445
- }
446
-
447
- command_exists() {
448
- command -v "$1" >/dev/null 2>&1
449
- }
450
-
451
- invocation_script_path() {
452
- local script_path="${FPF_SELF_PATH:-${BASH_SOURCE[0]}}"
453
-
454
- if [[ "${script_path}" != /* ]]; then
455
- script_path="$(pwd)/${script_path}"
456
- fi
457
-
458
- printf "%s" "${script_path}"
459
- }
460
-
461
- resolve_script_dir() {
462
- local script_path="${BASH_SOURCE[0]}"
463
- local script_dir=""
464
-
465
- if command_exists readlink; then
466
- while [[ -L "${script_path}" ]]; do
467
- script_dir="$(cd -P "$(dirname "${script_path}")" && pwd)"
468
- script_path="$(readlink "${script_path}")"
469
- if [[ "${script_path}" != /* ]]; then
470
- script_path="${script_dir}/${script_path}"
471
- fi
472
- done
473
- fi
474
-
475
- if [[ "${script_path}" != /* ]]; then
476
- script_path="$(pwd)/${script_path}"
477
- fi
478
126
 
479
- script_dir="$(cd -P "$(dirname "${script_path}")" && pwd)"
480
- printf "%s" "${script_dir}"
481
- }
482
-
483
- load_fpf_libraries() {
484
- local script_dir=""
485
- local manager_actions_lib=""
486
-
487
- if [[ "${FPF_LIBRARIES_LOADED}" == "1" ]]; then
488
- return
489
- fi
490
-
491
- script_dir="$(resolve_script_dir)"
492
- manager_actions_lib="${script_dir}/lib/fpf/manager-actions.sh"
493
-
494
- if [[ ! -r "${manager_actions_lib}" ]]; then
495
- die "Required library file not found: ${manager_actions_lib}"
496
- fi
497
-
498
- # shellcheck disable=SC1090
499
- source "${manager_actions_lib}"
500
- FPF_LIBRARIES_LOADED="1"
501
- }
502
-
503
- fzf_command_available() {
504
- if [[ "${FPF_TEST_FORCE_FZF_MISSING:-0}" == "1" ]]; then
505
- if [[ -n "${FPF_TEST_MOCK_BIN:-}" && -x "${FPF_TEST_MOCK_BIN}/fzf" ]]; then
506
- return 0
507
- fi
508
- return 1
509
- fi
510
-
511
- command_exists fzf
512
- }
513
-
514
- assume_yes_enabled() {
515
- local env_assume_yes=""
516
-
517
- if [[ "${ASSUME_YES}" == "1" ]]; then
518
- return 0
519
- fi
520
-
521
- env_assume_yes="$(trim_whitespace "${FPF_ASSUME_YES:-0}")"
522
- env_assume_yes="${env_assume_yes,,}"
523
-
524
- case "${env_assume_yes}" in
525
- 1|true|yes|on)
526
- return 0
527
- ;;
528
- *)
529
- return 1
530
- ;;
531
- esac
532
- }
533
-
534
- ensure_tmp_root() {
535
- mkdir -p "${TMP_ROOT}"
536
- }
537
-
538
- resolve_cache_root() {
539
- local os
540
- os="$(uname -s)"
541
-
542
- if [[ -n "${FPF_CACHE_DIR:-}" ]]; then
543
- printf "%s" "${FPF_CACHE_DIR}"
544
- return
545
- fi
546
-
547
- case "${os}" in
548
- Darwin)
549
- printf "%s" "${HOME}/Library/Caches/fpf"
550
- ;;
551
- Linux)
552
- if [[ -n "${XDG_CACHE_HOME:-}" ]]; then
553
- printf "%s" "${XDG_CACHE_HOME}/fpf"
554
- else
555
- printf "%s" "${HOME}/.cache/fpf"
556
- fi
557
- ;;
558
- MINGW*|MSYS*|CYGWIN*|Windows_NT)
559
- if [[ -n "${LOCALAPPDATA:-}" ]]; then
560
- printf "%s" "${LOCALAPPDATA}/fpf"
561
- elif [[ -n "${APPDATA:-}" ]]; then
562
- printf "%s" "${APPDATA}/fpf"
563
- else
564
- printf "%s" "${HOME}/.cache/fpf"
565
- fi
566
- ;;
567
- *)
568
- printf "%s" "${HOME}/.cache/fpf"
569
- ;;
570
- esac
571
- }
572
-
573
- initialize_cache_root() {
574
- if [[ -n "${CACHE_ROOT}" ]]; then
575
- return
576
- fi
577
-
578
- CACHE_ROOT="$(resolve_cache_root)"
579
- mkdir -p "${CACHE_ROOT}"
580
- }
581
-
582
- normalize_cache_query() {
583
- local value="$1"
584
- local -a tokens=()
585
-
586
- read -r -a tokens <<<"${value}"
587
- value="${tokens[*]}"
588
- value="${value,,}"
589
-
590
- printf "%s" "${value}"
591
- }
592
-
593
- trim_whitespace() {
594
- local value="$1"
595
- value="${value#"${value%%[![:space:]]*}"}"
596
- value="${value%"${value##*[![:space:]]}"}"
597
- printf "%s" "${value}"
598
- }
599
-
600
- selection_debug_enabled() {
601
- case "${FPF_DEBUG_SELECTION:-0}" in
602
- 1|true|yes|on)
603
- return 0
604
- ;;
605
- *)
606
- return 1
607
- ;;
608
- esac
609
- }
610
-
611
- log_selection_parse_skip() {
612
- local line_number="$1"
613
- local reason="$2"
614
- local raw_line="$3"
615
- local raw_repr=""
616
-
617
- selection_debug_enabled || return 0
618
-
619
- printf -v raw_repr "%q" "${raw_line}"
620
- log "Debug(selection): skipped line ${line_number}: ${reason}; raw=${raw_repr}"
621
- }
622
-
623
- platform_cache_token() {
624
- local os
625
-
626
- os="$(uname -s)"
627
- printf "%s" "${os,,}"
628
- }
629
-
630
- cache_fingerprint() {
631
- local manager="$1"
632
- local query="$2"
633
- local flags="$3"
634
- local normalized_query
635
-
636
- normalized_query="$(normalize_cache_query "${query}")"
637
- printf "%s|%s|%s|%s|%s" "${CACHE_FORMAT_VERSION}" "${manager}" "$(platform_cache_token)" "${normalized_query}" "${flags}"
638
- }
639
-
640
- cache_cksum() {
641
- local input="$1"
642
- printf "%s" "${input}" | cksum | awk '{ print $1 }'
643
- }
644
-
645
- cache_catalog_key() {
646
- local manager="$1"
647
- printf "catalog/%s.tsv" "${manager}"
648
- }
649
-
650
- catalog_fixture_checksum() {
651
- local fixture_name="$1"
652
- local fixture_path=""
653
-
654
- if [[ "${FPF_TEST_FIXTURES:-0}" != "1" ]]; then
655
- return 1
656
- fi
657
-
658
- if [[ -z "${FPF_TEST_FIXTURE_DIR:-}" ]]; then
659
- return 1
660
- fi
661
-
662
- fixture_path="${FPF_TEST_FIXTURE_DIR}/${fixture_name}"
663
- [[ -r "${fixture_path}" ]] || return 1
664
- cksum "${fixture_path}" | awk '{ print $1 }'
665
- }
666
-
667
- apt_catalog_state_token() {
668
- local fixture_checksum=""
669
- local lists_dir="/var/lib/apt/lists"
670
- local listing=""
671
- local listing_checksum=""
672
- local policy_checksum=""
673
-
674
- fixture_checksum="$(catalog_fixture_checksum "apt-dumpavail.txt" 2>/dev/null || true)"
675
- if [[ -n "${fixture_checksum}" ]]; then
676
- printf "fixture=%s" "${fixture_checksum}"
677
- return
678
- fi
679
-
680
- if [[ -d "${lists_dir}" ]]; then
681
- listing="$(find "${lists_dir}" -maxdepth 1 -type f \( -name '*_Packages' -o -name '*_Sources' -o -name '*InRelease' -o -name '*Release' \) -printf '%f|%s|%T@\n' 2>/dev/null | LC_ALL=C sort || true)"
682
- if [[ -n "${listing}" ]]; then
683
- listing_checksum="$(printf "%s" "${listing}" | cksum | awk '{ print $1 }')"
684
- printf "lists=%s" "${listing_checksum}"
685
- return
686
- fi
687
- fi
688
-
689
- if command_exists apt-cache; then
690
- policy_checksum="$(apt-cache policy 2>/dev/null | cksum | awk '{ print $1 }' || true)"
691
- if [[ -n "${policy_checksum}" ]]; then
692
- printf "policy=%s" "${policy_checksum}"
693
- return
694
- fi
695
- fi
696
-
697
- printf "state=unknown"
698
- }
699
-
700
- brew_catalog_state_token() {
701
- local formula_fixture_checksum=""
702
- local cask_fixture_checksum=""
703
- local formula_repo=""
704
- local cask_repo=""
705
- local formula_head=""
706
- local cask_head=""
707
- local brew_version=""
708
-
709
- formula_fixture_checksum="$(catalog_fixture_checksum "brew-formulae.txt" 2>/dev/null || true)"
710
- cask_fixture_checksum="$(catalog_fixture_checksum "brew-casks.txt" 2>/dev/null || true)"
711
- if [[ -n "${formula_fixture_checksum}" || -n "${cask_fixture_checksum}" ]]; then
712
- printf "fixture=formula=%s;cask=%s" "${formula_fixture_checksum:-missing}" "${cask_fixture_checksum:-missing}"
713
- return
714
- fi
715
-
716
- if command_exists brew; then
717
- formula_repo="$(brew --repository 2>/dev/null || true)"
718
- cask_repo="$(brew --repository homebrew/cask 2>/dev/null || true)"
719
- fi
720
-
721
- if command_exists git; then
722
- if [[ -n "${formula_repo}" && -d "${formula_repo}/.git" ]]; then
723
- formula_head="$(git -C "${formula_repo}" rev-parse HEAD 2>/dev/null || true)"
724
- fi
725
- if [[ -n "${cask_repo}" && -d "${cask_repo}/.git" ]]; then
726
- cask_head="$(git -C "${cask_repo}" rev-parse HEAD 2>/dev/null || true)"
727
- fi
728
- fi
729
-
730
- if [[ -n "${formula_head}" || -n "${cask_head}" ]]; then
731
- printf "repos=formula:%s;cask:%s" "${formula_head:-missing}" "${cask_head:-missing}"
732
- return
733
- fi
734
-
735
- if [[ -n "${formula_repo}" || -n "${cask_repo}" ]]; then
736
- printf "repo-paths=formula:%s;cask:%s" "${formula_repo:-missing}" "${cask_repo:-missing}"
737
- return
738
- fi
739
-
740
- if command_exists brew; then
741
- brew_version="$(brew --version 2>/dev/null | awk 'NR == 1 { print $0; exit }' || true)"
742
- if [[ -n "${brew_version}" ]]; then
743
- printf "version=%s" "${brew_version}"
744
- return
745
- fi
746
- fi
747
-
748
- printf "state=unknown"
749
- }
750
-
751
- brew_catalog_state_cache_ttl_seconds() {
752
- local ttl="${FPF_BREW_CATALOG_STATE_TTL:-300}"
753
-
754
- if ! [[ "${ttl}" =~ ^[0-9]+$ ]]; then
755
- ttl=300
756
- fi
757
-
758
- printf "%s" "${ttl}"
759
- }
760
-
761
- brew_catalog_state_cache_scope_hash() {
762
- local scope_token
763
-
764
- scope_token="fixtures=${FPF_TEST_FIXTURES:-0};fixture_dir=${FPF_TEST_FIXTURE_DIR:-}"
765
- cache_cksum "${scope_token}"
766
- }
767
-
768
- cache_search_catalog_fingerprint_uncached() {
769
- local manager="$1"
770
- local command_path=""
771
- local state_token="state=unknown"
772
-
773
- case "${manager}" in
774
- apt)
775
- command_path="$(command -v apt-cache 2>/dev/null || printf "missing")"
776
- state_token="$(apt_catalog_state_token)"
777
- ;;
778
- brew)
779
- command_path="$(command -v brew 2>/dev/null || printf "missing")"
780
- state_token="$(brew_catalog_state_token)"
781
- ;;
782
- *)
783
- command_path="n/a"
784
- ;;
785
- esac
786
-
787
- printf "%s|cmd=%s|%s" "$(cache_fingerprint "${manager}" "" "search-catalog")" "${command_path}" "${state_token}"
788
- }
789
-
790
- cache_search_catalog_fingerprint() {
791
- local manager="$1"
792
- local state_cache_key=""
793
- local state_cache_file=""
794
- local state_cache_ttl=0
795
- local state_cache_tmp=""
796
- local state_cache_scope_hash=""
797
-
798
- if [[ "${manager}" != "brew" ]]; then
799
- cache_search_catalog_fingerprint_uncached "${manager}"
800
- return
801
- fi
802
-
803
- initialize_cache_root
804
-
805
- state_cache_scope_hash="$(brew_catalog_state_cache_scope_hash)"
806
- state_cache_key="state/search-catalog-fingerprint/${manager}.${state_cache_scope_hash}.txt"
807
- state_cache_file="$(cache_path_for_key "${state_cache_key}")"
808
- state_cache_ttl="$(brew_catalog_state_cache_ttl_seconds)"
809
-
810
- if [[ "${state_cache_ttl}" -gt 0 && -s "${state_cache_file}" ]] && cache_is_fresh_with_ttl "${state_cache_key}" "${state_cache_ttl}"; then
811
- cat "${state_cache_file}"
812
- return
813
- fi
814
-
815
- state_cache_tmp="$(mktemp "${SESSION_TMP_ROOT}/search-catalog-fingerprint.XXXXXX")"
816
- cache_search_catalog_fingerprint_uncached "${manager}" >"${state_cache_tmp}" || true
817
-
818
- if [[ -s "${state_cache_tmp}" ]]; then
819
- cache_store_key_from_file "${state_cache_key}" "${manager}" "${state_cache_tmp}"
820
- cat "${state_cache_tmp}"
821
- fi
822
-
823
- rm -f "${state_cache_tmp}"
824
- }
825
-
826
- cache_search_catalog_key() {
827
- local manager="$1"
828
- local fingerprint="$2"
829
- local checksum
830
-
831
- checksum="$(cache_cksum "${fingerprint}")"
832
- printf "search-catalog/%s/%s.tsv" "${manager}" "${checksum}"
833
- }
834
-
835
- cache_query_key() {
836
- local manager="$1"
837
- local query="$2"
838
- local flags="$3"
839
- local fingerprint
840
- local checksum
841
-
842
- fingerprint="$(cache_fingerprint "${manager}" "${query}" "${flags}")"
843
- checksum="$(cache_cksum "${fingerprint}")"
844
- printf "query/%s/%s.tsv" "${manager}" "${checksum}"
845
- }
846
-
847
- cache_meta_key() {
848
- local key="$1"
849
- printf "meta/%s.meta" "${key}"
850
- }
851
-
852
- cache_path_for_key() {
853
- local key="$1"
854
- printf "%s/%s" "${CACHE_ROOT}" "${key}"
855
- }
856
-
857
- cache_atomic_write_from_file() {
858
- local source_file="$1"
859
- local destination_file="$2"
860
- local destination_dir
861
- local destination_base
862
- local temp_file
863
-
864
- destination_dir="$(dirname "${destination_file}")"
865
- destination_base="$(basename "${destination_file}")"
866
-
867
- mkdir -p "${destination_dir}"
868
- temp_file="$(mktemp "${destination_dir}/.${destination_base}.tmp.XXXXXX")"
869
- cp "${source_file}" "${temp_file}"
870
- mv "${temp_file}" "${destination_file}"
871
- }
872
-
873
- cache_write_meta() {
874
- local key="$1"
875
- local fingerprint="$2"
876
- local item_count="$3"
877
- local refresh_status="${4:-}"
878
- local generation="${5:-}"
879
- local last_error_at="${6:-}"
880
- local last_error="${7:-}"
881
- local created_at
882
- local created_epoch
883
- local meta_key
884
- local meta_file
885
- local temp_meta
886
-
887
- created_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
888
- created_epoch="$(date +%s)"
889
- meta_key="$(cache_meta_key "${key}")"
890
- meta_file="$(cache_path_for_key "${meta_key}")"
891
- temp_meta="$(mktemp "${SESSION_TMP_ROOT}/meta.XXXXXX")"
892
-
893
- {
894
- printf "format_version=%s\n" "${CACHE_FORMAT_VERSION}"
895
- printf "created_at=%s\n" "${created_at}"
896
- printf "created_epoch=%s\n" "${created_epoch}"
897
- printf "fingerprint=%s\n" "${fingerprint}"
898
- printf "item_count=%s\n" "${item_count}"
899
- if [[ -n "${refresh_status}" ]]; then
900
- printf "refresh_status=%s\n" "${refresh_status}"
901
- fi
902
- if [[ -n "${generation}" ]]; then
903
- printf "generation=%s\n" "${generation}"
904
- fi
905
- if [[ -n "${last_error_at}" ]]; then
906
- printf "last_error_at=%s\n" "${last_error_at}"
907
- fi
908
- if [[ -n "${last_error}" ]]; then
909
- printf "last_error=%s\n" "${last_error}"
910
- fi
911
- } >"${temp_meta}"
912
-
913
- cache_atomic_write_from_file "${temp_meta}" "${meta_file}"
914
- rm -f "${temp_meta}"
915
- }
916
-
917
- cache_store_key_from_file() {
918
- local key="$1"
919
- local fingerprint="$2"
920
- local source_file="$3"
921
- local refresh_status="${4:-}"
922
- local generation="${5:-}"
923
- local last_error_at="${6:-}"
924
- local last_error="${7:-}"
925
- local cache_file
926
- local item_count
927
-
928
- cache_file="$(cache_path_for_key "${key}")"
929
- item_count="$(awk 'END { print NR + 0 }' "${source_file}")"
930
-
931
- cache_atomic_write_from_file "${source_file}" "${cache_file}"
932
- cache_write_meta "${key}" "${fingerprint}" "${item_count}" "${refresh_status}" "${generation}" "${last_error_at}" "${last_error}"
933
- }
934
-
935
- cache_meta_value_for_key() {
936
- local key="$1"
937
- local field_name="$2"
938
- local meta_file
939
-
940
- meta_file="$(cache_path_for_key "$(cache_meta_key "${key}")")"
941
- if [[ ! -r "${meta_file}" ]]; then
942
- return 1
943
- fi
944
-
945
- awk -F'=' -v key="${field_name}" '$1 == key { value=substr($0, index($0, "=") + 1); print value; exit }' "${meta_file}"
946
- }
947
-
948
- cache_is_fresh_with_ttl() {
949
- local key="$1"
950
- local ttl_seconds="$2"
951
- local created_epoch
952
- local now_epoch
953
- local age_seconds
954
-
955
- if ! [[ "${ttl_seconds}" =~ ^[0-9]+$ ]]; then
956
- return 1
957
- fi
958
-
959
- if [[ "${ttl_seconds}" -eq 0 ]]; then
960
- return 1
961
- fi
962
-
963
- created_epoch="$(cache_meta_value_for_key "${key}" "created_epoch" 2>/dev/null || true)"
964
- if ! [[ "${created_epoch}" =~ ^[0-9]+$ ]]; then
965
- return 1
966
- fi
967
-
968
- now_epoch="$(date +%s)"
969
- if ! [[ "${now_epoch}" =~ ^[0-9]+$ ]]; then
970
- return 1
971
- fi
972
-
973
- age_seconds=$((now_epoch - created_epoch))
974
- if [[ "${age_seconds}" -lt 0 ]]; then
975
- return 1
976
- fi
977
-
978
- [[ "${age_seconds}" -lt "${ttl_seconds}" ]]
979
- }
980
-
981
- cache_emit_query_rows_if_valid() {
982
- local cache_file="$1"
983
-
984
- [[ -s "${cache_file}" ]] || return 1
985
-
986
- if ! awk -F'\t' 'NF >= 2 && $1 != "" { next } { exit 1 } END { if (NR == 0) exit 1 }' "${cache_file}" >/dev/null 2>&1; then
987
- return 1
988
- fi
989
-
990
- awk -F'\t' 'NF >= 2 && $1 != "" { desc=$2; if (desc == "") desc="-"; print $1 "\t" desc }' "${cache_file}"
991
- }
992
-
993
- bun_generation_state_key() {
994
- local key="$1"
995
- printf "state/%s.generation" "${key}"
996
- }
997
-
998
- bun_generation_read() {
999
- local key="$1"
1000
- local state_file
1001
- local current="0"
1002
-
1003
- state_file="$(cache_path_for_key "$(bun_generation_state_key "${key}")")"
1004
- if [[ -r "${state_file}" ]]; then
1005
- current="$(awk 'NR == 1 { print $1; exit }' "${state_file}")"
1006
- fi
1007
-
1008
- if ! [[ "${current}" =~ ^[0-9]+$ ]]; then
1009
- current="0"
1010
- fi
1011
-
1012
- printf "%s" "${current}"
1013
- }
1014
-
1015
- bun_generation_write() {
1016
- local key="$1"
1017
- local generation="$2"
1018
- local state_key
1019
- local state_file
1020
- local temp_file
1021
-
1022
- state_key="$(bun_generation_state_key "${key}")"
1023
- state_file="$(cache_path_for_key "${state_key}")"
1024
- temp_file="$(mktemp "${SESSION_TMP_ROOT}/bun-generation.XXXXXX")"
1025
- printf "%s\n" "${generation}" >"${temp_file}"
1026
- cache_atomic_write_from_file "${temp_file}" "${state_file}"
1027
- rm -f "${temp_file}"
1028
- }
1029
-
1030
- bun_generation_next() {
1031
- local key="$1"
1032
- local current
1033
- local next
1034
-
1035
- current="$(bun_generation_read "${key}")"
1036
- next=$((current + 1))
1037
- bun_generation_write "${key}" "${next}"
1038
- printf "%s" "${next}"
1039
- }
1040
-
1041
- bun_refresh_idle_seconds() {
1042
- local idle_seconds="${FPF_BUN_REFRESH_IDLE:-0.12}"
1043
- if ! [[ "${idle_seconds}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
1044
- idle_seconds="0.12"
1045
- fi
1046
- printf "%s" "${idle_seconds}"
1047
- }
1048
-
1049
- bun_emit_refresh_failure_meta() {
1050
- local key="$1"
1051
- local fingerprint="$2"
1052
- local generation="$3"
1053
- local last_error="$4"
1054
- local cache_file
1055
- local item_count="0"
1056
- local last_error_at
1057
-
1058
- cache_file="$(cache_path_for_key "${key}")"
1059
- if [[ -s "${cache_file}" ]]; then
1060
- item_count="$(awk 'END { print NR + 0 }' "${cache_file}")"
1061
- fi
1062
-
1063
- last_error_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
1064
- cache_write_meta "${key}" "${fingerprint}" "${item_count}" "error" "${generation}" "${last_error_at}" "${last_error}"
1065
- }
1066
-
1067
- bun_try_hot_reload_after_refresh() {
1068
- local query="$1"
1069
-
1070
- if [[ -n "${FPF_BUN_REFRESH_MANAGER_OVERRIDE:-}" || -n "${FPF_BUN_REFRESH_FALLBACK_FILE:-}" ]]; then
1071
- FPF_BUN_SKIP_REFRESH_SCHEDULE=1 \
1072
- FPF_IPC_MANAGER_OVERRIDE="${FPF_BUN_REFRESH_MANAGER_OVERRIDE:-}" \
1073
- FPF_IPC_FALLBACK_FILE="${FPF_BUN_REFRESH_FALLBACK_FILE:-}" \
1074
- run_ipc_reload_action "${query}" || true
1075
- fi
1076
-
1077
- if [[ -n "${FPF_TEST_CACHE_REFRESH_SIGNAL_FILE:-}" ]] && command_exists fpf-refresh-signal; then
1078
- fpf-refresh-signal >/dev/null 2>&1 || true
1079
- fi
1080
- }
1081
-
1082
- bun_run_refresh_worker() {
1083
- local manager="$1"
1084
- local query="$2"
1085
- local flags="$3"
1086
- local key="$4"
1087
- local fingerprint="$5"
1088
- local generation="$6"
1089
- local output_tmp
1090
-
1091
- sleep "$(bun_refresh_idle_seconds)"
1092
-
1093
- if [[ "${generation}" != "$(bun_generation_read "${key}")" ]]; then
1094
- return 0
1095
- fi
1096
-
1097
- output_tmp="$(mktemp "${SESSION_TMP_ROOT}/bun-refresh.XXXXXX")"
1098
- if ! manager_bun_search_entries_strict "${query}" >"${output_tmp}"; then
1099
- rm -f "${output_tmp}"
1100
- bun_emit_refresh_failure_meta "${key}" "${fingerprint}" "${generation}" "bun_search_failed"
1101
- send_fzf_prompt_action "Search> " || true
1102
- return 1
1103
- fi
1104
-
1105
- if [[ "${generation}" != "$(bun_generation_read "${key}")" ]]; then
1106
- rm -f "${output_tmp}"
1107
- return 0
1108
- fi
1109
-
1110
- cache_store_key_from_file "${key}" "${fingerprint}" "${output_tmp}" "success" "${generation}"
1111
- rm -f "${output_tmp}"
1112
- bun_try_hot_reload_after_refresh "${query}"
1113
- return 0
1114
- }
1115
-
1116
- start_bun_refresh_worker_async() {
1117
- local manager="$1"
1118
- local query="$2"
1119
- local flags="$3"
1120
- local key="$4"
1121
- local fingerprint="$5"
1122
- local generation="$6"
1123
- local fallback_file="$7"
1124
- local manager_override="$8"
1125
- local script_path="${BASH_SOURCE[0]}"
1126
-
1127
- FPF_BUN_REFRESH_FLAGS="${flags}" \
1128
- FPF_BUN_REFRESH_KEY="${key}" \
1129
- FPF_BUN_REFRESH_FINGERPRINT="${fingerprint}" \
1130
- FPF_BUN_REFRESH_GENERATION="${generation}" \
1131
- FPF_BUN_REFRESH_FALLBACK_FILE="${fallback_file}" \
1132
- FPF_BUN_REFRESH_MANAGER_OVERRIDE="${manager_override}" \
1133
- "${script_path}" --bun-refresh-worker --manager "${manager}" -- "${query}" >/dev/null 2>&1 &
1134
- }
1135
-
1136
- build_apt_catalog_entries() {
1137
- apt-cache dumpavail 2>/dev/null |
1138
- awk '
1139
- function flush_entry() {
1140
- if (pkg == "") {
1141
- return
1142
- }
1143
- desc_out = desc
1144
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", desc_out)
1145
- if (desc_out == "") {
1146
- desc_out = "-"
1147
- }
1148
- print pkg "\t" desc_out
1149
- }
1150
-
1151
- /^Package:[[:space:]]*/ {
1152
- flush_entry()
1153
- pkg = $0
1154
- sub(/^Package:[[:space:]]*/, "", pkg)
1155
- desc = ""
1156
- next
1157
- }
1158
-
1159
- /^Description:[[:space:]]*/ {
1160
- desc = $0
1161
- sub(/^Description:[[:space:]]*/, "", desc)
1162
- next
1163
- }
1164
-
1165
- /^[[:space:]]+/ {
1166
- if (desc != "") {
1167
- line = $0
1168
- sub(/^[[:space:]]+/, "", line)
1169
- if (line != "") {
1170
- desc = desc " " line
1171
- }
1172
- }
1173
- next
1174
- }
1175
-
1176
- /^$/ {
1177
- flush_entry()
1178
- pkg = ""
1179
- desc = ""
1180
- next
1181
- }
1182
-
1183
- END {
1184
- flush_entry()
1185
- }
1186
- ' |
1187
- awk -F'\t' 'NF >= 1 && $1 != "" { if ($2 == "") $2 = "-"; print $1 "\t" $2 }' |
1188
- awk -F'\t' '!seen[$1]++'
1189
- }
1190
-
1191
- build_brew_catalog_entries() {
1192
- {
1193
- brew formulae 2>/dev/null || true
1194
- brew casks 2>/dev/null || true
1195
- } |
1196
- awk 'NF >= 1 && $1 != "" { print $1 "\t-" }' |
1197
- awk -F'\t' '!seen[$1]++'
1198
- }
1199
-
1200
- build_search_catalog_entries_for_manager() {
1201
- local manager="$1"
1202
-
1203
- case "${manager}" in
1204
- apt)
1205
- build_apt_catalog_entries
1206
- ;;
1207
- brew)
1208
- build_brew_catalog_entries
1209
- ;;
1210
- *)
1211
- return 1
1212
- ;;
1213
- esac
1214
- }
1215
-
1216
- search_catalog_async_prewarm_enabled() {
1217
- local setting="${FPF_SEARCH_CATALOG_ASYNC_PREWARM:-1}"
1218
-
1219
- if [[ -z "${FPF_SEARCH_CATALOG_ASYNC_PREWARM+x}" && -n "${FPF_TEST_LOG:-}" ]]; then
1220
- return 1
1221
- fi
1222
-
1223
- setting="$(trim_whitespace "${setting}")"
1224
- setting="${setting,,}"
1225
-
1226
- case "${setting}" in
1227
- 0|false|no|off)
1228
- return 1
1229
- ;;
1230
- esac
1231
-
1232
- case "${ACTION}" in
1233
- search)
1234
- return 0
1235
- ;;
1236
- feed-search)
1237
- case "${FPF_SKIP_INSTALLED_MARKERS:-0}" in
1238
- 1|true|yes|on)
1239
- return 0
1240
- ;;
1241
- esac
1242
- ;;
1243
- esac
1244
-
1245
- return 1
1246
- }
1247
-
1248
- search_catalog_prewarm_lock_dir() {
1249
- local manager="$1"
1250
- printf "%s/state/search-catalog/%s.lock" "${CACHE_ROOT}" "${manager}"
1251
- }
1252
-
1253
- run_search_catalog_prewarm_worker() {
1254
- local manager="$1"
1255
- local output_tmp
1256
- local cache_fingerprint_value
1257
- local cache_key
1258
-
1259
- output_tmp="$(mktemp "${SESSION_TMP_ROOT}/search-catalog-prewarm.XXXXXX")"
1260
- build_search_catalog_entries_for_manager "${manager}" >"${output_tmp}" || true
1261
-
1262
- if [[ -s "${output_tmp}" ]]; then
1263
- cache_fingerprint_value="$(cache_search_catalog_fingerprint "${manager}")"
1264
- cache_key="$(cache_search_catalog_key "${manager}" "${cache_fingerprint_value}")"
1265
- cache_store_key_from_file "${cache_key}" "${cache_fingerprint_value}" "${output_tmp}"
1266
- fi
1267
-
1268
- rm -f "${output_tmp}"
1269
- }
1270
-
1271
- start_search_catalog_prewarm_async() {
1272
- local manager="$1"
1273
- local lock_dir
1274
-
1275
- search_catalog_async_prewarm_enabled || return 0
1276
- case "${manager}" in
1277
- apt|brew)
1278
- ;;
1279
- *)
1280
- return 0
1281
- ;;
1282
- esac
1283
-
1284
- initialize_cache_root
1285
- lock_dir="$(search_catalog_prewarm_lock_dir "${manager}")"
1286
- mkdir -p "$(dirname "${lock_dir}")"
1287
-
1288
- if ! mkdir "${lock_dir}" 2>/dev/null; then
1289
- return 0
1290
- fi
1291
-
1292
- if [[ -n "${FPF_TEST_LOG:-}" ]]; then
1293
- run_search_catalog_prewarm_worker "${manager}" || true
1294
- rmdir "${lock_dir}" >/dev/null 2>&1 || true
1295
- return 0
1296
- fi
1297
-
1298
- (
1299
- run_search_catalog_prewarm_worker "${manager}" || true
1300
- rmdir "${lock_dir}" >/dev/null 2>&1 || true
1301
- ) >/dev/null 2>&1 &
1302
- }
1303
-
1304
- ensure_search_catalog_cache() {
1305
- local manager="$1"
1306
- local cache_fingerprint_value
1307
- local cache_key
1308
- local cache_file
1309
- local output_tmp
1310
-
1311
- initialize_cache_root
1312
-
1313
- cache_fingerprint_value="$(cache_search_catalog_fingerprint "${manager}")"
1314
- cache_key="$(cache_search_catalog_key "${manager}" "${cache_fingerprint_value}")"
1315
- cache_file="$(cache_path_for_key "${cache_key}")"
1316
-
1317
- if [[ -s "${cache_file}" ]]; then
1318
- return 0
1319
- fi
1320
-
1321
- output_tmp="$(mktemp "${SESSION_TMP_ROOT}/search-catalog.XXXXXX")"
1322
-
1323
- build_search_catalog_entries_for_manager "${manager}" >"${output_tmp}" || true
1324
-
1325
- if [[ -s "${output_tmp}" ]]; then
1326
- cache_store_key_from_file "${cache_key}" "${cache_fingerprint_value}" "${output_tmp}"
1327
- rm -f "${output_tmp}"
1328
- return 0
1329
- fi
1330
-
1331
- rm -f "${output_tmp}"
1332
- return 1
1333
- }
1334
-
1335
- search_entries_from_catalog_cache() {
1336
- local manager="$1"
1337
- local query="$2"
1338
- local cache_fingerprint_value
1339
- local cache_key
1340
- local cache_file
1341
-
1342
- initialize_cache_root
1343
-
1344
- cache_fingerprint_value="$(cache_search_catalog_fingerprint "${manager}")"
1345
- cache_key="$(cache_search_catalog_key "${manager}" "${cache_fingerprint_value}")"
1346
- cache_file="$(cache_path_for_key "${cache_key}")"
1347
-
1348
- if [[ ! -s "${cache_file}" ]]; then
1349
- return 1
1350
- fi
1351
-
1352
- awk -F'\t' -v query="${query}" '
1353
- BEGIN {
1354
- normalized = tolower(query)
1355
- token_count = split(normalized, raw_tokens, /[[:space:]]+/)
1356
- token_index = 0
1357
- for (i = 1; i <= token_count; i++) {
1358
- if (raw_tokens[i] != "") {
1359
- token_index++
1360
- tokens[token_index] = raw_tokens[i]
1361
- }
1362
- }
1363
- }
1364
- {
1365
- haystack = tolower($1 " " $2)
1366
- matched = 1
1367
- for (i = 1; i <= token_index; i++) {
1368
- if (index(haystack, tokens[i]) == 0) {
1369
- matched = 0
1370
- break
1371
- }
1372
- }
1373
- if (matched) {
1374
- desc = $2
1375
- if (desc == "") {
1376
- desc = "-"
1377
- }
1378
- print $1 "\t" desc
1379
- }
1380
- }
1381
- ' "${cache_file}" || return 1
1382
- }
1383
-
1384
- initialize_session_tmp_root() {
1385
- if [[ -n "${SESSION_TMP_ROOT}" ]]; then
1386
- return
1387
- fi
1388
-
1389
- if [[ -n "${FPF_SESSION_TMP_ROOT:-}" ]]; then
1390
- SESSION_TMP_ROOT="${FPF_SESSION_TMP_ROOT}"
1391
- mkdir -p "${SESSION_TMP_ROOT}"
1392
- else
1393
- SESSION_TMP_ROOT="$(mktemp -d "${TMP_ROOT}/session.XXXXXX")"
1394
- fi
1395
- HELP_FILE="${SESSION_TMP_ROOT}/help"
1396
- KBINDS_FILE="${SESSION_TMP_ROOT}/keybinds"
1397
- }
1398
-
1399
- run_preview_manager_output() {
1400
- local manager="$1"
1401
- local package="$2"
1402
-
1403
- manager_show_info "${manager}" "${package}" || true
1404
- }
1405
-
1406
- run_preview_item_action() {
1407
- local manager="$1"
1408
- local package="$2"
1409
- local cache_dir="${SESSION_TMP_ROOT}/preview-cache"
1410
- local cache_key=""
1411
- local cache_file=""
1412
- local temp_file=""
1413
-
1414
- [[ -n "${manager}" && -n "${package}" ]] || return 0
1415
-
1416
- mkdir -p "${cache_dir}"
1417
- cache_key="$(cache_cksum "${manager}|${package}")"
1418
- cache_file="${cache_dir}/${manager}.${cache_key}.txt"
1419
-
1420
- if [[ -f "${cache_file}" ]]; then
1421
- cat "${cache_file}"
1422
- return 0
1423
- fi
1424
-
1425
- temp_file="$(mktemp "${SESSION_TMP_ROOT}/preview.XXXXXX")"
1426
- run_preview_manager_output "${manager}" "${package}" >"${temp_file}"
1427
- mv -f "${temp_file}" "${cache_file}"
1428
- cat "${cache_file}"
1429
- }
1430
-
1431
- cleanup_session_tmp_root() {
1432
- stop_loading_indicator
1433
- loading_progress_end
1434
-
1435
- if [[ -n "${SESSION_TMP_ROOT}" && -d "${SESSION_TMP_ROOT}" ]]; then
1436
- rm -rf "${SESSION_TMP_ROOT}"
1437
- fi
1438
- }
1439
-
1440
- run_as_root() {
1441
- if [[ "${EUID}" -eq 0 ]]; then
1442
- "$@"
1443
- return
1444
- fi
1445
-
1446
- if command_exists sudo; then
1447
- sudo "$@"
1448
- return
1449
- fi
1450
-
1451
- die "Root privileges are required for: $*"
1452
- }
1453
-
1454
- manager_list() {
1455
- printf "%s\n" "apt dnf pacman zypper emerge brew winget choco scoop snap flatpak npm bun"
1456
- }
1457
-
1458
- manager_supported() {
1459
- local manager="$1"
1460
- case "${manager}" in
1461
- apt|dnf|pacman|zypper|emerge|brew|winget|choco|scoop|snap|flatpak|npm|bun)
1462
- return 0
1463
- ;;
1464
- *)
1465
- return 1
1466
- ;;
1467
- esac
1468
- }
1469
-
1470
- manager_command_ready() {
1471
- local manager="$1"
1472
- case "${manager}" in
1473
- apt)
1474
- command_exists apt-cache && command_exists apt-get && command_exists dpkg-query
1475
- ;;
1476
- dnf)
1477
- command_exists dnf
1478
- ;;
1479
- pacman)
1480
- command_exists pacman
1481
- ;;
1482
- zypper)
1483
- command_exists zypper
1484
- ;;
1485
- emerge)
1486
- command_exists emerge
1487
- ;;
1488
- brew)
1489
- command_exists brew
1490
- ;;
1491
- winget)
1492
- command_exists winget
1493
- ;;
1494
- choco)
1495
- command_exists choco
1496
- ;;
1497
- scoop)
1498
- command_exists scoop
1499
- ;;
1500
- snap)
1501
- command_exists snap
1502
- ;;
1503
- flatpak)
1504
- command_exists flatpak
1505
- ;;
1506
- npm)
1507
- command_exists npm
1508
- ;;
1509
- bun)
1510
- command_exists bun
1511
- ;;
1512
- *)
1513
- return 1
1514
- ;;
1515
- esac
1516
- }
1517
-
1518
- flatpak_has_any_remotes() {
1519
- if ! manager_command_ready flatpak; then
1520
- return 1
1521
- fi
1522
-
1523
- if flatpak remotes --columns=name 2>/dev/null | awk 'NF > 0 { found=1; exit } END { exit (found ? 0 : 1) }'; then
1524
- return 0
1525
- fi
1526
-
1527
- if flatpak remote-list --columns=name 2>/dev/null | awk 'NF > 0 { found=1; exit } END { exit (found ? 0 : 1) }'; then
1528
- return 0
1529
- fi
1530
-
1531
- return 1
1532
- }
1533
-
1534
- flatpak_has_flathub_remote() {
1535
- if ! manager_command_ready flatpak; then
1536
- return 1
1537
- fi
1538
-
1539
- if flatpak remotes --columns=name 2>/dev/null | awk '{
1540
- name=tolower($1)
1541
- if (name == "flathub") {
1542
- found=1
1543
- exit
1544
- }
1545
- } END { exit (found ? 0 : 1) }'; then
1546
- return 0
1547
- fi
1548
-
1549
- if flatpak remote-list --columns=name 2>/dev/null | awk '{
1550
- name=tolower($1)
1551
- if (name == "flathub") {
1552
- found=1
1553
- exit
1554
- }
1555
- } END { exit (found ? 0 : 1) }'; then
1556
- return 0
1557
- fi
1558
-
1559
- return 1
1560
- }
1561
-
1562
- ensure_flatpak_flathub_remote() {
1563
- if ! manager_command_ready flatpak; then
1564
- return 1
1565
- fi
1566
-
1567
- if flatpak_has_flathub_remote; then
1568
- return 0
1569
- fi
1570
-
1571
- flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo 2>/dev/null ||
1572
- run_as_root flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo 2>/dev/null ||
1573
- return 1
1574
-
1575
- flatpak_has_flathub_remote
1576
- }
1577
-
1578
- winget_has_default_source() {
1579
- if ! manager_command_ready winget; then
1580
- return 1
1581
- fi
1582
-
1583
- if winget source list 2>/dev/null | awk '
1584
- {
1585
- line=$0
1586
- sub(/^[[:space:]]+/, "", line)
1587
- if (line == "") {
1588
- next
1589
- }
1590
- split(line, cols, /[[:space:]]+/)
1591
- if (tolower(cols[1]) == "winget") {
1592
- found=1
1593
- exit
1594
- }
1595
- }
1596
- END { exit (found ? 0 : 1) }
1597
- '; then
1598
- return 0
1599
- fi
1600
-
1601
- return 1
1602
- }
1603
-
1604
- choco_has_any_sources() {
1605
- if ! manager_command_ready choco; then
1606
- return 1
1607
- fi
1608
-
1609
- if choco source list --limit-output 2>/dev/null | awk -F'|' 'NF >= 2 && $1 != "" { found=1; exit } END { exit (found ? 0 : 1) }'; then
1610
- return 0
1611
- fi
1612
-
1613
- return 1
1614
- }
1615
-
1616
- scoop_has_any_buckets() {
1617
- if ! manager_command_ready scoop; then
1618
- return 1
1619
- fi
1620
-
1621
- if scoop bucket list 2>/dev/null | awk '
1622
- {
1623
- line=$0
1624
- sub(/^[[:space:]]+/, "", line)
1625
- if (line == "") {
1626
- next
1627
- }
1628
- if (tolower(line) ~ /^name[[:space:]]+/) {
1629
- next
1630
- }
1631
- if (line ~ /^[-[:space:]]+$/) {
1632
- next
1633
- }
1634
- split(line, cols, /[[:space:]]+/)
1635
- if (length(cols[2]) > 0 && (cols[2] ~ /^https?:\/\// || cols[2] ~ /^git@/ || cols[2] ~ /^ssh:\/\// || cols[2] ~ /^file:\/\// || cols[2] ~ /^\//)) {
1636
- found=1
1637
- exit
1638
- }
1639
- }
1640
- END { exit (found ? 0 : 1) }
1641
- '; then
1642
- return 0
1643
- fi
1644
-
1645
- return 1
1646
- }
1647
-
1648
- manager_no_query_setup_message() {
1649
- local manager="$1"
1650
-
1651
- case "${manager}" in
1652
- flatpak)
1653
- if ! flatpak_has_flathub_remote; then
1654
- ensure_flatpak_flathub_remote >/dev/null 2>&1 || true
1655
- fi
1656
- if ! flatpak_has_flathub_remote; then
1657
- printf "%s" "Flatpak has no remotes configured. Add Flathub with: flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo"
1658
- return 0
1659
- fi
1660
- ;;
1661
- winget)
1662
- if ! winget_has_default_source; then
1663
- printf "%s" "WinGet source 'winget' is not configured. Restore it with: winget source reset --force"
1664
- return 0
1665
- fi
1666
- ;;
1667
- choco)
1668
- if ! choco_has_any_sources; then
1669
- printf "%s" "Chocolatey has no package sources configured. Add the default source with: choco source add -n=chocolatey -s=https://community.chocolatey.org/api/v2/"
1670
- return 0
1671
- fi
1672
- ;;
1673
- scoop)
1674
- if ! scoop_has_any_buckets; then
1675
- printf "%s" "Scoop has no buckets configured. Add the default bucket with: scoop bucket add main"
1676
- return 0
1677
- fi
1678
- ;;
1679
- esac
1680
-
1681
- return 1
1682
- }
1683
-
1684
- manager_can_install_fzf() {
1685
- local manager="$1"
1686
- case "${manager}" in
1687
- apt|dnf|pacman|zypper|emerge|brew|winget|choco|scoop|snap)
1688
- return 0
1689
- ;;
1690
- *)
1691
- return 1
1692
- ;;
1693
- esac
1694
- }
1695
-
1696
- install_fzf_with_manager() {
1697
- local manager="$1"
1698
-
1699
- if [[ "${FPF_TEST_FZF_MANAGER_INSTALL_FAIL:-0}" == "1" ]]; then
1700
- return 1
1701
- fi
1702
-
1703
- case "${manager}" in
1704
- apt)
1705
- run_as_root apt-get install -y fzf
1706
- ;;
1707
- dnf)
1708
- run_as_root dnf install -y fzf
1709
- ;;
1710
- pacman)
1711
- run_as_root pacman -S --needed fzf
1712
- ;;
1713
- zypper)
1714
- run_as_root zypper --non-interactive install --auto-agree-with-licenses fzf
1715
- ;;
1716
- emerge)
1717
- run_as_root emerge --ask=n app-shells/fzf
1718
- ;;
1719
- brew)
1720
- brew install fzf
1721
- ;;
1722
- winget)
1723
- winget install --id junegunn.fzf --exact --source winget --accept-package-agreements --accept-source-agreements --disable-interactivity ||
1724
- winget install --id fzf --exact --source winget --accept-package-agreements --accept-source-agreements --disable-interactivity
1725
- ;;
1726
- choco)
1727
- choco install fzf -y
1728
- ;;
1729
- scoop)
1730
- scoop install fzf
1731
- ;;
1732
- snap)
1733
- run_as_root snap install fzf
1734
- ;;
1735
- *)
1736
- return 1
1737
- ;;
1738
- esac
1739
- }
1740
-
1741
- detect_fzf_release_asset() {
1742
- local os=""
1743
- local arch=""
1744
-
1745
- os="$(uname -s)"
1746
- arch="$(uname -m)"
1747
-
1748
- case "${os}" in
1749
- Linux)
1750
- case "${arch}" in
1751
- x86_64|amd64) printf "linux_amd64.tar.gz" ;;
1752
- aarch64|arm64) printf "linux_arm64.tar.gz" ;;
1753
- armv7l|armv7*) printf "linux_armv7.tar.gz" ;;
1754
- armv6l|armv6*) printf "linux_armv6.tar.gz" ;;
1755
- *) return 1 ;;
1756
- esac
1757
- ;;
1758
- Darwin)
1759
- case "${arch}" in
1760
- x86_64|amd64) printf "darwin_amd64.tar.gz" ;;
1761
- arm64|aarch64) printf "darwin_arm64.tar.gz" ;;
1762
- *) return 1 ;;
1763
- esac
1764
- ;;
1765
- *)
1766
- return 1
1767
- ;;
1768
- esac
1769
- }
1770
-
1771
- resolve_fzf_release_url() {
1772
- local asset_suffix="$1"
1773
- local api_url="https://api.github.com/repos/junegunn/fzf/releases/latest"
1774
- local api_payload=""
1775
- local asset_url=""
1776
-
1777
- if [[ -n "${FPF_FZF_BOOTSTRAP_URL:-}" ]]; then
1778
- printf "%s" "${FPF_FZF_BOOTSTRAP_URL}"
1779
- return 0
1780
- fi
1781
-
1782
- command_exists curl || return 1
1783
- api_payload="$(curl --silent --show-error --fail --location "${api_url}" 2>/dev/null || true)"
1784
- [[ -n "${api_payload}" ]] || return 1
1785
-
1786
- asset_url="$(printf "%s" "${api_payload}" | awk -F'"' -v suffix="${asset_suffix}" '
1787
- $2 == "browser_download_url" {
1788
- needle = "-" suffix
1789
- if (index($4, needle) > 0) {
1790
- print $4
1791
- exit
1792
- }
1793
- }
1794
- ')"
1795
-
1796
- [[ -n "${asset_url}" ]] || return 1
1797
- printf "%s" "${asset_url}"
1798
- }
1799
-
1800
- install_fzf_from_release_fallback() {
1801
- local asset_suffix=""
1802
- local asset_url=""
1803
- local archive_file=""
1804
- local install_dir=""
1805
-
1806
- if [[ "${FPF_TEST_BOOTSTRAP_FZF_FALLBACK:-0}" == "1" ]]; then
1807
- if [[ -n "${FPF_TEST_MOCKCMD_PATH:-}" && -n "${FPF_TEST_MOCK_BIN:-}" ]]; then
1808
- ln -sf "${FPF_TEST_MOCKCMD_PATH}" "${FPF_TEST_MOCK_BIN}/fzf"
1809
- return 0
1810
- fi
1811
- return 1
1812
- fi
1813
-
1814
- asset_suffix="$(detect_fzf_release_asset)" || return 1
1815
- asset_url="$(resolve_fzf_release_url "${asset_suffix}")" || return 1
1816
- archive_file="$(mktemp "${SESSION_TMP_ROOT}/fzf-bootstrap.XXXXXX.tar.gz")"
1817
- install_dir="${SESSION_TMP_ROOT}/fzf-bootstrap/bin"
1818
-
1819
- mkdir -p "${install_dir}"
1820
-
1821
- if ! curl --silent --show-error --fail --location --output "${archive_file}" "${asset_url}"; then
1822
- rm -f "${archive_file}"
1823
- return 1
1824
- fi
1825
-
1826
- if ! tar -xzf "${archive_file}" -C "${install_dir}"; then
1827
- rm -f "${archive_file}"
1828
- return 1
1829
- fi
1830
-
1831
- rm -f "${archive_file}"
1832
- chmod +x "${install_dir}/fzf" 2>/dev/null || true
1833
- [[ -x "${install_dir}/fzf" ]] || return 1
1834
-
1835
- PATH="${install_dir}:${PATH}"
1836
- export PATH
1837
- return 0
1838
- }
1839
-
1840
- build_fzf_bootstrap_candidates() {
1841
- local seen=""
1842
- local manager
1843
-
1844
- for manager in "$@"; do
1845
- [[ -n "${manager}" ]] || continue
1846
- manager_can_install_fzf "${manager}" || continue
1847
- manager_command_ready "${manager}" || continue
1848
- case " ${seen} " in
1849
- *" ${manager} "*)
1850
- continue
1851
- ;;
1852
- esac
1853
- printf "%s\n" "${manager}"
1854
- seen+=" ${manager}"
1855
- done
1856
-
1857
- if [[ -n "${seen}" ]]; then
1858
- return
1859
- fi
1860
-
1861
- for manager in apt dnf pacman zypper emerge brew winget choco scoop snap; do
1862
- manager_command_ready "${manager}" || continue
1863
- case " ${seen} " in
1864
- *" ${manager} "*)
1865
- continue
1866
- ;;
1867
- esac
1868
- printf "%s\n" "${manager}"
1869
- seen+=" ${manager}"
1870
- done
1871
- }
1872
-
1873
- ensure_fzf() {
1874
- local candidates=()
1875
- local manager
1876
-
1877
- if fzf_command_available; then
1878
- return
1879
- fi
1880
-
1881
- while IFS= read -r manager; do
1882
- [[ -n "${manager}" ]] || continue
1883
- candidates+=("${manager}")
1884
- done < <(build_fzf_bootstrap_candidates "$@")
1885
-
1886
- if [[ "${#candidates[@]}" -eq 0 ]]; then
1887
- die "fzf is required and no compatible manager is available to auto-install it"
1888
- fi
1889
-
1890
- local candidate_labels
1891
- candidate_labels="$(join_manager_labels "${candidates[@]-}")"
1892
-
1893
- log "fzf is missing. Auto-installing with: ${candidate_labels}"
1894
-
1895
- for manager in "${candidates[@]-}"; do
1896
- log "Attempting fzf install with $(manager_label "${manager}")"
1897
- if install_fzf_with_manager "${manager}"; then
1898
- if fzf_command_available; then
1899
- return
1900
- fi
1901
- fi
1902
- done
1903
-
1904
- log "Package-manager bootstrap did not provide fzf. Trying release binary fallback."
1905
- if install_fzf_from_release_fallback && fzf_command_available; then
1906
- return
1907
- fi
1908
-
1909
- die "Failed to auto-install fzf. Install fzf manually and rerun."
1910
- }
1911
-
1912
- normalize_manager() {
1913
- local manager="$1"
1914
-
1915
- manager="$(trim_whitespace "${manager}")"
1916
- manager="${manager,,}"
1917
-
1918
- case "${manager}" in
1919
- homebrew)
1920
- manager="brew"
1921
- ;;
1922
- chocolatey|chocolate)
1923
- manager="choco"
1924
- ;;
1925
- "portage (emerge)"|portage-emerge|portage)
1926
- manager="emerge"
1927
- ;;
1928
- win-get)
1929
- manager="winget"
1930
- ;;
1931
- esac
1932
-
1933
- printf "%s" "${manager}"
1934
- }
1935
-
1936
- os_release_field() {
1937
- local file_path="$1"
1938
- local field_name="$2"
1939
-
1940
- awk -v key="${field_name}" '
1941
- index($0, key "=") == 1 {
1942
- value = substr($0, index($0, "=") + 1)
1943
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
1944
- if (value ~ /^".*"$/ || value ~ /^\047.*\047$/) {
1945
- value = substr(value, 2, length(value) - 2)
1946
- }
1947
- print tolower(value)
1948
- exit
1949
- }
1950
- ' "${file_path}"
1951
- }
1952
-
1953
- manager_label() {
1954
- local manager="$1"
1955
- case "${manager}" in
1956
- apt) printf "APT" ;;
1957
- dnf) printf "DNF" ;;
1958
- pacman) printf "Pacman" ;;
1959
- zypper) printf "Zypper" ;;
1960
- emerge) printf "Portage (emerge)" ;;
1961
- brew) printf "Homebrew" ;;
1962
- winget) printf "WinGet" ;;
1963
- choco) printf "Chocolatey" ;;
1964
- scoop) printf "Scoop" ;;
1965
- snap) printf "Snap" ;;
1966
- flatpak) printf "Flatpak" ;;
1967
- npm) printf "npm" ;;
1968
- bun) printf "bun" ;;
1969
- *) printf "%s" "${manager}" ;;
1970
- esac
1971
- }
1972
-
1973
- detect_default_manager() {
1974
- local os
1975
- os="$(uname -s)"
1976
-
1977
- if [[ "${os}" == "Darwin" ]]; then
1978
- if command_exists brew; then
1979
- printf "brew"
1980
- return
1981
- fi
1982
- fi
1983
-
1984
- if [[ "${os}" == MINGW* || "${os}" == MSYS* || "${os}" == CYGWIN* || "${os}" == "Windows_NT" ]]; then
1985
- if command_exists winget; then printf "winget"; return; fi
1986
- if command_exists choco; then printf "choco"; return; fi
1987
- if command_exists scoop; then printf "scoop"; return; fi
1988
- if command_exists bun; then printf "bun"; return; fi
1989
- if command_exists npm; then printf "npm"; return; fi
1990
- fi
1991
-
1992
- if [[ "${os}" == "Linux" ]]; then
1993
- local distro_id=""
1994
- local distro_like=""
1995
- local os_release_file="${FPF_OS_RELEASE_FILE:-}"
1996
-
1997
- if [[ -n "${os_release_file}" && ! -r "${os_release_file}" ]]; then
1998
- die "FPF_OS_RELEASE_FILE is set but not readable: ${os_release_file}"
1999
- fi
2000
-
2001
- if [[ -z "${os_release_file}" && -r /etc/os-release ]]; then
2002
- os_release_file="/etc/os-release"
2003
- elif [[ -z "${os_release_file}" && -r /usr/lib/os-release ]]; then
2004
- os_release_file="/usr/lib/os-release"
2005
- fi
2006
-
2007
- if [[ -n "${os_release_file}" ]]; then
2008
- distro_id="$(os_release_field "${os_release_file}" "ID")"
2009
- distro_like="$(os_release_field "${os_release_file}" "ID_LIKE")"
2010
- fi
2011
-
2012
- case "${distro_id} ${distro_like}" in
2013
- *arch*|*manjaro*)
2014
- if command_exists pacman; then printf "pacman"; return; fi
2015
- ;;
2016
- *ubuntu*|*debian*|*linuxmint*|*pop*|*elementary*)
2017
- if command_exists apt-get; then printf "apt"; return; fi
2018
- ;;
2019
- *fedora*|*rhel*|*centos*|*rocky*|*alma*)
2020
- if command_exists dnf; then printf "dnf"; return; fi
2021
- ;;
2022
- *opensuse*|*suse*|*sles*)
2023
- if command_exists zypper; then printf "zypper"; return; fi
2024
- ;;
2025
- *gentoo*)
2026
- if command_exists emerge; then printf "emerge"; return; fi
2027
- ;;
2028
- esac
2029
-
2030
- if command_exists apt-get; then printf "apt"; return; fi
2031
- if command_exists dnf; then printf "dnf"; return; fi
2032
- if command_exists pacman; then printf "pacman"; return; fi
2033
- if command_exists zypper; then printf "zypper"; return; fi
2034
- if command_exists emerge; then printf "emerge"; return; fi
2035
- if command_exists snap; then printf "snap"; return; fi
2036
- if command_exists flatpak; then printf "flatpak"; return; fi
2037
- if command_exists bun; then printf "bun"; return; fi
2038
- if command_exists npm; then printf "npm"; return; fi
2039
- fi
2040
-
2041
- if command_exists brew; then printf "brew"; return; fi
2042
- if command_exists winget; then printf "winget"; return; fi
2043
- if command_exists choco; then printf "choco"; return; fi
2044
- if command_exists scoop; then printf "scoop"; return; fi
2045
- if command_exists bun; then printf "bun"; return; fi
2046
- if command_exists npm; then printf "npm"; return; fi
2047
-
2048
- die "Unable to auto-detect a supported package manager. Use --manager."
2049
- }
2050
-
2051
- detect_default_managers() {
2052
- local emitted=""
2053
- local primary_manager=""
2054
- local manager
2055
- local prefer_bun=0
2056
-
2057
- if manager_command_ready bun; then
2058
- prefer_bun=1
2059
- fi
2060
-
2061
- add_detected_manager() {
2062
- local manager_name="$1"
2063
- [[ -n "${manager_name}" ]] || return
2064
-
2065
- if [[ "${prefer_bun}" -eq 1 && "${manager_name}" == "npm" ]]; then
2066
- return
2067
- fi
2068
-
2069
- case " ${emitted} " in
2070
- *" ${manager_name} "*)
2071
- return
2072
- ;;
2073
- esac
2074
- if manager_command_ready "${manager_name}"; then
2075
- printf "%s\n" "${manager_name}"
2076
- emitted+=" ${manager_name}"
2077
- fi
2078
- }
2079
-
2080
- primary_manager="$(detect_default_manager)"
2081
- add_detected_manager "${primary_manager}"
2082
-
2083
- for manager in apt dnf pacman zypper emerge brew winget choco scoop snap flatpak bun npm; do
2084
- add_detected_manager "${manager}"
2085
- done
2086
-
2087
- if [[ -n "${emitted}" ]]; then
2088
- return
2089
- fi
2090
-
2091
- die "Unable to auto-detect supported package managers. Use --manager."
2092
- }
2093
-
2094
- join_manager_labels() {
2095
- local out=""
2096
- local mgr
2097
-
2098
- for mgr in "$@"; do
2099
- [[ -n "${mgr}" ]] || continue
2100
- if [[ -n "${out}" ]]; then
2101
- out+=", "
2102
- fi
2103
- out+="$(manager_label "${mgr}")"
2104
- done
2105
-
2106
- printf "%s" "${out}"
2107
- }
2108
-
2109
- join_manager_labels_with_ids() {
2110
- local out=""
2111
- local mgr
2112
-
2113
- for mgr in "$@"; do
2114
- [[ -n "${mgr}" ]] || continue
2115
- if [[ -n "${out}" ]]; then
2116
- out+=", "
2117
- fi
2118
- out+="$(manager_label "${mgr}") (${mgr})"
2119
- done
2120
-
2121
- printf "%s" "${out}"
2122
- }
2123
-
2124
- build_help_file() {
2125
- local default_managers="$1"
2126
-
2127
- cat >"${HELP_FILE}" <<EOF
2128
- ${SCRIPT_NAME} - fuzzy package finder
2129
-
2130
- Syntax:
2131
- ${SCRIPT_NAME} [manager option] [action option] [query]
2132
- ${SCRIPT_NAME} -m|--manager <name> [action option] [query]
2133
-
2134
- Default behavior:
2135
- Fuzzy-search available packages and install selected items.
2136
-
2137
- Detected default manager(s):
2138
- ${default_managers}
2139
-
2140
- Action options:
2141
- -l, --list-installed Fuzzy-search installed packages and show details
2142
- -R, --remove Fuzzy-search installed packages and remove selected
2143
- -U, --update Run manager update/upgrade flow
2144
- --refresh Refresh manager package catalogs only
2145
- -y, --yes Assume yes for confirmation prompts
2146
- -v, --version Print version and exit
2147
- -h, --help Show this help
2148
-
2149
- Manager options (one or two-letter style):
2150
- -ap, --apt Use APT
2151
- -dn, --dnf Use DNF
2152
- -pm, --pacman Use Pacman
2153
- -zy, --zypper Use Zypper
2154
- -em, --emerge Use Portage (emerge)
2155
- -br, --brew Use Homebrew
2156
- -wg, --winget Use WinGet
2157
- -ch, --choco Use Chocolatey
2158
- -sc, --scoop Use Scoop
2159
- -sn, --snap Use Snap
2160
- -fp, --flatpak Use Flatpak
2161
- -np, --npm Use npm (global packages)
2162
- -bn, --bun Use bun (global packages)
2163
- -ad, --auto Force auto-detection mode
2164
-
2165
- Examples:
2166
- ${SCRIPT_NAME} docker
2167
- ${SCRIPT_NAME} -dn nginx
2168
- ${SCRIPT_NAME} -ap -l openssl
2169
- ${SCRIPT_NAME} -br -R wget
2170
- ${SCRIPT_NAME} -sn firefox
2171
- ${SCRIPT_NAME} -fp org.gimp.GIMP
2172
- ${SCRIPT_NAME} -wg Microsoft.VisualStudioCode
2173
- ${SCRIPT_NAME} -np eslint
2174
- ${SCRIPT_NAME} -ch git
2175
- ${SCRIPT_NAME} -m apt ripgrep
2176
-
2177
- Supported managers:
2178
- $(manager_list)
2179
- EOF
2180
- }
2181
-
2182
- build_keybind_file() {
2183
- cat >"${KBINDS_FILE}" <<'EOF'
2184
- Keybinds:
2185
-
2186
- ctrl-h Show help in preview pane
2187
- ctrl-k Show keybinds in preview pane
2188
- ctrl-/ Toggle preview pane
2189
- ctrl-n Move to next selected package
2190
- ctrl-b Move to previous selected package
2191
- EOF
2192
- }
2193
-
2194
- print_help() {
2195
- cat "${HELP_FILE}"
2196
- }
2197
-
2198
- print_version() {
2199
- printf "%s %s\n" "${SCRIPT_NAME}" "${SCRIPT_VERSION}"
2200
- }
2201
-
2202
- parse_args() {
2203
- local manager_value=""
2204
-
2205
- while (($#)); do
2206
- case "$1" in
2207
- -h|--help)
2208
- ACTION="help"
2209
- ;;
2210
- -v|--version)
2211
- ACTION="version"
2212
- ;;
2213
- -l|--list-installed)
2214
- ACTION="list"
2215
- ;;
2216
- -R|--remove)
2217
- ACTION="remove"
2218
- ;;
2219
- -U|--update)
2220
- ACTION="update"
2221
- ;;
2222
- --refresh)
2223
- ACTION="refresh"
2224
- ;;
2225
- -y|--yes)
2226
- ASSUME_YES="1"
2227
- ;;
2228
- --feed-search)
2229
- ACTION="feed-search"
2230
- ;;
2231
- --dynamic-reload)
2232
- ACTION="dynamic-reload"
2233
- ;;
2234
- --ipc-reload)
2235
- ACTION="ipc-reload"
2236
- ;;
2237
- --ipc-query-notify)
2238
- ACTION="ipc-query-notify"
2239
- ;;
2240
- --preview-item)
2241
- ACTION="preview-item"
2242
- ;;
2243
- --bun-refresh-worker)
2244
- ACTION="bun-refresh-worker"
2245
- ;;
2246
- -ap|--apt)
2247
- MANAGER_OVERRIDE="apt"
2248
- ;;
2249
- -dn|--dnf)
2250
- MANAGER_OVERRIDE="dnf"
2251
- ;;
2252
- -pm|--pacman)
2253
- MANAGER_OVERRIDE="pacman"
2254
- ;;
2255
- -zy|--zypper)
2256
- MANAGER_OVERRIDE="zypper"
2257
- ;;
2258
- -em|--emerge)
2259
- MANAGER_OVERRIDE="emerge"
2260
- ;;
2261
- -br|--brew)
2262
- MANAGER_OVERRIDE="brew"
2263
- ;;
2264
- -wg|--winget)
2265
- MANAGER_OVERRIDE="winget"
2266
- ;;
2267
- -ch|--choco)
2268
- MANAGER_OVERRIDE="choco"
2269
- ;;
2270
- -sc|--scoop)
2271
- MANAGER_OVERRIDE="scoop"
2272
- ;;
2273
- -sn|--snap)
2274
- MANAGER_OVERRIDE="snap"
2275
- ;;
2276
- -fp|--flatpak)
2277
- MANAGER_OVERRIDE="flatpak"
2278
- ;;
2279
- -np|--npm)
2280
- MANAGER_OVERRIDE="npm"
2281
- ;;
2282
- -bn|--bun)
2283
- MANAGER_OVERRIDE="bun"
2284
- ;;
2285
- -ad|--auto)
2286
- MANAGER_OVERRIDE=""
2287
- ;;
2288
- -m|--manager)
2289
- shift
2290
- [[ $# -gt 0 ]] || die "Missing value for --manager"
2291
- manager_value="$(trim_whitespace "$1")"
2292
- [[ -n "${manager_value}" ]] || die "Missing value for --manager"
2293
- [[ "${manager_value}" != -* ]] || die "Missing value for --manager"
2294
- MANAGER_OVERRIDE="$(normalize_manager "${manager_value}")"
2295
- ;;
2296
- -m=*|--manager=*)
2297
- manager_value="$(trim_whitespace "${1#*=}")"
2298
- [[ -n "${manager_value}" ]] || die "Missing value for --manager"
2299
- [[ "${manager_value}" != -* ]] || die "Missing value for --manager"
2300
- MANAGER_OVERRIDE="$(normalize_manager "${manager_value}")"
2301
- ;;
2302
- --)
2303
- shift
2304
- while (($#)); do
2305
- QUERY_PARTS+=("$1")
2306
- shift
2307
- done
2308
- break
2309
- ;;
2310
- -*)
2311
- die "Invalid option: $1"
2312
- ;;
2313
- *)
2314
- QUERY_PARTS+=("$1")
2315
- ;;
2316
- esac
2317
- shift
2318
- done
2319
- }
2320
-
2321
- join_query() {
2322
- local query=""
2323
- local part
2324
-
2325
- if (( ${#QUERY_PARTS[@]} == 0 )); then
2326
- printf ""
2327
- return
2328
- fi
2329
-
2330
- for part in "${QUERY_PARTS[@]-}"; do
2331
- if [[ -z "${query}" ]]; then
2332
- query="${part}"
2333
- else
2334
- query+=" ${part}"
2335
- fi
2336
- done
2337
- printf "%s" "${query}"
2338
- }
2339
-
2340
- dynamic_reload_enabled() {
2341
- local manager_count="$1"
2342
- local mode="${FPF_DYNAMIC_RELOAD:-always}"
2343
-
2344
- case "${mode}" in
2345
- always|auto|on|1|true|yes)
2346
- return 0
2347
- ;;
2348
- never|off|0|false|no)
2349
- return 1
2350
- ;;
2351
- single)
2352
- [[ "${manager_count}" -eq 1 ]]
2353
- ;;
2354
- *)
2355
- return 0
2356
- ;;
2357
- esac
2358
- }
2359
-
2360
- dynamic_reload_use_ipc() {
2361
- local transport="${FPF_DYNAMIC_RELOAD_TRANSPORT:-reload}"
2362
-
2363
- case "${transport}" in
2364
- ipc|listen|http)
2365
- fzf_supports_listen
2366
- ;;
2367
- auto)
2368
- fzf_supports_listen
2369
- ;;
2370
- *)
2371
- return 1
2372
- ;;
2373
- esac
2374
- }
2375
-
2376
- rank_display_rows_by_query() {
2377
- local query="$1"
2378
- local input_file="$2"
2379
- local ranked_file
2380
- local has_exact=0
2381
- local candidate
2382
-
2383
- if [[ "${FPF_USE_GO_DISPLAY_PIPELINE:-0}" == "1" && -n "${FPF_SELF_PATH:-}" && -x "${FPF_SELF_PATH}" ]]; then
2384
- "${FPF_SELF_PATH}" --go-rank-display --go-query "${query}" --go-input "${input_file}"
2385
- return $?
2386
- fi
2387
-
2388
- [[ -n "${query}" ]] || return 0
2389
-
2390
- while IFS= read -r candidate; do
2391
- [[ -n "${candidate}" ]] || continue
2392
- if awk -F'\t' -v cand="${candidate}" 'tolower($2) == tolower(cand) { found=1; exit } END { exit (found ? 0 : 1) }' "${input_file}"; then
2393
- has_exact=1
2394
- break
2395
- fi
2396
- done < <(exact_query_candidates "${query}" | awk '!seen[$0]++')
2397
-
2398
- ranked_file="$(mktemp "${SESSION_TMP_ROOT}/ranked.XXXXXX")"
2399
-
2400
- awk -F'\t' -v query="${query}" -v has_exact="${has_exact}" '
2401
- function lower(s) { return tolower(s) }
2402
- function normalize(s, t) {
2403
- t = lower(s)
2404
- gsub(/[^[:alnum:]]+/, "", t)
2405
- return t
2406
- }
2407
- BEGIN {
2408
- q = lower(query)
2409
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", q)
2410
- q_norm = normalize(q)
2411
-
2412
- token_total = split(q, raw_tokens, /[^[:alnum:]]+/)
2413
- query_token_count = 0
2414
- for (i = 1; i <= token_total; i++) {
2415
- if (raw_tokens[i] != "") {
2416
- query_tokens[++query_token_count] = raw_tokens[i]
2417
- }
2418
- }
2419
- }
2420
- {
2421
- mgr = lower($1)
2422
- pkg = $2
2423
- desc = $3
2424
-
2425
- pkg_l = lower(pkg)
2426
- desc_l = lower(desc)
2427
- pkg_norm = normalize(pkg_l)
2428
- desc_norm = normalize(desc_l)
2429
-
2430
- pkg_token_hits = 0
2431
- desc_token_hits = 0
2432
- for (i = 1; i <= query_token_count; i++) {
2433
- token = query_tokens[i]
2434
- if (index(pkg_l, token) > 0 || index(pkg_norm, token) > 0) {
2435
- pkg_token_hits++
2436
- }
2437
- if (index(desc_l, token) > 0 || index(desc_norm, token) > 0) {
2438
- desc_token_hits++
2439
- }
2440
- }
2441
-
2442
- pkg_token_count = 0
2443
- split(pkg_l, pkg_parts, /[^[:alnum:]]+/)
2444
- for (i in pkg_parts) {
2445
- if (pkg_parts[i] != "") {
2446
- pkg_token_count++
2447
- }
2448
- }
2449
-
2450
- score = 12
2451
- if (q != "") {
2452
- if (pkg_l == q || (q_norm != "" && pkg_norm == q_norm)) {
2453
- score = 0
2454
- } else if (q_norm != "" && index(pkg_norm, q_norm) == 1) {
2455
- score = 1
2456
- } else if (query_token_count > 1 && pkg_token_hits == query_token_count) {
2457
- score = 2
2458
- } else if (q_norm != "" && index(pkg_norm, q_norm) > 0) {
2459
- score = 3
2460
- } else if (query_token_count > 0 && pkg_token_hits > 0) {
2461
- score = 4
2462
- } else if (query_token_count > 0 && desc_token_hits == query_token_count) {
2463
- score = 5
2464
- } else if (query_token_count > 0 && desc_token_hits > 0) {
2465
- score = 6
2466
- }
2467
- }
2468
-
2469
- if (q != "" && has_exact == 1 && pkg_token_hits == query_token_count && query_token_count > 0 && pkg_token_count > query_token_count) {
2470
- score += 5
2471
- }
2472
-
2473
- if (q != "" && desc_l ~ /(plugin|template|starter|boilerplate|router|hooks?|mcp|integration)/) {
2474
- score += 2
2475
- }
2476
-
2477
- mgr_bias = 0
2478
- if (mgr == "npm") {
2479
- mgr_bias = 4
2480
- } else if (mgr == "bun") {
2481
- mgr_bias = 3
2482
- }
2483
-
2484
- print score "\t" mgr_bias "\t" (99 - pkg_token_hits) "\t" (99 - desc_token_hits) "\t" length(pkg) "\t" pkg_l "\t" $0
2485
- }
2486
- ' "${input_file}" |
2487
- sort -t $'\t' -k1,1n -k2,2n -k3,3n -k4,4n -k5,5n -k6,6 |
2488
- awk -F'\t' '{ print $7 "\t" $8 "\t" $9 }' >"${ranked_file}"
2489
-
2490
- mv "${ranked_file}" "${input_file}"
2491
- }
2492
-
2493
- exact_query_candidates() {
2494
- local query="$1"
2495
- local compact_query=""
2496
- local dashed_query=""
2497
- local underscored_query=""
2498
- local nospace_query=""
2499
- local -a query_tokens=()
2500
-
2501
- read -r -a query_tokens <<<"${query}"
2502
- compact_query="${query_tokens[*]}"
2503
- [[ -n "${compact_query}" ]] || return 0
2504
-
2505
- printf "%s\n" "${compact_query}"
2506
-
2507
- if [[ "${compact_query}" == *[[:space:]]* ]]; then
2508
- dashed_query="${compact_query// /-}"
2509
- underscored_query="${compact_query// /_}"
2510
- nospace_query="${compact_query// /}"
2511
- printf "%s\n" "${dashed_query}"
2512
- printf "%s\n" "${underscored_query}"
2513
- printf "%s\n" "${nospace_query}"
2514
- fi
2515
- }
2516
-
2517
- manager_bun_search_entries_strict() {
2518
- local query="$1"
2519
- local effective_query="${query}"
2520
- local line_limit=0
2521
- local query_line_limit=40
2522
- local effective_limit=0
2523
- local bun_search_file
2524
-
2525
- if [[ -z "${query}" ]]; then
2526
- line_limit="${FPF_NO_QUERY_RESULT_LIMIT:-120}"
2527
- else
2528
- query_line_limit="${FPF_QUERY_PER_MANAGER_LIMIT:-40}"
2529
- fi
2530
-
2531
- if ! [[ "${line_limit}" =~ ^[0-9]+$ ]]; then
2532
- line_limit=0
2533
- fi
2534
-
2535
- if ! [[ "${query_line_limit}" =~ ^[0-9]+$ ]]; then
2536
- query_line_limit=40
2537
- fi
2538
-
2539
- if [[ "${line_limit}" -gt 0 ]]; then
2540
- effective_limit="${line_limit}"
2541
- else
2542
- effective_limit="${query_line_limit}"
2543
- fi
2544
-
2545
- if [[ -z "${effective_query}" ]]; then
2546
- effective_query="aa"
2547
- fi
2548
-
2549
- bun_search_file="$(mktemp "${SESSION_TMP_ROOT}/bun-search-strict.XXXXXX")"
2550
- if ! bun search "${effective_query}" >"${bun_search_file}" 2>/dev/null; then
2551
- rm -f "${bun_search_file}"
2552
- return 1
2553
- fi
2554
-
2555
- {
2556
- awk 'NR > 1 && NF > 0 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }' "${bun_search_file}"
2557
- } | awk -F'\t' 'NF >= 1 { if ($2 == "") $2 = "-"; print $1 "\t" $2 }' | awk -F'\t' '!seen[$1]++' | {
2558
- if [[ "${effective_limit}" -gt 0 ]]; then
2559
- awk -v limit="${effective_limit}" 'NR <= limit'
2560
- else
2561
- cat
2562
- fi
2563
- }
2564
-
2565
- rm -f "${bun_search_file}"
2566
- }
2567
-
2568
- manager_search_entries_uncached() {
2569
- local manager="$1"
2570
- local query="$2"
2571
- local effective_query="${query}"
2572
- local npm_search_limit=500
2573
- local line_limit=0
2574
- local query_line_limit=40
2575
- local effective_limit=0
2576
-
2577
- if [[ -z "${query}" ]]; then
2578
- npm_search_limit="${FPF_NO_QUERY_NPM_LIMIT:-120}"
2579
- line_limit="${FPF_NO_QUERY_RESULT_LIMIT:-120}"
2580
- else
2581
- query_line_limit="${FPF_QUERY_PER_MANAGER_LIMIT:-40}"
2582
- fi
2583
-
2584
- if ! [[ "${npm_search_limit}" =~ ^[0-9]+$ ]] || [[ "${npm_search_limit}" -eq 0 ]]; then
2585
- npm_search_limit=500
2586
- fi
2587
-
2588
- if ! [[ "${line_limit}" =~ ^[0-9]+$ ]]; then
2589
- line_limit=0
2590
- fi
2591
-
2592
- if ! [[ "${query_line_limit}" =~ ^[0-9]+$ ]]; then
2593
- query_line_limit=40
2594
- fi
2595
-
2596
- if [[ "${line_limit}" -gt 0 ]]; then
2597
- effective_limit="${line_limit}"
2598
- else
2599
- effective_limit="${query_line_limit}"
2600
- fi
2601
-
2602
- if [[ -z "${effective_query}" ]]; then
2603
- case "${manager}" in
2604
- apt|dnf|pacman|zypper|emerge|choco|scoop|snap)
2605
- effective_query="a"
2606
- ;;
2607
- brew|npm|bun)
2608
- effective_query="aa"
2609
- ;;
2610
- winget)
2611
- effective_query="aa"
2612
- ;;
2613
- esac
2614
- fi
2615
-
2616
- if [[ "${FPF_USE_GO_SEARCH_ENTRIES:-0}" == "1" && -n "${FPF_SELF_PATH:-}" && -x "${FPF_SELF_PATH}" ]]; then
2617
- case "${manager}" in
2618
- apt|brew)
2619
- ;;
2620
- flatpak)
2621
- if [[ -z "${query}" ]]; then
2622
- :
2623
- else
2624
- "${FPF_SELF_PATH}" \
2625
- --go-search-entries \
2626
- --go-manager "${manager}" \
2627
- --go-query "${effective_query}" \
2628
- --go-limit "${effective_limit}" \
2629
- --go-npm-search-limit "${npm_search_limit}"
2630
- return 0
2631
- fi
2632
- ;;
2633
- *)
2634
- "${FPF_SELF_PATH}" \
2635
- --go-search-entries \
2636
- --go-manager "${manager}" \
2637
- --go-query "${effective_query}" \
2638
- --go-limit "${effective_limit}" \
2639
- --go-npm-search-limit "${npm_search_limit}"
2640
- return 0
2641
- ;;
2642
- esac
2643
- fi
2644
-
2645
- case "${manager}" in
2646
- apt)
2647
- if search_entries_from_catalog_cache "${manager}" "${effective_query}"; then
2648
- :
2649
- elif [[ -n "${query}" ]] && search_catalog_async_prewarm_enabled; then
2650
- start_search_catalog_prewarm_async "${manager}"
2651
- apt-cache search -- "${effective_query}" 2>/dev/null |
2652
- awk -F' - ' '{ name=$1; desc=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", name); if (desc == "") desc="-"; print name "\t" desc }'
2653
- elif ensure_search_catalog_cache "${manager}"; then
2654
- search_entries_from_catalog_cache "${manager}" "${effective_query}" || true
2655
- else
2656
- apt-cache search -- "${effective_query}" 2>/dev/null |
2657
- awk -F' - ' '{ name=$1; desc=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", name); if (desc == "") desc="-"; print name "\t" desc }'
2658
- fi
2659
- ;;
2660
- dnf)
2661
- local pattern="*"
2662
- if [[ -n "${effective_query}" ]]; then
2663
- pattern="*${effective_query}*"
2664
- fi
2665
- dnf -q list available "${pattern}" 2>/dev/null |
2666
- awk 'NR > 1 && $1 !~ /^(Available|Last|Installed)/ { name=$1; sub(/\.[^.]+$/, "", name); print name "\t" $2 }'
2667
- ;;
2668
- pacman)
2669
- pacman -Ss -- "${effective_query}" 2>/dev/null |
2670
- awk '
2671
- NR % 2 == 1 {
2672
- split($1, parts, "/")
2673
- pkg = parts[2]
2674
- next
2675
- }
2676
- NR % 2 == 0 {
2677
- line = $0
2678
- sub(/^[[:space:]]+/, "", line)
2679
- if (pkg != "") print pkg "\t" line
2680
- }
2681
- '
2682
- ;;
2683
- zypper)
2684
- zypper --non-interactive --quiet search --details --type package "${effective_query}" 2>/dev/null |
2685
- awk -F'|' '
2686
- /^[[:space:]]*[ivp ][[:space:]]*\|/ {
2687
- name=$3
2688
- ver=$5
2689
- repo=$7
2690
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
2691
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", ver)
2692
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", repo)
2693
- if (name != "") print name "\tversion " ver " from " repo
2694
- }
2695
- '
2696
- ;;
2697
- emerge)
2698
- emerge --searchdesc --color=n "${effective_query}" 2>/dev/null |
2699
- awk '
2700
- /^\* / {
2701
- atom=$2
2702
- desc="-"
2703
- next
2704
- }
2705
- /^[[:space:]]+Description:/ {
2706
- line=$0
2707
- sub(/^[[:space:]]+Description:[[:space:]]*/, "", line)
2708
- desc=line
2709
- if (atom != "") print atom "\t" desc
2710
- atom=""
2711
- }
2712
- '
2713
- ;;
2714
- brew)
2715
- if search_entries_from_catalog_cache "${manager}" "${effective_query}"; then
2716
- :
2717
- elif [[ -n "${query}" ]] && search_catalog_async_prewarm_enabled; then
2718
- start_search_catalog_prewarm_async "${manager}"
2719
- brew search "${effective_query}" 2>/dev/null |
2720
- awk 'NF >= 1 { print $1 "\t-" }'
2721
- elif ensure_search_catalog_cache "${manager}"; then
2722
- search_entries_from_catalog_cache "${manager}" "${effective_query}" || true
2723
- else
2724
- brew search "${effective_query}" 2>/dev/null |
2725
- awk 'NF > 0 && $1 != "==>" { print $1 "\t-" }'
2726
- fi
2727
- ;;
2728
- winget)
2729
- winget search "${effective_query}" --source winget --accept-source-agreements --disable-interactivity 2>/dev/null |
2730
- awk '
2731
- /^[[:space:]]*$/ { next }
2732
- /^Name[[:space:]]+Id[[:space:]]+/ { next }
2733
- /^[-[:space:]]+$/ { next }
2734
- {
2735
- line = $0
2736
- sub(/^[[:space:]]+/, "", line)
2737
- pkg = ""
2738
- n = split(line, cols, /[[:space:]][[:space:]]+/)
2739
- if (n >= 2) {
2740
- pkg = cols[2]
2741
- }
2742
- if (pkg != "") {
2743
- print pkg "\t-"
2744
- }
2745
- }
2746
- '
2747
- ;;
2748
- choco)
2749
- choco search "${effective_query}" --limit-output 2>/dev/null |
2750
- awk -F'|' 'NF >= 1 && $1 != "" { ver=$2; if (ver == "") ver="-"; print $1 "\tversion " ver }'
2751
- ;;
2752
- scoop)
2753
- scoop search "${effective_query}" 2>/dev/null |
2754
- awk '
2755
- /^[[:space:]]*$/ { next }
2756
- /^[-[:space:]]+$/ { next }
2757
- /^Name[[:space:]]+/ { next }
2758
- {
2759
- name = $1
2760
- $1 = ""
2761
- sub(/^[[:space:]]+/, "", $0)
2762
- if ($0 == "") $0 = "-"
2763
- print name "\t" $0
2764
- }
2765
- '
2766
- ;;
2767
- snap)
2768
- snap find "${effective_query}" 2>/dev/null |
2769
- awk '
2770
- NR == 1 { next }
2771
- NF > 0 {
2772
- name=$1
2773
- $1=""
2774
- sub(/^[[:space:]]+/, "", $0)
2775
- if ($0 == "") $0 = "-"
2776
- print name "\t" $0
2777
- }
2778
- '
2779
- ;;
2780
- flatpak)
2781
- ensure_flatpak_flathub_remote >/dev/null 2>&1 || true
2782
- if [[ -z "${effective_query}" ]]; then
2783
- {
2784
- flatpak remote-ls --app --columns=application,description flathub 2>/dev/null ||
2785
- flatpak remote-ls --app --columns=application,description 2>/dev/null
2786
- } |
2787
- awk 'NR > 1 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }'
2788
- else
2789
- {
2790
- flatpak search --columns=application,description "${effective_query}" 2>/dev/null ||
2791
- flatpak search "${effective_query}" 2>/dev/null
2792
- } |
2793
- awk 'NR > 1 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }'
2794
- fi
2795
- ;;
2796
- npm)
2797
- npm search "${effective_query}" --searchlimit="${npm_search_limit}" --parseable 2>/dev/null |
2798
- awk -F'\t' 'NF >= 2 { print $1 "\t" $2 }'
2799
- ;;
2800
- bun)
2801
- {
2802
- local bun_search_file
2803
- bun_search_file="$(mktemp "${SESSION_TMP_ROOT}/bun-search.XXXXXX")"
2804
- if bun search "${effective_query}" >"${bun_search_file}" 2>/dev/null; then
2805
- awk 'NR > 1 && NF > 0 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }' "${bun_search_file}"
2806
- elif command_exists npm; then
2807
- npm search "${effective_query}" --searchlimit="${npm_search_limit}" --parseable 2>/dev/null |
2808
- awk -F'\t' 'NF >= 2 { print $1 "\t" $2 }'
2809
- fi
2810
- rm -f "${bun_search_file}"
2811
- } || true
2812
- ;;
2813
- esac | awk -F'\t' 'NF >= 1 { if ($2 == "") $2 = "-"; print $1 "\t" $2 }' | awk -F'\t' '!seen[$1]++' | {
2814
- if [[ "${effective_limit}" -gt 0 ]]; then
2815
- awk -v limit="${effective_limit}" 'NR <= limit'
2816
- else
2817
- cat
2818
- fi
2819
- } || true
2820
- }
2821
-
2822
- manager_search_entries() {
2823
- local manager="$1"
2824
- local query="$2"
2825
- local query_cache_enabled="0"
2826
- local query_cache_setting="${FPF_ENABLE_QUERY_CACHE:-auto}"
2827
- local query_cache_ttl="0"
2828
- local bun_cache_ttl="900"
2829
- local bypass_query_cache="${FPF_BYPASS_QUERY_CACHE:-0}"
2830
- local flags
2831
- local key
2832
- local fingerprint
2833
- local cache_file
2834
- local output_tmp
2835
- local generation
2836
- local should_async_refresh=0
2837
-
2838
- initialize_cache_root
2839
-
2840
- flags="$(query_cache_flags)"
2841
- query_cache_setting="$(trim_whitespace "${query_cache_setting}")"
2842
- query_cache_setting="${query_cache_setting,,}"
2843
- query_cache_ttl="$(query_cache_ttl_seconds_for_manager "${manager}")"
2844
-
2845
- if [[ "${manager}" == "bun" ]]; then
2846
- bun_cache_ttl="${query_cache_ttl}"
2847
- fi
2848
-
2849
- case "${bypass_query_cache}" in
2850
- 1|true|yes|on)
2851
- ;;
2852
- *)
2853
- case "${query_cache_setting}" in
2854
- 1|true|yes|on)
2855
- query_cache_enabled="1"
2856
- ;;
2857
- auto|"")
2858
- if query_cache_default_enabled_for_manager "${manager}"; then
2859
- query_cache_enabled="1"
2860
- fi
2861
- ;;
2862
- esac
2863
- ;;
2864
- esac
2865
-
2866
- key="$(cache_query_key "${manager}" "${query}" "${flags}")"
2867
- fingerprint="$(cache_fingerprint "${manager}" "${query}" "${flags}")"
2868
- cache_file="$(cache_path_for_key "${key}")"
2869
-
2870
- if [[ "${manager}" == "bun" && "${query_cache_enabled}" == "1" && ( "${ACTION}" == "feed-search" || "${ACTION}" == "ipc-query-notify" ) ]]; then
2871
- if [[ -s "${cache_file}" ]]; then
2872
- cache_emit_query_rows_if_valid "${cache_file}" || true
2873
- fi
2874
-
2875
- if cache_is_fresh_with_ttl "${key}" "${bun_cache_ttl}"; then
2876
- return
2877
- fi
2878
-
2879
- generation="$(bun_generation_next "${key}")"
2880
- if [[ -n "${FZF_PORT:-}" && "${FPF_BUN_SKIP_REFRESH_SCHEDULE:-0}" != "1" ]]; then
2881
- should_async_refresh=1
2882
- fi
2883
-
2884
- if [[ "${FPF_BUN_TEST_SYNC_REFRESH:-0}" == "1" ]]; then
2885
- should_async_refresh=0
2886
- fi
2887
-
2888
- if [[ "${should_async_refresh}" -eq 1 ]]; then
2889
- start_bun_refresh_worker_async "${manager}" "${query}" "${flags}" "${key}" "${fingerprint}" "${generation}" "${FPF_IPC_FALLBACK_FILE:-}" "${FPF_IPC_MANAGER_OVERRIDE:-}"
2890
- return
2891
- fi
2892
-
2893
- bun_run_refresh_worker "${manager}" "${query}" "${flags}" "${key}" "${fingerprint}" "${generation}" || true
2894
- if [[ -s "${cache_file}" ]]; then
2895
- cache_emit_query_rows_if_valid "${cache_file}" || true
2896
- fi
2897
- return
2898
- fi
2899
-
2900
- if [[ "${query_cache_enabled}" == "1" && -s "${cache_file}" ]]; then
2901
- if [[ "${query_cache_ttl}" -eq 0 ]] || cache_is_fresh_with_ttl "${key}" "${query_cache_ttl}"; then
2902
- cat "${cache_file}"
2903
- return
2904
- fi
2905
- fi
2906
-
2907
- output_tmp="$(mktemp "${SESSION_TMP_ROOT}/query-cache.XXXXXX")"
2908
- manager_search_entries_uncached "${manager}" "${query}" >"${output_tmp}" || true
2909
-
2910
- if [[ "${query_cache_enabled}" == "1" ]]; then
2911
- cache_store_key_from_file "${key}" "${fingerprint}" "${output_tmp}"
2912
- fi
2913
-
2914
- cat "${output_tmp}"
2915
- rm -f "${output_tmp}"
2916
- }
2917
-
2918
- manager_installed_entries() {
2919
- local manager="$1"
2920
-
2921
- if [[ "${FPF_USE_GO_INSTALLED_ENTRIES:-0}" == "1" && -n "${FPF_SELF_PATH:-}" && -x "${FPF_SELF_PATH}" ]]; then
2922
- "${FPF_SELF_PATH}" --go-installed-entries --go-manager "${manager}"
2923
- return $?
2924
- fi
2925
-
2926
- case "${manager}" in
2927
- apt)
2928
- dpkg-query -W -f='${binary:Package}\t${Version}\n' 2>/dev/null
2929
- ;;
2930
- dnf)
2931
- dnf -q list installed 2>/dev/null |
2932
- awk 'NR > 1 && $1 !~ /^(Installed|Last)/ { name=$1; sub(/\.[^.]+$/, "", name); print name "\t" $2 }'
2933
- ;;
2934
- pacman)
2935
- pacman -Q 2>/dev/null |
2936
- awk '{ print $1 "\t" $2 }'
2937
- ;;
2938
- zypper)
2939
- zypper --non-interactive --quiet search --installed-only --details --type package 2>/dev/null |
2940
- awk -F'|' '
2941
- /^[[:space:]]*i[[:space:]]*\|/ {
2942
- name=$3
2943
- ver=$5
2944
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
2945
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", ver)
2946
- if (name != "") print name "\t" ver
2947
- }
2948
- '
2949
- ;;
2950
- emerge)
2951
- if command_exists qlist; then
2952
- qlist -ICv 2>/dev/null |
2953
- awk '{ print $1 "\tinstalled" }'
2954
- else
2955
- local pkg_dir
2956
- for pkg_dir in /var/db/pkg/*/*; do
2957
- [[ -d "${pkg_dir}" ]] || continue
2958
- printf "%s\tinstalled\n" "$(basename "${pkg_dir}")"
2959
- done
2960
- fi
2961
- ;;
2962
- brew)
2963
- brew list --versions 2>/dev/null |
2964
- awk '{ name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="installed"; print name "\t" $0 }'
2965
- ;;
2966
- winget)
2967
- winget list --source winget --accept-source-agreements --disable-interactivity 2>/dev/null |
2968
- awk '
2969
- /^[[:space:]]*$/ { next }
2970
- /^Name[[:space:]]+Id[[:space:]]+/ { next }
2971
- /^[-[:space:]]+$/ { next }
2972
- {
2973
- line = $0
2974
- sub(/^[[:space:]]+/, "", line)
2975
-
2976
- pkg = ""
2977
- ver = "installed"
2978
-
2979
- n = split(line, cols, /[[:space:]][[:space:]]+/)
2980
- if (n >= 2) {
2981
- pkg = cols[2]
2982
- }
2983
- if (n >= 3 && cols[3] != "") {
2984
- ver = cols[3]
2985
- }
2986
-
2987
- if (pkg != "") {
2988
- print pkg "\t" ver
2989
- }
2990
- }
2991
- '
2992
- ;;
2993
- choco)
2994
- choco list --local-only --limit-output 2>/dev/null |
2995
- awk -F'|' 'NF >= 1 && $1 != "" { ver=$2; if (ver == "") ver="installed"; print $1 "\t" ver }'
2996
- ;;
2997
- scoop)
2998
- scoop list 2>/dev/null |
2999
- awk '
3000
- /^[[:space:]]*$/ { next }
3001
- /^[-[:space:]]+$/ { next }
3002
- /^Name[[:space:]]+/ { next }
3003
- {
3004
- ver = $2
3005
- if (ver == "") ver = "installed"
3006
- print $1 "\t" ver
3007
- }
3008
- '
3009
- ;;
3010
- snap)
3011
- snap list 2>/dev/null |
3012
- awk 'NR > 1 { print $1 "\t" $2 }'
3013
- ;;
3014
- flatpak)
3015
- flatpak list --app --columns=application,version 2>/dev/null |
3016
- awk 'NR > 1 { print $1 "\t" $2 }'
3017
- ;;
3018
- npm)
3019
- npm ls -g --depth=0 --parseable 2>/dev/null |
3020
- awk '
3021
- NR > 1 {
3022
- line = $0
3023
- gsub(/\r/, "", line)
3024
- gsub(/\\/, "/", line)
3025
-
3026
- n = split(line, parts, "/")
3027
- if (n < 1) next
3028
-
3029
- pkg = parts[n]
3030
- if (pkg == "" && n > 1) pkg = parts[n - 1]
3031
- if (pkg == "") next
3032
-
3033
- if (n >= 2 && parts[n - 1] ~ /^@/) {
3034
- pkg = parts[n - 1] "/" pkg
3035
- }
3036
-
3037
- print pkg "\tglobal"
3038
- }
3039
- '
3040
- ;;
3041
- bun)
3042
- {
3043
- local bun_pm_file
3044
- bun_pm_file="$(mktemp "${SESSION_TMP_ROOT}/bun-pm.XXXXXX")"
3045
- if bun pm ls --global >"${bun_pm_file}" 2>/dev/null; then
3046
- awk '
3047
- NR > 1 {
3048
- line = $0
3049
- gsub(/\r/, "", line)
3050
- sub(/^[[:space:]]*[^[:alnum:]@]*/, "", line)
3051
- sub(/^[[:space:]]+/, "", line)
3052
- sub(/[[:space:]]+$/, "", line)
3053
-
3054
- if (line == "") next
3055
- if (line ~ /[[:space:]]node_modules([[:space:]]|$)/) next
3056
-
3057
- split(line, fields, /[[:space:]]+/)
3058
- pkg = fields[1]
3059
- if (pkg == "") next
3060
-
3061
- pkg_copy = pkg
3062
- at_count = gsub(/@/, "@", pkg_copy)
3063
- if ((substr(pkg, 1, 1) == "@" && at_count >= 2) || (substr(pkg, 1, 1) != "@" && at_count >= 1)) {
3064
- sub(/@[^@[:space:]]*$/, "", pkg)
3065
- }
3066
-
3067
- if (pkg != "") {
3068
- print pkg "\tglobal"
3069
- }
3070
- }
3071
- ' "${bun_pm_file}"
3072
- elif command_exists npm; then
3073
- npm ls -g --depth=0 --parseable 2>/dev/null |
3074
- awk '
3075
- NR > 1 {
3076
- line = $0
3077
- gsub(/\r/, "", line)
3078
- gsub(/\\/, "/", line)
3079
-
3080
- n = split(line, parts, "/")
3081
- if (n < 1) next
3082
-
3083
- pkg = parts[n]
3084
- if (pkg == "" && n > 1) pkg = parts[n - 1]
3085
- if (pkg == "") next
3086
-
3087
- if (n >= 2 && parts[n - 1] ~ /^@/) {
3088
- pkg = parts[n - 1] "/" pkg
3089
- }
3090
-
3091
- print pkg "\tglobal"
3092
- }
3093
- '
3094
- fi
3095
- rm -f "${bun_pm_file}"
3096
- } || true
3097
- ;;
3098
- esac | sort -u || true
3099
- }
3100
-
3101
- manager_installed_names() {
3102
- local manager="$1"
3103
- manager_installed_entries "${manager}" | awk -F'\t' 'NF > 0 { print $1 }'
3104
- }
3105
-
3106
- installed_cache_ttl_seconds() {
3107
- local ttl_seconds="${FPF_INSTALLED_CACHE_TTL:-300}"
3108
-
3109
- if ! [[ "${ttl_seconds}" =~ ^[0-9]+$ ]]; then
3110
- ttl_seconds="300"
3111
- fi
3112
-
3113
- printf "%s" "${ttl_seconds}"
3114
- }
3115
-
3116
- installed_cache_fingerprint() {
3117
- local manager="$1"
3118
- local command_path=""
3119
- local extra_token=""
3120
-
3121
- case "${manager}" in
3122
- apt)
3123
- command_path="$(command -v dpkg-query 2>/dev/null || printf "missing")"
3124
- ;;
3125
- dnf)
3126
- command_path="$(command -v dnf 2>/dev/null || printf "missing")"
3127
- ;;
3128
- pacman)
3129
- command_path="$(command -v pacman 2>/dev/null || printf "missing")"
3130
- ;;
3131
- zypper)
3132
- command_path="$(command -v zypper 2>/dev/null || printf "missing")"
3133
- ;;
3134
- emerge)
3135
- if command_exists qlist; then
3136
- command_path="$(command -v qlist 2>/dev/null || printf "missing")"
3137
- extra_token="|mode=qlist"
3138
- else
3139
- command_path="/var/db/pkg"
3140
- extra_token="|mode=vardb"
3141
- fi
3142
- ;;
3143
- brew)
3144
- command_path="$(command -v brew 2>/dev/null || printf "missing")"
3145
- ;;
3146
- winget)
3147
- command_path="$(command -v winget 2>/dev/null || printf "missing")"
3148
- ;;
3149
- choco)
3150
- command_path="$(command -v choco 2>/dev/null || printf "missing")"
3151
- ;;
3152
- scoop)
3153
- command_path="$(command -v scoop 2>/dev/null || printf "missing")"
3154
- ;;
3155
- snap)
3156
- command_path="$(command -v snap 2>/dev/null || printf "missing")"
3157
- ;;
3158
- flatpak)
3159
- command_path="$(command -v flatpak 2>/dev/null || printf "missing")"
3160
- ;;
3161
- npm)
3162
- command_path="$(command -v npm 2>/dev/null || printf "missing")"
3163
- ;;
3164
- bun)
3165
- command_path="$(command -v bun 2>/dev/null || printf "missing")"
3166
- if command_exists npm; then
3167
- extra_token="|npm=$(command -v npm 2>/dev/null || printf "missing")"
3168
- fi
3169
- ;;
3170
- *)
3171
- command_path="unknown"
3172
- ;;
3173
- esac
3174
-
3175
- printf "%s|cmd=%s%s" "$(cache_fingerprint "${manager}" "" "installed")" "${command_path}" "${extra_token}"
3176
- }
3177
-
3178
- manager_installed_names_cached() {
3179
- local manager="$1"
3180
- local output_file="$2"
3181
- local cache_enabled="${FPF_DISABLE_INSTALLED_CACHE:-0}"
3182
- local cache_ttl_seconds
3183
- local cache_key
3184
- local cache_file
3185
- local cache_fingerprint_value
3186
- local cached_fingerprint=""
3187
-
3188
- initialize_cache_root
3189
-
3190
- cache_key="$(cache_catalog_key "${manager}")"
3191
- cache_file="$(cache_path_for_key "${cache_key}")"
3192
- cache_ttl_seconds="$(installed_cache_ttl_seconds)"
3193
- cache_fingerprint_value="$(installed_cache_fingerprint "${manager}")"
3194
-
3195
- if [[ "${cache_enabled}" != "1" && -s "${cache_file}" ]]; then
3196
- cached_fingerprint="$(cache_meta_value_for_key "${cache_key}" "fingerprint" 2>/dev/null || true)"
3197
- if [[ "${cached_fingerprint}" == "${cache_fingerprint_value}" ]] && cache_is_fresh_with_ttl "${cache_key}" "${cache_ttl_seconds}"; then
3198
- cp "${cache_file}" "${output_file}"
3199
- return
3200
- fi
3201
- fi
3202
-
3203
- manager_installed_names "${manager}" >"${output_file}" 2>/dev/null || true
3204
-
3205
- if [[ "${cache_enabled}" != "1" && -s "${output_file}" ]]; then
3206
- cache_store_key_from_file "${cache_key}" "${cache_fingerprint_value}" "${output_file}"
3207
- fi
3208
- }
3209
-
3210
- mark_installed_packages() {
3211
- local source_file="$1"
3212
- local output_file="$2"
3213
- local unique_managers_file
3214
- local manager_counts_file
3215
- local installed_file
3216
- local installed_map_file
3217
- local installed_part_file
3218
- local manager_match_count
3219
- local installed_match_count
3220
- local installed_part_files=()
3221
- local gather_pids=()
3222
- local manager
3223
- local gather_pid
3224
-
3225
- unique_managers_file="$(mktemp "${SESSION_TMP_ROOT}/installed-managers.XXXXXX")"
3226
- awk -F'\t' 'NF >= 1 && $1 != "" { print $1 }' "${source_file}" | awk '!seen[$0]++' >"${unique_managers_file}"
3227
-
3228
- manager_counts_file="$(mktemp "${SESSION_TMP_ROOT}/installed-counts.XXXXXX")"
3229
- awk -F'\t' 'NF >= 1 && $1 != "" { counts[$1]++ } END { for (mgr in counts) print mgr "\t" counts[mgr] }' "${source_file}" >"${manager_counts_file}"
3230
-
3231
- if [[ "${FPF_SKIP_INSTALLED_MARKERS:-0}" == "1" ]]; then
3232
- awk -F'\t' '
3233
- {
3234
- desc = $3
3235
- if (desc == "") desc = "-"
3236
- print $1 "\t" $2 "\t " desc
3237
- }
3238
- ' "${source_file}" >"${output_file}"
3239
- while IFS= read -r manager; do
3240
- [[ -n "${manager}" ]] || continue
3241
- loading_progress_mark_done "${manager}" "installed marker check skipped"
3242
- done <"${unique_managers_file}"
3243
- rm -f "${unique_managers_file}" "${manager_counts_file}"
3244
- return
3245
- fi
3246
-
3247
- installed_map_file="$(mktemp "${SESSION_TMP_ROOT}/installed-map.XXXXXX")"
3248
- : >"${installed_map_file}"
3249
-
3250
- while IFS= read -r manager; do
3251
- [[ -n "${manager}" ]] || continue
3252
- installed_part_file="$(mktemp "${SESSION_TMP_ROOT}/installed-map.${manager}.XXXXXX")"
3253
- installed_part_files+=("${installed_part_file}")
3254
-
3255
- (
3256
- manager_match_count="$(awk -F'\t' -v mgr="${manager}" '$1 == mgr { print $2; found=1; exit } END { if (!found) print 0 }' "${manager_counts_file}")"
3257
- if ! [[ "${manager_match_count}" =~ ^[0-9]+$ ]]; then
3258
- manager_match_count=0
3259
- fi
3260
-
3261
- loading_progress_mark_running "${manager}" "mark installed (${manager_match_count})"
3262
- installed_file="$(mktemp "${SESSION_TMP_ROOT}/installed.${manager}.XXXXXX")"
3263
- manager_installed_names_cached "${manager}" "${installed_file}" || true
3264
- installed_match_count=0
3265
- if [[ -s "${installed_file}" ]]; then
3266
- awk -F'\t' -v mgr="${manager}" 'NF > 0 && $1 != "" { print mgr "\t" $1 }' "${installed_file}" >"${installed_part_file}"
3267
- installed_match_count="$(awk 'END { print NR + 0 }' "${installed_part_file}")"
3268
- fi
3269
- rm -f "${installed_file}"
3270
- loading_progress_mark_done "${manager}" "${manager_match_count} matches, ${installed_match_count} installed"
3271
- ) &
3272
- gather_pids+=("$!")
3273
- done <"${unique_managers_file}"
3274
-
3275
- for gather_pid in "${gather_pids[@]-}"; do
3276
- wait "${gather_pid}" || true
3277
- done
3278
-
3279
- for installed_part_file in "${installed_part_files[@]-}"; do
3280
- if [[ -s "${installed_part_file}" ]]; then
3281
- cat "${installed_part_file}" >>"${installed_map_file}"
3282
- fi
3283
- rm -f "${installed_part_file}"
3284
- done
3285
-
3286
- awk -F'\t' '
3287
- FILENAME == ARGV[1] {
3288
- key = $1 "\t" $2
3289
- if ($1 != "" && $2 != "") {
3290
- installed[key] = 1
3291
- }
3292
- next
3293
- }
3294
- {
3295
- key = $1 "\t" $2
3296
- mark = (installed[key] ? "* " : " ")
3297
- desc = $3
3298
- if (desc == "") desc = "-"
3299
- print $1 "\t" $2 "\t" mark desc
3300
- }
3301
- ' "${installed_map_file}" "${source_file}" >"${output_file}"
3302
-
3303
- rm -f "${unique_managers_file}" "${manager_counts_file}"
3304
- rm -f "${installed_map_file}"
3305
- }
3306
-
3307
- merge_search_display_rows() {
3308
- local source_file="$1"
3309
- local output_file="$2"
3310
-
3311
- if [[ "${FPF_USE_GO_DISPLAY_PIPELINE:-0}" == "1" && -n "${FPF_SELF_PATH:-}" && -x "${FPF_SELF_PATH}" ]]; then
3312
- "${FPF_SELF_PATH}" --go-merge-display --go-source "${source_file}" --go-output "${output_file}"
3313
- return $?
3314
- fi
3315
-
3316
- if [[ ! -s "${source_file}" ]]; then
3317
- : >"${output_file}"
3318
- return
3319
- fi
3320
-
3321
- sort -t $'\t' -k1,1 -k2,2 -k3,3 "${source_file}" |
3322
- awk -F'\t' '
3323
- NF >= 2 {
3324
- key = $1 "\t" $2
3325
- if (seen[key]++) {
3326
- next
3327
- }
3328
- desc = $3
3329
- if (desc == "") desc = "-"
3330
- print $1 "\t" $2 "\t" desc
3331
- }
3332
- ' >"${output_file}"
3333
- }
3334
-
3335
- collect_search_display_rows() {
3336
- local query="$1"
3337
- local output_file="$2"
3338
- shift 2
3339
- local managers=("$@")
3340
- local managers_csv=""
3341
- local query_limit="${FPF_QUERY_RESULT_LIMIT:-0}"
3342
-
3343
- if [[ "${FPF_USE_GO_DISPLAY_PIPELINE:-0}" == "1" && -n "${FPF_SELF_PATH:-}" && -x "${FPF_SELF_PATH}" ]]; then
3344
- managers_csv="$(IFS=','; printf '%s' "${managers[*]-}")"
3345
- if "${FPF_SELF_PATH}" --go-build-display --go-query "${query}" --go-output "${output_file}" --go-managers "${managers_csv}"; then
3346
- return 0
3347
- fi
3348
- fi
3349
-
3350
- : >"${output_file}"
3351
-
3352
- local part_files=()
3353
- local gather_pids=()
3354
- local manager
3355
- local part_file
3356
- local gather_pid
3357
- local merged_source_file
3358
- local merged_marked_file
3359
-
3360
- for manager in "${managers[@]-}"; do
3361
- part_file="$(mktemp "${SESSION_TMP_ROOT}/part.XXXXXX")"
3362
- part_files+=("${part_file}")
3363
-
3364
- (
3365
- local local_source_file
3366
- local result_count=0
3367
-
3368
- loading_progress_mark_running "${manager}" "query index"
3369
- local_source_file="$(mktemp "${SESSION_TMP_ROOT}/source.XXXXXX")"
3370
- manager_search_entries "${manager}" "${query}" >"${local_source_file}" || true
3371
- if [[ -s "${local_source_file}" ]]; then
3372
- awk -F'\t' -v mgr="${manager}" '
3373
- NF >= 1 {
3374
- desc = $2
3375
- if (desc == "") desc = "-"
3376
- print mgr "\t" $1 "\t" desc
3377
- }
3378
- ' "${local_source_file}" >"${part_file}"
3379
- fi
3380
- rm -f "${local_source_file}"
3381
-
3382
- if [[ -s "${part_file}" ]]; then
3383
- result_count="$(awk 'END { print NR + 0 }' "${part_file}")"
3384
- fi
3385
- loading_progress_mark_running "${manager}" "${result_count} indexed; mark installed"
3386
- ) &
3387
- gather_pids+=("$!")
3388
- done
3389
-
3390
- for gather_pid in "${gather_pids[@]-}"; do
3391
- wait "${gather_pid}" || true
3392
- done
3393
-
3394
- merged_source_file="$(mktemp "${SESSION_TMP_ROOT}/merged-source.XXXXXX")"
3395
- merged_marked_file="$(mktemp "${SESSION_TMP_ROOT}/merged-marked.XXXXXX")"
3396
-
3397
- for part_file in "${part_files[@]-}"; do
3398
- if [[ -s "${part_file}" ]]; then
3399
- cat "${part_file}" >>"${merged_source_file}"
3400
- fi
3401
- rm -f "${part_file}"
3402
- done
3403
-
3404
- if [[ -s "${merged_source_file}" ]]; then
3405
- merge_search_display_rows "${merged_source_file}" "${merged_marked_file}"
3406
- mark_installed_packages "${merged_marked_file}" "${output_file}"
3407
- rank_display_rows_by_query "${query}" "${output_file}"
3408
-
3409
- if [[ -n "${query}" && "${query_limit}" =~ ^[0-9]+$ && "${query_limit}" -gt 0 ]]; then
3410
- awk -v limit="${query_limit}" 'NR <= limit' "${output_file}" >"${output_file}.limited"
3411
- mv "${output_file}.limited" "${output_file}"
3412
- fi
3413
- else
3414
- for manager in "${managers[@]-}"; do
3415
- loading_progress_mark_done "${manager}" "0 packages matched"
3416
- done
3417
- fi
3418
-
3419
- rm -f "${merged_source_file}" "${merged_marked_file}"
3420
- }
3421
-
3422
- collect_installed_display_rows() {
3423
- local output_file="$1"
3424
- shift
3425
- local managers=("$@")
3426
- local part_files=()
3427
- local gather_pids=()
3428
- local manager
3429
- local part_file
3430
- local gather_pid
3431
-
3432
- : >"${output_file}"
3433
-
3434
- for manager in "${managers[@]-}"; do
3435
- part_file="$(mktemp "${SESSION_TMP_ROOT}/part.XXXXXX")"
3436
- part_files+=("${part_file}")
3437
-
3438
- (
3439
- local local_source_file
3440
- local result_count=0
3441
-
3442
- loading_progress_mark_running "${manager}" "read installed"
3443
- local_source_file="$(mktemp "${SESSION_TMP_ROOT}/source.XXXXXX")"
3444
- manager_installed_entries "${manager}" >"${local_source_file}" || true
3445
- if [[ -s "${local_source_file}" ]]; then
3446
- awk -F'\t' -v mgr="${manager}" '
3447
- NF >= 1 {
3448
- desc = $2
3449
- if (desc == "") desc = "-"
3450
- print mgr "\t" $1 "\t" desc
3451
- }
3452
- ' "${local_source_file}" >"${part_file}"
3453
- fi
3454
- rm -f "${local_source_file}"
3455
-
3456
- if [[ -s "${part_file}" ]]; then
3457
- result_count="$(awk 'END { print NR + 0 }' "${part_file}")"
3458
- fi
3459
- loading_progress_mark_done "${manager}" "${result_count} installed packages"
3460
- ) &
3461
- gather_pids+=("$!")
3462
- done
3463
-
3464
- for gather_pid in "${gather_pids[@]-}"; do
3465
- wait "${gather_pid}" || true
3466
- done
3467
-
3468
- for part_file in "${part_files[@]-}"; do
3469
- if [[ -s "${part_file}" ]]; then
3470
- cat "${part_file}" >>"${output_file}"
3471
- fi
3472
- rm -f "${part_file}"
3473
- done
3474
-
3475
- if [[ -s "${output_file}" ]]; then
3476
- sort -u "${output_file}" -o "${output_file}"
3477
- fi
3478
- }
3479
-
3480
- build_dynamic_reload_command() {
3481
- local manager_override="$1"
3482
- local fallback_file="$2"
3483
- local manager_list_csv="${3:-}"
3484
- local script_path="$(invocation_script_path)"
3485
- local bypass_query_cache="1"
3486
-
3487
- bypass_query_cache="$(dynamic_reload_query_cache_bypass_value)"
3488
-
3489
- printf 'FPF_SKIP_INSTALLED_MARKERS=1 FPF_BYPASS_QUERY_CACHE=%s FPF_IPC_MANAGER_OVERRIDE=%q FPF_IPC_MANAGER_LIST=%q FPF_IPC_FALLBACK_FILE=%q %q --dynamic-reload -- "{q}"' "${bypass_query_cache}" "${manager_override}" "${manager_list_csv}" "${fallback_file}" "${script_path}"
3490
- }
3491
-
3492
- build_dynamic_reload_command_for_query() {
3493
- local manager_override="$1"
3494
- local fallback_file="$2"
3495
- local manager_list_csv="$3"
3496
- local query_value="$4"
3497
- local script_path="$(invocation_script_path)"
3498
- local bypass_query_cache="1"
3499
-
3500
- bypass_query_cache="$(dynamic_reload_query_cache_bypass_value)"
3501
-
3502
- printf 'FPF_SKIP_INSTALLED_MARKERS=1 FPF_BYPASS_QUERY_CACHE=%s FPF_IPC_MANAGER_OVERRIDE=%q FPF_IPC_MANAGER_LIST=%q FPF_IPC_FALLBACK_FILE=%q %q --dynamic-reload -- %q' "${bypass_query_cache}" "${manager_override}" "${manager_list_csv}" "${fallback_file}" "${script_path}" "${query_value}"
3503
- }
3504
-
3505
- build_dynamic_reload_ipc_command() {
3506
- local manager_override="$1"
3507
- local fallback_file="$2"
3508
- local manager_list_csv="${3:-}"
3509
- local script_path="$(invocation_script_path)"
3510
-
3511
- printf 'FPF_IPC_MANAGER_OVERRIDE=%q FPF_IPC_MANAGER_LIST=%q FPF_IPC_FALLBACK_FILE=%q %q --ipc-reload -- "{q}"' "${manager_override}" "${manager_list_csv}" "${fallback_file}" "${script_path}"
3512
- }
3513
-
3514
- build_dynamic_query_notify_ipc_command() {
3515
- local manager_override="$1"
3516
- local fallback_file="$2"
3517
- local manager_list_csv="${3:-}"
3518
- local script_path="$(invocation_script_path)"
3519
-
3520
- printf 'FPF_IPC_MANAGER_OVERRIDE=%q FPF_IPC_MANAGER_LIST=%q FPF_IPC_FALLBACK_FILE=%q %q --ipc-query-notify -- "{q}"' "${manager_override}" "${manager_list_csv}" "${fallback_file}" "${script_path}"
3521
- }
3522
-
3523
- fzf_supports_listen() {
3524
- local fzf_help
3525
- fzf_help="$(fzf --help 2>&1 || true)"
3526
- [[ "${fzf_help}" == *"--listen"* ]]
3527
- }
3528
-
3529
- fzf_supports_result_bind() {
3530
- if [[ -n "${FPF_FZF_RESULT_BIND_SUPPORTED_CACHE:-}" ]]; then
3531
- [[ "${FPF_FZF_RESULT_BIND_SUPPORTED_CACHE}" == "1" ]]
3532
- return
3533
- fi
3534
-
3535
- local probe_output=""
3536
- probe_output="$(printf 'probe\n' | fzf --bind='result:abort' --filter probe 2>&1 >/dev/null || true)"
3537
-
3538
- if [[ "${probe_output}" == *"unsupported key: result"* ]]; then
3539
- FPF_FZF_RESULT_BIND_SUPPORTED_CACHE="0"
3540
- return 1
3541
- fi
3542
-
3543
- FPF_FZF_RESULT_BIND_SUPPORTED_CACHE="1"
3544
- return 0
3545
- }
3546
-
3547
- send_fzf_listen_action() {
3548
- local action_payload="$1"
3549
- local listen_target="${FZF_PORT:-}"
3550
- local host="127.0.0.1"
3551
- local port=""
3552
- local payload_size
3553
-
3554
- if [[ "${listen_target}" =~ ^[0-9]+$ ]]; then
3555
- port="${listen_target}"
3556
- elif [[ "${listen_target}" =~ ^([^:]+):([0-9]+)$ ]]; then
3557
- host="${BASH_REMATCH[1]}"
3558
- port="${BASH_REMATCH[2]}"
3559
- elif [[ "${listen_target}" =~ ^https?://([^:/]+):([0-9]+)$ ]]; then
3560
- host="${BASH_REMATCH[1]}"
3561
- port="${BASH_REMATCH[2]}"
3562
- elif [[ "${listen_target}" =~ ^\[([^]]+)\]:([0-9]+)$ ]]; then
3563
- host="${BASH_REMATCH[1]}"
3564
- port="${BASH_REMATCH[2]}"
3565
- fi
3566
-
3567
- if [[ -z "${port}" ]] || ! [[ "${port}" =~ ^[0-9]+$ ]] || [[ "${port}" -le 0 || "${port}" -gt 65535 ]]; then
3568
- return 1
3569
- fi
3570
-
3571
- if [[ "${host}" == "0.0.0.0" || "${host}" == "*" ]]; then
3572
- host="127.0.0.1"
3573
- fi
3574
-
3575
- payload_size="$(printf "%s" "${action_payload}" | wc -c | tr -d '[:space:]')"
3576
-
3577
- if command_exists curl; then
3578
- if curl --silent --show-error --fail --max-time 2 \
3579
- --request POST \
3580
- --header 'Content-Type: text/plain' \
3581
- --data-binary "${action_payload}" \
3582
- "http://${host}:${port}" >/dev/null 2>&1; then
3583
- return 0
3584
- fi
3585
- fi
3586
-
3587
- if command_exists nc; then
3588
- if {
3589
- printf 'POST / HTTP/1.1\r\n'
3590
- printf 'Host: %s:%s\r\n' "${host}" "${port}"
3591
- printf 'Content-Type: text/plain\r\n'
3592
- printf 'Content-Length: %s\r\n' "${payload_size}"
3593
- printf '\r\n'
3594
- printf '%s' "${action_payload}"
3595
- } | nc -w 2 "${host}" "${port}" >/dev/null 2>&1; then
3596
- return 0
3597
- fi
3598
- fi
3599
-
3600
- if exec 9<>"/dev/tcp/${host}/${port}" 2>/dev/null; then
3601
- printf 'POST / HTTP/1.1\r\n' >&9
3602
- printf 'Host: %s:%s\r\n' "${host}" "${port}" >&9
3603
- printf 'Content-Type: text/plain\r\n' >&9
3604
- printf 'Content-Length: %s\r\n' "${payload_size}" >&9
3605
- printf '\r\n' >&9
3606
- printf '%s' "${action_payload}" >&9
3607
- exec 9>&-
3608
- return 0
3609
- fi
3610
-
3611
- return 1
3612
- }
3613
-
3614
- send_fzf_prompt_action() {
3615
- local prompt_text="$1"
3616
- local action_payload
3617
-
3618
- action_payload="change-prompt(${prompt_text})"
3619
- send_fzf_listen_action "${action_payload}"
3620
- }
3621
-
3622
- run_dynamic_reload_action() {
3623
- local query="$1"
3624
- local manager_override="${FPF_IPC_MANAGER_OVERRIDE:-}"
3625
- local manager_list_csv="${FPF_IPC_MANAGER_LIST:-}"
3626
- local fallback_file="${FPF_IPC_FALLBACK_FILE:-}"
3627
- local min_chars="${FPF_RELOAD_MIN_CHARS:-2}"
3628
- local reload_debounce="${FPF_RELOAD_DEBOUNCE:-0.12}"
3629
- local output_file=""
3630
- local detected_manager=""
3631
- local manager_seen=""
3632
- local manager_entry=""
3633
- local -a manager_list_entries=()
3634
- local -a managers=()
3635
-
3636
- if [[ -z "${fallback_file}" || ! -r "${fallback_file}" ]]; then
3637
- return 1
3638
- fi
3639
-
3640
- if ! [[ "${min_chars}" =~ ^[0-9]+$ ]]; then
3641
- min_chars=2
3642
- fi
3643
-
3644
- if ! [[ "${reload_debounce}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
3645
- reload_debounce=0.12
3646
- fi
3647
-
3648
- if [[ ${#query} -lt ${min_chars} ]]; then
3649
- cat "${fallback_file}"
3650
- return 0
3651
- fi
3652
-
3653
- sleep "${reload_debounce}"
3654
-
3655
- if [[ -n "${manager_override}" ]]; then
3656
- manager_override="$(normalize_manager "${manager_override}")"
3657
- if ! manager_supported "${manager_override}" || ! manager_command_ready "${manager_override}"; then
3658
- cat "${fallback_file}"
3659
- return 0
3660
- fi
3661
- managers+=("${manager_override}")
3662
- elif [[ -n "${manager_list_csv}" ]]; then
3663
- IFS=',' read -r -a manager_list_entries <<<"${manager_list_csv}"
3664
- for manager_entry in "${manager_list_entries[@]-}"; do
3665
- manager_entry="$(trim_whitespace "${manager_entry}")"
3666
- [[ -n "${manager_entry}" ]] || continue
3667
-
3668
- detected_manager="$(normalize_manager "${manager_entry}")"
3669
- if ! manager_supported "${detected_manager}" || ! manager_command_ready "${detected_manager}"; then
3670
- continue
3671
- fi
3672
-
3673
- case " ${manager_seen} " in
3674
- *" ${detected_manager} "*)
3675
- continue
3676
- ;;
3677
- esac
3678
- manager_seen+=" ${detected_manager}"
3679
- managers+=("${detected_manager}")
3680
- done
3681
- else
3682
- while IFS= read -r detected_manager; do
3683
- [[ -n "${detected_manager}" ]] || continue
3684
- if manager_supported "${detected_manager}" && manager_command_ready "${detected_manager}"; then
3685
- managers+=("${detected_manager}")
3686
- fi
3687
- done < <(detect_default_managers)
3688
- fi
3689
-
3690
- if [[ "${#managers[@]}" -eq 0 ]]; then
3691
- cat "${fallback_file}"
3692
- return 0
3693
- fi
3694
-
3695
- output_file="$(mktemp "${SESSION_TMP_ROOT}/dynamic-reload.XXXXXX")"
3696
- : >"${output_file}"
3697
-
3698
- if collect_search_display_rows "${query}" "${output_file}" "${managers[@]-}"; then
3699
- cat "${output_file}"
3700
- else
3701
- cat "${fallback_file}"
3702
- fi
3703
-
3704
- rm -f "${output_file}"
3705
- }
3706
-
3707
- run_ipc_reload_action() {
3708
- local query="$1"
3709
- local manager_override="${FPF_IPC_MANAGER_OVERRIDE:-}"
3710
- local manager_list_csv="${FPF_IPC_MANAGER_LIST:-}"
3711
- local fallback_file="${FPF_IPC_FALLBACK_FILE:-}"
3712
- local reload_command
3713
- local action_payload
3714
-
3715
- if [[ -z "${fallback_file}" || ! -r "${fallback_file}" ]]; then
3716
- return 1
3717
- fi
3718
-
3719
- if [[ -n "${manager_override}" ]]; then
3720
- manager_override="$(normalize_manager "${manager_override}")"
3721
- manager_supported "${manager_override}" || return 1
3722
- fi
3723
-
3724
- reload_command="$(build_dynamic_reload_command_for_query "${manager_override}" "${fallback_file}" "${manager_list_csv}" "${query}")"
3725
- action_payload="change-prompt(Search> )+reload(${reload_command})"
3726
- send_fzf_listen_action "${action_payload}"
3727
- }
3728
-
3729
- run_ipc_query_notify_action() {
3730
- local query="$1"
3731
- local manager_override="${FPF_IPC_MANAGER_OVERRIDE:-}"
3732
- local fallback_file="${FPF_IPC_FALLBACK_FILE:-}"
3733
- local min_chars="${FPF_RELOAD_MIN_CHARS:-2}"
3734
- local target_manager=""
3735
- local should_warm_bun=0
3736
-
3737
- if ! [[ "${min_chars}" =~ ^[0-9]+$ ]]; then
3738
- min_chars=2
3739
- fi
3740
-
3741
- if [[ ${#query} -lt ${min_chars} ]]; then
3742
- run_ipc_reload_action "${query}" || send_fzf_prompt_action "Search> " || true
3743
- return 0
3744
- fi
3745
-
3746
- if [[ -z "${fallback_file}" || ! -r "${fallback_file}" ]]; then
3747
- return 1
3748
- fi
3749
-
3750
- if [[ -n "${manager_override}" ]]; then
3751
- manager_override="$(normalize_manager "${manager_override}")"
3752
- manager_supported "${manager_override}" || return 1
3753
- target_manager="${manager_override}"
3754
- else
3755
- target_manager="bun"
3756
- fi
3757
-
3758
- if [[ "${target_manager}" == "bun" ]] && manager_command_ready bun && [[ -n "${FZF_PORT:-}" ]]; then
3759
- should_warm_bun=1
3760
- fi
3761
-
3762
- send_fzf_prompt_action "Loading> " || true
3763
-
3764
- if [[ "${should_warm_bun}" -eq 1 ]]; then
3765
- (
3766
- FPF_IPC_MANAGER_OVERRIDE="${manager_override}" \
3767
- FPF_IPC_FALLBACK_FILE="${fallback_file}" \
3768
- manager_search_entries "bun" "${query}" >/dev/null 2>&1 || true
3769
- ) &
3770
- fi
3771
-
3772
- run_ipc_reload_action "${query}" || send_fzf_prompt_action "Search> " || true
3773
- }
3774
-
3775
- manager_dispatch_action() {
3776
- local manager="$1"
3777
- local action="$2"
3778
- shift 2 || true
3779
- manager_execute_action "${manager}" "${action}" "$@"
3780
- }
3781
-
3782
- manager_install() {
3783
- local manager="$1"
3784
- shift
3785
- manager_dispatch_action "${manager}" "install" "$@"
3786
- }
3787
-
3788
- manager_remove() {
3789
- local manager="$1"
3790
- shift
3791
- manager_execute_action "${manager}" "remove" "$@"
3792
- }
3793
-
3794
- manager_show_info() {
3795
- local manager="$1"
3796
- local package="$2"
3797
- manager_execute_action "${manager}" "show_info" "${package}"
3798
- }
3799
-
3800
- manager_update() {
3801
- local manager="$1"
3802
- manager_execute_action "${manager}" "update"
3803
- }
3804
-
3805
- manager_refresh() {
3806
- local manager="$1"
3807
- manager_execute_action "${manager}" "refresh"
3808
- }
3809
-
3810
- confirm_action() {
3811
- local prompt="$1"
3812
- local reply=""
3813
- local normalized_reply=""
3814
-
3815
- if assume_yes_enabled; then
3816
- return 0
3817
- fi
3818
-
3819
- printf "%s [y/N]: " "${prompt}" >&2
3820
- read -r reply || true
3821
- reply="$(trim_whitespace "${reply}")"
3822
- normalized_reply="${reply,,}"
3823
-
3824
- case "${normalized_reply}" in
3825
- y|yes)
3826
- return 0
3827
- ;;
3828
- *)
3829
- return 1
3830
- ;;
3831
- esac
3832
- }
3833
-
3834
- run_fuzzy_selector() {
3835
- local query="$1"
3836
- local input_file="$2"
3837
- local header_line="$3"
3838
- local reload_cmd="${4:-}"
3839
- local reload_ipc_cmd="${5:-}"
3840
- local script_path="$(invocation_script_path)"
3841
- local quoted_script_path=""
3842
- local quoted_help_file=""
3843
- local quoted_kbinds_file=""
3844
- local preview_cmd
3845
- local fzf_shell
3846
-
3847
- printf -v quoted_script_path "%q" "${script_path}"
3848
- printf -v quoted_help_file "%q" "${HELP_FILE}"
3849
- printf -v quoted_kbinds_file "%q" "${KBINDS_FILE}"
3850
-
3851
- preview_cmd="FPF_SESSION_TMP_ROOT=$(printf '%q' "${SESSION_TMP_ROOT}") ${quoted_script_path} --preview-item --manager {1} -- {2}"
3852
- if command_exists bash; then
3853
- fzf_shell="bash"
3854
- else
3855
- fzf_shell=""
3856
- fi
3857
-
3858
- local -a fzf_args=()
3859
- fzf_args=(-q "${query}" -m \
3860
- -e \
3861
- --delimiter=$'\t' \
3862
- --with-nth=1,2,3 \
3863
- --preview="${preview_cmd}" \
3864
- --preview-window=55%:wrap:border-sharp \
3865
- --layout=reverse \
3866
- --marker='>>' \
3867
- --prompt='Search> ' \
3868
- --header="${header_line}" \
3869
- --info=inline \
3870
- --margin="2%,1%,2%,1%" \
3871
- --cycle \
3872
- --tiebreak=begin,chunk,length \
3873
- --bind="ctrl-k:preview:cat ${quoted_kbinds_file}" \
3874
- --bind="ctrl-h:preview:cat ${quoted_help_file}" \
3875
- --bind='ctrl-/:change-preview-window(hidden|)' \
3876
- --bind=ctrl-n:next-selected,ctrl-b:prev-selected \
3877
- --bind='focus:transform-preview-label:echo [{1}] {2}')
3878
-
3879
- if [[ -n "${reload_ipc_cmd}" ]]; then
3880
- fzf_args+=(--listen=0)
3881
- fzf_args+=(--bind="change:execute-silent:${reload_ipc_cmd}")
3882
- if [[ -n "${reload_cmd}" ]]; then
3883
- if fzf_supports_result_bind; then
3884
- fzf_args+=(--bind="ctrl-r:change-prompt(Loading> )+reload:${reload_cmd}")
3885
- fzf_args+=(--bind="result:change-prompt(Search> )")
3886
- else
3887
- fzf_args+=(--bind="ctrl-r:reload:${reload_cmd}")
3888
- fi
3889
- fi
3890
- elif [[ -n "${reload_cmd}" ]]; then
3891
- if fzf_supports_result_bind; then
3892
- fzf_args+=(--bind="change:change-prompt(Loading> )+reload:${reload_cmd}")
3893
- fzf_args+=(--bind="ctrl-r:change-prompt(Loading> )+reload:${reload_cmd}")
3894
- fzf_args+=(--bind="result:change-prompt(Search> )")
3895
- else
3896
- fzf_args+=(--bind="change:reload:${reload_cmd}")
3897
- fzf_args+=(--bind="ctrl-r:reload:${reload_cmd}")
3898
- fi
3899
- fi
3900
-
3901
- if [[ -n "${fzf_shell}" ]]; then
3902
- SHELL="${fzf_shell}" fzf "${fzf_args[@]}" <"${input_file}"
3903
- else
3904
- fzf "${fzf_args[@]}" <"${input_file}"
3905
- fi
3906
- }
3907
-
3908
- main() {
3909
- ensure_tmp_root
3910
- initialize_session_tmp_root
3911
- load_fpf_libraries
3912
- if [[ -z "${FPF_SESSION_TMP_ROOT:-}" ]]; then
3913
- trap cleanup_session_tmp_root EXIT
3914
- fi
3915
-
3916
- parse_args "$@"
3917
-
3918
- if [[ "${ACTION}" == "ipc-reload" ]]; then
3919
- run_ipc_reload_action "$(join_query)" || true
3920
- exit 0
3921
- fi
3922
-
3923
- if [[ "${ACTION}" == "ipc-query-notify" ]]; then
3924
- run_ipc_query_notify_action "$(join_query)" || true
3925
- exit 0
3926
- fi
3927
-
3928
- if [[ "${ACTION}" == "dynamic-reload" ]]; then
3929
- run_dynamic_reload_action "$(join_query)" || true
3930
- exit 0
3931
- fi
3932
-
3933
- if [[ "${ACTION}" == "preview-item" ]]; then
3934
- local preview_manager="${MANAGER_OVERRIDE:-}"
3935
- local preview_package
3936
-
3937
- preview_package="$(join_query)"
3938
- run_preview_item_action "${preview_manager}" "${preview_package}" || true
3939
- exit 0
3940
- fi
3941
-
3942
- if [[ "${ACTION}" == "bun-refresh-worker" ]]; then
3943
- local worker_manager="${MANAGER_OVERRIDE:-bun}"
3944
- local worker_query
3945
- local worker_flags
3946
- local worker_key
3947
- local worker_fingerprint
3948
- local worker_generation
3949
-
3950
- worker_query="$(join_query)"
3951
- worker_flags="${FPF_BUN_REFRESH_FLAGS:-$(query_cache_flags)}"
3952
- worker_key="${FPF_BUN_REFRESH_KEY:-$(cache_query_key "${worker_manager}" "${worker_query}" "${worker_flags}")}"
3953
- worker_fingerprint="${FPF_BUN_REFRESH_FINGERPRINT:-$(cache_fingerprint "${worker_manager}" "${worker_query}" "${worker_flags}")}"
3954
- worker_generation="${FPF_BUN_REFRESH_GENERATION:-0}"
3955
-
3956
- if ! [[ "${worker_generation}" =~ ^[0-9]+$ ]] || [[ "${worker_generation}" -le 0 ]]; then
3957
- exit 0
3958
- fi
3959
-
3960
- initialize_cache_root
3961
- bun_run_refresh_worker "${worker_manager}" "${worker_query}" "${worker_flags}" "${worker_key}" "${worker_fingerprint}" "${worker_generation}" || true
3962
- exit 0
3963
- fi
3964
-
3965
- if [[ "${ACTION}" == "version" ]]; then
3966
- print_version
3967
- exit 0
3968
- fi
3969
-
3970
- local detected_manager
3971
- local detected_managers=()
3972
- while IFS= read -r detected_manager; do
3973
- [[ -n "${detected_manager}" ]] || continue
3974
- detected_managers+=("${detected_manager}")
3975
- done < <(detect_default_managers)
3976
-
3977
- local default_managers_display
3978
- default_managers_display="$(join_manager_labels_with_ids "${detected_managers[@]-}")"
3979
- [[ -n "${default_managers_display}" ]] || default_managers_display="None"
3980
-
3981
- build_help_file "${default_managers_display}"
3982
- build_keybind_file
3983
-
3984
- if [[ "${ACTION}" == "help" ]]; then
3985
- print_help
3986
- exit 0
3987
- fi
3988
-
3989
- local managers=()
3990
- local manager_list_override="${FPF_MANAGER_LIST_OVERRIDE:-}"
3991
- local manager_list_entry=""
3992
- local normalized_manager=""
3993
- local manager_seen=""
3994
- local -a manager_list_entries=()
3995
- if [[ -n "${MANAGER_OVERRIDE}" ]]; then
3996
- managers+=("$(normalize_manager "${MANAGER_OVERRIDE}")")
3997
- elif [[ -n "${manager_list_override}" ]]; then
3998
- IFS=',' read -r -a manager_list_entries <<<"${manager_list_override}"
3999
- for manager_list_entry in "${manager_list_entries[@]-}"; do
4000
- manager_list_entry="$(trim_whitespace "${manager_list_entry}")"
4001
- [[ -n "${manager_list_entry}" ]] || continue
4002
-
4003
- normalized_manager="$(normalize_manager "${manager_list_entry}")"
4004
- if ! manager_supported "${normalized_manager}" || ! manager_command_ready "${normalized_manager}"; then
4005
- continue
4006
- fi
4007
-
4008
- case " ${manager_seen} " in
4009
- *" ${normalized_manager} "*)
4010
- continue
4011
- ;;
4012
- esac
4013
- manager_seen+=" ${normalized_manager}"
4014
- managers+=("${normalized_manager}")
4015
- done
4016
- else
4017
- for detected_manager in "${detected_managers[@]-}"; do
4018
- [[ -n "${detected_manager}" ]] || continue
4019
- managers+=("${detected_manager}")
4020
- done
4021
- fi
4022
-
4023
- if [[ "${#managers[@]}" -eq 0 ]]; then
4024
- die "Unable to auto-detect supported package managers. Use --manager."
4025
- fi
4026
-
4027
- local manager
4028
- for manager in "${managers[@]-}"; do
4029
- manager_supported "${manager}" || die "Unsupported manager: ${manager}"
4030
- manager_command_ready "${manager}" || die "Manager command(s) for '${manager}' not found on this system"
4031
- done
4032
-
4033
- local manager_display
4034
- manager_display="$(join_manager_labels "${managers[@]-}")"
4035
-
4036
- local query
4037
- query="$(join_query)"
4038
-
4039
- if [[ "${ACTION}" != "feed-search" ]]; then
4040
- log "Using manager(s): ${manager_display}"
4041
- fi
4042
-
4043
- if [[ "${ACTION}" == "update" ]]; then
4044
- if confirm_action "Run update/upgrade for ${manager_display}?"; then
4045
- for manager in "${managers[@]-}"; do
4046
- log "Updating with $(manager_label "${manager}")"
4047
- manager_update "${manager}"
4048
- done
4049
- else
4050
- log "Update canceled"
4051
- fi
4052
- exit 0
4053
- fi
4054
-
4055
- if [[ "${ACTION}" == "refresh" ]]; then
4056
- if confirm_action "Refresh package catalogs for ${manager_display}?"; then
4057
- for manager in "${managers[@]-}"; do
4058
- log "Refreshing catalogs with $(manager_label "${manager}")"
4059
- manager_refresh "${manager}"
4060
- done
4061
- else
4062
- log "Refresh canceled"
4063
- fi
4064
- exit 0
4065
- fi
4066
-
4067
- local display_file
4068
- display_file="$(mktemp "${SESSION_TMP_ROOT}/display.XXXXXX")"
4069
- : >"${display_file}"
4070
-
4071
- if [[ "${ACTION}" == "feed-search" ]]; then
4072
- collect_search_display_rows "${query}" "${display_file}" "${managers[@]-}"
4073
- if [[ -s "${display_file}" ]]; then
4074
- cat "${display_file}"
4075
- fi
4076
- rm -f "${display_file}"
4077
- exit 0
4078
- fi
4079
-
4080
- ensure_fzf "${managers[@]-}"
4081
-
4082
- if [[ "${ACTION}" == "search" ]]; then
4083
- loading_progress_begin "${managers[@]-}"
4084
- if ! run_with_loading_indicator "Loading packages from ${manager_display}" collect_search_display_rows "${query}" "${display_file}" "${managers[@]-}"; then
4085
- loading_progress_end
4086
- die "Failed to load package search results."
4087
- fi
4088
- loading_progress_end
4089
- else
4090
- loading_progress_begin "${managers[@]-}"
4091
- if ! run_with_loading_indicator "Loading installed packages from ${manager_display}" collect_installed_display_rows "${display_file}" "${managers[@]-}"; then
4092
- loading_progress_end
4093
- die "Failed to load installed package results."
4094
- fi
4095
- loading_progress_end
4096
- fi
4097
-
4098
- if [[ ! -s "${display_file}" ]]; then
4099
- rm -f "${display_file}"
4100
-
4101
- if [[ "${ACTION}" == "search" && -z "${query}" && "${#managers[@]}" -eq 1 ]]; then
4102
- local setup_message=""
4103
- setup_message="$(manager_no_query_setup_message "${managers[0]}" || true)"
4104
- if [[ -n "${setup_message}" ]]; then
4105
- die "${setup_message}"
4106
- fi
4107
- fi
4108
-
4109
- if [[ -n "${query}" ]]; then
4110
- die "No packages found for ${manager_display} matching '${query}'. Try a broader query or --manager."
4111
- fi
4112
- die "No packages found for ${manager_display}. Try adding a query or using --manager."
4113
- fi
4114
-
4115
- local header
4116
- case "${ACTION}" in
4117
- search)
4118
- header="Select package(s) to install with ${manager_display} (TAB to multi-select, * = installed)"
4119
- ;;
4120
- list)
4121
- header="Select installed package(s) to inspect from ${manager_display}"
4122
- ;;
4123
- remove)
4124
- header="Select installed package(s) to remove from ${manager_display}"
4125
- ;;
4126
- *)
4127
- header="Select package(s)"
4128
- ;;
4129
- esac
4130
-
4131
- local reload_cmd=""
4132
- local reload_ipc_cmd=""
4133
- local reload_fallback_file="${display_file}"
4134
- local reload_manager_list=""
4135
- if [[ "${ACTION}" == "search" ]]; then
4136
- if dynamic_reload_enabled "${#managers[@]}"; then
4137
- reload_manager_list="$(IFS=,; printf "%s" "${managers[*]-}")"
4138
-
4139
- if [[ -n "${query}" ]]; then
4140
- reload_fallback_file="$(mktemp "${SESSION_TMP_ROOT}/reload-fallback.XXXXXX")"
4141
- loading_progress_begin "${managers[@]-}"
4142
- if ! run_with_loading_indicator "Preparing baseline search results" collect_search_display_rows "" "${reload_fallback_file}" "${managers[@]-}"; then
4143
- loading_progress_end
4144
- die "Failed to prepare baseline search results."
4145
- fi
4146
- loading_progress_end
4147
- if [[ ! -s "${reload_fallback_file}" ]]; then
4148
- cp "${display_file}" "${reload_fallback_file}"
4149
- fi
4150
- fi
4151
-
4152
- reload_cmd="$(build_dynamic_reload_command "${MANAGER_OVERRIDE}" "${reload_fallback_file}" "${reload_manager_list}")"
4153
- if dynamic_reload_use_ipc; then
4154
- reload_ipc_cmd="$(build_dynamic_query_notify_ipc_command "${MANAGER_OVERRIDE}" "${reload_fallback_file}" "${reload_manager_list}")"
4155
- fi
4156
- fi
4157
- fi
4158
-
4159
- local selected
4160
- selected="$(run_fuzzy_selector "${query}" "${display_file}" "${header}" "${reload_cmd}" "${reload_ipc_cmd}" || true)"
4161
-
4162
- rm -f "${display_file}"
4163
- if [[ "${reload_fallback_file}" != "${display_file}" ]]; then
4164
- rm -f "${reload_fallback_file}"
4165
- fi
4166
-
4167
- if [[ -z "${selected}" ]]; then
4168
- log "Selection canceled"
4169
- exit 0
4170
- fi
4171
-
4172
- local selected_managers=()
4173
- local selected_packages=()
4174
- local selected_line
4175
- local selected_line_number=0
4176
- local selected_parse_failures=0
4177
- local selected_manager
4178
- local selected_pkg
4179
- local normalized_selected_manager
4180
- while IFS= read -r selected_line; do
4181
- selected_line_number=$((selected_line_number + 1))
4182
- [[ -n "${selected_line}" ]] || continue
4183
- IFS=$'\t' read -r selected_manager selected_pkg _ <<<"${selected_line}"
4184
- selected_manager="$(trim_whitespace "${selected_manager}")"
4185
- selected_pkg="$(trim_whitespace "${selected_pkg}")"
4186
-
4187
- if [[ -z "${selected_manager}" || -z "${selected_pkg}" ]]; then
4188
- selected_parse_failures=$((selected_parse_failures + 1))
4189
- log_selection_parse_skip "${selected_line_number}" "missing manager/package fields" "${selected_line}"
4190
- continue
4191
- fi
4192
-
4193
- normalized_selected_manager="$(normalize_manager "${selected_manager}")"
4194
- if ! manager_supported "${normalized_selected_manager}"; then
4195
- selected_parse_failures=$((selected_parse_failures + 1))
4196
- log_selection_parse_skip "${selected_line_number}" "unsupported manager '${normalized_selected_manager}'" "${selected_line}"
4197
- continue
4198
- fi
4199
-
4200
- selected_managers+=("${normalized_selected_manager}")
4201
- selected_packages+=("${selected_pkg}")
4202
- done <<<"${selected}"
4203
-
4204
- if [[ "${#selected_packages[@]}" -eq 0 ]]; then
4205
- if [[ "${selected_parse_failures}" -gt 0 ]] && selection_debug_enabled; then
4206
- log "Debug(selection): no executable entries after parsing ${selected_parse_failures} selected line(s)"
4207
- fi
4208
- log "Selection canceled"
4209
- exit 0
4210
- fi
4211
-
4212
- local unique_managers=()
4213
- local candidate_manager
4214
- local already_seen
4215
- local existing_manager
4216
- for candidate_manager in "${selected_managers[@]-}"; do
4217
- already_seen=0
4218
- for existing_manager in "${unique_managers[@]-}"; do
4219
- if [[ "${existing_manager}" == "${candidate_manager}" ]]; then
4220
- already_seen=1
4221
- break
4222
- fi
4223
- done
4224
- if [[ "${already_seen}" -eq 0 ]]; then
4225
- unique_managers+=("${candidate_manager}")
4226
- fi
4227
- done
4228
-
4229
- local selected_manager_display
4230
- selected_manager_display="$(join_manager_labels "${unique_managers[@]-}")"
4231
-
4232
- local idx
4233
- local mgr_packages=()
4234
-
4235
- case "${ACTION}" in
4236
- search)
4237
- if confirm_action "Install ${#selected_packages[@]} package(s) with ${selected_manager_display}?"; then
4238
- for manager in "${unique_managers[@]-}"; do
4239
- mgr_packages=()
4240
- for idx in "${!selected_packages[@]}"; do
4241
- if [[ "${selected_managers[$idx]}" == "${manager}" ]]; then
4242
- mgr_packages+=("${selected_packages[$idx]}")
4243
- fi
4244
- done
4245
- if [[ "${#mgr_packages[@]}" -gt 0 ]]; then
4246
- log "Installing ${#mgr_packages[@]} package(s) with $(manager_label "${manager}")"
4247
- manager_install "${manager}" "${mgr_packages[@]-}"
4248
- fi
4249
- done
4250
- else
4251
- log "Install canceled"
4252
- fi
4253
- ;;
4254
- remove)
4255
- if confirm_action "Remove ${#selected_packages[@]} package(s) with ${selected_manager_display}?"; then
4256
- for manager in "${unique_managers[@]-}"; do
4257
- mgr_packages=()
4258
- for idx in "${!selected_packages[@]}"; do
4259
- if [[ "${selected_managers[$idx]}" == "${manager}" ]]; then
4260
- mgr_packages+=("${selected_packages[$idx]}")
4261
- fi
4262
- done
4263
- if [[ "${#mgr_packages[@]}" -gt 0 ]]; then
4264
- log "Removing ${#mgr_packages[@]} package(s) with $(manager_label "${manager}")"
4265
- manager_remove "${manager}" "${mgr_packages[@]-}"
4266
- fi
4267
- done
4268
- else
4269
- log "Remove canceled"
4270
- fi
4271
- ;;
4272
- list)
4273
- for idx in "${!selected_packages[@]}"; do
4274
- printf "\n=== %s (%s) ===\n" "${selected_packages[$idx]}" "$(manager_label "${selected_managers[$idx]}")"
4275
- manager_show_info "${selected_managers[$idx]}" "${selected_packages[$idx]}" || true
4276
- done
4277
- ;;
4278
- esac
4279
127
  }
4280
128
 
4281
129
  main "$@"