fpf-cli 1.6.10 → 1.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/fpf +151 -42
  2. package/package.json +1 -1
package/fpf CHANGED
@@ -3,7 +3,7 @@
3
3
  set -euo pipefail
4
4
 
5
5
  SCRIPT_NAME="fpf"
6
- SCRIPT_VERSION="1.6.10"
6
+ SCRIPT_VERSION="1.6.12"
7
7
  TMP_ROOT="${TMPDIR:-/tmp}/fpf"
8
8
  SESSION_TMP_ROOT=""
9
9
  HELP_FILE=""
@@ -642,80 +642,168 @@ rank_display_rows_by_query() {
642
642
  local query="$1"
643
643
  local input_file="$2"
644
644
  local ranked_file
645
+ local has_exact=0
646
+ local candidate
645
647
 
646
648
  [[ -n "${query}" ]] || return 0
647
649
 
650
+ while IFS= read -r candidate; do
651
+ [[ -n "${candidate}" ]] || continue
652
+ if awk -F'\t' -v cand="${candidate}" 'tolower($2) == tolower(cand) { found=1; exit } END { exit (found ? 0 : 1) }' "${input_file}"; then
653
+ has_exact=1
654
+ break
655
+ fi
656
+ done < <(exact_query_candidates "${query}" | awk '!seen[$0]++')
657
+
648
658
  ranked_file="$(mktemp "${SESSION_TMP_ROOT}/ranked.XXXXXX")"
649
659
 
650
- awk -F'\t' -v query="${query}" '
660
+ awk -F'\t' -v query="${query}" -v has_exact="${has_exact}" '
651
661
  function lower(s) { return tolower(s) }
662
+ function normalize(s, t) {
663
+ t = lower(s)
664
+ gsub(/[^[:alnum:]]+/, "", t)
665
+ return t
666
+ }
652
667
  BEGIN {
653
668
  q = lower(query)
669
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", q)
670
+ q_norm = normalize(q)
671
+
672
+ token_total = split(q, raw_tokens, /[^[:alnum:]]+/)
673
+ query_token_count = 0
674
+ for (i = 1; i <= token_total; i++) {
675
+ if (raw_tokens[i] != "") {
676
+ query_tokens[++query_token_count] = raw_tokens[i]
677
+ }
678
+ }
654
679
  }
655
680
  {
656
- mgr = $1
657
681
  pkg = $2
658
682
  desc = $3
659
683
 
660
684
  pkg_l = lower(pkg)
661
685
  desc_l = lower(desc)
686
+ pkg_norm = normalize(pkg_l)
687
+ desc_norm = normalize(desc_l)
688
+
689
+ pkg_token_hits = 0
690
+ desc_token_hits = 0
691
+ for (i = 1; i <= query_token_count; i++) {
692
+ token = query_tokens[i]
693
+ if (index(pkg_l, token) > 0 || index(pkg_norm, token) > 0) {
694
+ pkg_token_hits++
695
+ }
696
+ if (index(desc_l, token) > 0 || index(desc_norm, token) > 0) {
697
+ desc_token_hits++
698
+ }
699
+ }
700
+
701
+ pkg_token_count = 0
702
+ split(pkg_l, pkg_parts, /[^[:alnum:]]+/)
703
+ for (i in pkg_parts) {
704
+ if (pkg_parts[i] != "") {
705
+ pkg_token_count++
706
+ }
707
+ }
662
708
 
663
- score = 8
709
+ score = 12
664
710
  if (q != "") {
665
- if (pkg_l == q) {
711
+ if (pkg_l == q || (q_norm != "" && pkg_norm == q_norm)) {
666
712
  score = 0
667
- } else if (index(pkg_l, q) == 1) {
713
+ } else if (q_norm != "" && index(pkg_norm, q_norm) == 1) {
668
714
  score = 1
669
- } else if (index(pkg_l, q) > 0) {
715
+ } else if (query_token_count > 1 && pkg_token_hits == query_token_count) {
670
716
  score = 2
671
- } else if (index(desc_l, q) > 0) {
717
+ } else if (q_norm != "" && index(pkg_norm, q_norm) > 0) {
672
718
  score = 3
719
+ } else if (query_token_count > 0 && pkg_token_hits > 0) {
720
+ score = 4
721
+ } else if (query_token_count > 0 && desc_token_hits == query_token_count) {
722
+ score = 5
723
+ } else if (query_token_count > 0 && desc_token_hits > 0) {
724
+ score = 6
673
725
  }
674
726
  }
675
727
 
676
- mgr_score = 9
677
- if (mgr == "bun") {
678
- mgr_score = 0
679
- } else if (mgr == "npm") {
680
- mgr_score = 1
681
- } else if (mgr == "brew") {
682
- mgr_score = 2
728
+ if (q != "" && has_exact == 1 && pkg_token_hits == query_token_count && query_token_count > 0 && pkg_token_count > query_token_count) {
729
+ score += 5
683
730
  }
684
731
 
685
- print score "\t" mgr_score "\t" length(pkg) "\t" pkg_l "\t" $0
732
+ if (q != "" && desc_l ~ /(plugin|template|starter|boilerplate|router|hooks?|mcp|integration)/) {
733
+ score += 2
734
+ }
735
+
736
+ mgr_bias = 0
737
+ if (mgr == "npm") {
738
+ mgr_bias = 4
739
+ } else if (mgr == "bun") {
740
+ mgr_bias = 3
741
+ }
742
+
743
+ print score "\t" mgr_bias "\t" (99 - pkg_token_hits) "\t" (99 - desc_token_hits) "\t" length(pkg) "\t" pkg_l "\t" $0
686
744
  }
687
745
  ' "${input_file}" |
688
- sort -t $'\t' -k1,1n -k2,2n -k3,3n -k4,4 |
689
- awk -F'\t' '{ print $5 "\t" $6 "\t" $7 }' >"${ranked_file}"
746
+ sort -t $'\t' -k1,1n -k2,2n -k3,3n -k4,4n -k5,5n -k6,6 |
747
+ awk -F'\t' '{ print $7 "\t" $8 "\t" $9 }' >"${ranked_file}"
690
748
 
691
749
  mv "${ranked_file}" "${input_file}"
692
750
  }
693
751
 
694
- query_is_single_token() {
752
+ exact_query_candidates() {
695
753
  local query="$1"
696
- [[ -n "${query}" && "${query}" != *[[:space:]]* ]]
754
+ local compact_query=""
755
+ local dashed_query=""
756
+ local underscored_query=""
757
+ local nospace_query=""
758
+
759
+ compact_query="$(printf "%s" "${query}" | awk '{$1=$1; print}')"
760
+ [[ -n "${compact_query}" ]] || return 0
761
+
762
+ printf "%s\n" "${compact_query}"
763
+
764
+ if [[ "${compact_query}" == *[[:space:]]* ]]; then
765
+ dashed_query="${compact_query// /-}"
766
+ underscored_query="${compact_query// /_}"
767
+ nospace_query="${compact_query// /}"
768
+ printf "%s\n" "${dashed_query}"
769
+ printf "%s\n" "${underscored_query}"
770
+ printf "%s\n" "${nospace_query}"
771
+ fi
697
772
  }
698
773
 
699
774
  exact_match_entry() {
700
775
  local manager="$1"
701
776
  local query="$2"
777
+ local candidate
702
778
 
703
- if ! query_is_single_token "${query}"; then
779
+ if [[ -z "${query}" ]]; then
704
780
  return 0
705
781
  fi
706
782
 
707
- case "${manager}" in
708
- brew)
709
- if brew info --formula "${query}" >/dev/null 2>&1 || brew info --cask "${query}" >/dev/null 2>&1; then
710
- printf "%s\t-\n" "${query}"
711
- fi
712
- ;;
713
- npm|bun)
714
- if command_exists npm && npm view "${query}" name >/dev/null 2>&1; then
715
- printf "%s\t-\n" "${query}"
716
- fi
717
- ;;
718
- esac
783
+ while IFS= read -r candidate; do
784
+ [[ -n "${candidate}" ]] || continue
785
+
786
+ case "${manager}" in
787
+ brew)
788
+ if brew info --formula "${candidate}" >/dev/null 2>&1 || brew info --cask "${candidate}" >/dev/null 2>&1; then
789
+ printf "%s\t-\n" "${candidate}"
790
+ return 0
791
+ fi
792
+ ;;
793
+ npm)
794
+ if command_exists npm && npm view "${candidate}" name >/dev/null 2>&1; then
795
+ printf "%s\t-\n" "${candidate}"
796
+ return 0
797
+ fi
798
+ ;;
799
+ bun)
800
+ if bun info "${candidate}" >/dev/null 2>&1 || (command_exists npm && npm view "${candidate}" name >/dev/null 2>&1); then
801
+ printf "%s\t-\n" "${candidate}"
802
+ return 0
803
+ fi
804
+ ;;
805
+ esac
806
+ done < <(exact_query_candidates "${query}" | awk '!seen[$0]++')
719
807
  }
720
808
 
721
809
  manager_search_entries() {
@@ -724,10 +812,14 @@ manager_search_entries() {
724
812
  local effective_query="${query}"
725
813
  local npm_search_limit=500
726
814
  local line_limit=0
815
+ local query_line_limit=40
816
+ local effective_limit=0
727
817
 
728
818
  if [[ -z "${query}" ]]; then
729
819
  npm_search_limit="${FPF_NO_QUERY_NPM_LIMIT:-120}"
730
820
  line_limit="${FPF_NO_QUERY_RESULT_LIMIT:-120}"
821
+ else
822
+ query_line_limit="${FPF_QUERY_PER_MANAGER_LIMIT:-40}"
731
823
  fi
732
824
 
733
825
  if ! [[ "${npm_search_limit}" =~ ^[0-9]+$ ]] || [[ "${npm_search_limit}" -eq 0 ]]; then
@@ -738,9 +830,19 @@ manager_search_entries() {
738
830
  line_limit=0
739
831
  fi
740
832
 
833
+ if ! [[ "${query_line_limit}" =~ ^[0-9]+$ ]]; then
834
+ query_line_limit=40
835
+ fi
836
+
837
+ if [[ "${line_limit}" -gt 0 ]]; then
838
+ effective_limit="${line_limit}"
839
+ else
840
+ effective_limit="${query_line_limit}"
841
+ fi
842
+
741
843
  if [[ -z "${effective_query}" ]]; then
742
844
  case "${manager}" in
743
- apt|dnf|pacman|zypper|emerge|choco|scoop|snap|flatpak)
845
+ apt|dnf|pacman|zypper|emerge|choco|scoop|snap)
744
846
  effective_query="a"
745
847
  ;;
746
848
  brew|npm|bun)
@@ -894,19 +996,21 @@ manager_search_entries() {
894
996
  ;;
895
997
  bun)
896
998
  {
897
- if bun search "${effective_query}" >/dev/null 2>&1; then
898
- bun search "${effective_query}" 2>/dev/null |
899
- awk 'NR > 1 && NF > 0 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }'
999
+ local bun_search_file
1000
+ bun_search_file="$(mktemp "${SESSION_TMP_ROOT}/bun-search.XXXXXX")"
1001
+ if bun search "${effective_query}" >"${bun_search_file}" 2>/dev/null; then
1002
+ awk 'NR > 1 && NF > 0 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }' "${bun_search_file}"
900
1003
  elif command_exists npm; then
901
1004
  npm search "${effective_query}" --searchlimit="${npm_search_limit}" --parseable 2>/dev/null |
902
1005
  awk -F'\t' 'NF >= 2 { print $1 "\t" $2 }'
903
1006
  fi
1007
+ rm -f "${bun_search_file}"
904
1008
  exact_match_entry "${manager}" "${query}"
905
1009
  } || true
906
1010
  ;;
907
1011
  esac | awk -F'\t' 'NF >= 1 { if ($2 == "") $2 = "-"; print $1 "\t" $2 }' | awk -F'\t' '!seen[$1]++' | {
908
- if [[ "${line_limit}" -gt 0 ]]; then
909
- awk -v limit="${line_limit}" 'NR <= limit'
1012
+ if [[ "${effective_limit}" -gt 0 ]]; then
1013
+ awk -v limit="${effective_limit}" 'NR <= limit'
910
1014
  else
911
1015
  cat
912
1016
  fi
@@ -1094,6 +1198,7 @@ collect_search_display_rows() {
1094
1198
  local output_file="$2"
1095
1199
  shift 2
1096
1200
  local managers=("$@")
1201
+ local query_limit="${FPF_QUERY_RESULT_LIMIT:-80}"
1097
1202
 
1098
1203
  : >"${output_file}"
1099
1204
 
@@ -1140,6 +1245,11 @@ collect_search_display_rows() {
1140
1245
  if [[ -s "${output_file}" ]]; then
1141
1246
  sort -u "${output_file}" -o "${output_file}"
1142
1247
  rank_display_rows_by_query "${query}" "${output_file}"
1248
+
1249
+ if [[ -n "${query}" && "${query_limit}" =~ ^[0-9]+$ && "${query_limit}" -gt 0 ]]; then
1250
+ awk -v limit="${query_limit}" 'NR <= limit' "${output_file}" >"${output_file}.limited"
1251
+ mv "${output_file}.limited" "${output_file}"
1252
+ fi
1143
1253
  fi
1144
1254
  }
1145
1255
 
@@ -1405,10 +1515,11 @@ run_fuzzy_selector() {
1405
1515
 
1406
1516
  local -a fzf_args=()
1407
1517
  fzf_args=(-q "${query}" -m \
1518
+ -e \
1408
1519
  --delimiter=$'\t' \
1409
1520
  --with-nth=1,2,3 \
1410
1521
  --preview="${preview_cmd}" \
1411
- --preview-window=55%:wrap:border-sharp:hidden \
1522
+ --preview-window=55%:wrap:border-sharp \
1412
1523
  --layout=reverse \
1413
1524
  --marker='>>' \
1414
1525
  --prompt='Search> ' \
@@ -1424,8 +1535,6 @@ run_fuzzy_selector() {
1424
1535
 
1425
1536
  if [[ -n "${reload_cmd}" ]]; then
1426
1537
  fzf_args+=(--bind="change:reload:${reload_cmd}")
1427
- else
1428
- fzf_args+=(-e)
1429
1538
  fi
1430
1539
 
1431
1540
  fzf "${fzf_args[@]}" <"${input_file}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fpf-cli",
3
- "version": "1.6.10",
3
+ "version": "1.6.12",
4
4
  "description": "Cross-platform fuzzy package finder powered by fzf",
5
5
  "bin": {
6
6
  "fpf": "fpf"