From d5f207424bbf9e4e681c853cd8d46644f0b8b90a Mon Sep 17 00:00:00 2001 From: wehub-resource-sync Date: Mon, 13 Jul 2026 12:27:16 +0800 Subject: [PATCH] chore: import upstream snapshot with attribution --- .cargo/config.toml | 16 + .github/ISSUE_TEMPLATE/bug_report.yml | 58 + .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 34 + .github/workflows/external-tests.yml | 217 + .github/workflows/lua.yml | 56 + .github/workflows/nix.yml | 37 + .github/workflows/panvimdoc.yaml | 85 + .github/workflows/python.yml | 48 + .github/workflows/release.yaml | 757 +++ .github/workflows/rust.yml | 162 + .github/workflows/spelling.yaml | 25 + .github/workflows/stylua.yaml | 37 + .gitignore | 65 + .luacheckrc | 25 + .luarc.ci.json | 44 + .luarc.json | 42 + .mcp.json | 9 + .nixignore | 4 + .stylua.toml | 7 + AGENTS.md | 111 + CLAUDE.md | 1 + Cargo.lock | 3251 ++++++++++++ Cargo.toml | 73 + Formula/fff-mcp.rb | 60 + LICENSE | 21 + Makefile | 348 ++ README.md | 825 +++ README.wehub.md | 7 + _typos.toml | 26 + assets/logo-dark.png | Bin 0 -> 7767 bytes assets/logo-light.png | Bin 0 -> 7188 bytes assets/logo-orange.png | Bin 0 -> 7703 bytes biome.json | 35 + bun.lock | 417 ++ chart.png | Bin 0 -> 323968 bytes crates/fff-c/Cargo.toml | 21 + crates/fff-c/cbindgen.toml | 33 + crates/fff-c/include/fff.h | 1353 +++++ crates/fff-c/src/accessors.rs | 882 ++++ crates/fff-c/src/ffi_types.rs | 831 +++ crates/fff-c/src/lib.rs | 1699 +++++++ crates/fff-c/tests/smoke.c | 89 + crates/fff-core/Cargo.toml | 95 + crates/fff-core/README.md | 29 + crates/fff-core/benches/bigram_bench.rs | 164 + crates/fff-core/benches/glob_bench.rs | 386 ++ crates/fff-core/benches/memmem_bench.rs | 85 + crates/fff-core/benches/parse_bench.rs | 180 + crates/fff-core/build.rs | 45 + crates/fff-core/src/background_watcher.rs | 853 ++++ crates/fff-core/src/bigram_filter.rs | 1227 +++++ crates/fff-core/src/bigram_query.rs | 998 ++++ crates/fff-core/src/constants.rs | 39 + crates/fff-core/src/constraints.rs | 932 ++++ crates/fff-core/src/dbs/db_healthcheck.rs | 39 + crates/fff-core/src/dbs/frecency.rs | 531 ++ crates/fff-core/src/dbs/lmdb.rs | 259 + crates/fff-core/src/dbs/mod.rs | 10 + crates/fff-core/src/dbs/query_tracker.rs | 548 ++ crates/fff-core/src/error.rs | 99 + crates/fff-core/src/file_picker.rs | 2252 +++++++++ crates/fff-core/src/git.rs | 253 + crates/fff-core/src/git_status_worker.rs | 120 + crates/fff-core/src/grep/classify.rs | 131 + crates/fff-core/src/grep/fuzzy_grep.rs | 358 ++ crates/fff-core/src/grep/grep.rs | 1548 ++++++ crates/fff-core/src/grep/grep_tests.rs | 372 ++ crates/fff-core/src/grep/mod.rs | 16 + crates/fff-core/src/grep/utils.rs | 122 + crates/fff-core/src/ignore.rs | 68 + crates/fff-core/src/lib.rs | 161 + crates/fff-core/src/log.rs | 281 ++ crates/fff-core/src/parallelism.rs | 84 + crates/fff-core/src/path_utils.rs | 211 + crates/fff-core/src/scan.rs | 404 ++ crates/fff-core/src/score.rs | 1604 ++++++ crates/fff-core/src/shared.rs | 391 ++ crates/fff-core/src/simd_path.rs | 612 +++ crates/fff-core/src/simd_string_utils/case.rs | 168 + .../fff-core/src/simd_string_utils/memmem.rs | 494 ++ crates/fff-core/src/simd_string_utils/mod.rs | 5 + crates/fff-core/src/sort_buffer.rs | 131 + crates/fff-core/src/stable_vec.rs | 177 + crates/fff-core/src/types.rs | 971 ++++ crates/fff-core/src/walk/mod.rs | 146 + crates/fff-core/src/walk/ripgrep.rs | 72 + crates/fff-core/src/walk/zlob.rs | 111 + .../tests/bigram_overlay_coherence_test.rs | 1656 ++++++ .../tests/bigram_overlay_integration.rs | 410 ++ .../fff-core/tests/drop_during_post_scan.rs | 161 + .../tests/fixtures/binaries/codex_view | Bin 0 -> 4557704 bytes .../tests/fixtures/binaries/codex_view.codex | Bin 0 -> 127330 bytes .../fff-core/tests/fs_delete_handler_test.rs | 320 ++ crates/fff-core/tests/fuzz_file_operations.rs | 854 ++++ ...zz_git_watcher_stress.proptest-regressions | 8 + .../fff-core/tests/fuzz_git_watcher_stress.rs | 1671 +++++++ .../fuzz_real_repos.proptest-regressions | 7 + crates/fff-core/tests/fuzz_real_repos.rs | 712 +++ crates/fff-core/tests/grep_integration.rs | 1837 +++++++ .../grep_overflow_path_constraint_segfault.rs | 57 + .../tests/lmdb_stale_lock_deadlock.rs | 455 ++ .../tests/new_directory_watcher_test.rs | 539 ++ .../tests/overflow_frecency_segfault.rs | 93 + .../tests/path_separator_constraint_test.rs | 248 + crates/fff-core/tests/real_binary_fixtures.rs | 214 + .../fff-core/tests/watcher_stop_under_lock.rs | 165 + .../tests/watcher_thread_lifecycle_test.rs | 197 + crates/fff-grep/Cargo.toml | 11 + crates/fff-grep/src/lib.rs | 19 + crates/fff-grep/src/lines.rs | 232 + crates/fff-grep/src/matcher.rs | 175 + crates/fff-grep/src/searcher/core.rs | 139 + crates/fff-grep/src/searcher/glue.rs | 127 + crates/fff-grep/src/searcher/mod.rs | 194 + crates/fff-grep/src/sink.rs | 138 + crates/fff-mcp/Cargo.toml | 29 + crates/fff-mcp/build.rs | 15 + crates/fff-mcp/server/.gitkeep | 0 crates/fff-mcp/src/cursor.rs | 52 + crates/fff-mcp/src/healthcheck.rs | 117 + crates/fff-mcp/src/main.rs | 372 ++ crates/fff-mcp/src/output.rs | 576 +++ crates/fff-mcp/src/server.rs | 768 +++ crates/fff-mcp/src/update_check.rs | 78 + crates/fff-nvim/Cargo.toml | 58 + crates/fff-nvim/benches/fuzzy_search_bench.rs | 661 +++ crates/fff-nvim/benches/grep_bench.rs | 236 + .../fff-nvim/benches/query_tracker_bench.rs | 224 + crates/fff-nvim/benches/scan_bench.rs | 220 + crates/fff-nvim/src/bin/bench_grep_query.rs | 138 + .../fff-nvim/src/bin/bench_lmdb_parallel.rs | 150 + crates/fff-nvim/src/bin/bench_search_only.rs | 117 + crates/fff-nvim/src/bin/fuzzy_grep_test.rs | 183 + crates/fff-nvim/src/bin/grep_profiler.rs | 453 ++ crates/fff-nvim/src/bin/grep_vs_rg.rs | 443 ++ crates/fff-nvim/src/bin/jemalloc_profile.rs | 289 ++ crates/fff-nvim/src/bin/search_profiler.rs | 140 + crates/fff-nvim/src/bin/test_memory_leak.rs | 282 ++ crates/fff-nvim/src/bin/test_watcher.rs | 205 + crates/fff-nvim/src/error.rs | 26 + crates/fff-nvim/src/hex_dump.rs | 190 + crates/fff-nvim/src/lib.rs | 1034 ++++ crates/fff-nvim/src/log.rs | 3 + crates/fff-nvim/src/lua_types.rs | 281 ++ crates/fff-nvim/src/path_shortening.rs | 745 +++ crates/fff-nvim/src/user_config.rs | 47 + crates/fff-python/Cargo.toml | 20 + crates/fff-python/src/conversions.rs | 107 + crates/fff-python/src/finder.rs | 982 ++++ crates/fff-python/src/lib.rs | 44 + crates/fff-python/src/types.rs | 408 ++ crates/fff-query-parser/Cargo.toml | 26 + .../fff-query-parser/benches/parse_bench.rs | 180 + crates/fff-query-parser/src/config.rs | 248 + crates/fff-query-parser/src/constraints.rs | 83 + crates/fff-query-parser/src/glob_detect.rs | 18 + crates/fff-query-parser/src/lib.rs | 235 + crates/fff-query-parser/src/location.rs | 265 + crates/fff-query-parser/src/parser.rs | 1467 ++++++ doc/.gitkeep | 0 doc/fff.nvim.txt | 451 ++ empty_config.lua | 62 + flake.lock | 153 + flake.nix | 154 + install-mcp.ps1 | 261 + install-mcp.sh | 319 ++ lua/fff.lua | 1 + lua/fff/conf.lua | 493 ++ lua/fff/core.lua | 176 + lua/fff/download.lua | 289 ++ lua/fff/file_picker/file_info.lua | 415 ++ lua/fff/file_picker/icons.lua | 91 + lua/fff/file_picker/image.lua | 264 + lua/fff/file_picker/init.lua | 212 + lua/fff/file_picker/preview.lua | 946 ++++ lua/fff/fuzzy.lua | 58 + lua/fff/health.lua | 362 ++ lua/fff/highlights.lua | 304 ++ lua/fff/layout.lua | 651 +++ lua/fff/list_separator.lua | 138 + lua/fff/location_utils.lua | 308 ++ lua/fff/main.lua | 653 +++ lua/fff/picker_ui/README.md | 82 + lua/fff/picker_ui/combo_renderer.lua | 49 + lua/fff/picker_ui/file_renderer.lua | 217 + lua/fff/picker_ui/grep_renderer.lua | 297 ++ lua/fff/picker_ui/layout_manager.lua | 150 + lua/fff/picker_ui/list_renderer.lua | 258 + lua/fff/picker_ui/navigation.lua | 289 ++ lua/fff/picker_ui/picker_ui.lua | 778 +++ lua/fff/picker_ui/picker_ui_state.lua | 260 + lua/fff/picker_ui/preview_manager.lua | 236 + lua/fff/picker_ui/renderer.lua | 324 ++ lua/fff/picker_ui/search_manager.lua | 367 ++ lua/fff/picker_ui/ui_creator.lua | 500 ++ lua/fff/picker_ui/utils.lua | 97 + lua/fff/rust/init.lua | 76 + lua/fff/scrollbar.lua | 119 + lua/fff/treesitter_hl.lua | 111 + lua/fff/utils.lua | 140 + lua/fff/utils/fs.lua | 33 + lua/fff/utils/system.lua | 61 + lua/fff/utils/version.lua | 156 + package-lock.json | 4451 +++++++++++++++++ package.json | 17 + packages/fff-bin-darwin-arm64/package.json | 18 + packages/fff-bin-darwin-x64/package.json | 18 + packages/fff-bin-linux-arm64-gnu/package.json | 19 + .../fff-bin-linux-arm64-musl/package.json | 19 + packages/fff-bin-linux-x64-gnu/package.json | 19 + packages/fff-bin-linux-x64-musl/package.json | 19 + packages/fff-bin-win32-arm64/package.json | 18 + packages/fff-bin-win32-x64/package.json | 18 + packages/fff-bun/.npmignore | 2 + packages/fff-bun/README.md | 148 + packages/fff-bun/bun.lock | 61 + packages/fff-bun/examples/glob-bench.ts | 153 + packages/fff-bun/examples/grep.ts | 276 + packages/fff-bun/examples/search.ts | 392 ++ packages/fff-bun/package.json | 74 + packages/fff-bun/src/download.ts | 136 + packages/fff-bun/src/embed.d.ts | 17 + packages/fff-bun/src/embedded.ts | 53 + packages/fff-bun/src/fff-api.ts | 609 +++ packages/fff-bun/src/ffi.ts | 1405 ++++++ packages/fff-bun/src/finder.ts | 607 +++ packages/fff-bun/src/index.ts | 35 + packages/fff-bun/src/platform.ts | 125 + packages/fff-bun/test.ts | 156 + packages/fff-bun/test/git-lifecycle.test.ts | 367 ++ packages/fff-bun/test/index.test.ts | 503 ++ packages/fff-bun/tsconfig.json | 19 + packages/fff-node/README.md | 126 + packages/fff-node/package.json | 76 + packages/fff-node/scripts/cli.ts | 122 + packages/fff-node/scripts/postinstall.ts | 50 + packages/fff-node/src/binary.ts | 148 + packages/fff-node/src/fff-api.ts | 609 +++ packages/fff-node/src/ffi.ts | 1662 ++++++ packages/fff-node/src/finder.ts | 623 +++ packages/fff-node/src/index.ts | 79 + packages/fff-node/src/platform.ts | 127 + packages/fff-node/test/bench.mjs | 0 packages/fff-node/test/demo-grep.mjs | 21 + packages/fff-node/test/e2e.mjs | 353 ++ packages/fff-node/test/stress-515.mjs | 122 + packages/fff-node/tsconfig.json | 21 + packages/fff-python/.python-version | 1 + packages/fff-python/README.md | 75 + packages/fff-python/examples/basic.py | 43 + packages/fff-python/pyproject.toml | 33 + packages/fff-python/src/fff/__init__.py | 70 + packages/fff-python/src/fff/__init__.pyi | 256 + packages/fff-python/src/fff/py.typed | 0 packages/fff-python/tests/test_finder.py | 391 ++ packages/fff-python/uv.lock | 208 + packages/pi-fff/README.md | 153 + packages/pi-fff/package.json | 54 + packages/pi-fff/src/index.ts | 1027 ++++ packages/pi-fff/src/query.ts | 87 + packages/pi-fff/test/extension.test.ts | 321 ++ packages/pi-fff/test/query.test.ts | 71 + packages/pi-fff/tsconfig.json | 13 + packages/shared/fff-api.ts | 603 +++ plugin/fff.lua | 109 + rust-toolchain.toml | 9 + scripts/analyze-results.py | 166 + scripts/benchmark-claude.sh | 816 +++ scripts/determine-version.lua | 32 + scripts/diagnose_lockmdb.py | 233 + scripts/release.sh | 80 + tests/clear_cache_spec.lua | 86 + tests/fff_core_spec.lua | 129 + tests/fresh_db_open_test.lua | 94 + tests/grep_jump_file_spec.lua | 137 + tests/minimal_init.lua | 22 + tests/picker_dir_resolution_spec.lua | 197 + tests/picker_ui_snap.lua | 242 + tests/programmatic_search_spec.lua | 225 + ...-picker_ui_snap.lua---combo---boost_bottom | 67 + ...sts-picker_ui_snap.lua---combo---boost_top | 67 + ..._snap.lua---debug---file_info_panel_bottom | 67 + ..._ui_snap.lua---debug---file_info_panel_top | 67 + ...ua---debug_narrow---file_info_panel_bottom | 67 + ...p.lua---debug_narrow---file_info_panel_top | 67 + ....lua---debug_wide---file_info_panel_bottom | 99 + ...nap.lua---debug_wide---file_info_panel_top | 99 + ....lua---default---cursor_second_item_bottom | 67 + ...nap.lua---default---cursor_second_item_top | 67 + ...icker_ui_snap.lua---default---empty_bottom | 67 + ...s-picker_ui_snap.lua---default---empty_top | 67 + ..._ui_snap.lua---default---no_results_bottom | 67 + ...ker_ui_snap.lua---default---no_results_top | 67 + ..._ui_snap.lua---default---query_main_bottom | 67 + ...ker_ui_snap.lua---default---query_main_top | 67 + ..._ui_snap.lua---narrow---cursor_second_item | 51 + ...p.lua---narrow---cursor_second_item_bottom | 51 + ...snap.lua---narrow---cursor_second_item_top | 51 + ...picker_ui_snap.lua---narrow---empty_bottom | 51 + ...ts-picker_ui_snap.lua---narrow---empty_top | 51 + ...r_ui_snap.lua---narrow---no_results_bottom | 51 + ...cker_ui_snap.lua---narrow---no_results_top | 51 + ...r_ui_snap.lua---narrow---query_main_bottom | 51 + ...cker_ui_snap.lua---narrow---query_main_top | 51 + ...ui_snap.lua---scrollbar---next_page_bottom | 67 + ...er_ui_snap.lua---scrollbar---next_page_top | 67 + ...nap.lua---wide---cursor_second_item_bottom | 83 + ...i_snap.lua---wide---cursor_second_item_top | 83 + ...s-picker_ui_snap.lua---wide---empty_bottom | 83 + ...ests-picker_ui_snap.lua---wide---empty_top | 83 + ...ker_ui_snap.lua---wide---no_results_bottom | 83 + ...picker_ui_snap.lua---wide---no_results_top | 83 + ...ker_ui_snap.lua---wide---query_main_bottom | 83 + ...picker_ui_snap.lua---wide---query_main_top | 83 + ...r_ui_snap.lua---xwide---cursor_second_item | 99 + ...ap.lua---xwide---cursor_second_item_bottom | 99 + ..._snap.lua---xwide---cursor_second_item_top | 99 + ...-picker_ui_snap.lua---xwide---empty_bottom | 99 + ...sts-picker_ui_snap.lua---xwide---empty_top | 99 + ...er_ui_snap.lua---xwide---no_results_bottom | 99 + ...icker_ui_snap.lua---xwide---no_results_top | 99 + ...er_ui_snap.lua---xwide---query_main_bottom | 99 + ...icker_ui_snap.lua---xwide---query_main_top | 99 + tests/snapshot/fixture.lua | 126 + tests/test_cd_during_post_scan.lua | 46 + tests/test_lazy_async_bug.sh | 84 + tests/version_spec.lua | 234 + 328 files changed, 87463 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/workflows/external-tests.yml create mode 100644 .github/workflows/lua.yml create mode 100644 .github/workflows/nix.yml create mode 100644 .github/workflows/panvimdoc.yaml create mode 100644 .github/workflows/python.yml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/rust.yml create mode 100644 .github/workflows/spelling.yaml create mode 100644 .github/workflows/stylua.yaml create mode 100644 .gitignore create mode 100644 .luacheckrc create mode 100644 .luarc.ci.json create mode 100644 .luarc.json create mode 100644 .mcp.json create mode 100644 .nixignore create mode 100644 .stylua.toml create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Formula/fff-mcp.rb create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 README.wehub.md create mode 100644 _typos.toml create mode 100644 assets/logo-dark.png create mode 100644 assets/logo-light.png create mode 100644 assets/logo-orange.png create mode 100644 biome.json create mode 100644 bun.lock create mode 100644 chart.png create mode 100644 crates/fff-c/Cargo.toml create mode 100644 crates/fff-c/cbindgen.toml create mode 100644 crates/fff-c/include/fff.h create mode 100644 crates/fff-c/src/accessors.rs create mode 100644 crates/fff-c/src/ffi_types.rs create mode 100644 crates/fff-c/src/lib.rs create mode 100644 crates/fff-c/tests/smoke.c create mode 100644 crates/fff-core/Cargo.toml create mode 100644 crates/fff-core/README.md create mode 100644 crates/fff-core/benches/bigram_bench.rs create mode 100644 crates/fff-core/benches/glob_bench.rs create mode 100644 crates/fff-core/benches/memmem_bench.rs create mode 100644 crates/fff-core/benches/parse_bench.rs create mode 100644 crates/fff-core/build.rs create mode 100644 crates/fff-core/src/background_watcher.rs create mode 100644 crates/fff-core/src/bigram_filter.rs create mode 100644 crates/fff-core/src/bigram_query.rs create mode 100644 crates/fff-core/src/constants.rs create mode 100644 crates/fff-core/src/constraints.rs create mode 100644 crates/fff-core/src/dbs/db_healthcheck.rs create mode 100644 crates/fff-core/src/dbs/frecency.rs create mode 100644 crates/fff-core/src/dbs/lmdb.rs create mode 100644 crates/fff-core/src/dbs/mod.rs create mode 100644 crates/fff-core/src/dbs/query_tracker.rs create mode 100644 crates/fff-core/src/error.rs create mode 100644 crates/fff-core/src/file_picker.rs create mode 100644 crates/fff-core/src/git.rs create mode 100644 crates/fff-core/src/git_status_worker.rs create mode 100644 crates/fff-core/src/grep/classify.rs create mode 100644 crates/fff-core/src/grep/fuzzy_grep.rs create mode 100644 crates/fff-core/src/grep/grep.rs create mode 100644 crates/fff-core/src/grep/grep_tests.rs create mode 100644 crates/fff-core/src/grep/mod.rs create mode 100644 crates/fff-core/src/grep/utils.rs create mode 100644 crates/fff-core/src/ignore.rs create mode 100644 crates/fff-core/src/lib.rs create mode 100644 crates/fff-core/src/log.rs create mode 100644 crates/fff-core/src/parallelism.rs create mode 100644 crates/fff-core/src/path_utils.rs create mode 100644 crates/fff-core/src/scan.rs create mode 100644 crates/fff-core/src/score.rs create mode 100644 crates/fff-core/src/shared.rs create mode 100644 crates/fff-core/src/simd_path.rs create mode 100644 crates/fff-core/src/simd_string_utils/case.rs create mode 100644 crates/fff-core/src/simd_string_utils/memmem.rs create mode 100644 crates/fff-core/src/simd_string_utils/mod.rs create mode 100644 crates/fff-core/src/sort_buffer.rs create mode 100644 crates/fff-core/src/stable_vec.rs create mode 100644 crates/fff-core/src/types.rs create mode 100644 crates/fff-core/src/walk/mod.rs create mode 100644 crates/fff-core/src/walk/ripgrep.rs create mode 100644 crates/fff-core/src/walk/zlob.rs create mode 100644 crates/fff-core/tests/bigram_overlay_coherence_test.rs create mode 100644 crates/fff-core/tests/bigram_overlay_integration.rs create mode 100644 crates/fff-core/tests/drop_during_post_scan.rs create mode 100644 crates/fff-core/tests/fixtures/binaries/codex_view create mode 100644 crates/fff-core/tests/fixtures/binaries/codex_view.codex create mode 100644 crates/fff-core/tests/fs_delete_handler_test.rs create mode 100644 crates/fff-core/tests/fuzz_file_operations.rs create mode 100644 crates/fff-core/tests/fuzz_git_watcher_stress.proptest-regressions create mode 100644 crates/fff-core/tests/fuzz_git_watcher_stress.rs create mode 100644 crates/fff-core/tests/fuzz_real_repos.proptest-regressions create mode 100644 crates/fff-core/tests/fuzz_real_repos.rs create mode 100644 crates/fff-core/tests/grep_integration.rs create mode 100644 crates/fff-core/tests/grep_overflow_path_constraint_segfault.rs create mode 100644 crates/fff-core/tests/lmdb_stale_lock_deadlock.rs create mode 100644 crates/fff-core/tests/new_directory_watcher_test.rs create mode 100644 crates/fff-core/tests/overflow_frecency_segfault.rs create mode 100644 crates/fff-core/tests/path_separator_constraint_test.rs create mode 100644 crates/fff-core/tests/real_binary_fixtures.rs create mode 100644 crates/fff-core/tests/watcher_stop_under_lock.rs create mode 100644 crates/fff-core/tests/watcher_thread_lifecycle_test.rs create mode 100644 crates/fff-grep/Cargo.toml create mode 100644 crates/fff-grep/src/lib.rs create mode 100644 crates/fff-grep/src/lines.rs create mode 100644 crates/fff-grep/src/matcher.rs create mode 100644 crates/fff-grep/src/searcher/core.rs create mode 100644 crates/fff-grep/src/searcher/glue.rs create mode 100644 crates/fff-grep/src/searcher/mod.rs create mode 100644 crates/fff-grep/src/sink.rs create mode 100644 crates/fff-mcp/Cargo.toml create mode 100644 crates/fff-mcp/build.rs create mode 100644 crates/fff-mcp/server/.gitkeep create mode 100644 crates/fff-mcp/src/cursor.rs create mode 100644 crates/fff-mcp/src/healthcheck.rs create mode 100644 crates/fff-mcp/src/main.rs create mode 100644 crates/fff-mcp/src/output.rs create mode 100644 crates/fff-mcp/src/server.rs create mode 100644 crates/fff-mcp/src/update_check.rs create mode 100644 crates/fff-nvim/Cargo.toml create mode 100644 crates/fff-nvim/benches/fuzzy_search_bench.rs create mode 100644 crates/fff-nvim/benches/grep_bench.rs create mode 100644 crates/fff-nvim/benches/query_tracker_bench.rs create mode 100644 crates/fff-nvim/benches/scan_bench.rs create mode 100644 crates/fff-nvim/src/bin/bench_grep_query.rs create mode 100644 crates/fff-nvim/src/bin/bench_lmdb_parallel.rs create mode 100644 crates/fff-nvim/src/bin/bench_search_only.rs create mode 100644 crates/fff-nvim/src/bin/fuzzy_grep_test.rs create mode 100644 crates/fff-nvim/src/bin/grep_profiler.rs create mode 100644 crates/fff-nvim/src/bin/grep_vs_rg.rs create mode 100644 crates/fff-nvim/src/bin/jemalloc_profile.rs create mode 100644 crates/fff-nvim/src/bin/search_profiler.rs create mode 100644 crates/fff-nvim/src/bin/test_memory_leak.rs create mode 100644 crates/fff-nvim/src/bin/test_watcher.rs create mode 100644 crates/fff-nvim/src/error.rs create mode 100644 crates/fff-nvim/src/hex_dump.rs create mode 100644 crates/fff-nvim/src/lib.rs create mode 100644 crates/fff-nvim/src/log.rs create mode 100644 crates/fff-nvim/src/lua_types.rs create mode 100644 crates/fff-nvim/src/path_shortening.rs create mode 100644 crates/fff-nvim/src/user_config.rs create mode 100644 crates/fff-python/Cargo.toml create mode 100644 crates/fff-python/src/conversions.rs create mode 100644 crates/fff-python/src/finder.rs create mode 100644 crates/fff-python/src/lib.rs create mode 100644 crates/fff-python/src/types.rs create mode 100644 crates/fff-query-parser/Cargo.toml create mode 100644 crates/fff-query-parser/benches/parse_bench.rs create mode 100644 crates/fff-query-parser/src/config.rs create mode 100644 crates/fff-query-parser/src/constraints.rs create mode 100644 crates/fff-query-parser/src/glob_detect.rs create mode 100644 crates/fff-query-parser/src/lib.rs create mode 100644 crates/fff-query-parser/src/location.rs create mode 100644 crates/fff-query-parser/src/parser.rs create mode 100644 doc/.gitkeep create mode 100644 doc/fff.nvim.txt create mode 100644 empty_config.lua create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 install-mcp.ps1 create mode 100755 install-mcp.sh create mode 100644 lua/fff.lua create mode 100644 lua/fff/conf.lua create mode 100644 lua/fff/core.lua create mode 100644 lua/fff/download.lua create mode 100644 lua/fff/file_picker/file_info.lua create mode 100644 lua/fff/file_picker/icons.lua create mode 100644 lua/fff/file_picker/image.lua create mode 100644 lua/fff/file_picker/init.lua create mode 100644 lua/fff/file_picker/preview.lua create mode 100644 lua/fff/fuzzy.lua create mode 100644 lua/fff/health.lua create mode 100644 lua/fff/highlights.lua create mode 100644 lua/fff/layout.lua create mode 100644 lua/fff/list_separator.lua create mode 100644 lua/fff/location_utils.lua create mode 100644 lua/fff/main.lua create mode 100644 lua/fff/picker_ui/README.md create mode 100644 lua/fff/picker_ui/combo_renderer.lua create mode 100644 lua/fff/picker_ui/file_renderer.lua create mode 100644 lua/fff/picker_ui/grep_renderer.lua create mode 100644 lua/fff/picker_ui/layout_manager.lua create mode 100644 lua/fff/picker_ui/list_renderer.lua create mode 100644 lua/fff/picker_ui/navigation.lua create mode 100644 lua/fff/picker_ui/picker_ui.lua create mode 100644 lua/fff/picker_ui/picker_ui_state.lua create mode 100644 lua/fff/picker_ui/preview_manager.lua create mode 100644 lua/fff/picker_ui/renderer.lua create mode 100644 lua/fff/picker_ui/search_manager.lua create mode 100644 lua/fff/picker_ui/ui_creator.lua create mode 100644 lua/fff/picker_ui/utils.lua create mode 100644 lua/fff/rust/init.lua create mode 100644 lua/fff/scrollbar.lua create mode 100644 lua/fff/treesitter_hl.lua create mode 100644 lua/fff/utils.lua create mode 100644 lua/fff/utils/fs.lua create mode 100644 lua/fff/utils/system.lua create mode 100644 lua/fff/utils/version.lua create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 packages/fff-bin-darwin-arm64/package.json create mode 100644 packages/fff-bin-darwin-x64/package.json create mode 100644 packages/fff-bin-linux-arm64-gnu/package.json create mode 100644 packages/fff-bin-linux-arm64-musl/package.json create mode 100644 packages/fff-bin-linux-x64-gnu/package.json create mode 100644 packages/fff-bin-linux-x64-musl/package.json create mode 100644 packages/fff-bin-win32-arm64/package.json create mode 100644 packages/fff-bin-win32-x64/package.json create mode 100644 packages/fff-bun/.npmignore create mode 100644 packages/fff-bun/README.md create mode 100644 packages/fff-bun/bun.lock create mode 100644 packages/fff-bun/examples/glob-bench.ts create mode 100755 packages/fff-bun/examples/grep.ts create mode 100755 packages/fff-bun/examples/search.ts create mode 100644 packages/fff-bun/package.json create mode 100644 packages/fff-bun/src/download.ts create mode 100644 packages/fff-bun/src/embed.d.ts create mode 100644 packages/fff-bun/src/embedded.ts create mode 100644 packages/fff-bun/src/fff-api.ts create mode 100644 packages/fff-bun/src/ffi.ts create mode 100644 packages/fff-bun/src/finder.ts create mode 100644 packages/fff-bun/src/index.ts create mode 100644 packages/fff-bun/src/platform.ts create mode 100644 packages/fff-bun/test.ts create mode 100644 packages/fff-bun/test/git-lifecycle.test.ts create mode 100644 packages/fff-bun/test/index.test.ts create mode 100644 packages/fff-bun/tsconfig.json create mode 100644 packages/fff-node/README.md create mode 100644 packages/fff-node/package.json create mode 100644 packages/fff-node/scripts/cli.ts create mode 100644 packages/fff-node/scripts/postinstall.ts create mode 100644 packages/fff-node/src/binary.ts create mode 100644 packages/fff-node/src/fff-api.ts create mode 100644 packages/fff-node/src/ffi.ts create mode 100644 packages/fff-node/src/finder.ts create mode 100644 packages/fff-node/src/index.ts create mode 100644 packages/fff-node/src/platform.ts create mode 100644 packages/fff-node/test/bench.mjs create mode 100644 packages/fff-node/test/demo-grep.mjs create mode 100644 packages/fff-node/test/e2e.mjs create mode 100644 packages/fff-node/test/stress-515.mjs create mode 100644 packages/fff-node/tsconfig.json create mode 100644 packages/fff-python/.python-version create mode 100644 packages/fff-python/README.md create mode 100644 packages/fff-python/examples/basic.py create mode 100644 packages/fff-python/pyproject.toml create mode 100644 packages/fff-python/src/fff/__init__.py create mode 100644 packages/fff-python/src/fff/__init__.pyi create mode 100644 packages/fff-python/src/fff/py.typed create mode 100644 packages/fff-python/tests/test_finder.py create mode 100644 packages/fff-python/uv.lock create mode 100644 packages/pi-fff/README.md create mode 100644 packages/pi-fff/package.json create mode 100644 packages/pi-fff/src/index.ts create mode 100644 packages/pi-fff/src/query.ts create mode 100644 packages/pi-fff/test/extension.test.ts create mode 100644 packages/pi-fff/test/query.test.ts create mode 100644 packages/pi-fff/tsconfig.json create mode 100644 packages/shared/fff-api.ts create mode 100644 plugin/fff.lua create mode 100644 rust-toolchain.toml create mode 100644 scripts/analyze-results.py create mode 100644 scripts/benchmark-claude.sh create mode 100644 scripts/determine-version.lua create mode 100755 scripts/diagnose_lockmdb.py create mode 100755 scripts/release.sh create mode 100644 tests/clear_cache_spec.lua create mode 100644 tests/fff_core_spec.lua create mode 100644 tests/fresh_db_open_test.lua create mode 100644 tests/grep_jump_file_spec.lua create mode 100644 tests/minimal_init.lua create mode 100644 tests/picker_dir_resolution_spec.lua create mode 100644 tests/picker_ui_snap.lua create mode 100644 tests/programmatic_search_spec.lua create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---combo---boost_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---combo---boost_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---debug---file_info_panel_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---debug---file_info_panel_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---debug_narrow---file_info_panel_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---debug_narrow---file_info_panel_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---debug_wide---file_info_panel_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---debug_wide---file_info_panel_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---cursor_second_item_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---cursor_second_item_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---empty_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---empty_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---no_results_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---no_results_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---query_main_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---default---query_main_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---cursor_second_item create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---cursor_second_item_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---cursor_second_item_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---empty_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---empty_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---no_results_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---no_results_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---query_main_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---narrow---query_main_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---scrollbar---next_page_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---scrollbar---next_page_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---cursor_second_item_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---cursor_second_item_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---empty_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---empty_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---no_results_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---no_results_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---query_main_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---wide---query_main_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---cursor_second_item create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---cursor_second_item_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---cursor_second_item_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---empty_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---empty_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---no_results_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---no_results_top create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---query_main_bottom create mode 100644 tests/screenshots/tests-picker_ui_snap.lua---xwide---query_main_top create mode 100644 tests/snapshot/fixture.lua create mode 100644 tests/test_cd_during_post_scan.lua create mode 100755 tests/test_lazy_async_bug.sh create mode 100644 tests/version_spec.lua diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..74e8438 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,16 @@ +[target.'cfg(target_os = "macos")'] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] + +[target.x86_64-unknown-linux-musl] +rustflags = ["-C", "target-feature=-crt-static"] + +[target.aarch64-unknown-linux-musl] +rustflags = ["-C", "target-feature=-crt-static"] + +# Android/Termux: no hardcoded linker so native Termux builds use the system cc. +# For CI cross-compilation the linker is set via CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER env var. +[target.aarch64-linux-android] +rustflags = ["-C", "link-args=-rdynamic"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..3813ba8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,58 @@ +name: Bug report +description: Report a crash, hang, or incorrect behavior in fff (any frontend — nvim plugin, Node/Bun SDK, MCP server, C SDK). +title: "[Bug]: " +labels: ["bug"] +body: + - type: dropdown + id: frontend + attributes: + label: Which fff frontend? + options: + - Neovim plugin (fff.nvim) + - MCP server (fff-mcp) + - Node SDK (@ff-labs/fff-node) + - Bun SDK + - C SDK (libfff) + - Other / multiple + validations: + required: true + + - type: textarea + id: logs + attributes: + label: has logs + description: | + Attach your fff log file — the single most useful thing for debugging. + + fff writes a fresh log file on every process startup, named `fff++.log`, and keeps the last 20. Find the file matching your crashed/buggy run and drag-and-drop it here (or paste its contents). + + Where the log files live: + + | Frontend | Linux / macOS | Windows | + |---|---|---| + | Neovim plugin | `~/.local/state/nvim/log/fff+*.log` | `%LOCALAPPDATA%\nvim-data\log\fff+*.log` | + | MCP server (`fff-mcp`) | `~/.cache/fff_mcp+*.log` (override with `--log-file`) | `%LOCALAPPDATA%\fff_mcp+*.log` | + | Node / Bun SDK | path you passed as `logFilePath` to `FileFinder.create({...})` | same | + | C SDK | path you passed as `log_file_path` in `FffCreateOptions` | same | + + Neovim users: run `:FFFOpenLog` to open the current session's log directly. + + Also some useful commands: + + ```sh + # Neovim plugin + ls -t ~/.local/state/nvim/log/fff+*.log | head -1 + + # MCP server + ls -t ~/.cache/fff_mcp+*.log | head -1 + ``` + validations: + required: false + + - type: textarea + id: body + attributes: + label: Description + description: Please provide as much helpful information as you can + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0d3dd56 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discussion / question + url: https://github.com/dmtrKovalenko/fff/discussions + about: For usage questions, design discussion, or anything that's not a bug or feature request. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..caa70e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: Feature request +description: Suggest something new for fff +title: "[Suggestion]: " +labels: ["enhancement"] +body: + - type: dropdown + id: frontend + attributes: + label: Which fff frontend(s)? + multiple: true + options: + - Neovim plugin (fff.nvim) + - MCP server (fff-mcp) + - Node SDK (@ff-labs/fff-node) + - Bun SDK (@ff-labs/fff-bun) + - C lib (libfff) + - Core or Rust crate + validations: + required: true + + - type: textarea + id: problem + attributes: + label: What problem are you trying to solve? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: If you have an idea of the shape of the API, describe it here. + validations: + required: false diff --git a/.github/workflows/external-tests.yml b/.github/workflows/external-tests.yml new file mode 100644 index 0000000..7c21232 --- /dev/null +++ b/.github/workflows/external-tests.yml @@ -0,0 +1,217 @@ +name: e2e Tests + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + +env: + CARGO_TERM_COLOR: always + MACOSX_DEPLOYMENT_TARGET: "13" + # Force Node 24 for all JS-based actions to avoid the libuv + # process_title assertion crash on Windows (known Node 20 bug). + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + lua-tests: + name: e2e (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + # e2e tests could be flaky on CI so we do not block release creation if they failed + continue-on-error: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + - os: macos-latest + - os: windows-latest + target: x86_64-pc-windows-msvc + steps: + - uses: actions/checkout@v5 + - uses: oven-sh/setup-bun@v2 + - uses: actions/setup-node@v6 + - name: Install Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1.15.4 + with: + cache: true + cache-on-failure: false + cache-key: "v2-lua-e2e" + rustflags: "" + target: ${{ matrix.target || '' }} + + - name: Build Rust binary (Windows) + if: matrix.target + run: cargo build --release --target ${{ matrix.target }} -p fff-nvim --no-default-features --features zlob + + - name: Copy binary to target/release (Windows) + if: matrix.target + shell: bash + run: | + cp target/${{ matrix.target }}/release/fff_nvim.dll target/release/fff_nvim.dll + + - name: Verify Windows DLL has no unexpected dependencies + if: matrix.target + shell: pwsh + run: | + # Find dumpbin via vswhere (always available on GitHub Actions Windows runners) + $vsPath = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath + $dumpbin = Get-ChildItem "$vsPath" -Recurse -Filter "dumpbin.exe" | Select-Object -First 1 + if (-not $dumpbin) { Write-Error "dumpbin.exe not found"; exit 1 } + + $deps = & $dumpbin.FullName /DEPENDENTS target\release\fff_nvim.dll | Out-String + Write-Host $deps + # zlob must be statically linked - fail if zlob.dll appears as a dependency + if ($deps -match 'zlob\.dll') { + Write-Error "fff_nvim.dll has unexpected dynamic dependency on zlob.dll - zlob should be statically linked" + exit 1 + } + + - name: Build Rust binary + if: ${{ !matrix.target }} + run: cargo build --release -p fff-nvim --no-default-features --features zlob + + - name: Install Neovim + uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: v0.10.4 + + - name: Clone plenary.nvim + shell: bash + run: git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ../plenary.nvim + + - name: Run Lua tests + shell: bash + run: make test-lua + + - name: Dump fff trace log on failure + if: failure() + shell: bash + run: | + # init_tracing writes session files named fff-test++.log + found=0 + for f in fff-test*.log; do + [ -f "$f" ] || continue + found=1 + echo "=== $f ===" + cat "$f" + done + if [ "$found" = 0 ]; then + echo "(no log file produced)" + fi + + - name: Run version resolution tests + shell: bash + run: make test-version + + - name: Run non windows tests + shell: bash + if: ${{ matrix.os != 'windows-latest' }} + run: | + make test-bun + make test-c-api + + - name: Verify bun --compile + shell: bash + run: make test-bun-compile + + - name: Install Node.js + if: ${{ matrix.os != 'ubuntu-latest' }} + uses: actions/setup-node@v6 + with: + node-version: "25" + + - name: Install node dependencies + shell: bash + run: cd packages/fff-node && npm install + + - name: Run node tests + shell: bash + run: make test-node + + # Regression for https://github.com/dmtrKovalenko/fff/issues/480: build & + # run @ff-labs/fff-node end-to-end on real Alpine Linux (musl). Forces + # findBinary() through the npm-package resolver so detectLinuxLibc() + # actually runs. + alpine-musl: + name: e2e (alpine-musl) + runs-on: ubuntu-latest + container: node:22-alpine + continue-on-error: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} + defaults: + run: + shell: sh + steps: + - name: Install build deps + run: apk add --no-cache git rust cargo musl-dev + + - uses: actions/checkout@v5 + + # libgit2 refuses repos owned by a different user; checkout in a + # container can land at a uid mismatch, so opt every dir in. + - name: Mark workspace safe for git + run: git config --global --add safe.directory '*' + + - name: Sanity check libc is musl + run: | + if ! ldd --version 2>&1 | grep -qi musl; then + echo "FAIL: container is not running musl libc" + exit 1 + fi + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: alpine-musl-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + alpine-musl-cargo- + + - name: Build libfff_c (musl) + run: cargo build --release -p fff-c + + - name: Install workspace npm deps + run: npm install --no-package-lock + + # Upstream @yuuang/ffi-rs-linux-x64-musl ships with libc:"glibc" in + # its package.json (a publishing bug in ffi-rs), so npm filters it + # out. Force-install it so the FFI runtime is present on Alpine. + - name: Install ffi-rs musl runtime + run: | + FFI_RS_VERSION=$(node -p "require('ffi-rs/package.json').version") + npm install --no-package-lock --no-save --force \ + "@yuuang/ffi-rs-linux-x64-musl@${FFI_RS_VERSION}" + + # Stage the freshly built libfff_c.so as the platform npm package + # so findBinary() resolves through the @ff-labs/fff-bin-* path — + # this is what exercises detectLinuxLibc(). + - name: Stage musl bin package + run: | + PKG_DIR=node_modules/@ff-labs/fff-bin-linux-x64-musl + mkdir -p "$PKG_DIR" + cp target/release/libfff_c.so "$PKG_DIR/libfff_c.so" + cat >"$PKG_DIR/package.json" <<'JSON' + { "name": "@ff-labs/fff-bin-linux-x64-musl", "version": "0.0.0" } + JSON + + - name: Build fff-node + working-directory: packages/fff-node + run: npm run build + + - name: Run fff-node e2e suite + working-directory: packages/fff-node + run: node test/e2e.mjs diff --git a/.github/workflows/lua.yml b/.github/workflows/lua.yml new file mode 100644 index 0000000..5455d13 --- /dev/null +++ b/.github/workflows/lua.yml @@ -0,0 +1,56 @@ +name: Lua CI + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + +jobs: + lua-ls: + name: lua-language-server type check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Install Neovim + run: | + curl -L https://github.com/neovim/neovim/releases/download/v0.11.5/nvim-linux-x86_64.tar.gz -o /opt/nvim.tar.gz + mkdir /opt/nvim + tar xzf /opt/nvim.tar.gz -C /opt/nvim + mv /opt/nvim/nvim-linux-x86_64/* /opt/nvim + echo "/opt/nvim/bin" >> $GITHUB_PATH + + - name: Install lua-language-server + run: | + curl -L "https://github.com/LuaLS/lua-language-server/releases/download/3.17.1/lua-language-server-3.17.1-linux-x64.tar.gz" -o /opt/lls.tar.gz + mkdir /opt/lls + tar -xzf /opt/lls.tar.gz -C /opt/lls + echo "/opt/lls/bin" >> $GITHUB_PATH + + - name: Clone snacks.nvim + run: git clone --depth=1 https://github.com/folke/snacks.nvim /opt/snacks.nvim + + - name: Run lua-language-server + run: lua-language-server --configpath .luarc.ci.json --check=. + + luacheck: + name: luacheck lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Install luacheck + run: | + sudo apt-get update -qq + sudo apt-get install -y luarocks + sudo luarocks install luacheck + + - name: Run luacheck + run: luacheck lua/ diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000..155eeca --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,37 @@ +name: Nix CI + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + +jobs: + check: + runs-on: ubuntu-22.04 + permissions: + id-token: "write" + contents: "read" + steps: + - uses: actions/checkout@v5 + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + - uses: DeterminateSystems/flake-checker-action@main + + - name: Run `nix flake check` + run: nix flake check + + - name: Run `nix build` + run: nix build + + - name: Run `nix build .#fff-nvim` + run: nix build .#fff-nvim + + - name: Run `nix run .#release` + run: nix run .#release diff --git a/.github/workflows/panvimdoc.yaml b/.github/workflows/panvimdoc.yaml new file mode 100644 index 0000000..d8d63a9 --- /dev/null +++ b/.github/workflows/panvimdoc.yaml @@ -0,0 +1,85 @@ +on: + schedule: + - cron: "0 4 * * *" + workflow_dispatch: + +name: docs + +jobs: + docs: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v5 + with: + ref: main + fetch-depth: 2 + + - name: Extract Neovim section from README.md + run: | + awk ' + /^
/ { capture=1; next } + capture && /^<\/details>/ { capture=0; exit } + capture && /^$/ { next } + capture && /^<\/summary>$/ { next } + capture && /

.*<\/h2>/ { + gsub(/<\/?h2>/, "") + sub(/^[[:space:]]+/, "") + print "# " $0 + print "" + print "The best file search picker for Neovim. Frecency-ranked, typo-resistant, git-award, very fast." + print "" + next + } + capture { print } + ' README.md > .panvimdoc-input.md + test -s .panvimdoc-input.md + + - name: panvimdoc + uses: kdheepak/panvimdoc@main + with: + vimdoc: fff.nvim + pandoc: .panvimdoc-input.md + version: "Neovim >= 0.10.0" + demojify: true + treesitter: true + + - name: Cleanup intermediate file + run: rm -f .panvimdoc-input.md + + # panvimdoc stamps "Last change: " every run, so a daily cron always + # produces a one-line diff. Skip the PR unless a non-date line changed. + - name: Detect real doc changes + id: docdiff + run: | + if git diff --quiet -I 'Last change:' -- doc/fff.nvim.txt; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create pull request + id: cpr + if: steps.docdiff.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v7 + with: + branch: bot/regenerate-vimdoc + token: ${{ secrets.GUSTAV_PAT }} + delete-branch: true + title: "chore: regenerate Neovim vimdoc" + commit-message: | + chore: regenerate Neovim vimdoc + + Co-authored-by: Dmitriy Kovalenko + author: "gustav-fff <66k7bxj9m6@privaterelay.appleid.com>" + committer: "gustav-fff <66k7bxj9m6@privaterelay.appleid.com>" + body: Automated vimdoc regeneration from README.md, scribed by Gustav. + add-paths: doc/fff.nvim.txt + + - name: Enable auto-merge + if: steps.cpr.outputs.pull-request-number + env: + GH_TOKEN: ${{ secrets.GUSTAV_PAT }} + run: gh pr merge --auto --squash "${{ steps.cpr.outputs.pull-request-number }}" diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..43426ef --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,48 @@ +name: Python CI + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + +env: + CARGO_TERM_COLOR: always + MACOSX_DEPLOYMENT_TARGET: "13.0" + +jobs: + test: + name: Python bindings (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.11.14" + enable-cache: true + + - name: Build and test Python bindings + working-directory: packages/fff-python + shell: bash + run: | + uv sync --all-extras + uv run maturin develop --release + uv run pytest -v diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..519f48b --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,757 @@ +name: Build & Publish + +on: + push: + branches: [main, fix/use-trusted-publishing] + tags: + - "v*" + pull_request: + workflow_dispatch: + inputs: + publish_pypi: + description: "Manually build and publish Python wheels to PyPI" + required: false + default: false + type: boolean + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + build-nvim: + name: Build Neovim ${{ matrix.target }} + runs-on: ${{ matrix.os }} + permissions: + contents: read + id-token: write + strategy: + matrix: + include: + ## Linux builds (using cargo-zigbuild) + # Glibc 2.31 (Ubuntu 20.04, Debian 11, RHEL 9). + # Rust 1.91+ requires glibc >= 2.31 for std::sys::random::getrandom, + # copy_file_range, and statx; earlier targets (2.17) no longer link. + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + zigbuild_target: x86_64-unknown-linux-gnu.2.31 + artifact_name: target/x86_64-unknown-linux-gnu/ci/libfff_nvim.so + ext: so + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + zigbuild_target: aarch64-unknown-linux-gnu.2.31 + artifact_name: target/aarch64-unknown-linux-gnu/ci/libfff_nvim.so + ext: so + # Musl (statically linked) + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + artifact_name: target/x86_64-unknown-linux-musl/ci/libfff_nvim.so + ext: so + - os: ubuntu-latest + target: aarch64-unknown-linux-musl + artifact_name: target/aarch64-unknown-linux-musl/ci/libfff_nvim.so + ext: so + + ## Android (Termux) + - os: ubuntu-latest + target: aarch64-linux-android + artifact_name: target/aarch64-linux-android/ci/libfff_nvim.so + ext: so + + ## macOS builds + - os: macos-latest + target: x86_64-apple-darwin + artifact_name: target/x86_64-apple-darwin/ci/libfff_nvim.dylib + ext: dylib + - os: macos-latest + target: aarch64-apple-darwin + artifact_name: target/aarch64-apple-darwin/ci/libfff_nvim.dylib + ext: dylib + + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact_name: target/x86_64-pc-windows-msvc/ci/fff_nvim.dll + ext: dll + - os: windows-latest + target: aarch64-pc-windows-msvc + artifact_name: target/aarch64-pc-windows-msvc/ci/fff_nvim.dll + ext: dll + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install Rust + run: rustup target add ${{ matrix.target }} + + - name: Install Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install cargo-zigbuild + if: contains(matrix.os, 'ubuntu') + run: cargo install cargo-zigbuild + + - name: Build for Linux + if: contains(matrix.os, 'ubuntu') && !contains(matrix.target, 'android') + run: | + cargo zigbuild --profile ci --target ${{ matrix.zigbuild_target || matrix.target }} -p fff-nvim --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.${{ matrix.ext }}" + + - name: Build for Android (Termux) + if: contains(matrix.target, 'android') + run: | + NDK_BIN="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" + + # NDK clang for C deps (libgit2, lmdb, blake3) that need Bionic sysroot headers + export CC_aarch64_linux_android="$NDK_BIN/aarch64-linux-android24-clang" + export CXX_aarch64_linux_android="$NDK_BIN/aarch64-linux-android24-clang++" + export AR_aarch64_linux_android="$NDK_BIN/llvm-ar" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_BIN/aarch64-linux-android24-clang" + + cargo build --profile ci --target ${{ matrix.target }} -p fff-nvim --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.${{ matrix.ext }}" + + - name: Build for macOS + if: contains(matrix.os, 'macos') + run: | + MACOSX_DEPLOYMENT_TARGET="13" cargo build --profile ci --target ${{ matrix.target }} -p fff-nvim --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.${{ matrix.ext }}" + + - name: Ad-hoc sign macOS binary + if: contains(matrix.os, 'macos') + run: codesign --force --sign - "${{ matrix.target }}.${{ matrix.ext }}" + + - name: Build for Windows + if: contains(matrix.os, 'windows') + shell: bash + run: | + cargo build --profile ci --target ${{ matrix.target }} -p fff-nvim --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.${{ matrix.ext }}" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: nvim-${{ matrix.target }} + path: ${{ matrix.target }}.* + + build-c: + name: Build C FFI ${{ matrix.target }} + runs-on: ${{ matrix.os }} + permissions: + contents: read + strategy: + matrix: + include: + ## Linux builds + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + zigbuild_target: x86_64-unknown-linux-gnu.2.31 + artifact_name: target/x86_64-unknown-linux-gnu/ci/libfff_c.so + npm_package: fff-bin-linux-x64-gnu + lib_filename: libfff_c.so + ext: so + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + zigbuild_target: aarch64-unknown-linux-gnu.2.31 + artifact_name: target/aarch64-unknown-linux-gnu/ci/libfff_c.so + npm_package: fff-bin-linux-arm64-gnu + lib_filename: libfff_c.so + ext: so + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + artifact_name: target/x86_64-unknown-linux-musl/ci/libfff_c.so + npm_package: fff-bin-linux-x64-musl + lib_filename: libfff_c.so + ext: so + - os: ubuntu-latest + target: aarch64-unknown-linux-musl + artifact_name: target/aarch64-unknown-linux-musl/ci/libfff_c.so + npm_package: fff-bin-linux-arm64-musl + lib_filename: libfff_c.so + ext: so + + ## Android (Termux) + - os: ubuntu-latest + target: aarch64-linux-android + artifact_name: target/aarch64-linux-android/ci/libfff_c.so + lib_filename: libfff_c.so + ext: so + + ## macOS builds + - os: macos-latest + target: x86_64-apple-darwin + artifact_name: target/x86_64-apple-darwin/ci/libfff_c.dylib + npm_package: fff-bin-darwin-x64 + lib_filename: libfff_c.dylib + ext: dylib + - os: macos-latest + target: aarch64-apple-darwin + artifact_name: target/aarch64-apple-darwin/ci/libfff_c.dylib + npm_package: fff-bin-darwin-arm64 + lib_filename: libfff_c.dylib + ext: dylib + + ## Windows builds + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact_name: target/x86_64-pc-windows-msvc/ci/fff_c.dll + npm_package: fff-bin-win32-x64 + lib_filename: fff_c.dll + ext: dll + - os: windows-latest + target: aarch64-pc-windows-msvc + artifact_name: target/aarch64-pc-windows-msvc/ci/fff_c.dll + npm_package: fff-bin-win32-arm64 + lib_filename: fff_c.dll + ext: dll + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install Rust + run: rustup target add ${{ matrix.target }} + + - name: Install Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install cargo-zigbuild + if: contains(matrix.os, 'ubuntu') + run: cargo install cargo-zigbuild + + - name: Build for Linux + if: contains(matrix.os, 'ubuntu') && !contains(matrix.target, 'android') + run: | + cargo zigbuild --profile ci --target ${{ matrix.zigbuild_target || matrix.target }} -p fff-c --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "c-lib-${{ matrix.target }}.${{ matrix.ext }}" + + - name: Build for Android (Termux) + if: contains(matrix.target, 'android') + run: | + NDK_BIN="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" + + export CC_aarch64_linux_android="$NDK_BIN/aarch64-linux-android24-clang" + export CXX_aarch64_linux_android="$NDK_BIN/aarch64-linux-android24-clang++" + export AR_aarch64_linux_android="$NDK_BIN/llvm-ar" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_BIN/aarch64-linux-android24-clang" + + cargo build --profile ci --target ${{ matrix.target }} -p fff-c --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "c-lib-${{ matrix.target }}.${{ matrix.ext }}" + + - name: Build for macOS + if: contains(matrix.os, 'macos') + run: | + MACOSX_DEPLOYMENT_TARGET="13" cargo build --profile ci --target ${{ matrix.target }} -p fff-c --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "c-lib-${{ matrix.target }}.${{ matrix.ext }}" + + - name: Ad-hoc sign macOS binary + if: contains(matrix.os, 'macos') + run: codesign --force --sign - "c-lib-${{ matrix.target }}.${{ matrix.ext }}" + + - name: Build for Windows + if: contains(matrix.os, 'windows') + shell: bash + run: | + cargo build --profile ci --target ${{ matrix.target }} -p fff-c --no-default-features --features zlob + mv "${{ matrix.artifact_name }}" "c-lib-${{ matrix.target }}.${{ matrix.ext }}" + + - name: Prepare npm package + if: "!contains(matrix.target, 'android')" + shell: bash + run: | + # Copy the built binary into the platform npm package directory + cp "c-lib-${{ matrix.target }}.${{ matrix.ext }}" "packages/${{ matrix.npm_package }}/${{ matrix.lib_filename }}" + + - name: Upload C library artifact + uses: actions/upload-artifact@v4 + with: + name: c-lib-${{ matrix.target }} + path: c-lib-${{ matrix.target }}.* + + - name: Upload npm package artifact + if: "!contains(matrix.target, 'android')" + uses: actions/upload-artifact@v4 + with: + name: npm-${{ matrix.npm_package }} + path: packages/${{ matrix.npm_package }}/ + + build-mcp: + name: Build MCP ${{ matrix.target }} + runs-on: ${{ matrix.os }} + permissions: + contents: read + strategy: + matrix: + include: + ## Linux builds (using cargo-zigbuild) + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + zigbuild_target: x86_64-unknown-linux-gnu.2.31 + artifact_name: target/x86_64-unknown-linux-gnu/ci/fff-mcp + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + zigbuild_target: aarch64-unknown-linux-gnu.2.31 + artifact_name: target/aarch64-unknown-linux-gnu/ci/fff-mcp + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + artifact_name: target/x86_64-unknown-linux-musl/ci/fff-mcp + - os: ubuntu-latest + target: aarch64-unknown-linux-musl + artifact_name: target/aarch64-unknown-linux-musl/ci/fff-mcp + + ## macOS builds + - os: macos-latest + target: x86_64-apple-darwin + artifact_name: target/x86_64-apple-darwin/ci/fff-mcp + - os: macos-latest + target: aarch64-apple-darwin + artifact_name: target/aarch64-apple-darwin/ci/fff-mcp + + ## Windows builds + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact_name: target/x86_64-pc-windows-msvc/ci/fff-mcp.exe + - os: windows-latest + target: aarch64-pc-windows-msvc + artifact_name: target/aarch64-pc-windows-msvc/ci/fff-mcp.exe + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install Rust + run: rustup target add ${{ matrix.target }} + + - name: Install Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install cargo-zigbuild + if: contains(matrix.os, 'ubuntu') + run: cargo install cargo-zigbuild + + - name: Build for Linux + if: contains(matrix.os, 'ubuntu') + run: | + cargo zigbuild --profile ci --target ${{ matrix.zigbuild_target || matrix.target }} -p fff-mcp --no-default-features --features zlob + cp "${{ matrix.artifact_name }}" "fff-mcp-${{ matrix.target }}" + + - name: Build for macOS + if: contains(matrix.os, 'macos') + run: | + MACOSX_DEPLOYMENT_TARGET="13" cargo build --profile ci --target ${{ matrix.target }} -p fff-mcp --no-default-features --features zlob + cp "${{ matrix.artifact_name }}" "fff-mcp-${{ matrix.target }}" + + - name: Ad-hoc sign macOS binary + if: contains(matrix.os, 'macos') + run: codesign --force --sign - "fff-mcp-${{ matrix.target }}" + + - name: Build for Windows + if: contains(matrix.os, 'windows') + shell: bash + run: | + cargo build --profile ci --target ${{ matrix.target }} -p fff-mcp --no-default-features --features zlob + cp "${{ matrix.artifact_name }}" "fff-mcp-${{ matrix.target }}.exe" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: mcp-${{ matrix.target }} + path: fff-mcp-${{ matrix.target }}* + + build-python: + name: Build Python wheels ${{ matrix.target }} (${{ matrix.os }}) + # Wheels are release artifacts; PR validation uses the develop build in + # python.yml, so skip the cross-compile matrix on pull requests. + if: github.event_name != 'pull_request' + runs-on: ${{ matrix.os }} + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64 + container: "off" + - os: ubuntu-latest + target: aarch64 + container: "off" + - os: macos-latest + target: x86_64 + - os: macos-latest + target: aarch64 + - os: windows-latest + target: x86_64 + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install cargo-zigbuild + if: contains(matrix.os, 'ubuntu') + run: cargo install cargo-zigbuild + + - name: Install aarch64 cross compiler + if: matrix.target == 'aarch64' && contains(matrix.os, 'ubuntu') + run: | + sudo apt-get update -qq + sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + + - name: Build wheels + uses: PyO3/maturin-action@v1 + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc + CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ + AR_aarch64_unknown_linux_gnu: aarch64-linux-gnu-ar + with: + target: ${{ matrix.target }} + args: --release --out dist --no-default-features --features zlob + sccache: "true" + working-directory: packages/fff-python + container: ${{ matrix.container || '' }} + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: python-wheels-${{ matrix.os }}-${{ matrix.target }} + path: packages/fff-python/dist/ + + build-python-sdist: + name: Build Python sdist + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + working-directory: packages/fff-python + + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: python-sdist + path: packages/fff-python/dist/ + + release: + name: Release + needs: [build-nvim, build-c, build-mcp, build-python, build-python-sdist] + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/fix/use-trusted-publishing' || startsWith(github.ref, 'refs/tags/v')) + permissions: + contents: write + steps: + - uses: actions/checkout@v5 + + - name: Install Lua + uses: leafo/gh-actions-lua@v12 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: ./binaries + + - name: Flatten and rename Neovim artifacts + working-directory: ./binaries + run: | + # Move nvim artifacts to root level with original naming + for dir in nvim-*/; do + target="${dir#nvim-}" + target="${target%/}" + for file in "$dir"*; do + if [ -f "$file" ]; then + filename=$(basename "$file") + mv "$file" "./$filename" + fi + done + rmdir "$dir" 2>/dev/null || true + done + + - name: Flatten C library artifacts + working-directory: ./binaries + run: | + # Move c-lib artifacts to root level + for dir in c-lib-*/; do + for file in "$dir"*; do + if [ -f "$file" ]; then + filename=$(basename "$file") + mv "$file" "./$filename" + fi + done + rmdir "$dir" 2>/dev/null || true + done + + - name: Flatten MCP artifacts + working-directory: ./binaries + run: | + for dir in mcp-*/; do + for file in "$dir"*; do + if [ -f "$file" ]; then + filename=$(basename "$file") + mv "$file" "./$filename" + fi + done + rmdir "$dir" 2>/dev/null || true + done + + - name: Move Python wheels to release directory + working-directory: ./binaries + run: | + mkdir -p python + for dir in python-wheels-*/ python-sdist/; do + [ -d "$dir" ] || continue + for file in "$dir"*; do + if [ -f "$file" ]; then + mv "$file" "python/$(basename "$file")" + fi + done + rmdir "$dir" 2>/dev/null || true + done + + - name: Remove npm package artifacts from release binaries + working-directory: ./binaries + run: | + rm -rf npm-* + + - name: Generate checksums + working-directory: ./binaries + run: | + ls -la + for file in * python/*; do + if [ -f "$file" ] && [[ ! "$file" == *.sha256 ]]; then + sha256sum "$file" > "${file}.sha256" + fi + done + + - name: Determine version + id: version + run: lua scripts/determine-version.lua + + # Nightlies publish to a permanent per-sha tag (release_tag == version) so + # pinned/stale installs always fetch the binary built for their own commit. + # The rolling `nightly` tag is also moved to HEAD for "give me latest" tooling. + - name: Move rolling nightly tag to current commit + if: steps.version.outputs.is_release != 'true' + run: | + git tag -f nightly "${{ github.sha }}" + git push -f origin refs/tags/nightly + + - name: Upload Release Assets + uses: softprops/action-gh-release@v2 + with: + name: "${{ steps.version.outputs.version }}" + tag_name: "${{ steps.version.outputs.release_tag }}" + token: ${{ github.token }} + files: | + ./binaries/* + ./binaries/python/* + draft: false + prerelease: ${{ steps.version.outputs.is_release != 'true' }} + generate_release_notes: ${{ steps.version.outputs.is_release == 'true' }} + body: | + ${{ steps.version.outputs.is_release == 'true' && format('Release {0}', steps.version.outputs.version) || format('Nightly release from commit: {0}', github.sha) }} + + npm packages, rust crates and python wheels are available under this version ${{ steps.version.outputs.version }} + + ## Neovim Plugin + - `{target}.so` / `.dylib` / `.dll` - Lua module for Neovim + + ## C FFI Library (for Bun/Node/Python) + - `c-lib-{target}.so` / `.dylib` / `.dll` - C FFI library + + ## MCP Server + - `fff-mcp-{target}` - MCP server binary + + ## Python Package + - `python/*.whl` / `python/*.tar.gz` - Python wheels and sdist + - Install from PyPI: `pip install fff-search` (when published) + + Update mcp via: + ```sh + curl -fsSL https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.sh | bash + ``` + + - name: Bump Homebrew formula (uses local checksums) + if: steps.version.outputs.is_release == 'true' && github.repository_owner == 'dmtrKovalenko' + run: make bump-homebrew-formula VERSION="${{ steps.version.outputs.version }}" BINARIES_DIR=./binaries + + - name: Pin SHAs in install-mcp.sh (uses local checksums) + if: steps.version.outputs.is_release == 'true' && github.repository_owner == 'dmtrKovalenko' + run: make bump-install-mcp-sh VERSION="${{ steps.version.outputs.version }}" BINARIES_DIR=./binaries + + - name: Commit formula + installer bump to main + # Uses the default GITHUB_TOKEN configured by actions/checkout above. + # Requires github-actions[bot] in the main branch-protection bypass list. + if: steps.version.outputs.is_release == 'true' && github.repository_owner == 'dmtrKovalenko' + uses: stefanzweifel/git-auto-commit-action@v5 + with: + # Workflow runs on detached HEAD at the v* tag — explicit target needed. + branch: main + commit_message: "chore: bump fff-mcp release artifacts to v${{ steps.version.outputs.version }}" + file_pattern: "Formula/fff-mcp.rb install-mcp.sh" + commit_user_name: github-actions[bot] + commit_user_email: 41898282+github-actions[bot]@users.noreply.github.com + + pypi-publish: + name: Publish Python wheels to PyPI + needs: [build-python, build-python-sdist] + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/fix/use-trusted-publishing' || startsWith(github.ref, 'refs/tags/v')) + environment: + name: pypi + url: https://pypi.org/p/fff-search + permissions: + contents: read + id-token: write + steps: + - name: Download Python wheels and sdist + uses: actions/download-artifact@v4 + with: + pattern: python-* + path: dist + merge-multiple: true + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + skip-existing: true + + crates-publish: + name: Publish Rust crates + needs: [build-nvim, build-c, build-mcp] + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/fix/use-trusted-publishing' || startsWith(github.ref, 'refs/tags/v')) + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v5 + - uses: rust-lang/crates-io-auth-action@v1 + id: auth + + - name: Install Lua + uses: leafo/gh-actions-lua@v12 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-edit + run: cargo install cargo-edit --force --locked + + - name: Determine version + id: version + run: lua scripts/determine-version.lua + + - name: Publish crates + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + run: make publish-crates V="${{ steps.version.outputs.version }}" + + npm-publish: + name: Publish npm packages + needs: [build-c] + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/fix/use-trusted-publishing' || startsWith(github.ref, 'refs/tags/v')) + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v5 + + - name: Install Lua + uses: leafo/gh-actions-lua@v12 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "25" + registry-url: "https://registry.npmjs.org" + + - name: Determine version + id: version + run: lua scripts/determine-version.lua + + - name: Download npm package artifacts + uses: actions/download-artifact@v4 + with: + pattern: npm-* + path: ./npm-packages + + - name: Publish platform packages + run: | + VERSION="${{ steps.version.outputs.version }}" + TAG="${{ steps.version.outputs.npm_tag }}" + + for pkg_dir in ./npm-packages/npm-*/; do + if [ -d "$pkg_dir" ]; then + pkg_name=$(node -p "require('${pkg_dir}package.json').name") + echo "Publishing ${pkg_name}@${VERSION} with tag ${TAG}..." + + make set-npm-version PKG="$pkg_dir" VERSION="$VERSION" + + cd "$pkg_dir" + npm publish --tag "$TAG" --access public --provenance + cd - + fi + done + + - name: Publish bun package + run: | + VERSION="${{ steps.version.outputs.version }}" + TAG="${{ steps.version.outputs.npm_tag }}" + + echo "Publishing @ff-labs/fff-bun@${VERSION} with tag ${TAG}..." + make set-npm-version PKG=packages/fff-bun VERSION="$VERSION" + + cd packages/fff-bun + npm publish --tag "$TAG" --access public --provenance + + - name: Publish Node.js package + run: | + VERSION="${{ steps.version.outputs.version }}" + TAG="${{ steps.version.outputs.npm_tag }}" + + echo "Publishing @ff-labs/fff-node@${VERSION} with tag ${TAG}..." + make set-npm-version PKG=packages/fff-node VERSION="$VERSION" + + cd packages/fff-node + npm install + npm run build + npm publish --tag "$TAG" --access public --provenance + + - name: Publish pi-fff package + run: | + VERSION="${{ steps.version.outputs.version }}" + TAG="${{ steps.version.outputs.npm_tag }}" + + echo "Publishing @ff-labs/pi-fff@${VERSION} with tag ${TAG}..." + make set-npm-version PKG=packages/pi-fff VERSION="$VERSION" + + cd packages/pi-fff + npm publish --tag "$TAG" --access public --provenance diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..1d60b0f --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,162 @@ +name: Rust CI + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'doc/**' + +env: + CARGO_TERM_COLOR: always + # Ensure consistent macOS deployment target across all compiled objects + # (Rust, cc-compiled C code, and Zig-compiled zlob) to avoid linker warnings + MACOSX_DEPLOYMENT_TARGET: "13" + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + # Guard against deadlocks in the shared-picker / watcher teardown + # path: a stuck test would otherwise consume a full 6h CI slot. + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + + # Zig is required to compile zlob + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1.15.4 + with: + cache: true + cache-on-failure: true + cache-key: "v1-rust" + components: rustfmt, clippy + + - name: Run tests + run: cargo test --no-default-features --features zlob --workspace --exclude fff-nvim + + stress-test: + name: Fuzz Tests + runs-on: ${{ matrix.os }} + strategy: + # Keep going after one OS fails so we can see whether a bug + # reproduces everywhere or is platform-specific. + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + # Long-running; don't let a stuck watcher thread burn a full CI + # timeout. Two scenarios should finish well under this limit. + timeout-minutes: 20 + env: + FFF_STRESS_CASES: "5" + FFF_STRESS_MIN_OPS: "30" + FFF_STRESS_MAX_OPS: "60" + steps: + - uses: actions/checkout@v5 + + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1.15.4 + with: + cache: true + cache-on-failure: true + cache-key: "v1-rust-stress-${{ matrix.os }}" + components: rustfmt, clippy + + - name: Stress test seeded + shell: bash + run: make test-stress-seeded + + - name: Stress test random + shell: bash + run: make test-stress-random + + - name: Stress test regressions + shell: bash + run: make test-stress-regressions + + - name: Upload proptest regressions on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: proptest-regressions-${{ matrix.os }} + path: crates/fff-core/tests/fuzz_git_watcher_stress.proptest-regressions + if-no-files-found: ignore + + build-i686: + name: Build i686-unknown-linux-gnu + runs-on: ubuntu-latest + # Verifies that fff-search compiles on 32-bit x86, where std::arch::x86_64 + # is unavailable. SIMD paths are disabled on this target; only the scalar + # fallback should build. See issue #656. + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + + - name: Install cross toolchain + run: | + sudo apt-get update + sudo apt-get install -y gcc-multilib g++-multilib + + - name: Install Rust (i686 target) + uses: actions-rust-lang/setup-rust-toolchain@v1.15.4 + with: + target: i686-unknown-linux-gnu + cache: true + cache-on-failure: true + cache-key: "v1-rust-i686" + + - name: Build fff-search for i686 + run: cargo build -p fff-search --target i686-unknown-linux-gnu + + fmt: + name: cargo fmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - name: Check formatting + run: cargo fmt -- --check + + clippy: + name: cargo clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + # Zig is required to compile zlob + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: 0.16.0 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy + + - name: Run clippy + run: cargo clippy --no-default-features --features zlob -- -D warnings diff --git a/.github/workflows/spelling.yaml b/.github/workflows/spelling.yaml new file mode 100644 index 0000000..b3b8d41 --- /dev/null +++ b/.github/workflows/spelling.yaml @@ -0,0 +1,25 @@ +name: Spelling + +permissions: + contents: read + +on: + push: + branches: + - main + pull_request: + +env: + CLICOLOR: 1 + +jobs: + spelling: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Spell Check Repo + uses: crate-ci/typos@685eb3d55be2f85191e8c84acb9f44d7756f84ab # v1.29.4 diff --git a/.github/workflows/stylua.yaml b/.github/workflows/stylua.yaml new file mode 100644 index 0000000..fa7edb1 --- /dev/null +++ b/.github/workflows/stylua.yaml @@ -0,0 +1,37 @@ +name: Stylua + +permissions: + contents: read + +on: + push: + branches: + - main + paths: + - "**/*.lua" + - .stylua.toml + - .github/workflows/stylua.yaml + pull_request: + paths: + - "**/*.lua" + - .stylua.toml + - .github/workflows/stylua.yaml + +env: + CLICOLOR: 1 + +jobs: + stylua: + name: Check lua files using Stylua + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Stylua Check Repo + uses: JohnnyMorganz/stylua-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + args: --color=always --check . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d026b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +doc/tags +big-repo +target/ +.archive.lua +_*.lua +.lazy.lua +dual/ +result +.direnv +.devenv +.repro/ +.wrangler/ +*.so +*.dylib +# all the perf like utility files +*.data +node_modules/ +crates/fff-notify-debouncer-full/ +packages/fff-bun/glob-bench-bin + +dist/ +scripts/benchmark-results/ + +# Native binaries (downloaded at install) +*.dylib +*.so +*.dll +*.pdb + +# Instruments traces +*.trace/ + +# Test logs +fff-test.log + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.pyd +*.egg-info/ +*.egg +.eggs/ +build/ +*.whl +# Virtual environments +.venv/ +venv/ +env/ +ENV/ +# uv +# Testing / linting +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.coverage +htmlcov/ +.tox/ +.nox/ +# IDEs +.idea/ +.vscode/ +*.swp +*.swo +*~ diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..706514b --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,25 @@ +-- luacheck configuration for fff.nvim +-- https://luacheck.readthedocs.io/en/stable/config.html + +-- Neovim globals +globals = { "vim" } + +-- Standard library +std = "luajit" + +-- Ignore line length (handled by stylua) +max_line_length = false + +-- Ignore unused self argument in methods +self = false + +-- Files/directories to ignore +exclude_files = { + ".luarocks/", +} + +-- Warn about unused variables, but allow _ prefix convention +unused_args = true +ignore = { + "212", -- unused argument (too noisy for callback-heavy code) +} diff --git a/.luarc.ci.json b/.luarc.ci.json new file mode 100644 index 0000000..f16b379 --- /dev/null +++ b/.luarc.ci.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime": { + "version": "LuaJIT", + "pathStrict": true + }, + "workspace": { + "library": [ + "/opt/nvim/share/nvim/runtime/lua/vim/_meta", + "/opt/nvim/share/nvim/runtime/lua/vim/shared.lua", + "${3rd}/luv/library", + "${3rd}/busted/library", + "/opt/snacks.nvim/lua" + ], + "checkThirdParty": false + }, + "diagnostics": { + "severity": { + "undefined-global": "Error", + "undefined-field": "Warning", + "missing-return": "Warning", + "redundant-parameter": "Warning", + "param-type-mismatch": "Warning", + "assign-type-mismatch": "Warning", + "cast-type-mismatch": "Warning", + "deprecated": "Warning", + "undefined-doc-param": "Warning" + }, + "neededFileStatus": { + "undefined-global": "Any", + "undefined-field": "Any", + "missing-return": "Any", + "redundant-parameter": "Any", + "param-type-mismatch": "Any", + "assign-type-mismatch": "Any", + "cast-type-mismatch": "Any", + "deprecated": "Any", + "undefined-doc-param": "Any" + } + }, + "type": { + "checkTableShape": true + } +} diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..6073601 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime": { + "version": "LuaJIT" + }, + "workspace": { + "library": [ + "$VIMRUNTIME/lua", + "${3rd}/luv/library", + "${3rd}/busted/library" + ], + "checkThirdParty": false + }, + "diagnostics": { + "globals": ["vim"], + "severity": { + "undefined-global": "Error", + "undefined-field": "Warning", + "missing-return": "Warning", + "redundant-parameter": "Warning", + "param-type-mismatch": "Warning", + "assign-type-mismatch": "Warning", + "cast-type-mismatch": "Warning", + "deprecated": "Warning", + "undefined-doc-param": "Warning" + }, + "neededFileStatus": { + "undefined-global": "Any", + "undefined-field": "Any", + "missing-return": "Any", + "redundant-parameter": "Any", + "param-type-mismatch": "Any", + "assign-type-mismatch": "Any", + "cast-type-mismatch": "Any", + "deprecated": "Any", + "undefined-doc-param": "Any" + } + }, + "type": { + "checkTableShape": true + } +} diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..e698318 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "fff": { + "type": "stdio", + "command": "./target/release/fff-mcp", + "args": [] + } + } +} diff --git a/.nixignore b/.nixignore new file mode 100644 index 0000000..b6abdc8 --- /dev/null +++ b/.nixignore @@ -0,0 +1,4 @@ +empty_config.lua +benches/ +doc/ + diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..6d75cf6 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,7 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferSingle" +call_parentheses = "Always" +collapse_simple_statement = "Always" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5da8e1e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,111 @@ +# To Clankers + +This repository contains **FFF.nvim (Fast File Finder)**, a high-performance file picker for Neovim inspired by blink.cmp's fuzzy matching technology. It's NOT a completion plugin, but rather a standalone file finder with advanced fuzzy search and frecency scoring. The project aims to be the drop-in replacement for telescope, fzf-lua, snacks.picker and similar plugins, focusing on speed, accuracy search and usability features. + +## Development Commands + +Always prefer Makefile commands listed to the cargo/bun/node if possible. + +### Building + +- `make build` - build everything + +### Testing and Development Tools + +This project does not have a traditional test suite. Testing is done through: + +- Create e2e local test file for Neovim: Load any Lua test file with `nvim -l ` +- Write inline rust unit tests for any functionality that is standalone and scoped within a single function + +### Code Quality + +- `make lint` - Rust linting and code analysis +- `make format` - Format all code +- `make test` - Run unit tests (limited coverage, primarily integration testing) + +When doing code make sure to REDUCE SIZE OF COMMENTS. This is very important. Every comment should be concise 1-2 liner maximum 4 lines if describes really extensive and unnatural concept. + +### Important coding rules + +- Do not add doc comments to the private structs and functions. +- Do not make public structs if something can be private + + +## Architecture + +Everything that is performance critical happens in rust world, everything that is neovim specific happens in the lua code. + +There are 3 main components: + +- Rust binary with the global file picker state containing index of all files +- Background thread with the file system watcher that updates the index in real time +- Lua UI layer that renders the picker, handles user input, and calls the rust functions via FFI + +There are 2 databases: + +- Frecency database (LMDB) that tracks file access patterns for scoring +- Query history database used to track the user's previous search queries + +### Key Files + +- `lua/fff.lua` - Entry point, delegates to main.lua +- `lua/fff/main.lua` - Public API (find_files, search, change_directory) +- `lua/fff/core.lua` - Initialization, autocmds, global state management +- `lua/fff/picker_ui.lua` - UI rendering, layout calculation, keymaps +- `lua/fff/file_picker/preview.lua` - File preview with syntax highlighting +- `lua/fff/file_picker/image.lua` - Image preview (snacks.nvim integration) +- `lua/fff/conf.lua` - Default config +- `lua/fff/rust/init.lua` - Loads compiled Rust shared library + +**Rust Side:** + +- `lua/fff/rust/lib.rs` - FFI bindings, global state (FILE_PICKER, FRECENCY) +- `lua/fff/rust/file_picker.rs` - Core FilePicker struct, indexing, background watcher +- `lua/fff/rust/frecency.rs` - Frecency database (LMDB) and scoring +- `lua/fff/rust/query_tracker.rs` - Search query history tracking +- `lua/fff/rust/score.rs` - Fuzzy match scoring with frizbee integration +- `lua/fff/rust/git.rs` - Git status caching and repository detection +- `lua/fff/rust/background_watcher.rs` - File system watcher thread + +### Scoring Algorithm + +Located at the score.rs file + +### Build System + +- `Cargo.toml` - Rust dependencies and build configuration (package name: `fff_nvim`) +- `rust-toolchain.toml` - Specifies Rust nightly toolchain with required components +- `Cross.toml` - Cross-compilation settings using Zig for Linux targets +- **CI/CD Workflows**: + - `.github/workflows/rust.yml` - Rust testing, formatting, and clippy checks + - `.github/workflows/release.yaml` - Automated multi-platform builds + - `.github/workflows/stylua.yaml` - Lua code formatting validation + - `.github/workflows/nix.yml` - Nix build validation +- **Cross-compilation Support**: Uses `cross` tool with Zig backend for efficient cross-compilation + +## Development Notes + +### Working with Rust Code + +- Prefer struct methods over functions +- If there is more than 2 impls in the file - create new file +- Smaller concise comments over giant comment blocks +- Do not add doc comments to the private functions/structs +- Be very careful around locking and better double check with the human if something is going to require potentially long lock on a mutex/rwlock + +### Working with lua code + +- Document the types of public functions in every module +- Use `vim.validate()` for validating user inputs in public functions +- Try to reuse as much of existing functions as possible +- When working on new features for the UI **IT IS EXTREMELY IMPORTANT** to keep the core functionality of navigating between files, selecting, and seeing the preview working as is. NEVER break anything from the core UI functionality, only add new features on top of the current UI. +- When making a large chunk of code make lua test that opens neovim at `~/dev/lightsource` and opens the picker to test the ui functionality across the actual code. +- When adding a new highlights or any new shortcuts and configurable UI options add them to the neovim config. AND IMPORTANT: update the README.md with the new configuration options. + +### UI rendering + +When working on the UI changeds IT IS EXTREMELY important for you to test it for both prompt_position="bottom" and prompt_position="top" as the rendering logic is different for both of them in both rust and lua world. When the prompt is positioed in the bottom everything should work the same way as the top but would be reversed in order. (though navigation is same for both) + +## Top level API that can not introduce breaking changes under any circumstance + +Top level rust, lua, C, and bun APIs can not be changed under any circumstance diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6ef491a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3251 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ctrlc" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +dependencies = [ + "dispatch2", + "nix", + "windows-sys 0.61.2", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "doxygen-rs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" +dependencies = [ + "phf", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fff-c" +version = "0.9.6" +dependencies = [ + "fff-query-parser", + "fff-search", + "git2", + "serde_json", +] + +[[package]] +name = "fff-grep" +version = "0.9.6" +dependencies = [ + "bstr", + "memchr", +] + +[[package]] +name = "fff-mcp" +version = "0.9.6" +dependencies = [ + "clap", + "fff-query-parser", + "fff-search", + "git2", + "mimalloc", + "rmcp", + "schemars", + "serde", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "fff-notify-debouncer-full" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a4ebea7b8a2840cd59358bbf396f6f04313ce8eae84ac79703ce80298b8731" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "rustc-hash", + "walkdir", +] + +[[package]] +name = "fff-nvim" +version = "0.9.6" +dependencies = [ + "ahash", + "chrono", + "criterion", + "ctrlc", + "fff-query-parser", + "fff-search", + "git2", + "ignore", + "mimalloc", + "mlua", + "once_cell", + "rand 0.8.5", + "tracing", + "tracing-subscriber", + "zlob", +] + +[[package]] +name = "fff-python" +version = "0.9.6" +dependencies = [ + "fff-query-parser", + "fff-search", + "git2", + "pyo3", +] + +[[package]] +name = "fff-query-parser" +version = "0.9.6" +dependencies = [ + "criterion", + "zlob", +] + +[[package]] +name = "fff-search" +version = "0.9.6" +dependencies = [ + "ahash", + "aho-corasick", + "blake3", + "criterion", + "ctor", + "dirs", + "dunce", + "fff-grep", + "fff-notify-debouncer-full", + "fff-query-parser", + "git2", + "glidesort", + "globset", + "heed", + "ignore", + "libc", + "libmimalloc-sys", + "memchr", + "memmap2", + "mimalloc", + "neo_frizbee", + "notify", + "parking_lot", + "pathdiff", + "proptest", + "rand 0.8.5", + "rayon", + "regex", + "regex-syntax", + "serde", + "signal-hook-registry", + "smallvec", + "tempfile", + "thiserror 2.0.18", + "tracing", + "tracing-appender", + "tracing-subscriber", + "zlob", +] + +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "git2" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +dependencies = [ + "bitflags 2.11.0", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "glidesort" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e102e6eb644d3e0b186fc161e4460417880a0a0b87d235f2e5b8fb30f2e9e0" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "heed" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a56c94661ddfb51aa9cdfbf102cfcc340aa69267f95ebccc4af08d7c530d393" +dependencies = [ + "bitflags 2.11.0", + "byteorder", + "heed-traits", + "heed-types", + "libc", + "lmdb-master-sys", + "once_cell", + "page_size", + "serde", + "synchronoise", + "url", +] + +[[package]] +name = "heed-traits" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" + +[[package]] +name = "heed-types" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c255bdf46e07fb840d120a36dcc81f385140d7191c76a7391672675c01a55d" +dependencies = [ + "bincode", + "byteorder", + "heed-traits", + "serde", + "serde_json", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inotify" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +dependencies = [ + "bitflags 2.11.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libgit2-sys" +version = "0.18.3+1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libmimalloc-sys" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +dependencies = [ + "cc", + "cty", + "libc", +] + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "libc", +] + +[[package]] +name = "libz-sys" +version = "1.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lmdb-master-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864808e0b19fb6dd3b70ba94ee671b82fce17554cf80aeb0a155c65bb08027df" +dependencies = [ + "cc", + "doxygen-rs", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mimalloc" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mlua" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd36acfa49ce6ee56d1307a061dd302c564eee757e6e4cd67eb4f7204846fab" +dependencies = [ + "bstr", + "either", + "libc", + "mlua-sys", + "mlua_derive", + "num-traits", + "parking_lot", + "rustc-hash", + "rustversion", +] + +[[package]] +name = "mlua-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f1c3a7fc7580227ece249fd90aa2fa3b39eb2b49d3aec5e103b3e85f2c3dfc8" +dependencies = [ + "cc", + "cfg-if", + "libc", + "pkg-config", +] + +[[package]] +name = "mlua_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465bddde514c4eb3b50b543250e97c1d4b284fa3ef7dc0ba2992c77545dbceb2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "neo_frizbee" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2f6120a8da26bea3587731072111062c5d8c51ca3a3a75a716bd8b735d5882" + +[[package]] +name = "nix" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "9.0.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b44b771d4dd781ef14c84078693e67495da6b47f609f72e8a4da8420a861240e" +dependencies = [ + "bitflags 2.11.0", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "objc2-core-foundation", + "objc2-core-services", + "walkdir", + "windows-sys 0.61.2", + "xxhash-rust", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "objc2-core-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags 2.11.0", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "pyo3" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rmcp" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e" +dependencies = [ + "async-trait", + "base64", + "chrono", + "futures", + "pastey", + "pin-project-lite", + "rmcp-macros", + "schemars", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "rmcp-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synchronoise" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2" +dependencies = [ + "crossbeam-queue", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zlob" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e41cb327ac1b7e7e0d4514658500cb5734cd655edbe4e5ffeda69955da9028ee" +dependencies = [ + "bindgen", + "bitflags 2.11.0", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e333fd1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,73 @@ +[workspace] +members = [ + "crates/fff-c", + "crates/fff-core", + "crates/fff-mcp", + "crates/fff-nvim", + "crates/fff-python", + "crates/fff-query-parser", + "crates/fff-grep", +] + +resolver = "2" + +[workspace.dependencies] +fff-grep = { version = "0.9.6", path = "crates/fff-grep" } +fff-query-parser = { version = "0.9.6", path = "crates/fff-query-parser", default-features = false } + +# Shared dependencies +ahash = "0.8" +blake3 = "1.8.2" +chrono = { version = "0.4", features = ["serde"] } +ctrlc = "3.4.2" +dirs = "5.0" +dunce = "1.0" +# git2 - base config without TLS (each crate adds platform-specific TLS) +git2 = { version = "0.20.2", default-features = false, features = [ + "vendored-libgit2", +] } +glidesort = "0.1" +globset = "0.4" +heed = "0.22.0" +ignore = "0.4.22" +memmap2 = "0.9" +mimalloc = "0.1.47" +signal-hook-registry = "1.4" +zlob = { version = "=1.6.1" } + +mlua = { version = "0.11.1", features = ["module", "luajit"] } +neo_frizbee = { version = "0.11.0", features = ["match_end_col"] } +notify = { version = "9.0.0-rc.3" } +notify-debouncer-full = { package = "fff-notify-debouncer-full", version = "0.9.4" } +once_cell = "1.20.2" +parking_lot = "0.12" +pathdiff = "0.2.1" +rayon = "1.8.0" +regex = "1.11" +regex-syntax = "0.8" +smallvec = { version = "1.13", features = ["const_generics", "union"] } +thiserror = "2.0.10" +tracing = "0.1" + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +strip = "debuginfo" + +[profile.ci] +inherits = "release" +# we use lto=fat locally for better SIMD for the march=native but +# on CI when we cross compiling we should not exclude any cpu flags checking +lto = "thin" + +[profile.bench] +inherits = "release" + +# For Instruments / xctrace: release-level optimization but keep debuginfo +# and symbols so sampled frames resolve to real Rust names. +[profile.prof] +inherits = "release" +debug = "full" +strip = false +lto = "thin" diff --git a/Formula/fff-mcp.rb b/Formula/fff-mcp.rb new file mode 100644 index 0000000..80a357b --- /dev/null +++ b/Formula/fff-mcp.rb @@ -0,0 +1,60 @@ +# Originally authored by @jellydn (https://github.com/jellydn/homebrew-tap). +# Maintained in-repo; auto-bumped by .github/workflows/release.yaml on stable releases. +class FffMcp < Formula + desc "Fast file search toolkit for AI agents (MCP server)" + homepage "https://github.com/dmtrKovalenko/fff.nvim" + license "MIT" + version "0.9.6" + + LIVECHECK_REPO = "dmtrKovalenko/fff.nvim".freeze + RELEASE_BASE = "https://github.com/dmtrKovalenko/fff.nvim/releases/download".freeze + + on_macos do + on_arm do + url "#{RELEASE_BASE}/v#{version}/fff-mcp-aarch64-apple-darwin" + sha256 "29a7fadeafb062f3e5954b1ab8c69e14dca24f5e061cd8d3b1ea1bab385a3754" + end + + on_intel do + url "#{RELEASE_BASE}/v#{version}/fff-mcp-x86_64-apple-darwin" + sha256 "58259324c2c13a1b6f24f13138c2cd3eae9ff20e05201a539beb8f2044a651aa" + end + end + + on_linux do + on_arm do + url "#{RELEASE_BASE}/v#{version}/fff-mcp-aarch64-unknown-linux-gnu" + sha256 "91e6fa14e040588dc92de854e35020536f1e2458ce3386b2b727b2e7a88f6684" + end + + on_intel do + url "#{RELEASE_BASE}/v#{version}/fff-mcp-x86_64-unknown-linux-gnu" + sha256 "d1bd2b89a79e8eda71b1754260499cec1feaafd2adf372e92371c8d6b68509a3" + end + end + + livecheck do + url "https://github.com/#{LIVECHECK_REPO}/releases/latest" + strategy :github_latest + end + + def install + if OS.mac? + if Hardware::CPU.arm? + bin.install "fff-mcp-aarch64-apple-darwin" => "fff-mcp" + elsif Hardware::CPU.intel? + bin.install "fff-mcp-x86_64-apple-darwin" => "fff-mcp" + end + elsif OS.linux? + if Hardware::CPU.arm? + bin.install "fff-mcp-aarch64-unknown-linux-gnu" => "fff-mcp" + elsif Hardware::CPU.intel? + bin.install "fff-mcp-x86_64-unknown-linux-gnu" => "fff-mcp" + end + end + end + + test do + system bin/"fff-mcp", "--healthcheck" + end +end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ea495f0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Dmitriy Kovalenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a3004f4 --- /dev/null +++ b/Makefile @@ -0,0 +1,348 @@ +PLENARY_DIR ?= ../plenary.nvim +MINI_DIR ?= ../mini.nvim + +PREFIX ?= /usr/local +LIBDIR ?= $(PREFIX)/lib +INCLUDEDIR ?= $(PREFIX)/include + +# Compile-time cfg that gates the watcher + git-status fuzz stress test. +STRESS_RUSTFLAGS := --cfg stress +FFF_STRESS_DEFAULT_SEED ?= 0xDEADBEEFCAFEBABE + +SHELL := bash +# Order matters: `-c` must be last so bash treats the recipe as the script +# string rather than the literal `-o` / `pipefail` tokens. +.SHELLFLAGS := -o pipefail -euc + +.PHONY: build build-c-lib install uninstall test test-rust test-c-smoke test-c-api test-lua test-lua-snap test-version test-bun test-node prepare-bun prepare-bun-packaged prepare-node set-npm-version header test-stress test-stress-seeded test-stress-random test-stress-regressions test-stress-repos test-node-stress sync-js-api sync-js-api-check bump-homebrew-formula bump-install-mcp-sh test-bun-compile + +all: format test lint + +SYNC_API_SRC := packages/shared/fff-api.ts +SYNC_API_TARGETS := packages/fff-node/src/fff-api.ts packages/fff-bun/src/fff-api.ts +SYNC_API_BANNER := // ----------------------------------------------------------------------------\n// GENERATED FILE - DO NOT EDIT.\n// Copied from: ${SYNC_API_SRC}\n// Run make sync-js-api from the repo root to regenerate.\n// ----------------------------------------------------------------------------\n\n + +sync-js-api: + @for target in $(SYNC_API_TARGETS); do \ + printf '$(SYNC_API_BANNER)' > "$$target"; \ + cat $(SYNC_API_SRC) >> "$$target"; \ + echo "synced: $$target"; \ + done + +sync-js-api-check: + @status=0; \ + for target in $(SYNC_API_TARGETS); do \ + tmp=$$(mktemp); \ + printf '$(SYNC_API_BANNER)' > "$$tmp"; \ + cat $(SYNC_API_SRC) >> "$$tmp"; \ + if ! cmp -s "$$tmp" "$$target"; then \ + echo "out of date: $$target (run make sync-js-api)"; status=1; \ + fi; \ + rm -f "$$tmp"; \ + done; \ + exit $$status + +build: + cargo build --release --no-default-features --features zlob + +build-c-lib: + cargo build --release -p fff-c --no-default-features --features zlob + +header: + cbindgen --config crates/fff-c/cbindgen.toml --crate fff-c --output crates/fff-c/include/fff.h + +# Install the C library and header under $(PREFIX) (default /usr/local). +# Override PREFIX for user-local installs, e.g. `make install PREFIX=$$HOME/.local`. +# DESTDIR is honoured for packagers. +install: build-c-lib + install -d $(DESTDIR)$(LIBDIR) + install -d $(DESTDIR)$(INCLUDEDIR) + install -m 0644 crates/fff-c/include/fff.h $(DESTDIR)$(INCLUDEDIR)/fff.h + @if [ -f target/release/libfff_c.dylib ]; then \ + install -m 0755 target/release/libfff_c.dylib $(DESTDIR)$(LIBDIR)/libfff_c.dylib; \ + echo "Installed $(DESTDIR)$(LIBDIR)/libfff_c.dylib"; \ + fi + @if [ -f target/release/libfff_c.so ]; then \ + install -m 0755 target/release/libfff_c.so $(DESTDIR)$(LIBDIR)/libfff_c.so; \ + echo "Installed $(DESTDIR)$(LIBDIR)/libfff_c.so"; \ + fi + @if [ -f target/release/fff_c.dll ]; then \ + install -m 0755 target/release/fff_c.dll $(DESTDIR)$(LIBDIR)/fff_c.dll; \ + echo "Installed $(DESTDIR)$(LIBDIR)/fff_c.dll"; \ + fi + @echo "Installed header $(DESTDIR)$(INCLUDEDIR)/fff.h" + +uninstall: + rm -f $(DESTDIR)$(LIBDIR)/libfff_c.dylib + rm -f $(DESTDIR)$(LIBDIR)/libfff_c.so + rm -f $(DESTDIR)$(LIBDIR)/fff_c.dll + rm -f $(DESTDIR)$(INCLUDEDIR)/fff.h + @echo "Removed fff-c from $(DESTDIR)$(PREFIX)" + +test-setup: + @if [ ! -d "$(PLENARY_DIR)" ]; then \ + echo "Cloning plenary.nvim..."; \ + git clone --depth 1 https://github.com/nvim-lua/plenary.nvim $(PLENARY_DIR); \ + fi + @if [ ! -d "$(MINI_DIR)" ]; then \ + echo "Cloning mini.nvim..."; \ + git clone --depth 1 https://github.com/echasnovski/mini.nvim $(MINI_DIR); \ + fi + +test-rust: + cargo test --workspace --no-default-features --features zlob --exclude fff-nvim + +CC ?= cc +CFLAGS ?= -O0 -g -Wall -Wextra -std=c99 +TARGET_DIR ?= target/release +SMOKE_BIN := $(TARGET_DIR)/fff_c_smoke +SMOKE_SRC := crates/fff-c/tests/smoke.c +SMOKE_INCLUDE := crates/fff-c/include + +test-c-smoke: build-c-lib + $(CC) $(CFLAGS) -I $(SMOKE_INCLUDE) -L $(TARGET_DIR) \ + -Wl,-rpath,@loader_path/../target/release \ + -Wl,-rpath,$$(pwd)/$(TARGET_DIR) \ + $(SMOKE_SRC) -lfff_c -o $(SMOKE_BIN) + $(SMOKE_BIN) . + +# Alias kept for the `external-tests.yml` workflow naming. +test-c-api: test-c-smoke + +# neovim instance swallows internal crashes and doesn't rise the the error exiting silently +# so check the stdout in case the sigsegv coming out of fff was printed (actual regression). +# Output is streamed live via `tee`; pipefail (set above) propagates nvim's exit. +test-lua: test-setup build + @logfile=$$(mktemp); \ + trap 'rm -f "$$logfile"' EXIT; \ + nvim --headless -u tests/minimal_init.lua \ + -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal_init.lua'}" 2>&1 \ + | tee "$$logfile"; \ + if grep -qE "SIG(SEGV|ABRT|BUS|FPE|ILL)" "$$logfile"; then \ + echo ""; \ + echo "FAIL: native crash detected during lua tests"; \ + exit 1; \ + fi + +test-lua-snap: test-setup build + @logfile=$$(mktemp); \ + trap 'rm -f "$$logfile"' EXIT; \ + nvim --headless -u tests/minimal_init.lua \ + -c "lua local ok,err=pcall(require('mini.test').run_file,'tests/picker_ui_snap.lua'); if not ok then io.stderr:write('mini.test failed to load: '..tostring(err)..'\\n'); vim.cmd('cquit 2') end" 2>&1 \ + | tee "$$logfile"; \ + if grep -qE "SIG(SEGV|ABRT|BUS|FPE|ILL)" "$$logfile"; then \ + echo ""; \ + echo "FAIL: native crash detected during snapshot tests"; \ + exit 1; \ + fi + +test-version: test-setup + nvim --headless -u tests/minimal_init.lua \ + -c "PlenaryBustedFile tests/version_spec.lua" 2>&1 + +prepare-bun: build sync-js-api + mkdir -p packages/fff-bun/bin + cp target/release/libfff_c.dylib packages/fff-bun/bin/ 2>/dev/null || true; \ + cp target/release/libfff_c.so packages/fff-bun/bin/ 2>/dev/null || true; \ + cp target/release/fff_c.dll packages/fff-bun/bin/ 2>/dev/null || true + +prepare-node: build sync-js-api + mkdir -p packages/fff-node/bin + cp target/release/libfff_c.dylib packages/fff-node/bin/ 2>/dev/null || true; \ + cp target/release/libfff_c.so packages/fff-node/bin/ 2>/dev/null || true; \ + cp target/release/fff_c.dll packages/fff-node/bin/ 2>/dev/null || true + +test-bun: prepare-bun + cd packages/fff-bun && bun test test/ + cd packages/pi-fff && bun test test/ + +# Same as prepare-bun but puts the compiled binary into the actual npm package location +prepare-bun-packaged: prepare-bun + @machine=$$(uname -m); \ + case "$$machine" in \ + x86_64|amd64) arch=x64 ;; \ + aarch64|arm64) arch=arm64 ;; \ + *) echo "unsupported arch: $$machine" >&2; exit 1 ;; \ + esac; \ + case "$$(uname -s)" in \ + Darwin) lib=libfff_c.dylib; pkg=fff-bin-darwin-$$arch ;; \ + Linux) lib=libfff_c.so; \ + if ldd --version 2>&1 | grep -qi musl; then libc=musl; else libc=gnu; fi; \ + pkg=fff-bin-linux-$$arch-$$libc ;; \ + MINGW*|MSYS*|CYGWIN*|Windows_NT) lib=fff_c.dll; pkg=fff-bin-win32-$$arch ;; \ + *) echo "unsupported OS: $$(uname -s)" >&2; exit 1 ;; \ + esac; \ + src=target/release/$$lib; \ + [ -f "$$src" ] || { echo "missing built library: $$src" >&2; exit 1; }; \ + dest=packages/fff-bun/node_modules/@ff-labs/$$pkg; \ + rm -rf "$$dest"; mkdir -p "$$dest"; \ + cp "$$src" "$$dest/$$lib"; \ + printf '{ "name": "@ff-labs/%s", "version": "0.0.0", "main": "%s" }\n' "$$pkg" "$$lib" > "$$dest/package.json" + +# Compile a bun example to a standalone executable and run it. Verifies the +# native libfff_c is embedded + loaded from a `bun build --compile` binary. +# The staged bin package is removed before running so success proves the lib +# was embedded, not resolved from disk. +test-bun-compile: prepare-bun-packaged + cd packages/fff-bun && \ + if [ "$$(uname -s)" = "Linux" ]; then \ + if ldd --version 2>&1 | grep -qi musl; then DEFINE='--define FFF_LIBC="musl"'; \ + else DEFINE='--define FFF_LIBC="gnu"'; fi; \ + else DEFINE=""; fi; \ + bun build --compile $$DEFINE ./examples/glob-bench.ts --outfile ./glob-bench-bin && \ + EXE=./glob-bench-bin; [ -f "$$EXE.exe" ] && EXE="$$EXE.exe"; \ + rm -rf bin node_modules/@ff-labs; \ + "$$EXE" . '**/*.ts' 1 | tee /tmp/fff-compile-e2e.log && \ + grep -q 'fff.glob' /tmp/fff-compile-e2e.log + rm -f packages/fff-bun/glob-bench-bin packages/fff-bun/glob-bench-bin.exe + +test-node: prepare-node + cd packages/fff-node && npm run build && node test/e2e.mjs + +test-js: test-bun test-node + +# Bug pinning stress test script over fff-node for issue #515 +# Just keep it untouched because it's good enough + some stress for SDK +FFF_STRESS_ITERS ?= 50 +test-node-stress: prepare-node + cd packages/fff-node && npm run build && \ + FFF_STRESS_ITERS=$(FFF_STRESS_ITERS) node test/stress-515.mjs + +test: test-rust test-lua test-lua-snap test-version test-bun test-node test-node-stress + +test-stress-seeded: + FFF_STRESS_SEED="$${FFF_STRESS_SEED:-$(FFF_STRESS_DEFAULT_SEED)}" \ + RUSTFLAGS="$(STRESS_RUSTFLAGS)" \ + cargo test --release \ + -p fff-search \ + --test fuzz_git_watcher_stress \ + --no-default-features --features zlob \ + -- --nocapture stress_seeded + +test-stress-random: + RUSTFLAGS="$(STRESS_RUSTFLAGS)" \ + cargo test --release \ + -p fff-search \ + --test fuzz_git_watcher_stress \ + --no-default-features --features zlob \ + -- --nocapture stress_random + +test-stress-regressions: + RUSTFLAGS="$(STRESS_RUSTFLAGS)" \ + cargo test --release \ + -p fff-search \ + --test fuzz_git_watcher_stress \ + --no-default-features --features zlob \ + -- --nocapture stress_regression stress_merge_conflict_convergence + +test-stress-repos: + RUSTFLAGS="$(STRESS_RUSTFLAGS)" \ + cargo test --release \ + -p fff-search \ + --test fuzz_real_repos \ + --no-default-features --features zlob \ + -- --nocapture + +test-stress: test-stress-seeded test-stress-random test-stress-regressions test-stress-repos + +# Update version in a package.json, including optionalDependencies. +# Usage: make set-npm-version PKG=packages/fff-bun VERSION=1.0.0-nightly.abc1234 +set-npm-version: + @test -n "$(PKG)" || (echo "PKG is required" && exit 1) + @test -n "$(VERSION)" || (echo "VERSION is required" && exit 1) + node -e " \ + const fs = require('fs'); \ + const pkg = JSON.parse(fs.readFileSync('$(PKG)/package.json', 'utf8')); \ + pkg.version = '$(VERSION)'; \ + if (pkg.optionalDependencies) { \ + for (const dep of Object.keys(pkg.optionalDependencies)) { \ + pkg.optionalDependencies[dep] = '$(VERSION)'; \ + } \ + } \ + fs.writeFileSync('$(PKG)/package.json', JSON.stringify(pkg, null, 2) + '\n'); \ + " + @echo "Set $(PKG) to $(VERSION)" + +format-rust: + cargo fmt --all +format-lua: + stylua . +format-ts: + bun format + +format: format-rust format-lua format-ts + +lint-rust: + cargo clippy --workspace --no-default-features --features zlob -- -D warnings +lint-lua: + ~/.luarocks/bin/luacheck . +lint-ts: + bun lint + +lint: lint-rust lint-lua lint-ts + +check: format lint + +FFF_RELEASE_REPO ?= dmtrKovalenko/fff.nvim +FFF_FORMULA_PATH ?= Formula/fff-mcp.rb +FFF_INSTALL_SCRIPT_PATH ?= install-mcp.sh + +# Read the sha256 for $1 (filename, no .sha256 suffix). Reads from +# BINARIES_DIR/$1.sha256 when set; otherwise curls the GitHub release. +define fff_fetch_sha + if [ -n "$$BINARIES_DIR" ]; then \ + awk '{print $$1}' "$$BINARIES_DIR/$$1.sha256" \ + || { echo "Missing checksum file: $$BINARIES_DIR/$$1.sha256" >&2; exit 1; }; \ + else \ + curl -fsSL "https://github.com/$(FFF_RELEASE_REPO)/releases/download/v$(VERSION)/$$1.sha256" \ + | awk '{print $$1}'; \ + fi +endef + +bump-homebrew-formula: + @test -n "$(VERSION)" || (echo "VERSION is required. Usage: make bump-homebrew-formula VERSION=0.9.1 [BINARIES_DIR=./binaries]" && exit 1) + @export BINARIES_DIR="$(BINARIES_DIR)"; \ + fetch_sha() { $(fff_fetch_sha); }; \ + sha_darwin_arm="$$(fetch_sha fff-mcp-aarch64-apple-darwin)"; \ + sha_darwin_intel="$$(fetch_sha fff-mcp-x86_64-apple-darwin)"; \ + sha_linux_arm="$$(fetch_sha fff-mcp-aarch64-unknown-linux-gnu)"; \ + sha_linux_intel="$$(fetch_sha fff-mcp-x86_64-unknown-linux-gnu)"; \ + sed -i.bak \ + -e 's/^ version "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"$$/ version "$(VERSION)"/' \ + -e '/fff-mcp-aarch64-apple-darwin"$$/{n;s/sha256 "[a-f0-9]*"/sha256 "'"$$sha_darwin_arm"'"/;}' \ + -e '/fff-mcp-x86_64-apple-darwin"$$/{n;s/sha256 "[a-f0-9]*"/sha256 "'"$$sha_darwin_intel"'"/;}' \ + -e '/fff-mcp-aarch64-unknown-linux-gnu"$$/{n;s/sha256 "[a-f0-9]*"/sha256 "'"$$sha_linux_arm"'"/;}' \ + -e '/fff-mcp-x86_64-unknown-linux-gnu"$$/{n;s/sha256 "[a-f0-9]*"/sha256 "'"$$sha_linux_intel"'"/;}' \ + "$(FFF_FORMULA_PATH)" && rm -f "$(FFF_FORMULA_PATH).bak"; \ + echo "Bumped $(FFF_FORMULA_PATH) to v$(VERSION)" + +bump-install-mcp-sh: + @test -n "$(VERSION)" || (echo "VERSION is required. Usage: make bump-install-mcp-sh VERSION=0.9.1 [BINARIES_DIR=./binaries]" && exit 1) + @export BINARIES_DIR="$(BINARIES_DIR)"; \ + fetch_sha() { $(fff_fetch_sha); }; \ + sha_linux_intel="$$(fetch_sha fff-mcp-x86_64-unknown-linux-musl)"; \ + sha_linux_arm="$$(fetch_sha fff-mcp-aarch64-unknown-linux-musl)"; \ + sha_darwin_intel="$$(fetch_sha fff-mcp-x86_64-apple-darwin)"; \ + sha_darwin_arm="$$(fetch_sha fff-mcp-aarch64-apple-darwin)"; \ + sha_win_intel="$$(fetch_sha fff-mcp-x86_64-pc-windows-msvc.exe)"; \ + sha_win_arm="$$(fetch_sha fff-mcp-aarch64-pc-windows-msvc.exe)"; \ + sed -i.bak \ + -e 's|^PINNED_RELEASE_TAG=".*"|PINNED_RELEASE_TAG="v$(VERSION)"|' \ + -e 's|^SHA256_X86_64_UNKNOWN_LINUX_MUSL=".*"|SHA256_X86_64_UNKNOWN_LINUX_MUSL="'"$$sha_linux_intel"'"|' \ + -e 's|^SHA256_AARCH64_UNKNOWN_LINUX_MUSL=".*"|SHA256_AARCH64_UNKNOWN_LINUX_MUSL="'"$$sha_linux_arm"'"|' \ + -e 's|^SHA256_X86_64_APPLE_DARWIN=".*"|SHA256_X86_64_APPLE_DARWIN="'"$$sha_darwin_intel"'"|' \ + -e 's|^SHA256_AARCH64_APPLE_DARWIN=".*"|SHA256_AARCH64_APPLE_DARWIN="'"$$sha_darwin_arm"'"|' \ + -e 's|^SHA256_X86_64_PC_WINDOWS_MSVC=".*"|SHA256_X86_64_PC_WINDOWS_MSVC="'"$$sha_win_intel"'"|' \ + -e 's|^SHA256_AARCH64_PC_WINDOWS_MSVC=".*"|SHA256_AARCH64_PC_WINDOWS_MSVC="'"$$sha_win_arm"'"|' \ + "$(FFF_INSTALL_SCRIPT_PATH)" && rm -f "$(FFF_INSTALL_SCRIPT_PATH).bak"; \ + echo "Bumped $(FFF_INSTALL_SCRIPT_PATH) tag + checksums to v$(VERSION)" + +CRATES_TO_PUBLISH= fff-grep fff-query-parser fff-search + +publish-crates: + @test -n "$(V)" || (echo "V is required. Usage: make publish-crates V=0.2.0" && exit 1) + cargo install cargo-edit --force --locked + cargo set-version $(V) || exit 1; + @for crate in $(CRATES_TO_PUBLISH); do \ + cargo publish -p $$crate --allow-dirty $$(if [ -n "$$CI" ]; then echo "--no-verify"; fi) || exit 1; \ + done diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7bb11e --- /dev/null +++ b/README.md @@ -0,0 +1,825 @@ +FFF + +

+ A file search toolkit for humans and AI agents. Really fast. +

+ +Typo-resistant path and content search, frecency-ranked file access, a background watcher, and a lightweight in-memory content index. Way faster than CLIs like ripgrep and fzf in any long-running process that searches more than once. + +Powers file search in [opencode](http://github.com/anomalyco/opencode/), [nushell](https://github.com/nushell/nushell), and many more amazing projects! + +Originally started as [Neovim plugin](#neovim-plugin) people loved, but it turned out that plenty of AI harnesses and code editors need the same thing: accurate, fast file search as a library. That is what fff is. + +--- + +Pick what you are interested in: + +
+ +

MCP server

+
+ +Works with Claude Code, Codex, OpenCode, Cursor, Cline, and any MCP-capable client. Fewer grep roundtrips, less wasted context, faster answers. + +![Benchmark chart comparing FFF against the built-in AI file-search tools](./chart.png) + +### One-line install + +Linux / macOS: + +```bash +curl -L https://dmtrkovalenko.dev/install-fff-mcp.sh | bash +``` + +Windows (PowerShell): + +```powershell +irm https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.ps1 | iex +``` + +The scripts live at [`install-mcp.sh`](./install-mcp.sh) and [`install-mcp.ps1`](./install-mcp.ps1) if you want to read them first. They print the exact wiring instructions for your client. + +### Homebrew (macOS / Linux) + +```bash +brew install dmtrKovalenko/fff/fff-mcp +brew upgrade fff-mcp # after new stable releases +``` + +Formula lives in [`Formula/fff-mcp.rb`](./Formula/fff-mcp.rb) in this repo and is **auto-bumped on every stable release** (see `bump-homebrew-formula` in [`.github/workflows/release.yaml`](./.github/workflows/release.yaml)). Installs the prebuilt `fff-mcp` binary from [GitHub releases](https://github.com/dmtrKovalenko/fff.nvim/releases). + +Once the server is connected, ask the agent to "use fff" and it picks up the `ffgrep`, `fffind`, and `fff-multi-grep` tools. + +### Recommended agent prompt + +Drop this into your project's `CLAUDE.md` or equivalent: + +```markdown +For any file search or grep in the current git-indexed directory, use fff tools. +``` + +### What changes + +- Frecency memory. Files you actually open rank higher next time. Warm-up from git touch history runs automatically. +- Definition-first hinting. Lines that look like code definitions are classified on the Rust side, no regex overhead in your prompt. +- Smart-case with auto-fuzzy fallback. `IsOffTheRecord` finds snake_case variants; zero-match queries retry as fuzzy and surface the best approximate hits. +- Git-aware annotations. Modified, untracked, and staged files are tagged so the agent reaches for what you are actively changing. + +Source: [`crates/fff-mcp/`](./crates/fff-mcp/). + +
+ +The MCP server gives any agent a file search tool that is faster and more token-efficient than the built-in one. + +
+ +

Pi agent extension

+
+ +### Install + +```bash +pi install npm:@ff-labs/pi-fff +``` + +### Modes + +Three operating modes, switchable at runtime with `/fff-mode`: + +| Mode | What it does | +| ------------------------ | --------------------------------------------------------------------------------- | +| `tools-and-ui` (default) | Adds `ffgrep` and `fffind` tools, replaces `@`-mention autocomplete with FFF. | +| `tools-only` | Only tool injection. Keeps pi's native editor autocomplete. | +| `override` | Replaces pi's built-in `grep`, `find`, and `multi_grep` with FFF implementations. | + +Env vars: `PI_FFF_MODE`, `FFF_FRECENCY_DB`, `FFF_HISTORY_DB`. Flags: `--fff-mode`, `--fff-frecency-db`, `--fff-history-db`. + +### Agent-facing tools + +- `ffgrep`. Content search. Accepts `path`, `exclude` (comma, space, or array; leading `!` optional), `caseSensitive`, `context`, and cursor pagination. Auto-detects regex, falls back to fuzzy on zero exact matches, rejects `.*`-style wildcard-only patterns up front. +- `fffind`. Path and filename search. Matches the whole repo-relative path, not just the filename. Frecency-aware. The weak-match detector flags scattered fuzzy noise before it floods the agent's context. + +### Commands + +- `/fff-mode [tools-and-ui | tools-only | override]`. Show or switch the mode. +- `/fff-health`. Picker, frecency, and git integration status. +- `/fff-rescan`. Force a rescan. + +Source: [`packages/pi-fff/`](./packages/pi-fff/). + +
+ +The Pi extension swaps pi's native tools for FFF implementations and feeds the interactive editor's `@`-mention autocomplete from the frecency-ranked index. + +
+ +

fff.nvim

+
+ +Demo on the Linux kernel repo (100k files, 8GB): + +https://github.com/user-attachments/assets/5d0e1ce9-642c-4c44-aa88-01b05bb86abb + +### Installation + +#### lazy.nvim + +```lua +{ + 'dmtrKovalenko/fff.nvim', + build = function() + -- downloads a prebuilt binary or falls back to cargo build + require("fff.download").download_or_build_binary() + end, + -- for nixos: + -- build = "nix run .#release", + opts = { + debug = { + enabled = true, + show_scores = true, + }, + }, + lazy = false, -- the plugin lazy-initialises itself + keys = { + { "ff", function() require('fff').find_files() end, desc = 'FFFind files' }, + { "fg", function() require('fff').live_grep() end, desc = 'LiFFFe grep' }, + { "fz", + function() require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } }) end, + desc = 'Live fffuzy grep', + }, + { "fw", + function() require('fff').live_grep_under_cursor() end, + mode = { 'n', 'x' }, + desc = 'Search current word / selection', + }, + }, +} +``` + +#### vim.pack + +```lua +vim.pack.add({ 'https://github.com/dmtrKovalenko/fff.nvim' }) + +vim.api.nvim_create_autocmd('PackChanged', { + callback = function(ev) + local name, kind = ev.data.spec.name, ev.data.kind + if name == 'fff.nvim' and (kind == 'install' or kind == 'update') then + if not ev.data.active then vim.cmd.packadd('fff.nvim') end + require('fff.download').download_or_build_binary() + end + end, +}) + +vim.g.fff = { + lazy_sync = true, + debug = { enabled = true, show_scores = true }, +} + +vim.keymap.set('n', 'ff', function() require('fff').find_files() end, { desc = 'FFFind files' }) +``` + +### Public API + +```lua +require('fff').find_files() -- find files in current repo +require('fff').live_grep() -- live content grep +require('fff').live_grep_under_cursor() -- grep in normal, selection in visual +require('fff').scan_files() -- force rescan +require('fff').refresh_git_status() -- refresh git status +require('fff').find_files_in_dir(path) -- find in a specific dir +require('fff').change_indexing_directory(new_path) -- change root + +-- Programmatic search (no UI). Useful for plugin integrations. +require('fff').file_search(query, opts) -- fuzzy search files / dirs / mixed +require('fff').content_search(query, opts) -- programmatic grep +``` + +#### `file_search(query, opts)` + +Returns a structured result `{ items, scores, total_matched, total_files?, total_dirs?, location? }`. Each item has a `type` field (`"file"` or `"directory"`) and `name` / `relative_path`. File items also expose `size`, `modified`, `git_status`, `is_binary`, and frecency scores. + +```lua +local r = require('fff').file_search('button', { + mode = 'mixed', -- 'files' (default) | 'directories' | 'mixed' + max_results = 50, + page = 0, -- 0-based pagination + current_file = nil, -- path to deprioritize for distance scoring + max_threads = 4, + cwd = nil, -- switch indexed root if different (see below) + wait_for_index_ms = nil, -- override the default scan wait timeout +}) +for _, item in ipairs(r.items) do + print(item.type, item.relative_path) +end +``` + +#### `content_search(query, opts)` + +Returns a `GrepResult` `{ items, total_matched, total_files_searched, total_files, filtered_file_count, next_file_offset, regex_fallback_error? }`. Each match item has `relative_path`, `name`, `line_number`, `col`, `line_content`, `match_ranges`, plus the same file metadata as `file_search`. + +```lua +local r = require('fff').content_search('TODO', { + mode = 'plain', -- 'plain' (default) | 'regex' | 'fuzzy' + max_file_size = 10 * 1024 * 1024, + max_matches_per_file = 100, + smart_case = true, + page_size = 50, + file_offset = 0, + time_budget_ms = 0, + trim_whitespace = false, + cwd = nil, -- switch indexed root if different + wait_for_index_ms = nil, -- override the default scan wait timeout +}) +for _, m in ipairs(r.items) do + print(string.format('%s:%d %s', m.relative_path, m.line_number, m.line_content)) +end +``` + +Both functions accept the same constraint syntax as the UI pickers (e.g. `git:modified`, `*.rs`, `!test/`, glob patterns). + +#### `cwd` and indexing + +Both `file_search` and `content_search` honour an optional `cwd` field. The first call to either function lazily initialises the picker at `config.base_path` (your Neovim cwd by default). + +- If `cwd` matches the currently indexed root, the call returns immediately against the existing index. +- If `cwd` differs, the picker is re-indexed at the new root and the call **blocks** (default up to 10 s) until the new picker is installed and its initial scan completes — so callers always get results from the right tree. +- If the index is still warming up after a `change_indexing_directory`, you can pass `wait_for_index_ms = N` to block for up to `N` ms regardless of whether `cwd` triggered the swap. Pass `0` to skip waiting entirely (useful for fire-and-forget calls where partial results are acceptable). +- Invalid or non-existent `cwd` paths return an empty result and emit an error via `vim.notify`. + +### Commands + +- `:FFFScan`. Rescan files. +- `:FFFRefreshGit`. Refresh git status. +- `:FFFClearCache [all|frecency|files]`. Clear caches. +- `:FFFHealth`. Health check. +- `:FFFDebug [on|off|toggle]`. Toggle the scoring display. +- `:FFFOpenLog`. Open `~/.local/state/nvim/log/fff.log`. + +### Configuration + +Defaults are sensible. Override only what you care about. + +```lua +require('fff').setup({ + base_path = vim.fn.getcwd(), + prompt = '> ', + title = 'FFFiles', + max_results = 100, + max_threads = 4, + lazy_sync = true, + prompt_vim_mode = false, + follow_symlinks = false, + -- Allow indexing the user's $HOME directory. Enabled by default. + -- Disable if you strictly sure you don't want this, as it makes whole fff error hard + enable_home_dir_scanning = true, + -- Allow indexing a filesystem root (e.g. `/`, `C:\`). Disabled by default + enable_fs_root_scanning = false, + layout = { + height = 0.8, + width = 0.8, + prompt_position = 'bottom', -- or 'top' + preview_position = 'right', -- 'left' | 'right' | 'top' | 'bottom' + preview_size = 0.5, + -- Border style for the picker windows. Leave unset (nil) to follow the + -- global `vim.o.winborder`; set it to override fff's borders independently. + border = nil, -- 'single' | 'double' | 'rounded' | 'solid' | 'shadow' | 'none' + flex = { size = 130, wrap = 'top' }, + min_list_height = 10, -- do not display anything except the list below this threshold + show_scrollbar = true, + path_shorten_strategy = 'middle_number', -- 'middle_number' | 'middle' | 'end' | 'start' + anchor = 'center', + }, + preview = { + enabled = true, + max_size = 10 * 1024 * 1024, + chunk_size = 8192, + binary_file_threshold = 1024, + imagemagick_info_format_str = '%m: %wx%h, %[colorspace], %q-bit', + line_numbers = false, + cursorlineopt = 'both', + wrap_lines = false, + filetypes = { + svg = { wrap_lines = true }, + markdown = { wrap_lines = true }, + text = { wrap_lines = true }, + }, + }, + keymaps = { + close = '', + select = '', + select_split = '', + select_vsplit = '', + select_tab = '', + move_up = { '', '' }, + move_down = { '', '' }, + preview_scroll_up = '', + preview_scroll_down = '', + toggle_debug = '', + cycle_grep_modes = '', + -- grep mode only: jump cursor to first match of next/prev file group + grep_jump_to_next_file = { '', '' }, + grep_jump_to_prev_file = { '', '' }, + cycle_previous_query = '', + toggle_select = '', + send_to_quickfix = '', + focus_list = 'l', + focus_preview = 'p', + }, + frecency = { + enabled = true, + db_path = vim.fn.stdpath('cache') .. '/fff_nvim', + }, + history = { + enabled = true, + db_path = vim.fn.stdpath('data') .. '/fff_queries', + min_combo_count = 3, + combo_boost_score_multiplier = 100, + }, + git = { + status_text_color = false, -- true to color filenames by git status + }, + select = { + -- Return winid to open the chosen file in, or nil to open in the original window + select_window = function(current_buf, action) --[[ default impl ]] end, + }, + grep = { + max_file_size = 10 * 1024 * 1024, + max_matches_per_file = 100, + smart_case = true, + time_budget_ms = 150, + modes = { 'plain', 'regex', 'fuzzy' }, + trim_whitespace = false, + enable_filename_constraint = false, -- treat filename-like tokens (e.g. `score.rs`) in a grep query as a file-path filter scoping the search; off = searched as literal text + location_format = ':%d:%d', -- printf format for line:col prefix in grep results, e.g. ':%d' for line-only + }, + debug = { + enabled = false, -- show the file info panel next to the preview + show_scores = false, -- inline scores in the file list + -- Per-section toggles for the file info panel. Accepts a boolean shorthand + -- (`show_file_info = true|false`) to flip everything at once. The panel + -- adapts to width: narrow renders sections vertically, wide renders them + -- as a two-column grid. Disable a section to also shrink the panel. + show_file_info = { + file_info = true, -- size, type, git status, frecency + score_breakdown = true, -- total + match type, bonuses, modifiers, penalty + -- modified + accessed timestamps; pass a table to hide individual rows: + -- timings = { modified = false, accessed = true } + timings = true, + full_path = true, -- relative path at the bottom (wraps if too long) + }, + }, + logging = { + -- logs will be written in a parent directory of this file path in files like + -- `++.`. Run :FFFOpenLog to open current one + log_file = vim.fn.stdpath('log') .. '/fff.log', + log_level = 'info', + retain_runs = 20, + }, +}) +``` + +### Live grep modes + +`` cycles between `plain`, `regex`, and `fuzzy`. The list is configurable via `grep.modes`, and single-mode setups hide the indicator entirely. + +Per-call override: + +```lua +require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } }) +require('fff').live_grep({ query = 'search term' }) -- pre-fill +``` + +### Constraints + +Both find and grep accept these tokens to refine a query: + +- `git:modified`. One of `modified`, `staged`, `deleted`, `renamed`, `untracked`, `ignored`. +- `test/`. Any deeply nested children of `test/`. +- `!something`, `!test/`, `!git:modified`. Exclusion. +- `./**/*.{rs,lua}`. Any valid glob, powered by [zlob](https://github.com/dmtrKovalenko/zlob). + +Grep-only: + +- `*.md`, `*.{c,h}`. Extension filter. +- `src/main.rs`. Grep inside a single file. + +Mix freely: `git:modified src/**/*.rs !src/**/mod.rs user controller`. + +### Open in invoking window + +By default fff.nvim will try to open a file in the most suitable window, so any non-file buffers are not affected. You can customize or disable this by providing: + +```lua +require('fff').setup({ + select = { + select_window = function(_current_buf, _action) return nil end, + }, +}) +``` + +Caveat: the chosen file replaces the buffer in the invoking window even if it's a non-modifiable / special buftype. `winfixbuf` windows still fall back to `:split` to avoid `E1513`. + +### Multi-select and quickfix + +- ``. Toggle selection (shows a thick `▊` in the signcolumn). +- ``. Send selected files to the quickfix list and close the picker. + +### Git status highlighting + +Sign-column indicators are on by default. To color filename text by git status, set `git.status_text_color = true` and adjust the `hl.git_*` groups. See `:help fff.nvim` for the full list. + +### Float colors + +The picker maps its float content to `NormalFloat` (via `hl.normal`) and the border to `FloatBorder`. Default `FloatBorder` links to `NormalFloat`, so border and content share a background out of the box and the picker reads as a single popup. Override `hl.normal = 'Normal'` to make the picker blend with the editor instead. + +For finer control, set `hl.winhl` to override the per-window `winhighlight`. It accepts either a single string applied to every picker window, or a table with optional `prompt`, `list`, `preview`, and `file_info` keys. Missing keys fall back to the default built from `hl.normal`, `hl.border`, and `hl.title`. + +```lua +-- Apply the same winhighlight to all picker windows +hl = { winhl = 'Normal:NormalFloat,FloatBorder:FloatBorder,FloatTitle:Title' } + +-- Or override specific windows only +hl = { + winhl = { + prompt = 'Normal:Pmenu,FloatBorder:FloatBorder', + list = 'Normal:NormalFloat,FloatBorder:FloatBorder', + preview = 'Normal:NormalFloat,FloatBorder:FloatBorder', + }, +} +``` + +### File info panel + +Enable with `debug.enabled = true`. The panel sits above the preview and shows +file metadata, score breakdown, timestamps and the full absolute path. It +adapts to the panel width: at narrow widths sections stack vertically (B2), +at wide widths sections render as a two-column grid (H2). Each section can be +disabled individually via `debug.show_file_info`. + +Customise the panel via `hl`: + +| key | default | used for | +| ----------------------- | ----------------- | ---------------------------------- | +| `file_info_section` | `Title` | section header label | +| `file_info_separator` | `FloatBorder` | dashes that act as section borders | +| `file_info_label` | `Comment` | row labels (Size, Type, Git, ...) | +| `file_info_value` | `Normal` fg | plain values | +| `file_info_value_dim` | `NonText` | dim values, separators inside rows | +| `file_info_size` | `Number` | file size value | +| `file_info_type` | `Type` | filetype value | +| `file_info_path` | `Directory` | full path | +| `file_info_total_score` | bold + `Number` | total score (bold) | +| `file_info_match_type` | bold + `Special` | match type (bold) | +| `file_info_score_pos` | `DiagnosticOk` | positive score components | +| `file_info_score_neg` | `DiagnosticError` | negative score components | + +### File filtering + +FFF honours `.gitignore`. For picker-only ignores that do not touch git, add a sibling `.ignore` file: + +```gitignore +*.md +docs/archive/**/*.md +``` + +Run `:FFFScan` to force a rescan. + +### Troubleshooting + +- `:FFFHealth` verifies picker init, optional dependencies, and DB connectivity. +- `:FFFOpenLog` opens the current session's log file. +- Historical log files are stored near the main log file `/log/fff++.log` (up to 20 files) +- For a crash backtrace, run `lldb -- nvim` or `gdb -- nvim` and reproduce + +
+ +The best file search picker for neovim. Period. Faster and more intuitive queries, frecency ranking, definition classification and much more. + +
+ +

Node & Bun SDK

+
+ +```bash +npm install @ff-labs/fff-node +# or +bun add @ff-labs/fff-node +``` + +```ts +import { FileFinder } from "@ff-labs/fff-node"; + +const finder = FileFinder.create({ basePath: process.cwd(), aiMode: true }); +if (!finder.ok) throw new Error(finder.error); +await finder.value.waitForScan(10_000); + +const files = finder.value.fileSearch("incognito profile", { pageSize: 20 }); +const hits = finder.value.grep("GetOffTheRecordProfile", { + mode: "plain", + smartCase: true, + beforeContext: 1, + afterContext: 1, + classifyDefinitions: true, +}); + +// Run extremely fast glob matching which is significantly (10-100 times) faster than Bun's and Node implementation +const rustFiles = finder.value.glob("**/*.rs", { pageSize: 100 }); + +finder.value.destroy(); +``` + +Every method returns a `Result` (`{ ok: true, value } | { ok: false, error }`). Full type reference: [`packages/fff-node/src/types.ts`](./packages/fff-node/src/types.ts). + +
+ +TypeScript wrapper over the C library for nodejs and bun. Build custom agent tools, CLIs, or IDE integrations on top of FFF. + +
+ +

Rust crate

+
+ +### Add the dependency + +FFF is written in Rust, so this is the lowest-overhead way to use it. + +```toml +[dependencies] +fff-search = "0.6" +``` + +Full API documentation: [docs.rs/fff-search](https://docs.rs/fff-search/latest/fff_search/). + +
+ +Native rust crate that is performing all the search. Stable and well documented. + +
+ +

C library

+
+ +### Build + +```bash +# Builds only the C cdylib (fastest): +make build-c-lib + +# or directly with cargo: +cargo build --release -p fff-c --features zlob +``` + +> The `zlob` feature (requires the [Zig](https://ziglang.org) toolchain) switches both +> glob matching **and** filesystem traversal to [zlob](https://github.com/dmtrKovalenko/zlob)'s +> native parallel walker. Without it, the default build uses the pure-Rust +> [`ignore`](https://crates.io/crates/ignore) (ripgrep) walker and `globset`. + +The output is a `cdylib` (`libfff_c.so` / `libfff_c.dylib` / `fff_c.dll`). The header lives at [`crates/fff-c/include/fff.h`](./crates/fff-c/include/fff.h). + +Prebuilt binaries for every version, including every commit on main, are on the [releases page](https://github.com/dmtrKovalenko/fff.nvim/releases). The same binaries also ship inside the `@ff-labs/fff-bin-*` npm packages. + +### Install + +```bash +# System-wide (needs sudo): +sudo make install + +# User-local, no sudo: +make install PREFIX=$HOME/.local + +# Staged install for packagers: +make install DESTDIR=/tmp/pkgroot PREFIX=/usr +``` + +Drops `libfff_c.{so,dylib,dll}` into `$(PREFIX)/lib` and the header into `$(PREFIX)/include/fff.h`. Remove with `make uninstall`, which honours the same `PREFIX` and `DESTDIR`. + +Link against it after install: + +```bash +cc my_app.c -lfff_c -o my_app +``` + +Ensure `$(PREFIX)/lib` is on your runtime library search path (`LD_LIBRARY_PATH` on Linux, `DYLD_LIBRARY_PATH` on macOS, or an entry in `/etc/ld.so.conf.d/`). + +### Minimal example + +```c +#include +#include + +int main(void) { + FffResult *res = fff_create_instance( + ".", // base_path + "", // frecency_db_path (empty = default) + "", // history_db_path + false, // use_unsafe_no_lock + true, // enable_mmap_cache + true, // enable_content_indexing + true, // watch + false // ai_mode + ); + if (!res->success) { + fprintf(stderr, "init failed: %s\n", res->error); + fff_free_result(res); + return 1; + } + void *handle = res->handle; + fff_free_result(res); + + // Search + FffResult *search = fff_search(handle, "main.rs", "", 0, 0, 20, 100, 3); + // ... read FffSearchResult from search->handle, then fff_free_search_result() + + fff_destroy(handle); + return 0; +} +``` + +### Versioned options struct (preferred) + +For instance creation use [`FffCreateOptions`](./crates/fff-c/include/fff.h) — a +versioned struct that evolves without ABI breaks. C99 designated +initializers keep call sites readable and zero-init unspecified fields: + +```c +FffResult *res = fff_create_instance_with(&(FffCreateOptions){ + .version = FFF_CREATE_OPTIONS_VERSION, + .base_path = "/path/to/repo", + .ai_mode = true, + .watch = true, + .enable_fs_root_scanning = false, // off by default + .enable_home_dir_scanning = false, // off by default +}); +``` + +### Glob-only search + +`fff_glob` filters indexed files by a single glob pattern, ranks by frecency, +paginates — bypasses the regular query parser entirely. Use this when you +already have a literal glob (`*.rs`, `**/*.test.ts`, `src/**`) and don't want +fuzzy matching layered on top. + +```c +FffResult *res = fff_glob(handle, "**/*.rs", "", 0, 0, 100); +// FffSearchResult in res->handle, free with fff_free_search_result. +``` + +### Notes + +- Every function returning `FffResult*` allocates with Rust's `Box`. Free with `fff_free_result`, do not use malloc's free +- Payloads (search results, grep results, scan progress) have their own dedicated free functions listed in the header. +- C strings returned in the `handle` field (e.g. from `fff_get_base_path`) are freed with `fff_free_string`. + +Source: [`crates/fff-c/`](./crates/fff-c/). + +
+ +Stable C ABI. Bind from C/C++, Zig, Go via cgo, Python via ctypes, or anything with C FFI. + +
+ +

Python bindings

+
+ +### Install + +```bash +pip install fff-search +``` + +Or build and install from source: + +```bash +cd packages/fff-python +uv sync --all-extras +uv run maturin develop --release +``` + +### Basic usage + +```python +from fff import FileFinder + +with FileFinder("/path/to/project", watch=False) as finder: + finder.wait_for_scan_blocking(timeout_ms=5000) + + result = finder.search("main") + for item, score in zip(result.items, result.scores): + print(f"{item.relative_path}: {score.total}") + + hits = finder.grep("class Profile", mode="plain", before_context=1, after_context=1) +``` + +### Async usage + +`wait_for_scan` is a coroutine that polls scan status and yields to the event +loop, so it never blocks other tasks. Use `wait_for_scan_blocking` from +synchronous code. + +```python +import asyncio +from fff import FileFinder + +async def main(): + with FileFinder("/path/to/project", watch=False) as finder: + await finder.wait_for_scan(timeout_ms=5000) + result = finder.search("main") + print(result) + +asyncio.run(main()) +``` + +### What you get + +- `search`, `glob`, `directory_search`, `mixed_search` — frecency-ranked fuzzy file/dir search +- `grep` / `multi_grep` — plain, regex, or fuzzy content search with context lines and cursor pagination +- `track_query` / `get_historical_query` — optional frecency and query-history databases +- `reindex`, `refresh_git_status`, `scan_progress`, `health_check` — lifecycle and diagnostics + +Typed result objects (`FileItem`, `Score`, `GrepMatch`, …) with `py.typed` +stubs included. Ships as an `abi3` wheel compatible with Python 3.10+. + +Source: [`packages/fff-python/`](./packages/fff-python/). + +
+ +Native Python bindings built with PyO3. Use them for notebooks, agent scripts, or any Python tool that needs fast file search. + +--- + +## What is FFF and why use it over ripgrep or fzf? + +FFF is a file search library, not a CLI. Ripgrep and fzf are great tools, but they are command-line programs: every call forks a new process, re-reads `.gitignore`, re-stats directories, and rebuilds whatever state it needs in memory before it can answer. That is fine when you grep once from a shell. It is bad when an editor or an AI agent wants to run hundreds of searches per session. + +FFF keeps the index and the file cache resident in one long-lived process and exposes the same Rust core through four thin layers: a native crate (`fff-search`), a C library (`libfff_c`), a Node/Bun SDK (`@ff-labs/fff-node`), and an MCP server. You call `FileFinder.create()` once, then every subsequent search hits warm memory. On a 500k-file Chromium checkout, that is the difference between 3-9 **SECONDS** per ripgrep spawn and sub-10 ms per FFF query. + +Algorithm for fuzzy matching is much more comprehensive than fzf's algorithm it is **typo-resistant** and we provide a query language with additional constraint parsing for prefiltering e.g. "\*.rs !test/ shcema" is a perfectly valid query for fff, but fzf wouldn't find anything even for a single typo in "shcema". + +### Why a programmatic API matters + +- No process spawn. Every call stays in-process and avoids the fork, exec, argv parsing, and stdout pipe setup that dominates short `rg` invocations. +- One FS walk, metadata collection, and parse of `.gitignore`. The ignore walker runs once at scan time and the result is reused for every search. +- Results come back as typed objects, not text you have to re-parse. The SDK gives you `{ relativePath, lineNumber, lineContent, gitStatus, totalFrecencyScore, isDefinition, ... }` directly. +- Cursor pagination that survives across calls. Ripgrep has no concept of "page 2 of these matches"; FFF does. +- A long-lived process opens up optimisations that a one-shot CLI cannot apply: warm caches, incremental re-indexing, cross-query frecency, and shared SIMD state. + +### What the core actually does + +- **Frecency-ranked fuzzy matching.** Every indexed file carries an access score and a modification score. Searches rank files you have opened recently and frequently above cold results. This is the same idea as VS Code's recently-opened list, but applied to every search result, not just a sidebar. +- **Typo-resistant matching for both paths and content.** Smith-Waterman fuzzy scoring is available on the grep path; path search uses SIMD-accelerated fuzzy matching (via the [`frizbee`](https://github.com/saghen/frizbee)-derived core) that survives dropped characters and reorderings. +- **Content grep with three modes.** Plain literal (SIMD memmem), regex (the Rust `regex` crate), and fuzzy (Smith-Waterman per line). Auto-detects which mode to use from the pattern, falls back to fuzzy when a plain search returns zero hits. +- **Multi-pattern OR search.** SIMD Aho-Corasick for "find any of these 20 identifiers at once", which is faster than regex alternation and a lot faster than 20 separate ripgrep runs. +- **Background file watcher.** The index updates as files change. You never pay for a rescan on the hot path. +- **Git status awareness.** Modified, staged, untracked, and ignored states are cached and returned with every result, so callers can sort or filter them without shelling out to git. The watcher talks to libgit2 directly instead of spawning the `git` CLI. +- **Definition classifier.** A byte-level scanner on the Rust side tags lines that start with `struct`, `fn`, `class`, `def`, `impl`, and friends. + +### Performance choices that matter + +- Efficient memory allocator and memory allocation strategy (see next paragraph). By default we use `mimaloc` +- Parallel multi thread search pipeline that is not contaganted by the orchistration logic +- SIMD first algorithms for everything. Efficinet & non-allocating sorting. +- Platform specific optimizations for FS ([getdents64](https://linux.die.net/man/2/getdents64), NTFS api on windows and others) +- Lightweight on the flight content index for realtime even typo resistant grep +- Memory mapped content cache. We store some of the files in virtual memory (the amount is limited) +- Single contiguous arena storage of string chunks. Significantly reduces the amount of memory to work with and dramatically increases CPU cache hits. + +### Memory allocation + +Yes, fff fundamentally requires more memory than calling a single child process. That is the primary source of the speedup. In practice, alongside one of the most popular file search pickers for Neovim, [fff ends up using less RAM than a burst of ripgrep invocations](https://x.com/neogoose_btw/status/2041606853155811442). + +FFF also keeps a content index, around 360 bytes per indexed file, so roughly 36 MB for a 100k-file repo. Not every file is indexed - binaries, oversized files, and anything not eligible for grep are skipped. If even that footprint is too much, the index can be backed by a memory-mapped file instead of anonymous RAM. + +### What this means in practice + +If you are building an agent, an IDE extension, a pre-commit check, or any long-running tool that searches the same repository many times, calling FFF as a library is dramatically cheaper than shelling out to ripgrep. The tradeoff is real memory: FFF keeps the index in RAM and warms the content cache. On a 14k-file repo that costs about 26 MB resident. On a 500k-file repo like Chromium, expect a few hundred MB. In exchange, every single search is enriched with git status, frecency ranking, file metadata, timestamps of last access and edit and so on. + +If you are running one grep from a terminal, `rg` is still the right tool. If you run dozens of them inside the same process, FFF will pay for itself starting from the second call. If you work on AI agent fff will finish preparation work before your AI will have a chance to call it. + +### How it compares + +- **ripgrep**: FFF uses the same underlying regex engine and more advanced plain text matching algorithms. Stores content index and file tree. Main wins on repeated-search workloads. Loses on "grep once from bash and exit." +- **fzf**: FFF's path search is fuzzy like fzf, but it is also frecency-aware and git-aware, and ships a more typo-tolerant algorithm. fzf is a pure match-and-filter tool; FFF ranks results by how often you actually open them. +- **Telescope / fzf-lua / snacks.picker**: FFF ships its own Neovim picker with the same ranking the MCP server and SDK use. The picker is optional; the core is the same. +- **Tantivy or other full-text search engines**: different class of tool. Tantivy indexes documents for query-time scoring at scale. FFF is scoped to one repository and optimised for sub-10 ms response. It does not persist an inverted index on disk. + +--- + +## Repository layout + +- `crates/fff-search`, `crates/fff-grep`, `crates/fff-query-parser` - Rust core. +- `crates/fff-c` - C FFI used by every language binding. +- `crates/fff-nvim` - Lua/mlua bindings for the Neovim plugin. +- `crates/fff-mcp` - MCP server binary. +- `packages/fff-node` - Node.js SDK (`@ff-labs/fff-node`). +- `packages/fff-bun` - Bun SDK (`@ff-labs/fff-bun`). +- `packages/pi-fff` - pi extension (`@ff-labs/pi-fff`). +- `lua/` - Neovim-side plugin code. + +## Contributing + +Bug reports and pull requests welcome. Agentic coding tools are welcome to be used, but human review is mandatory. + +## License + +[MIT](./LICENSE) & open source forever. diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..9aa2531 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`dmtrKovalenko/fff` +- 原始仓库:https://github.com/dmtrKovalenko/fff +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..6c280a3 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,26 @@ +[files] +extend-exclude = ["/CHANGELOG.md", "data/filetypes/base.lua"] + +[default.extend-words] +noice = "noice" +fo = "fo" +ba = "ba" +ue = "ue" +# file extensions that look like typos +thm = "thm" +# some typos we use for tests +comparsion = "comparsion" +modfiers = "modfiers" +shcema = "shcema" + +[default] +extend-ignore-re = [ + # git commit hashes in CHANGELOG.md + "\\[[0-9a-f]{7}\\]", + # Line ignore with trailing # spellchecker:disable-line + "(?Rm)^.*(#|--|//)\\s*spellchecker:disable-line$", + # Line block with # spellchecker: + "(?s)(#|--|//|)\\s*spellchecker:off.*?\\n\\s*(#|--|//)\\s*spellchecker:on", + # Vim doc formatting + "vim:.+:" +] diff --git a/assets/logo-dark.png b/assets/logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..57e2b763ea27c61c26e067b4f9daeb2173bc02ea GIT binary patch literal 7767 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 G$o~hh;L`8_ literal 0 HcmV?d00001 diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..2103849 --- /dev/null +++ b/biome.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.7/schema.json", + "files": { + "includes": ["packages/**/*.ts", "!packages/*/dist"], + "ignoreUnknown": true + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 90 + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "trailingCommas": "all", + "semicolons": "always" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "off" + }, + "suspicious": { + "noExplicitAny": "off" + }, + "complexity": { + "noForEach": "off" + } + } + } +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..568f126 --- /dev/null +++ b/bun.lock @@ -0,0 +1,417 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "devDependencies": { + "@biomejs/biome": "^2.4.4", + }, + }, + "packages/fff-bun": { + "name": "@ff-labs/fff-bun", + "version": "0.1.37", + "bin": { + "fff-demo": "./examples/search.ts", + "fff-grep": "./examples/grep.ts", + }, + "devDependencies": { + "@types/bun": "^1.3.8", + "typescript": "^5.0.0", + }, + "optionalDependencies": { + "@ff-labs/fff-bin-darwin-arm64": "0.0.0", + "@ff-labs/fff-bin-darwin-x64": "0.0.0", + "@ff-labs/fff-bin-linux-arm64-gnu": "0.0.0", + "@ff-labs/fff-bin-linux-arm64-musl": "0.0.0", + "@ff-labs/fff-bin-linux-x64-gnu": "0.0.0", + "@ff-labs/fff-bin-linux-x64-musl": "0.0.0", + "@ff-labs/fff-bin-win32-arm64": "0.0.0", + "@ff-labs/fff-bin-win32-x64": "0.0.0", + }, + }, + "packages/fff-node": { + "name": "@ff-labs/fff-node", + "version": "0.1.37", + "dependencies": { + "ffi-rs": "^1.0.0", + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.0.0", + }, + "optionalDependencies": { + "@ff-labs/fff-bin-darwin-arm64": "0.0.0", + "@ff-labs/fff-bin-darwin-x64": "0.0.0", + "@ff-labs/fff-bin-linux-arm64-gnu": "0.0.0", + "@ff-labs/fff-bin-linux-arm64-musl": "0.0.0", + "@ff-labs/fff-bin-linux-x64-gnu": "0.0.0", + "@ff-labs/fff-bin-linux-x64-musl": "0.0.0", + "@ff-labs/fff-bin-win32-arm64": "0.0.0", + "@ff-labs/fff-bin-win32-x64": "0.0.0", + }, + }, + "packages/pi-fff": { + "name": "@ff-labs/pi-fff", + "version": "0.6.0", + "dependencies": { + "@ff-labs/fff-node": "*", + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.0.0", + }, + "peerDependencies": { + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + "@sinclair/typebox": "*", + }, + }, + }, + "packages": { + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="], + + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.974.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@aws-sdk/xml-builder": "^3.972.28", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JDYCPI0j7zGrzXTDFsLB346cxss7J/AxH7+O0MzWlqppJBEyB9Qe6TQXRL6iwLUo/xZkNv9KFmBL2hqElmwW0g=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.44", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-3hKJVrZ7bqXzDAXCQp+OaQ1ASN+vWstaNuEH418wQVl//cRZhqhfR9Bjk1qIWmgUGe8/D3gdO73PgidRj378EQ=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-VhwC9pGAZHhiQ2xSViyOPDFqvr9aRxGCAXZtADsUhU3R65nad7y//CwynE6mQnWNR+suRlqE79W36IVayL+m1g=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.50", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-env": "^3.972.44", "@aws-sdk/credential-provider-http": "^3.972.46", "@aws-sdk/credential-provider-login": "^3.972.49", "@aws-sdk/credential-provider-process": "^3.972.44", "@aws-sdk/credential-provider-sso": "^3.972.49", "@aws-sdk/credential-provider-web-identity": "^3.972.49", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-09Xi6ovxiK42+De/qBGF71sT5F2bWgYM+1fFyDwSOpy1xpsQ5R/naIu7MVDpH6Dic36QNc8dAv4KADtMGK2JYg=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-EfJF/1Fh9mI4pZyoheU2RY9xUhTcugIZNkD63+orXMkYj/QXacJNbKVDUK90Yv5hE+aX+rt9J/EZ9Qr3vKOa7g=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.52", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.44", "@aws-sdk/credential-provider-http": "^3.972.46", "@aws-sdk/credential-provider-ini": "^3.972.50", "@aws-sdk/credential-provider-process": "^3.972.44", "@aws-sdk/credential-provider-sso": "^3.972.49", "@aws-sdk/credential-provider-web-identity": "^3.972.49", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-7QX+PbyiWBEOVipJq8Nke/TqXT6lAPLE7fvTaopa39/IVWuLfS+Fzdy71sZJONf/mLGgmtj6aU17+REw3+aRrw=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.44", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-V+UUhZpRP7QDRhi+qgBDisM9tUBnYmMje8Bk77A6MZsfeGeGdMsQXmaHP1CDYFcept0o/Rz5g2Y0TMeVlG9dzg=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/token-providers": "3.1063.0", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-9QqOYGuh5tZ76OzaT68kwI78AH+5lS/uZGGvkfxb3fc8FzRrIz2jOufNTliEBEeSAwmgK2rWLNsK+IB3zbtNPA=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-IYx1lN38MnnPXv+NBLpuATu0cZakbZ321TAfjW+aVkw7HIJF38YnEwdeEO55MSl3pl7hIX1IvvnD6EmnAzmAJw=="], + + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.20", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-qr/S1iFCDIXlZwlZPaCqjKcHbJFr9scIFUhbh2+SrwPXZvRhyOUWjVDJpp8xoU4qrrMR0PqK1Yw5C2sSj7xAyw=="], + + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.16", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-KR2Gdui/QLbkdG9FxW3vk/vIa8KiDP5vQBNERo7MmlPHjn23GXJ53Cq5P/ok7/ALbTUiYZ78DiBHoDcvzPWvgQ=="], + + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-foM3KvxGBHY9lRIm6C9JJJ5haodtXfJPPgJQcv5/c4A2pN4I7tlnOjh1o2d8Il1Y/j6GWOw3YeIYc2/VYjtGVQ=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.17", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/signature-v4-multi-region": "^3.996.32", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-lDRgraoTfKRawUyc176Ow93mrNrOho/x+EoK4C+lKU+vKkHWhNhzvSMVAx0WEJUJoeQxxDN5ZdKMfiGEyNejig=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.32", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-llvApLcsWtmRFhG2wT3WIp1CmDeRaIYutqty1ZZXoMzK7TiJ6MOLOimk9eXUS8PwgG4ew4pa4QAbt0lfhn++1w=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.973.11", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-YjS0qFuECClRh4qhEyW8XagW0fwEPBeZ1cfsW/gU73Kh/ExFILxbzxOfPCmzF/2DwEvhvsHYt0b0qnvStwKYrg=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.6", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZfHjfwSzeXj+Lg9AK5ZNmeDkXev6V+w2tn1t4kgDdRtUaRCthepTQiFwbD06EF9oNGH4LaLg+Mb6U16Ypv5bSw=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.28", "", { "dependencies": { "@smithy/types": "^4.14.3", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-lI/l3c/vPvsxmspzV63NfS3x9q4CkMmdhJy4QiM+NThAufVkDvi/PZZQ6xETnICL0UD7jI808pY83gllf86RFg=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@biomejs/biome": ["@biomejs/biome@2.4.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.4", "@biomejs/cli-darwin-x64": "2.4.4", "@biomejs/cli-linux-arm64": "2.4.4", "@biomejs/cli-linux-arm64-musl": "2.4.4", "@biomejs/cli-linux-x64": "2.4.4", "@biomejs/cli-linux-x64-musl": "2.4.4", "@biomejs/cli-win32-arm64": "2.4.4", "@biomejs/cli-win32-x64": "2.4.4" }, "bin": { "biome": "bin/biome" } }, "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A=="], + + "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.78.1", "", { "dependencies": { "@earendil-works/pi-ai": "^0.78.1", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" } }, "sha512-oPwVRkkAvyKPWyM7E4k+EaTNmynbYn7ZLG/LBh9BUnMNb2gvpMp+VQ420R6JCJ20uogSqrHnWTyosSa/rU8lVw=="], + + "@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.78.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.1", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.1.38" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-CM2pkTs1iupG/maw381lC9Q/Y/aQaMGK7GILc28ttImD0ci3LDwKroDsGkWbly5JIy3iqxdRxB9JlG7vvzCzTg=="], + + "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.78.1", "", { "dependencies": { "@earendil-works/pi-agent-core": "^0.78.1", "@earendil-works/pi-ai": "^0.78.1", "@earendil-works/pi-tui": "^0.78.1", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "typebox": "1.1.38", "undici": "8.3.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/cli.js" } }, "sha512-Syjf6Ib8UoY5t9ZdKjp0BRrQZuFkFBc8j2KEU9zG/ZnmYPcAxYeioofdv2Q3MEXnHEX2U8sKQptkSnJIdMsd0g=="], + + "@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.78.1", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "15.0.12" } }, "sha512-07GVQo/38a0yvIPlWDr3RJn1B8gk3ZuIX9h2oIQ+Biyu3JN0KppWmgWHfaWRydQgse5JtC++KDw5MWaIRnV0mw=="], + + "@ff-labs/fff-bun": ["@ff-labs/fff-bun@workspace:packages/fff-bun"], + + "@ff-labs/fff-node": ["@ff-labs/fff-node@workspace:packages/fff-node"], + + "@ff-labs/pi-fff": ["@ff-labs/pi-fff@workspace:packages/pi-fff"], + + "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="], + + "@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="], + + "@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ=="], + + "@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.9", "", { "os": "darwin" }, "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ=="], + + "@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg=="], + + "@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw=="], + + "@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ=="], + + "@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.9", "", { "os": "linux", "cpu": "none" }, "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw=="], + + "@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw=="], + + "@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ=="], + + "@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ=="], + + "@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA=="], + + "@mistralai/mistralai": ["@mistralai/mistralai@2.2.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" } }, "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ=="], + + "@nodable/entities": ["@nodable/entities@2.1.1", "", {}, "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + + "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="], + + "@smithy/core": ["@smithy/core@3.24.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.8", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ=="], + + "@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + + "@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], + + "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], + + "@yuuang/ffi-rs-android-arm64": ["@yuuang/ffi-rs-android-arm64@1.3.1", "", { "os": "android", "cpu": "arm64" }, "sha512-V4nmlXdOYZEa7GOxSExVG95SLp8FE0iTq2yKeN54UlfNMr3Sik+1Ff57LcCv7qYcn4TBqnBAt5rT3FAM6T6caQ=="], + + "@yuuang/ffi-rs-darwin-arm64": ["@yuuang/ffi-rs-darwin-arm64@1.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YlnTMIyzfW3mAULC5ZA774nzQfFlYXM0rrfq/8ZzWt+IMbYk55a++jrI+6JeKV+1EqlDS3TFBEFtjdBNG94KzQ=="], + + "@yuuang/ffi-rs-darwin-x64": ["@yuuang/ffi-rs-darwin-x64@1.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-sI3LpQQ34SX4nyOHc5yxA7FSqs9qPEUMqW/y/wWo9cuyPpaHMFsi/BeOVYsnC0syp3FrY7gzn6RnD6PlXCktXg=="], + + "@yuuang/ffi-rs-linux-arm-gnueabihf": ["@yuuang/ffi-rs-linux-arm-gnueabihf@1.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-1WkcGkJTlwh4ZA59htKI+RXhiL3oKiYwLv7PO8LUf6FuADK73s5GcXp67iakKu243uYu+qGYr4RHco4ySddYhQ=="], + + "@yuuang/ffi-rs-linux-arm64-gnu": ["@yuuang/ffi-rs-linux-arm64-gnu@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-J2PwqviycZxaEVA0Bwv38LqGDGSB9A1DPN4iYginYJZSvTvKW8kh7Tis0HbZrX1YDKnY8hi3lt0N0tCTNPDH5Q=="], + + "@yuuang/ffi-rs-linux-arm64-musl": ["@yuuang/ffi-rs-linux-arm64-musl@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Hn1W1hBPssTaqikU1Bqp1XUdDdOgbnYVIOtR++LVx66hhrtjf/xrIUQOhTm+NmOFDG16JUKXe1skfM4gpaqYwg=="], + + "@yuuang/ffi-rs-linux-x64-gnu": ["@yuuang/ffi-rs-linux-x64-gnu@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-kW6e+oCYZPvpH2ppPsffA18e1aLowtmWTRjVlyHtY04g/nQDepQvDUkkcvInh9fW5jLna7PjHvktW1tVgYIj2A=="], + + "@yuuang/ffi-rs-linux-x64-musl": ["@yuuang/ffi-rs-linux-x64-musl@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HTwblAzruUS16nQPrez3ozvEHm1Xxh8J8w7rZYrpmAcNl1hzyOT8z/hY70M9Rt9fOqQ4Ovgor9qVy/U3ZJo0ZA=="], + + "@yuuang/ffi-rs-win32-arm64-msvc": ["@yuuang/ffi-rs-win32-arm64-msvc@1.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-WeZkGl2BP1U4tRhEQH+FXLQS52N8obp74smK5AAGOfzPAT1pHkq6+dVkC1QCSIt7dHJs7SPtlnQw+5DkdZYlWA=="], + + "@yuuang/ffi-rs-win32-ia32-msvc": ["@yuuang/ffi-rs-win32-ia32-msvc@1.3.1", "", { "os": "win32", "cpu": [ "x64", "ia32", ] }, "sha512-rNGgMeCH5mdeHiMiJgt7wWXovZ+FHEfXhU9p4zZBH4n8M1/QnEsRUwlapISPLpILSGpoYS6iBuq9/fUlZY8Mhg=="], + + "@yuuang/ffi-rs-win32-x64-msvc": ["@yuuang/ffi-rs-win32-x64-msvc@1.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-dr2LcLD2CXo2a7BktlOpV68QhayqiI112KxIJC9tBgQO/Dkdg4CPsdqmvzzLhFo64iC5RLl2BT7M5lJImrfUWw=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + + "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], + + "fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "ffi-rs": ["ffi-rs@1.3.1", "", { "optionalDependencies": { "@yuuang/ffi-rs-android-arm64": "1.3.1", "@yuuang/ffi-rs-darwin-arm64": "1.3.1", "@yuuang/ffi-rs-darwin-x64": "1.3.1", "@yuuang/ffi-rs-linux-arm-gnueabihf": "1.3.1", "@yuuang/ffi-rs-linux-arm64-gnu": "1.3.1", "@yuuang/ffi-rs-linux-arm64-musl": "1.3.1", "@yuuang/ffi-rs-linux-x64-gnu": "1.3.1", "@yuuang/ffi-rs-linux-x64-musl": "1.3.1", "@yuuang/ffi-rs-win32-arm64-msvc": "1.3.1", "@yuuang/ffi-rs-win32-ia32-msvc": "1.3.1", "@yuuang/ffi-rs-win32-x64-msvc": "1.3.1" } }, "sha512-ZyNXL9fnclnZV+waQmWB9JrfbIEyxQa1OWtMrHOrAgcC04PgP5hBMG5TdhVN8N4uT/eul8zCFMVnJUukAFFlXA=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "gaxios": ["gaxios@7.1.5", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg=="], + + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "google-auth-library": ["google-auth-library@10.7.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ=="], + + "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + + "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="], + + "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], + + "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], + + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + + "protobufjs": ["protobufjs@7.6.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], + + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + + "@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1063.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-nYDaWWdzjKiDP5xj8k4oUgcYd4WPgzfAOgdU5vJsaqH/07Dfvm7ffisHCFJ+NEl7kUC9JEIUxh0kznvenbo3NQ=="], + + "@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="], + + "bun-types/@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="], + + "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "protobufjs/@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="], + + "bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "protobufjs/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + } +} diff --git a/chart.png b/chart.png new file mode 100644 index 0000000000000000000000000000000000000000..00ffea12769dbfb84f7f2d264e7a8ecb112590e7 GIT binary patch literal 323968 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
H|QMizbTD( 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%0DS;@@paT;xIE&MSmOAqmD6C3mSo9m;;%_{Xdju8 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

HJS8`)7vro=?XKy-EBwu)OMT`#+}y0 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

I~euVtJt^Zty@A=)>=~#su(ntZq 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

BqzAHvmTybU`xwcsSh= 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%0AOK!zYdOGu2WnR%77jbraHRK6_1$RFztfb- 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�d}xFKxZxUJ)=OxS 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�<#TbAn=5Gz9O*XaI-iM6BaE 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

=~t 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

l{!pV%r=$KISPK)5!6uVbSPhk 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�UXxg|Xjel)tu_cyA3HN2qZrTAjNI$o;HR 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

9g9Vk~sN^gU;|# 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

w4wDI( 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

bdb&AyteMKJ4qIs00@T8^$Nu%ncnv|4ba*DWpb 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

g==D+JvcO=D#KNVO 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 XmA`YlghJ^bv_+~|zM3WRVQ%Pgs=(cm~SPHpk8 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®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≫8xusm}_)Nl3dQ}fL}wDeZqI!q-;>@KZgX{LzB=TQ1s#bF@$5ersuQXsyl=U;#5Neep3m<3 zgt2_Wkuh_2)Epdqf_=8!>=`z@(9&-fn%%{7L)V^t$(s4$I?sN$V69!$kCH`P^5vKJi<==)vuD)o6{CW| zIU4oGg{mQ+VK>i&)q~@5BLCyrYmJiK;E#!!8<=@Z!ZBu!If$Q_a_<~9r$o)zXtQ7I zFEt0m%pod$bdI|8pW((Vf0{;Mc{t`z@S6(S+Zpra9{(^!f9Bb$3UzwD8J`>3!mg<> zLuq=B9y}d3v%@RAX=V^TH6rRy@E|+8sJz3o|1#?Gc!a|>uPCp{GGiSL9vf>gyGPAb zF@swE6qu>Ge)Foy+W4WCqVZTW0dZ_E9D5N%?2W z%!sgU)a}LLXk?uIQyJ6kG`q^0!$_U1}6 zDNM^q9-`z~`cOFEepC={%W}oV$ozcone;o6+0?aJZED35^AKZrNX$RbT$HQH($4LT z-F|=3b<({+*TKKjTg?Q$uqJZ4XE%me$FwMV=7=0Kok=n% z=8yGj-E+G7FBMEZJ-5$=>40kcPzBxV7x0SR+ywh=WxX1iYdXW*nfcM=Pt3d%&Nb^y z)U1H1n=`3+pBQs98^SaBylrTXM=fv-{zQIei}QJy#lZCTuq5ByNA0`9qCfTz^c(z` zzs8)pwf~hlDJrfCRZN1m!XhPjOs+XGNEEvute^mwb4GSods3@}Ri=T?j&JWEAJ~O(*e9`{tT> z)Z7`4T@~E#2`6k6AACOyV-1!ye-d-fT;9($DBG0hFe#V>_nElAf%n}MjMO@xj$X_A z%u+bxglMZ7DR$@&q&P$C*{|cv?Ju%)S3*|}o|%&44L?<#b|4cq-j)SfsT1b#Nk^NJVLOtE zGm93ks`JbV5KD9LXHcE_ZJ6G9?{t5XKX#Qr7Ix#oiYDowQ+y;c+Ou=iqiu)gn&YAy zY_Y$i-MYvhWFDm4Ab(K1X$|N0TF#%|v)Dj?C(m9}$+&ff9LF0k*4jo516OzwFj5UO ztKg#p&9s<#+R&tW&s@g~e|owZk{6k5AFr&34ow3S;}(-^Mn}y)(a3>z|HPubwuXhu zc2xMw?KNS)Fxwm>o>3X~f(M`JL2c@R=W>5i;kVhPE>ZlxZbb=8=)e6X{=ZmAhiv69 zGei3O*O?((`q$e9W=Pn-!Oqv88|^&)*v*k`hQ!R#Q5JK*VQw=G^6Z}~3?gVlnC_b! zm8^AK)L#u{YJ!ZN8owphDglZ5{@>Td->vQQ-ru>N~31{)*2Lsb~2N5 zK77V4;aBtN%lL%}WSaxs^c?9IGI8y%!_0qHX!RK_der2#`tREIik9`XiMqFS{A6#Y z7!^47whp%L*i+1mLbwtWU1EP%(O7M>s#YOd)iaQG?dE=qzo@~!I^WC++j=kG-WSf7 zH`J}HUhn_w4gWiT=XN^!I-@Q|?pr*6xo5v(nn-Sj^Iox#8R3)) zzsNP*qxEKA7)wn!$6SRtrX~0x!{;KT{M56DtW9$2D|qOu`;e<6-uuBYNg1U(KyK=xMq8~uUnSwEQ7iy+af#8z&qFjZlMe-%0_THwEKOPDDA zizJ^6^j|mkn;1fX-`Q$*j!|ZhSSyloq@TUFvPBGavRx*o(M8iyULA?RjLjHiuzGRd zhDv`&<&D_a9P@1mYPho!fz2hqu4KeBM+~>8a=)Qu-p~ju>G6Wc^n%F+w3%V@{M>-M z&?iSa%DBl^ITokovQwIZePKb|-Y@aBgBdjmKJ;hD&u(`7Y=hsV?l8yYnw?_yS7k^z zP|gO+0Ih``5017=a@!HYxL5htlbv}nA(zF*v-d7=BK)6{ybg{S(0Y?;ViANW#zHl5 zb8^_O6MwFtH4nzoM(lICUCev;%Jmyq-x|#Dn18Q7*8aNGZ)BZoweM}-(&}}1!%wr9 zm$vcxe~2#a&2e)gLPfGPgbb-7>8vS53^wQII1%T*W?ql@_FLM3FfuLXA7EbNU1%%E zM$H1(Z}u_X+4sbHxH>G}#KY_lyd~^EJT*25l z4f!xS<1lqbTpt#2GBY-2hC?ggl}NH&l+~iEh3<+)Flr8sB7g8&mCbf~Tu>H4PaQs2 z9i(S9m0b;j_@T5xQ6@4^kI#wh=Gm4~F*x-ovc|sA$LQ!HjW60J0xk_2&_s(mymQR~ zj?fTY>%uKgGz}gs@u&MG^v@IKuvndA?2X>->cG5C5vJ6_3dAW^c4Yd!qUJBiLUai_ zt$k9W&{IyRUM_+4ul=wlNHpn$*&G%{Wq5fmOA(Vy76-Gl8}EjB9si91clHAY9NDoH z!Q&jqzo%=pMVMcUC?C%@zlM5mMn7sUMFT{vJVbg8qBvsT`ew&6q+Z<+hAkcvZH8Gb zu53a2SdLnr+uDk}`AG-J=IC*r$}CDDDACUNHA*8gX-U{K*4%6?AN1!F|*Q2sNS79zU;mDcx!7xfIqxvZE9Fsx{M zzm8cC%Q#%$c7`*_yjUiwYb}!udz>V`X~=u-^-CsboDWnOsWX2_6%+F}mGOmj5NzGR*$ z2-|<;3!c= zb)bj-*;!wlq`p#=WOi~a_knWeok$gF#MNleuK)h^KF{BAeL_KJ(Yz;&*+zy@|3>o& zuf-CWpNCP-{R}XVk#rC1Aq(mmG{|oh2|pYTY#>N%k<;xxWoWWYx8GKX++PZ}C<_|t zMPW%bV=&ffF>Q?Oj}@srSaHUSdp1^Lhm{BkA0E~u*{>A`Mg27vd=WrM|4n|CPjNh^Nh8YCsAxD z>ldKybb3g+XbA3KNcfI^`3x3MUt0k4+$HfK_4sZc9}e~1v%pP+^=S0j=3r-?zmEP7 zRC3QQnJ3r`Mt@*ceSL)Fo`JQ#BTO*D$f> zJELNtC3Qr63<&d~nRsMN`?QhOj%<)_#J7)^x5@Inr;JqxAYmOHoi1aHcsKUY%{<#* zQlbjpnqF`-`{(jDr)cfDt!BvKQS4TUsbIG>r?VP0M)vXS`ikXQ2pfS#3hU!}PNJM< zXG^QG+o*qF>5KjIB@GOcy#^EEbocNl!NbVUJv(i=6Ow49nGvftiy-iQqy7xPy4o)2 zVDjh4XtFP#Abxx1HvUZiP}$F@GFUzPdv;jLj3nCKBWC{TnhV#>u93m~@-SirY42iQ zqwUytQDf}9w%|6hC5nd=RhL7G>OWNLJ)?Fis?5Mv^Dc|0EPeUt>!UEq8?=QbSS1R@ z52icK_TYp#X2!+L`G^Uv((tiRUn92jrIXw8^_Df2tR`j;S{}96vI+zPB`cD9;kV^$ zn-H2Yg;v|oD&)4hw$dCEMucVjTE#07Sck|9U_TDYHhoQBkN@T#FYfE{r|$7peLeon zJ-)K9$Dg~$S8VdJCjiSubb7hvnvsu)V=h z2qou?sJ{$TW4SZ=uW)&HVolnj#+;O^sj<;UeJCMnw?exABUY%n(rRxrytosAz0|gr zLjz&>FS347ko1yEB3C*7MC|h>-ps%|dXH?*;!^GuALi8(n>Hagx1W_UAz4oc<@jq6 z8Us~A!kAX6$Q1k}EZ%{+gV~Oq#uUdIHbD9qo~~tLTpNy|=bpkhp_-cGu{GE@irH30 z&cOXNHbJGNL?k$UPYOziM3`x83YKs(}ShkEwZM0;5`={Dqz@iH1b z$YQQ?T&UUyeHF@d(6;%d(t0OXz5XA~*xhM~;%`0P|hz zZvOeLrWkoC7vbME24ecOj2^AAuT@a6nkC$vOU2h!$Ta)0#KpDV5*d+ki?bvQMowWF z@}Ht~hsfm@6^U6=S3;cyINlN3f(dkoR}<*iWAVK*{R_-k5eq8m)_9pQzdKP6Uu-!7 z)LVvE$piWe%ud(_5$W2v>)&{bn>R;k-ay)a3CU?`SC%8O*>^Ncj>N_t+1^$ocKOwj zt(k9F>&a4wp!F9j)#S1(Dr@}CcU2SOITY_&KgF|c<=Er{n%yy=*-^3THFBR|)zB~Q zYDjih%|wX5$;KN+u;chda~T8+n|L9Fd?+W5%pR;%THk*aj;>4n!@H^^FV~5I{uiwn z2cUU{Wl(UI2+hd}xiV)!tCKu)K@NThIrBUmw3ugd{U+E8%-Vkpm#j1@>}X6NQKF2z zm2Rng6Xq|^lBs319VLHJv0Ut40>LtKVAPC`v3g;t|9OsAXF6~?nnH3`h4Yxs4aw=f>0j5mY9)9}Mu>%wt0X@6+U-XAtU|FP8|uQfKw z2nM6DZ&c_7w+F3WiqeC1G7(J#Juq`8oE*3Z^sUb-z=DiiD3i zScBlSmI6vJB`V|gYx$8-Sy{mu|NIDp)q+JO=?DF}6TKGU9!z*crpM@}G8T|GEF^nk z4mYe0Z`n9X^QUDJv5;-=h7+WG3Z7kENxJX^WOD?4mS-Px-d2RgX!QhGs$L!!cR(0} zxyTC}@Ed2FTeT2(=^i=u-{Kjq_HqLZKnp;Peb#9x_`h!V+xZ{Aq`y%HMMk!Uah3XV zQVusrDQ^Ut2|lKkoGNj%{S7X?H?ae@pi(v3 zwH1y2QtJ45)Kuh}Vb}{{ul`e#y=7vFA)zmfX+dDMrP^;2)p{#gV6i~KvU)APTE*d{ z3v*48KrHxV_JSLY&ai`0)q}j?lSgRNE%o1|$U~Ua^29C3CIfp9eFpOkO zieW)1b?P@sJ3OLxBXVYe;9k%jqAc3t_?YN$y zWS^5|N6hMh`S^+K^T91FP?n?LzyKbl3TBVsEK$5Z2k}|k*e#$1CdGWM_OGi5bdqgG zJ8`IazB8_7Eb^EZp0;tfDn_=lztk#*-Qj&%UIMcNIzQZkPrI?7N`rTa+VNF*K-Wx< zoNbrRZE1bmZqY&6^?u7byN6%3NX9+JIv^ixuJ)BuGd65jZO&>r4#ScYx?{Djw4ak! ze52jhZ}~UPg&EPV_64$YIGC1aKUr9Bmqw5_<_b`&-#i zQFO1I$hMxnv!W$SqZ7a`t}S0~ztF-v90La~)$$hG(RI7HywP^*b`;~zV#NZL&CqiL zP$jsG3}*(Ft2KLT!UzR4L!Pozm?sn6JYoGg&|Rz$2FV;7W4&^d$m6;Jh0EMQvVI|h z+aNpS51Nl~!Wy0S8*AufP3v6klHiu6Oh$DKY!G;(*6hP1r~UUF56C_La7(sUi~ok; zWA8@n(M)G`!PWZ%q#n=Wg8#1xUjLi-M^o>=RD$2PB8I6VhGuMjb^~TcvtLYr8yM~e zwEP`2CBW*ilK}r+CJFEo0i2KkJCQ>Z9r3j~qSl-lv)>}T4~{0KzQu_}atLBmIqnd< zH4bS}texU7Uj+ZrO#Zi%+b;>X=n5V763Gx_BKtz)0y{cVFc%~B*b`;5Mf^5vtZ;3Z zu2#xJhWEJ3bVaeZE6mwixv;c5>%gGEEe`iw?9VbEO4n|b%j=qm)@PB0gw5Sz_Tw^~ zbwZuux7MoM>@1Rn5}og?z!yC@$DT{${W`H6n8^j^UN$@^v^?`;bK({-ACQhER6 z@(zj0Xq3qNRwD1MLA_mhUk&ONsOjkixC4+yx}lnOu#No$Yx@0yw%#5#xra|J(1P1b z*~i@D*UQ(ktVVXwmj+$5z8pGpNp~cZT3x1OuVE7Xu%BP3POC!+!49#wj7hcJoR%Ay z9l)dg7_-BgA8u#Z96JL}l;a2dH>?fN{Y$8i&d?1yRn&m-=t*{KL}b3OM`%un5-=0jr=I;y&&a(rGew+% zm9TA|G_=EM)VA2vWAf|^D~PEP!uw4H*wLP}HQc@z25RyfmepoNRhCAf@Jc-Uc{$jV zu&vQtF2H5p*?Dv4*Jcp{BhCr)1;5v@T7JQ6U{t;QV7AWqe~tLsn-mk^x9d08{DLa? zl<6=1{{yi4NT=cx_~9aP9qU#FgV*$>Ll?uDR+uw$<>yHIjWuhDhdq?aj^v8LX52qTKK)9<({K0Z+*eD{rM#VF9*MyB#c)Wj z0L9yRyl^GRCv`j4gL%g9QG78Jc z0<@If^&H7;qMLL3M-4yc^Fyyc&PO(ry;0I;tycq5MJ7S_7p`z8kB8@$G^+$M*{EEM zPTMz|e}}Q_LfkE7XIf$TKPNSWcU(>#~K<|ck-XVGir@^(JJYzO(wb? zKTxax(>=^xTszGB5VYPSm|gxFvNhB_>{)RqYNT@bz2Uo#g#3e-Ev-UG_I zRSu-}=03a#1I;m8pAI^oc>v=;rStNI&i9}5So{xyWJtvAR0N)pGCW6Z^sCmt;SYQr zEqXrEv$0)#> zRSoSlf1qE-UwHG0G|bM$io8Y9q)vC|?=ab33`hY2k+&gMix+EBt1IDm7SIqopX^%} z(QkEr=lV&=S3k7cUkB9=mrc;$=m7@uWDeVzj>gf%cc3ZqeP++J@0b|%=QWU}^)$cz zb>Ooq`lU0;N&D2W&`NXx$DjwA!CRf-&&8gcjbTGg^7Pj_s zvtQWWBn#SIj0lek=(&^nPhQ2V?0}Y4_!KWjoxYL@`5bC*J0;P98Z9fop=uere4`4)>?}uLF&6owP2P3 zkF!7)X?gr*vif#cWDsbD7%h?iSk@9X!h`7Sn`LG);_;nMy2rSCX=MUJu&6;&!I?N; zk3wbN5&%Ovj~_8{r(qAos~lpn-%reL5vV9O1If?^L}Q6`ild`8w+(aCjb`*p&S!M(iM30vogW>Zgb5ll+7P^FbYK6?#SFt z+WZmbLH`66Tr+Gt>TDX}KPGv65wg5ry}_K#SMI{w=4|E9^2{mnFaWT34*M#p$WCSW z&+r#TVldz@h9O2`kaSRglwej^V2l85WE^;;65lSUwqf?!5PFx{Z5#hww!1hG4#p7t zzH-X!8>?F(M=JKU5pbFj{rwC4TDEk6RXNMEtAKqbc2m4-bDObB*(WPB@K2y_akW3a z_7tL;_8oZmalkoB6pK!@+cwg@a@m@b!uC#wgN9cuVQ&DL*_A{$F2xl1zv;(gv^9u+ ztl~wjz%suM`O8~TraJ83L-0RciGTBk76aZ?ruH8GT*>Ku=^c?OhN-{0r0hX00I44^%UCxq<7Yt}$_Q)?sRaK&iH2JiVm?`gs-%1^_H4?-7x79nJYc0iTTDVW;G zrm1nNeK*vC;C^UWZcA5}Q`^6{uF}5hicyzmN^;b!?_>e!s;Z**X7yQB9*p_<)HiV@xr6%Fq5UC-!UTBh`q_d0arH}JwIeo(D_PK}ljM2M%n0{=c+ z&{(r>3&*JTksnlTpN)MpBoMsgLtg@$nHFZV0WxT-8y~;A)xXTvxn7&!Tc^DdZdniF zdpeZoG`T-V1l!a4nkPt-tJd#a<)4abxL%$sRIMIa2aAo(Z!>4fKI`4U0)-u}N3NmD zRg%mFSfUk^MYA#=*)#J9Wj;TZIaVW_49Zb)6sDP3^|?{Ik8)2<<^D;H@{Q}6Z;bLy zN#)xYrx`3AQote63q`Fkj(9yGYkRzty%S@?r_y(I-5=6uU%cY zb=CDY&HK{C*3-PzLG#LcYu!3(sD?^@@$58U*(dkS126LTL}!Iz00gYwo35bKl1X z?1-6vu^gucREg{-5s@?9fkY1`IN%&nsY zg9FihF)HmGJYtVJ|M{~{&;q5pWHnx~3OSXY7c-d^ZLnXW?e3J^&Myoa z?5wM0u40|Km`$k%uqhoY4K|JEQ2uY?k_T^~jVSey+*LkSN6>%feUkQex&UT)j18T* z%yA!bB~PRK^8s52$wU`dS7KGL{|xcX69xA?KChcub7mCOPK-*%Qh1)W=iy34%?Qj| zbF_bwu@d3yRhS7YP&?RQM}>UAbhGC+IL-Fho;JrOIXeaP31`Dtb^}e=Cx$!xcItQk zIt@vFP3#ySSNK&u4T?Ww`=JcoA63XX(8HGfGrP<^{;&eebuUk^XFDD^e!1D2+o45F zEtiCI?PHbZGvw5@NRRevff-~>3Z(5|t8WJ~^RbAuOfl4v=OF1P<#bGT~d(aQbyPm0bRUn_bQ@!a=6=Om*Zj6!WCYYhtUfet@dVoFU$?L2xE+{ ztw>@c2_`a`LdeF`!$`(di;|2+gD1ki;%)|_R5~+)%GHh_$Yks`Wz^fm1IzwXD zd~uF|vMZ!NsrmS|L7ZW77^gSoOFwHha+m&a=k3_QdCT@GDH=bzoLr;dz+};9d+Z(J zyMKp2n79(z$Q=M+yDqWBC!AdW@Ay6=Y~_1Y+u?-Y10LCcCec_uuNAj8vwIK&%)VTV z1UtnEUIHYKPuVDRv@E85&|#l$%KwlK+qH2VDr9V1!vDK5qV)vLV2lJJ!1`0-PZGHh zBS0}#w2$C0DdaAaM=Pvl=r(>HOPael*VHjxNjVo~4z@802;D)3#O$^NumOV@P5nO1b>=UI85xH5a3)}+9yO0K zeba(af-T0$0u5rX#D3IxgAVZ6W*sV5PF&h;zweeZm;JS+~CHt7I%A5PUL89 zJZGWTQ|-JY^Ob_Z%J6ws)Zf^O4O1pmY;{}}@QavC5|&ynC&`*h1PfR?(UqRaP~l<;wg~0LTd?fP+?2q6*UkTcWU>N58zR-6sgk zhHSRg*F^SYrYv{uo8p>xJ)V22iX3A<2veJvolmEo<1(n5?@jBlos<3L*&VtruT6C$ zh?r1UdO_Fau3eYUCUP)r_zWMM3%!BzWdUgwu6>epjD|J1BbFS|`md%*40P}{B(P5nyUBmlmGMgmL4&?{hQ9CGaVAp}zD}V=k z+VU(so(F~7fio3g98Ua?-NYZ^wu_&ECdf7#nlL;EXu?|UwbLE(T*z?UmVPj-Yv{YZ z82$NGZV(@|uD#dJZ_8@J3_V@8KE9P*H#dld=h~C1u59;L;3D)7PB!Z!0gxNo+v&o2 z+V{CxmBRS56PK_SBV)1uS7l5HL}aKFS&qi)verbT1& ztRg~a6jlV((xKfC8l(rZ9K(BQ9$UG%y~K0#bSUg-f*CP@e~36Cny09EMs8m> zfB$JBV|8J!*2ep5%L(K#C!#o`AQHyZCvqTSBK*ek5)Ynsx<|WL zR>NAGAh)gAe!(h=z#^q7t}X3Tm|HT4>H>e*ei~gLd?m>b1KhwkhJBKtppbDF#|&E0OjZRvmSJjSTvz z`i1lq(~G@2yRh3-w_M{j?g+V8^R~w=gad2w?28NiDtlj~FncYR6bVMiD%FQqP`r*MdGiUY?)T-Y)nDF`0N$t!}8X z|J3#6?pj^)kOe%wXAXP>Gn9-_wXMVq0t{(7P}8*g@E`qvZ=0BZid2P3*{%4hZWK}x zrp1b-U=%RgFUlDf$*Sz4U=6+6*=|VP-8yg@5k;p3u}Fx;bIXmP_}3<1jPPbA=E=qkeY zhQBvvc@Rgl?=aK{e{U@EKBFxsV+i+|p_i!G-xFo~M?`}DQSrVv9liU9hD7iAJ$-)q z4+`SG$vEvjUsmvaA;mWxvb~QW+&?m?eL)a9wa*X#q4C<2p}PBu^u6i$=bH=}&v7!d z&$@8RoW3K@AHV4^;+qJH{-Xo0XM`Vj0kJuQAQ@h^m+0UGtWBgQqtm8E!sTIFFmUdY zQ_$rl6nR>RX+n{wl_WxL+8rT;>HqkJ1ZtkV$-vw+IIS2Vz*C{FVLquyKT zMfi2CqpE;(GQz48J2x$?I(ZUd)rq8?7Dzqw1;kFyjFHacZjsQbsF#S^bV*u7ZAIkH zjG#@50$wMA5bLhjxY#`C5mY--)ScFq7AsrH8PTy*WqXZ|&C?zsyh+Ol*R2|61P8Rb ze&xP(=X4MEPTIKmh3BV5e@`O=;&7)$ zXlBfM4dPA}saXA7_YBX>x1>U?Gpb#Yr3uO174sQ_cCG8{9chu=X-QfuslKb0XvQNV zVRt>s>{5bQ&&dcjPt$|g;hC|;Db*Eynt@7tG4IL<7EW)^ykJ@oQe6EyXU>%QZhWaD zJ*Ga-QvA(5C;WFZk~s4-E#|)p#*L&GS^~ZLWI>v`uPSKy^MTGf+GU>EZmzojSZgvGkL1%Lz%| z6}_FC6iZWR@?MWY(0hX^}cm-m(v zLCn*>o(p2mAPJnJNQO!N=649TEoU;IyiSLJjQQy|cqim`J4R{QlfeDH7;4G-Mk*Ov z;p;NXX4~vSV(m6&HK89}QYoD7#S$11igy8j9>ypSQqs1RE5z^Jaz_9VY(&RfLPQL- z?(CXQGh}C0N5ZfMNEJGX{h}ZaQrFIi3-$XVe)eU zo#EZ%GTYCM82e6n!fyE%$_1^wNvNd>4!Dadh_%|;!BA6gL!OpSvQ|d%Pr`xm6PF-m zs%=fyW0$tNZGLg@Tz}viA~;{{mz`p>JDgR}pWsilvlJldysB(Fpa{d*Ul#;5oa#cK zd=aY2>ZC_->R_JPh+0`B)%drSfprP!$Z)Je!oy7qHvjW2k=;Ez0P9MPIk>lw>$GxZ z5ORG~Vl&6?JwvWSxxVlT1<#eau@&XDD4@O9Je4bXvztXPv z?kc!PaCro|2L)Dg6&%==;kJdr^|=mNz6yI~8zaY_47U@pRgiY=xTpn8d@3eb1B48V ziH+yuK9^-4t3$1k{YX*nE&-pPZ7zoj=&UEv0|V_&+cMC>{#+&;{909S?=A<z;vMu)7KpGrqutNK<_pDb%PDC^OP>-UBtiw?MShoUnHbpP5%~M zo#(FLF#uE@yc%9WV3s<}2YC;Q+aSj8vq0MB0 zGgts%EyR!r{58a7!EW<)8Jm!M^#NeqGz|Veu6@gRwSrP@aDTz_Ud9Cw#LORzj0;UE zZ~Qjm-iRx}$TY=X3DHn4b~()Bk&)u_@=Pd@0Y+^4;)!f5^-XTm^GC(R@V$PED*I6} z(G9pN#5Qs*PMc!QieGLa6PLDN+*^WK3tT6?7X;&_rp-Nj)t2%HR0Hjya){9Wx|oEq zisKNE?7?tXz~@DBY(5~$UYyN)0nK6KuLN34&>gyOYOKj?vr8&*7&s=t#3_u7rn?l- zQl%p!?MHL{4shM0?J^(Cb2TX+&Si6TwLbydh>JKtfsg#STq>B_spxj?9F|!a`z>!@ z#rDKTbLaqJ6UO3GS@cSxri5AULV0{PUl#B}8cPD6@xd51`-wj&j*MSo4vn!l-Ysl&8@p|t zi<8WLz1y^+Ee8Z-nxu7E2}|Wzvwxo}eD@WEA>istsEtccz(g?(axL~Bz#8R_tN>TQ zkrkdf1TPyarz^Nr2~+UQVOl(5gxN)5PWWah`U~ye3hY+cg%bv>y%U1u5n6gtG_nu&=(Ul(?dy>wtQKBy!$LEa0r`kL z3dCuzUs%Hzy1NlMcB$?@*BzfhV;f}#tXs&EP4n3gBZn*E1RwVomVf-pFsNY~{e{lV z*;vWA3DM1`Haq;3Ev&J7u@P-oMU$>(s1F(7j$k0_z>A`*Ki~l=#<5JCSzboH-|FCv z4t?*mU|9U&!RYdE??#HYBP9SE?c>@3u?^Z?UOh2YCum7rrzLLX$@|zuG@GG zdoj9h=hT2)?z){_V{?MrT1&kb)PTTpPu=aT8o-fVna{1k3+`^8!GgM!7vc}4TW%1M zchKF0rh6a}QebTb+*J>+m02B-2z_&@BK-%;(#P6)eVwSN1swfNCXaoiZRLu_d!ySR zQ6vtx8`Lx0aiSC5w!`y8SViI&cd>plFpN6<`xtkui$762=L!9)%{*@B*e+l$%Y&mp zZenodT5~A5Lj*rk2q^GPLU6k?KtLm`BD$7#60yA#A;R24M=vo?Ml`e?E)HHYR1E~= z0`7kuQGJx}CEz}0XSY{M)b_oZMGp0TQg*0g4R|U%pw;b~VP6YTcI}dq<@VDox~l_* z@q9SixgimM|JUj9_rJ-CtA~&;=1I17k_ajUEDV;0cUnk&7xiyC!DPor*_;{`ezdhh zQ8GZI+(i^XBJYoM(6XSWI{@rC8lT%bUCa746^zl}oQeUxe7FVc%~~A^awwsjvZ@vl z+`LMm_lbyJwXs@JOlJlG(I2w;c+ilcFVbT0ju!bN0W-FM+tqwJYkQ{hMQiO}`p0qj zm9=^Jy))ya^HH{jAonnmizF}zqD&}aSQg!}5Hk&*3)+&T{?q1EW5oCsg_B}<#` z-o&GgZ244)R1EIdAzF@M(tE-`h>usqv7L8SZ_SqbQ4?a&7Ynuwdg%tA9fn!#OGNsE;{1KPMV zT+>y4O;G-|ik8=5ZbU@#JE-VgH{&})ap4w!9!xZ*LKDlY$ZE#-QdM?7R4bXbH+spc z;bBQ154X_6;yxa3Rbwye^^t`9yWI#vw2{4Rm0{n>KxE8+mLn~Nt0e)C7ngD&RQ?=c ztXo9hpmilv9~+D0+K(3?gY>{CZe^FU8$Z{6w4j%5oLkxJh@bfi>}qhdxp|#qNKnJj z#w?rD2(lnQT0m%oXyhmCI1i-IcOeeEtZy%5_1Cch$R9Qyw_C8j%L7HKDE0-4ae3STNuY#V66b>vYg?9R1I`Gb6;lFZCwnZ@yLF zxx-ihUKM5t$K^K%Bp{vVJ}H--M75)|-2s*yiGx%|bvEeB*D~u#a6K1M$t7?g*Wi(P*9E z#Y^$ayC8nF{>8Zlx0(34ul8_avIo0cP}o~|xO5H1CJVQV%I z1aZNW4_AC%0dqhFYK8pe<}JFq7Hxt{g@*EfdWJCDCq?zcp=c6pk<{tPKe30|bui)f z75FpJf8T@E3Ku+CE!^KqX90pNJR6X+b1dxWPfzrPC}o#(;gJJ^T@Qg zod+YrRYHrj-o<8+Buf8BoniD3Fm`@7x<~;#xnLvg%G@Wq(bQr_Vk`6vti|TCKCZuo zGRQMsU5`L<1-m9NLBsOlu1{;@2b`InAQ;9Zm}`yp^}c}Gui&dl;5w>_Z78B3WPt0xS27gPW1^pWX$IdS8x5F`o)gKWirB>Epzi=9KqR5VI9F7Nl z#=Y08OI-kB7lrnIck<5*iAJrQPE-=iDK*{(8JXGey=|-0zdwX!PN!*XwBQ@qXrU`Y zB7M$EVP-+Oiemy*{91yuctbl9#b+f7b5fhdXI*EapowLQUvDpDH;=Grwtovpp7;%? z+OOxDqr);B`Gh%AkpJz>3mDr!un#a3pd98+dK*F_Fv!%{t_hN}) zn)m5V(j#0LF&-{scCR4ctQ`Av38LZ?P9*P8#OK>M0z%w{GqC9TwosL>_@Fk-)4*V$ zyxA>DCa2@ih`tT$SwajZ@Lb2_W)od>xILcjGJ7z%ZWYn2LPybg0NQo~1+zJxBdPQ+ zK7-&fkz};LVzpr=te%fO>*XN4_L2t0re^2(uDPr~?jokJd<&x^xv`~cT7T`<`Q?1` zwwCA*ot=azNwn%MHYcap9v`&?X;Qly9&m>WJTFt(Mc6rVIoOAunYC5vvfZ1Kwgd+IKd>uE18X@GP{o8yb@@1ZYX|)eyW7Po%H+rmJC?dbt z8E8(=Nk*Y(agd$;m!tli$=~R+|A(F@8)Y&tMiGOEl$#WT zK4wR<>#;C4Q{6ME(vPY%pFro)R(2#NYJFVk%KE#m=GQ3=?QmzApDO&v@?>O`SultH7t z6@W7bt*(Yi5r+rsKI-&-T26Nq*mWwVHpHa?xZBXdSnCCk#lZIl>`(`PEMbsgs5)*E zi`6*KACMuYWUH_340P+hfvSgiPbL;a_U>MoI)#BSQ`u~{a(0c`hz%BS}A_P(j*3(TW9~iz@OI1;h~rJBfK0$G<=l6 ze$(x{3w$Ytb^Vy-j58wQ#RT!DOf<5cIciH{WnNN75Qucc7JU0|?bC`g-kGNu;YTA_ z1e0EtVeq;&=vf0ap8Yt~MtS!d$ADxQ+c2!jU}^CM@yWX$aDX0(JAp2Qf(s zdpg=Hd58=YvziF;ola-07f$$O#4!&46j#5lA|8Y^U+xiuBl*7zAqpHo7WK!lqi7Rr zwhm}|%x*8qL+PA{19`&@mOJ6$J%k6YSfSr@zU}@<7wlKSu;50V;&^Qa>xC{-BP4H+ zV&5Vh_OKo&l{5s0$*}ULP9ynmom4W|c0Z|v-BU2bzbeiR>$Gi=rk1qXCvEFTZSNmQ zYJTMBI7V^u!n-kWAF9p03hq*%gD`qKB?CpdmqRiRBh0TbgXN$ zB#4i5^w%iCTh1a%e%U$f`gxeP^Re5 zoF%MNriW~22Ic9r6zqmQPfOuI5|;W#GpXnKD1TxG{owOaaN-?Kj8C|cy@9PcVq4G< zUBRZLMZszHT2ihl(~+|swn|sbo(}bwLnJ)oYww?om3%2j?c`!7DG|w(a6ij^c}*6? zxN{DMFKT+b7~h_~c?%@ep>RU_rC1iGTa`dStXej*x`f*j3FLUaSRCVyWP;Iou&;tr zaWyQ3`iph^?<$^S16V4jAB8`fkE+!Z)#*VIXPZcYY`r}<$o-D=-MzoSdmZi*C_5>A6GXH&$ z#vNV#^cHlrTC+@14)!3TB$oO5xS!t{MEyM}%7)bE$r0}4!98(P1^{bM!NS~=7xjcK znk!`~8O$s0Dz34ui5GB<8QQ&o4p{0!)?qEVwHOk7i#+tyvQi<7hN`S>pXcXq?BrlN zb1;15reZPL_C!@Tsj3aBswB3*JI`NB_TRYdXc6}MVjV^Ck*k;+(NVI%l6B^x4NQLP|uG15{We3i1Ss1SL zFdCszqeO6K^80h#Ijk#yB&ip`Ce|SUada@7kBBc?hevP@XT#XFTej##ac1Acb96jF zbMju=YMsS|Z10&Po6&1qvXpTwoxOVwT-dhgR>!R0HHXf$t=w8-`i3Qi(;jo)0Ak8( zb=@sjE6>?cq>iCsQNV67;hj!UyjSF9{bfmqAuilb))t{<7{T{0!fLU+e}dMBxpe#X zMe6>W6R8;ZN_`Ic%~8FlZ7)Cd&Q74Qk6a|1&|3L4(YJ^fK@6!=`tBSpt98ZbhxztP z1KUb0s>H@7dR`P3MEr4gKs}95{NxE01SW*<|4e(!14HUmFh_q8AC}EP>VZ446!!&` z@SU^Bh+D9iSS2C$fVM$wE4D zTnpcwWpG(7Yq%9AU?t}>^J9v$gtjbm4TqX3Du`NnqT^r7wvhIUBq(W$&91l2M^M*N z)akE7$eQMk{GF;z>0NicjOH{ikl{s+7X-3qMGsY{V6U--M34gV;VIs)XOEE1vUdxA z(OC9IGdM!jxi=%YTu!VB+g z9>6D7w|m-O=bdUUQ0P%2>E<|V8lnT<-i`BFR#)mY6K#8Z-C*iMtkpR+V-n}o==Qd9 zjzQy?8ZN}z-$drb1&@)Mj{cQSI&7suIMY@JUdB#|Cbqgvb^O!vk(@s;2_@qcbyh1x zCC6@^zuq5M*UaSExKJc_weUEPsy8|p${n?sdplFXc!nu5)R;22D z$K9_~MJKAF-J^CGP3UZXKGBF4SIOO}0nf4v`M1NRs`AUENR@{r`yxq?F$Q_Bpn0J zJx791!ze20Pa!X;_LBu#f@R%}M;3j4&I_?5(h6a@I_VXOD-rM!2j9JBKmNu;j=T!Au|$s*K8SID?_{?4v$Cj}mBZ zWk&qQ5c^TsEo{^-l+lx@BV1{BIG3K8wv8DI7kI2f)_MDISo2_v%#;O~vBx#A*fFC5 zSi-GHtO@MErcdrb1X;)cHAGsfZH~Z`oZmsGT;=-uKv*iG@;q(K{o`ihC?2M%`{q&L z72UQ)j+DS9VUDKks1bP&(_EGdJ;`^-FuSK>H#QKEqG6T4ZoS!mI|Fo&D70tN+$pih zQTDQJ{leBrq5WpM-_q2ojl1nlL}Dp{Q|SK&n@i~4jYOvfI_sjbzOR{dB!Ng8NTe3I zB(8T)K5|dEy-r6Z*pGFKyY?sk`EbNC?F#=?NhcFT$&>=P`2`LfK^BoJ2q3gD*Z;EF zMN+>SbPg)W@0eA@o%$ULvb)PQ2jrsXu3$V+9`R8GB=4sRLm$pzxvox%N50rprES=LI(dg+4Y z_S@jel2&0&@sb_Ky#8*!P9=9qYAJ!k)tEWa^Oz# z$ejMci90!d{UTwZSK+%lt6mP@UNdgA+xji*3H3sAK3B*%=P2%A89R`aPfDfyFVzV> zrkdB{UXkeB7zK1x(Zc9@NU$KhiRSG}EQkhs%%(F9 zBU2M0XE#~XZe)58=Siu2Rku2WFx|PK3{$2<28@ew921)Ce2LE2diAkLhZd8LG1CB9{@G(ZseJ>WW{Q3~_LZNPjG#>^2Y64JLWCY5|VvLhUW zpmP9PkAN(AdkML_OW?X5s^b|zfXQmqK!5Ec)S}-o2?Yj`#p%t`ou43%0P^9^96N@Z zAnh7SNBeY$IpBPHyWq_x#ysb4%J~K*J1^Y5P5?t*iNSk-Rrv)Q&HZ7*c&{|~k+zHE z5ykv1lcf>s3P+X|_+PsGr|2k|&W&;g7UuD7wWVDzrh0mZ7<(B12`z_y`fnpU4f_G; zr%x)dRP^kppAqX%uqL7$?$PUG!n$!@#W-vR&TN3Wrv?V~XTAs*_-uml=4CuoK!C1? zH20v)=PLYMSK%JdRX*)`o;0+xqNVzC6vzuz#@t)z49p~)<5)7rWyYON zCtOkjmOhIH(i-#=lViLFcxIz@_wbm% z2TR$6I$Y5smd=&G1}FZpG5e6jOA#h+C+D$z*qt6LONm^Th({~I>R>Y<;bvr4q@A9Z zf)t%Y(4XM+A`sn)(~EXRNcgvN)n9@j!4(WHae5I_#QVZsflJphmQ(oAj9KfgPEQg{ zRmp8?$hQt_J49y;9U4X{aI~#h&+d$&qn#+57JLsaaMNcDu>gsFgF~v2u83vV!Zb;q zF;v7}vD6tu97vdU#t_N}t_el2N$e?MJ`16%GdJqOd7XYc3m} z-yuqQB5g%7?QP~jB5kG8zLe!q$AsO=?;3r+IoQQTdR5S|Q@BN{+Y|Q%TSi!Zh&3Y< zRqpqjrZ%_}b^5SHb)BK}pIM|hWR3AER9KfAovtWpnIM?%EVvb-T~JBPiT$*X$Q>!W zbL;~V{*kfHjT~+xCO9o>&W&M0=JZ$C^F84f#2OE-*~z?M<72Ij6KdO^)(XCb(;1cL z5ecWvrulhHn@*(us67hPxdSsbMvg;RuGLJ$lHQ8*09VyN z-B(sKiMTpBY!g>Rp3k^qFOtbqBpBxt;H;FNM_-4Hp?VH10OGQND$QXrb72hV*!Ggq zJ`Cq+Jz)J8sBIh1o|)J`M%{t> zz#v?~hAM2r2om3gkODcxlZdfx8uYz6yn$1|?9h5R@MV8*6dp+x2}yFYIJLi@zx}FJPz{l~8og8Ma8i$*Wa3d1&-F{} z90KBO<4>#O`#H1SwRI`AvrB;meq%obWmLbiZN+}?K^QlcSaX{rF%ZPp^~1A?nhz)) z6Eg`LYktD(}4_2#NYHIKoHVT0q#e@0njtt znYWG^fRideS1gdBkoXY%DBLsk*UJfu;t8u9E1Kls5CVDw=`R=cu1(fwZ$ zS#A-l)ha1ik`$L~9Pk?NzqNm^*}uO(3(V>MVgCYV4}L5L6*0n1q)(K-9mKAc3yBkuF^#MjV^Cx|Hcq!Z8; z1>w{(#D*Gavms1ZLL|0&9Zm_bXa4|XVxGU_dI|e1d)Q9eC8hC^IH$4k7Bn|Kw5LC? zv+3c{CCyF$4|VSWUsaX0|C_Tf*Mo>9_a>m@yrZCw;+UY=2XL%7AdcMxR79zYppZaF zLkQT*C7@zUKtx4Ju$KfKbu0w2jE+eVy8+8s6R?i>|32%So14%qGw*-i--nQ#v-duI zmsOtitcN@88`&FPT$aJ5h&V^4tzqDZkjVbCf|Oz`lKDkb-y*T$-D_*6Jg@8PCG@4M zI8GSEpU_LR;)!*P2g}eZ&NP7bNvMr$3i67GfAu5sg|vkdWU5nT*$-j}hDrJIGZ840 zJ1R2Qe^#b?1iw!lx*N+=O;L8@Ei9%*AWF~J_DaW zePjMT?T!g9c&55&ja=XVgN-V;jGk1+j4^ooPX0V1gdUOwB#1CGx)Uu*Xz?csQa+y=VCA6<5q9AM-s;eGN5I0{7$I>_spLH;o0 zb?FJn<=Kw|EsU)N)1)yjaQwMhZU-U`fL<}vLE(I2Na4&;Dj}r>CF^u|5At}zm2&zX zvHS*0T(L%WI8s8;<_ayMOjJDRF?J8%xmbZs@K7wv_N_G@0q zr@oAM>$8nvSK;yqm%vz(uM6LS5su-s`xwG8Avi9#R6bwfv11D%`71njIa>b42y6hU z-c7|L+$y>8AOlbWwGYAsYEd_Y1r!{EqAkt~vavl++XsWT&#UdjVKz3!6RrfgBs3wu z3c&xT#9L4{=+vu?N&QB300bgYbsx07KbOwhRZ(DMuhg-laA^Vb8%>5L!v~Ym9Bc3G z9OTB!E66I#`?fF5oxe<+r%lS<%?!lrSXOvx2zRYN?6&*KtO8Ij;i&~kLC-F(Al&Ne zCWTwu232kUpnVeAF`;b(W@w5ov?5@{Y+ul>dRvN!7%!@D^CB3-_DE-&4>aHS3iG6B z%rqFERvuaD7kZ2QfijOO=)}EJQV&Hassl;Du!|xr^kbwZ(m=DbXzdquN6Y$%Wi?-h zLCEnB`?=)!LJo->f4_hn{~~W=+L~rhIWk}4wBKEe&>FA;EG7O2i{U`9G5)QMarzC5089Q9}VMJ$Xy z@9Lw;U~2>^56#ugz^hf{Zj^vhXlF`tJM`?0}yEh63t#7)vSxduzvyZg4;)t5e+Ma{Gqv%&s4upe&=gqQV}{}Yy@1Ax_tzP1kn{aup* zCNxBAMDE%>-QB6av=SvY^h_=+RA?b}vb;fvgmV6W!~6x*3xmpkWjbALlPUBd3PS%i zBcLq-$L43+_46CT2QY04@~ftut`I{AZ$NB`eoMXSAK}y915cGddEjLBvm(3W5{H9` z?PGlRe}b$vNj|wrF3=56`3suoXWX#i1w$r|9zH31!uWGG`QA5A8T%7l zubX9I-8_M7D~I7G?`$(9tUp2DYFExad-T|0qsNY%kZqKaB0L>FX57$W#^48`-|^%B zJs#FJ%!wQDt|kYDU26m8RZ)zt&l3uB<9Y;&$@}~ToT=eZP{Em zWK(S=Nrag7Cz(}wnzl@lZC(}Ro4&|JHp*YRasF0XZ@}NG^R**t zZEdtsqE>1*AZn$o9Z{>!FscnvEBhPpu)<5(=IuZu*=7%>&rNfh!miqeoRvLmdj9ad z@kusT@vn{>mOo~=^(5F*H_DAltMw^dhjNYrJBUuxO3vE;bg?ZqPSx7xiV%mkcA9on zsyyZRcI2s?x(z=o7i~v=nj|-6lx>1ybtCk*?P*Zgqbf~NvhqpV+%f#0t<#b{Kh&!0 z5sJ1WRBg*v8gisUqPhVm=_XiJ*BkE}CrsTq32IwH+zm-oH$_?te`_lrD&1j|RHto; zf%!BWqm6CP!a8#J*x}vcQY5Qzy{2x8v3z!tid7YDh*h;ae)RdH^F~h` zKEb{K<710RCx}(I`|O~ASal`Ds{W{IbZ9)>Ip7|KWXL9$W4lOXITn$j*mtP=Tad^W zMLH3jLn@w@swHBvz?zFD7y^hO{x|1=^mTW+#nF5vtLbPnt|$mZ8VT7M-t7>ied(93 z)7=R}R13j{3eOx}=1=D`Sd*3zKTw7zs#hns)y@&0$k2fQXa98JUXKh;06!lmz#+aB z;Yj_XBbuUq_H-q+AP?M0VB0;sZbxNC^P+>?^NnFO2S#FyOHzvz{U790+$aqZESP2v2Tew`YLnKD;6CD8v=sJ=- zxsSJ~hp1vW{xKC@t0K7hC>jRXb_8AzVmTK<<5RD6Oy~Hst!$RcKD9D8OG$}7<8=vq zs*q$cj7aYN0T97h0Lm(Y_uM;D(wjFJ_cxdk~KBkt=YcU(p^)f`v;{u?%wb? zZ6A^<-Z!9}o{kNRY*%MEI@`V^l>T>KnSc0D953nb~SHU$MCYRu0 zflIJ2EFBK5Ix`S9vy!ob!~Y1cFB=X{9%J7u7k!;lFmVpv-IHmuc|bS13}+s;T4f#6 z?8Kdd@RPeORfiKSS*MY=)Z8Qj9vHSAA99)S!uDnG9P|2Cbi{z6pkSDPzzXMk695Q{ ziY_ZU^ASwc`JnBdF_w!8c`@E4U_}(zy4y}(fuU!v?I;VDq492@^Nht>4TMK$nB_H6 zFaRrI1{iIxNw$vw37ANRA~J&A+tLE@e1tl@(}ymNFdSG@x#pOxF8> zJQx84OcZSH5=tyr5(~}w>dIsyw(NH!%ak#bs7h}Acjh}kTzC`~^mhoeo-5*+rSz{8 zV;Tssmf*MhoXBj3nmMCR76wIA73ZBBA!DBzm~BaZszB7@kLy)JKtkH$XivOke~7a( zW8f^HzduKv07hKwX((0blMw$lO+Wt9GbLJmKicho43yg$b=cvngAIl$bO(`-iNpjqCgl*)owFh#XyE12g%YDnAgXBy*3k>RNhQka&L=17J4`?yH^xcoKqLcN5hC|A zdfmGCxm9jg@31tmXuz||Ax++8+B5l-K2k;C25Wfx6iX8ngi z^c8IL#$U7btwKOY=%d1^nl2CO6;`-Ig(Fj>)7*#R5);kV^+dxl?P?b^2vHHX z|FQOSxC&8>RDYt~fe`rZ!V+&KpR457wAiadaAsM_8kLaE)m>q`Yqbk57U|V^%)Zh- zeV8|TwqchEHFg(U)9>PpXWZ-KAFbK-7=yooRR3IYI9cbNi!SNS791~-jE z-XIG7m8r7_0}^^N&I)ERBz%1+s*chu7MHcfdKZeIMd!%MfK3M=YnoqCFXmkV8bydnUXJ3n{4!!j zn0y%HpYZ9RpRQY`zz~i2aj|_tSHaw6&ZBGfn6DR+-oT$(>(}AZ_Lna6Zuju=_rw+W zkfs7lj@zQRJL835#v|lfs1F!rsoYzHoe~@ZdFH2CYUv*Llc-HIXi*zB*bkFzuoYY3 z@ic%R6rWd4oL1&qIs#%yOq>Sw*cTlwobIa$ikrB6Yd$HV)^61w&=Kn^h<2CZwMfHpywHImBp>2^Id0* zJ(4u=5iW!MZaEEj*GbBvFVsMJDWHB)7|dKzKn@YR2q3*|@0iYbxb{fnn2U=E-awUN zuzjmztSBca_+AC>yd1zeO9gH`Ky$>pqSlK{jZmNKS5f+}RB4$?2LYGJaA)SY?i{KZ zb2nIFd3&&Tp!QDH-rjKBH}WPJL_id0@NE-;@C0cGmt$c7Jr6$>=YK18{&_k-Y>@6y z4%Yx|cPzo|O#SrG2LD6FC>%srJt@?R8DKj9%*~YhtE1k;pt;V?S`Jcx$zqj!p}>~_ zrs@>s0`hWOccpHNFBXzimD>U2#tqLj6O5xBHCxyw#LSD*xO0{zAz`O7#xG`Hq9s(P zTf0Q+lwazUxLx9U16QQV*Vr3qF3oY}I#c^MK>k-vv#TI0^m4&Cs)ab0@zCPaX1+N$ zPL2l*0-A}Qm<$xqJt2?nnAjh<%I6RW1t0&q4$0@4-B6tmvlq?po2G@ohBKM$J!HP!sd zHqFNbAK=l{_3Nyb`H_!cArr)5@RA-#wRm1F2I(8Q>-ip^=u37d_&DgbccvOPs3DYP zIqpZ=aFQESb*rpyDs>R=s_yz!-G^4Ut|Z6J2i%hU-&1!fb#)w=NL7BW%0cSD zVl~S}r_kHKJk?;W8mMPomMZ&7WkV>NmMUAPGLc+Rd{L_SM->zBStD)lHYw8*HZBX6 zro~>M#e*&qoBd}`m;o>q>vs+w3To+Q%D_))}%#lm7NP=v1 zj`tkX`)M)a1}Hmu2Wf(YEat+%VgwQ&(f*uNdue}{nqxK3AArbtr(xV1VHRQwTv{U> zvCOsbgCaRU>6qG$_*N)d*?oWo+*vYBd54-i^(T0anV8qMTG@5=1V&Lq&Sm64%mh&p z-m#{$BNB+^q!4%@Od-(d*D}ow5*Q!}JrBsD5%a{fv&745U_5r$%wQ5qwPhgI3W}D; zVhAt7BBVI%V-T$m)UuoF)=t(UI$&O~HWp&kx;$Dl6Ogt(!@l~uNR4)zOjY}EN8gnw z`1xW)F1DW1RO8hXLy4UY9?SJ&Ct(FOd=>qPGkJO3ShKQee}}Db6>8L^5pD+L-8Ipk z`Q`r1nf@?TQqg!{BU#=O_C8*K%AkcZKw@R+u69Ga0_~OT8?0&%(KmFHLXbBwQ0P|o zv{KQ%WW08~O-iduTGzW*teCR9Q?z!CMr)^qk@)iDy0=n=BGt3cHb1QJ&fh+mao$oj zN6bqd@lJ3WV!u`?!h4b?&CZ|&8HFXSOXG)XG*~xGd3oyK&+Kxi*xayJtkIU~D^t~L ztokk+I+v%;2DbqtkI;Bdc4?~h*KLX~P8ENv;z8aGUIxvlrpkY`lP9lD-MZBBKUFJt zZt4YpM$(a=U<&V;>OnVYst~Td_Q$qr7BwhHCe^msiWov z*9pqsN|oQ*w*1Xh`TVx!Z>(3Ym&#+jo+@6T=55|}ee1hksG7mFel^v)DmXbQUAX&K zQsvcc%m1D#U#xOY@p7u@9u*154T8&VW_haae$|EdxGYutpo#~112Pb4_C_=^*<89~ z7J|GX=r&)Hrv(Hy2dp3*kr#ItbQ3o z6WJeh!Po3&D7!OtgAFRvZ11W?_-1xFtxfRRVt%cK&a9LM5{e(TP}mArCmP0@UqN9E5++t-7(LvW>O~~H4^^cMHwF`4F5=rHMna_0G#K#e|pTQOm2npR?O&7 zuPs87*osO&1sea2MMPiP0Tdhd3U|K@i)JCpgfC=gF}Sq&`O`t*WBPM7lk(iN$uMpe zJWN4dSWt_sT@T&n8f6kOkxrHD!G6VhC3N zOtZ`t2gmd4BoGcoG>k>I6WQ9H8;NP{3CnU8;PYvQqzyTu=y&<_IyfchR!fopa&(uSn{@>}?*ybT!dcFaQ~Dj`%$SU#B4NTTBSW#D z4NpK>T9zB0<9>&9)r<9d8;lD*z2h>l1&Cs?+~dOz!g$bwTPJjhFeiTRA5+Xfg6q;P z%#?^t=-r5nV(OgUo?ZN7^Z5#mlO@sLDUle(Zv?cmz#!o_xm*4;8GA+|ea)&wI1oqi z17Yb*!j)!W#hE<9J*uJuxB-&XWaM0nDOwGmR4ETJ%b%eDGD@Bc4=vY+f*+G$T0S`p zR@YJ>7@l_~_9uey*+R^z$LA*HM@{7MD>t;Ii5GJPnMU&_6k`c@&CpeMtcmO)GhZTL z>h*DxNzEA48_L(~u9h;4T_wcW)Vn;`)S zCkWzHuw^l2R+hn>!`*c8v#Z>*JNV6Q10-GT2)awLy08NFWKTLitrBsnk~gF`!o2Zh zN6drv#Rc+x3Qh|$Wti4hFOF=S8*X8~SeY8Mi<^Y3Aq{TPl#rPl`s93BG{OuvB2k2$ z?RRkEL|D|@ z9)=Mt^TT*i7Z)eoccP@E&&~DMaKwAX-gs7p&ETGa8UK+C_69SK8xz|omO7l_~@0t7+3*6(QYh^q2Qs18r%?FqhT|vNCd{ z#`eWiFgc6li9xJX3)np57dncn{aNlHwo#3ZxKoH}kzq`$JChT0DFYO819xM-cTSpH zy;Z%oWNc<1;uYqTGZ~8%HvWtiMNU4%XlM1K*{`!cKB)s-qtDzjPs&%S4W$o zmD#J{w=e+t17b)|$U@#EQpkS?0FL@M%`#EQ^Tx*@*{f`abCkvqLpcwNWp-1U9GDqy z$1E7QimEjr!P1kufpNX4%B=ubOs0l>ayZF^CkZ1lL5yi%$UulWti{dGCqvUr_Xhmb zxJvRgAwbbwrbAKjx}jX+M7VSUwm|o-nC)=Il+z`kxw6m*EIi%nNT0>UKGntS6xZhC zODl69>lAV-Psh&61T%-&n2tC^|umBwx8>-!6rhK=e}m( zW0cuXJKE|XwAm7gm>d5!rQQoT>>oFBuBQmR0{Ot5CQ1_EU6>I5q6wj19r#O+HIu^H zBJWd=Kufjl9r+)|FoDd{&$)+OoKgrny#s%DQ{+%28KR=Hcmu51oTRw}BCT1tmGa>d zmQZsZ+$rgzF%%E30i}QkNq8YM{{u=PtIdzH#>kwFX;AacNHIZ)Ma_spnWGjcgOh!l z$82(2Api7Uyl^da5=)RbB$puGm*WMO)xp(D#JL1nkLMyaD}20wk~5cDOD{W>jXY$HC=Wggy)Q+eLD&%(t}< zNnu_kQp8;UMI(jP(T5a8R9`|Wx*At0->7O{kJO7rQTLB0tjN2U zi@pU#9-ALa-+L##tH@!%h2-MWERG=1puopmer14dnWEH)^r+qAh~$W{a>Xx5%TtmZ z#tx(TQl+!e&~PuMVds-B(QR|df$mbu31dkQM(ukM>6s>HFC!ahtvJ-MLNYy^(mFjn z;|Me>^M)(a1y2Nz!@dN@!@r=+Orlqm4RegTmpJ^5$iGunWKWXU(WMkiLntsWWI0LE z3^qcE7O!4#+tNie#JpCraTcmOJea|hImdLhDz}opLt?+bAE#az z4M>h8`12|kSxPlEBF^p0Fw4Xhr6hh-Z8H5QSG>lcMl6#+gm!k7EO1Q_05eIyQy`9% zxujfyzP2|TsC1YM%4-{z(3_QP+1&+ zhx$r`rYz87E5e(FSG4cyzKCX|=!R4DEzIU@F$un9g!~;CX5=-ul~8d*)_KtcR;3~4 z=n5IGpW+gw4b)75ZxHi$=CQJlW;&DwM(TMY7K}tzfbATdJAatSwZlc468!N?Pz&HNfbmjF)^` z`qf)lfmpp{yIEIKLoq2B#w!H_#nV*0gkjGK=~Ubp%pzix=_%zBAetPo+*v6Bx)9^6 zF2Nh#{=_%hi9cHZ#Mi61me=(W&;5!f=+d8*`?sjn!{C(~*vyW>7q}(81OXOgZEe4E{a<9U!^g}U2gD#&Uj-(C7c*U(hQmhfR zRL=t;I%3M;G|rU7CYUIL6U|rH3tt45~RizY&+)E?u{)~WRxq7R;40x zI9@CY5aa)y9232e+AT=g<_@AaixxSUayxmGWOX_qUODo5S^ugkQ9-j`FLRw1!8FaB zfSiRQr3htB@66w*)yYIw7$&ZaU|h>c&heUgpu~wp-7wj30}1rVJl7jJvOgB6XXqbS zLP%7N?I<9Y=F`I120ZX~J(Q@?I93$m*a%^sozxr2!cPIh;xnqB}?j z(8>hq3d^c4@e=8I*uuud78<`j0iw#hqIwgWvvjii1h_Wm8~r}R8w5!rE1-8MujGKZ z*`{9+xv8s`&_0}V|J{7~i!m6oH8jnITW8x=CkSFpid|9|GBy0?)UX3(vq8v8rFhsD zQ2=e8&*Jd#w9j?NW6nOL_<_-7<`@$R97X!u~asPQ!EU?C5imldfx@r{mA<#Nn%2E0;++pYR?iQ_~ zHABt-jyJL0-XvY(53!bLbF@WL2urjv>a^&J^yRiigT)@Uh6Uj{{v#h!x5f?(k6B~9 zV4UwznZYYd0P&^p{<}cB%?!-Vll?B)=7#m#V3P~lFc)$&`n6UM-oR_f_Kwc*4+j;> z-k*ZKzWM~GnG60N2J1ugj3Wf>Vtz6GROoFpU7a1TTFrQZ3yc2&In+tUmI8I-E-oMG zXq(X1BA9@K3LkOBpQWL!$?JK+ADf8R=)RY~YY%_7J^kHXVV3`Ur;zfX37AVYagzwu z2uds?EG79PL}}tkWYd{H6Nnw*&-g5bo45-ICFOu*XE2QjF78UQH&TS-8v)o#P2>O*ZfNlBQc0wys&_gARA{<(rsY(f(CJNQr zM_WEgluchD#{}k#sP`fFV);=jqASpwB-|b3cEtS2mhz{9Gia|OVNI~SjiOskGQu3p zt7BbsdH|HT! z>}Q;|=80oo)8y=Sz#GRsEBQiyK(@FMb_GpPhJWhx4B7JckG1%7QCo$=!sRMruTx`6 zk(Qfbgb|KmG+#D{Wx^O{!bPZ`l!RSGmNq?{ed_mL`Hcy;jMOc>aj`YV~u8u6i_U0mn zwt*&PdLM%G*X_bf0Dssmwgj26lQ$?`+!)t6sVp3oqfY$V7Omuso0m2)H*UbVPEZGo z9)aGEqoi7D%-li&VBV2CcMSJYS%?IiM)({1hG?rWbmV(De%WFbd{6w=6>y913vhCg zM7cW{hI9E3x%fWrmJWz=)V*DZVAEdC3o8d<D7hkzwSq!2%aXUqsiKzCRLjueFj?TJfK@{ zh8Y5S>-#P=6Sby5#Ou z>ZIfSKIZ?qr$4#&R=@9RX*h?;E}yMa4<@~Yd#VsxYmBTq7goIi?+jse7!<2ry-!`O zpZ%^aJ}>KKztSPkCvpa;1JmY_mg z6P5He1=GwFl9~OvseJ}ve>X3+|CaV|P3^y_{rRc=H?)6Su6nL{_P5nqlC2=#Zm2Ez> z9(7{|Etcc+U1_mJNOf32&Ww34vK}q5|2xn{>lg~KOyQkvz$%S6>zXFxx`dxASvk9H z<;SYTs~8@5b=$fx+SgqgrD^h{UxjsKB*XrSH!AtN&ilm6#Bk&1&NY;CLZFPMA_Qf3 zGd5zckQ~y{35<G`dHN{E8)v zeF^sYc*bduBt@^3(&i1Z)IGA< zW>3)!0|{XD7Flomq@`|c{RaXJRZ)w|*sf73CqsDuOLcEZE6iDh;DKgIllZ56s}-1N zH6LWSWAogBS{XViD@$*rIw=K6l*rvnQKNdo+`ntgpTWndOq5S;sz6>LCDs7MtL-?yNjuLa`Dqb*36SZnbw5lv z6UYWXC*h3C!`vI$1x9rtVF`cb#;;1~5GYU>p)o`U3KVv&bEU1Wr$Fhhk!HIg z%r0VW{r7xKr)!14$HZ$`*Fgdlc6BgNfVkW(lN0WJE$?`a{oTPMPZmYmKV?J^aX&tl zDwgp&rN{GMuT!VE(F5>9P8V&HNmCzgPa=2Fx6jol%h3w+u30`B(9ss@zjV(kcvkWJ zaANl&7TTUTJHp+U*&T$*)ZLXVsi!2D-c1_fuuWsKldPg(5CeP;sh;dcKBOQQ^>7jPYa;T((jO^3)t~L)Z48=C#T9rmKsIO~ zgMu;&fUtvRgE%rMf<8RY732VlNs!?(EWAX86TxCo_;s@IG8N|K@Lm#bZL*M@oOy11 z4r(m2V6u>C%erP^4Q{Q(IK0E&;l+*&?(dLr_vqm64hnaV z@$>dk>t5mhKw5w0_6hg?LT#?wJKQ^&Z7#pl??|saCPSv3OMzshE5Wlm z))?+_Yo@W}%uz0vTb|ollaIy#;tx8+B|bXHzNY9Yq15k5Yr(pH#UupWufbF8y1jzR zYofCiuWAwi;Z-I90N$!%rCB%8D?()KNY3bq-ZY$)Y(~8rOt!x})7@24FZ$T^gY!p# zKg$FOpscev*Y{@uzPw{SipHappv&Gs(|F3aIkyINwj{B63$mT>AkKGHN8P)SL@05+ zutkWkx|z8nlJ0&e3M>hOw{K%fkcV!53GN^h-!1J2OQ$?!=umORM)F^40boLY%p2FU zi{M}U2uKB3Kr>1B18|h>uCl$MLLX{II5q-nT=1LRBGevkzgx>`iI0zy9NtA1t-XTU z;LPOGHvga+2gllJ5ATZoDm>Nkb_p7cX>AZ}bP<-769O4DW(87*$*Z6|KeP^S+HRM7 z8f6!tJp9Qp%Fp0YxI5i-@M8yujqLQgeS3oQxG6I=a%LH4T8ox zThPFc{gYN4eK*{V^F&I_pntp2dzj8OuLF$A=@=gn>VnXVCNsvN4~Q2Ph!OG$D0>CP zVG$OwgLhO0eyc)n1YX7t_1Ky?d>q5$V=KCjBH=+~*Kr>e$~AvIMU@m~d;PXM181KV z-jr-7EM1cwdUKn0pq#OhH(L3fl-pIt{>QJGhZ+>hS&O;Tqt>4j=6!T zl6at-WrbBD7p5nyx(W9#CzRQloGuB!;}>0CiDdBL z2Z>~U*7YUm{r7G_`3|N6y=a%PCu_R1`R(#icl=d9YkMi>{?aFZHFZ+)s=#dhf%Fr{73I8g>##;2XQyF^r$V!pZ5&MZ{ z_O7Qt`T5xU%dv_2b&-xRsUnC~QZf?^BSr&?K1&02R!31HC<01eOo`5NPlvo^{xVJq zVZ^Q=oQM03x%88}{MYTR8IXxSql z^FM;)B!-Y!C*V7s2j_uYZ@n|UgSz@7F>b%A`Lqt$H*o?=1N9NjnaL)H-j?J>>&k*A zMYVq7a^Fs+`IFec$wL$V7k0;f+7^M z%LE9>RcD(7iKojza)8aOzg6F0;oqOP5+`t)*H~S`%*h!8w@zy`!EfP(&+?uosNxRp zOkC4X^A~wt4p}inksREF^LJb4h=0#{*1xN7DV-%e1Hkw|$jC7N^=(|K7o14~Z4AUY zI1*RkHzk-HE+?sKC+3TWEr>1&VCI^$kQkc68RTmJaeX*Vr+)MkmK0K((|2abG=M5D|2@0rzh}51 z3`qKqy4(Z_Q)=94cu0a9y2^ak&E!T+5D{MEPRerGGCX`zfHm5NO(|0HZtIVlF79Oz zR|t!|F_3a}+A|G6i4hJD&`R+0d2I2(Z-{mwFOtsNoPtU&fN&*&*^l~jAMxis?B6;M zv|q-mwZiXaSIJW{(tZdMC!Xauu44{n2}SPXGw!dv&)xysdfj(e)lv)Ws?s3k(o9J4F_^uk#A)a9YQ$96(YqOsy|Ql$tg{QqQ;nBfEM>&|@~0fFhv{q6H2fLp z&3oI6c+`t6!Amb6d@uR&nM&R-0rIFE6wJl9cqE}%gNA~+y6vwa+J*j)`L2#$T`V)? zM;c5-0Vp*qyOx@jDGR&xp21vrr#Y>$4I3dCEps zfSufYm&NVUVucdoZn#G5hANBJo9Zboz6s% zn2kiSXNErjJtp1FwwwJ#H#;R zz6NOo_t7PVOv2w$%21>D+6K&F=w%IjeMqB7ms`lBda=H2Cojhmf!%6C!z9AUj2doT z2^Ky>vszlPnXg-3q9;-O?$Hbc?o|*U{~un~4sdRkdaqIQuxf^y?5b?nSu9L{EA^hG z=CPp08?RFOKYnDrfP+{Gw$N-$98#BBgXf=XH&7#r%XdqaSU7Idj9LRDI zMVhYga=F41<7s)j{wd4JhhA_T>T^lJUCz4E1nI5Y-NEx4pMOv(Xwy6Y)OO6!OCgM%*M1g zNbGcHGV8=^gLML{BKHfaf)=&KLXQ1>xLltQE(ao`Qpj7pZV)CaGvGn-e6Gt&I*Lzx zsfjg=?fCiqHp(yN36ts&;$b~Ae{}|Kx)iZ!{ zin4_6&E!jjk79KdF5CVOq%O2pqzb{AD8K|Ah_y#~?DfpvI2OhexAVGdYVap8vmWEm z^85L-{e5aD8pQ17B9STFqxPp|_{-|-By~k{nLXU*B!hyR<~nD^mXLA}XM?PC1X1Q7 zzw!)&g7<*F(Ix-%jXV){sv=?A-qcC2pgZJ39VMKJP`a>vUsiBT{)$|W8qL6gVY54p&R$OO% zeI~VC!3-cl20!4nL{GM~5DyL6M1z(Y5*`rf$n~A(x8OGSvn!2sj)OVv!r-(G$;QWZ zMH0m#MaECvSCs?rUYzE>PQ&bcSt-VP*!&XGyAuF|35xcPG_=X9immr@)1{==Nu@fO zJoQzL!r_2otN}Ua9w^mpuw3)cFfM$*?Y>~s5#gMyeSCLx)5LS=h$4QfKb z+$iq?2?G}djI0*~Ng83tWcm>-8B|^i3%uJFZ#}&zim;I$&0ox;B3|TVwD7e2H&MWs z=UuWQc?RQ9FXrLng)T;Gi%r9bdv3NRQ-?wg)WXOX7EUIRC$S`N58)*5QF71Wu}Voh zfTME5zrml(nY;j#W0)g==fX?68qoeSeNTeyQ4veQhbz|iHoha#?N$1<7@3s$7+q7u zMm#sfM#!ZxcLAQNIMN(z>Zx&I5 zjSSVV6sTFXq7o(=Io58lxpk$FH4tL(4syoKTwb-I3bhZ7Ndu`(M%08C^uYVOm87A2bb5a*XEE0_)P!B7MoWnG5cZ3Mq6ciTSD z)bgFqGqq+cejTr$E%BJvo+YR)@Dl>=JjVZ%e*q4WLb}hKH68(WS_gZff|0KBSIplG zyhbyXTg=M|%!|y8yy^d)ynyRv=L*uv2y(sLc#qbHTu(9UaY(I(asnSysRyBDxyIJ< z8mitQ84Yw&^|_fW3%_TjLE8I0Bhy_-2PB{)t*TlO`=0b&8LJV#tpN#vcQ(&3)iP?U z#R|nzL)OXwL+WDf4Cl4DHz_zO1Ek!}ph55_|I^!nwwHSZCXnmfKLT9HtWx!=Ps4FP z(*GUYc*@xKTmRgz0%fWWN@cD?KGTPkY#N_)Z$OtLkeZYKj%5r4m^;2c0X7lB6R#{K zyLXpG?!vs$t6HEm$0ii%;kHKV0T^?%0>uxR=8oo6hakfBN*C4hT$g8=3+ES)aBl|L z2jBbLVu-4bjMIH*&-)$ERJrEEQnZJiXm+x^I@kZ zHaXQtIu(dHy562hpub#l;Hy8bq~dgb+#Uj9jAXIjgDL2M&Yan9?9mcp|G6))g^XbV zfF7TsY=^*s=&AIF$EFDT_BZXqC&XPuS*{?zV6`VFs8UzMmcbNV59c1!G)l|2XG(XgJL4)h2;Mp8Q%L$oXvJ=gzNKhmLce{Y(QQwh)(h^Fib;rc5+IQ??DaoNb*!r{a^fxMYt2mUbs7ckOns|v z_NitnSOg5q0PhF{1LMeGDMV-lTk>1)bAYta%?K(-mT7eDp3=kNkDExR{?hbimcgUq z{$V(fj9PN`6sC(baq4@e$cm%AJ$CSZi1t{-!Og;hVNJj1)1 zesE`6W}On~dGk;UNKRSneH-i`L9Au^sM5J1b^Xjrm5G}sU)W9;cxSk+QE_5 z9r=n`f$z7G_kso@trBTiy1Jt4Q&VL%uZA_FQWbYt#fq?EG@%y@r8Oo0P?OsZ8X;_wWJZirMil@re3Gsvh1ME`KqN0_E|Bf%ny{ zg-yefj{+3)rUng>Erth~zmKyMU=uAB{l*u`_SdtHs{IT)O)o*`b@p>&K#S6i&T>qq zH*p*Pd|oLy=kDMFL1cT!nSGEk2Xgy@j0UZ@*v09$K;zCwtb2w|hU^&E2WRcX=b|yP z105BRxXhVDyU=&#mdcAZdKYu)J+WwceY=R8qfRk$Za@>qBg}|cm)2O;kTAQ zy+wsAok+klH5stvE}kOUj7SOy`dgbVrgA}uZL6y@Ff z{eL^}-cRLm`}yhRHkR=%NMJM}JDGHBlL^&+Y5KoSo@m3ge*cGZ^=;(lej<(GNu!32 zCwI}n;pdMVKY7RzqlS(hJABM0AO0tD_H8_$-tb?Tm2dO8^L`>L-)SVi8#jr3Qp1Pl zjUG34lTWhQ?0v0?ur_)4|5N$-+GP3a&@Sf}+4+(K#}ZC$*{-&IelX?N+eq#{5|*u( z%gfG0j=^7$yN|Sbo6Oxu(!Bpn?mnf_8#Vm=RBo+}Wbz~9*4g=adE>^~Lns4WDv2DK z+ty3NM}3<#eA+u_{J1e=)=zIp#m`Rrw?S$?owZ$RJ_>?H8%@ne?FOm&C~KFRPiGj_ zCN&@X8%ZL!LAE|J^0j#zOOMdDbebf;X{Jn zY~6wjt(O?@q{$QVhM%A8`0dm0wMp*BleJ6kw_(;s`d=zn935`L@Vq>w@w8KOm#q_% z6_9T)nIDj`nmUjhOr`3xQ(lmNL2F96Fj*ft>e{C8Yx5#WM7N2oeTR?E8}gS6hLi4& zsWmtu1AW6Jd~H+owa*C1-B`D1o2!xM8a;Nzxa2c#GBw}Ea`C0o?CsblvmVse7qGwAcCP08H z^7$c%=a`*=Y;-W%Ei+2HN}`P5w5vn-CNK?>@5)ZgBAw-yNZ+c{H1~Be5fSHa1DvdYJ*R^h2Y!8_5*OLJK$5C@6D=6Ku>X|AVsGu$NERdZwuwV197 zvF4tH{3%fqWdsAli$I*ws+4srE8Q(nH1!*1{O+AD;6+_AQf{{lJlyi(Pv9wV_xWf8 zCETLU0@Gm>TNXKiv_}@8&dBGOGsx?A&Ck#4_p$Q&!Mc@*`~fGZj94wx;jVcEL3`em zCigasGYQB>06^jzd?&ewpDfS4t36z1XnpS!yv8Z8gU#KN+Y=@;ZgC%NaAT-v`q$U2 zNT~SUeW*_NVbs2tzp4K`sCWC8Mon}lK3SooyWkR6s38}+p>V`jwGHsBLE^bcujAuM zu-bD7G_+`{x4~(kJ*Aqfha)Gs{YIPZom0iXO>OR)+T1U-`S8Xw-nGez*CzR0x72aJ zOKvJpVoiRYQA8}2Blj_v49Wcl1y})Jf_RsMSy~nC)T|GGmyt*)ll$G+sCq`-@3Dqs z+9-~BuYx14b(q)s5Gn?kxEy5{Mca7l-4d8447}dS%jPssE5_Q07T{#X%{__W?&%^y zZjR0*j%AU8GKI`p9|eC7sOR-i@E4l}wOKd1)QBI<7cNq}@)?+V-0el8-FrQfwU4{F z0RBOkZMJNdEgz)I9g{Hr@eRdS%p;M4j?Z*LI1p}(SQfZ?0*;$;=1_lvCDpl6dF{o` zd5Oo9bO8-Y{8CkAW#wFY!OnU~UjV67T&h=Syj?GnJTqW9VZhbXpTtx_f0>2734@SW zMfbCOg0q!RP;|$K1w6+DpDG$;qM}fV01?mY%i-bXiWme&6T%7R6CBxG?&x%V#OcOA z9oUIG;X|@-_%9!huVAE9{spm^=vG1q$~9;Wh4l!$Av{ zOIX>SQNre#zSOP^4s(18yy2$E0T^Y#aE68m3^AG}WtimhzY(2;9=nge6eILN`e7;e zlMlRs`DxjtDWJ;I}KtGQw<|xMG>M2>IQ-d9^A`K&WU;D~Q zOR2wHVA48XiPHBhDJ3Z<-YfAecM={&Xbs2*vuuLd*Sa8CN0iF)@EGV!pI(G*vjsb( z-orLgH&AO&knRka!FCKyqg-19#7RjbHJ0TGkKWzaEjI(2&eo$C15SKvfr+<^+!htkw(c6Nc7Wo&9Dl^Q_{ za4N18?5>eE#+>{179pm*%XET()eW8yF$C>EGJ{NevULxS$29j;uv@No#ESQHxNF$c zM{!nb#?T9|0G5e)>-k_u-}@hOlQEIGLwKBgN7BXKz=;Gfo48@pML+BRp{$EFZL==A z7Why4)fiLjK5>6d)D5Ns?-tC%d_l6xUFClhb*E9fXKN{zd`HlqCh#Nl`I_NY;q{#% zH0uF+pA?**EI2sNXYEkY=wwl!u+4~M!4pP$LKCp3729BI4l+f8@4S$a^EaynqHtD% zTv4fOo}O^O2Z?-!x|$}joP^uUu($Uqc0KA|(s2T<&H_aX7rOOuE^?#Wxl2KbfkQ@p7xG+*P*=HFF7zCi;P@(3Px|0B1>Uc0|6D`vc!6h*8bl(b+r+X)zD=o2uMlep7>c z654+E*D26H?IoPGztfQD2;tvEp=%r{g6cEhUkYzG(fhh1ye7g>V&!E)1A*fm0Z^!t z%WJVYm{?if#Gl~eH|X`rA@urqKyVX9vWC~k*vm=RaUv82LJZ+kBlzpO5apxm?F zN1)sRd^d7QIpP+uHs!?KXl6JTdC8P#;J-3v2RIchC0t%Qrgi2Je28Jn1H2fu_*ZIc zVJGnN(?yf|hA2MvvH&5X7$ z808e7;ZJ9GW4u2UW$tCt%PeD7FT+Xb*HtYR?bmU*y^-Vh78yMlrUtW!*HuBIj$kkO z=BtSn1mfA)m<2W$;)=k_J*diE7aTAEz{Ny` zIlB9dPJkhEZ$XVwr!d1V>_9i!U2hdyiUOmh5H14-9^#AQ4ONv5>v~mIRj%T9L(j!V zHwj=0x=AuB;m*$_r=Yz0`8vXgxGUU?0^>bsfDJqWb1Y<6izKuZ0`NBnAr+X$qITau`C=AnQzcRnW>V!0*p95x9Vh^C-mCC+Lv)q(3lE zDIi?1-*`;HPu;+c8(|2|al&{7Lg0$pE8Aa!lLeAtma|4fz_2p!9qN4aOp(CzW*WBu zL=9Wh3Gf`~l$~k_U{LqYbH|ZFBq$>HbwVg0{4hfJAxRN{L5rhXTS&@&wIk?iktt1U zKueh+O!clAmCE>82drlv6GDE%-9T<)#trnLr~7c%z75a-EFOh z<+x%ZeSVR5EQt?r=9b|lLXIUj9B7Rm3V`h|vFatQxP}Z+r0Y#UBw5e}4KZ*jA&O*Z zfX$0x0dKrGQXfnMM}C^goM>Cf!4mJ*xNG2bT~*}wQO3rJEVy5gTzxT*fmSq0D4sX; zO_&wj{7LGXZ<1XCxGSI_ClSpYb?Sejz?{iclSd+DS_c!`N-98}jApcW5AdC!iA2P1 z$aSmo$r!}=xKU)~4GKnrah-@Rx*m^Cmwj=9kW}}Ha=nS3Iu?AjR zg#c!O7BY|)gTKsV<_(X=Zk@sd~RzMCXZEid9_ih-d@ zOCCVTK^kWFOKah79@J@orySX}(p>}}N{gTE)^(g#2A1{@`&Kdk*3mWT;!Us)-+&xG zTWp0oaG6qWGaz;mT@GFHa z`8Pve*6dizt@#ajQ0d8EP8OxlH7Z6gOhorQi}`3WL-+b2Dy}VM0031Zs zW~n!pE7a-W3|*o$13`Cx5N4Lt;23)OU_tr!ia!r>f`49LrMl^-R5$&U>ZboT{>{)dE$q9c>xLJ@;!W(upBqmAp3U_APiTde82WN816SZ}sPI;nEd19bWk{?Og zbNVLJ=JZSIm28MvAW!RkXEr8iV_P;ZRO58IebZKYy`xYJ@Ij3q z9tkL$nUK+R;?E$%-h<)C8R!z%`kEaihmop+&hka|>WUOLWf0G$o4cD)=a&$r2u zGSbwXJ10u*PCS$}H*Kb09;OkF?xN0l^SR^9l0Hb%@4I?|f zr3(evy?}Yuv!ZTQ*2(W1>FOT#L@uWryfDLi*!rp-{b^HG&P8i36>#QtGTq#+3?Hup zE?`07(_3Gpb3C8~@`|HDUt*H2R8CiBX7$|Z#-iLl--i}039@@iW8GB3kGr{#Amaxs zhSnowA-BkKd;DXodTlAU2b<_?$=n_Q1yK11&}q4H17H+8I}<1pj4S~_qX@@SCFITw zC__VWM4xyiUAU&89=&`g_pCB4D>@jQ3VHCM1N+T5F=q$HK zUL7UO`ik+Hvt?L5doGdfg13MYH+YMnw2VhJx;q*(9tS&KIZyw<5-*<51|RE<(ql1& zR5~IZ=oM`^(&?`YB0=~Aq9HAF(%mwnnA8-f&i{TfYBQXlV*lh~MSPX_l_-pvQOrtt z>TL&Wnq%6zvoOy!5|vM{Wt$0pMF*WFN4=}Ohz}q`hI>5C*yRp16(Jm7MBGcG+4$=f zlOp2_O&GDfO++YpBQ6tXy}`i*+#0ziz-{T#p)^23i`##8Ew+}4gj_#_8zAoIq*c8xNEWtriH*aAwnFE?BDkT-v7_shrLHVUjO-OnGBUqxW_4a!X1+lc zVPfBx*;|ogFa(W`<@fOR*b!pIIa5fy8jFCa+`=-+NhzXuma=~a6`!d>M!7@9rW{Sk zHge?@OLdH(uS(jJ0xC!)iI$-va6kvCJdtepudpGRe3K2WNL`z6{fJBpNL1UQ&0(oV zCNujq_H$;88Gh4NwX71DTS#?*C9yc{4N9PAYGOHL)i@6R9E%)CCOR_23^2n?ak{9w zPfXKE-tT~L5v)*ul2WT)X9gYR`oO4xiB(K@l61VOd@ByQHllH_ZkJ?X9zjr4teT6w zT1aF7@24%z;ZAO`vHf}PdFe>5rqD^(tOQ@1osAv|4Y{V1dpc-@i)WuisdzOIu~DY> zjAwTuCG-w%aXw<0O|Yk8Q-Z=dU{g3w2dj4s5FT3!1s!rBePb^|zGN>(^gwt7Ui=(| z2U7T0SSZ>RC3gVJw3UAyQL+l(-;Uu|BMZ?um?{W#Nba7m%6M`Q+RdoTw7pqTH_ew6~hJVXLbb@6v@e1b)wl-5i zzerSZ5iA3ews6VhE7^a20p zPwzZBR191?bFcShBpKbaFDTEkbcNBhnH~;9>mT ztD4pLA}@IG%hJ9@Hp(YT#Zvf$`ZJQj`SK$(R+`;orOda>0>w;1%oV_5&D4nXTwy+M zb%_0Nx~9j&(f$f~UO26~vJMzGhq<;6L$zSKCRNdv?v*G?q#270AA3lwvKH}+=^Gw4 zIEUS|B2tl6xr;hLC|Kw2De`m3-4OwxOblqIM#o>6vj>s+WHO7Z`%;<|ngl!=?yAW$ zjeI5gS29}0VTP_&0(PclHOH=LG~MBzJXgSVGMtQAMGIaF>5gH&dZcj1l%<7Dyqhe$P~h0 zmX`de1;XdrVruTvQkKg!RVLHJ(4B9^nQkVQe$p7f!AQI+;=?FI>=IAe)A|4sFt;a1J5h8@ePZa8 zJ}q?mAYSXECCH#lMd`tZMFdRcJzrf&;;j;V~s^q^F3mlrIz%l+1oNbNT;T|=jL zMsz7*$hH|LK$6##K_D!h4o;My)9Dr@HFS_$KnhYM=5F#ZH3ccXW04N-gOVl$6jpsx zhzG_h=a%7?I~QG}t7JMc!Td2(DtnERI(&~gkFKDHK^^HAkGj8%aQkp@1uG4z3B88j zgh~yFy=~8BBvVS54KF~(j+&W8Rmzfpe15eN&a z`%PG9B;SmkUw;Y_zmC?@)|)9>=HKqy9EbS_JJ0jPh}0rwp=X?t&Oqf|y+rJ_I++df z;4$b9`;gDVk+;0{tH7tg%1NL8C&qOnnC+9U_4P6j^C)aagsHa4V{tu19Q}zCihIi( zhWl7`gHhWqgf#`~3z>dqsX!_V-QZH$WA*v=rwDJMa#MlGU`SdI9TyWJ`K3;1cqK6A zMRQ?{qlkv49HSPhr_E%V69~W7e+(lA)_g7K!*xph9GvnaAcK0Iet9T7@r5xW?R9OYJa@Xk)7)87eaJiCn@U@p`CBKA#x%B1uqqsorC2SqIJ zPz5lJ{y_e+qxgs&aER0XVdI>)Oe5t}c(k1s=RO7GDNu95Ka z4B3?E9yW#U!WVG#md9GwzyeZ4*cM+o!c4&Uk&sM~UX_;e?u^`u$iA56!bHDJe0-Z= zl%mL?l#d8TEM2<=J2?b%T!DN(di!4~P32)y$mcRk;ub6N=F1kiI)YbJ>j#5kB~E~_ z_@mZhSD5hM;?q|~v*2Q2mijcP>c{7pn85y2A66z*`DLndd;&7dWaX8rT$`#S6>_|_ z@={fjJWw~H)1I3kBLnuw8Aaahx<|kd;XS&M3Gy;fHLb{7sHz`RRl^cwf2NA~ZL2El z(0UY}$8M@3^|w`BoT@q_;dWEiH(}Lfsj5K<)N!iThE-Rls!o%yk}CMY?S$8)s!mS0 zELD9TR+Xo!PDr?YRP{+%b#1EZxCE3(R1raLC!CY2GSVZeAmy{Fn^RT&67G*W>bVde5v<@Hql(<<)^D_?G{oP}wt(0fM8T76K+M9w7qQZW`DTL|3iCEiF9 z$06YQi-;T-sf}Vp;q$-hAm zhb-TbS=^sU8lZGds9;Dj-moz*B$XPerIjzTnyHngaCIrx3T)|Jy<#TTf0T@Hi$tAP zfir<9FiTvRqf0)ac6EsTNYPW^Z*M_khQbk9e+vFBsDSIWHQu(Oh+A6Y@{~~qm&}#^ z{ifxSLGW7&IQkN4+TTgj&eNBy(Gry9E^4y?xv3>hwN>70yc^y9Ebf@ay&uC;?lXf; z9bu~U7qZsca)fHV3;mr)X4A|4n0F0QSdTcJ@a(wvjkIKPDM;t4GjFjeUH>sd=P`OQ zOSk%}tc>JI-C-lGt9@vHf0vfphxf~`t9`iBzL5;~RT}<8c^x1-RQhj%oGtzL0@Q~4 zkU*4XfFyMZg&0cEDVHFG6o`tj7k7`uL+6-ZD)CG=@Aqx+O60v&G1kb>4vmm^^65#d_gj9Y_*+;E+bsaa?SoSw1Jkz-5`^c|}B*%=K2djB+&_ zlxq#J*N;&AskDUu@m`L|v2+xY7QL3&+TBtv6z~<>_ewh00y(|#;UJ>HE{Asi4n$B5 zyo|ZdU&QYF!lof2{Onvk88G8z@s9UOk4{83S&M%k0dx_Md>EW0afB6Kmo^D9*PjeD zh77FiO8Y(lnNFrF`=TAWuxKTgO9ePmjh zyE}~}NSH!Pm5Jt`(#FYcEL;BrXzjDST{CePbc^{xD}MXYc3YG{SMRM9>0U8|2K!35<9A z>##z|A5eO3Mu8xp-wTTY#gR>#Wd#-P2Q_PUWrAV+xMiMjR!}rbQN$G>R4va1td^rd zaKLfKc%M|^E;gJkn;dniA0Aci%_4&yXmIz19hJ~ga3#6VCdtWF-u5J)aFW@9K=_|(R{RcR0xn%;+|#2@pSs$de=`GdewA|d8iEH131Bc?qTn=9i*2^ zkT<{Obb%~?zR3`Btfa}13%$}IbXj&@#kV?F{<^blGJ9b%1_^mX8({jO`$)^(w4X+9 zuy>Rc80v9Oah&Cxe%S!Jcpwo`x}~u&htT$CZMR0d?pgVYmdIBmm551nUJ=WU&7uDh z(;92c@D1hTN&kb`K30wsgDX@A~=2wYgoi zmewb&(2yp(?=&0QP?L0%S9Ph3V#)%dslrchc*||gBobr+k-XeUVD)AjJxe3F1Fm73 z#1D#k>;5`X!~030%BrWJl5XY-%Q<+Rw?`M|>8@_U<}dV`?#4`Y_uBCK0*;|J-I9y=RTC-+ZI@bw%cjCFa*;O5G>s zuED3hnVXcL-H4uyr*E^@|4i5S8~s{wJ#X%FHmji&?{PROQT;dD_-h)2$7F_D0!zwf zn|`OJRgixw?*>qH@8r$B`*pPb(I1_w1c5qzEsO}8J^ggTavI#)%{Q8%M#}LrPRVoQ zLbt&bgsE)Pz5B}G5IWEg_-E5xnV<<(Y1=|O0?S(ue}p^I1Z)VHmQS?Mf7mEFBUsj9 z9P}`{P@N=J#9@F>wW8mbyGh!hmCgigyLgJ%lL!R4FP>F(71i=Jv6QlzbSSYnUyu;0 zB_Eq_l_mahx6u+no=!hgw3)#ETI(vQd$&y;{hj$2a2^W}cjL~=al7zJ&*m=Y(DcKw zDKyvVg8z0$nD{tsJkt%%al6x4f)Xz0g!?pX?Q(F7^BR_7O$6Io>z#_UC?stu4KJRe z%zWP=*t3Pbk}d$o1mGB1wcLy;zzfJ9Qm-wbY1$N?(<-N1<-e$8YX+5ULIg=AmPyP} z9EmwfTV+(T*?QzOt6WDVTO)XeAH!$BkR#eyMM)&wbje4&GnsFBDWc2P+x|{C`pbfy z$Zo70B1;cBE#d;449f_3VY|nw8idGLEY2=Dq?SgP)h)xHQ3s7$d@*>;KI&6UG-n76 z@E07|8vu}ZbJ7+Z!HO406NGRaMqI)= zke=kS+j%~=D$`5Xp{KesqN4Okl(y4N6+#>EZVMJz#7p4?zDdI$F%y-&ho>Mn*<3JW=jD+!R&TFqpEo z3l&{A)URkmt50^j4QTauA|7ZPW+L2f&z?SmP-ZDYd?B}zT z(S)yU^90_4X$3D4E6-0e-E~ z!|Gfq-ISTFt_}|=W3*XuPS;8xH%tiF0T_qU+<~Bx7$-wT@@$C(j*MO%h_pluRn-C~ z?ypd$1EzI?O;+7#u_qN+cBNBH==zKxL5_8I6NdAe5k%juiJ@MMJIo0mDX(B)o_azZ z{|UVk#wpedd}AyGPf6@Qj%oQKh7o3uob3>GOElfoQDg2nQXJt^Kt#S9K5$(lkFDf2 z@}f6~*KgYJ`T~=DgY`s+GWtE1bh+K<@nUwhSlp~QvymWACo&;1FTixjPmjRKZ?{vd z_yP^$4v3OK2{=&%l)A6XvRmsEh4B31rm?^Qq88anHFB;#`D8kIl9N$T3FBHdG z-N+Eh|D-l&d?YkehVcA{Cwt!sz_C19FIWFf+~*!Zvog3ftV%XgIn8}Wn zB<9IE#+3e{cCoVXp1Mop10Dg<8@N)mIIuvLq5PC><-Cs0Lw`;Odmh~K5bTqBrc3NJ z-v*~y4H}p{qWn9Qb09KgG*lt|+>+J=Zk{9FF0IE<2(LXb^DEWq`inRdCs7dp+t=K} zr_uq%!aX4NJiiCVmP4aiU5)g&OgONM2=%dZT{4BMLln)a?zdBs&mT8yT@%)7=h{z)2oDKISYPcTw{Ogj8{yI{g2o;%M#Pz z8=@gJ+!aeKZXXTATmiCojrGt_ld-WUP1+lhGMR`0s?!+U!heOpQiE6;xHMkG2)EE% zL=q3ygDNIG_eu%!D;d6{-i7H36^Y^;j1njbZVubnVcdO0^M}#}l(CNZ1IqS#GasTl za=FT^$B25w{QW(dmy5+2__!m45K9>w{mJnn2tF9%Co!Hf@g1v?iFqdc0auj3=jNu4 z$Dg{NW|?DMdJWP6>rbhqty>At6bKzvl>`|Y!DvCsP&)psyw1^_Unq_w@S=$x%>jUBV;G5DUs;o`rXBgO4I?P${?~iP$mIYcFdj3E1?lWAGGVkL&YU`l3ietiz z88dCfj2Sa#%$PA_#*7&=X3UszOqela#xY^WjAIxab;OK$Ifw7L_Br2c_r*T@X0L0@ z?sK|7eS1@l2X`+Rob!Hdvccz8gLjF+9V<8belQB_hrz!b zHoE73?g{_PZR_B$dSt`;zt~2NoWmU5N)1n*{%@}%$d7#dGo+J!^7|6e{=ZK}c5M}x}!|Hwhq;!{mAxVy&S z@dqPYtC9P;lm@>q7~LK%HFEF!;g?x&ADQkLIoB}q2+q-m0xdE)9~%npcg-z?0lLn)f1|PYOd>szn9r;*mFzA194dnE}y#^ok zZaTQ0cQAZrfzb=D2m9%R5Ag=K7$06y@%8BU(E+y=Djh>+^$aqzA3L!!NU5GqS~5YH$hZ|M_kCIS$VUKOa0N{v?M|crF#k-0#|j;I&EfA(zWL}wc?S3Q z$PRBozyB=n3>NA}BM*3+e9ysWeq(=}<@v#l-$(KP>QA$MKXT4z@N4iXB}ZS$!DY^a z`_m70oCk||u>Bl45L#@e??^t}A&V{z;F&EWe-95j{9)wE(82YP zgPp(6jvCa^;O+J5!4TMK2g3kH-U%y%!`lDF&d=b$apX}i|C`P1V94y?2^531cI4BI z!4peo8yQhK7%VvQb?_!R7|Su3t>?!^LH|G9wH~a47Y^qm z8W>z4JNV=1_s=IE+{Jxx?dObx3;0J4G>3nx4y-t8XAiC@Do!@TVAR6s=ZC+KTvt9g zU?15y-#N0=_45{k7a6(JRb-0ijvAcndSt4>sfC9oEHW7Ta`Z_f<1z-r&ZipO1aeyL@aniN?kw#@RACQVuc)QW+? z|7;!`YaW}lOl;Y}?)&e0;NFoV_B`a!!M)ESlVVE^{#$an!*@IUz{3uobo`|FQnBTh zS|$?M{gA^ZEp^aRd+fQ};d_tVNNoDs?mb{&`}|Kk>Y^h)9@v5Z(|(^ZJ8AUBWB)f_ z2KQMT+|Bjdzx>aGd&&hS3rq||Ze4uzpQ1FF6Hnob;UnKi+85+2;Wd0Y+9&ZIzC6Ay z9=mP$y362u;W>N-{BXR6kIm;Pc{ys9j zGWb2R94h!CqZ$Z&h_~?h@UQW}ox}de`uQD?;S1A#+Cg9={b%s8e&)bS__DNL6mQ{U z%QKFLYQxuEkoN2234Cn+x50CGjP`rt8J6c*KZoHZd@Prf@es>l?B}{*)WdFUjx6%kn4jiu`4~Dt{NR$v?;I^6y4{VxWE0Fu}3;|C9C&wV!ryGsVdQ z;XqR!##{1*Mtx$SqyCq{yYe;go_u4xFW(Li96f&i_rinnL-CNjfQRMh;1T)dqaF^l z_4D40cjWitUHKDuPyRCAm%oPxb{}7FT|6lN5f8~HpJJF}SUv+Dkz&&zMc3-UT%ls|))@j{lx(g4=AHzfP7x1vWjYs64;ZgZ_cuf8m z9+yuX+_`!r--LX2JSkroPsx|X)A9tKk#B-$<=f*q`QCV5ei&YmpNJRb=i(*#6?j=* z#Vhjr@T&Ytye5AIugl-Z8}cvkru-+oC7)udVUBHi2=B<}#=G*x@Sc1{yf0q|59~R< z{x`>i@}2RJd_O!a&*KsKsd!X=0UncIgU98!;|cl0qdqav)cf`Gv`?!2+jvU;8U3`h zz5O0H8 zY=7@yJS0B>56jQNBl0WoC_dPY9Gz~#7nMJNFD8EmUjiTO#*9v{<4egu#$)nt@a6Ea ztCw8}YO8u^jKg&yhcYm(>3&cv=1dUXg!^*YLs3*GH#c@fJR|e-fOU^-lfFig)D; z;yw8?cwe3v^>84q?Zc+D4;;Jb===?4Z*)YTv~>_*gDK;az-eIZT*#nAcbGzvDgi6T$oV*mh&7Q4a@7%4Jo& zEZ>lR0>>>nT9;$8LO-N@r3+oJSl&T{)6hj zgNNk*#l!O7@rZny!Q**Gj_;!KFdmaHjK}56;R*R#cv7CiQ}Uhhw0wU&BR_i7Ck9el zZbdwekJZB^ct-7Sz_aqZ@tnMY=jAWs1^N4U5g%K>{)3m)zK@sXQw^TvGV;Dwk>+X%OBR?EZ;$!R4$#_crpO2^I*WelaJPx$(zs&__>1zB@s|92{0;dvcw2ru-oeM_ z|6#llTP6Y#O)`nmC`T|#+edf=pPJ?QUp%JvzvBt@ zKkZDzypn1^JD$eJmj5F7cJk%%3_kXAt&L~#vAWtE-ya|Qc{6w(ANzR^#Ea_xSiFRf z&F7h;9u5?>Jul-W`Av9PelK2;KZ#f6ExacGaMTC;d(7v^`{>B@HJ-R?{QJ~zcnY6} z_S4Kf%qw%-`1WBuhrhPju>VEyse>UF1-xzFPMy4z9`b^{7--5UBgIGRwJh8y|_Rr!e ze7?bc-pKSOo}V=|OlpM{6zm*Ell&3IIPA0CrGg~#Qu;tBajcvAi~o|6BD zr{z-*9$7k)S4KV?o|P|z=j6-cdHI@nLB1JYl<$O>|>BC*u+M`FK=*4IY!qK>;|clmcvAj0o|1ot zr{&+{8Tn*`N6C&JN6TlxbMm=Ieek}i<+eCp#mDw5R>E(?$My%-$8X2S_BXb{>-gCF z-5z*T?GMFUYJVc$!N*?rJiLpK)#p`sPkt-jmp_OHmKnZJjrH>!9+ba{hvc8&VfnXs z1RtCKzwr6w(+zg)N7lRf<#XZ-$QQ+<_}Kifh{xpX;&J(wctXA_o|GSir|_{lId0Sk z=K)l2XVE^belDYZLG5qGi}L&MlKd&WEPoZR$UnlX@~=mIFg{lE`5WzPYCrYhnav~X zU0psK-jFYZH|5KY`out1ue&Cm({kR7ep>2hC%i4+5AVp2!n^X*@t*u*yf42V59~93 zdwv%lls}G#7*dK2Owg`x!6bW9!|Ng8}HH?`vv56JCaqeM`Tgm6Z)AA_3 zqkIKCBVPyKL%s!`m1pq%nM@5N7%KZzIRE&Ocxhj>Z; z6@HQYSG+6_&NIyGO8KmKMZO?@gM1mhDo^0I$v4Go@*VMe<@@4wc^-d6ej478UxYs` zzYcH8Yxs-u$MBZ?Mf?rf<<{jqssr>JFS02H?k}rk#K@-UN*>44^7ZhHd@DRF-wn^nb9i2UJYJBWjThyY<0W|&FU#-8 zEAprDs{A#)Chy>N`G4_-{CB)5pJu*cjxBi@Z_5|PJM!i5u6!-LCr{yh`ObJ?`SHiI z`{P0R(RfH+#KZDS@QC~dJSx8%kI5T&T>dhikiU;7<^RD`@;;uHPc{EAuZ(P$Kp--nRrWH#@q6n@Q(amyeoea@5x(uU;ZH;SaE#)e}xC-zv3ZzaG_yd zVfn0hM7|&%l`n(GCcf^zOeesk$kEi9Q;TicwcvgNLo|D(`y!i>pbMAw4X-pzr&}M2NoIT6~f1k zBc{j0YCjh~yL_=x4+mnJpE&I!v|ojvcYQpAkDZ6y8qeWl^RqjimmiE5 zcwfFb9$0Doc77K;C_iA-!-2T!x8}YRK9y}v|0?*1{!E^Et@Vxv> zydeJtFUtQm7_Kw&c}+<^3tpBlfLG*`@Tz=`QJ)wHY58x0hvhrq5&1rNRDL8LlaCAo z9{F4&F24{@$gjnd@;mXA{82nDe*w?P-@&u;f8#m%k9b}_#bEf;$j?=f&xjZ0^Wr7> zl6YCZ3SN}c0bZjOujB2mv4zD>UY7rcSL9PKFAk5}by<2CtbcwPQI-jGkW)G)86d7sosDmGG{7eY_{% z8t=<@#{;X5um6Mbp!@_pBtHia%dfyA@>}qz`~f^Be+G}sU&j;jkMX4Z8$2cd15eAR zU3!>fMm{^9l`n$la{ctm~+9+f|U$K=o8arx_bLjEzHlz)S#kJTG4xFUU8?i}GFYlKcR?EI$UX$j`v5@=NiW{6@SkzXxx~pTL{)SMZkn1H3K& z67R@=!MpOmtvJl9C!YoH%NM`{YmTq~NqA7c1|E`cf`{ch;1T&gcvOBQ9+RJn$K@B| z3Hh~nQhq0%l0S;4pD#&NVi}HE#l6*B6EDg?#Y^(<@v?mKm4~@hQbtdGWY>DLf%x z4NuB9##8bg@U(niJR?5}&&rEG8Slt9z`OD^ z-jnZ*_vJ_6fsp|Q|Ks!DQ}LkuB0MC&9uLd!#v}44@Tk0n$K)U3aePWXr~WUVz(e>S zcuM_DTw|C^T0SS9kuQd4`Lu~)E*<$CcvrqC-jm1izI=T=u-^DO-v$rL_ryc;!|<^D zWIQ6j0FTPA#bfds9+y9kC*&{VN%;qOO8ym|mj8xlyCHWqBS$-&9k)MQD<>%uyc?GY_@5CGO$MB~7CA=knA8*UQ#5?j|@veO8wT8L$ zySN;XwllSqy{BP?Fa|tBJ*Z-_|P`(fzk}rpc+`4)Io zzAGM+=kU1v1Uw->7f;Hs!c+3w@U;A4JR|=no|V6Y=j2^HFaH@Y$fsI&m`hPU3to~h zh?nKd;uZN?cvZeRUXy3=y8Ix#AwM2(%Fn@D@+&DxZYcc~0cwl4-j(!aUF2RHH8}X3*UOX&+3XjNN!=v(#@tC}a$K`+F z3HkIJ5A#aO=f+d=CGfO-6+9!~5YNiD!*lX1o|hkq7v!hmMft^eNqz%fmfwR{f8gI#e$J_F0Hyh^C zkXf`Qvy({xY7F ze}JdtU*T!_Z+J#NO=_4+Rz5qPlSlEqd_}wjUx@tVAV*X8Ho4f)l0Q+_+%l0SmCm z{srEX_wl~`Z(9y?38co?|EzdWz7QUgFNcTaYvU357I;*?D;|^Q@VNX0JRv_9Ps*>t zQ}WyJwEST_BmXC!mA`}M3{~0gHr`l?mOHn=xUXm|}m*vai75Q3tRlYf1lV|X{ z{2;s`KOS$&&%s;rEAh7cR=gvB2=B_D$9wWN-k1Ly4{ScZ{(r)Q@+r3-<`R<6jECh5 z;1T&UcvQY79+Ri=xO^8pAwLjL%8$cS^0V=@{0clH{|BCxKZxh#O*}7u8!yN|$BXhG z@sfPPHp5)X@|p08e15ztpM=-s3A`@f3~$JH#+&j3@Rs~oye&Tq@5nF5yYgG`p1h9t z<^Qe z2G7XP#Iy3t@SMDg=j9LJ1^KghQT`@gl7EVq<=^8K`Q+(gE>-!AcugL`>++@XhJ1Cr zDc=Nd$#=xt^8N6R{Aj!@KLhW{%XnXYGaeYZVbow6JO6n<9+W?WhvaYIVfiO`ME)Hf zl~1nO@54*- zr}47@>TGR zd_z1d-ww~ovv^*9Bwmo8h8N`*<0bhGcv*fAUXeeESLLtbHF*cG%fG=J@;~vWe7YTn zxwPbS;cfZict^f6-j#2F_vC53FW(yvY(2jIkHCZSQ}K}eB0Ma=9*@ZH#-s8l@R+=X z$K@a43Hg8Vr2G#&C7-y{FqgD^PCO%D4A06}!gKN@o|kWn7vy{4Mfu@)Nq!1mmS2ch zf8gI#e$J_F0cOK@_kf_cwFrt#}o3G@ud6%JSG1MPs@M9GxBM6 z8Rn9e&yMHhQ9LhS5iiKs!;A8*@sfNGyevNyugFirtMc>ln!JM7<#*x@`D1uf{u17j zzmK=&U*aA4uXtBJb!M1LPaekm@?f5CI|;I6}5^77g6f_!1TC|@2g$=AWl@-6X- zd^fx*KNzpc3wT|A9^Q~&jW^}D<1P6kcw7Dg-jTnHcjaH;J$WDR%m239Fqc4jeErXg z2jvUlA^CE6SiUwMk#B)V<-6iBc@B@uPrwuMbMd77Dm*2>4NuD-#xwGN;#v7ScuwBM z^YWkZf_$pohq)Bxv*0E9f_PcJEMAeXg;(X9<288(uged@8}j4vru-bdCBG7H%WuUy z@`v!Q{CT`5Z{vOWzwy9!hzXfl}>-Zb;=kT`tE&P4?XLv{c z1OBOeioJ%pbmf1?zmm^~_vA7B2l*O!U%n~+yL=}+u>Ej-AIo=te1iNKJSaaCpD4c! z56P?e%<>2Du>4tkF8P~yME)thp!|D0DxZAsVJ=I^XT)Rj2)>+rX*@1p9bZ+x37(Me zh_55x4^PUE#y6Iqfv4nUd@K3Qcv^lxzN7pZJR^St-$VWho|S)x?=PP$JIo~~58;Q& z=f(5#rSN0rtKkLt#`r1n9q^)jU;J$OQFuvS#4nOxikIa#;aAG-jmnxALNhYefi7y@A41uz>bTL9@vfT|9^!~kpG4U z<VHdv^-N%^n%#`38T8s?Ichw-iCi{NSb3iyulb@7aRD|`?6?s!&y2)@7k zL_8-i;fKku!SnJv@MGnV;syDO_$l)D@S^-b_}TJb@RB^38|HG6d^Wr+Ul_kqzC2!$ zuY=zp-x9CNcf)U!AB@-J1^iz5d3arZHU5bFcDx~f1b852U_x3@V0zGydz&0@5}qNyp9*;&*3HcTXZ_8K1JMxY3u6zf)C*K$E%a6hX zyNs{@A|8}qiihMk;bHlGctrj*9+kh2$K?OQ{1Ut@zY(v<@5QU~r|_EmHM}nW7;ngXcvJot-jYv$ z)G)8Md~UoWUjpySSHXMo4e`EwJ3NpXU;kM=C_fSp$xp+>@{93^{02NKzXy-WpTy(x zSMh|rgD2(R;3@f^cv?Q)(ZgIa^11M=d~rM{Um4HKH^2+>G+vbNjhEy{;AQ!#ctw5@ zUX@>u*W`EOb@>x`L*Bxh@{jPA{J(fx{s-QXPdsLrOIJQ8-jgqe_vI_$fnCSfe-aPM zx5Y#9z3{O7a6BSE1&_)v#AEX7@VNXgJRxu3N%<>yO8z09mVb?B#7v<~YCHXdZS-vM;kspRvEFVC7zT2is$81A3w~cAP?h3`675pz5-sBuZvgYTj5pt?s!dp z2ws<;h&SXVyeYp1Z^`e#+ww>8j{HTuD}N8~$^V1*<-g#8-N)B|@PuJ5LHTTWNWL&0 zmM@P-+)UkhCGKif&9Xznd`1Df#hu zT7C|mkza{t<+tKF`9pYK{ybigxACI<-*`#>6JC~2dD1YKihO3gDqjGv$(O zJcT#qyWlPPfp}Yf9Nv+ijd$f&;63?2@V@*(Jh12Z`fuVv`P+C%{y83&|ADxcz%VJtX~;u(Q$8== zk}rj~<*VTx`Nnuxz60Kq?~C{4N8y3J$Jc)m56UmaL-L#Ou>3weB7Yi>%3sH0@_*rR z`L}pN9yooNOHw`qo|4alr{zoH8TqPsR=yFQlW&jb<@?|Tc^)syPsdC0OYpM%M!X`w z7q7~n!fW!^@VfkCydm%5P5EDVOFn&Zm{(gqH{Ovifp_Jr;63?|Wb|12Jq zABl(Lr{Q7w#dt)110I#%gU94g;&J(_ctYO6lk#uyl>ARTEuZd;VJ;c@TzFQ#IG&TQ zjOXPW;01XaFUt4EOY$S|viwxMBEJZ)%CE<3^1Jc6{0Y1vZ{bb(M|eyAU%V~<1MkQu zo;l2=E1whZ$rr=>@|EzwKI7{@i3jD|;vxB7cvyZo9+97dN97mdG5K|PTz(gxkT>w8 z{1rSU{}4~hzs582-|?(`+Ovkagzh-xIIM55ueSlkuAT z0=zE27H`OFcvJp3-jctJx8)z;9r;&ySN$sfbh@|W<8{Czwt{}Rv1f5r3i zsm~eaQjmx7qI?m&Bwqn9%h$y#@~!Zye0RJiKLoGKPsAJY65fHTg04z49~hy8JTy5qTAF$REI;mOqO( ztKH3rQtNng>;K1?Q zjid3P{0uxKFXLhP&3Ht9KOU7ogU94=;BomUctZXio|I2^@i3Q^JcOs^^Wqu#Qg~Lr z8lIDHjOXP$;05`9%9+Kzru>5p9BEJNW%5TJD@_X^P{3$#k ze+^H{KgLt?9-fx}g=gf`UpmYyE1w(B$(O+M@>TGHd_%k_-wrRyvv^s4Bwmr9hF9em z<2CsWcwK%E-jF|uH|4M5EqMoT%fG=p@;~vee7f>5m!5nsyf0rI59G$z|H^n!z5yPR zr}40SZ#*JD0*}g1#bfe|@VNYXJR!duPs*RbQ}Py`mVbn2V;!8`H~@vi)9 zyeI!1@5`sXe3(n%;PLf82Og9!iihNJJS<-ykI1*dqw+oRnEWt2E)}=T)_6_62VR#S ziZ|pZ;Z6DZcuQWv+wwc{j{GsaD}M>^$=}EO@-OkgA>-@+S3D@6`pRK0A$b@N%NM~T z@)hu?d|f;y-wKb*cgGX*L-3^hL_8%g;c59bct(B)o|Qj}=j1QqdHH*ILH-}SDE|d7 z$%9u7b1BPb!z=QI@v3}zye3}kKldz z3wYqr@%8^M9+ZE9hva=cEdSfp!(1ZrS@EcRAv`8u4v)*%#uM@_@T7cKJSETJY556w zMt&}ym0yMD`d&){wOL3l@gJl>U`gZJcD;(ht8c;K+{_5Tnals}J$1l&^`Wwu>58`BEKJx%Adhw@;C6f{1ZGO z{|-;eC%a*oOG+NX)AD)ojC?6PD_;%I$v4LH@*VJkd|$jMKMF6&i+EXnDPED^gjePF z;WhcwcwPQF-jM$bZ_2;LTk^n-!(7_(8SsvL9=t1G67R`Z#ryJ&@W7Gd>wkMZDBlMU z$@6$vemWkJUxG*FH{vn*y?9*y6rPa3h9~78<0*L$Ps{(pGxF(g8s?Ri&yDBgOW=9= zDtJM@AzqYkhnM78yevNwugFistMZHSn*0X5F24tF$e+ZU@>lVeyo0yp-{2kjpLkb3 z-Oa;Xdh)sOK0YBZoEFD_m#>Tm^27CFto;Uf5Fh(|IgN+Zes6qc`4M&c;M*q^}h%nl&^q?P zwZ9!t<3aj=1kcD{z_apq@tph%JTLF#1^M4@ALde&&x)7i3*lw?a(G3)HeQu)f!E}_ z;&pirZ^%!;oAPtcKctpM|9+j_!$K;#iad`$$$PdDk^5gN8{2V+jzY@>LZ^g6nhwz;Ic|0#~;|2M@ z@uK`Eyd)rf# zP(BF{$rE^3z8M~o?~F&~2jDUJv3Oj57M_q_jwj`};3;_>Ps^XfGxE3ato$=PC;tJ@ z%cr<&m`g$acf2T{4=>4Mcv-#%UXgE#SLHk5HTnK{U49JSke`V+<(J_tc@=NVAHX~E zXYsE5O}r=n6z|Kw#{aP5u>Lm;Z)0yh{so?r_wls+Z;uRf$;fBLv+{-ToP0SvFJBuk$hW|Y@?G(gJcpO%C*T$N zxp-B66<(9yhS%i};|=*g@uvJ8ye04AZTZi5M?Te~!(6)ZS@52GLA)g_q^K;1&6ScvXHJUX!1V*X38>4f#Ltru;#?C2!(w`P+C$ z{yE;2|A_bG6CNMt(wEPK2TmPd|MTNP`6N6fPvBwsW_U!tGai*6fXC#=;&J&|ctUR9DR~-C%lF1J@+0u9{8T(AzX;FEug44WyYZs@3A`k4 z;br+pct!qSyej_#ugNDqJItjnpA&D$7sH$KmGG84iMQq3;vM;3cvpTn-jkn#_vIJj zfiuR}|8;m!eit5+H}J6h6+9yU5Rb~g#$)o|@wj~2=Z3i?>%2_3?~+ z8$2uD6VJ&H!}Ic!@q+vUyePjGFUf0oS^hX)k-v;r%oM`PFz-emfqMKZ3{QFW?FJyLeLm1)h@k@wEJJFAQ_Z$Y;f~@`do6 zd^tQXUmGvTx4?_?UGb7UhnM9i;1&6~cvXHCUX$O3*X0l64f#Luru-eeCGX;G`OkPq zKGloET)OgE@Sc1@yf0rC51hStU}Or8ehmcH!h`b7@sK=&hvf(15&7|WRDKQ~lV6F) z<+tJq`9pY8{yd(NxAC<6-*`s;6P}e%`O+|#oP1_HFJAyJ$d|#3@-^|2JcXC#yWkc1 zfp}GZ9A1;3jo0N@;0^gd@TUAhyd`hqZTZ`HNB%k9mH&wM~L?A2i|C3y%h%jd-_@}=;qd^Nl# z-x#mUcfcF+eetIJD7+;v;%)h*ct?H{-j&~n_vBCGefjHn;Joqm|1Uf!{}vC)1FsEp z3Cm}|Bl3ChsC-F0CSMhg%QwOk^6l}Yd>=d|&*N$N>3Bwd37(bTi09<@;(7T~ctQRe zUX*`~m*hRXEdL9y$ftjOm{(OkH(rx3f!F1$;0^hPcvHR|-jZkWw){xEBR>uA$}h%y z@*D8J{2n||8ejiU;z9YVcu3yC!}4$Ni2P4HDxdC+VJ+1Fy*^zB$aLE}s)` z$QQ$#@|EzGJc+mE+u|MhUU*l2INp<=g7@VY;(_zW*Z*~RP<|I4k~i?M{1rSR{}7ML zzs6(o-|@J7+P8+eB;<48N%^99N*>44^7ZkId>cF~-xJTt55x2FlktN50=y`{7B9(b zcv=29UXj0ySLGkzHThR~UH%*1kWcgWFqfu$cDyBz;%)hgct^e--j#2S_vCxvefgny z;DYh>e-a*)pO1&+6+A4z6OYIr!=v(-@R)cuu|*o|o^A7vzWFMfr(%NnXOs@@w#l{0_V-e-y9DU&QP3_wa`NKX_CA3*M3k z-x=o8md}QF7&}QHuhK>``FW>(kfCs1VyY3im5fV4lPY7LM^FU(kfC~VvlNxJ+x)) zqgwvY&w1bf>pGwLUDxlqu2(YB5-zAyM3?31`508pJhR4KTz+1%M#N*f!J^;^%55}|N``|h8Bk;WV2)s=^gBQdv$J@nk!i(bf;wAAJ zcv*ZlUJ-u-uZn++*TkE6UHlKcA->utZe5z<>*F2bTjHJKJK=%b{QbW-9uz+u4~eJn zu=qK6MEo*5Dt;p#6Tb&<5q}hqi$9Mi#OLEl@jBir{wpz+1GoG8e+xV)J_rwq$MLZEa6BS@G9DE_3y+D9$6Lg2z~ka~;R*5S zcv8HKw~D`xr^G+R)8b#_8S!87ta!Ii-MZw&*TwVVo8xWbF}xr?3~v`d3@?hGgqOs} z;$`tm@QV2LcvXBdUK20kb@Au$hWKlEQ~U$GL;NeeQ~Vb^aEHJDyME@@B`CfQ9ukk> zVeuXDi1?m(RQympCVnE`B7P!kAl?se7vBLditmA!#1FyC;wRu0@iBN+{35(2p2zFrcj684hw-NP zGkAyiJiJrvfdGXKjHt`O;ApQ^DF22?mZoP`)o8l$$ z?eMbrZg@rfAiOGm99|Po<8|>1@rL-dcvJj#yhD5{-YGs44;1|UKNk;*zk`Rw8+cg! zM?50_Hy#yV^GmlbG4Z~5i}<#9TzpqNA$}m96mP{_#m~S~;uqj)@oVsm_-%MrdrUOS$q?`BEAh?72gG~i6`*7__26Hd=%ak z|1aJlel^}Hek&fB?C<{v@u2vVcu2g0hsEE%V7vnABOYyk)3Opgc`nPUflHy^!ReUQvB|ZdCi|>bL#E-(W;-}&{ z@pJLK_!W4YcpF|2zaMWGe;hB0zlfK_7vN>_PwEPe@I5x*X67vl->>+qy_0dEzbhNr}z#na-i;u-Px z@vQilcuxFhJTKnmN4G9*;vu{s-VbjV-vKX*?}3-Z55ddgC*T$FF?dz{BD^M^$Lr#E z;tlbK@uv7Qc!&5ryi@!=JaE6i|G&V4;y>Xb@j!=Lm$3NSctm_NJSx6D9uwakZxKHj zkBc9VC&WkNN%1V+Dn0>EiQj>z#UH{m;!opQ@mKJi_`7&s{ByibyaO+Y|AV)Sul18# zucG*-cu9Oayez&OUJ*YCuZkas*TmC!UHn44A$~316u%ws5TA;7iqFIY?f(9siwDKu z!9(H=JS_er9ufZ=kBYDPvs;&#cwf9ld|Ny&zAK&(KM+rfx8kkhXW%LE3-Gk~HF!q+ zHasgn1<#2;h3CcR;BDeHydeG=-Y)(FUKC%2m&AMj;?|`sz6o9t-v+OW?}FFF6L?+x zSiB)V3U7-47w-_i8t)Xp6%Rb%@Bat!p!kz`NW6lF#oxvw;-BJC@#T0-d?nr@zQ(U^ zUE<;!;|cMt@uYYQ-YR|oo)SL>Pm7<9XT;CPv*K6bIq_TYy!Zonn|KK?h`)@ti@${z z#h2hE@nv{f{4cyB-s?BFE>-c3@S1oOuZs`G8{+%pP4T1g4)N3QPVw{bz=Qt&zY-6M zPr^gu?RZ%H2|ObH5*`&_h{wbi<1OM#@woU3JR!dN?`~a^;$gg1d@DR9J_JvT?}ul^ zkHWL!r{X#BbMd_R6?mI?8(t8vnQ zd_%k;J`it;?~Hed?~8YeABhL1`1^k(9uyykhs1MuSo~%@B7Pqp6@LtmiNAoih`))) z#TVfT@$c}YcqiT}-m}xKOGUIq@U#y!Z&bO+14a#4pF&#c#rk z;`ibu@fmnod^TPYe*>?Ie~j0}n|NLP54<71+MjM+n&Ru@9pYQ!o#H#;fvNuf-y08# zAC8B_Q+QbX96Ta^86FkC5s!)AgSUu3ipRyD#}nf7@uYYiZx#O*Pl^AIr^S1$aO;v0 z?}KN>`{OzB|KNG?z3?{iBwi3d1#cHW8!w7qikHME;$`u>@rw8(cvXBBUK6k4b@7kz zhWIylQ~Wo)L%jQ6Ze2RX*TVx3`TKthJSaX04~fU|u=sF1B7QO+6+a7)iI2xy#Badk z;&# zS@AFNocPapUcAfSZe7~MLwG^FAKos$16~y0122gmf|tclz$@Zo@T&MlcuhQy*TwI| z8{!Y+P4Q>&4)J+-r}%q#V4A=GzrcgyKj9(qz&~zX!s2V=5%JCNsQC7HOni5|Mf_kq zE`B_o5Fd>v#j|*;_yjy9eg~cwe+bWrKaFR_U%_+Y@8Ws!&+#_#4!j`#58f`mR-nuO z{;zkSD84CP65kFli|>Y4#1F!&;>Y1N@ibl+zYuSTUyC=zZ^t{tr{bOBGx0#t-~V&* zp!hp@NW6iE#ec*j;(y~&@in`+b%}}h#aqO;#pB|;;tBBs@uYYw-YR|uo)W(RPm5oJ zXT)#Av*J_mocL3CUVIMTCSJn};-BH|;y>U;@l|+9ymwc(E@kme@QV00cvXBCye6K& z>*B}a4e?QUQ~bYphxpZar}(XSV7kBmAH;*=PvRl*3LX}J8;^*8ibutl<1z7-c#HTN z-Q2pw#W%(i;#=cM@fN&Q`~W;9ehi)#KON7ApO0t7uflWUx8Qm42k(Q#24c&;!E+k_zFBBzIqS0E=ln)-YUKoo)RB|r^WZf zGvY_#S@BcxocOtTUi=EYO}q^+h~JO5i$9JR#b3lr;tTMy_$PQp{Cm7A{^!3Q4m8Fj zz0X?)SNp$p2?v_u8{!?}1ON5jftu9c8Lx})i#NoN#GB$H@ec8Ec&B&{4?OChM>pd^ z@%!+Q_+xlj{O?o#*PpJv1F>tj{I_o{d``80%K0vbZuPHp#gE0m8t%Lsp2ok$H^48# z=N;nLnT!YW{&By;8{!*`aN~)-ySb~}#r;nlFJ9-@xdP8$?|a+7zHuOsbNeJW!`1nS zeDVfAKVYOgZreoP2jgEH>E_j)`Hsb3#(UwH;(?kQe;B_RPf|aEFU03dolo(MGx%z=gzVC^b#1F;C9^?8mk$IhrH_6+4=a}E> z*Z<>R|6jKvZoS6u?FyZGx*JdJHa~wZepRbGuIaY*3P6wVRZyp%u#$OZ<;Z^aS@zN~+xU=!hY~R1bQ!n`b zCtkp9Je!~E#?$hmpFb7P;FiAXCa=%t%j=@K9KCZ(!&%j&B zTb(6%>Q%qa6aRJfSsS6xsY-G z;`=x}@~iJt@mAcP2L@(coxtyYel*??f7bjDKi};lS0~@;`w%?+r|%cz#TC96@#$?H~74yhD5n9{tDVdo!LL zFLCu>8}DBy<9P9Ym$&=QaJ+>&HqJ@-QtE6&{Ri<@$-j-Sf2lj}f5f72Q}Nt7{`20m=G2Lh zUxYWh`}KFZ)Xgifn(vq3;hw%ffER-mH z;{kZ6pFgkj@My&MeXn+Pf}8t(4u1HJZv1;Po*W)0Z{v9xuk`op{D6no=lPsEyI$jt z8yeu}Kg06_eg6@Er_GJ$AnF9Jb#*G_Z9D_=qH zY`jR`j=R|eH=ZBJufaG+;Gw8rKZp0b#f{Tm|4zcg8@j&5d0jIFPf*8>y8th3?blz7 z2guv?e;Hoo{U7^z`M>bCZTvdhcmcP*g|BzxDU0uc=UV(aSK_H%e4l|AaI60UesjUCZ+G^?pH^pA zzs~tLxbXyc^ZjbPvAge2;>A6D{~J#Z^L_Y4S3fU)8Q$E>&)chc=sFKcuM>F$K3(X?&tfScx0ace03e(w!fdBj92l9 z8)jfSe*H8z9((=!JYN6Qop*LWU4S>G&SE@xfIF_$Uxr7;x4OyAH*>H*&Y^fi{CK>2 zh@Za{Z#&fY3f^&;@4w(JN#DEO%y@9SU*C>*Nd8m2mHIZ{AMmXBdTp+LL3}bEKhhu1 zbbR<|w=O+cujlcj@YV38cvI^1ndFY!^-))+5BULjJ#QaI`55;`=l_ znex4c$42=6CmtH<`;gmQ{SNUI9z4y@XYdoBb?a#N-z)Gqd0WRx_+8|Ou&soi1NI)6egS-#W|n+5KxEa4B9I>*rs_Yx4Z?7G4)$f;Yt9EV%Jx&-LqnhsV$J zeavK+kNns7i}9xT^_IWD<*g5Q;c@Zp@1ma<`t>isv*P!dXZ`#l^NV~Rc(cyT;`(Oa-qS{a1m&p?E~O7ipZO#`726 zF8MVcaQ(cLynEUY1p49?$!~{0K;C_NClJ^JZ%F=7JaC=g&y(;4)Y+MFo{dK&e;MB8 zHP?q7$lr{|C4WD@BYF2KBM>OzDalvx!^!VX{de%37vM2t0C@%QtxaI1R6@>DOsmotymQ_L|DPa2w|k zJa)66zYhK7*Y`CIYYExtd8mu~fa37#qVzS_gCe#bq&?}OLw^?e*( zxZn4?@Zf{Kzl=AS+(|o@Gk00#zzwNhvMOT0C zx18V4H_l^tmb{H;FZ}Z*F5ip%5qLrJr{YVOx%|7s-Nrp1FH8O!{KehfmHCvzT>cKc zF8OJA-y2+hhc?;ctt-jlYZYc21yyzb}5v zBX0b8J~wFBjR){fzOQQjBp!Ujf1hU#9u|KGkBWbew}}6QC-9(aPoT@AZk$QUuY;$= zH^(#LgYcaAUU-}M;dqtr`&%Efc=}Gi50M#eoW1z`=U~R4!1u)M{&F0i!tJ?w41Nb5 zqs}GxYq-5{JQ3fG&#Brz>HU}+&z`vTb5r~{`CRMjC6_PAehA~Ch5me_ctpGfkKuOQ z1l}U~Ry-k|##_a+cv?Jv&bXiPyzD@uqn2N!R~Q@h~2I zOZtzuE%3bsZx_D+kMg+)+s~ik>F(}6(v$tR<5O-twO-CGKMZe(563&iPr(Cg_;sGf zL%8kRc~*zdf!Ooa`*=m_e}(sv`n$|@<8R3G%z=1|^z%5pL-M2Xz*_$JC*TFXKOW#Y zYW~yixaoadK0-e~Hhc!4_hd7JTgQS$3P=jw;# zIcE@FnZk{faqcA^WnRPa^a}UNqCVdJ2P5(3R9B};opbThL(U8Mm3UR=dn=xJ*yZCK zcM9J2Jy%Zr89YDD<*om(;>qPMpCkW~<%=$F^_zI@2bZ`0ufT)PyS(lHHOj7^)#=VF z)bEQosS{2TgAm5cf<5BW9{`2vAhhOJfJlyHL#yAUj^H1kC z&N+DS33uE;cX!-(@x*h^lZ@v}tMiwC+~4r_fZHclzvnFaCcY6~?&k8=|84R7YJQzP z@LI(xjNRjVR%D4iFe|*pGV+f_POn= zbMf-}ZalV*SL3a--Ejl-=XSgy`DvDak$g8du`(Xqz_0&?@pyKj^9nu|Z;^d|nfY5TpCjLfm&G5n z{M&y1Y0Kj_&e!mocpVSdTpe5Y@9`*Z>$noH^mXgf#yHn{!S%VYmGcB1!5erh{vW(0 z>hdXkU#o-Ld3OvR*xKcjLeG@Qv#I@x(IcRr0NP$8zU3 z-!XXR2R}a^Z~4)=-5+nlJBRt>e*llfeSaEH;C9^C@%&G&zRmYz^Pio^ySND~#RGqk zr_ZY_&(H5vTs$!FW!KNZpDthT=Ki4tudk#IevtV;&g0ZS4-a?s*YO%Wx3`;boH_+O zM&8E%2%b6I*8Hqp*S8q$@L+}3(+Gj z-^V}ht>*GM#uxDL27dl)%Zsly*Nwk5?DFk%-FPmW=e%=~bNhU08=iiF{ljze6ud(| zr}PXS3c0*}&gykM$aT{0Qy=54b6vhlKbPS#@(KF?H=f1+w@m+jgvf>{N6u}WC*m!iyZ&T2?%9_Ag1#}%9G?Hud7k_&c<5{AHvXwr z|7&+#yZ%3ir^G+U8@S!yf5mIx`1MzN&GjengFmkrUSI0G!ua>W+kW-^7(B`AV(ZUn z%QxL|16^IAOYlz4uU7hSBc7JmanIs8JkEHkct!Gy@DAM8cNt#&-Hj*2asM{w^@Xi_ z=ylhh>aYIzx4`RvIJfZ(#zQNdTOanvqj;R-w&H1CKU$qJcu{;j9$4w>=eoK^+=S=; zcHT*U+VSi^|NA_DunYTy`S!=FU7g!? z`w%?K>tCyXGG4=T)XCt<9U?hA%lBXK z46lnV-($Y(Ly_0TcHiF+Z?5g?*m=D*URl?9F5td)!{h5YFW`sbbv%xrf`@q>ZS}|D zt<*Qa3eVwI=XN|!9rI~;me;xFv+x+NAI;yyGxEA}3Eqy|`u>PV!fw8{E?wSm{Y-7_ z+}3eDJhGAVIQwJ(o+WSfhvJ=^xO_F>z7E8boBH)nz?Uut(3|XA-0Jkl8~<_n zB>5qD2YK692jb!FUB24Q{RbynotSgG4xeLn206EVJ{iw*TpQ;Myo%d?co~lkc6Dq# z@8Yd^g?;ih9va6j$>rcyU=LLK-JU`00&3BOHpLTA~A^YM@-0B>Qw^PSnmyN+AXS+H%UI$%@ zcbw-Rx6SHIb8h=*3SKPv`DgJ8Zu$9mQ+zQVdeX1IMa}i6{aoL7#;bUOz9sMo>oMqMtJQWKffKGzSs9*c<24TAAy&qI=AtkjtAKfwm-9YivCoX?+tj8 zaoT+E!)uqj`XTn=lX&B5=W*6$9$uzD0p1V%$b6zd{_pYRjn3^lxeBk{?A+!Rde`-{ z*5*9J`1|AWTl_jh@XBq};e0<3@3_Oc_2)!9f2Z%~;5EjdW4>2feW`OhUKcOoLF!wb z*?7D7TX+lm%GTv`ygb>T@2{4Zebw_l*S92Y$K4ojif@nS*oSu9I38dh+WC8=)yM68 zAB7k26n(o054E!{-P}J+G@pXAzW1B6&#lf=cm|JC|5d#7kgH?sTgR(-occ@g2+YbXVW5pIhQ3@u7Gfx8oj!hadIpoP@`5J8lLKJm$~qD!lT9U*`_1 zPu`9@9dCWy&%c1@aLd1qH>qEtKVRT2+!rdGU%%mpe{`+`{@l@!;w|I{6B)YkOSb>LYu1=i% znjg8owc7 z`N-ADbAEk;Cl*m3|I>VlbF0(4?)s4Z+_{ajAKw0@^BjHn4<7l-_Y?8_V&@g|=in)h zYksBG`IJ24oPjqQ&g0a1+479j#{Vwff?NJ;ydAga{7!SmZ}~Moc714wZ-z&{cH^<* z4#M-e)!7%XO8!`@-*j~n%xerD|BgQ3m*TD8JGbjn8y+QZ<9Qwrkhk$HwEDQ^8+c)v zt8dqhU(A;~xBal%BG;b^ZuK|9!#}uuJI{gJ;pLy5w=%C`c-t?|t zJMLC^{K75&ol%0%vG0n1D4!cY7_W<;h%drvf8Z>96`xz?^a@;tXZf6GmVVxZKRwQU zPSSp^{C@m-+&-sz+2a2-%8F9s|NiI3|M~yD51+rr&8vlfn~Yz?IQ!v`;04@%?r%0; zkvdhpQ~X2xFg{;xzi;GQ{2<)=(}}-Jf7WF@t1ofmPxE_;gZPGcMtlIC#cjSj;U)3C z@TT~1JTk%W+evt<_?dW{_(gbCd;^f7VuEgt-I~#d3X!X+qZ!a@I7$*Il*r% zFTX$eSNsg}_Inq@pSnJ0BtH-@iVwCtbvRuD`{9@2|NrOI@R@Sl%keOux3~3r08fg~ z#b2O~?f>`iSMZ(KCtu+Wsq+i|JNYpA?w`3ntaZ5Szx{k-3m(4S-wy}i2a~s-hi%12 z;De}t9iEgr1^gEBy~t0;?~(e?;}xkhAAgFx{oHXU-YNN>4L9H95pKTrbGu>uEZlxR zI*G^l{n!!4c?$k4c{_hIcwT;=_T_k$yq(u=ctz^pk2j>w<9Lla_P*20mdEYqO1{U# z{N8NaZ-3&m_&wWpz1{e8*Pr)retashEnby6yW-!FkI|o#@c51X{uzsJaFpx6t=FY^ zR`L_^?aAAD_YA%pzCOo&4R1=F5AY;;+i&0Ck(>N+{)Qh<-md4Xec}4kO5WCSeLN*~ zw!}}D@gItxCw?+slR9VNSCF^!?mj$tvp@dF@H@#{e_q08;0MsRAMm);S%v>1$KBye z*N4@QcI#+AmoyA7Nu9&+^(Fsbyeavs@y#WFJ06kW(>@j7p1j>Jp27cv+s|=-h__0e zukjPe+x_Bq{4}ZG^DEbf(YW2e!gy8cZ-q}HZ~L$Xzgy}Yh`)kcpO42|}4lK4OP#>e{C=g`-#&rQkq z!-tZ$>tPH(0Jq=IxfdSc_n+AHFo|D8eiP1%5%_IV=Ulv1>fDaEiI?%$sIwmR=i?va zd*h4ni2R=a@9<6dJ^wb}6?iN82z6Hf#`P^L9>xn&e=Gbb>f3#-1s{dmee*y(%HJ2T zezxM1rOq{YO7geikCM0FkMR&b7q|M);svSmD!zie?c3k*n&i8G>-w|yac;lab#-Ta z0B+a+{qY!XKks=g9>2psFGk^~k+*$yA)b}|wfN=a?e7%aiI>RR`aX=;#Gk=C#OL85 zevZ}Zyobkd>+=`*LdIj)lQo;{E6Ml87n8SrzCHdUZqFwt;3cUu1|PuRiLiCN3vWt( zI=(A;dwuadelR|e^Ws}PQt;R7cYGXqTlY=BbMtK_Z++ViA5Y${=X>BasecGQlf2z8 zPQruocPPfb;;JN+fvt`ez^6a4?YmL z*Ixth)_eSYemveLJ{mukIvdfqi}4I@_o*B3s?@m)e@f~+ioc25>#Wc4z`bsqwq8Hr z5!}{g72f?M*Pk%sj4k6lC2!}~Fgz#q55wEVPr@tWWATRgC3ryo?#cD|8z;MQ+WqAP z{7>BW)k3^Y>MX`v`1>q&omr0Wja&V{@xcB5y05vM{z!f^{A}F%wj7 z;dn;!C*${!xA))9!Dr*Vdl{IDFUSA?>pr|g>d(WY@^@su#8cuw<9|`#?#tbNaQ)eU zzw=_x+kNnw)aj3JLEic{7!UIIYivL7gGX`O&qv^6<+!KfS*d>mo{~Cu;qBz@{`)9i zll=2|@Iil{e2d4$`~2wolNIlem&B8JQ~VUXRsLSj*?61y-FQ{}5j-G&$LAxwMf@9l zCjGH}^#`7j{AwL;zD4o%@j2A7^&NR2B( z{>k;fAa%CJ%TlKWzlS=(fcrWD?~poU@X$0jFB|7Y_{Y@gNuBHQGBKIlkfAh>rX=R{qYURTm8ZKP~3ix>wb7n>KuiS zCvVSzr{g!_cKyEq?~ppz;J1;t>(Wd-G2Nf=2GVcyh7fda~9%t z$uGe_ll%{O=rMmE{*C`F`L%y@>#`|-m(BKP1n-aA_jd;2IjO%Fo+5AiB!id9TOV?G zUFx*qp~wCHx8pI~`u`T5mi(u9L-M_Uck>ND;n(krx8PQPJG{=kY=0hsXCyxo|B}32 z_s+wA!|gtGH6AGW>vbExuKeA)m+%(y*3Y-_R`E~qtoRRjL5}-3ej&%T=i9aaaDA&u zz8`)IdD}lRJou#F=Qth}PvQyj6rL8(;Cb;JUKDS`tK#i=6Ss9Q;ZHL!Td$At$W#8j zzQaEvZ}0!Fz_aA7ey>i~&jxwhS24UK`8d8*`aA+}%6P`%!I}OzufU_?lkkN219)2e zNxVD1&)@dRT)aZw=KC(*kG%EsFT7LoYy9c@vpsoxecBg~J?+nHJ3J}AJD$O9Jcr;# z$)AW0iKimV*FV0_P)?^ zd@MeYI-9L<{k#RY>(Y*Rhtwa2PbF{9IY~VFtedaRD}_Hp-uCmgcm=oD#dqRmsWS~9 zc&2+kv3|aXr=RnW`z1bvyuIJ`E8Z^o?ti&H>?8U0@w(&(;7RgZ(9e;0sO-jL*Wq*V znD~|Wxzw@egj?|@dF$sCyp8-$9QSEFGRyD7Jp2{%wm-kYTglrvf5)>@XSJ2CKW|XS z`acL?gxmV=jW?yv5%|~S?Rt0t9(mp$|F!rJQhx^CD)|@iKgrwvT!^=kxA`u?OXAD% zn)oWbLwwCuu0NsK{pGo%nmNHm^PKy3{!o?-V}?A3+^EkIu$xFZ=Vo44)~- zeHst)_hfDS^YEzn`*;Gk{rnZakotCCTmOF_+Wf!IXD-LB&jazY)ER>RN#4$j6y71@ z$>5vRM-0rjA z;934Iu=W3U{6zBB|E;>ZzLg|D6hD`|z27(jZ%Td~eudP35Rc9C`}q`p4|%(;evGFj z{~bP!ygfhn>*mJaF8Lkt*U8&;X*gadZ|B7+coUBY+}Any2h_3mIdk|D+|HLt_)^^F z^&tK`Zr|^liAP`c`|}FE_Bglh_B!z^e0|*d@Ecx`Iz76({=~@J^$LAOD&7t>o=F^H#haxBJ~xJodU@=V^S7A!eiydl0F-XXp_9#|m#$3x;L;t}z&cuage9v7d8C&llylKZxt3 zJy#rx7bSlZo+NLdM?D*FNd7YXEb{ie@wnBgx$)b0Uc&R@Z{b%`$6iOT#4F_OzSDaR z*Pq)ZzbPJm$3O1&cv5^1yht5;-a8D>Oa5fMM7}5Yog7{ve;NaA!=pd@<7vmAqmI3< zd=jtX_I;%#c#jL+{m!1Fe!vS-|8IOF@^&BGx;NKV$?t-1P2Qg0M&Q9;{PB;&hmg1D zqC4<7ZqGl{@T}B%4nLl}Jtr^4OOpQ!Kaafaldacu^J+?d7yMeOe-?f}ZtpK$hPVFa z&-W($N%FQ{&*N>9e*>Q@Uwb~h4c{7fM-K#E$BR{5Iyj}8Zt>fl9g}m+0VR&8g z!|_=%uM{3y;g2VSzee8P-*^m9khlHtBAymsi08$Z;0-zM8tbx-f4T8k{Y~%|-0ol7 z;u*>BhPO%ncs#PwpYIsFeu>++wolsdR`NEUc04O}O85%u*t&dFO z?0H~09_;GoW#eCkr^wrVbMOYPKXLN5zWd@S@uTpZ_-S~%9QSx;M+xtSx@#VNZzx|E3OZ@>Gy8hI~cgF9{x&GU8&i;6a zzrSYvJO+>9wl1UajQ9okv(&f!GXYVH#vhY$-hd~2 z`t!OQpG)5QvjERaelcDr|NpNic!j*J?<%|@zUIcRKY^e>o=x%ZsNbFavjg5D`91OF z&zn)m~FL;Oj+LwpV%kiYZz4jvN! z9FK_qgvZ3Y^mYA@i?4$x#W%-O;)C#v_+EHU{BXQYd<5PuJ`OL5Ux7#YdzW@!n1sh< z-JMl1oC)DZ(H*@`o;#NP5C&Z(88n<<9!QW+G zb{!s#w@aOi@Ct6%jqC6bf49`?PsW2+yX&6aHy^>*!QHEaK;SJrK^>ddr+8ZY2Rtvw zU9X?(Per^xzB9+Qbsvl;aC;sbiFZn!bMbNH?e*zoJhryK-=^c^$=iMWW4vAR-{F&_ z{tCP<`Cbt>-+Rd0>#0rfP{$@BASRa3!_u{SOtxg?JOTLL`$=mxF8~1nfYA0`Xw!te>XIK1Y>e%{@#ycgS$7AdJ z<1gSdsbjA%Uc}oazYwpBFTr1=j@`dD*wW1_w1GRW?el?nM0^My6WO?)%PY4`WV zcn5i#*D`z?$*(ui^)1@h_097A@gd~x`xk@pl;rot4<~Qu*U|WCxV?TEh1aCc1^5Nz zd%9&06!74t{y3k*Eh zX#5!5exJZ7{4(6u{X#qy@#i%GZzFH_!2*6SZa-Ht9WO|oS@@SDlobKuczkhlBzGk9|_^aq$oEm#Aa+skOIp^UacvFyDT7L3~HNEItgcix0;;#ZSS* z{r&NugD1r=$MfPh;{$JY>uBd)JH8vfJM(%6@02=Swsn1oZRw9^5S|tvfwzl~!|UQ# z;1@C;dmVHOp2O|@oq|uq?Q;%KrOpTV2e|7}Ag~J0Nu4#fbA4Dz-pgA?nmF@Yv1C= zX~$iGr=$M7dhOumdl-4U?{9*S#_f7|1l~>^TbGe|Mf_a6A$}zu*xFy;Tkr^O>-Zpk z3*)i-=}f#;>b!zyrOtbJojUeB|0n(pZlBlfwWI4#Q|fGt|3=>KJKN%sZTvp(h6itT z>tfgEF?g%wFUI?lxBL4I_%`DA;8m$J18*U3uPa}~1KaxjUx-I=>;Dq`D(cwl;`L*$ zKdq7Pr@^-$@!$*?0b@>2K?BI{*Ydk0Z8(tRg@gLWpv#4+9%Le!rxLwcp!#kwT z(fD-ocHbX`zl7WM^Flneqd)!$`1|DT`zb}dAo+LjMda;z^##5Zx1US;1rNsj<96H0 z^`ZCeZryFa^}+k$dvPDx8;?t!Bk&Y?Ti;9ZtmJRRGvuvrFXAQ1FT|VTOYm!`WA7J* z2XoyRkOu=L7L7dE1}I;Z5<;cNkBVQ1C&VY?Y4PcJUVIi_ z6rYb*#XrWI;@{!Ho&53tiATj(-`Vv!A-)lw7T+4ri|>LL#Sg@*;>Y1l@zHp2us{Bb z@TmB8ctU(Ko)({u=f!8?Me+G~Rs3VTDgGV)F8k1~8!Pbm&i;PxHN^G5Df@O4{14o& z1KZ(Q>e&9=9WRIFoT*-fkzk|mEeC_V$6&~iV*8qH-yZwF+!7GyAAK#L^_2F#1Q}VaqvABQS z2k|s+{eKG2i_gW2Qs-T~A^rs(-pfDk&-ft5Z_nG^_Hg}4N`5_jDET1m9D?V`+xYj# zi{i)NRq;{yuJ^e9+xc=K9@*O;{{(#Rhg{xX_szmvB|jfOj=a6Uw+KHS{}1z8inmdx z9sdiD?cP;Z3RYBOaLM_c<`k%{Q{2--i&sA$hybY>u~*xA_jjwS{e+qsvdD|y%;H{EhgkMYEKCieGPaNRa{|kSXyshI_ zao5ir`7+}iinkl)@CI1<|GkJS{>%hZ@`1J#Ox&90zZ`X%GcvA9v;YX3T=aX~rJoyOyxg0Nw-;9r< zj(xwN9SgDQfE9KPWt_sh~Lb3?ERK|@f>*@|6};$Ry^z|+k8*K7czePK2Zh_AM1}lhbP5n;Cb;H-YMR|Z+hD82YYT@H{tpaYxR%2 z1)dg9;O*jL@u}3e>&&Hi=s3UrjrdFCZ6CgfCnUcRf0w+i`>%M8ysdBd1KqsZrA{CG zQ|j1#WEftTI>Yf!@l){R@&3Hd!Iw+@yYRf^XW+dB3-$jx`dXWaVQ z_xm=%izoQw+!k-(_ME&M-XZye@xj!$*GHG)(Gy*Lt8*jXB7QHP!0o(v3_qCq_P*jv zc#eE)m%zVYZ{hWm{CRzfkD`v9Uq9mK<8fCe5I)%T?P=Ve2e!r&C;Rnx!CxhB`{y7$ zM?OOR%v;&r1GsyhqvX6T8klj+e;WK6weR zNu4k8;AnrGzu+5D-=3?(hr9V^B_G90;w^Z8>e%&UB;F)%<2)CiOy16ytMJqqf1J1C z1>EXP!OP+^@w)gcc&F5V51-C>>~-{4c~FH8O`JUrI#+okwC^0t3w;7Rf}uNUx)_yW95d@(-yc{eY6KW91K zl<};>FClO1zVDH)Z#UrfzWvd70Us1_U!(B(xVS}(YH zZA<=bd>7oV{|$U!+{Uxv(Qf>saeFR};yK*DPr3{KAa1Xt55k|rBaG(~{3G0+_a@@s z;JcB(7vKCvH!pjC;BkB_+{W`V9yrI}|8L_5lDG4^fwxG$10PA=?#o*p!+9;Ff13wf*aE#4~r2cDJsJ&$$sEs1Z4 zZ~2m2moV#f5Z;vh@%ZlKZJ%F-N5;8%**I^-Gw}j>yPkZ4H>LhkJb0cz z&cE=7sAK0*-{ahTAI0r{v^}1cI(y)A$lG(mNW3KZbMZIH_h6sgf;T1qAYLPH_l1Rc z*z6fRq_|(tG(>%+x_JR zJn&yPe%lXs7Wz;|1{}@tXLl z_^H&l_o>du;}`n%uf{JWZ`ak?_+;G9qXl?L>MX{a;>++z*6;I5JdRtRw>Z)Dp(K77 z-V{F>k6h%>>umfL#&74v<#;Q3TlbsslGM2$Uq~H$j#_{>rOskJc(Fg8z)5aiaq$rT z8TEUz|2N08k{^VBBjY(4FOj!>bv9lRzYMR5--I{B@54L9AIAfixcS;~U&2G;Z{ZQ~ zPw^OT`{4(?llj_xq5sLQpJ~bOgtv?DgRfI@*8y9{ad>FFzg}11{mI+&KnWj;+jHO? zJSTPD!A~L|W_`P#;>KSlZ++{7S8>~)TjEXe!FcdeH-5|Si%0Pk$2|%!Fdlo}I~|{f z+kNK({6*aQFaduDxA7P7&vCnMOvjf?{&_qv<9`D$iZ8;u&vEN$^_SwSsBh=dO1vs{ zdZ%3fo4Ad?FW!mUKHLr;$Z_pCp%sr^=I{SB9v9EzN%1_M!tJ;P{1}dF`+osmko;o& z9P+k~YmIR8y#}}U-}>Rv%l-YoBmOXX`@U@)Pm#C%mc(;XCxw^AGx)32x96WL@D8az z2@mD`aXy77a2wBDyj}9|;&sV?fpC~n7n122d#!q=SZu0QsCwf1Rl zzQJq!ebNu#ioCrZjNx&~$MN0CTYnD6v*fLxBk+RwIJ_+NZ^sX$zFq$x#@ny+$6v+k z;>+;R^?rUOe&cJdKX$)Z^K{proaA@M%i@RN4^YSYd?nr?`CIVWcJoc&?2mI@yxV-& zXY0crc)R2e#n+R37OzYGAw1FMANLtNC;ky$7GH(;=eYL$m$gQ_c||Ar^*6(}BX8?| zFrJe93HYJp?e%^ZACB9)T!+`B&Sd;J^0xmgc<>fCPTLPPJc`@-+rUq!jy;ET8RO=g zlKeV&L40#Oc&p!^eek&W8F*IwI=m@98IRoN*Po8JiqFE^#OLE@GG9C2f5NNeZGUz- z)AgY)z7F0L-yH80AA|>QcjLGEd*Na6!||y22)spn9G(!r0&f+cgr~(Hz_a2{;(75o zctQLfyeR%TUKaleuZnjWOaH~!!JFcn^OHI35)rfw$mxevQMk?5iFD z_jM(HDQ-UpaVy>`b*5My@tJr5k5T^>{ATL+#NWs7!0mG#U*mV-c76LDugY;(JInRI zDaYLaf1Elt->vYOGM*OvdC4D$hwpUj5@Gzu;Vrn$cQn30>RgP!BYp#(mil+&dGSZ_ zqWEmQD*h(k#BH3P;9cKv_d8qnWq2>#?iZ`@aKZ1xnrFNIx8Qc&-We~7?}u+meS5$6 zJiPJ$=(_X3nd<)k|Hr6Bhg<&K{C4lRe4nqz*Zcf)yymm{ zIX~hNpZ|-`@_D`C8gI#Y`8k*4uFtQ=NBg`Fp7i-0_z|Cv$Ghy*O0f5dkKn=i>W|Iy zJlys9N<4zwI{5%k`}{CoGLb*tU+{tS$L@=YjnMpzz}v8I)W=<4&K3B})Z6}cJ3gBF zFgfFJ=RM7X?I$zv;<(-4K8v4;+w+8#_{DfJ@;Bjiaoc~=_%`}u^PItt;4V2?JmkwM zG*a`^7`OfBO#BL;SHnBtw*NH7Yd)8M+}l_^9-u!x@Ls<90r+U#=J_ssIPKbXVG{lz zZtwS>!XLx!I4;H)_!Atd@$1jPu7#)Nq;)y!OP`A zChjJ@1@mV2OLyR-yQrS8=Q|Vdt++kMn28_6Z&df41m5myL+0-PE7SwEI5ZrMuknzr+{${13YxmH&?B_IB;Rk!lTQi?2Jb0blzMr`VKY>@J{tLWLFV$N=f5w~R_IXgz zv6|;+daK_2EPNe)F8MX^12?GNex9fqUa61V?oT@4)$vi}^v37lx8XzZA~!3??nB1m z*W&k3KLgL;_W9tm_`zG0!#3uu#5+agHgB8oOZ&?0c%`i#xBcf3-UXjae}2M`;C7xC z8K-$})KB?#URS_-;rd<2sgB==*CM|uz68GlZ;$_q+xJd;;i3LYw)Ha@zZv;D;s<-!vyYNA{o$sIH7v84&w&Y~-S-8~~8n1bN3%7YV6F-jI zc&p)84OYJOvoYQYxAUtl-UYY$xemVpx9ilc_&~fE{U41F!JFU@;q&q0_-uS3ZtM9) zd<|~bwT<}8zMS{)jkwLvL3|Hx7lB(b#)J38Mk#Z6>oQk>TN%H2JeO2I$4I_j@$gahTq}y zo%nFv*2!o1UAWEL5BM0L|BFw+Z9fUzr}>|W+i|%Ne+swzmP_#Gefd}6i+%My@U^(D z=YjYexb0V?@OOMUQ}DgGt>;kt{hH^eqsp;< zR>9x$c?jQ$+d6E8e>hw@woba^ALCZvAJ5>{?p^p-cn8Kc39mOo`8NMg;Z5)gys=!2 zcO0pDJAc>XH{iCPzl-0B+kW^7J_Wbu1K;6K;&z|;7rqj=^Xs$+G|%gCJMNY7Ex3)V z0lpKrd2{g(ahtbp_$RpCxAen*!L2_d@qciehX?U8cWK^iy*-Jaja&U9yc%xzC$Hd_ z_FmB^LiucB?|9|5BaqCaniJIpqZpZ5ae1b2hK0ejwSK^QPJdDrvd0%`H zZpUQ={vvMcY9e0eZq0K|*7*~7f4nWe5Fa{P^$YQr@grm8cjE8h*Nv4=!S~^x;Un;G z@vh@kZ~gxR{}Q*K|0?sK=DFj&s<->$N_bz~uH*Ia>A0OQSKw2}E60v|SNu`j`V+yQ z!b{Nq;rO#Ye*pgmxAplr{+rJi;Ke4WKX#t3!_UC&eb!dID_)EKe2m|FpK|QyYmVTr z;C6jHiBEYz^|lX`o}_u6iQ9GUe0&jJmv-yoNAP<1<@i|>l^?>d!Q0~cUB|fvABtN} z6n_S{edd0AEnb`a$MDZ^J5OK0kK%S-zl8sS+ja15eCdPgp`F(s;jiFN(C#<*&`GMd zc2D5XPnO$tq10r}bFCS2TW=NdPal@sJlDZXJ|eG5yDjnBcn$n&JdE4@@Xh#8+~#>0 z{tRyKQ)2io+}6*d_#WKW&-3_x{A&8M7O%>`lX4xt1%Kf&jmz5o5dQ+Vb@(;j{c+Vd zB+q$|Be-^jvU>sk9+y3wz{t|BMZ4Le=Zu{G2e7ny-z>oNHzQTXSZ5{rG zcbKhl+4GPRQ#H@M@aD|Jx%icHR6h!@g|EYHTrKc7aXapv@wah1Uv9#;`}_`kH*V*} z1pHUr+MS6PnyY?ZNSEVupVSNs5O z?+c1QqITn8ye+lo8 z&%@7Iq51*%DtsJ15KrM5+^!dU@Y7Z*$F8SeSUzrlALGw>6WsRIqSH0cG5i|ZJqzE3 zcf)JoRaU9pj(9WtHoOzw0bhfM@!ojhq;kxM;N9^K)Q`hwN(8s&5)=MX*^za0MwUxc^9i_FkGe}lKfE8z9kD&NLi9lsm5 z@QY!0mh=i=VkpId+~-#~ZGfTm4+TmCsk;-Eg}; zzJcG2TYq-rLvXwPX7Ic5ix}^5{4Ly$Z{deE&&4;WpXTN9PPl&8aW2C5;a8I11V86h zRkp?3;WyznuIup$udCjkhunt8aJ#;Y!JokGx$89iINpeM=iuk@^SpKO<@iF}&iB{x zyZO0JyWZ`>AN2ja=jZrz+{Tr~18-_vwjK&SqIo_GxBKcd@n*PP*Q()H;w9*RW4s-1 z*V(ps7u@!@>+o$`)t{@#xfQ?o9l5>V9F1R(e@gvB_@Hg7x9jq3d<<^K>qY!w+}7tt zd=_r^d+*`T;dXsIh_Axcy5szae}voh%bBTpzT#a~TDxc99r2=!t13PTw|Q%Xzu?Ph zgKxs^xc9_A!fpKw!oR|8AGinq)t56Bue4qLxApK0{xELsF2i@=A;$F@{wMC@JMqhQ zDBsrSXSj>ozWoC}6YodPzxZ+7_KiSX^WS8r^6h!Zg?I$Fb}zxV;Wj^4;rnn~S3U6W zeLfKH_FjIwqww2sTW?eF9X_9hUq8H7Zlo76-lh0B+~)aJ{6)Mz^*iwWcs2Y}{4d;| zlN`faj8MD!U&r|e?~mL5R_;;F^HaF(Z$bPm-1^xN{|VPDIZkW*ypd|r{94?_ZJ!^2 z--p|E{BC@suYNNACvNNHY5dB&)UMq}Ex{kg>oIQ|@K^Dg_;&mY+~(&1e%9T}xBBn# z%kjqK{EZLCE$8&dG|vlg>rWMY4_=#`5dJ4#2XBSf7^VK$I_Zx0!0kBp$78f+WNd0596&FStC4w+w&+F&)_ycSL1=TO17LE@Ca`GxfPG&wx8UE zr*S(j_v6k>%D0?H@DOhOnS)1gJHM9TabM0lJcZkFc?-|_>UZP8b?Q%5=Iv8Fj9-X< zi^p)A&)@JAZs%Q*k~+Rw-1g@H9(-Bt+WcIAhjDASHXg$*rx~8aUHaJu&*Ik4Zg}Vw zwQJ|^&3F{Iedcz{$L)L^ZTYzEhm$-%)%g{tKD9~h+Hss~Ik>HdC3rBUdOMF^!XvoN zTMCckma`L2`FtOq_4#2u_@>&meJP8FaocbI!sEEjbE#4~?rGfmb2je0rF=WTs^MYW z)_(|(;WpmO@uaW56Q03sp0CFPoAcWpfJbm!Kg00^Zs*YiJdN9Ur(6CO<=gt7g}b=* ze<2>lZ9T8S6S%GaO?Vo&e(uBrZ>wFKxBa+_+x&lxM{!%{KjTSXj#FC4F@xLwb{Zbs zs&?(XsE9{!JHIZ*6S$4HF`mZly3@+@Q=La$s1LlOcJ27~!d=|XmjQSTw|++P6mIiy zFYatpzRlZIJmm8y@Ca_}Ie{l}yWTCwv$$Guob`C{U5TxexA8D;`S0Ti+~)IhJdNA= z`#tV#SH87-5)a|F-inveagX5EpYnJdxA_m^Dct%~56|MZZd>4?9qNhIx5p#6^`{3O z$88<<#nZU0|2uJKr}FJMj>AK^ou^ar2yW})Nj!nuKL5Pur&^!usL$+CyNN=&AZ_!! zuv5;-^w$%Non_>QaHo&_vZC^ncygG$3lFRVWt9_nLEiLi)nAOK`^oPSJ1!o=C*nOk zFXU8kVhi+6IfCCoeVBeu!XGs6tNc6hxwu1q6?`q8d{p(<^T24o)lZWzEulZ~Hy#hk zuj7E7bsGE6dzw#g(sQq7o)^q#kakPaZYMlNKka>Ae{)~`DBPuz5y`euumz1x67AdUyacrOgJWyYLHT4&IUNE0o)|<;b)T2J$Oa1B2{?-=H_LnzdLI;xL zl4H;7#!w#`s`@9HpLnj`n?&C{B=Dj>Pm&W$>G-xFXAd6UDF1_bJB~-2YrIDnYG8%V z(EK>NRbPVzbS55uNAr0W3$Z$$wROUSj25039Nz%_wCB}*s84QFj`y{S-0OBcJxufP zP;vQKJi>8*NbEe3%P;H9WIh?Lv)1yDDZd&Inz!LG_7j`ukMPuWYB$Zej^m*oa__NT z?p5+k%}@3_<-cL`fQQ-N_R*o1p6B)%&c}=Be=j^h|IJ6`yn>VAI%WI84Dwx$7t7X} zi)WkZIC6P(R^S2Zxtcg{4Kh-=hHi2nYR~m`DL9j`*4sy_Zl85to=Di&>lQqR34-L3p`LP z-+%GEpxyMnYIhiT$9uD+r(!#6+uJTGWBkyX2P zyzatN6XYjocPgHpB!8QIX|C08m$&6cB-fQo5&U`TPg;JN z{Q4SaYg|#>>bv7XU;h1gL%b~ctMHI7|5LmbZuKS4;kd_iyasVxDtTTo&(Y%Qr+vQA z1W)4jxo1~AfZKi15Il@uKtCtrp%Tis_3*6c1?$jxN&Ojlmj1v?mQTH%_aER{^38v? zdXAS%yT#5`e&o3FpXa(<5lKY(^S;-Q-I!sJ|sAMoXj!oTwQ4E#r*KZ~ElIW3%Zc*&yq{oLkx z!TVNcVb@&$YWwpXCwBLV|Zqh+-1Oj;_hns;bICp<9y9$XoGxlartF<;uZPB z%uhG`OkY0-SpCPU@5pt02A=&y{wMukZhj!&-!cC*-w&IACLc`xzj)+OzE`ZIaaE$9 zHqR%w7j_C4F6V>;s&CD>%I=T{&m@O_UXMr4kvC+XhvSL!<=$VXkbB*4IhEzMkIlv7 zLHSAAU1?rbZr{_}f+z9!=+AXK)z3h6)epc2<1xH5ejlF3zrmlt-P+3WPPg1^2_C5< z_qLtfYqRCom3PGtS$;kF3o3U`Sbcr@Tqv_2vWcP$Rkb7(e&gf+rix2a$6C4>pn8bDm?ki*vd-|KMTVu8(Kz(s-lzSlX?E zXPYX&746o?-DdKEczZmFKfpTdXZ0;rek z)&F=$c@6wZJcY;bn{l_3>PwLy#lv0XTiAal>|s3*lHWwmCOmSRd?fxcp26qfb>CM` zVwmdN;nhEor)SEaXW!U_C+5gc($C%I^W-m+^En=wFE7V|KZ!>d${)lle5iJv<>Zqy z)_j*dh|k1>xZU5*$6dTN^(*l>elz|$p2eAFX9w==R=eY={{)Zu{5z{ptKQb(-*^=F z-X`Z>6+Y5<<9k$Z?>Fn^@N7xd-%kEKJW@*D5MP5Q z@U7(R!lPyK+x-rY<7=red4c*FK27zu&sV~;xPAWH01uw7dSaZ`cnE*Aq&nIQPoJ&& zu*Oq(M)dpZ3Iu^-sLs-nuy0*sJ=bt%OX5| zx!l&zW;~1UDxsVVo@%T5HT36q%kL!r^-R^5_1-WRtj|~mf%r3?7p&V1>(-vXu3la6{kjT{yIkwJCiA}ok1dv`n9onGewq9T`9GMymY-8( zjlRE^Y*;s!Z0(+f$6CtmKDNH+1@D_8%rn#Dw8f)7zX6ZobjOL}ar`@eW3uOk3VYwj zqumZAHQ;A(r>*)khv!sF$w@BOeA;zwBlYfus<-FoAK?+)u6IZ9SXI@BndidZ2363X zG;Z@#0ngxeT|C{1bUwsEW=Bw|ACw=vI;VInu^9Y{t)hF;Q zZtLMyJWxG8t9Kj1-Mz4Nl>A>^yCh`YY}x_H=E-wyBP^Imua zx8pkkkNNT+#1r^!tj~FP@Z$V+p2S_;^0(s=pMQ#v#%+Fn!(+IupOUX=eiC?1`coB8 z`|2Cx8DIW2c-B`R!JV2Km-S~X9>k-xI|GmV>KEY&+|Il8cnY6N&iimld z2A;q}%+EeNjoZ39iaT}HPpdDwf%6Er{-2Gzxb;7T$9(mz@dWPDpPTRuZvD9vKY`o) zPsN>j`SUp&5BU5gJcwKVR=hcG`5D~x)&GR|^3|7pRr3?^)mO%2xXoL0Jb~Lf>4b0d zZM$Jd4}>9KoFi`Sbq=9`JdE*EByt zpVz=cxaGUJi`(()j`#A_55*(C{0Vr}S3e7n`SO?H2|UPrZoyN&{EzUo&$D>O=Y=yUzV^APiS z2#@%@BkuaVA0G7iINb616L^OE3cLQUz*9conscwp_Z;#7o_aO^`D7N4`MlWHg7JFy zEk3V^J3eoKr#9s0x4~mR?``=$zti%4KGE{m=jYF|e4nqde4oE%`9A;H@?Xi%|K9R_ zUda1md~SYxem3s-ybhjvIX~aUV?OU``92?F`98nT^4I0(KVkVkUuyY2f5Y;9zSr_! z%FjPy`9A;K@_l~hHqD#k^BQ<+ZGQe`c+BTv-1YfD%lG+M%U_e9KhyGkzQFQ*zQOW+ zzRU7g=jVTE`942k`944GUFOHEsA7S~v&xxj3 zzR%~Hvk%+*xK}O5=c~NWr%v_x_&Pq%%JTWkUfvIG%6*>YeSh^vUDty6J9q+*;XBAl zFVQ>DbMTKXAGgn&zQ&!Ust;2C3m(Mndjf?o)aQLJZr?XM8&BgOlT!;%F3ayvORLBA zEd|Hvf(Ktz{al`N-iW7`%gd4<^*r}E^+v4+d(Jc7@~Iz2{Y>0lp`0ds-nRgct&;Dj zehnT;$`9e2@$hQunb5tS7kuA1c$1D7-FNm=A6lpSPssTJ54&1>;KorvBJ^UW=!3 zzEN1@Q3 zh2-qSGq@d>gLoFV{qPtb_*3oLdi&Gz@h8YHQ=Rh^xBcOKJcHMyz9Am?OZn^YR+fX? z&rNj2Lw~D&GWC7&6duKgTm3((|CxO(=6P=3258=FpP6p;g>_!o_kZT$;Ue-<%=0UF zq?r6N+TDdG@N;SRD?C+F^{q?l5B!A(&XD&b=kyxd4->n!A13khJ}Tl1eNrGYi2-tDY<@3B(u)dde+Bfo{5K6tddyb$e1@z}NUgVe|HNH2Lq zu3wMfi9T{~+t0n8!_zm*JK-zvU_|~a{Y;tnm$#+W3%#>HOVJf9}C!)Z2bI z-8`cDm5O&3;BH@e7jj<7x%WM*w2pg(`aO68ckwJ9+oSrLc+px~KM|bM-8mOe;w*os z0p813-w}6l+y8IIqwlL-%efnm&`;~n#oV7{cBwu^KZhOAeM^e`Fg^_rlK+41<)7+) z=1I?U_Zv^?IJP2ZIiA3oW@jUw#O?aB9Z%zSefb2BKCOJqIf}<{o1en9)t>}z-;X^9 z&){q)PCYz}+xN^b#e=idZW+ea0T1DJJspUvcxs;VzhobH3QxYk zcsY+2Sp7ozyG+EZczm(EA^t9&ULr3`e-3z_+u!!)uk$0+XQ;RD|DVJoOO-RYoPtW! zQI7M0a*xoEPyJ&NMh1@Hjq<{P%F@4draa zGk6NO_XR)W8Jy+g6s@cNWPN@X?rc&%)9+Nr1Gt?>P4FOo4gG10NAYj*>+ncQ`J-4^ z1M$pGxvjUcp6A}r9@PB1MHOZ_}LG#8KWQjXo1F2j?!{e0*qJi1%;wK-qj^St0X zk{GCczA^0{#3Mfc+3E+WK1hABddi9U{Cv*~){y;72Gt}EUX^y9GTb~{A z2wsVH2jMPm*TJ!P(C3fgjxXmqJi~Y`|0U0J>-m`a-<^4TD_38^34X5QW&8ggJb>G{ z4&jc^@3;5G9QPFCI^ory>ijBHU;Rn{sQ!G!LN9|Sf0MT%=RDl`UC!;2Qv(m+Jy=(l z;n5SSx92?VEa#;B8}e`TykPyLqFN93bNBslXL!C3!CmTIa)#qEpO3Nn5z2Ac;3j%r za9rY*I^_~JU96z#mUFA>W8^=HJGbTM%rmFm8q_Z$Kio$7bC{n^c($$l3f9AJJk&v6 zikw5_yUc?(S-ICD@BX3Sd*E5-)AsE(xbuhR+4ix`cmV&u`vLAlV!m~_m--O(_H(HR z@bI6?xA$dV;&I&elkf2){xcW&ljbEf-lL4S^rsqEqNLouAASy=EG4)1BQ^0r>3qKw zkK$v=?}%sd_si)wuE*0Am1FZc5YOOCnEyL)m*e#z`Q!0mN9CAL$DK~{8Pw0m<33+) zIh|GCjrzCn7;fk3c07&SdHTEO1>d`H$7lv zh`bW(_WaLuykb4%wW+U%C-J`c6?m|x>Yv8D;wgMvY5m5zUc0C2XJz`6?5CV-$*GG6 z`pa$qY=H;yw$xvPNAWn`7k36IrwKkBck$=&hwvyA) z`6u{6Jov1967&B9`60$-_X(Z3p1K*0H${JL_SR>?=SmUk!+3G;jlika=jomooG;1w zYS-THRL3*;Kjd6$o>2W!yaS$oPR`+VuE)d0JLf7_F|J|ej{J6VCgGt%@)7tfJVpI5 ze5utJR(&DzH{xzlc`^J$JW0O2ulWIYN~!*P>I+?>`N@`%H)miK@z81Vu3W$x;IZ=Z zzT|YmGt7^z&l~W-^O`sNTxlep#jXB9Jotj@domBR@fdD7FX52|s<)gicpN{QoZWa5 z58@d-jW@)9!vhPIZ(h2Q<}Hl#ILE1s$8fv;UVSILw)*Arhp4|353P_N#i!xHmGU4tvvGHo zyd1s?4=3eu{B=CDT0W8+m-q0e3{W09#EH6xcp{AU79QSUFw>%!%s`~d?w+(QYaoPO0!?U>keDP4LXP*0$ zKLJnr{82o(L+$eMud~{6@H$)vH{;QdRsSXVALEgI@<;Gr@hpBL?lfZ_I4%+V4D(Nv zWA}|0p}5QWXwUr~$HSkiz8&=o@r2Je;92}A z$9Fp(|3W$6;h*8L!}7=Je-;mZBfkUx$Mb^sv%y&1Tu);3r>u8EquSCb#)~5>Nat-%ZX6JbF@o9&aSJ z;qiauuT#Gd&z_}zu3}urEWe`s1pP1Uov;P-kT_qyn9sS+#lzL)^Le0F4bRq)&tY5* z@OUkG71odIdG0ybSGuBwslSH$WOLQO%>o*jtM~q%MvfP!i*q-gxlhN@d=j4Z`IET! z7jfoBYV`|q?)@Ccm+DV1=HUgq&&2IMe>nGmYXBR;Q%$MAEhZ-F~L?}7($m-?Hno_+@Kb>2Lm z>OMT>dBJ*1a~>U{em|a?qxEn#>+MH8FkjB))G6IUIYFNX@sQ7(;x2wE?Y6;VKJSjF zaPKmdd-cVG>(rmS$%*38Me^r3?_zl7Ik{b@p1|Xu$nAdVc|3AZ{v++K#RCWA_V;7F zg-5@Xd#8EswHMERA)i1$zqEY(e*8Dwp`Rb(rM(-2g5%{rubj2y*TtO$@`0S!E*^YQ zK9u(hUGey8d7R^WE1qCJdy*5k{Pn6oopxWs0~_RLP`?=uy(YK6XX8se{f4~gc^cSB z%ikwo&v;9<)Ob_)R@%J~4`)<=I_?aaNB{#2>@mZ&g1BUxH`w8u%M{;Hc{B;2&8I zZu{y{&vVZuzSTSgssEGuF!jf|ewDpk*X0EJb5H6o%y|VTNRErY%zP$%^En~6|9HK) zU;F&)d?D{KJkzgUE}p+3Io1o&9(ef0us#h`T40(~J2mc7^6A@rT@U&c~BJuZ^d0 z4wussPvaNTZWrA7Q~7q@MerbA#^w_b;VJq#3eWzn9J>#nh=>1?Hz4O}Jcf57XDOa` zy5uVDdFdN?sF3^t_Rn|lR0*8>v@{+rC-2PpwGU5Jkbi!fI`lifrMi3>{XG9l&3~+_ zybTjt*85x__neLMaWwVc=hnaXJP^0%Ep@z{Q?37oRB8?}wjJ zAE&+>>*0GmTUR-@uKvQq_42)ptK*f#-=N(}c(lIiKf>$b;RbS>pH_GTxB2gGIk>IQ z{&*Un!3VStT233~+w~=mJ8k8=>Cart!AsKcQasa6^=t5#@k9st6THEG7k6)z-^2Rf zkEbH?vzX^^Ex(^Uji1Dm1Ldj z9P@uUkAlAZ(>~{Z-serdc29MFwfYb5;CaDyC;F7eWyj@ueBuLF=Z=W2huiVM)2d&> z@wyjJ;dY-h1JB@0oAV5w#qGK43d^6R{7cAB;bGkNhxhR)ZvFWh4?d$Dt3QFe_=V&b zZ_Pa5w%*ReW3!d>GVASP+?^|L!#?cd@#pi~?T#n$=CnHq&*D??F?j5S{G91{0v|#B zd_1^7_4ZtJ4IW-7x9j^BJc`@8-EaB$VDgXQfkpW_&Q+SX5N^k@93IBKubt;!mGRhO z<+Q<@&`B&pvpGWY(Qq^0#!|)J3nf_0}Q}~6nJInHy<+r=S@?XsNl;z_WlD`{| z;C8+o#-n&C>QCS?ygXjUn~;L@F@ZP5gSb;w=c9L5PC-5i9`4x9pt9~h7%KIX2!8~Nw z$Y(R5b@2Gh@#q-?${ImAq2!03kVe0Ms*!SU)*Ob$M`bY6NelxxR z&*F3G&w8tWLpj^2-(&fkm9r<`N-=Sw`2mJjE;|Ci-_D1U|v(&^p@9|h|z_L+PN>#aUMnd{(4`g1KFIHdYo z)DOem!}8M9Pr<|ZZ2TEKi#Or9(2IEJTjlh|-}JoTd9^#HUM|6|v-|L%&wnB(eoQ&~ zU&krhK{=7p%4tJ?%6ncg4;ku@Gp_2?yVTqF?pojh+`dN_#zS~n@&{Y~arGpFkE1>{ zO8v3>rHAks-je!f$w~dBoT2zC>VwqlcOB;)>O((Eu4A3-$K$`s`*9ur4tM^Ne?k7= zcov^cPPvYpkN>Lved?>?QRnKMjAh+6!LxVUgN<)kjYpZZWK<$IyI*HP+& z)F-L`9d~d$?qxcuKN*grt*a_{+P6*`d0wz?L#5U3YVzCSS-cqU3wn@~x?BCXbu}1w zeEH+aaV}8)NzTWIakrZM2HJfVPvG`GVtp>(dw#|Hf-v>lsE^iA&ODwweT)a1$ak{d ze#bM-7guZ+iWJMJMogU{r6U4=(mDCb*puEQgj<@<0v+)}=Tb^8$R zxbg)1?Q?jdz5EmEU%_LY<+Esa2OjJq??yilTm9AY6Zl`|UFE}=w==qEezM)y>v_TX5@o-&b$bgQ@%doOAEcbFP=eIHX(}Db|yw8nszTAel#2xC*yIOr#<1+7O^*+Db>VM3y zpKA3!pJVkut8;(OU=Zo`9*tNw4sHO~A=xjj#tj>qr@^k*KP#K)J^ zZ>+*Y^OR%H!8YS@ydwSo)N-CveLeDz;u+k&cY4Bd7O4It^*`I^N6hDFyljT&LcVz} z=508qT32N}FIZRch014}an8XLcy;ot;c0vj-V}EiDd%D4xxLjdmRHAn;?C0ic87Rg zuuc-cYMlhhA4`3J`gZsucmyAXKTl4Y91fS0#8bHKSG{@u7Q}5nw|n)cnzs*WH$%IP zX!kH4T&8i^`-PKu0_QT~lnZPAQ+Q=^s^b~l`hN+Y#qD@qhC45+-S*^k#KX9L*Kw}L z6S%jt-TyOAhZ|B+Sl7Sxx=6Fv{(8Jx?hQyb6vyfN-1mH!Rxwzm3B z@(r|mEuKut>rj7-)vuP%p#C<`3+_V_57v`VKZ^R;8r4t7$K!#Q~uezle6{;R)Q{=dQ3EoET>#p29yQXOHDc~kEFH9omleLT5EzKfiWcx;0fHm@BJyB+^d|O^ye2m_MzP7ztFW@7x43J zKJny7s=u6htAc0u$%kWA09of`d4f|E$3(XQQpUu>!JA! z7S@Mm&q}<+$9Ar%R}QIqgotW2NNx zve7^3dBJ{?xwl>}+0OSD@Dv`T-F4(R6_wMDh4?o0F<;JJ>ciDkZ{JJ#5|884>Hn{I zu!icZ(El<$HJ|Rqa_=!$?sWkkubJ=l@Juav4eFcWfjaVvwA&sJ)svr3{k3?wzI-n8 zFa*!y_I;;&@JK_|FQuCScOs(+LH;dMOJTz&!d@8ID}^F576 zTgcZ_f7o(b%D3Ua&Rdx{6cZ3=fW% zr`ZQuk(27CoJ;WQaOZmYV7`z&1drV#Uq<~nJTzY3l;=Ow@aRnW5-wn~@ZfCuM;xz3 zp69N!lhjY|KXR{iIrl#Q{axp2g!(;r5O?t($#JGAhwaTNeZ9{20QGjiR}D|$c0X2v z`~M90t10rE>IkKmVjIj6dhZRL5v{ZBNl@z$rl2cE#KKmG9}-jMox@GO2B zJ_C35D4*ryJdX$Pjnr?z)40w5dwBAF<;-LL598qvBS^YJph7%y()IuDQG z&r)9-4}PQ^oBx)02!De5&UhN9o6Ze*2JcS&P~81k`F1^>iO2TK$5B5IcRrE-O9RWz zKb4na{lA6>4#{hC0N%yjZ{)X-e*jP5yIG*e@aXrdf17p-^=2RbK^`E#G9La#elPv0 z<$3No0P8u;0cb*f`lRZ=BBwo`a&&&#&rx2B2aC$@ra!mgiDGhZ$H~1$d0xaOXn#A=cZ=c(%HHJvrO({HFaDDk0?r$67=_c|L)VIMiP34_WR|DPfP+NI`^WtXD3)W|< zu-2#TOT+OrZu`#^Jc!q%|FiLouRe)8zWmL21dot&4%e5IqyD7uPrdq6T}Qt1yx=+# z=De`}|A9wvTUX_7Qhfrq>qT`uiL(tjE%6l2G&^1JG;aOB2@iGFJolylci;*996V->Yq@XM$_<7VZgaC=`+4UddezCEXEWcBzp?1!!Kz$n$* z@$Kn(!F*_XKte?yz%};IW7vO=(a?4+V z2YtQ{58*Y)c@K}^_BqSD{JZc`+^&QB_v`0Jf>YIQCvv{QqqyC-?DO(ZHLmZx&!-B0 z4kbyw?YDp7DW7-E@b`-Nybm77*#?{_p742tWy%SKuFCbCZOM5m=K-haLmC&ua^~jz z3@7OGwch+Vh0k-E-)<{Vf8wCmu2c9dC(3cRoG-k2IMX|hiSZ58F8fm1h&=Yq6*+&B ziD--`GxA9cY?}G~%Be#AVm!N0EBtg8#A|q<&ZW8hk?j9zJpF)jTn^YzcsQV(F|=FL zdr-hzTN_gD*9JK+=LBXb=MwtU6;BmyovVwn|3vWc!>WIYwAq%kTkY;3=QZ<3RR0h4 zUz@+D`UcdO?Wg$+%vAkiHn_HU{8G(>_cl5AnrGfppA_7Rzk&yUZJw)Bn&TY9Q*$J% zsXxCz$MISDNxU^4cGYfw`X9lav1<2kj@P|-q>Jj8bG#CkKVSLJ<8SAj(Es^mC-tGl zs<-FiNAchXnzyx_04MRl64l$!#Z(@kaYdKOZ)03{;OXtkvHO_^@K{#!X8XeeJoBP* z9%lWg@kA4i*ZVqR?)3#8s-%4Hf9vPpA_{K1{L)4|9!R0xwoHRs(#u!T!^PPD8J?z z`m<>~wNLxNDBAt`Kk`rfhnMmecESFWT@}g&-%id_Jo%O8Im>yv6L)r~KZ_aHzj(NV zKFO-V{!rIj7zO#sovMGEp?AT<@90VF9M*XsJocXI=h2_ZIsbp>?G5UKdsSbb{v5OX z59BrIe}%!Uldt8rZ(M}OK9aYf|DAIF|F(PWe|T?lQu~#&pFcVaPoCB~C*D76l6!5y z6QAVQpTJ`W5wL6~qEPcD$O?<1IB6ux4-BRm(AMWDrH_G{)=bS?<=X0&we&8xe)%D0%ei-u~vv76MNd#QKv z=&#Bt%Xr7&PL=%g_i@YlP4z#KvldT(r}L!*^ZyYZuA$>Hm~kD$Q{6SspHN@q4%YLx zI)8^!e;ywDL+yS>{R}*s(EN0!em)-gSM{$@e*_QIRJ)HcKPN1Iy^c#C=DE_H>QAt! z`tt`lSDN=%j_n(_;BHOz$Bz5`c%X-J#_@*ydCMs!KZp7M*m6qCpRTA56&a@fIHfet zW7$ud;<3xrAA2s^5f5$9ah$-odRzVJ$}t~|M_yJ=ko$^9@ytc)&*!we-#nptdzAVA z4o@D{Jls$H#Ziv$L7gvlUAPrb)mHv5C%=k$3l3LKps~CJ>#!~Eu2X+(-y4D_ zmS`SY@&;fMp1n-<-8sJV@nAFMm*KeVz(dXD)97cZ5$aE>g?tA6X^6YsG@mxlVceN2 zuTB0)JbZ<6yvJ*~*JF5mqUxLB&*8}{RllUX>MM^_e3ca znw#@B6-mI(jcqXD8J5P^U{Z{2HqunCjgYJUsxO-Cl{E6dU8P8O4bCuVT z-`?tH>-xTwedBIC7Sep4+gc4gYB>YdpR&x`COq3n?Y_XcJ~Y2o_4fC5|B-V#{Xf4H z8m0bchN`|D`4`}!Ve$?9nagq4)%ob%-sE0A@htUMG2XFOf46daQok6FP1gL6#oxda zqg1~G{~ixLr23oa&tG_SwCe49dZBwXu2^$jFBUSco_KJa>VLxT$I~s`T=ECxzl5h2 z%YVc7;E~_79?qbjg-5I1-~{D+|CxI=#hss3Z-3u-4?J<7>f2L422bwNc<-Uz*?2al z`n~v8JpG{Rmy>hM{C?H%qrUtY^)vZ^+}mbzuXcE3oxCvXc>o@KP(Fs7X?Ow;QU4vD zo~rte$oC{i%w(+ZyG1p5Xe}3=e#+cIz+?F{{tWA0dAU9;&EM96n?o z-oVp`RX>#aV|c2)=IuO=V~_jQ&-m*ta|v}XQqUMYTlVrCJVXDdn|INAzMA}J@o-b+ zN66oWhrZEq`HOyjh9|#MKkeUZEB%1_6Z%Ddg88`>j}BGNCi0)g-N)Tr@;rPm9&D=l zxs>C6#Pa`C{`ZXQ+=;A{%T%wsS;wh~2mew1&FnMRgQm_^$H%Tr<|qC&u+^p5Q zUrf94obw<5^UHkdvt^Xim7LvpDl7k(_25iWe=5k*ZwxToql5l?%b{Q|1Im}IqZ{OuJJ*{J*F1QSFLJND@XWc&kMjq1;Le?@zk_-H#r#^GU&m;- z!4&l;xzo+b3D!e$OaH?1o@)GrA<;Pdy$af|D`7=NznKf|5tl>ZFtv($9f;choq*qd=x!80S&|6
u65!Tx+cksbS(JGf$i74@?Y_aQs*^d@;b_SHXeXTI8mo)_y9cprTSy%^+R~@Te-c@dIe8BqH%2` zCxb_3=6fmcjb843eLUZ*;)!3C-<|nshdaN^`?9Xq;L$79|DCk^h55g#pGQuyCz-cG zy5P@e{nWujzqQIGyh=_RJXuWj^H>i9@o-7`Y@X{*!!!HUpZCd`jc3cNelY7lg-0sO z?K*xCch~E9UBi9O_vTep|18fr&wh$=_0jwNo4B4f!LwiLzSLzMMsW8r`KRPOfX6OS z|1Vrkyb&L+$6uKt_vvYd!=hA^%pRzFJPs&j!RuH@4iSEi=? zN!GKA$HwY?>*NZmzX=a)kT>HE(!+S9tNOEtoc3 za}M=?;lWOFdmnnn9Q8lDM>#F%Pg6YfvHW^`6rTE5^M3{B?+iTtsp`AYpN}p7bGiNf zx+UkT-Ao6~Pr=u*3SJFyr?yTw`#Ff#=3gks#ybFy+>}4wQFyAe@?YkOYM2%Ejg#L|M_M5fB2i^gk0@!5-;nciPO5|E|3-oa!;p6|qpZE{o_#ggT&b|Glv&MB3O-B>zXP~x@myPStbNCGH zgm{eKkCzOQiPLTgYei~2jaB~SCkaGf$Jg)i`oOcx$ zFy397{~GLj?eJhn&HpNLB6$2O)!)lJjKH1xIv_yB!_u)P1e{I}3lwW@>p2F?^|9(8MPsi8p z|CgD6qnxRnkGpaA9Ub2!>)|+_IHr0V?^%o0&s0|4hJLod13$^H;CvZ?hfm6Tvp%Qb znRRY%1_rZEmf^{BbliW#Q+TkAj>`$=?K3>ySI2i2derPr}2+Ro{?#IF83ld(cG;xU}tXQ%!O^(S_g za_oI;7!RKo36LK5vXW74z#m<5Aqsi+*?-Pcc8EE&qJw%)@8m!SY%^_V`(#wfu|J ze|tVT5f4_Ezr+488_(8|zeu~Q@x;aQr|>;^hVK&&BoJdTg$KBU8H z%|oEE>Nk*o3m(kqxHMuvnT)5YUqJm+c&v$XY(HFrCoh%PrhX%y!OOF6{D3<=FB(97 zp*8AfvXyey;OFAe*77do48tSc++5}3)_>d?ulvBFTo<0j-RE^(ILLhdf(Pfy&!)c2 zTJ;atco=^fPvLFw z-FTqCa_%9&$jjdWKq?>gh`-zTbrr>M8{`U*TcP&sy7uKxend-L#Es%r1Q zaX`Hy#CrvC!l*dCz#%<%JBC?AFpLTgojyq?ry)a|P7a)-ViXj_DGm%*MB{)U7d2P$ z;&=@zPAJ4#5j8lAf(NIIBky(9Zn=;x}B8a{HJ;Y;qP`7y(XE-^gL zkC!^W<9=2@MEv_7H~ZXqX78&#x$8b<_^_`}#yV=RPdolot9z$n|2sZVQ1#xu8jf4;==(O(%J`}1u#g6}%Gt2A_6(F@tY& zd?|yU=lF64f1~3o8T_4&uV(NMIlh*`KjZj%2LFcRJ2LpX{Tv_3;0HK9n!)RicQW`9jxT2L;~ihh;HNphoWaK(U&-Kej<06$ zvmIZ{;1@W)p2071d`AX`-{|;I2LHa}!x{YNj*n#U-#I>- z!E>Lp{B|<Ok2Jbq)mchS#z46%U!G|S8QgBvz z$mb0oz1`-!Shu{x@x=?t&)-&|8`jO%{$jiu`MLZ>_kS_%UlH-Q8vYVL?kjFE`(@Xq zMnCWWs^J}9pE=m=D>oXxe!lsM^}ug$GJL@6!&ndOzGnFF$E~i5^X%QlXhp?x%IL z;T_jS#=2$Y`-Ts_-{!&iyN=&;eE3CnUOT+Ly2mYMzxuCcA9W1}IX<$t%~L1e%MN^s z<3kvfI~zsBqj@cWbx zM*Q#A&w0o9{*n0~zSQi?UYE8b{s23#_}B ziXZnT$CnBg&yT&|_WX(Y8Tx1Q6Y+-QqaXA0_2Yit@sYjF{(bIeyE-J~Yh+)?{k&fO zGsAa0$&UL6w||f0ql0F@kH_Esx!JEh+VBH?{CwN-Ro8=G9>*9nz z`uRS@@c4fIisM5I7H7ooaC~t;+CTMA=4a^|JKrlj&dVJid5qbA$n$mGf0_NzIp+Ty z#~*f=$8)gRM?c3qzIvY7f8NLEPaGc|GW+ZNy#Djg9?z2uzs=+MhvUn4S)3O;{-VG5 zahJ^hi+$npqmHk;p7hslU-_%quP>YZqdlH)IX;}T^^kw@>+ZpSGy8$}n*A%Cx5pe` zInv@>^nSS5@zLGQPt*~7=I?&o51XG6Kkn!MVfg5P*&l4fD7Ww3hIf2^zry{$!SUti z*m2+E_#Yi#I^6tRxOfXMW;y&!_Beeui!~Ki~3xyV&uy?dIn( zj=w=KmW1Q3{M78P@&5Ud<4c#D|D*kUXZ1pOuwTF0@JD;zKjZk)&& zb*Dc%zP`i!=r$qwJ9aPgKkx?gbA|iA-0{x!=I0={|E1$AzcW87o0GqT2F%auh303) z;M|zwOMfu?^L_lh&GF7fX8#MnUaoh1CW4dzt@eKk4|u)#m32k7q!H7RE#8L1y1^-nhW= zp2zjr;{{0!b^xXRb$?~9HP?{4>x z!#>~r!|~iH!--v-zqUQX{0#iY{A?XCKS%H9ah_)O58BW0PdGmK2=jln$N3M(*UmEg z_+I)E6553Eu=02Fe_!X-a~xlLl=)e3KOc8|;B51=$*=bV2F=g#xrWF0ZAKhldbQ#2 z_qy?2j<4)yE4PpK<39FL=4bsPv)|YIZQk+4dz<~ky&rCMe0guf|KRrf$zeFJ!2>M* zc%2^S_`oIRf5Q9!1CB30*!;}8pFcak!~Mkh`WQ_>39nveevb9{w>duW5cBg0_w&Ap z4_Q1XjDHh~xV{-s8E-@Js!CryXCp+VGdf^L2dLb(Bx{@$(bM zmkzPx>h?4Fd&CpW|Ii1_&oyrU^d}lVa)`wr_1zao{G(?78@Krn&i)>Mkoj49huMF^@s~Qj!|gxm_?H|XJi+4mq37$sP39+e zvf=0ZeZfy1ANay>;^7k>=d%tr`+@h`aqn{fcSQUO!(Zch{MC@zuU%>QU)|3EdH3^e z^WXA#&USq5{bnEMug4e6zH^P?$GiVa9ADgS@yB`mqmHkA#Oz}|@YtgH+3{kte}MD+ znB%J-GyCr(4_$J(w>zGDmD!)}$GyYxrJq>*QJ?ak74tuQv-!C@_K)L34?845j_`gy zyz2dMf%(7PeDEUk6X&nLIzDup`HB5muABeC1I^FPe%x0?{0_5^_3ft}U%c4--?7O~ z;2Tf!IKOQ8ojx8Otq0TLIvu#n{KVf0e4gX0cl&wyao^_nVA10FiTnA5<0HF0B{|?{ z+;P`OI;(w*rjbC$o?Zt-Q+vk@<4mCdm zM_D}Ac>I?+zW#Evf0&=|VTYN0?mWZa&1@eUT5|{bNgk-muBp^ zzjgd}#}~VXzjQY{v6GK5|LYfeE552Ke&(i z|2M~1Z#DmMzW9mbqrWx#uX%qS{w(t|^b@n+?ZM{fHI5Je!R)o{ocw(?;(sywPkB5K zQ$ZN6)7(D{|AOO3JHF#j=I4{Fo!bKddQKO4TquhUx`AIcq?L=x9w{`uM75BD|yANBsZ)bZi_nEmCR$KN}?w3pcr zKGaTZpAqx3<9=p8TW8Uf%OWvmf}n+282>@C3(~o^Q{y zUTki1=R3YQW$_dp|E1$O$D{p_1X*F6toeKR5#Q$c$TRKvTJ-bLi2HFPey8Irt}A(q zANP4Dd;gqa@to!OZl@T&{%t$2k9mF4c6|9KhF|FY|53*mU2k}*uLnNiIp$~hH)gMC zCHXtU@%4uF=e5pbpL2ZWp%%~gJ)ZkK*W&%2b!OFzDI2~`^6J1o~X09 z+40eX%|7Y}j~_Gp9c9CB@;c$8j;~GHapO4rwd1SK53lm^{PJ->ZpZ9nJpbW%ZpPw? z`kL=LzT|qYX#a?o`5Bxu`zJf!zQFO7<1EfSydU1}`0AN<+~>Ld118MR=vn6Hd(OkB zIKDb!etzoquXTLzZ1Yp{>*ZdP=4a(OX8%uqztePl_yY6)0rzu-pC$KmzT>%N^YdIEKi50HJZAPWU%zmC^h&e; zH*=pmxNXNBxY&;S=7(9sRK(wB_OZ^n%JG#CnSIrH@=nK>C+)aT@;LLS`+0rJ>|@?f zbbNh_*~h$}>G7z89LG zb9`R7cgOIxubTah9{=MUUw)U_pXU91Lc~8|$34dF-x%?m%uig0xz6z&pEUbeFYWar zJMQW#@&9yu>2rq1I%l6bv(J6p;(3zC|7^zxzhw5`c(Ve@`ouEUCcj(0rwki!zg zI8M5bkN&{okNj|@<2$}=$Bp^=isLH}H$O37cSQT2n4gFbELc1PH=3X5f5P##LG$xG z@6QXP{V&Z=jPqK@m%qt)zVGh9XF1LeJ-+oTg>1E$5$Np={@%h$A|sB7h{iH?gftTsMvcNaeb^4 z{XET{zr4fG;kAyh`tM8s&hd9g`#pyfq~rLC<7?yQ$FAaB?u(8OU1t5DWwGS%7mkn4 zoBhY!&tDzS`Sbdd9e=>Z)(<)F&zP^rMcm&HiFrBP@xk{P66eR~IlkujN8JC59AEst z#S_o>632JE!|>?mgO2C?IqAQrzjl1-Z{~l{$L-&u ze}6u8mD}%eN$7{2{~p-~FZ98W=z|~A2Y*fyBU1$Nx2b@ay{EYklw^CAhxd(Y52ob&1{n)y7rlJi|Zd@eDdX;P16w;rLS> zU-s_``E7b`)bU~ez0Ehe{k-F&g+mgg>G-Q1U;DZp_sNdG$MGG$9{wuFKOX)6!>->S zI(}pHf11sMA9DPsjt_X9AMw9L{8Y1#d^qq{>xa=BEuJ{e4|IIsCc}T@{-5S}?rRqL zT|Uk?J3i=otLMA@g5#?{G(R&w&R_5NkgpHN>-v3;kN(l@4{|?Wa(u@dtv};+^ex9n z{CB+ttT zpW=QV)yMvzKKPUS;79hsPw9h?_rYiT;4kZgzdFJ79>QsMef{0f>+OyYpJFpkUw1s#pRaU$_`dY>Hpf>SkM!0EK)BbUeuVnD2IzG4u?N4%i$?^Aizn$TDu5R|Pbo|YZ5BPiUuXg;31ow^9f8O5* z|B(Ax936LmgjqJdU?~wD;e2_hp9M zF^;eMcX{@AKTXF6{XN@{IDVGnBmVmmmpcB21ow^9f8OHu9epj@R%unox;Xd}K^uf3E!MpCi^EvC8SifE1_{g#y_g5b0I~-s3_hcUB z`1Otte%S0|o&1XgccavQ{wv!1`w36*w)?)@uG8F-RS4hP+ZqNFJmtXud0-#B;(mso zZhn4vPut-*$JbA`EB#8JN1D<8<%a+1er7-Gcnc1C4KNK6I|~P zpM7L<+*n_I+VQnpEgtKQT<+Tu_xrDrWG{V()bS;MzS(m8K9~Er_2(CH9p*6+_uq?q zq}x{=A9VZ`jyD}&{f;q7th-*~_=xK+7|GVQ$8T^Zm=l)LnA35I1;D2&_J%c~s3hRfFf6&kV zj<06$Cp$iLH|-brYkGPI3j>9)O-g$=|x8>vi?T)YbcL`tPey(zSz<+Ni#($mT zqpsh1rrUob`Z;JgK{}5AGQoW#^`ATY;D7Cd=iayLe!hPn{6T&2{Sthy+~^g2?1S&!2Y+}U zeE&Z96Z_ypeeh}@e7Fxj(g%NTAN+-V@UcGl>3#4Q^})~TgP+|8KerG5+CKPO`rz;G zgJ01He}5nR!wJ4uZuogtSD)*b+N$F_o@=U}x)AJ?Dvb$m4Ar{wq!uk)k-XFI;+c)SnoIKFbX9rr$d+&4JB?)ZL=f5h>TkC}a3 z5BZYg%Xb*=w_mv*Ilh{~|LAxwcX;CYnY-Bm?sj~wZ1_+7xck1}`e9@@!}oCe105fA z`*F8F)baHL%s#${dA#F;Rl`5)_Op%;9Ax}?+y|ro zUm702bM$4$m)!nUyPKo+=;!-p|A%`UewX8m-fwL&V z-!uF8{?GRuANnqj`+LVnZ#MjP&&z#3=>2n};W7S4I=<-r6UX^*w0As?^DT}qXReF$ z9AC{`7w>nx^A9_2d>`X_$4CBd_<0`Z4;)|jJif~DyCd%QuXFss6(9e8+*mgr>-ef4 z_guH1aeUP8TjTk@Hrl`J$iy<*Un=~8svk;cSRHaN_j7Huf1TOK`^wKdzWhqV?dr(o ze&G1PZHC9^7r$}5^JT-I<^J#a?{;2`Uot%2&;GOU5YK^`c#3X6;C@bYKhJc0$9v4r z9(!5CXteh@3!`@hZc-2R5oIQ}=sJB~lj@rQrK<9Ga?jvwgwisJ`3{&dIJGkEu_ z=4ZHM$KBoS-{km62EWSjQO7Mix!koknxCa%^Yc#k^F_yp4>9~;$G_)z$ML6lzx~qj zl|#+`MDMphJ3erP;gL7)f0G?|$no#E|9u@FIMVEQb3gk#KKKm7Pxs@V6Y&!akM}ii za(v10MYn%f^na4suQ~qRX#X6;f8h8p9PgZJ_<0`Z-=d%A8UB06@4sf}HQ?X-JJj(( z$Cn)cqT>a}M_*um-s$*Z(a#GF|F+|&IKIR2dB^7*?=;N*Xvg2=_^RV^{{4XCxlyx^ z{P0D`M>F`3qaVlP^ZGwHzSuNB_ws(*{cAq1HXDAa$MXoshkRXk%<+RGK5q7JbNo=p z*BpPT6yU*Pyx9Uq!E`#7$C?s&)XqTAo?_=@B4 zxDWk?_0M|7ev{*a3yky0j*mDV{h#3YlH(8Z`f$?mmF?!|bdTq(=>Nrrf8O!)9pB;j zwT@pB{k+8N-{<(%j*q_7@aH>zo#U&H$Lsx@jxU{U_K$P>pGSMgU+eha93Od^*ueCbld@9X{_`z?!q=vu?a9nU+yccl;Fhv)A`5&f$BS z|5y(XIzBROmGDE{{$R&D8T_e^FFC%akN;;mzC3Gw1_#X13msp5nc?^M8^{+qzIMLh zFWy!mc8m4T z`Z0D#bF&wYKXrWd*@j={e(v`J@6QtqFL*yZeBJQerFNyi*IsMRJ5tPyql+qED(h-yqK^aLYrvoad11hHjDyIV~rvoad11hHjDyIV~rvs{_ z1FEDWsH7vPq$8-LBdDY!sH7v%?^JB`TqPZWls6kK)pP{abOhCO1l4o|)pP{abOhCO z1l4o|)pP{abOg0@1hsSowR8lvbOg0@1p3m%#`)CJ5!BKV)Y1{u(h=0t5!BNW)YB2v z(-B|_siz~Trz5DRBdDh%(EF_$QRgvl+COMO&N!A2y=5dMUL7e#l`UTXP2Y2Q{o_Vlm9`u<9f964; zc`#@m6q*Nz=0T!)uxK7Mng@^OL8N&wX&zLX2bbnare$^$$Tbgk&4XU^rSve)gJAPu z*gPmU501@)Wb>faJa{z^Vl9=KPY|sdG3-ISc`$Dt)SCzQ=0U!Bux}ppn+N~qLBM%1 za2^z#2M6at!g;W89yFW>59@SsU4xg^h&+L(^C0Rxm^u%t&V#G-AnQEXIuE+egRk=- z>^vAd56aGiv-2SBJXkvq+RlTw^C0d#m^%;Z&V#%2An!cbI}iHKgTM12@H`ki4+<}U z!V94A0w}xy3NL`d3!v}fWix)@B%2j017XF!V94A0w}xy z3NL`d3*e>#F8Knk@d7S^0>*p+6kY&@7eL_!PG#&oe%0N<-IT_Y4g{{_&00rX!0{TD$01<-#1 z^j`q|7eN07(0>8+UjY3V!1o0Zd;tVs0KpeP@C6Wj0R&$F!52XA1rU4z1YZEb7eMd@ z5PSgyUjV@uK=1_+d;tVs0KpeP@C6Wj0R&$F!52XA8WibTq5!@xfbR?7`vUmB2)-|Z z?~CC3BKW=tzAu9Bi{Ser_+DYBYl0%^UybRSporN(jcJ}MVsa>AawuYQC}MIbVsenv zG(Q$GR}?W<6fsv6F;^5ZR}?W<6fsv6F;^5ZS16b?>lHC|s4>kgMa(3nGKjW_89|L{ zZYg3$P-B`~ir`%}rpH(j?50L!66~f%WD@MAMr0D~R>ZI_a`8VV`-+{z z-r(u;e0hRABg{fY%tA%XLPg9%Ma)7)%tA%XLPg9%Ma)7)Oglx)Get}+Ma(2c%p^t3 zBt^_5Ma(2c%p^t3Bt^_5Ma(2c%p^t3Bt^_5Ma(2c%p^t3Bt^_5Ma(2c%p^t3Bt^_5 zMa(2c%p^t3Bt^_5Ma(2c%p^t3Bt^_5Ma(2c%p^t3Bt^_5Ma(2c%p^t3Bt^_5x&Xm? znTdtt6%$Jl6H5^jOA!-G2@^{R6H5sbO9>N82@^{R6H5sbONkQ;XA({wm@7({D@vFv zN|-B3oGUOnlrTAzFgcVkIg~ItlrTAzFgcVkIg~g#a5fOjYdF;7R~*{dv#FTPfZ<@n(s4IlRr_eFbkMc;~=d1>PI*W`NZ{%YD`!W$-Q; zx(wbegLli|-7<>fGI+O);} zD1R#`e=8_|D=2>}D1R#`e=8_|D=2>}D1R#`e=8_|D=2>}D1R#`e=8_|D=2>}D1R#` ze=8_|D=2>}D1R#`e=8_|E2wuX;N1%9-3oZO0^Y5FcPrrC3V62y-mQRlE8yJhAW`q3TU_j8m@qbE1=;DXt)9zu7HLspy3K=xB?okfQBoe;R zhAW`q3TU_j8m@wdtDxa3Xt)X*u7ZZEpy4WLxC$Dsf`+T0;VNjj3L370hO40ADrmS0 z8m@wdtDxa3Xt)X*u7ZZEpy4WLxC$Dsf`+T0;VNjj3L370hO40ADrmS08m@wdtDxa3 zXt)X*u7ZZEpy4WLxC$Dsf`+T0;VNjj3L370hO40ADrmS08m@wdtDxa3Xt)X*u7ZZE zpy4WLxC$Dsf`+T0;VNjj3L370hO40ADrmS08m@wdtKi)#c()4Pt%7%};N2>Cw+h~^ zf_JOn-70vu3f`@PcdOvtDtNaF-mQXntKi)#c()4Pt%7%};N2>Cw+h~^f_JOn-70vu z3f`@PcdOvtDtNaF-mQXntKi)#c()4Pt$}xI;N2Q{w+7y=fp=@*-5Pke2HvfKcWdC? z8hEz`-mQUmYvA1)c((@Lt$}xI;N2Q{w+7y=fp=@*-5Pke2HvfKcWdC?8hEz`-mQUm zYvA1)c((@Lt$}xI;N2Q{w+7y=fp=@*-5Pke2HvfKcWdC?8hEz`-mQUmYvA1)c((@L zt$}xI;N2Q{w+7y=fp=@*-5Pke2HvfKcWdC?8hEz`-mQUmYvA1)c((@Lt$}xI;N2Q{ zw+7y=fp=@*-5Pke2HvfKcWdC?8hEz`-mQUmYvA1)c((@Lt$}xI;N2Q{w+7y=fp=@* z-5Pke2HvfKcWdC?8hEz`-mQUmYvA1)c((@Lt$}xI;N2Q{w+7y=gLmuT-8y);4&JSU zckAHYI(WAZ-mQao>)_owc()GTt%G;#;N3cSw+`N|gLmuT-8y);4&JSUckAHYI(WAZ z-mQao>)_owc()GTt%G;#;N3cSw+`N|gLmuT-8y);4&JSUckAHYI(WAZ-mQao>)_ow zc()GTt%G;#;N3cSw+`N|gLmuT-8y);4&JSUckAHYI(WAZ-mQao>)_owc()GTt%G;# z;N3cSw+`N|gLmuT-8y);4&JSUckAHYI(WAZ-mQao>)_owc()GTt%G;#;N3cSw+`N| zgLmuT-8y);4&JSUckAHYI(WAZ-mQao>)_owc()GTt%G;#;N3cSw+`N|gLmuT-8y); z4&IgWC9S^EX8M&YZKj_RYcu_rSexnR#M(?hDAs2BWhrf@U)|7V`js(lrXO5Ofy1c4 z!D?kxU}3c~D)6vc85NjVt&9p>EF}=50voH9QGt)u%BaA|YGqX5WVJFXu(A|Mj0(K0 zRz?M8Rx6_dH>;IVft}ULsKC!sMlmWdv|1SzI9jcY3M{QwMg^W$E29EaOQFT6z}0GH zRA6hhGAi)3S{W4>Tdj-=oGqmoqXKKIl~IAW)yk;A+-hZ1;BK`tDzLW{Ym5r~tyV?_ z23ISi0*9-WQGvzP%BaBOQqD0dFu7V86}Vijj0$Y7Rz?LrS1Y3eqidO-vP0sa_ZSF)n}BC8R=t2`ux^q5gSW@L{U-D5`hm{C4vq>mZxV@CX# zQ9n}ts0)SHAgMm1amL%m^JbO2>@UF{5?Nh#fO($Bf)Dqj$^*9y5x^ zjN~z+dCZ6&Gpfgo>@lNz%m^Pd%EyfKF{6FVh##qb6kUPasee=}u0bjw)r!$h4WwGZ z?NmXk72Hl8q*#+tv5cn_QpT8ZD27xkW*o{P)ruL1f=IPu#-SuqT#nIBQKVWK70Y<) zB4y0JVi`|qq>LFA%XrEmHN!B2EaNGGlrj5?Wjs}oGG+!@##0EXnT>r#Zl@Mf#vE5H zWy}n+jHlF5 z#>^ngc&Z&`%nY(_r{Gbw7&FMaotj4(GlQ(#sdJPuGswD~Qb*N*$RNdzYDHgB?x6v^qFT{cR4}Rp#<-$}QLV@zRg7w7 zR9vE=Hc`gxD=yJcai~h1QE`c;NN%T)Q2R0}a(j{7UgQ!@k=$M+w->oYQzW++$?Zkf z?L~5Xk=$P75>1iZPFiL)}RuD(Vf@ ziVRY5s8;k9HHT`&xGJ%3FOl0ztlKF()crj&Na>+k(N`tb?NlGim{F12DL~Xh0!BL} zh-zh2tlLZE_7b_h#JasiZZDDBORU>VDN)FsN8HD%S0kB+8gkv2NFrYkE?_vo6fDY^eZ{(+nnfA2uUNNJwkTuv73+2?7qu=(W?yMj2yVQPHSYWRRLh zwW6=6YSeNp##QDw^eAkUG5d<#PHm%%85Oym@DFuQ|YM1Y4#Plonl8Bv#-eQ)H}+UeZ{(+l1CY{ugLA0Ct{R6=BazsCi{xqPU)kJ z*;nLtsvosf&%PqJQvfMr_7%CE8b}$lugL9`LCTnY#k!qJNIg&BxMJN-F{F&i46NI! zhmwcHk}^gHDU;Mw9rP82l4?a%luD`<8KhWJt>`PtCDqEPShrI#sb@_XSJX_Z6&a*z zQmyDK>L%5SaYf~%9%>^nOooY%w;$vTt+bO7&F~=3TotjD+GlS%I$|_~daYb&YvQp0>nL*a=6j#ca z8RVJ_^_4Pa2Dv6fiKU)@GJ{-`p~_Om%plifsH~JRGsra=iYxWpl^JB+PJN|}nL*a= zlvv7`8D!l~m8FcCLDuaQTI%62GswD~T1y!-gRI*rx0Eq6$hw`1OFfik23faLbSYzI zkaatCmojDsS+`SqsfXdnAjOwzMPE^Vsa8Zq0j64!K}s;yioT)V*Z2D{3>< z%Dy7EQ=TbfW{})Yg{F+zSLAkzH1!$<`-gP`oqEFveMN<*S`igBo@zw~sq$1S`ieSFwK6JlJEfj_?+N3I zVo$XqgOq!!6@5j)r&=+tDEZV2R>&YlpK4`PU%nY(_r!Z8;%pmJ_YD4ud9y7?go$^o_GlQ(#sSuShGswD~B2m2q$PBV> zr%qJH%pmJ_N=0SN46<&gT2#i&AnSGtM)jg1GswD~no$`ugRI*r8gg`{dl1}P;~EBcCJQoSF_ zs93jCPby=KD=JFWiVRXys#f$BRi$dhxT3C9Z>=JORFvn2W zWy}n+Zl^p|Z=ExPtlOzjl`%8Ox}73b88d^Z+x2>Ty05gE&Y(8aQE4;XSK3TxP@Czf zw3+TJZKgA*&2&`SO!t-EvS(D_cC|7paJyO=6}Vlkj0)VYRz?MG*USEl3f!(%Mg?wH zE2Bc)u2x0`ZdWU#0=G+Zz^G8StCdlK+ttdb!0l>fRH)n4%BaBYl0GmhaJyO=73y}i zGAeMpS{W6%U9F4?b-NT8j0)VYRz?MGS1Y4J-L6(f1#VX>qXM@}2*RjPx2u&=f!o!} zsKD)NWmKr!)yk;A?b4$#Dsa1685QbwwK6JjyIL6)xLvJ`3U#|=F^me_u2x0`ZdWU# zLfx)bMg?wHE29FpOP#~0P`9gE>*4IcIr~q3T~$^RUdI)5#WKhzk)LU?B zMFy!$RV$++w^NrYWA+ufow`&hw-^<QbdPuk=yl1494VkJp^JaGe~ZyE>(I#_7%CEx>OmnugL9M zMMW6ocIr|kOk`h?+o?;HG5d<#PF<=rj_fOPyY93RB)OfsRJmdX$?epo%9t4>x2s}K zA6JztHxZR;HMXL!RGqLD8Pt5tRz#&42(7H!#kp){RIJ;nOO<$(QIXrJOO-LBV%<(% zs&uG~irh|Js*KrJOl6gXDJV zQq>vEAi15oR2egaOl6gXDJVQf15xlG~|El`%6&Zl^9)Ba|6r-A-MqjF~}lJ9VitW(LXa)TK(X%?y&; zsY{hHGe~ZyE>*_NAi15oR9%tGAi15oR2egatlOzel`%8Ox}CaI86$(#rHUueSJb7d z6;V-_s#au>x>U8Iuc%8^E2AQ}Q2B}L` zE2H9?40Wk8W?yklhPqT4Gb*mhP?sw5W(HZeQQZIQ4026|x>Ws`0sD$| zJ9Vit=D1?rPF<>unL*a=)TPRp%t>`Q2Qq_v6s7qBVGDuyjTG3b3rK-wfRIJ;nOO-Lk6?LgQdFps93jCmnviS73+5DQf17jShrJ`s$Xhh23faLmnviS73+5DQf15x zvTmm?Rh2mVigi16sWRrcV%<(%s*IUI*6q}#>JEV8igi16sWN5;S+`S{Dr07lbvr+p zDPv}kbvwVAsh`hb23fcBvzanx23fcB%b7A}23faLm#W(%W{`C|b*VCD23faLmnvgs zkaatCsk$v=23faLmnvgskaatCsWN5;S+`S{Dr07lbvt#b`b{HdkaatCsWN5;S+`S{ zDr07lbvt#bx@ScOsY_KW`ii<#wIV9&Qq_tKQkSY$^c8ifx^-q$tlOzel`+N@b*XAa z2B}L`EBcDMRQ;3`#uas`YDEUAOI0hQV%<(%s*KrJtlOze)ye^*V%<(%s*IUI*6sZI zri|HFtlRkkP8lT+Fp|7Y*RV$*RE>*3_Aa$u~MPE^us#ZqDx}CaI{Z1an z6?LgU7dTv3;*g->LVx>U6?D%S1PrOKFn#k!rkR2efW*6q}#YB`n} zWZh0(s*KrJtlOzel`%8Ox}CaI{X8N2igi16sWRrcV%<(%s*IUI*6q}#%9!Jdbvt#b zTB~LTS+`S{Dr07lbvt#bGG+!@w^Ns@HF9QQeRK zf*EApPF<>unL*a=)TPRp8D!l~U8;V_6B(o~RjueN>QdE;sHjU-D>6u3s#?)k)TQdV z4WnY+PF<>uF|MdfRVy+`U8-8qSJb7d72}G!R6R{X2B}L`E2CoFPF<>u*;lOFsY{hH zqhj4oU8;V2l^JB+&aZ~bn0>{%ogWUBF*C@zo!<`Cvo-b=>vn!VRK^@vtlRkoQ5iFX ztlRk!Q9aY+xMJPT?}*Bn8D!ngPl?Ky8D!nguZhZ-8D!ng4~pssahXBZ?fj;wjF~~! z?fk5$jF~~!?fkN+9(Xc?tlRlm#S9u z6?Lg*4QD}I4g55pN1>vrl=WsGq}U8-7lvM8mFe=vV{4S}CnL*a={4}YI*;lOF`E^nmGlQ(# z`GHcsu)w}z-Og{6%9!Jdbvr*(Dr07lbvwUQsvq#?xMJPTkCn=p8D!ng@0H4!8D!ng zPnPNh4Q7yaJHJ{gV`h+bJ3m}1V`h+bJHK5jV`h+bJ3n8lw@8>l*6sX)sf?LH*6sX= zsf?LH*6sX`seV*1U6?D%S1%=BbR?SFGFl*;5%aD%S1%@~K|1Vg^~a z^WW?!*x=l4%#%nY(_=OvnzxRmL1wtlRk^R2egatlRl5R2g$zp>EgX zP^Ju2uU19{W3{sDV60Z=7shJkDZp5*?B}d;rj=uZcLe<6s@|vL*x(%jKe{Soo*?fC z_}x_*Gb-K@@YAdMz68ex?+EzyRT(o2yd&TTSY^zPLo{Jkaq<9B&&>>LEaJYtE@6+26;!o53}m!%5(;` znT|@E>AuotI)mCwN2SelUuiR)L2ag^(q_7^w3*JJ-tlBqxFb+2qrz&BS{W5qd(_IP zu-cr*WmK3o z)XJzZYp9h`;f_Er-ZCo88fs-!m^IYOsBlN1Rz`(cL#>PovxeUPWmK3Z)XJzZ5vY|> zf$G)DsKEScWmMpHy@t%F!0l>fRJbEhE29FptCdlK+ttdba7UoGp&1ppU9F4?+^$we zg*yVZGAeMpS{W6%T`y%bD%=sMl~IA))yk;A?P_IIxFb+2qXM_<-Ec+)ZdWU#!X1HH z85Ou)t&9rXu2x2cI|6+=2~kn;s#Zir#j9Eo6&0^)MO0M0sufXD@v1lF85OxbPj08; zRr@k3ayu2TGRBNU#j9E|<52Oc_xCXmQt_%*%!5?CsulAf6|ZV#ROEIlUZopgUy<9X zc$G1uBDYiVDr07l+|Dnt%9wpcZs(U+C2rujBDd?)9Vro??9R6|a(dFe-986|XX8ROEIlUS-U_BDYiV zDv1e4JGosyFpAzFw^Q*dSL`ctI~A`oW?zxpsd$y}g?&YCr{Yz{>??9R6|XX8Uy<9X zc$MIWeMN5Pmsn-Y(N1pXmsn*?W+1ooORN(5V6;>5s#f$B6|ZVVR8+jG6&a-BRjueN zDqf{GVpQaIsr}OfiC<#XzKn|8&M&db7=1;>t6DLxsCbnM3K`^=Sk;QrPQ|NQF|MRG z#2Jy>`6X8E%c#ihQg*==>vk$$^+iGU73+2?US-U_V%<)~t3+VTAnSI${f{8Y?Nq$V z75j?ZPQ|N?Ij+dDFuQ}HTg9{Y;iu6G3xB)Of6SGi&a$?a6U%9t4>x9gc7j?21T&%oHq z46<(Lmslk|WCmHc^GmEUW(HZeYt(NSXS6$0iMn!I?;#GQ3Mn!I?;#J0sifb~W zeuTj_8GeaXA4Fwek=ywtRvELeShw>_tkSTuugLBE603~aSLF60xt)qv?aHXg?Nq!< zO3SFo?Nq$Vm{GBAr{Yz{%plifsCbnzGe~Zy;#FtQam6(mDqdyG(N1os;#I~R?c{bU zUe)UySLAjoUS-U2MQ*3!RmRL9xt)qvWt$l!w^Q*dV`h+bI~A`oW(HZeQ}HTeW{})Y z#j8FJ%nY(_r{Yz{%pkd)idPvkgXDH9UUexjgXDH9US-S-lG~|xl`%6&Zl~f^mmo4o z#j9G;S5&;J6;V;~s#au>idVIwuc&y{H<=j~xt)qv8Dm^g@v2s2kcwBeqOYiURV&67 z6|dqaWRQwiwK6JlI~A`oW?zxpsd$w!qawFc@haA22FdMIyvmq;#Wfi!US-S-a!rPc zSA92|eZ{(+idPwPT(NGa;#J1XAnSH2UNuW_T(NGa;#J1XAnSHLOij=1RJ>|mMn!I? z;#HFsqawFc@hW3RMQ*3!RmL1whysd$w!Ge~Zy;#J1XAi152R~aLN zRJ<0cc-1B{NX4sK(N|QwsufXD@v2s2kcwB;Q|K!yUe(H|$n8|T${6E{idVHFgH*h# zm3_s!or+f#eT<6SPQ|N?85OymidPx4ugL9GycYQ-R&6pWay!4oDq}`PZs(U+Wz49^ z?fep}YF=D}RJ^Jcqn(OZwK6K!?Nq$Vm{GBAr{YzmH=|vk$$WlUyZ-A=`; zjCl>RZl~f^w?`c9tlO!0l`+Q^>vk$$Wz2EKx}A#GVwv1d#j9G`SLAjoUS-U_BDYiV zDr58&6|cHG#JHm3Rjr7MidVIwuc&xcE5;QSuWDsfiWz4?fnhX`M zGGn;MDqgkH$qbU)sd$w!`-?R~e(PsCZQ?#uXK>YDEUAc-3??9Rzr-qI_7%CEUt*Op`-OmH8OZI_rOKGsAi15o)FQvcs!c{kZs(U+Wz49^ z?fep}j2RWVonKfR9KTyE29FptCdlK+ttdbuqLBdnHUx7cC|7paJyO=6}Vlkj0$zT zS{W6%U2jw|Dsa1685QbwwK6JjyIL6)xLvJ`3U#|)>|#_{lTj<90=KJ`QGwgl%BZj= zqgF-*ZrA%|j0)VYRz`)oU9F4?+^$we1#VX>qe9)T*W4HtxLvJ`3f!(%Muoavt&9rX zu2x2cH5t8a$Ed*VYGqX5cC|7ptjVaAQGwgl%BaBYdg+f*p>9_zqXM_9l~IA))yk+) zx2qLVQJ1QB4-pl0scJ=3)TOEwQBjwwRzyWzs#+Npxt(8PEm4=MO-4m-=a*Pzj2VZz zRJCHpp)OUgUSb}kE>*3V2dPU{E9OD!Qq{_+$nAM@J9Vjg1C&vb+o?;HG5d<#PF<>u z85Oymx>Ol6gXDJVQuUfD`-w;mnvgsklapPs*IUIayxaYGG+$J?bM~}-C<^s+@2@5QOl6Dsnq@sd|N(QIXrJOO-Ll6}g?dR2g$zk=vMQ*1q zRmL1wX!;T^}+* z%;a|JQss&nB)3zSDr07l+)iDpqz~*XayxaYGG@U2t5k$>^s{*otvQU8n;M>QYPm600^D6}eryO>`Z(ow`)HVg||W)TK%k#l9l9OMivqlG~+6Vk`TK+%ByP zjoDY^cF8JWOm3$xRYER~D{?z^sWRrcBDYhQDr1gza=YHdMUdon>QYO3%ag|JD{?z^ zsoIx)MQ+!tZ17BOr!G~pHue?kcIr}P%)VmXu6Iy)T=o^YU9UU9n033J_p_CKMQ+!# zWEyi^k=yl96UO9rJ*Z)ZCD{?z^snT>ZDz3?BVn9^ncIr~)ihaepow`&Rv#(gUQco#Tq!PF<>uIj+d<)TPRp7CN&frO{*)8VJ{oF01V zsMF~M!Gc~*xor_2Z;j24w;J8C=FC)krWN7V%r0Sb zs-w(LFHB9%%}z)A*s!pBiIT`LQjnsi_P+KiBNEw$HZ5cf#Av znaK?=>W$`fXR6iMGCSAaK07ntoNA2Eo;hP^+<8}43!R;8)US=^+~&3pzR@?Kdm9U5 zTbgsBvqSgjUlUWa&H3oPyU^*(&doPwH+n8YJDo}&-A?J6vcSs^iFHiOme;R3^w`X0FB0}hiyJAiT#A54wcVo>(WM}Ip1i_ zPPozL>1Owg-oHDIPIIoSPHi>ETXPX!Xitqdwk%9cOgCp7Q|iXfXmn;K-E>p11@r zc&nq6&?Te6w=uA_!)#;wRC}}gooq}r+fxg3;ks>hXJfxjw&rWmqCLTWo)nNIcBXY! zj3Qw3t!~`hQkZH?Hm9eXjkbFUh{e)F#^)M3%>3*bt$5Tef`Vt?r#(J4pB@m&H9FmF zNAq^Kk*_pT!0DUYP2-Y=jXLj~&FaZGc#@7v||ry2%)zhQg5BBf#tpJ@l6%B z*jc8wlXHyQVe~g=#v7g4?!1mN*6N0cb^ZQ#2DMCk>ik0AOr%eEc458|x;_j6_h}bK z7rMc4_>v{Im znwV=&$Ln#E7%A@P&C|`fGkO=QM%uUna1(ICWNW51=j7Z7Q;Cshpo=Yy!mjq%vb9l2 z4-)L7Hs7bar9CmP3xB3HzDctJu84G7@^7}zkGE&Tlr^=vId(=IUfl%U;3wM-XBH2b z7=%u9U}2^+*V3Wq+nuRx;$pjoaQ*MvBuxXmw%A~Uz)o(Dg^0mvu^fO8ysGrTo zp|)?{1>bHTZ%s6Hj*anlcfL8(8zw#6xYfI4d$4M3Zg%5nGC;I4*Ni5Sn(dN}s8e)$ zSBH8#JM*47rxB~U$%r}Wl+?MYe-Z<4wk)QQ7B{G*rt z8_hBCi;A!IOc)1^nYmln2R44T7I$Z+WFMN|AFfbiB zQ-{d)hVjmh=O>$~u@+`@ZBC0~y4_9TvhoqXb3@CynJsqp>~be-^4-o1#m!i;uu1=) zqDjGCjE9-0SEKI?Z%S&!>87#bF51=9Y0p^ErRH#noYtN^&C4)Hd@?=!AZ7w;9vU>{L^?5v`4IV>t0ple%iUjq&E(neCazmWgg>YN4CkW#dfi z%&ij}ZPU#&lG`y|shY_{H0tDzrju=|7&qyZ=Qj=Ijy$PxY*Y6`jT03_1Y=KxTM%Wh z8}W&@N}sKYw>hISt~J#fo1YbDn$@Oucdlt3pS>_222A6G){KhzxHn_oI-!!AkoH+P zOY?Fkr!Z$!#%i+Z{cF?M>{QaxlT+I|Te?%TnvufoPmDFO);Vr8hbyP0$*4WAbFkfY zw#T*3Y#bF_oG2GSlO9NpBC``(FH*nHH;!#7v18qy;UVMW4O{!`1;mrE!aS*_r$v}a zSVwn{ZH}GL>dr3A>68>?@2pgk>27&OrPBs9a$;jbEZgW#gkDYtppG?@6r3G(@P=h# zwpmrI3NakRVq;*s3U%M$jHxIx^k*{IPPpVX9u_He9#j4w=2Z)?~-5lgQ6Q2xJY zL05n-nA8!p_@V2w&*81noT-z}(8V)3-BLK=Na@CSBtI{W4UG^BCh5F+C*xdu zc3X2>+oH(O*k0&ZV<%ET#vp!6r)j$+?dCH% z&dj)Ob#{V7f2jXr7idK2zEs065|x6nu8$Vo`PQ^$G-V>nlk@)%d5vCrW6GTG99A+= z>XnI5h^uSl6@hj66c91!aM^tMhhY^R1K)ViD|33mE_Os1&#CKXr>1aWrml-VyRmZk_s&#G38q#>d-}?QpFW+BuZ~Nu?BOoW#Oz*fsZs-5O(>ee5Fc zZ6%jfZ!1~oQzWgkHtZ8DS&a#1Ve|zgOlPV#4n6$HWV&vgm`uUR_EBLCXJNyfVYiSBt<(hOOU|s*ZKyO(IqxQLFw^qe zn8sJ!OaHb?!~(G)n#3pD>1j=&VT?y>0AZvGIJ4EB+%ms4sT0$N{czQmiCNfP|JHOL z7I=DlW}xJ9P+gR)N`x@BgjJ|;YuX$?eL+i?0gD%R3bN}nOk0z>GGhF_sv&0GB8jyG z(1~$fOriV@z1#B;!rSPRFgQ}18x?CfpkJT8jQchRRLo}&-P%>X-Lkqjcu9Qre1r@- z*=U(Bo|e+x8$ovBx}%xOb!N{LKg_pgHnJbg0waot!p@O!;uNIOV7AFAsyVE~r&4RM zEg_}g#9=ZBlPNwt8j7K96b{MB#L6LTBwS>}Zd}D=vWt208!+{LD4=@2GPdC!&Qp|_ zB-_r031D3j*iKBK65q}_G3#}=9kAEGNn>v-HX}l_X_Ya*7EhZ)_?(K0d7r z%!BSo<1`+2Nyg*>O(F8q5VkD1}LRV95KGa77R6ef|_vR&_-8op4PFt6%b z%R zk{;%D9(M?xv$JQr=?{})I>zwtWUzz>e|hbxiO;%NebJohD(Z0aD#CF%Nvg%D!=&g; zTcERzZ5vzK4uc{*GU<-ZwL7XJ!+JyrHa%V}@C@jK$Xz6f_kYK^$~S|zd80QS-ob~rMYxUud9nAnFiK|vdw1Tf4-noPBo{3||I-2kS$o713GBfCH{ zcFC9wqb~71KRcG*C0Sxh17V5|%fzj5FL%<7y&m>w$sx8LmN~3AF}-WpOj-2$<>*kw z2S2HzJ#5AS6-w#!wjVA~(8T=Y_O@2za?LQK>2;FrVOb|&4L#$UO%{2Qtz?BEHd(RZ zC!q1GyT8^Esy$;nmT9L%8k@>FJ>1hea77D*+AQYAlg(0YW0l+eD>((SV zZ)%Y^%pf*>1tf&-V=uAjUEZ9^_`!n8%#ET@^tE-a<7sG^}G1y~>BnRFT$pd=bTDRtsHJ7^t;K}oTok!1FcbgjdEp>1j(ws(M^ESOJKxk}M7mLq@*g5gH ztA=buI(7o>WNRcC>bcY*H$Z=&GM{fGHyyg2NxC4l&5<4|9$2P{_rYmiBi6wT;B zrxzQNBZd9vRGRB`G#xa#Uc+gtbP(=EmU zVR$81hmF>x?uyp&?h*%#;4ZwQ$rmsR$*N!=D*aDAx3S%x(Kw%fhL0edv9v_3Hnxw8qS$8ihON7xNyGMeKK zNmqok5TTsdYK|SIOHz07$)K=P>!l}oxaK!q*<(g(&5o~#r$UUKA0zO$+iWpBW!F2E zeEMKIAKl38JZ$|u!aaQQvYffghLh}<$9PXWOl2iT8M9_3a3ocQX^&WaO-bvax(vby zN>;VR`QgaDedk)*UFE;JIl0T%NQo;rg}B~*cyFyH3%QN^ zYQ0YsZfZkbPYA0Y;oydJW;;hF#Fv5=x?7I!t%9bVgjMY@*pqA}r0w&4cuOdG8Pf;P zNqG#P=tF0rZkXHF2`rO}Ku6p843kUD_RzavPWNHAUQZ@3OorZ26q*{sqPd|HN$_=cyxCxx}ALhm9RTi6^R%|wUk z&C13}v*Bb_3ME;zhou{Bc8Ju-OHMBaHc6IFm&W<7<;f0CRkk3I*64p+3Txz0(2!!#gHOa_Er&TWn# zsYl5thQY5InA<~{?R;jtppy&pN8&itN!kv5qRLqHj$W>?H{H7a7N|dLMMJ2|!t1KN zJ$*6?Z^-H);=;^W;9ql^G--Q)p!Zr(WAx0GLZl%&@;wW6K8yO9pd zW{{2e{S|_637Yrh@m9~ld_HU!Hx3szlX=6e=kv*n209VneZG)9Tu5z?%`Sv~3nrn- zXEM9t36Uj_uENCB&dOZ`i_eb(5&;~8?i*#FV+poD^|H!R|^eQN|HFY(BRO;q5&S(FE@5{*h{En zTfKr)eE=&??f4KVt9??6n~Sn zapIY+R;O`vxQWkTCu-#{iyf}jp)4j+>g1lWp?^qu+zCy5?t~^@cR~}t>WhO=`2>P$R@}$SJ;Wd}Wh#qI+n)vUVZQkky=@E33XU{_QkKPG~ zeRU; z$M0gZw;-i}lJ*{;h|@dtP9Wu^H%?La@Cu*V~2!eZOn)< z<2|#T&!0{4aKTM-?-<8lZ?#kJdg|@q<~C83&XBMjSPPQj)-2VSAhuI-;iUJPol6)* z%Q&hs;!Yivzw^l(cytt(24&aPMcunMv9(sMvsq=QCAnQ&XqTk)lR@tS->4Pr-O*Ta z#8?vF;lRDdNGu7YSt9` z0oBO5i%1U-4_}U-eZhm9__;Pp`P7%2p+Un2H)TC^C_|TcXTuhH3mjDNSwm|q>}I`x z99rVd7(KQ^BrHz2Y!>|5=+@2W1s}j}5W4zv0H)R$CmkPJKLq|68}?~c(Zd!IU{<^% zroc9ZFuLPZUEps+#E;mLwnG8S)8_@xw@xtwaiq9^r(=uhihKsRmFL62@y@6F=kZfz zlnfcs61woK+oyvG1O6;11&Pk?70r!I6)Ocf;d>{-?cpntv58{J(WG`Giq`Z~*pmgMf!b zjV`868kw-b&Jo^9gx|^OzqXuAT9%WR^GOR7q8|vPK506hHch|RNJPVf0pk^|x3&?4 z_0;$3@)M}V*Gt+612UQP@g%|nu$>~Bx{560n0Nc51OC4~UZo#pvlsvEeA0IG-_9m& zrx^0;&ZaGjt1WU-uJ#|mWOqWEPcvz@SpY1uXT`h)OvEG&4Aj7+#Lk3TE0Dv!@r-p6 zXcb`X+pFbfNJCN|SBfXPEt0T)>RzwU+c9YKYtukE$D`31w1F$x&o6?v!p}sm+x7D_ z1#;cNeoG0AhOm1y*Iq{%`^OYQWzcNjBdLi&K_;-1<41!e%L{H;MMbRbfdWTH(iqzY z?gZ}=rld1yB;nNoa8axw0OJdl8k7cp2$DZ-zgBloW*6(w(AHB8&R&dL-bl`rGDq;Y zmOOnF96VBno{mFBzw%ZER}?{dT;|)D{1F7b-KN|+muZLZSG?ESWp~%qsso5tr~@cK zy}#c@EFy{nZgy3D9f=3&B+--0>k=KyK<=O?o>gkS;m-hUL zps3M+h(xb;4z~}43Li)SV#aNkqMs&(qs3jFdr0ObCM_N}!EHKbx7ZLzqgR!Xs~(_E zIX0}S0cul{cgtps@?{yP%q3*McFD^$ngH1*RV2vxH3*$wZa5EZ8JA$#(zp;fT-Wv* z#w2rlYZ~WJ`=nX&ww5t&VC_)lEf zCkr}w`+}9f{iy$5d~J^}zM*U?!8|{NE<48*Q0osnbn3O& zMQN=I5h#;C;q&$?EUIzCw7{`FmJ@Vu^hRu(7|5`W6a;b2yP9omF@Jy4eHj{}iyHDP zvkT50K7FZ!e(5~qS=divh6Gx>J>%4E;QIqI5Q5;hR%U-wfNhvSE7UBtkaGgAR|Ff^l*czJuJ#T^m~4l(-_c)m;qxr7me> z8TrYdYAN1xO8(Ndh1sb#_CY~i*Xoad@NrRYjMAQfKec$ZRmlHG1pNHH1pKtFKVMjL z7_f_ffaHSeUf#wR*2FLB z_KWYJ{kmRnq^IVd$g7SqQkNJ~#G+ikGT|X!fWC1XVl-`@wm7sBRqR>F^eVd8s7)E5 z1U6pX1bCrPBYC0d|73Re-0IhDoV1gX>(91KBUGQ%Q!G~CY^ z+NQEYWtz5j32n^igU~@7BQIoJJ0R=<)EZ7L4Hw}1i=wI9q;I4l{OXs zrU=&HJ`;r-fd8u!CHx?1k}vAtV%B{g&N*X)hJng)HRx)soO{ubr+3G|fHa~oj_yoq z@=zE}@`QXM2hS(PBEirBlK~-W%rI6y+BH_hR9bt?^pWO@0LqQ$cWh{lht+f2U^T>T zO*q7w%CWT9hF`Iv!zVJ%(IN!T`P$P|uOq4quH*1K-^Z8=FQz+5zu{80anp?&$H^Bz z#u?C}@X>*q#`~jR2_X12ppkLJ0jH^{0YV0k)I2%F*1rJ``AJx~1X{T1s&@cA55F}1 ziU7PVv{~it*wmtQJow=k7%Jg|daU}L5N~>+&g7sr4ZEq&#O4PvuTM>-86O#sdH7_z znraw#krD3y(>07~dC~|<|Ey_1JN!GeJ#Gnim~Y)fFeBh9wB;IrWQy8AQ{R?|P`6Xy>IVk~!@gr2Af{#bL=ss(B9LzIh+0TTh0V4B1oWy0&)6b`9US z+Frg$tpP#TN5k(Dg$1bTlKQaxw^rKZ=GCs!VkNgvA!)7CrE(t+oz5x3bX@yA?AYBK zgigeNf+sKG^`%a7Yg*>HWoZnc7C#m)r9tiG<_f-IkqbJy3z` z1qAL-W8)Gt0>}Wge6ctyLmjTuQ0GxRB0L0(CxTpz=vNIlw{}@Ee)j@! zg=oyMjmK$IA$cN94BnPt{OSbOcG?zs9;FV3!OyABaIQrvoDJ`-iyaYI515;jILFRr zt3=!Ry`?g8=WQ<4SB5TUB+i#^AGIV|V$ssHsUDJ3J%Ib-l0Ev|R=rVX`QdFQ7TMH? zhBE*>7AW|BoLOeR4HQH>m(8UHLy3c=eh>dGt}|>?&+D*s3p$B1Z1?oYISrlH-5{tr zhi9wuY_kRe2}s@%kBLfsi>B>r=yP~*xkWhHlc$5DsRy#o5Pft?o_~OIb5Gu{|6s2w zXhzcZw)mrSa(>6tazj-K64yKQ2q7S}cQ(p@KD?tM@Fn0G>*ap01jI}ZNNd0wcd2!! za*#pYIhp=5>YyCZIbz{WH^%i6XV*Q>{gG&aR8*+m{3{e?xq4dvA%7t-r(*pBLC0wv zMyC2<3QHh;^MVTw(&AO=aCsRmmpihdqRy&~Ky>k$4augaN#u&)85CAUCDRJ)ZoO=j z2Z%Y4Zs@9Q))IR^ns&r_C17IP$R-p?I7me)jcA3Jt*R0XGhB6>bXtnpj?dYo3iI{- z%WYiVNVqm$@Vv8>%sAl&GKWY=RZoY1(uYafe|`X&A=#qHi~O(Z(xQ4$`#uF0Pj#Im zBzBxO&C4ihlKL&FQzEQV4lCr7NXY7=M?!gfOEXWGp8CYPwqpxr45LUudx@d|C*coW zPznNgA09~2|`nCHtfV%lqF~l zr`r7S`Dv*<*Phb-JQDej>!U_#6a*PtsMI)VDTU+PFsG__CUS7`cnejkYhULBJ{2aa zFs+ry6%R-DY7J_@NGjem*QJsw3xt(I_JUL-3cJ_umxnv0x+@x$_U`uR0F4n=mWqZJ zi|aht8D*He=fsu-KSSVE%;$NSxoR}D#X;SurKj0b%&e34m4@oc2;dr=`yC(CsY1O%iW?yjYjZRmUE#mvJa7>LXk~V;I}< z6`c9DpAWW5arg=DMA_@g{b(#nloo1VJS#;`$AvDIDuWWQhbJCsh`%6QN<14-FVz3f z#?LhB>@>KqG(POu`2jiA;7kfzKH`-OQs;+&wqPl!CUl)#7?L+3om;T1Jd@gi9#!5! z#~_yu_6i+DqgNX+CBsOotk>bBgX(;{C>+|NLqTUpQ3R*$4`p3df>IL+5d!{hFJYun zp-l!RbvB;rTtHsM*X&>M;ly-E=N8$stFDAIaDQL zfkGSJK%$cOtJQ%*gNS&!-HS7>&1_Q44{v!`ss_5$sU!4k(ii94kPAqJ%K;P36(Cnd zigjKLHPsC-v1hB;#PP-*Etf&la!VqaxGT;aoQin0Rcs{Oa@--bHaAmGMy8^Fkbk*R~nwEhzd;BKy{*F#Lt;v>~>~ zg~iqFpC?FL(#EzCsuv~Ti;903LU{v*O$h$DL@UN?Z9jZoXTV4YUCWaJzOM3K9f=r| z)=aIiEY3mg-k6IUebWYUm=XM%+yce_MB89~H>Mq}`33iWGf?owGMNcgH-1cQT0qakEFU4cj1Db6wfTu??e zGZg$b&U10B{t5aTwAt-H)V6Lq<KoTSF-qE($7Pi|kOIap z+%0KEE*^%FF{!3oa3UD7-%&4Y{IW#g{Jf}whUcrpWt4av8W2O;#|wdKHwpb)8s}7d zr%Eklo~3I!^hFey90o)lCfU2NU0}Kn&mo`dk$AFOp2OhOLC~9oBBJOC&;>{#QlD|D z(^OW6t0=}ceo=x&3>$jH_SW{iA(__^GXeYws0+)6L)koR+s4Cg+GRdpEut3eIIB=b z#9|eCs)7nH&$n9gKzrm%uMSQ31;#1MZ+pURl1?D#IXYToepQgT0W*MHE7mkk&0?uq zx0wZNxV7*Ts7Ie)m=1+wh=Z(Zf~%%t1EXEN6vac~)Cm{QmjvSHhUZ@QAf#07$ zVJs(rRQ49sqTYLb5c6cYVuYin(J&JJmyCo4TP8Z4bS$y0$k5mFl#Sl(9u?(p5@%}-?5AE+AU#}gR;}rY3D1DBR(1Gz&}VQ zjTxn;;q?2PEG#Zoy4YW@3L%UY7UsNS@hC<)%mn5crAVyES$H^R%B^UV>IM`4FiIWQ zSsitBb`FjF@CPhV!&O)EVE=N90vi(nnTM!}6+jgHdD-yAQNCaB@JxhU^UUHC3l`J` z*VEo^r-U!NSpBM(ElSWj;3DIWqjO=yvL-=^G<`|M)#i|iKdyE4z`-qfHi-$OY<=3^8P^efLrS==9S@_hp-F^eU8lDx?BS03Lb|UmMG^gfU~|}F8$WY zY7X@^>NAGKTZHNX3^KAhg11vNzV4v3Wjj>^<9hKg^turML+g};m&h;S@R3d+4|ynp zu!i23I6@6befVr9tW=7Jh5r+QW1D4MO2tRn(D8yCuY!de#f!xf&2-vv%E_ak2r>fu zBt=p|r@>E)iE8TWMprVagiVsLI6)~Uc*8rvOu-h46%6UK$&*>_f4n$^2Et3WWDvt` zygVZzSgGvQY4~g%iv<)dPO;*=CYrW0=5kbD)nG;McMpV7iE*tfqkME>9GNF9?9B^C zk;423-o{sp@a+p+|89{&VQ34mw9)gSEmoBE&XlhTdF@zGh=V~tD_2>sw8YnFcX7{? zTkMUfz7qqLuNq${wW8CIz=>`{8xc@dNuavdt9&u^cGUbG-#+5njn@$5V{A9SO^)C< zV0E>P?QUPl_xN~mu6vB)Bg_I%X>2~eLUF(EWDx9lg9@RlKrRimF)!xxhJz{%BWQz# z1K5XEp@2mUWlsu5Zb;ZxicaXT(egEw3IwFMyMiAYUKXQY(N=9bq?n7aTo@fENG@Xy z?iPdrA_&idDAGh6VXzIA;v$p#^ox0&$6+1OB6^)gmTVClHCa^z@wxqiHmexhkaN~DX+AanC@NPEV0XGg%kVKcY$9&tZHp6jOtFcmOK%p`cC zu;&xgmo#Z|tZ3NQCUU$iWz>f)MCwnBT~@G}tr(66Vm2k+izD!8aD2jsuuniCgtqAd z4lyl`yKsE=3guZG<;^-i6M@OE35nZplWyp5%D3Y)?PRbIg%_0>Jml6Y9Mn5{y*Mak zFxr(kJX<&4fp!NsM}@o3P%NY5pnc{`X=dkA$LRpt`zZO|l&~J99{NsJFSwF9x%(Jv zixjd-V)mlKFhGHrr$+XSgrnZH9XgjXcbvy!b%y>XHON8WBrLxT)Ilz}|GCuYW5aQ9 zhq4rBmzDPJI+Ke9s;a0fJ%AL(!Uj?qU@0*zsyVWpTx=CbY3Tnjui$tknv-RP#qn7| zyT!#Jn11s`F>JTe$#7USMMijvuz_V#mC#aZCNEZD3X-8b&Bg&UNWbwdmWc+A7 zfckT(cr<>z_H5TF&q3eB+e8x{wX5w4NM__H(Rd44OY>b>3%obRX`|e9KIrf|KjsT4 z1n8eZ6w|+tz!e#>LinT99b0EQc5)=+ZT|GBG1kyx8#kEfo4X%`O@fmxb**8E>Jm-n zJ_&|b7B)$=@bmXE4B+V0ChQ!eYi%r#IW$ z%Ww&d>Yc&I%-?J0(_TC-S1@j1^D-+-mZdRm;ITGv&_X3<#TkT?W4QuMnU%vUi4&05 zhtCgE5Xc7?P87`Fkc5NEiH4y9+mF2SM*h0*L8sDZf61f-@h-(xt$o!^H`#Dme}xn$7JH`l%0^g}7}tyj42er9_!HGj$%l zGcS*AR{*BSHY@DN&4_{9fVjyPq^T6u72&mKo5O?u`9FuFIQJFHEhSGyUU!YS%U`oz zk$Mds>jkrVa~IJSibZ9!zJCw_;}2ppcyj z-{JYy>!)mfrzd9uo3@nL-LjVbfGRROaB}XRR6=0*f#?M57I3>k?)SsndEe*_E0{JC z8~KTG>iCr{FzZgi*dg#-`@i|NL0FToW|TK?l*@GJCo7i}!0jL@UGdmH1yeV-8ut4D zwd+&$J_?(`;tV4Uohwmvin@{+ECI#p#1wvV7+YxdBcV7ylom3-;zr_=Rq-)vj55Ea zM@9=m3SiYvMNxwnSTRl)rIda(H|I?6PrFZ@9?IS>Z5rb804D1!Go0e>TpieClCEL} zO!YY7Q*5oMaf%@WZ9Xx%s=wkZr2Ol#CH zN1R8(I<}~zL(U%EQmM1o7JeCK(##o`V3AmJ>qIb8-e9;?{|&C4UI)Bn>?OWlWDFd} z${Y;uT}cU2s-`J=B+<3HH_Ph-3h|cN$-nUV_8Dtm_JM~j50&)gZa3)a@lQ^(z<(wK z_kv4m?#~H0bht0_P73d!-g@HOm+KQoL#?MEGs75lakSnIoO?I#uHr^r_ z@Xllf(QA&=*sP7tn48V%u?Xv&_;6nG1S8c_TTsQ;3aUp9!p&`~agWb1^h4;s3uS)0 z5}QkX-rI1@Ne6HugjB57aC>1PYadvxV;_sVw2Yli|6YP`8$XYoDUVk)QVkoOo}8Hu zf!~I^`7IR!S(O_`y3B_p!#Wo~Dzb&AxSLz;xK5>m;yQh~-QP;TFN<5}CMIMi4n7T4 zJklyp&+!Oyg7>aXo0ZS*?BMHwJfX7FTV}TD(^D<)PYAVnZ-C^u$U&%;p(VWG+ew<8 z=roK#^BqIZ7`8tgPUkz~MXtrh!}b+ZVVq+2fV^!scQ4^O51~gI>!b&bULoWF~UmC%x-lW3cIxTZIQ?RVOeVD zcj)}Asqz}NG)#jVGhr0_FX1(HbJ+yLD?=(!&_mEH6+9Y4fC_C!jJme{#1LCTD(Z*v z)J%JIE~Zv$w8~<@Oa(}XfK2JOKlbf1MIg=q4CiuZ6lb7v#Zp?J(N&Dq*qu~jFHc;X zCp3yEu4Hp>OE-!`Fu_rYU#Xo#xAKIU8#%wu3R8bo+gA{R=`0M=;g#+wEQK`k?Ve#; z=f>7Eg6UV*xbbI=(kLqoq~7MmS72w$l;>vAJblVdt+;J*m^sQ*99i{~!cunc0@W4YQeXrA>bg;t&)Qcxd^$i)EN8}^Y*1x^<_2>B<;}4R~AKPT6 zna9%bG6@2Hj@#bTe=`J)VeAWttLFk_ef{>I?OX{p7<`SP_|0Wx0qGw8#>=~+{9C2b zeI#C~3fXAb>l5hwUKW$$fuJz6=WneSirm_ZMD=$OmjO&FaUp2Bjs(jnY*c+23e72? zv9S1*<)JnsFD~yjS{R-hju%4z9N6W7;qd9f?jFfT2?v8hJiPk!w z)smo)?zd@c5TuYT*gURV*iYMjpbZZPAhK!5c8*zpR4Y`;pQEdO-miB$B1ZCGcfx-n zFF{^h|Kj6Ea`p=a^#du82IY?Fl9mLNR>gtoLGjGZp9{nAYYfy8oNN?J9Q>V*%!8NR zgKl6mO=_DXFi%1IS=oCvV?8)J$q0QJUZce>DaHMDXnF&Og$IE=$S$gmy+W-yRxQKS zTkuSX*Nx!dH!}zxNI%kkH17i$kz(#ma>{6SSsBEJ!1>KpFMbQfMp$A2#M88I;oX;x zG6Kk|A@g5-8OJuVKgloVa}4l{M0 zshI8Vdl4oyEFJ~epIMq}0_dD>04Z&80(bKM@g(*>xm((Q)v>$*Z6|zlb6B8Z>WXd0S3+x z?A&(qTS?>R@#sIm7r(I>3O#F%)VeEuX3=iASxPH6o>QcbCGKNLz+(&ZKjPCy#`# z%?f!;^g_5|cj0e&@jxJ(bBm4wZ>Sb0mr)Yh!cY(o&mytlxAqMaBbR3pck~I~`g7xp zQHR01RhdQX=j{`mV?{DBH*MU@s3%$wWnpv=*U~<{`f7<>Y#M^f8=Kz+(*gJfg`e>@ zn2b2W%GIK6fJ!z-b~U`RKu~C}FOc-7vgi?>bmK7{M_|He)yg(Nh$xzCtVR8d4VKSz zPkSnw@%i$ssB{PIFy6T@aN$*&vRFQl1nP)TrHN73UgA02dMdOf@?JKP4TWgaIu~Vc zvj!|UrhZNt!!FO|(%NwnmaXdQ9GSAte!lh?57)NJ$pU<#omh*I-&ucGR1pe7VC?r8}hj5O}_;q$9o&Wrt zEXW?_h_j!Z^pT}sISaE83A$?9^ZWjcPZ4?!IP#}!a3^fiFqCfhfL zl@e&u_`_pek`Q<`Zab&q!O_5%_|0+AM*v2b&S3#6wjSu;@=arHLL_Mn@rhjzISi#9 zC&xror3S``@YRt4TN3-05u$ongZL1O-=>GMNTjd8NcS_97@B&VAENL{4I0DPnWJ-@ zEcA+WWKr3$0n6cukmq+`hz zBaNz3SfK)HU4_&q`@0ArnlvAVJ!Pgj#~uMc$W1NzmnZznq=tTHmV8DHhA7nF!ItOT zrhNVAq3B6$j_4Nf=BkS#n-6(gO2J@4k*WAql6#ggJe;C2HHKQQup#nft(@+)4i;rH znO&uPj8xgE7Rlc^jO|#N?;9*2a)2@VV`>44b$(X@ujw|1@#~-K+aYWkug2TqyNa_% zsa+9sm~f^;z-G^lj9fk2{0d&0A0&Wy`pNJ}pJ|>)Zo#8YdoOh+5wD^E%ElMsL^3z+ z5LBmQaH(Li@Xh-H>Nql2+u|LfsH>tKt2;;#a$_-j1DTM1(TPc!n!}Vq6hTSRQbg)k zbS9<4ms{LGirx(`&lZpU0e@6-vXpiL7!FXt+ykdUzS}%t{Zr0`NUk+q+~qvT;HFTR zkfIH10vr4c0hc5REMY^E?TTM_{pQ)_ppci^^7F%s>gTRU*WnXM0@GPwSV(x#<-SFP zDW=CTV}3QnGTC!S!x6J+&m9dz-wh3)yZa0DAMP)|F9FbgoA^)vj#TyuI&e0z_uUIX++@5|?Zc2|W`W;Q#^T6mzyrm0AycF0&*?#Xr_SKkIoCkOxd`2BA` ze|Yy}lPwmf0_EmJSbSUnhj0KXQqK_?%EnzfMg<1&;7D`-h-}=fCSt}WGsInIzBwQB zG5*5&*!XQ66u%7&_#M=E=4)azC(Vb1sn0gss!lxqVj=ASueGz{qsB+^JCrIjUlX%8 z(>#kZVNC2J&Rvqc%6QsioH>1`#A~iE2a77PAK94;S2p@*kXR6mhJl7i`AOPFJjaT9 z=Y_17*dO5ls-+5jkENYj9830AZWUAnQG)Y7w@=|G(+t(ph+PumyFCcaxBBpJdARM zZzJYKQk#NoZ-&oh1;A>%+D?&z;yR=^W)hwA@C@=`(1X~ETVwVR$ zLsIr=RwYVB86#mU3gyK8ej^t|(aJ(snv91d!XLzM4r6Rvmb9jxX{^^us=xIe=`Gt? z;89>k)H63u%Ud=X=a&{zbA(QB$cR(%Rov7G^vR_I8u#r10t;1c0yzxPOzUD)FHLAa z!qER1eDS~%<=S)_B{LJ?U(1}b#{mkq@l*qo8er(@2qsu{OFF=@spz4{--R-bzpJ5X zY$$&E<8Nqb5WBIZNTuU%sAb^Uv1L)bV#bE^>Tfl+$g`%Kd*IQr2k(R^KeiCBAS^gG zK*+*XZEQJ`+f3IbF2dO5{IsaNQVRiOLjAd=L@!KWqlo!q$LgFnHb@2RL!B3bFm^%3 z)=zR(%ztAaB89sBA%jK8hN(|@u;yuFQwqjqI_)mCArWW3O+=u@X3xCo$8^#I)appF z6*h~b9v;Z)=3SuoG10V`D!Ps>;M8s_Nfqj|#ud!R#mCqWQr=cRxf2fCTEehp^Pd1~ zm^HdC-Biw$RC11zO*D}`_Ik%=J- zUOc~5xaZu`BQeb9-KHv_t6SGmQTF6K7=N8cybZ>c{5NgYBJmOS_ItZKIexe=hYNTL?Vd% zYJXLaaLIQ#CwuvH#S`2Nf8C$cvY@K;H9QEqeLC++#e)6r_GwkFY#QgV#wmGIKQ>9+ z`ir#cPZLkU?ll=p{lxJZcnxPrJ{c!*gmTLF?ntSDybn6+c^wSC12$NdlE&dd6(|z> zPF+jk-nvdbDpjWr(C~WdZXVd8u%hD(=+s?vTpoI`7H#X+3kfDcsMcGA)Li=D<6{k(XN4p#BTqd>r7OR`g6YQgT zPVeaDE0U!one^FJq31L2cYqXmkmf!_l?fm5HLY}LsPd=S~{Q;-=~x{7J5Xhv)jY%K6ds6Pe8ZaeMXriND{=1K|ULUHpCb?PmAz zv&tjh+_!)^b&c6fJ>th0Dqp2m+O{dkMnx5Sq7Dc;V^hkLyrI`)kKq(<$F`r4bMJ-+sk1+bH{R__IHKSt*7+MYNS*cHQL4(!VSstI zD&~_B0?E5@+_PgaQhx8D8y(4qE_S$`dIv>CGmcHCjwb?#cX)kqyefBPd~HDv%zkj9 zV|rqGf)M7|0-a8W<`9-TjDjJvU&@qKAr_|NSqXhDoZ|#Z99_EN0zRYX;xWbalSgO@ zY(E+9FoK%$W{~^CPXoTCCms@myBVWV?l&KwJ}0W@a6sC^=`7G=WrRk@9!Me#+WYi^ z5(<}2WGQL`UIEF}`^8nW;J8nu%vP;IP*x;v>H16<|J6wk^|XW=V)_>7 zt()`lWECeRSRlQH$65E*Nskj&CnZf!eyez-rWP({H_mB~CRdRgz|KNmZN7)NoOZi` z>!Gto@I@VqygHlg;pCc-x~k(l8K=8$tFxYeq@~~Iv*PcY);>XmFw4EfsiqFKAUyWm zIlcPTs;Qd-0V=(Egr?$jGKK%}cpPWTSCxA-AA=^+!5~c&+CSmc!Sn?mf9 zV(t8kk!9MOs{JKT^<=vAa9Q*hPy4$m0ki`VnkM23Co-A+>4b38YqDSA^w;% z)**Z-zyuHw&3lfA*tvcZEn!@O?8WJX8$_?x1H$oXN$KG)_B*jSD@OC&3#avNCs2v> zpm%&SkvbWvzY;Wc)OSJCD_k93gs(bGMEtEyvd68I$1Blm)~k37zcJtd0P?js{6V(eEd!@E&GbKGFO|5`}btOP1jtpDR~zhXaTSm_XJ<&m=GX z$yA9Y?T(%`_K=0*slT8YSSFi8Zl~_fLKZsy6U}K)`)QhP^s?uC(BJgBL?V)guf~-A0qndvcD5kA1HxCOkSXc~SCdR}G6dO~#t4Cjo%Tagcz& z!%5_J?va`3Z#PvSr(>!xJ@yo9gk=6nv-b12)ac zSZL$=;g5Rm4vqQ*$mML^0RX!39*5JbzmLbqrRYsMv}5A6x?3x6k4e*n$peQ^UJMJr z(H7<2?Wc-(*l+Z5I#t4SCcSzF9!uk)fw znG*6yMr1vU1C}-Q+E8>QldqD!y2o@l3H7_df%iL$=+pxNWA$Lil6vSlk%7#a0-Ab2 zFX;m9cwtp~AfTZ?UiKmI&UDZEV?k)^NdZlhrB}=EbiByTl{TDQSUrcr)#;HSO_QxW z=~ZZplhX;TolLoE(r5MP?c{8_N!90ix`K$9$SauIY53}7)5!@cv@{tHqf2+LaI4*g zJXub#0;}k8E%M0(MxZeLSW`lTnr$)`)gb$MO{7@hdqTkgtsbY5QEUSAI0tH8lifIs zK)<}Z=u$d#&Uljzs(CZ*#fc1r{;vnD(d7fSE3rY%%MjQ^*kP5{pv72& zmXaFKhvf!>RBZ^#<^)n1-cSQ6(n`I(a8}MWZ@O}O-VrCTZ7Yt`btLOY8L>KwJu7~# zR55Bb_KFAC0)OzCu9$O%VtlP5sV*l~0q6Q5Fj9fFcsNJN>v4Ys^~1{9Uf(MkY;OYe zhwgEL!!I27BUzp`bi?BjlfGBjf!4oJ+~HT;$Kh891?^XHvS_~w!5x0ZZEwE{4>J4; zxNpAzrj;e0Q3Drwef7_mC|09Fq~9ozSiREZlwTFL}ch;VMpW}ANNlURBMYHt2~CAyIUeq zYT}UUUFnrO8~}Nr*l`~aoRk|pos3=pQ$$2H>BE&80{K3QveeIc7WKkdr9J8Wu08Flpd& z$^mw+grY?#6>2|>Os#kh2%I$(5aHJ`Eny59a<`cYH)1t6Y^fXlHbCoa@kk)#4#O*(9w{kFV_##h~93OtOPrT3x1EO^& zL;daZ=9AJSmEf+3aIJLdByU`0?$dl0$UWohCB#PY>@7lUVR?Y`J@-bJCB-z5mAcx+woXr_sQz69F4?@hBla+D z*t~oYjDRGcrl^LlVlz6*+@lKqX%Wah1b$mhl;YBa#UUGk0AcRKD<$%*r}6b89{mKQL-*>j_sRgu!VgB%@15g+>oBZy#}d|$Qo;*XWlJO261ZO7ZUDt;Mb*5UW!*ahX6a18fs`me#>{dXj9X3svV^%o)q0@dmaHv8MjbVBd@6XTej}3m_kEoIE3YV z?0Rl#pVVM!=emq$JZBTrfNCJm_BbVMUHK=^@fWe@#u%JB&&UDgw@>>B%2TDz96zSf zt?1`zk@C~}#SR76QJFge(yUCbmHUizYCb#|l3Xqv??Z`PZ{)^Ha45Wgvt7?<?*3$ z5$-kA7b`*Uju}~5{zQ$lNT0ws)<#rJaiq0r1 zSypq}ye@;u-zZK0%<{=23gp$YVZGqGBoZi%&?VE-3mwC`1m)Qao!CyY9(!JFB58)u z_w+tr&?Oktz;}uK8i00qm@3tF2*oj~eT7Al8Qu~J=UZM?x~;dSU^SF5FcKV*Cdt?G zn)DoQbKG|jb6!(*!5+sa5riKDXs%_QFM6LA^xOsB8T#O}#TT#*e5NdqX%;epY`NGJ z^1b;)X+#fvr7U*tEH%xab$jjIpzE2--_y{;CFh=w8gI?Wek%m_vgSVT|LW6VGR%=XRDgfT|4$facRscSDX{ z8IW^lrpObL-{y*T-^N0b=jK8uxI4eiw^?1GDid;&w~*W<8R#QJ_P7BIBhnbT9!RHL zrC~D3j$I-XcK+GlDlgn#9dX*0=>uSa2sn<^^pziN@e7=~FD50^9{F`8OyD5Z+%7l5N}J`As$RtHU{(__TUrG$cAwdL{KO|eg_a7=WQymE)EYwW!oFov0xmrBW(bf4N$|u z$NnAmQS?||dv%o<`xgg`;Wr_~ESaLyWO?YMAu2GYV^|dT&-|jxAJuM~J8%s3bkSZ8 zr<<7)oRNHonGe|W^hoc`1C$1J?|9rz-C|-2=237XQu>fjwg9$pIOCfIs zsWA2z-^l6F=rhjKHnZX-nl^x>E!LqpuMHh~eLhER&{rKdhyLGk@Lncnz>dxTt=TJchzjKfb z8v$UTPD9OtT3CRE<+cZ@I9))QZ^x<;$jZ8<(r?nDHXDy&NV=Ldh>!7PHYi62?A;W; zcJ-=r-*5s9%JIhc4V4(5rGy4{EWBlAZaTPl4(_y#s$R0gXQMz$0#>~54B5-6E&~pd zg^dSg^P{7|v0JZ4r-NDU@SrmtAUR7_;U>?i0~b2*#xtti{Ub;k`15RVeiv$l-LPWb zb|oAMy=+N+ZY>O2-JauEoaaZOR@*ba;Dwj6 zl7eyCXb3{^dHM6t8V$yhV_>P+&3q&{WOZkDMq1m#20jqAUGqbwLbR}Yc>pE4u|vkA z%N9xplD5`TN40KAlZ~v39Pf2=b1e?feG3^Gnk7;2S~Hd9h#F3%L}a5;kxpR%>FU&; zemk~gM5@*nw30-10}o6jwgK2VY)4m6a@zou+ui}r(quXcu^GTo_RLL2)wOt>cE|S+ z$jBL7m6VmrJC0`-df)v$m{paQRU{teS3|GeY&z9(SKxzS1mu>2Z*l!nQR1k48f<05 z`o1AOnqqRbM!FQyr65kTl^Gve0}O_BGLUkr7A#{hX=fys>YcrUCN^1rH z7~hRP1WlV5anHkdI!(3WFSu%#eE9R=8H0aXlvW~V>{zJCj;#iG4WlwjC{J;1jJmY< z)IYV!cHmxPDbo9RE9Dn3+PdsdgJWnKOux0L9_V>Ygc$0OeSNI?W$~))zG3A+6oc*v zVli!S1ke?(U^h-6U5eZ<5G-MPaaR0?$4!nhI0N_9VF^R(xKi6X zYn;I_)LF+gt|If&Fm%C04gdH^ol=80o#J;Yc3g6?R0Y&|m+_O`an#3XXcR)Ss0JR1%r~b~h zcUpW@{2gp`@+;9&eoP%75gO;TA1ufA@4VNZbbJQs^NnbbZo`Ns*VxA^p-02|YUoz_ z?dFj7PDrcfWf(ozr}d)5q)mzB=}?2Iq|P z>);hNnUB}O>v;#SH3A`x=S(nCu8-HjYmH4E-|yg)>_g5sBJA4+(ZMk}jJrVu9ncQI z?S;!-bq+JqaOFsLg6(_-j0c|vQ4eQ!Sn8HEyD~NBCWV5Q`sq5mzUnb{PbL8v+rgE4 z=785kx_0&A$@+M%K5VLYJax8tZF$pbt&oZV=LGShm#EtveLGs z)Q-6r+5`?Zp{JcIJXappUv9%a9!(z}&(saAg{o_&YrBqED7(V~D4^@4$Zf?%0UXt9 zYHw#ypX>(k%Y$R3!4^io3^1$( z9&x%B@<9KgYEgAo#zXo0?Lq`qO@ajp1zuIzZTte{yF;cp-}=#X6qP9cn5P6VmD3UQ<+}e(l^(*)0}wvCia& z--b28Z@}lDnE%MZH|`d_jooSC>20n2wu1)$0drK=8V+fjCqqb32j-z_WaXuH>1ff* zSPcYefXwd>tMHtv?9x=rmk1ca{j;htDv`f-DC;zusu-5%iG7ck3 zm+Dp>lhZlZ)4`NQa%k^nK$I}|?0WrjS1+uBvm(f*L0x7VP$BTe0{=2fv`b<}3_9`E zX(yxNAbkkXw9`7@OLmC-CV7W?BPDW%yOhBPar453U8UT`pcK^T;a|s!QMZAe!GeO= z?cukH@}e-As>yPaXU^}AZ9os4JFqkoz}1OvpPx+;lu0+SS9WIn?q=pd!oYsW5VDT6 z;dKJ>*dz7tZ>bZB;bo}wR`)8(WewM_=Gbe$^#!}_YA^_(fpf6!?F{XNBe9C%LI%x` zt94DAc!6dECR)E^>{)&xB6uKuvF45**ACb0q!^c) zql16E`{DcF-+lc4ZLk|6*iPcjhiruIHEY+ch}r!67;V%){!PN?IzT{3R7-`;nPG-Y9YCZ9_0|+O*Wc7*+x-?MkbIy+Mb5V<^=C?S8xyhdkZpe4;3E zJE}lZy}XTlZfQDJXh45Ws3$xG7_MK$u@X`i{C(X-%)aBMDb+-GjY{^pVM_=RwO!|@ zK?0`SegG4Q(Qx)Vf`fE(C=i=SNxG{S%A`h+NkwB+7s-=!ftPKd#Bduzdq&w|BI9JyXrK2za^kHK9Y=OHUy{xkmgRT1XRZV!2-o(I-_Fn+J448G z=Pr(2$-;9xRDL_j>kP<5 zLxtEjYa)51xmgw8+#j8N1BY{N}b z(v<|&8rl5Bkl^f?=z#h`^_-+&K%!7#yxWKCU87Wo*T#dMNWvL!jb1miI}Q{!0e;&> z;CFWnu4_a*GRISAADHOE}K~#tGCYQgPgC#88GB$`;gtNZ6P$0hI=$cq=&TB}z{eBW^ zZy)GEcuA%y?=W1JBxR^(_qZ3w!0F+?qSMApopwreYLImMC8A}Nf657&$XE-^w7bdi zh)m}>Izxhp-aU6QS_7aq-QR7uj~Dm1cXt%!E2S5!eL5=Vrq3;n(j)eoXuxnwlm>eW zmns1SYPHbm)k|WE4f!iMS|O)yh6;^kzHHFH{$mOX?82;dd}A^mLpx@dV>G^>MEb>% zogE;UHL$B+eLNNtEW|Us5}J&EO$|;Mdcu9e zA3~+?+P5-0NODgjYfM#ic6^(-K;<9^ceh~#gY0D|;o+U$Ena~n6{CxGvH47KrPF#1g0AlQ)ApXIMJiSUep_mt zO1rz=Mq$6vaA0c}pywNOR+2*vAFhe((r;&CYR2^TTodt2e%9+NF}hRzbe!oW@oZOw zblpGRJ~hSTq4^hptsGzBcb+c~jGU9WS|6%SN)hkYpHj#9fO+nmS8onvQJ{%5XFNi_ zmGX!6a+@X!#?LS^znzmL;fR0VuM1C|lk7{R;;U<6tN0$;2H?JoZ(cLm;I-A1GNkFu zhfU~zK774KW|@Rf2}E_Soa4^-SZ{Po>0$y_#0VZ8T)sR%OJFF@Bb4^d-w?l%F_D?} zVTvJ!_Bd}H6Ce~I4~G+2UK32SNkY_pCP&J_S9FTh)F8%p6vMC9&+CE0`mzC_ylgmc z11JrOW6sycC7|mGXY>Lbi<6A6)YrExGAoM580E|+_&Q^?aqt+lA1ZFp=@5WU!b0_?|JtaB3MI^j^Q4a9U~m)x+X!P<9wMNQa&qA!N+h z6>eL%`=l#K-$0JB>!l$rxSa;nUFi)qbo7jvJ-Z5hj1nPC*ar~A8%NNjg)5(H&&v|r zm*JC(ZG0*-QS5UGj-3_s53}xv*9}vV&BvH`hEp;2;;~d&fv;DMTkIYF^R6 zo2Z1pe6ajB98AJaLpl65$Y=>Y8^|F%c2@?fmp_0iQQrLk@|Bz(tHGpb2-@D8;3e_% z@X!03KH`MRTSQO)+4$GKLlUI^ynhBEiNE=>@Z0V=3&h>cpmJg#gox*R%T4L7f_%?> z2w|o+$;`alr%uSjo(GoRmdSO@V}(i%;CYMdDH>pflRHS8n&&B=*d86AirpVB8i&2&F1Ct z+eIlQzP}^(rCq>{ABW>%Y}*F#nB$Clka^DEcZHXx(Ix-!fErSA86Sn0hq%=OTO8d0 zspcQ7UKYC{JyZr&bujV{J+dQE2hi|F^~BmC;7Fq*DRSbGX=i0+%qObXZDZ76Nef8w zWyq8cu2ZIiaccbTKcQ$78FIu86fMR4RLz=#@xqRT4?DX~JuI6Uzx%DhJdrP07te+E z&eT=>Hgy%hjh82XnNc{+$XHG~7~xU}cDF6P=5}m&r=3r>YGJfBNtzKb+zumDyLDI~ zgcRBkSw7Y)=DSVpA7e01!sIa3KRGq;%nlm08^ytZ4&cbV+o(Oe-@d!dvI9kDwltzt z7UDiXg{K0JRmJ#H?a{vZV9$Jo^}5*&XdwBK&Ct`PsRICkbo#PsM8KThu|NZ1qopqS zhAg6PRKvm=$?mlpO!lNoTMykN!cLRZ8#t&wr z7GN|t6*<@Z5}vq%>7-N0^V>Fz-*z^{ycyW=+fXRK?M`WW zK0ilSJa;ToYk{SEac1HW1`69CPSn4k2Im+$Z8|<4zNA`TxP9`ZOIXHlJ93=zAT$~1 zxVibaI`}4l1YGPtGZfy1ap`^5C^QDw3i7XkZwi|H{mn0b``f^K*{!OzY1=Xw1zWC7 zQwU^_dAu@OrX&R}`bA*aB@fvijt?c=BQyEdcoKOxCVT)$CDD) zEfyvYlEp0}SG{Jitp;xQo1Z`Yc=6%g|Ng$JGr%79aW@?DlJrlFf(A2PiJrx0A%%Bv zMCJk5Z)&ch=!RW>x`;%VjbP@j);Bt2ji0Y^sp!`8HPsvDXbKd zWEcZ0ZLtJ>EDpP3KBUl)D*-?SJ)0;tKVyPUmTn~Ech}#UCfq)HxM9!i^X{L^{Lq1V zhyIEapp-jsIr4~%fP=Vo@SqTNa5yD5Ro-Aa5C=ZMX|ok29?kSf*g0(i{5HI=;TWIP zkb>M>yg<={71&*zXN^7h+7LEQPVfya>=t2|SNQ!HviROG<*Gav&sXHbOr z@F!~k>Wm^0S7BfJ?g>@-u&--}kWOCYXTFWC?rlTQCuH#P_uat(x@m-8i#auj>%x?x z!-|5%MLkihOULWHA#k^bm6Ryd=6K%2=n-OW-oVbb!1-aV&^McbcYFDAgSz7loHNlMUn!g& zI-#~##^e%(=!7>6hAAzHL2%A~A4YG)#}pmfyB!eaulL{qq;p z!7-Wwx6{ed!QF*PyKJ8z#J?ZWO9N;GAUbQO%m{pq(74m z#7X-5u23L>T1Pc;TFk&J)^k+zW(}v&OV$XOr%eEOO>9vIT$h`wwCa~{?!G;K8tD5b zUwsBJIfMbISR-!m2EJqea=Axzdx6z!R$#N(o>DRq=*sbC&Me6-eaKamT?yX7F zRrs%A@%eJfMb{2p30>0JFg7Va!^5W>06^KIFmCa#83y$wIpk;%3(??o95jg^$VZ?T z47Pc}I07(+X-kAbnq&RHa3Winf*402`!c`n!^{NJUD^;(k)cj?urw6--q_lrL*5-V zSn?H+mr*V{fWImOFkQ`;_1()5grk%ZBRXNEj_)U(bLItAVb6{`iZM-DjZG#uxacy! zAW9+deA?b2Fb$l7dBoG#W&m2X50Z{ci4fN4twEhqxCdD6ixZBs@OH(I+;@-M?j4*0 zQF)h7iA1lHb}`Lxo~9F3_8R3=>J#4}vEvxEojbC0Y8u4YVTxDvlRHA(yt(_@jAghk zh1+x`sF!w#GW~OdEq_zBQflkvIT| z{j2c4HT)Us!pXjEef(Y*fLf^TNW>FWeEld~-4N==PsjBD1SW9HX4pil7e(fH?%rf> z7LLrREB#zTAy~DX0AL2Mhx{5rvD52(hxve{&B8}M?=X5)AUSj*9KOSO8qS<(82n^f zKD8~L5GefHW?dP&>a>oeZFQ8?rMA-0P^VBO1OoDN@XZmZv7-a>Gv8(fabz;40@QwC z$OZskBHdRY@i4P?S0{tEre`14lCQz(9jsZ$t`Oj4&|ZsL4j7f|)UC-0rshZwMb}+KT;%2MYZK1BQV>Fp?I*Dz z{c7>~&cNJ7kDPXnt}2IM=L@Hm4~*xwy-bzdiV6_hZ-Hp?at`iV&N_E>5A+_c@`Voz zOeneGmavflker=~>IUO&AZaBQ3|pAH8>j<d+<#u?97tPh8Z%}hn+b^Ie3^?I%I3B3LB@=kY8I@mq!U!7-&rU){S;(-h4W} zLrg3CPb4aPD3LEsPh=9tX_p3x-yp$1`OMmIepHB@{mh{;PTCyvhXR~TLBMZFlNk3H zt9}fez^VJEpgKE}yDyUFZ z(N@?GJ+!a)yf+APy=x>b{NJddb|iW3RzfBR%T|m9!wesHk1vmH5+{a3x(q<4=c7tL z>i`__Aa!L#t8bzyQ~&a%rWZBn>zs9kB=fdw5M>I0Aw{mJVIY-7@RZWLX<*Bno)nVE zo^EG5!q8O)z}jQ_A%5FM*9TTcnN+JFgADZnjO>zbJ|jj3fb^snXpoSYU>Skv zFMLQF)dFK$&r?!?<_~LJ7=p(g8uA*143P^*^=jTQ^BAu?`=Atwc~-TSa^nWbP)ONp z>)^o>>JaH3?$NSt$*726u^FOxuyct+?ABct4-VDJkLW>xy0&j>(8E-}R($ zYJf+mFK+lST7CY}S>EriUf_z_1ndg%njtQY)9-HzA;L7^^Y7cB?5N|xpTZ#9MdS@v58zkcK?+G}4=%sy1ku~&lQ7}n@Z2P=l{@7z zGp}BP!;B*#;X+c4-(E<4Q__PrmzZGn4mZZ;;N+LDGMegc+FY3PW?+g`zeHv|;xgKb z$GH~}@7rln(92MT3Vh{lAqj*r)wu34|KM3ht`;A<;9LOx!mXNRlOY_7s_Zb%2cJn+61ri-COo0E#Uc0 zsYVC8oEjDzSy-3D>^FhI#!=;UBdZzz%P3@?NDD<-YkUpA?Xyd+tPj|+`|`?9=Gi`KFl?yC@RWOYiVzb$1(D?GPemJmx~W}c@$XWSDS z-2Ry4^y2$j5k|$CZ9_9)XG!%vl z_-&#LzYVYR+jL0$cBPfdw+sb9zAhF`a_M7&GLNzBoS2tln9><)M+ilbJT>|=DZ^hA zht4w8F1x%aybXRSw^mUGVf86)H+4ttb#*rz89}J0@* zsH<`q!tSHu^9?a6u}WGnmIyyp<=jZmnS#gssKMM6)L^SlM2DDXn zw(>jcj)efz^n!-8e1^PX>UVydAc|?YKWG(FiLK2J#PytP{=Dmu*_GPhc{lsXHCrJ& zCML%bVx^pxz>fR{b}lR!WeE-rVm(S0ynrw?siQ8`qE7XOoy(H2#qRbavZhU?gME|V z1`CMt9e%;RC`&e^2Cy&in^RLZ(=^A-mC&L4LI=HFZD}e7apk92FgwmJy8_er02t`- z+rEhrnHnA$35Hur+%o)J2VCg$c1(*|%Jd@q_FH)3@kz@QX9M}|w^CklRgaf-L54n< zf`K%@O@8FJfu$De=i8ek=l~J!Lgp&fe%WU$+o|nFXSrD7@sa@zrLo`5$RbirQjP8C zdN|gS%N`hn1ig3j;aG&kl`U1!xUo!Qmw9uz9M30r)Z!f+Px$9V8=1o`Ym2 z;AqG~gIcZLKExAA$cd3IC7?3x)1j|v2y)jlRW9z6xZot`PZ(tyq(SvkLd$(`p$sFXnd<}XF z^vrgnk3qI3)}^Xqm{bBF;!N_aW!7*y2Wc^$^2|MgcshB3OCDtGetWO9(dOoRbedJ; zWH>P^D8C(J!0h|<0*1m1J)@y+4GuZvLFpk=nbnI?LMGgH<82`rbnI%eq4J-Kg+-2+ zs>ip;D>|J!i92`9qYl`Cm1#~PuUrCrp|`_{8eDV<`}HcIn_hiblX;}t_D+rBoXZ&Z z`WbXn^w=M&bc5=9Waz_7XHu?l?Y+I|$fH(ds6u%e4HU26I|*g;$&=={?KH`JjNbFxlmmPbe4IX@28*QU4TEiEb!u3%zpL4Vj;Lm9)xaFl zK*m8@)s|3EmYF1Y+rFx|%~Bo>d1q+wrK-V{?`p7X7!7&%qM;wufI>YwYYQ=`E`!t1 zk72hNs%^K88c>D}^bJddLj!wM9Kz)5+K9zjrn8JGNN%KD`9kx|xDb9bI1K&3q@4Ruu5dd;yv_IsBN{wQ=dp=)0Zn(K z+2>h*M1MdnxodPRI_kOv@@EUjuJA{8NB9ln^(UiQEC_6{-?)Xdaf(dyuRDj1yKjGQ zym~Gj>}tSg`+!j}I8C>0k-^m2k^Ahcu2-HGFn88jR|yq5I>7bg65ghi>1`vM_{>O# zf*46j%!5Bt8Nf-(){K@}YQOpkB8N>#r^oPAyY3n~MH()I8OPxh8h91u8dRUNFA_mWi6clH^D* zR4I+H_%wL6Si&<>rbb$=R3hZa{Ojn!S(Qa6agDkjHn|R{D2I?~izL!Kc@77>627-m zIF>wIn>%tD4mi#6aVSf>JzQr(bB1I$vmI6-k3K*^za4%O9o<0uFD}BbPSa3tr`x26 zpWnSsm8oq|-Ps*kgC>|E?=Ky}jCsQ$?-^Ec(K8khZMre3{5IYSM`rs?azDGF^1d;O zNi_jybJ0Kf;y6%e~_(>+|o|afCjQ}$QVyOOhp$B z8Ffxj4NjhEaO+Mqbn{R=!^kx+ zh=aAvB{V=ayhoRLAKyO1oxGyB0~-|1p=;=NjJ)uE49A>*_e!^X5-xOKIqyymuB7UVbOh) z(eU}66942=$FIB_gS+luaraBe9%UE2hsS}@!*h12p@Tvix_PK3O1^XOzJAz5V0JwS zy7gDw0fW!VZQBMVF0pIq;8cZDx*q*JsGFU8AY}T~6+&X4B^bo9C9;9>0A%?f*vbbb z_g2^7RE$^rdZ@wnlm=f54Sv{au*0Uo#4SzC5iF4ymdl?Wa6&%QZjpD2p1hsiqnijy@p*y*dGUz|fBJDDQngkh%LYI^aJ-;OoY4@`~o5J180)_>Vl32dnk?05v$XNrQ>@>d{>>m~lUB zYK-*_&Idt&%s-Gp$>m~;!%*$}aqLzAK}WY^a9@5dNN>K$sE2(&I{hc!GoEBq?lc`@ zLwfdOprIR-04rMy(Ma7q@Coo9UgLt#s>)PG7f?oD92@4JRR$4z1@%YQ18%~5xNp5` z?sPDQpPL^gxzBtp;58o<_hyH6x!c-j<>%Tp^nOljT-RI$;tt41RPH4c{)Lua&kS`%UC=u&DsFm6^SSCfi(k-8q z#y$p7=>Az6G6acab;p3VE`udGP7u~@9xz*71DWr=hmsiPgF40b(=`}x$yeNSI2yWn zI18s9hPKquK{8R~-DgSI;`1PY#2e1c_RgSamAnUf7H{Zc6hNLvaG?6GAX0-NHX6tc zVuRwL>R*xTqW2(umpAn3i17CD<)eq_8hXIRUhJ#Iy6X+tJ^KdZPhn|1F((b=fU%i& zUP<)83hg~yTBC=v=QOZG{t6Z*Z$PEkHFV(6DG^}4xa{^yC-2h(H03?ud%6b0xV(af z+IzSc1w9nUo9_!nV!E#YAl}0h(!!mc8q1=FZXP&CI0UImoDHfnXM)z%?;tU*ERSx#naMcWLRFRtNSY*oYKQsNqmoyBkdI&MyoqT?Hc8@XRX z8XSGXdK-%`uW*w+)L@D`H8{3}lX6&BgYQ_MVVtEnBx`NHIO;hWy1<*QTRsMk{uSf3 z5hFU%NQ3b~^uRP=n~b8yzyWTo+Yd;Cfi|8Ty%7?GVS|zk+fN^5kDo2Z178c;j_wsw z+WVlmYjzFYK@mpO&BL9W5Ua>gMg#c1p?H`=3#TxjA%iFvQ}O z^$k~Ee6#SkZVxUG4hk`_O(B`{;iR7;r>kaXPaube891>Xi zZvWx-=7Aj9B}8Yjlm&V^R-E@tvyO14d47;U!_(e@CuW-E`#Wt8_?~HgrT6?y;(kvD zL4;CPl?R-t%LZ*X2OBc_K1EH38pK4B$m@qMi*-rNQ;+lbT5l4eAJs2Qqm1I@fw~7e zwqG~IoDkPkyOZMP{^fyqc$K*PIuueuU9kz*U$XBA&IaG?K4%fWhp#_FVlUxS#g)26 zF_2oZOh%3qZ5~&#zNm28mM2dLLW9UXq0oxQ-QC@;b&pFv-L}o?!S;R#R;K*dQ1p|G zE$v-GpTlje#1kA` zYOB)bU*wZkUFu0JUwlSzw;0{?54mL3=Unx~s!PftXS3gKKbNwva+7*_zCI@)jig>1 zIg{6m!tI(HlNpwiZR=)pe);vcpTGNQ%;b+w`rse$e)#b2$6vnt>HD`sYi3LhLE{6Edvk^8DN*R@7Ng{1Xm%;U%HPVU^_ z4u1a$7xuTel+j&(BfaJCKa-CCO+khSzyIw)P~M&bV&5LPCX{zo*!%^_c{jW3(~6{s zdI1mYzfq6jo^;y^e<=y6Dh}I-Xk^@+GBqDw$}jVlv>s0{_d~(g0^SV=ZVnHA_;~T# zuYdbxIEX{y6r=hAhxX$y6&J{4s2yr3H9|HjERgl+itXZNP!Q+2g&W{l2~Gg$iC1ZD zh=Xal?Ofq}2KW$9B?i8E(578nzTgSfGW-y$TQXaV*6r_=p`}6q zUSuT?@GYh1$X}pbYoE6R2pvMX{;h$~sr-{iMAAZs89|e!K$@y>My6u265bP=wRkmU z@#26y*iUVKWZ(qYe%w9-)tm3GNGh=3tL4q7-EP1A`|bW^eHXtY6V#iMYc(LZT$k^j zNh(C4(`~%PICQzby%~r2;V)wgkK51kp4_?QR^846)!^Ossd))6$mI$^JdzG|s2@Q* zeq^E@@uwua5eQl@7{|ili0HKsp+udz3`i>Tc_5Z+-=rhhnxN0m+Z%zuN{_Vf z5j$5lROIeqN2&lh)y{OhY+3Qjvl6J>M!DRuw%dof6`{PIZ$7UJt={)94LdtS^V?y|Z}^DD)bWT`rcynPxeIPn$z6KGQ7TdZu~yU=O2$?zDF} z(abc@4-(OS)83(kpJ|?-V5mZ8dc*D2IJ!u*1UOJTM$Al<-Wr$6c8+JCw#0mqPms(9-zXzSdCfMqpgEqFSq;q+b*8I8 zA5iY&^|iQ_R1X<7du0J5fVA;|VtRiFG%NO|j;>(jxzPc~&$oAXMR=*s{9~?nYPG^( zfr~(bfneYQ`19x8)$R2yS$AkFlE}V1&`h@vU&NN&K7afaDbEgm|MhYE(3pex@$Tn_ zg#HpWOMZCT-M?jFt@ZuwSMVb21u?CK(J3v3d+I_ClwJ6G^YDBZ9>1Fxg7@z)YG%s_ z3|9tYbsEWNvyy}x7Tnyl-j%9h@-7kf$X<%f)7{UldtzV z26N60EL-?~33R`I1Xt~qQvJ;?GHmdd(E9E{Jlw!d9{m0biCfBuGpy}`MbGekjfXC* z_2=#OQTtaTbt`sU;OLTAnc(s^yS;IFN`hVqU8O^BetrMHet18y7wZ`C=B(;mlJ zn4rqn;V4tH0uNPqP>tp-ZDa^e^7r?^1!urTh^k2CK!5*Z^V4qk`Q`CLq+^e7h&$Wt zt{{aCR=Bj$egFTzY+trl8f*Kx4)FQ%-?!h?{o{^5SSt_|Lhbt|VrYpMg?3-UGD1&< z=;qK!vDZv51{%VKBZ*{=t!m-Oeo1w6t_6{}eO!NF8VHKI_Em3UR92)SM z=J~P1{5sQne(X?|PJ4${Vy1a@fFSX(8*T#&spPWccWLx;1^Ku`nkYCN0YkFYf9S%9 zY^#7*$@aD&jTv{Ka(DGAv-1tYCtLt#&O+yFo?2%DRtLXxE1$l---#c5n%zy3pa$(% z!t~3RU;rw?z)4ipm7)Tlokl6c@9Z+y2OpTn_Tks-N{gEy%pv@jCdgr&`me*^^9<-# z<#ui!s5aJ6w)P;(Fk9+bx_!F8px`2?8+rXfU3+T^r0 zSKI5gsxDP|^7&~Ouz!f5gb)$o}UhG?&}}3 z8SrFpzPf|X=oR>ditHY5k+}K$>G>cy!t&?EyI*O9SX{j9!S>REUrOyvH^QJon#sK{ zZtl?xmB$wc2uV=6q6)tT>Nb$Fr_JLxzO#M)=l$cs3kuKD`Vh@}6Bc*#>GQ?)`t~l= zgnScu6fUGRF~Sq-aF}RvQa_QM_5L2kx(0sU-5uPa7kJbUSSJ0$|7nLM`yK?`NFBl7 zyrw}eF0Q}w$>)RXLX%!X>%=2{%6jc?d{z)-A8I~KQ8FSl=Ab^#8@cvZncZ!tDNJF zco}P>YSp8d7jG3yu~^QJQrL~z4~(41#r6s|3i=-7DhHdc%*E~FCn-T6Vd_9;jbnNJ z@x^nn@nsO5vrH14(!MU^;?orZ`0j#)j~6#PsV*pjd?)f*6TYg1p-<`rMtUt7a%M3E_3;l4hFL7_wsMV1f#v(Vu(iQHb1Rh-bY{o>{Y&O#jUYj2J{D%){=x4xNne%Mu}m^XzdI4m(Jv9i*2xE90DK{`mi{`pwvUwZL#t`}d(L+gX$sHEwY z;o@%lrJ}|vid4)MqDcB!Z3c$&XwNqlAC)Qab8ZT1mVWvIN>tvC18mbcSYOAtB?wo; z1sX=k++I(LRW~$=Pw^iS;FI*m}NiJh8raWQd*b8&9n7m-X!{Yl|n=_xT!l zn4NDt@xGt%Hkj|LC*F5^_wq_#J@LNlFR$&do^*Zh?_L>q%@gapS61%X`Nk9Pd;9R| zmA-o7eLs;ie12R#@xJSqr&s#wiS>QjeR}0A;)(U$Vdk12H=cBTkp*YxyW8```#ybs zWgl=B>GolF2j@ILuAbPqbbDnE@x=Rn-o0{%9`wZe-haD)Wq;#|_k~TKpD!ZBO*ifq zDnGlndp+@S@4nr?(pOKc?;X~@`M&YQ`>vlKU+JqSec#vc!}|Gh()Z!jaS^*RI=@@L zauj-CJwJb<%=zq&#go48E2l=^?pAyzt>Y`>>WPnwf%uiadSZP)Z7*M$Lp-s*H!n}G zoLW5T`u_8kac^FD;(ecRq?p~a8$I#9SOH(@t0&$UuIrV)dg6VzyVvHRCw<@7*0#kC zXnnVs%ID`0Ppt3N9-(e_AL5DkMN|JuUp=wDn{Q9AP>XoteIH+&!$wcM?+)H*cD`gP z>HErdJKtANd|aIOUg@hRUEhaS?%M{#O5gYMD;R3C=827a`SR%%su@qbFJ_|o`CjUY z_g&w=a)&PU#QLtkkO6vj&*F*qeY$@In_(yEuo>=BuVBe|;^T_noteYS^X?kWT&?^Q z46+!?b!nHiGE|}0(!bFzJ^#Zj=dGt$RM7g1ukZQt7*o@oW*-Qv z&OHbOJon&6Sp1a-_yNpFbvL3Xvkyd+W*>;M%^g&byt#vl2XhA%59SUk9?Tt7c`$QO z<-yEB)d$mqiU)HC6%S@tF8V+J0QO?;L2x2-4?c^XnSCJs>6HibmoxaQxi`e&&9^^4 z%pM@9P7kIxEgsArU_6*Rz<4lsfaSr=0hR|d2Us6W4=^6g9bi0oW##kum^W|-p1B^u z)XzP*mt-*eK;p&h1IZ|}4b26lu2_BLL)WzAkhOd7>d^o_wxOdDY&rj0NW zlSVNRlSVNQlSVNPlSVNOePhtmNuy|K-xzeXZwwkb(@1gaPI(%%bJ8ffIcXHloHUA@ zOdBC3ePfW3zA;G1q*3H!rg3^4K{ooHA{o;*sKQL+>C41P8x+FCym06(?&33-xyf2Zw!n$X%se`Gzt?=8ifV>#=wBn zMzG(cQJ8PiD6BVW6vpct1KahDf$93jz;bz_Z!kY4}(>$k_etrCAAo}5{Se$w_gqnV*eq7%yf5)FNkuq8QS)ylZ`fd9MLi4m0 z+fU#4mdo3>m8ga9zv3o2d8BRq@NyXrm*IaIf7PkHrw9_oO!}7y=g>ArUoxJC1w1zHg^i zf60YO{UZ)%e@94?c0eu88?Q{9 zXWu~J%o-EaZQ*6rzS4eZ4)Wn0KHq+De;GD@Y3si|5Z57V z`rseX>WAyia@TO@;tp-%bbs68(8n>QrrN1}p!I0KwN&{Fmv3JD_|va{`R=ESUw`=F z!}lLAK7RL?pT56{PnC0%i@Sd6V*Wb;c@^gL6BL6*CpET_t0%JhRL5i-*Kmny?~AYQZE<>zCdSine@E-Nes~^6oO`x^TO8~(etTZuE4#D9#ee#2 z|Le8Bke}!`&GqGBzN$Yo%s>kb__~PrrMg@1cl+l`fpT+l&8Fk{i{Wq~s_(w}8Aa1i z1meEPW%J!zYag-YcP)Iho$XJ7Hh@L8`w3Hc9WMXYA?4qiJYf%1EN{KbFXjUtxt7Kf zxU8DZ8#-6)px(dXhK0ZK_&S&`{ipWQE?Vf@?d8kO#b4PK_0Up3KD~tV;N6PxF;dws_ z1*_HL{b~Je+|(dz^}&055DTf{6Zyd#JbnJM8z4XWaj*1zI4qLi$jUn4z5+ zpn-OVneu284JNoCfk=oXL=3jXxMIdmsZQ+K8v>l0;BipiCD9i{`k?jAkupqcilN(O548{5fj~kxX_suH)j8Tl z+$qJH*_k;li4~Y&GlI6ul)xDS9SLu7piY5ZU~rBY!fq-k+0x1hyI;2Z22A`Y0Vx4~%65Q)9i&1%f<;XKpA zL|{tHLn~~a(oSksgEKsMh7E>!=p=1JaF{!ub8?`@KfVbVaQ_j?6)<+! z5F=>riIvfd*tL@IsnlS7sjuxnf?R5suhaS2-fq_rV&D0r7 zy<%Yj6#CdQ!$~MX(`!B&lrJl#PjW(7O*GNnYUV5IPBYt_Y!oT%sFr5zqr=;4w-Q3o zm4`u+-!b9#38{-;CLPx3E0(YW9opL4ON8sJzMsyPSYh-68A+Q_jZs@{4$sK!HqbcP zjFn?HW2Gsh8z+ z5>HJV2xMy5&u5bzMY?m6oBmn6DVB3sIhvQfyb?<`FtMvW?>H;a zMT)@&u?BgORt=}e4cUmym@doQf$?Qa4VTqz)4EJDEe)e#bu+J94_oR}@&+D`y@720z{}V zYNE9)*vLQ4tpp-CLKysV!1(vUEnMm|% zxJN{%$16}|C#j*(xs}nUX$$mi7cm?)ks$0-(&ba7gUdS z(ssu`tL)@ur1L@ja~D&Q%w1H4&ZjhWyS*MyH$ePvfWJGN3gGUnB*~m63G1gxUXHPo zEy(;6;4fWGMRe(^YCL#VQ^&de%1x4fWFE!G?Kyud718-yk|aO2YwGwo9fX*6Ie!0q zDuDg-k|ZPNH1+TJ`tO5)Wjg-m*%w9i=2_L;FI0ZsZojvG6>{P?o8H=UieGz9@psx( zK%M9CiSae?sM(1RkMxS-(<_P}y2sv;A8DSI+zEVB)9s%;^SbaS&pa*Z!yS@jU*3Ms z_zam4U%##R`fbI(_NCh4`fk6|pBw%$W6XTKPI5EjDB@F zdHeugSby6J#kbv4e3zi|FULFQ1~PK~#@KH7?S{V|HMp7M@Z=}t!upd`>rd`m{W~*N z&A8#l{yD;T9aenTrgqV|&dDV>V*7uEEVMs4rTF{rD*ik#P;d7+s+CJSVnO9aX zjCcLji{LlTyd^5vPP?Y2Uaxc4ErS1T&D)~#%kd(jNF~W8!^h`;UdAL z8a`4>y1r7po<%N=x^prJe6fX8_oU&wY+`r`?C;j(~?$LP8QRA%O%4DXZV=mehK`(4)JnA9_E!_24C?vZ}JGQeBl7fzh@qD>QmcinVg-}!(0$3HUv z0!Z&aj|t}4lp?}*KSSUwr9^XwZ_%;XX}-A{q616@vC)N zx#`wxueovJ1?Nnhed@In=bd`EU0-a~W+$d@Bk-Q3*6hk+y|X8K6Jz3q6AC*q@j~II zm&>_%;+3b4tTelgrF!+~%JOon-7QBi-8MF7ou#;!O`LP;Xk%%)909+acnbU?1-^XZ z#i#CRF0ZU%Z=OBDDbA!7r!`aWbjs`{FuZ%BP?RfFlqVX^+182bRJ&g5Hd@Vc)Y9!9 z7M{udwN7KE9ML>`g1Z17sC5_0q4Dbpe73Upnu&8y9X;9U)|bk`lW!6gAuELJwj0g) zH6hKnhqVjqaJ_p3H7eD8_U?(|C7nX!$`Q=7C%BjNP-C%P4vpWMz!#$zeH015RLTof z%B>R@ojOuqR6QN9S08RQn%#PPODt<^83KG+VDe-sm*e$b9uqPefOOBQ@#h8YM`+sdz{n zA>LiavNKxz0a&a(PYlWPRsWibLjKKk-uITz#}Qy|zZ-pYEg>Zd)E} zHfB`fvVgO9Z@wRHwHC_(@k^mzraVkeTyScCqg#Dwx!$&xx~ArP@1~gEI38V4`(9(_ zl5da5&s9=eCSG!?)19t1mTL2BvR~ZjiRRX%xLZBZZgdAnPPcnlyh17Onz(Q^$BAd>PxNm$?DWXt=X)v5pdr?J?tjM?uLo2>Lyy< z)=X=$dZ^WQrc&gU+G6AFea+^E5mNQIaviD9Ey@)2vA?T#QVbXLT)STHN=dJ}?&@px zE9*|_c2A*PKlgQ8OO2T|YLvZGa;^?ji;d;!R;|58g^C+}lXz6QT&k_K=|Uax#zGf^q` z_39~U?{~{2^|61Yp1(J6;=4(FFP(VNsl)e|^Kkh79wB0_w#i!2raH^&+`4n+7~xio z`dV4S382)q#bM1>g|X3S4pV=&wzSOH2b+~fRePpa8jG`*>m5Uc?zCt2EY%uKi?CS{ zY$#XCLPf^IS{7mVkh$s9yIqY5Iu?oU;bnbWH^%U!seZzX!A9H(!LT{vlXw`hXw!Dz!_8LVww@(6LM0m@PT&+$P8B5jDU`XaS zB|^T=?vqF|62(|K76oCxYH*ZdJTa+oHp?rui#|hiih5xmmSNn3PG8g~NHOH0KGv-} zuSC;k)a;x-?ADCs33yhYfNN$Z6!-i-0s9QHk)Y;UYm8QTZgjoE4Wl%DYZlA#iav>~ zJL*Nsm-LBL4A98>i~9s9&uS_PcV3@xY3dTy=>~_gN zk-Buc6&6;I&f_WU-#fIMJA$ zuXj6pGPSy1H_NKGcH`M1+`uZCZge$PwQyA3Yx?B2&lHQcF-&%wD@)V$cD2ERj-|mM z?czRZ#Q7l&nU>TTYI0 zZ|;*^oOh6=p$a7axwQ=&WqPIC%>}=5*i=@gPuMD%iEHPOu_Whun!j%pqho7adlIoU z=%$7#|7^Rptkt_zk5>&D3ELO+kY>E4Pkr>A*$tbqnT7hyvFTQ7bsI&>885vXS}8i7cnp!nCAQsPSO=c5KNjf*JoPoSxp`bVGUC=3-zU} zE*sU0_hMR9(vs40Lu(&t)UdwZKU@rkFr!=hG~2kg!Z>hSpD=5dQ)ooP5Ac#POiE6_ zKDf?eNHVjdPiE4js$;7%CTv&^o?JwhD8MlDv>1ok?0OTh7?vy#oFT`6WMr5wG{&I< zw-1(wiGmifj*05ps%~|@-C9ZG7V2?lAD*REmp;o9I$w78Da!^MV|zEm#&cU z;9*uxy55}KGoLy~daWVCRyB61*PES%Rx(DHDB6IG9KPSOO63g*F~3-wt*U*e!S^-= z+|{Q{0~XhAQP7vlgrIm(q{d2@wF1%6VAH9I!-{cK$l?^#Dp1ts{-O?;El2l*;^%Pg z#HLd<`o?N)c6Nd(#h0b*s2Ox$4)!5Y%o%bgHgi`;ISa5>n6Da z+F6oMRThld%*xWl)e1c$X|NOmy*bC;FA6u$KP`~dt>&}8hzSy50r2&k>qKYbjMpv;2NmUt zp+0odvXP{bokNpXD#LY6B&rLIdCkJQ)#J6rmAX~)U#Vc*#0a`KGu$s4h3*1(q32*d zGOCoSxjv^!OSzU=nV@MQNT@Y4QKl-Mpqv9CR?3T%;MAhlo+se?Mj`D0YKBkLCP|{B z0PUdBsm?W;JcVKfjOGbPsOR@l)d%*S{K%8?7(!#D4X!c&0AZS(LaXl|)!rEx6 z3utsYN`>m&q(ZXz06moXU_8Al@hasKokj7%DbX_d$7waF%m-Mak+;nqyA z5e2N9OwOVIFQG8LXwdDoRWFIpw92>8+UyMTu3Bz1JnLQ`XZX zlIje-3L6v_8{L!Dv@2)gQN{j!F{M|7+WM&6mq%IXnilLRX94zHB41G-T}JEGW$leI zdny%ZcJ*wpYX)bJ5}?^by(5 zqM8+J_J_Ud&)cf)$<#sDiDO}dj2vJ=0 z6mx3W(N1KPLoewv638r{`w3U+dC<+He8AloZ(D#R3T1y(V_)E=q&}fO^{~ zbJ49KKpM%o>0G5py+KhJ;8Q~S7_tH)91;X0MCGCLPIbv3mMq>Nd{w4YDS#OuyKn_O z>rRvJ>V2+JL*_E*xdyNrA80#IO*g(r0-y_o?kA)GDUS;0DS-#}-FNW*eGeYK=YUD& zR%H;WKwT;f3Xwp}JNaHO@2gVLV{L_>N zCOYO>Esu>Xkp5y9f<`m)N~?_7w%G86iX;W-)k5He5L4?aQMQVN0Z74Bi4;2Bnrw+y z44d;+xi!v13Ew9~HK1;>260r7fq<%btDIYDrp-AID0o#!5uh$4Oe7lY(uX227n+;~ z_nDJU*zLp{Yr2g!yJb-UJ_uUBn(zN#%uYB*X3v5bCGG0|t} zJB7fn2`>^lcM1mgatiVKLZz~-C2HSQ!SbfYSTa zbV_0A8K6FXz?eipCXUe`H~s+4ef7aewSHVZgo)!Wm3^}UK<}}j6BC#Lw?x^iFQG{} zyj}^+FwbRCz1D1L^vTn}Zhc;qoqSfs7brgn2udFfxE7_W!T+%m7^}>zbd}cf#I1_* zeZosKr8GbcpL~Y-R};dVZV{4Es2lQ8f-Rv8(Z*=veFtf6=1V81DO9F!ZC z2e^-=DfiLbS4A1XIvYMZ2P`Vna&ipy$}HzPMPC2^VzJidX>Fg>tRaoutTCO?UPsdT zkn*rK(`z%U-J*zGNgLvJl(T?XEte~u&T_3;)g!yM9$~4IsMj^2)6&l@D>K^TYBv3E z6&ni)oyweU6!rA*a<}u3`N&Z;)(QrVF7}mei;t8V{M)xELg&Uc~=#CDXQdf7O(nbQ% zJ(2GW&AURWi$xAJI9}NKABQGu#8v=q8i+E>OsB3L-yXS z!cu;ym;c;ygrTlgew3HhD7s(h6*(ePDTM0f#!W^+*9JKa$T)yOUDEcDYO7;>7ZsS^ z5?Z$qX|4kgk3&Hma}XmG$ivWES_?(?^DPhbh%&QcLH7@8Q7IR{vElR#UK6zKW zG!!WymV>$RQtep1nl>j@ThsRX6t?v|@j}kft8(ON(U?T`UPHW9as%;b45S5{G=#lS z3^1(FXL8K$J(c(M3OH`1o?ZSwtdp;J0GfFWj-yUWIIhCdQNNrHhST847R~Ho!&?=a#&db5zIqUI=nVT&GUE7JC8#lCb!J6Ft|gJS3`$im zCd@AK+7gwpU&urp>NAU&XL%0^QvSyk^by5ocm+L_qkc~ApOYKulQ&u&O60~tP6NvA zz`)TqpxZ!&0gUYo*AmV{3bI#-<%VLzfOiO$P@$&fPS&LYbs&{ir#j=W8dMP%V=@!Q z2gHuX1pTfp!yCouULh3=eOL(mYaxkv_Aqbmj#-TgK*idc6C(iaVOi1QYkg^1>z19x zR$5Gai-gUT3rgPxAf3@c6^mw!rFo$-452ZXP;v~Mc22Rse=2cKW_mN+1m6*&=Pudv zo9|SK`{^u8~JU-GB9d@qSeC0o? zj9V24>S8&fYuZy;9D`Wn&)^^4zDVQ}(zrZ5tI#Whi2sa^8Bvno7NUyK>xICbLRUYo zV?A>1K)2$+&^E0vpmx8cvcnbX1Ljf0;anxeWCYLj_>)Zii4fI*q8`9$XOhbKiV%Ye z_2yiW+hLF#xgT|prKYqdzPi-(9p#Mn2xWE&P)}qSz99ACZaE7e%!~3B55rJ2O)2f& z;GPW^3_PcV^x#mhI*jwEz-Ug7bk*i(>JtwtHk~01p_4*@A>ydOPL9%+MU%|eB|?TK z(7l=Ir$rguxRmI99CHvOic$biRrNr1Sq~<)bf&#Et>rKS$6Q|{c8Cb-^-QTEcs)P| z0V0~!{NA22RTZis#BxBd5CSMvg!LGDIF_4v++m}t<(6K;c6&9a=m-G1B?qUWMnG7D z!0*xA+qFigKJkQtzDGEj>1Luz6WyqCIXtMj&nfg9l|7my^no1vmqg=S9y^1^Gh$G% z0z8X&@Vqna`l&a0m0IKfx2@D>JJ#GPid9h{=wU)D=2IeP3t`bKi_a<6+1c#@6snTjK*R@DA+t6yR zZQ7-x8INjiQMVD&B@)Y4MTVkQ0E2fg4A+Aa^MnY4tqJiT zkHusVR)1D2Y_m45KGR(a*7<9aIK~p{#`9KPN0q|uC}#m=-R%&|XJ&)4EKKb7s1=4s06h5*~Aw4=W>d zF3>$WCuQwfCxgW;I+wh4NBh8*HIi7?87~;nF|9wM7#|kWPyL|qs1PXkj9zfm5 zW=z&0*jhtPH{UI!+MtN3Ulq!G2v8Mb1O87Ta)xde0x*t%PB=#Y=w=l*KX;g|&rDQw z?=>nKLni1-4*LaB^qYLc61|RqAHaQwv&8xty(MBRjvtdReJS*K4ndz9!6Q43_uS3O zqls2q582-`ySkq9C51r*P@jaMdY)%FhXPiQmy~VV_4f-YH}qB^uw58=;&$YIKUrA2 z=AH1|d)s*Ne^y8`3Pm=+`xDvlpOK*hPa0b*+Ei;V<5g`Jn(e+gF2)!Rs2e^iAX-`^ zrrSL=r%N03&=zDnZGf|~Jj((`+U=8`W{zR^-&>X1rzK2!VCW%HV5^XvkW+xx=V%`J z&+l_df1kD!bl44X{8(MfX!aDAY1Hp3C=CfpjRMHZ0Zx&d`%Cr%dHc7?%JBG zc2Bfy`R|t$lyX45Bi$i4y5ig;m3IL#Wy+CPM}6$KSZ~gAkSy4;WH8jIu7@YV#4pduD}O3!bDm-)rkbWL4T@TqM+J5T?6N+1m!cw8D0UYpXD|6~^uOtpG5kLi8#zF~>rNe0^(En% zG=F!A-Sg=zW?EV~)5xi6Ya2FG`BCx44&ygb71JOL|(msGCrV$4f=EsF( z7wT5(wAF93n0!!(K0~<%%9&2S>;JQ<==R%fI{n`(Lt7OZO4R~aB~tvmSPPHf@M^W0 zu5BvSqZ>WWQe7@Lo~`@n-i*^3eO;=eOkAv-O=kLroGEXXUO|!OAZrLyXPfn?0NRkR z?x5B@nU|vVicQz1$k^-4gwQK93lu(rgji@s^p9?OhW^jD<`nEBVu4;lZ_m+U|AQCx zf!tm8_>}>@oKCOU6%WwReZ=&ALi7=OixB8n68tIAAi$b;PBgkR3u&>Tt^Fvx4rYn` zSBgc$g5H`roK7N$Yc-LM-pAFjz*0dx-w|Rz&&YOq#*CLrUof7~e(3^9}GLP)NF6p~v`f^1<+-N?ODgbs1NyN1pzl{LR*Yks>HpGeOI5^|TGA0u< z=yRFHLCF?t>Xs5W?W(M{T)TKu_VrePHQ#hji0#;i$!mnrDItL7U{8XN>HdZblOtxGhP^t!HXCZcnh z7CNP20H@Q^SO+@RT*8{(gIkSF>1J1Sb zW*b(%`|90Lt|xG=pkr|sBjiEXUiDWVzEnmP*o64fEi9C}kO z_-n{;CJLVup{5}^vgbAlbln)SitAcz|NF97iNF7v!AC<5%`7~kJq5pKzp!>;b zO_|<Z2uFw9V!y7K z*cB-G1+erHTsTg-hnvcEz$nJIs#u-)eFY-Z&^P6P&nw3jxrtBacM2J0f&l&?@t^=k z5qp>p711M!ZuL6r^#5EO5Hxg)1wB1ME7mEixkD-vW*bVEPYzMn2%baFUA(vI7DKwO zPbk#OGCh*-mIs3CGfI()$6f4tunPJe z|4s>021aIV5MLe3TW|gg${6B-`p67};TEpUCSD^d2i7WVryYkIP3y2s9Y3Pgmh>9% z4uxtdbEq_QTaGp3u`_jnc)9#`yvEu|K5Wh6lQJgo1qGmPq1Wd~491a%$AvoPAZrM9 zyLu>sjV_;7A-5~eCt1m<$G2`p)%1`L< zKznd>uOibSL6Hh@+R~s=enp6phQ3Zn122!>@!l9DUQ8#GGAtd`#Y#dET&%Is@jh;B zG`mP`MoK+Btk5um?jeMLW;iNfM(p*a<7$4b_VqTvJ1Qf~tCvIi9kRe*lis(^XGR>KH-gAnir zlCDy&(YN(I`+QD-Ro8I%O%iG@z(+r^X97`L*EVst#%r>LaGEt`V;@qJ22ZSwPfA zYSOnfRu^k+ZKFdbpBJL!(CtD1w` zA0-~6TY&r*TRqu7p;f=y>@#|eviCtV*}6e_p=P13?ByzmyrY}=Ai(G*Z=uRBYHG*4 z?MApuoauL<*IC%4L+8ouvAt9Z(gSLN1<>!<4v6K3j($Mlj|$IxkG5Z2za^xlLSG{U zJg@0$*#AwZtS3LCZ)wxBY!fn6fO=1J+BrnwZdPpC3-oGHfTClJ%GJWXL4d+BsL8wd zIUlg2U1|EZ*-0}U|56cAFz92M$DXF2ao8?tAjS^{V~O$`k*h)Zlo|eNa3aw>#$0RUO?Ua(Y{H<3y#}S&I0^C9`+`@ zwjBBE*Rp-uY-m<>Mscxx&}(yKEN=vtXY|93yd1E6v$ZzQRx9#5Pxnwf=#CtQX)igA zIm@>O5}q9{>m!+_iOb<>2V%HT-00(>&tw+Qi881n5s*BRs3+`+yZ-2w_3_2q_B5N{ zRf)G|>gv5k*D!Mi2*nx8*%$m(#N$6} zUlRs!$>F|C3FIC}D;l#a>FK1M@2i*X`DBH45?@zP#u(5^A>d1P>1wrNROAA1lbl0& zjKO&2XBCLn8n@I?8KZ-Q&l=3%36TwSN(i8)qr!Tp?I8CtKe(koIiR|ugMzYPUJ=d- zv1(qe$PO7tIqR}$dAOO&YW$)P$gRxlfYW)a%unhDa)NFX0+{azBtj*EgMf!FbXGbS zNc~jO9bN)UotHJJGyQ^7MS0nzMql3J;6tm>!osAl9^+&6z$d3NRzbD7rG! zVsQJloCatNwG3t3m6SW3I?uXq{Gy5J-&YihHVCDQ1?bU2+< ziv2EG45qcvYjO@A9h8%jKO%ErTB{G9oown5vf83a`G3+q`U2>8y)sB+`>bS-Afd)k zZiG2Hs6#L-s+f#74+xKBF3+fHw#x0b#CX@Os3%74otMKy7;nmo+Lzlm>zdQy5Gr(% z0D_88`5vj}d4b?|>#FWkRJs=EKNbb3z6j*bG5V9L=e(Fd__G7Xo2I%Qd*^3C0kM_P z&6&wTQO~&ww5C7F!K?*&U&Dlz*K+m9f?0%G2@B9v;_DOyt$0GU`XyYEbMQRv&Rc3=b z0;NJ;V1#(Rx*EhG>GpDKS78}aK|hq^RWjX(yeI@x{Es_UcQb8n7NA&UvU%QTVTjC3Ohh zo#UUIsS{FlbiXoX;gF-%^=066a^9FsAYJbJgp53)Hw%HY6B+40Z3RsSI~Irk(*FOG zWQHYz`h4M_u2BTnKRNqC6gSUuwG-pnWm~wPRCszSC{_nx!6tG<%w;J1+1#_vTPb6m zEX*k%&kO0=p-iTLV?w0u*}~KVSPcfs{eS^+N5^Jrl9P5&F&r7w1}EA@zp3(j88tiz))z5!U%2N7%* zWpAM9%G%ZrWteUi`aouLhp5*HUe44hWduD#gkc-&>SiaMeWmAxO$K&0-1sAfU`PvP zU<7!pN>_=ab9TQnWk{=Y(#M0<#55|zL?l|3ejUImJ*;rjtZDwz6){BgO46Lc=Vyij!YeVVghrzMTj7vthfShG0Hy9Au3tKN}Wc;dz@!5WC_S%t0j6N_R%tqSCz%026H4hb?Tw9ZW`Ecyub zI*ocZBsXO!4P9(^;~$F|_6hoQ<}fK5bE#VfPV;3CvHl=E-OO`2`z(=;`h)v>UYJH* zP@j>P)p0%Uo>UrsYMZeMVoKf>A#}G8z%F{;*W(!PrF|q#RiEtaqFzkN$L+j8xkd2M zEie~Qqq;22A%6Y9b%=Y|@V36$WFOKF8$a&Qy^kp)@P?isuwbtct?@j%8(Ibr!#aG& zzABww15-y)=lhykM$O0Lo%wYko+JFVGQfBmitK?{_T)3nzqFjy{O=WFSfH*dTn_^- z48k&Ws7-f_=NFVCctG8vFi#m3(&roK&3;0N`GBG#z_4wRTsmStW|_X~@S(K$!J*1i z3h^qj!-7M3<_SC`M7Ul~6v9!?0{lK6Sa9oo@`eC*?c4gn-zz3XfchNYM=C`0EV>)o z4=BRY$!h6sqsJBWg<^rhg6_*vpO+iGM_6)Nrv#-K5n`3ofu>`e?W4{n@IO{yEJ%Fl zb53L+H+&sSx58z%u74*l<^)i$(elHL5Ua--6&~w)lEIrkubp*F#r~aGAY14zQQ$3dTi1{-%}p7>GMt?~Y(J=dB(16sL+bH88ZxHo z_kWqJ>BN6mh!mkGg@8AmbT#(4a5L(5u&6VR% zpb=teZJQ9^gVA2bbbiv+3i|(w1>G=|t`1-V5W%^+Oh^^r+THBLw2jtw0I(fsh@IQ1 z`^W|A>^+LSM}SRfLqCaP2%raZ^s;J_fMX6~#PiREtw7kvuG3K2CQ#2Q7R;fKT9mc3 z9+)`hAZrNB^Qm=drGP&Dw-pO>1$}3ZQWM3z!Vtg!N`p*oxOSkfsij1<+oMs{M=;a- z?$xyQ!!-W$3WK_2Q#OH*VekeHw$nEGL1)-b& z0U<3AdV>(STj(O-I`(K>_XEaks~aNnlEmW*`7;tC^#Hvl$1Ha_o>;jgQT{BrcWp>z zw)OpD#b64$Gqe2-QCg8VK-2@H2m;Q|ukmBdt$f?NKOfgMqZH_kLg0;wZ20eK5|B}f zK5NizENg9Xt30k(%CFDV8Sm3I8SzZH4E$QR45CcZn^Jnkyj`8;Emn4S`9guU#NSjD z8V}TFB1!?9Iyz+gF{Z}@dz7JvWNoP-s4Ik1&o5GSbU$bmN_tmM+qlL#I6=i|F0?)9 zcir|5i^sS>X?J9A+#BWCALxw<4>sMs4x_R3ixyZVXO!7J`YNY*9og9 z^v?5mb7A$9)Fv-sy4+e)=V%oN5z@?hSJu6n}j5yJQ#S5KqGKXrwX;5K>bZ%wxoKe3F`GG-SGXQh(o_OPfL4S+KgFUrUL*@t`eT)^=O{P|Vp@ivKCDvG zo}u2RhpCBDBC_PF8I5vn$g)*;Hw&TOcf!Sl)oRNQ`F-(lpqYDdo!DNiNe%HkJ~W^F+yHty$t9~(%acny8en(ff7UA z2F>Z3l$t)^n$YAdo0JoVHo$n^=D*?mH)66&VfQ$lb4<4>EYxQwJ}>r|Wqor0 zNK3EX+Fs{76ySH17uo?7nED;;>WT=G(R9h4keTrVZywmWUhrc2QtT z2rqa8PSe%uKW4%KQ$aVI1}55^qP^uDvvaiq{Ey;&Ri;nn2zx|h8M61fWM@vJ=RKD^ zD!Q)x!b?T0KBN>79@HDsR$U_yFO>4u*G9>^0EDMbvcVUE^>s;WPY7vAQbLH(+j3~C zo+bc)kU~1jS%9aH2Ll$)(6diBr8LrW5L=0^D=6Is^r0O8UQxI@mom-4dI;Txsen2n zRgHa@$0ks4p-Dgw<|r7Z2#Cxv`g_9otywX8RGz=05ab@}Ps%<+=m01ZrHKl~?Rd?& z#ZL3L@15(=kfz?+QFmmcY!adb(25W!8;zLL6e9gce=Mdg-s_vC)-1lLyDauWT_ik= zkFs}dii}`Q6LMguGDB&`q3Mf0`hca5Ti~-M%mWW9G6jHc%aQ0WMg?2F=VP?#G>7Y~ z{e7==oXkV-%pAS5@&vqYp@X0}n^oI;;#X|!O=o-P^Dafjnm~7CHXjuA0UsiAu{h1a zdIp176H`@x@}n0LmRFXok2#z}^f?IaCe${znbzEm zmd+V{51Fi8tc)WbC|xlIg~8LPz?zsAkL{!}-%e;FRR2Ky(_)OmLm$uF%L+dzQMHkx zKL~SSnr>+>Oi{in6NNHDsT1Ic5X+^+`_{0$4Y==vCFXtkwfpp})so4Z;} z>1&}r%wljsyNcP`h&g>N1C_L1%~!Ij9eprnrf%G)l|Skg>T6pwx~4u!1*8b>E~l&p zZ-9#0_fm4&9tX zJ0p)g9F_W`D-Q@MGxR1Q;A)w!lAR!tehImsRjgLx^sO}XT~7s87Sox4pH(w!;(+o;cA(fr z7?!61@)`of5Vd`ql7}(WtL#m>W-SKhXD{Ra)0;j`psbDmcSQi0mUUJZ#xXVuaK@Ex>X1a;vQ3pZsLT!Nqj(ny8VI>Q9#}L(y~VfS>LxR*1uPxt=$0J)!=h0V=jz%q2Qfn8UOpsI)rWDGO#&x$3;PK5 z!4IKF2W68U{zhew^2T*KbS#2`Smi+nLoWBd$~es#dP@!+hYxrS!w{p9KN*&HOXzzk zd}qM8j(O5>w=#(oP+jN&A;8KD;&w?4DhOfdG@!IbWxJvIppF!`>6ISJBAQzjommd_ zK2d-!mzbUc9315=!0+R+`nXqZ(w4lRl`ov22Zg|ciP%4}NyQ;@i*6gosk^oP?UVScI6l=-=y>D&8WWevi{XC4ZVFj6j-MKl#$9IjqW;$LUz= zA5jzx67;$pY^x}`;#rLH%|Qy{_b^D&NrDGoF#hv^al2cu}UL0+8j=A!m0Cd!Zj!W`0=63J-L@5MY3h zQh9!%h}N&=Tm!bS&m!Ag{$6MON=N5yp*qC(V39()lq8rd%LV#m`Vrz*y8}%gW z5Dk*I@s>1Vs#hCx+R4?{!KqJ)9l=mT=$%5~Z-lW=ple1(E+iuWtz&>BC2etI&Q&?Rg zWL67xZC#X`Cg&{aq);0$LaZ=ef9aK*)$>J?D{}^@&jDk!$ZmbN>Ul;Xu2iN_edrEi zh}-Fig0ZYVVy~}`F+%2(+%z62*8m1{7?1`RlHw&%8!5Kh%aVpk_$uWTdjRzg`g&a> z26-94gX{G~gFQ$OB6_}zh_ZiDe8@i3XU7yHCgJSMNq80%Tx(3|hZJ2;%@|+6<86{I zeFpR<0tqfjWTby|&%QfqTaCxLvmc3kR>*)2dbF5i%lCutkC_!iV&@F=*Q9TManR_TGisl z5(~bwE85mH!(k8zW!y`uNCcrX`-V;mfpSYKt6&P1D#UFxuN<~!W)@elwPBqrFKjVd zKs;zY^wmOO5bJbz(7$ZUVcq+@5HkziE(9=IWks->cU8orwFu!4S{5RpLl3W0YB z=}?_LZgj6~W>FjmTftpk=EYsc&F@y+Etzr+a6KyY+bg1vC)-{T8vpH5KWY(rTV_t} zde-rB{ZGqGXbBPE;aK+f>)uTZ==_4BP^ZxA zb1)Pp=G?hL2LW}u`WV5w9^T-EH58Qx?oXunADXp}U$7&xI;H~q#ejh@^vcZaTcW6t zs{_3!$8;`0?o8$>(5q?H|i(Cm$-H-_6HOld3;dx8rTWmEJP8Vvn;>$vCu(4 z@3i_lvOeuyYtHN8+nmDRELk(ufbPxlsTy4tNt~Vi*CQyY50bQtk8i#fN`&eBijcMg z^+_bci0SBZL-40@@cgzAo^eg8EC$5nDCYo>jX_xu2#lfLRlD($*U(Vf|7;&ghVOqt z2ON{#v79J)wHUurHG&dAU9S)6Iw}KwO$6=%Zpm=7ylk&^vmMPp1dJj1u#gIc?hpc} zqZvFN+GQ$nrmOB2AnTzPRTVik=60W*^01?bwOLECTALTh7yfO{Z)>{dW`_b%P=x00SOjy#!ons2l6D z`J9}Y)}r>uQITWn#}142(?Ti>>cZG2cLZ+|wVBhKUl3kQ)^WHStA^TvQZF<}lx<9MeKvkx`9l&?iL1kXAF8DwxW7;XD` z;dpG_8^t+xRE+olrcVgdMS(kl=QN7KW;Hq`eR^f)SpHh+KT$a94eFCTXBpXT?3Ug! z_Wu{c?V0Y(vF^;&iT5X-NB1YMUXDqmtGvpL>T^(92yWJvI}0tnp3!2rpY}#_!Wo+W z{|Kp1=q@4f8^V_ey(ngKC)0hFV5tpkBil${m54bu9b)b#G@=k@q)MJUBrobFjCF zlA4Pf>X?J9A*^KDmsGR>TW{J@@00@FCEa2@49biiK<8mcW&vjnjo|n3W9xW43XfgP zd?C4sMzU1J>H~R(Vif?4{HTD0*k<3DZ)#%>w(2uN)-#~qT&dg$F{{x(y4l_`qwO== zT0hh1bTmRiFpDZRoe%WZ9GrY3jE%UWnGVcZZHvxqYeh$t@h;;r{ppK^EJur z`Iu7JA{{61P|60BD>eCU^e?sFtT1;;0+<=-mK^8ajpFvXpJoep=+3W;)7DI%$|1{@ z*@wXzFZPV*6DYKCwxa`rrT3)+DE27UWnxb639V!?HBE zD>8jh6gVM7w_R=wO>mdhE?)Ee#hPEM-B6b>#t!om<>b$l9dZaoxWHs0#Xq?t*Aa`G z*+Gle*Boqf>CN(E%mei<28%E%IpwDn$ z@E##7T&%Qi*gATSV@VK6@zD$h@IS6lQ)NB@aPONyg(v!x`q1!M#V_hqvO{PV(xjs)$bhZsv=^-89G7dg#g{T zk6gKqBIVj4`ZIL0=j1ItX)#@XtAf$1K|S|W>Zrhj{*`8f<@jooj<)!owDn(2l@!@S zug$!7m(qyBh_?`=1F}D{ptt0jw%3k_<;Vs4<{ah$(P3ooETPEd!H14Ku1yzQEQ%e1bEoyVAD4ecv7kOxV)z=RGMMS=9nJvG zhxzG&Scj=zdhE}s#xh_3>V@H&Iu!z0T`dR^M|I0#z z1*Jp));EF*ZQwNCb9b?klVR!^J*i+u40kJO)E>H(Bqeyw^m<^u+<8E9$tI$4=t)J1 zRAheWWuW92V8j*yopFr*(M_|Qu60(w2>TV?g%Ok`DS&6+tNV-nKD){IY`36rqzsz-hV~?w{`{9k}Q5$Et@8ndlc34W)!` z%Pi=pBOoZp=pWrEC9_ptl(KKt5P=Ls-9kMfw~yZN_gaXZj=ZfCI#(3A(O1}}2Rl@z zW5)W6@<*=F?Lxp>esT`|Z;RkM^RO{vt5I``cvxwoFi=;RQ@SRrUX2XCaD!B*14RM> z6vp*F9Y@mSSm180Eh*XD=Wf+H$_}j%Mc^rH6Y`~?Hu9AeDJ)^hgihZ;uy*qYT zp;$a75_G2!pe04{JUb)57q5F4k*UNt=r;3Ks7vHYUB~$y1`O#$3F)#hV7P62#z`%b zYLtIMUs`C*95ciBQ@TewP;?t64~x+Ic+M!9qnv#KWvTkhFl#>gx>a%NLUIU2<#5wN zMh7ViozItqFoJ&9eMUZ4wcoh&J>pMB5eImdkjy)}m{3xOo^dCW2LjQdLESjPV02LF zsj&S*as|bZ0H@7xPLZi`at7Td1pY`EHG#HC@y`~;`Gbd9`=&JJO1q+{XXUE&9OXhjq_C`3gN zH;8fS2g*@#597qsQF{!P9*5c-l{5U{4@LEWa`s6hf?qr39{hPTy}OVP+tUV!`{YmF z7$HKZguokwt`_hhpMa~0^M$$}BShb(o)4-^FRN`_^t2ME$3 zJ!zp~jd^PxvFcnI&fK(A=j6u- z$V}MN3J(t`BV@qqGhHP`LO_o0A8BtNe~WHCFQgivHc^r6u~-q{xDl*<%bx}5Cl!_M z68g@}V!Nm}QoN$fa}XoZpLI^EfmwDMTiKBn#`{dK+>_t2bj38ppOPTxilMu#n5*OD zd^u1cu~o39sYQErboR7@!6U{5dTr+8qco+oE^r@Rgkns3--%8qW|nbdtaVgfcov(wI6vT|kS0y2+yk zB6tE${a(DbConC0jT>DX)0bc7#QGnbG#pu*?8mZsIr1~6qPVfFK*u1wQ zjguDJGMICZ<&2!xcT2$I?Ez>Q44s@ZbpX%{9~DzrbekgdD~9{g8|Rln7df&a_m~b`c$}PkTuzM=Q zrbHhsm8msP+LL#pVgH~$7>;dC-bHP$mjw2&v#8H-R+k!!+8mkR9UO1xhAMtu0-z6u zzLnexydddnZ~>z7vqoUMJW)fP+ojLYSnd9mn9ylLSsMWENyPpUO5_u6*;)lt05qBf zQocZ0olhUTnzpZyr~2;L5t#H3)F||l%;qI>N5E8)8k!IRv**C0r10f}W@Bl3rDMi@ zuY!;QbgK}c`9|K!~J8?=tu3F8)F_D zEhtOdvaq4sa~2ueBIt6l^X5}38F+gBXgev>`&fi}NU=x&N`nSGV!9gkUuKP-;oZV5nLd)^ULoo^#ft_P zJ_V}?p_4*1`E2OOu<4XNeZ5r?=|-Vm^73eQ5O0d$#bC6U9=xtSwSkuY2U+3%P!#BlO@?COOZy=1P_NT)m9MpwrqSzVq`Fxo$KhbU0yBTaUrbSA?1s7~Pg*R5FFECmY1zzvjS5SUGRPN?l*a4v3mj2x=F)C5J8#2Zl)_y;Lbh z_ZR1uTiQs*BRK{c|4uA6XX<0WXk~N48xppXt$pQ02QYg_Wz*Nc7aw{i=uc#xA5$IP zAUFC#dB{~o=&B&60r4iY8;#9xMA(}U>0k;*e19jTaY20qh_xODqQ=r?9p&RR4=U#0 zD2wN1%FGZzXr5)VJqY4G4yc-0eEUr|SmS!PGfkJ%NoaN@;~m3pPyn=UQj}(RxiuGm0jQP&fC~$mpONz|0>}!BBT7 zV?W?tA+6Kp0(F$L0A%8Ty04zvQ=9;ac>gyc17PSDAwb?o1)kOGkj81=LT4+RHp0?* zTqh(M=v6|15n!BfU^^)RDjsvK%L1^-tLN%49m(TZlhbDv4$Xw#p5xpk>N0|BbQ8o= zA}0>^q@Zqx$Q;H`VJ^t@U=HJB!hBhX9`+HOJ$~#>DOM$!;nQpUYsEnD(7OpGs0!Vb zaUGd(?Lb)xhr|%PwzSULiM459HYM-3s$7*!|D`Ap*~8i^p`48S6NhKiX=ZFMNZa9| zX%4v3=9O2vm<1st1n4#)U~)@N`zH;@;N9c&(VA_AgxQQ_^SVrZEOo7}-MkT-%p!bf zgy_fC_W8xCHvaM^?W{c0|)q}UjGD8pEol?{Ol z-Km1~0WqYqiA;sZH$-Vt_^r=C0eV0TAC?CLYg8F}XAV#iC8y5N-Oxec4QlQ)xQ=}c~I}GPp3b!Z8~YMY5iHfC)Cu(B&oIEP`=;~ofHMu%W4YI?_V+@!*5aU zs5R)-Io!M`!bc`BrvQ#}7C=jMpnKL@XLmt=C-u>_&cvH^i$0C)LVf0eg^Z1DnCzp= z>?hL3BL8>;LTPF@HLCYA<%Ho26!w6($#k_^!G7%&L+rp}+ibm4(^>)Vwj%I-nKcxx z28PLT3K{<;@WZ+u)4tn1Vm+plTkaEUmEPvn&?zDC1|e#JI8YUrq>IO!l&g_~Xd*OV z`xc^s%UHS|a(uJ0N8^V+L1KafLeD+dl+KZB2QLZNEH1}09Q3yCLYGfYozOtfWb>7f$O3lG(*2$4mdei8muh+$Nzb+{(~m1SN(7}4K#Y-mhWS^6#dgti(BVT}#EetP z77_r+DsH!$_yI`C!4|DFhSQ-mVQ)X2#`FICBjS8ih^j%c5P)0xbhRp+vXJ66?}5~2 zkLw9p-Fl5TtDfKsb(vt}iQ_Wyh7@JsX*vzBuV^W6NxK}(M*g(!GYNxw3%bVRh?0b7 z&#B+`zGX%8m*_oiqLsoNLT? zypNmiFFi2Rp$;pHizoG2<_3?=X6-D=S1K;54!ttReY0q+4Mvas3{DPi#K$%Hp@-(# z`l9@j9W(T^;)P70tgixP$tT~9{?)3sD$HzV0A)Z2vuONuIV6Z7Q|UXi5TICEV2cpO z@-lF@UuW+gf)EBAwLPA8dPvDLc-fVywI>;u)0PGe{fzF*V14XMLh1?XkIm^XRy3wf&>M4bn)ooFViw@jK0BX1g3t7ZaU)b3yf9lSaNX`ub5!;5S@a@EHinSo53ZYwsz(FC} z4}UPW1YPz89@E%6Bzem;ZT1Vw^D8o?8Ud&2YPB5YvT&{FmOi{^bpT908qmgeY_!6n zLCprs{g}}6EoTuQK7gm8VNe0#G4L(&pIj#o>G8I#1L6PsW*V8 z6YCH6M!*8yF>N67eUz3<7}CE($vyBEp$h@VxEX)d{T?B?hhp-82UHT2 z9;N_ViZyWqfS-bx^gR)B-}7LW&grKWYBJN?M1itslJENemx_7b9+nM=J$&yJFN@!& z_~-zlf>JWz4MOZ6xg^J~7oKbU0I2&7oD$OuY!*@i=v6|10le1-*CYr-w9FHb)4&s9 zDY>+t#a>QNo0Z12CP6*!QjRD9^xBjst7|ewWby2Z zH`7xMxSyk-*aawc0$iU+@$acY%iw<41rt*~+Ynw~6f(MoI&bVltO9hPAHWyI1blRz zkGs~hXAR(!{EzDPnV*5s%*K0MB4T)4h`EP)gZ+ryQ5&slrR&RDXNB+ z-gE{bF91BOP+u0p7y51?@Ft=2N5t@@so@9U2gGV&kT|{4ShTr!&)HvJRmhz}=%Yg5 zokDWwoEUHPgXq6SDKPR%=f~K~TUrjCIaZv-mk$QRu6|Z{ex_&W;SJSYl~SUsP%J9& z6Nwc6u8$Ni0!8)WqN{d{f;N}hu5WFz(Tk|m@xKrr;r zN;9Kq^zRI!pw`DIg};@XX%5*28M$EazzFywK7jQPUf@aFg3y(_g|vMrZ5V*t=%5;; z+xv!)!b9&80`$c&MQT7vBj?UJ)hf z&?cex2!ZE>G<(l0;v^vui2lhZJxJ9TTcrgpE&I&fVp_0CToDA+wE!zTDx^1^4=EbD zMIAwJ69TVGr1Zz+_DO%~$`95-&TkfyXXvC5 zfWky+8$^cLYYm~T*yLuq)mpTX_8FZgOew*ys^G{3>Rk*SDvJtaHHuo7J>W&b)8AiR zs&!`;syaP$zPn(I|3Wv>7$}VsK)FW++v2`@t`2$sn#?Cw6zWR2UVaBBsm4fuJLXL# zG`(LHVlts_kWV|$$aNtzgnAptphTde9HW19`%;tbFkrNNnPMPa=$$#_9imj8l*Ir_ z?)^IXCOsf~dIk*a zfw2h}HUxVD{Uag3cf_v~buNsmY4G$@0I7iZJD`6s)pUZ*5l_f}v%=7ELBE*89T$~4 z7A#=nn1dK0j05ut8_HIjI`y+-{lS-%H%th0YYuoHj56Rv!Vb}#crmzV8{%5aCs$W7 z9+&SX1xHDsR}f6FLx|Wtx1N|&!jysebvhF~oxo(zWu8_jBnPE#0oTlQl_XzI$nQDr zeNut}?{@;hbMi+^oEw6m7_lh2v`T>j=z4Kb?qPX+T}VwrCxrlQAOZ;hPTlQF#5FZp zY-iG$To}Av$`P4|dJn%x*PcN+O%XlQTsz?Bz<4rg${dSas1`B5*N86Wd_O0O_IL%;1v=&+YY2}fS2y3i zZKXDA;`>zvq*sN0fJhSGe?_UgQp%oPfAwnl}I22-3U|wviPz&1i zQCHm^uPv^mjY5B=43ZR-z7II1#LK+;@=hJ1yL&lDB4ghH~+Q8CRCDYBK0H!*EGlQ1F{aAHo2Ap2Mu$*>>~sGEn=7Vuleh<9tT5YSrA(xfO%Jx915bC7~~7zTuS)&E%iWcp}P z+Ww2V81oi@OO!;vamst-kEJG)Aoc47i3N-U!=7;pjqbBT#^2D}g}_ZhX8_~q=DtfM zDpc6bO;dS??lG5uA|e1bql5JCQN{X*5@pQ{>NB%%mAEl_MJMAZmLRB<1Ns-Q=b0anI#q zLT3%#DGHPah{<>T|MW=FP9?FoQ|1)>7sV3`3%wynXXow~xjoN=(u%@z3)|9uZhh26 zGjS6Ohtg3O&^KiEXDcaKix|}{b8{Vme%pb%utU)27f&uPu+EgL2RH# zo1H0{!0p#^8juk(05ImaC1raRg<whLh{h?$Ipu{d2uHEgKc8Wg*huS(g z4+}UJCvB$SQ(cUge@o$M_0Zj-z-@_a`0p|yV6pQOrk@kiYT}YNitc%pb4XBgh2cX& z`X4AQ1Mt?7u2yT#1>pXXGT>|Iae4YK&6rM;!ocpAY-lP_Mm>}~8ctmns=OI3eLdA3yl!3Q3bV(Iqf#>>XC(?x&g~y5h7gZyby3xlCB2-M;pmP zgjOJ`df8j+5ft4E|v$?H2p+ZnG&!wfLg;0|N8^k<*bk6a29fl~ z#e(4i^a@b`y9F1QFn${W!-YoYkagx=y*%`YBK)$Dl|iTv7k*Q2+M)BO%Rxxc2+=68 zGMEoXk;T%x)wZ?yuN53FP&nW|Ul<{ZIr{gyrF0Bg$Fqh*LGBY`grIZ+0JABtKC(`y z;3#JS+;d>+yXFf$G`MdGQ9bBpAyAg6*XDXM36Cxs%^7Pj3_nmk=uRPUVIsx%*q4_DP#Z{h{L0&Y{<4J}e`_*5yB_6!?4c9Kqt} zWAh4l-fd)q?kRKxUET7tJ3r9eiZw~qR+{+A|h_>o;i;s{|f4!t_ZB%x8E7;j@>zE=nnD9izuWV#ymUrp*^ zh5Z>JMS;5I>StsT0XPVl#@Ckh1-e<=CAmZ4kRo&`$N4eE-zGN-2+$T_>E*x=h`mMH zMK{~$*nO&I z3x3Y1j4iw}OBZ}b8PeOLOq3ovB?OofkW0i$ZMZaC%&rQtrUq_mg()JSuAuWmfVLlj z43K~)67RVmc0y0g%v9J@VMOx0kfs7PrqT?WKm^Zm^eej=MDfIYbx(woYkE})<#GXF5uJ5<2dGI~9M-}cTg;-AL7lpt(C09fX zOMsCHb&Ji4;7`u*qxZ7ICl=~@j+ahP(+9=!K1W+1#^xXfD8j}~?T!u-%VEW$mp${N zsNLdvM0ioA7(D>NlVz7mR6h43T!h~Js`eRoYcb!DzEu9vO@<$7V5uO2>NHJx06Y^Dc1T76d(YDfqObfwYFbNM`W<4hTK72 zmP~I(2jvva|Auf&ratDP`XdaJV@}WHGn#cirj%Z4N-rCj$+H7;u*^>iNe=4TMgyn* zJsBjA+pi;aH_4SAPB37PN9&<}hs3v%>HVU><3dE}tPmg9U+$R#;8D7WClf&va{)Gi>WNt z;|+auKt}O*N z{MVH+8U}Q~D1Zb;1q|hZm8Ipw_a9{WF@5y&`&BBc6M9wVh}9hxsLuRKmwoheS({id z7L%=#0;3kFn>hqMI>;LBRV-=&iw?a>2$aVc+}jA4s`=8f?M~z)u8TTp%u=~S(P^2` zdo!!Oq8L=??iyLH3+{Uux;%Y;P3P#KeP?t!0mDx!9i$6I=YZD>k+5e0x1*c|_ zOodeqJ#6M6O5H0jgIW%^N@n9@ozSToKK|B$y zooCZ+ov7&qn~MB?Pcps^XLe55z1apJD#RkYUhaF~mMYz05RIm=2dS1r*RMmlt)u z*P=DYk18(tgkEC-QtQvop$STuyO%JwvO(1ksg|Q z@JCe`CIK7oFKS%Z&YNoN)dHG!aTf7iV*7hajy3_kF(-FhrcPY+CAKffjg%8G%_k)U4?j=bwg0qDVhE2&19ajUud_a*lFp3aT42w0^ zLr4vZhqQEBOUp}R99TCC@neBnkYU>Q8m^t?PgHG$Zi?m-3pyl``- zkL0MonEhM|q~Mr?7{O>d(Ejbf$YmWA$6!@ow_;0~DJ9$2ta6_bOVklMm7~5%lsxuJ zAqQDQP)D1Z&FS$2ulMXxkpCbgOHi+Y@@Uu-fer#r3!7QLOg~ zF)>gdx|RDk-P#cPzSDsqcsfo3P;cj_ljHPyl1@_Vwpa94hhFexzhK(*Xb+b@A+DR1 z5OizKFZ=H!V2&|9&yZ6nHL zceb^`orWpP$%i@C>&|QYE89i46juPdM+qR z095RM^-t;b1?hUBake9fQ~g`ZE6dh)$9uX9!g-}KfN-E(1N)TOXr(c&RC6@3lqWX$ zRpF>-%*U|UX!TDO5K9r)ManG#W{z?W05!2gx<$2BGQCk0s0fjyGl2q5(^a~rwcyWs z`RX|ae9}H|rAcoPjB|73tdeKOfwn=n3IXq=(^da2#_u@|U1LRlPfMfGMJSU30QRov zlpF~-%Gnpt#cuomtL@xle7mYTUT>ro&`^P(qL#UHAAPrQZz;Dg`WV`BX(@%aP@cow znYlC5dHlwC+2}C3Wd58%FQ7SPc68|73B*Gs+4Dk<4kQnvz zS$nTDdw%nqdw+Aevyyeb=j^l3+Iz3P_Vets&)g#W0Wh6owft5dQTO7l^_fqA7Jj8k zSrKH9q_}Rj9E+$gI{6l4iC=!7`+3Pb33Zp&BwM=w8aCrZhcuGyhdNNZso^}wsk1|E zG|7xBdqhRPUxvHAMKPPAgPf;WPCGSwz8yIaZr5XxR*%W#_(zl)u^d{XirF%aac-21 zs-%%fiJ64VUhi$4RJb44$M9-3BECXz&|L+(V=JX|b zh8c$4_!*0F_$U#xcz+zcs79ziZpQsgWbuyoZ1LUmRd`>4f4Sn%Jdjq&_M@R_4biR^zX8WgXfA-<46AR(*kIkdN@(b1IrV=@em^8z#qK&7ovYJF?oywy)dM zwoWI~ncxMdrv6^Izr{3<+eC3W1kqwn;O&DH90iCLQb}PuCj_eAN!U6>oQI`p*iw;# zEj7uUcGRl<5NhX(V*g*n5N2&|X1&KYBeB9M#x*O`3^;h1eS*(e;$3IFu8$UDmzFl) zOgtYV2{v?M*2I~rE*V3epsq1Ix+>DTN<35_d=;qBw9>?SyK`>?&QayY9|APQS@@jK z^W22cHxef;Tzo4i9fSw97;&>XkkXDUz%DOyq`Elq@G&xbVPbJPRC5oq?N*A`yOOO{ zziPPMsYE|gYe~N5EI<1@#+@f%b569(Dy{9Ta=AsCZd-{Qd?T*!h8>EE`(Vie->M=U z2OQcQw0Wa0TR%b*;cv#r*AE|x3hVP_+mWq50c`qjv_=2xJY!NDNOBix`l{SM+Q({vc}6*r^QwyiT{8Xi>r8`dN*rR zMLMM_>g@>BfHQE>6%|%A&MTC8*PxhlmHMR?*V7;U0At+pyJ_a+_}t3s zTCAl0Jdc|iaRipmCLEr6B>Dj#Bf=vH5wBAkWB{#t!to3T2fXzaX5IAI4eB=bJbo?D z+i^Qs@fJ|}8myrZe31D*h*WZt|0`5Fqv~p~^>K1Rt^ zORD(%`uHv2Fqy3%S#rfM4PxxLa3|NhG$1yuA133~fj9z68I5u^>=7MU4?hXhRXykb z0k)aCi2DC}cZ0}iEoXt~S&GCgHJd$dJaYOo1X`zm71nY@O;WNn zwUjYTZ13f{uO;|QI^G^Wh(~3J2djtQNRJgyCo8?SzgW^%?DCFOJXSsZ2yCl%(_(eo zU*)!GD&G-wFM!sjb9d9|=B(~&i5eqr|A;AEUhytavc@4#y9!(VBy-x4Zb=8xdELCF z8{mvj9B16^53Bhr=eVobd{+^D!N24=)gxY0Wmv#l(om%~nBuZ=bUM;b!)o0Tem}Qn zV6#C?+2fpRn<*>L^O{>hj%JeVkPK`EqjUC7yzPU=IyoHbp_TtU zWNz#sntSTv=M)RrW3ZK+_YITyCQL+^g(D z(k+eZjXyYc&jSxcMSLIXP?YGvOHxBSC$iRt@N2M@n5Yp-j^xgVGwz<7y>d(Kb@R5v zczp)8po&++()(fK+KSa(vegqpX$38P$-@3j}~?1FRWM{wMwxiVYq;{>{|;-#Q; zHmc#NGFP?_fp%oX>q1=V(3rSPxR_kMYymqm6O)tE%gj5i`?`q@5*PRiBPy0vyxVaR zFRqU|h1Qx)29SYPZ<`>-9MiQ1n2u>-joMi`@%rldsTFquiax${;sq0BpB-lnz6ERS z;s`9AwXPir&!g05SKO;KDAiG}HdNBDhPI$O=@bhtHuf8gy3kD0sKwi>z%2Z&d=t^1 zkTyJxx?9KU(jsReJ_~CTd>EIgqK#TcRbh8*l8z{q-R;JcH2bbfW7c)YPsWTV-Qpdw3E19wJqQcx(0d z7-(ahI=9DVUiA08+Lp<&Wm#xam*>VL=x-A%TOzkZN$Kv>mj=&iGy|gClCx<^BS@c_ zUSb5vG@`Hn*w~$Z%<{^#JIJ!K(e2l*XWl@Z&0jH#t+ixJ{Q>1Y-A%IOgjww2$x1ty zV*5E<-KH_RXrp+v$`q~VBnJ|zB;_m1^StIXs8yS9)JVhv-^VvUQ9g{naUL_z#jN1^ zGa#k+hfu;P$MQ3cS937~t5oq_uw>>8LuOc#^XX`xas5%oHFx6t_pk+7JP1qnoOVi9 zn#S8om@EC?ZR$T;;*pnkcruBf0}D`ly;!) zBy-x4fn*ce{Ro%sKlt#oe4IJiTd)6qK7=eYQnY_68PA>5o=3vw=6KTnQ;uO2c6)Ve z6OYpt^8N-P(CkxXyVB;_k=x}7%~67wfGv!o)uLq3DrR$ny|bCxoUo`|8P&))@np7( zX&RSqelTpyr&|eqUdX1>M~}Z;yoS57f<^O8ve9@7oKe-7TN>o9U_Z?E4-OII-@}(h zP`tQ4t&t2%8Hlg6*5}3@SkzZq4#%gX&*cI{+Y0eTRq!3)`MOp1d=Rx-ih0P`M~J$- zw>CW$R=7*Ey++7LS_DO#X31P=DH9-WN4B5M$066??@`#kzjzre%^@Sl>Wb^H3Nxsz z4SdGFf7#%+tVHB>=rZ+5`8$VSz-i#($Mp?eh{-$vT2raqn_&gELb;5I0}Vkxwl?v; zp|?bHlm8htD{q2rH^l?6^e#BEJFYhDvtD-g+G^5vleZ==R;JnH&rpXCxxzbs5oHX% zQDurFuyiYY0Zih8EDQRTThd*BmJdy6jlUtqYcu~U9Iwev@W?*yiZ2Ew_12Q1B)9Hu zXz>x2OKGNh>&N(41h4((Ik2@p%Um4*PsOUBpC=~fv#6pZgICm`AiSTHiS^or0Fw)E z;kEkxTz!{YHcw*KW*4=wiziCPtCE(kZZ6`tQL#IYf4LYxw$STM@oND7e!+V9F+ASO z)Ao_X)Rq>djx#*1t4&It=Y4Kt{(Bx-nm92xW9MwPY#+x`VPsw=goc5F% z)hBt<3QNq&%o;QegwvtO8P&y!Q!&T(6P{4%;$`(|s~|@?8QHXz)+Y3t-8|2209JXe z<(dY*06C7+M|fV#+1CAK-U$3EXQ$I+vCFOQxNiy3!| z>&^*f-|16CXIHv@zl!YLm>b`V-CHijqb2~d*3+s}wdP$(;kUwzo>DD}lf4yYAEMcOEtzjSBfcG!v_v0IyReLUF1FN0quVy9c|~IdFtftY zaq7Ggo_iY6v2==Aw^(XZ_nbz`#D2<@=xzsNbzyF@d`$m1F6OS7=F%TgZk>@_8@1&h zKHB)85&L7fKNVIhvPn=I$+}5uF-f!OM(q1>eJ7mw!s;tKUir{4XxGRQPX2%6x7Meb z6SH4e7fipB=vlJF`6|&o)}S*5Dmb}%3E+{Ad`A;~iuwVc>!n>QycFsFBL3_Gh}i|O zDNQn@w3yUS-V?ni*#)SZc1^AH7+6h>+a+F&3H>MWpyI`x5o`Oz83?x{X`<90VE4awATn&4q9omg*(K3GnY_%w6&m?OkoQ5XNkS}h_PJ~u{eo|=a#rvz6 zePHI5PI=z1#_B!5N~XM9TjVXoBL9((kC4Z>`2noR4vT2>Cgns#x!V5NW`rG#lY9CZPO4Bt+lMGuC|K;rjeOp7Gk6&Bt4=UIJ3ZUgWyg_!nK-WQo_u?k{3fa~G^8 z#B74z$a#jKcC^W4PCIHfXA`t#Fa1zAzAXDTMGw_S9|MhIM&G~`maMyEph=sWpd+|G z6<->-XzZnr!B#A-V2wp?sTHh&7eiqhZ`Z28e~Q-7SEkz#N*w-obDmSRh+n8m~EVWAe&bJb)Q9xc=V=d;x3+10BySd2C+MI4n=8|M%6WWh=d`tlOhR*Cy1IhFOGncLU&G-P6v(}ogF1H4X|y|agJs$bI9vKAHYZx-@Br8RCxh1U7@C;3-mL*sC> zpJRO1UN3{Kki^?zJ{G%OSi$Aa0>EV z4azOG*S0uCE4hP-jleD~>D6}a@MzOw+Hz}gM$`lhqD+k1iw49F zf4$v-0Vl=W+T_I2p%^ZGpGS?hxVL)wLVB&}xL)bvc4V|CrXE_4cWU$`W*%lYBud4FUd82d=D5qaM}1O+fr}yBfbEZtQAwwUbiy59z-#( z;pftRacBs!vOm^2C~hw--U3S&FN;!YHG}I9%4>5(v?mm6Sf|+mXUYyEpMjQD?O{t{ znOEY|^oWN*$uVsP;5GRa*BQ8O{j|+IjpghY5N-EYOfwzjx=jzax~$=}Bi)l6iI?xp znf^T)lkGQg-?sE-aH0Bp2WYP5qZ)8>^|A$AEIPMnW0#BPqWJzHGOR1bY^vKaigGfT zewn(fILW!xvI)_ErE>N~^=v&M+Gb17MBP@{3enwOrRQ34OBNAF5&E(3QK#gYjh|vF zfiBn*OVSrsW2mNI%|q0J&C&i52u?!~HM9}=Hf(JmYBtjS1>$I=`iofJcI;@z7$M(} zyHb&FxO>OgeK8tvdQFVRCRd~2eH<|sYB3A->$q+yRb{B#k%hYM>JmNb%kg*GT4fR(Cwpg`FKcr8mAP+se% zs$xQFb(%T(B^E`NITBXG(!;bYe-ANM7-DuJS_w)Sj>u)rV|TYJQ#pT2N}pTt)*3gf zSEHRuNUXOzXx(#4ZGCBe$)(NRI-6Y?IjQ6kf>-I{-Wn(EW~XEYI$-&LqF0e;1uAB5 z;;i#?7C}~-w!q>+Sh8%j)M~YLZ_jOk8Kbhkm_76O>}ZuO7tVVFK8&q~Bc@VTk@zyM zhY^goAg79LxaOxHrXph*pRqgw+m48r!jer_mVU3z0RuW+V6I?Nc+5rgj*qXbFD+SP zPM?})lXdr{iOT=8v{P34Vpa~;(N+idG6-hk}-np5hqKq zc(rkWY*~y_D#K_GqEv|Y%04)($d2_9*{IvnR?*rf z^0`r1<8uVF%J<@vvvoJa^G0xW`J2iWZO5ek0RXqU)P#ot2iwH>5!x!!`M=6N_)#O8 zh-fUNmk~u$>W-KIL~w(MagV?0_nCVqGlplYweV5 zs+z4|>(6c?uYMO<+N<~s<&qM6O)Z(Y@C% z6C@^Ar+~H)$Lh5Bzm>!3=(GL}o{Y1Y75q=*!80G)v_+iP7uM$3zParA!Sgn;qwp0~ z)-_a$Z{b`cm#p~B@oT(j$m<+(I65hHBk3G zhg!bEl{KGu2`qWtQY*Q2Z_h2&%M2#%`Cp0h)Tj6}>r;U)MK@BSc7WW_CD2huvU3=C zvgeUlsWr+?V<@H`{|5=SV(Lj@#$1Cp(^|5?TgB*Zw<-*3jV}THA+NDCgRCIMOuni$ z))`CB&qUd0qcgG+Z+8s!H9Tn`qV1YwjIw_B+7uYTpg__JY^hvdp6{hzjQ=kaJGCNa z#0Sa1%NYnYtIAzjn`OK4IQ*LxUj$111*y26{%B`u%c?R3e*-<40`c-HBP}oUa68fM zeY-T9)@1comRUV@-UttWjXX0e#T;E;%e5*`8D{HtS;?)S$Vy)Wy2l0E209FUlm|5z zakO~b(tKnX_4Yup#(NQT=n?c5Sdi@pq1iVmlPfWj`U(;) zm7>v?`a1?0XkziT`RF|F7%v{?Lm7T%WfVoFd44vX8lHS4CPpU_VD^bu!qNz=;EdLG zI?>%6)1SqM4&w6oI_uPDVlS2Q_k%Cu!)ler#3TW@v=- z1!P&JidkomhMx_yFmlP94kSA+v2k}e`>E{HxSSzt5T;jqo2O;YKBSjGMTXKh4$ zZGBRsOo>J~Bb`{ib?#pKIuu*c3cGQynfGTrWNRi~0*=6KOt>71?5vNEKQuAt{Kndx zKLTeRP4ua+eo9e-`06U`C155&C!><=byAxor?=bh1l(GFoE@EG6l@1~9^dLpyuM2K zD(7$GoUntos~NO_Y$MfG-AW1rnwfE_oR#BI*k(@r7g%~X@*U^V5(e3s);jFqwq#S5 zWV2BhT=yz9!%f)O}Kf}4A2_-m-zt@sFlQd)}xg}fGIVNn`yQFr!#;5q# zP<(QEG51xGm@_kChQaA+DU+#+>PPwU69nGp#gNs%Vw!w0tN017Rl9MPjk9^*nwjpI zMVy}JMm@X*XN(;x`7ur=PmBhzUIQJa$X0B3v!2*;l-+}kgl)6Z#FxXyXc5T9&q!@2 zHEqSm;=RiXpTRK{Sz4OrMWL@zo4+spGoJRtVgv*pgj4GoP$IVm(lEP79pTAc{?Oa@ z<%7i420$FGk+p0jnUw9Qy=ON*UJA2^!OGmJYr={i=See0Jm($QF@*jfwq}VJf94*V z>hP}?`zji1X#v)J+T`lqPnuhjZC7t)ZMHYFGO;{6H|ckT53%)&f7dA*iXNs#)?zGX zF*B&0)1I;f!XM|T2=Rt0#q8ZFSufZRjAh-&){pbkq?kupK(2L^XvC$q2zkw|j=dg4 zHn#MpqFs3g#mB^pFRh;EK{M9kEv5dI-BPP8XR{`zPE7cYYx8DL_wtCjYppEa1xqi6 zv+R1kUCi6Yrqs%;v8c0@xkN;*{9@ILc&P%RIatd`*@kDV+n941eynkoqxSOy)O@LV z5$+ElL`{j-4^qzhl&j&6He9jd&TSO=IG*8@iaw55`>tY?CgS)|pF}fTzsY&qOD2~FC5AB>V`6-?Lbv`mTMb*r;?DKY2M$@DIY)hvETPYK_3_jCw{@w|=s@L*=XM%ga>rbeb@c-Tef^ zI%EV^xuPuXEzli*jk(10E-OBcf@rKk=vku!K741+;)39z?a!j9M+dSkl%DgD~ z)~WWl>%8-QxPBita8XrzZX#Mz@|kH**&FZ^#KBrnJXD|RA75@K46BV~c;EJ-`A~FY zwbO3(fX4{*Da63Yi`N?q==HE#Ni7)3Gf}>}+1MkO9nSb1wQN%GQy$k~L~9z!I&v6l z4aF}M?>oti@0~p9uvH=A)v$DBf$o^e$fR^PniDJ9jStaY$DQXke4zTY^-pO!<@xC( zs}z^f_#Vrtnkn|>aJ#*SBmQqjit!h-)y&Xv3R3lkkX&3hA2-L}0&@qEQ=+)HdbT2O z$@mYV*4=Hda=tCb-iy7Z-Wu)ak*P8xuBPM#^4T%*wa5iQxl5Emni4VAUdC0ZT_=b)U)h z+(0s*bjPXlqgv`G(D_lf+hf%)o~%`_M!SwT-+H!9_7xTEj<&4BE{l;1lk=|eO> z7E$r?DlFqXob2?4auoDU*g8OTBp}@kr=57+D#Pm};?PknQI1b^FTu@PP&*Ni!O~T5 zt8$-Kaw~<)P2>cGEs}r2+VU}o8b8-PItNDD$!NuUKVW#SS#QrI4RVZ577)PtfmbNpL)(=mXVtFCH zpT!+9>((ncSFnO)?IlU~q&8s3Zncw@Lqqjyje224%cRtbC@#YsiLVKN2@z7Tm?dSL z>x{3WvJUJID9uGbPAHe@``)Q$Kh803jq|+4M!ZSCkTq5Nc&bKhslB$?Y}qX<|Hom^ z<;$?j5!2|LhTJLH9da|xi1m%6T1AQ1S4mbe7r?0yF}Zr#0xo}8Uvi^Mwm-U^+kXdZ RDWa{8lmg4uut&1;{{aV}z{3Cl literal 0 HcmV?d00001 diff --git a/crates/fff-core/tests/fs_delete_handler_test.rs b/crates/fff-core/tests/fs_delete_handler_test.rs new file mode 100644 index 0000000..a37e443 --- /dev/null +++ b/crates/fff-core/tests/fs_delete_handler_test.rs @@ -0,0 +1,320 @@ +//! Reproducer: macOS FSEvents does not deliver Remove events for files +//! deleted from NonRecursive-watched directories when multiple directories +//! are watched via stop/restart cycles. +//! +//! This test watches a temp directory NonRecursively, creates a file, +//! verifies the Create event, deletes the file, and checks whether a +//! Remove (or any) event is delivered. + +use notify::event::*; +use notify::{Config, EventKindMask, RecommendedWatcher, RecursiveMode, Watcher}; +use std::fs; +use std::path::PathBuf; +use std::sync::mpsc; +use std::time::Duration; + +fn setup_temp_git_repo() -> (PathBuf, tempfile::TempDir) { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().canonicalize().unwrap(); + + // Create a git repo like the bun test does + std::process::Command::new("git") + .args(["init", "-b", "main"]) + .current_dir(&dir) + .output() + .unwrap(); + + fs::write(dir.join("hello.txt"), "hello\n").unwrap(); + fs::create_dir_all(dir.join("src")).unwrap(); + fs::write(dir.join("src/main.rs"), "fn main() {}\n").unwrap(); + + std::process::Command::new("git") + .args(["add", "-A"]) + .current_dir(&dir) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["commit", "-m", "init"]) + .current_dir(&dir) + .output() + .unwrap(); + + (dir, tmp) +} + +/// Raw notify watcher: single NonRecursive watch on a directory. +/// Create a file, delete it, check if Remove event is delivered. +#[test] +fn raw_notify_nonrecursive_detects_deletion() { + let (dir, _tmp) = setup_temp_git_repo(); + let (tx, rx) = mpsc::channel(); + + let config = Config::default() + .with_follow_symlinks(false) + .with_event_kinds(EventKindMask::CORE); + + let mut watcher = RecommendedWatcher::new( + move |res: notify::Result| { + if let Ok(ev) = res { + let _ = tx.send(ev); + } + }, + config, + ) + .unwrap(); + + // Watch ONLY the root dir NonRecursively (like fff does) + watcher + .watch(dir.as_path(), RecursiveMode::NonRecursive) + .unwrap(); + + // Let the watcher stabilize + std::thread::sleep(Duration::from_millis(500)); + + // Drain any startup events + while rx.try_recv().is_ok() {} + + // Create a file + let file_path = dir.join("testfile.txt"); + fs::write(&file_path, "content\n").unwrap(); + + // Wait for create event + let mut got_create = false; + let deadline = std::time::Instant::now() + Duration::from_secs(5); + while std::time::Instant::now() < deadline { + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(ev) => { + eprintln!(" [create phase] event: {:?} paths={:?}", ev.kind, ev.paths); + if matches!(ev.kind, EventKind::Create(_)) && ev.paths.contains(&file_path) { + got_create = true; + break; + } + } + Err(mpsc::RecvTimeoutError::Timeout) => continue, + Err(_) => break, + } + } + assert!(got_create, "Expected Create event for testfile.txt"); + + // Drain remaining events from the create + std::thread::sleep(Duration::from_millis(300)); + while rx.try_recv().is_ok() {} + + // Delete the file + fs::remove_file(&file_path).unwrap(); + eprintln!(" File deleted: {}", file_path.display()); + + // Wait for any event related to the deletion + let mut got_removal_event = false; + let deadline = std::time::Instant::now() + Duration::from_secs(5); + while std::time::Instant::now() < deadline { + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(ev) => { + eprintln!(" [delete phase] event: {:?} paths={:?}", ev.kind, ev.paths); + if ev.paths.contains(&file_path) { + got_removal_event = true; + break; + } + } + Err(mpsc::RecvTimeoutError::Timeout) => continue, + Err(_) => break, + } + } + + assert!( + got_removal_event, + "Expected some event for deleted testfile.txt but got none within 5s" + ); +} + +/// Same test but with MULTIPLE NonRecursive watches (base + src + .git) +/// to match what fff actually does. Each watch() call stops/restarts the FSEvents stream. +#[test] +fn raw_notify_multi_nonrecursive_detects_deletion() { + let (dir, _tmp) = setup_temp_git_repo(); + let (tx, rx) = mpsc::channel(); + + let config = Config::default() + .with_follow_symlinks(false) + .with_event_kinds(EventKindMask::CORE); + + let mut watcher = RecommendedWatcher::new( + move |res: notify::Result| { + if let Ok(ev) = res { + let _ = tx.send(ev); + } + }, + config, + ) + .unwrap(); + + // Watch multiple directories NonRecursively — EACH call restarts the FSEvents stream + watcher + .watch(dir.as_path(), RecursiveMode::NonRecursive) + .unwrap(); + watcher + .watch(&dir.join("src"), RecursiveMode::NonRecursive) + .unwrap(); + watcher + .watch(&dir.join(".git"), RecursiveMode::NonRecursive) + .unwrap(); + + // Let the watcher stabilize + std::thread::sleep(Duration::from_millis(500)); + while rx.try_recv().is_ok() {} + + // Create a file in root dir + let file_path = dir.join("testfile.txt"); + fs::write(&file_path, "content\n").unwrap(); + + // Wait for create event + let mut got_create = false; + let deadline = std::time::Instant::now() + Duration::from_secs(5); + while std::time::Instant::now() < deadline { + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(ev) => { + eprintln!(" [multi-create] event: {:?} paths={:?}", ev.kind, ev.paths); + if matches!(ev.kind, EventKind::Create(_)) && ev.paths.contains(&file_path) { + got_create = true; + break; + } + } + Err(mpsc::RecvTimeoutError::Timeout) => continue, + Err(_) => break, + } + } + assert!( + got_create, + "Expected Create event for testfile.txt with multi-watch" + ); + + // Drain + std::thread::sleep(Duration::from_millis(300)); + while rx.try_recv().is_ok() {} + + // Delete the file + fs::remove_file(&file_path).unwrap(); + eprintln!(" File deleted: {}", file_path.display()); + + // Wait for any event related to the deletion + let mut got_removal_event = false; + let deadline = std::time::Instant::now() + Duration::from_secs(5); + while std::time::Instant::now() < deadline { + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(ev) => { + eprintln!(" [multi-delete] event: {:?} paths={:?}", ev.kind, ev.paths); + if ev.paths.contains(&file_path) { + got_removal_event = true; + break; + } + } + Err(mpsc::RecvTimeoutError::Timeout) => continue, + Err(_) => break, + } + } + + assert!( + got_removal_event, + "Expected some event for deleted testfile.txt with multi-watch but got none within 5s" + ); +} + +/// Test with debouncer (matching exactly what fff uses) +#[test] +fn debounced_nonrecursive_detects_deletion() { + use notify_debouncer_full::{DebounceEventResult, NoCache, new_debouncer_opt}; + + let (dir, _tmp) = setup_temp_git_repo(); + let (tx, rx) = mpsc::channel(); + + let config = Config::default() + .with_follow_symlinks(false) + .with_event_kinds(EventKindMask::CORE); + + let mut debouncer: notify_debouncer_full::Debouncer = + new_debouncer_opt( + Duration::from_millis(250), + Some(Duration::from_millis(125)), + move |result: DebounceEventResult| { + if let Ok(events) = result { + for ev in events { + eprintln!( + " [debounced-cb] kind={:?} paths={:?}", + ev.event.kind, ev.event.paths + ); + let _ = tx.send(ev); + } + } + }, + NoCache::new(), + config, + ) + .unwrap(); + + // Watch like fff does + debouncer + .watch(dir.as_path(), RecursiveMode::NonRecursive) + .unwrap(); + debouncer + .watch(&dir.join("src"), RecursiveMode::NonRecursive) + .unwrap(); + debouncer + .watch(&dir.join(".git"), RecursiveMode::NonRecursive) + .unwrap(); + + // Longer stabilization — each watch() restarts the FSEvents stream + std::thread::sleep(Duration::from_secs(1)); + while rx.try_recv().is_ok() {} + + // Create file + let file_path = dir.join("testfile.txt"); + fs::write(&file_path, "content\n").unwrap(); + + let mut got_create = false; + let deadline = std::time::Instant::now() + Duration::from_secs(5); + while std::time::Instant::now() < deadline { + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(ev) => { + if ev.event.paths.contains(&file_path) { + got_create = true; + break; + } + } + Err(mpsc::RecvTimeoutError::Timeout) => continue, + Err(_) => break, + } + } + assert!(got_create, "Expected Create event via debouncer"); + + // Wait for debounce to fully flush + std::thread::sleep(Duration::from_millis(500)); + while rx.try_recv().is_ok() {} + + // Delete + fs::remove_file(&file_path).unwrap(); + eprintln!(" File deleted: {}", file_path.display()); + + // Wait for ANY event for this path + let mut got_event = false; + let mut event_kind = String::new(); + let deadline = std::time::Instant::now() + Duration::from_secs(5); + while std::time::Instant::now() < deadline { + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(ev) => { + if ev.event.paths.contains(&file_path) { + event_kind = format!("{:?}", ev.event.kind); + got_event = true; + break; + } + } + Err(mpsc::RecvTimeoutError::Timeout) => continue, + Err(_) => break, + } + } + + assert!( + got_event, + "Expected some event for deleted testfile.txt via debouncer but got none within 5s" + ); + eprintln!(" Got event kind: {}", event_kind); +} diff --git a/crates/fff-core/tests/fuzz_file_operations.rs b/crates/fff-core/tests/fuzz_file_operations.rs new file mode 100644 index 0000000..8468493 --- /dev/null +++ b/crates/fff-core/tests/fuzz_file_operations.rs @@ -0,0 +1,854 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tempfile::TempDir; + +use rand::rngs::SmallRng; +use rand::{RngCore, SeedableRng}; + +use fff_search::file_picker::{FFFMode, FilePicker, FuzzySearchOptions}; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{ + FilePickerOptions, PaginationArgs, QueryParser, SharedFilePicker, SharedFrecency, +}; + +const DOMAINS: &[&str] = &[ + r#" +use std::net::{TcpStream, SocketAddr}; +fn establish_connection(addr: SocketAddr) -> Result { + let stream = TcpStream::connect(addr)?; + stream.set_nodelay(true)?; + Ok(stream) +} +fn parse_http_header(raw: &[u8]) -> Option<(&str, &str)> { + let line = std::str::from_utf8(raw).ok()?; + let (key, val) = line.split_once(':')?; + Some((key.trim(), val.trim())) +} +"#, + r#" +use sqlx::{PgPool, Row}; +async fn query_users(pool: &PgPool, limit: i64) -> Vec { + sqlx::query("SELECT name FROM users ORDER BY created_at DESC LIMIT $1") + .bind(limit) + .fetch_all(pool) + .await + .unwrap() + .iter() + .map(|row| row.get("name")) + .collect() +} +async fn insert_record(pool: &PgPool, name: &str) -> i64 { + sqlx::query_scalar("INSERT INTO records (name) VALUES ($1) RETURNING id") + .bind(name) + .fetch_one(pool) + .await + .unwrap() +} +"#, + r#" +fn verify_jwt_token(token: &str, secret: &[u8]) -> Result { + let parts: Vec<&str> = token.splitn(3, '.').collect(); + if parts.len() != 3 { return Err(AuthError::MalformedToken); } + let payload = base64_decode(parts[1])?; + let signature = hmac_sha256(secret, &format!("{}.{}", parts[0], parts[1])); + if signature != base64_decode(parts[2])? { return Err(AuthError::InvalidSignature); } + serde_json::from_slice(&payload).map_err(AuthError::Deserialize) +} +fn hash_password(password: &str, salt: &[u8]) -> Vec { + argon2::hash_encoded(password.as_bytes(), salt, &argon2::Config::default()) + .unwrap().into_bytes() +} +"#, + r#" +struct Renderer { framebuffer: Vec, width: usize, height: usize } +impl Renderer { + fn clear(&mut self, color: u32) { self.framebuffer.fill(color); } + fn draw_pixel(&mut self, x: usize, y: usize, color: u32) { + if x < self.width && y < self.height { + self.framebuffer[y * self.width + x] = color; + } + } + fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: u32) { + let dx = (x1 - x0).abs(); let dy = -(y1 - y0).abs(); + let mut err = dx + dy; + let (mut cx, mut cy) = (x0, y0); + loop { + self.draw_pixel(cx as usize, cy as usize, color); + if cx == x1 && cy == y1 { break; } + let e2 = 2 * err; + if e2 >= dy { err += dy; cx += if x0 < x1 { 1 } else { -1 }; } + if e2 <= dx { err += dx; cy += if y0 < y1 { 1 } else { -1 }; } + } + } +} +"#, + r#" +use serde::{Serialize, Deserialize}; +#[derive(Serialize, Deserialize)] +struct ConfigFile { log_level: String, max_retries: u32, timeout_ms: u64 } +fn load_config(path: &std::path::Path) -> Result> { + let contents = std::fs::read_to_string(path)?; + let config: ConfigFile = toml::from_str(&contents)?; + Ok(config) +} +fn merge_configs(base: ConfigFile, overlay: ConfigFile) -> ConfigFile { + ConfigFile { + log_level: if overlay.log_level.is_empty() { base.log_level } else { overlay.log_level }, + max_retries: overlay.max_retries.max(base.max_retries), + timeout_ms: overlay.timeout_ms.max(base.timeout_ms), + } +} +"#, + r#" +struct PhysicsBody { position: [f64; 3], velocity: [f64; 3], mass: f64 } +fn apply_gravity(bodies: &mut [PhysicsBody], dt: f64) { + let gravity_constant = 6.674e-11; + let len = bodies.len(); + let mut forces = vec![[0.0f64; 3]; len]; + for i in 0..len { + for j in (i+1)..len { + let dx = bodies[j].position[0] - bodies[i].position[0]; + let dy = bodies[j].position[1] - bodies[i].position[1]; + let dz = bodies[j].position[2] - bodies[i].position[2]; + let dist_sq = dx*dx + dy*dy + dz*dz; + let force_mag = gravity_constant * bodies[i].mass * bodies[j].mass / dist_sq; + let dist = dist_sq.sqrt(); + for k in 0..3 { + let f = force_mag * [dx, dy, dz][k] / dist; + forces[i][k] += f; forces[j][k] -= f; + } + } + } + for (body, force) in bodies.iter_mut().zip(forces.iter()) { + for k in 0..3 { + body.velocity[k] += force[k] / body.mass * dt; + body.position[k] += body.velocity[k] * dt; + } + } +} +"#, + r#" +use std::collections::BTreeMap; +struct CacheEntry { value: V, frequency: u64, last_access: u64 } +struct LFUCache { map: BTreeMap>, capacity: usize, clock: u64 } +impl LFUCache { + fn new(capacity: usize) -> Self { Self { map: BTreeMap::new(), capacity, clock: 0 } } + fn get(&mut self, key: &K) -> Option<&V> { + self.clock += 1; + let entry = self.map.get_mut(key)?; + entry.frequency += 1; + entry.last_access = self.clock; + Some(&entry.value) + } + fn insert(&mut self, key: K, value: V) { + self.clock += 1; + if self.map.len() >= self.capacity { self.evict(); } + self.map.insert(key, CacheEntry { value, frequency: 1, last_access: self.clock }); + } + fn evict(&mut self) { + if let Some(victim) = self.map.keys().min_by_key(|k| { + let e = &self.map[*k]; (e.frequency, e.last_access) + }).cloned() { self.map.remove(&victim); } + } +} +"#, + r#" +fn tokenize_expression(input: &str) -> Vec { + let mut tokens = Vec::new(); + let mut chars = input.chars().peekable(); + while let Some(&ch) = chars.peek() { + match ch { + '0'..='9' => { + let mut num = String::new(); + while let Some(&d) = chars.peek() { + if d.is_ascii_digit() || d == '.' { num.push(d); chars.next(); } + else { break; } + } + tokens.push(Token::Number(num.parse().unwrap())); + } + '+' => { tokens.push(Token::Plus); chars.next(); } + '-' => { tokens.push(Token::Minus); chars.next(); } + '*' => { tokens.push(Token::Star); chars.next(); } + '/' => { tokens.push(Token::Slash); chars.next(); } + '(' => { tokens.push(Token::LParen); chars.next(); } + ')' => { tokens.push(Token::RParen); chars.next(); } + _ if ch.is_whitespace() => { chars.next(); } + _ => { chars.next(); } + } + } + tokens +} +"#, + r#" +use std::sync::mpsc; +use std::thread; +fn parallel_map( + items: Vec, num_threads: usize, f: fn(T) -> R +) -> Vec { + let chunk_size = (items.len() + num_threads - 1) / num_threads; + let (tx, rx) = mpsc::channel(); + let mut handles = Vec::new(); + for (chunk_idx, chunk) in items.into_iter().collect::>() + .chunks(chunk_size).enumerate() + { + let tx = tx.clone(); + let chunk = chunk.to_vec(); + handles.push(thread::spawn(move || { + for (i, item) in chunk.into_iter().enumerate() { + tx.send((chunk_idx * chunk_size + i, f(item))).unwrap(); + } + })); + } + drop(tx); + let mut results: Vec> = vec![None; handles.len() * chunk_size]; + for (idx, result) in rx { if idx < results.len() { results[idx] = Some(result); } } + for h in handles { h.join().unwrap(); } + results.into_iter().flatten().collect() +} +"#, + r#" +struct Compressor { window: Vec, window_size: usize } +impl Compressor { + fn new(window_size: usize) -> Self { + Self { window: Vec::with_capacity(window_size), window_size } + } + fn find_longest_match(&self, data: &[u8], pos: usize) -> (usize, usize) { + let mut best_offset = 0; let mut best_length = 0; + let start = pos.saturating_sub(self.window_size); + for offset in start..pos { + let mut length = 0; + while pos + length < data.len() + && data[offset + length] == data[pos + length] + && length < 258 + { length += 1; } + if length > best_length { best_offset = pos - offset; best_length = length; } + } + (best_offset, best_length) + } + fn compress(&mut self, data: &[u8]) -> Vec { + let mut output = Vec::new(); + let mut pos = 0; + while pos < data.len() { + let (offset, length) = self.find_longest_match(data, pos); + if length >= 3 { + output.push(1); output.extend_from_slice(&(offset as u16).to_le_bytes()); + output.push(length as u8); pos += length; + } else { output.push(0); output.push(data[pos]); pos += 1; } + } + output + } +} +"#, +]; + +struct FileState { + name: String, + token: String, + #[allow(dead_code)] + is_base: bool, + /// Epoch second when this file was last written (used to detect same-second + /// re-edits that wouldn't bump mtime and thus wouldn't invalidate the mmap). + last_write_sec: u64, +} + +#[test] +fn fuzz_file_operations_stress() { + const SEED: u64 = 0xDEAD_BEEF_CAFE_1234; + const INITIAL_FILE_COUNT: usize = 40; + const NUM_ROUNDS: usize = 20; + + let mut rng = SmallRng::seed_from_u64(SEED); + + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + // Timing accumulators. + let mut t_sleep = Duration::ZERO; + let mut t_git = Duration::ZERO; + let mut t_bigram_wait = Duration::ZERO; + let mut t_grep_plain = Duration::ZERO; + let mut t_grep_regex = Duration::ZERO; + let mut t_fuzzy = Duration::ZERO; + let mut t_dead_check = Duration::ZERO; + let grep_plain_calls = std::sync::atomic::AtomicUsize::new(0); + let grep_regex_calls = std::sync::atomic::AtomicUsize::new(0); + let fuzzy_calls = std::sync::atomic::AtomicUsize::new(0); + let dead_calls = std::sync::atomic::AtomicUsize::new(0); + let test_start = std::time::Instant::now(); + + let mut live_files: Vec = Vec::with_capacity(INITIAL_FILE_COUNT + NUM_ROUNDS); + let mut dead_tokens: Vec = Vec::new(); + let mut next_file_id: usize = 0; + + for i in 0..INITIAL_FILE_COUNT { + let name = format!("seed_{i:04}.rs"); + let token = format!("FUZZ_SEED_{i:04}"); + write_diverse_file(base, &name, &token, i); + live_files.push(FileState { + name, + token, + is_base: true, + last_write_sec: 0, // set before index build, doesn't matter + }); + next_file_id += 1; + } + + let t0 = std::time::Instant::now(); + git_init_and_commit(base); + t_git += t0.elapsed(); + + let shared_picker = SharedFilePicker::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + SharedFrecency::noop(), + FilePickerOptions { + watch: false, // we do not need the backgrodun monitor + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + let t0 = std::time::Instant::now(); + wait_for_bigram(&shared_picker); + t_bigram_wait += t0.elapsed(); + + // Sanity: all initial tokens findable via plain grep. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + for fs in &live_files { + assert!( + grep_plain_count(picker, &fs.token) >= 1, + "initial sanity: plain grep should find token {} in {}", + fs.token, + fs.name + ); + } + } + + // Sleep so mtime advances past the scan snapshot timestamp. + let t0 = std::time::Instant::now(); + std::thread::sleep(Duration::from_millis(1100)); + t_sleep += t0.elapsed(); + + let mut op_counter: usize = 0; + + for round in 0..NUM_ROUNDS { + let roll: u32 = rng.next_u32() % 100; + + if roll < 40 && !live_files.is_empty() { + // ── EDIT existing file (40%) ── + let idx = rng.next_u32() as usize % live_files.len(); + + // on_create_or_modify uses mtime (seconds granularity) to decide + // whether to invalidate the mmap cache. If we re-edit a file in + // the same second it was last written, the mtime won't change and + // the stale cached content will be returned. Sleep to advance mtime. + let now_sec = epoch_secs(); + if live_files[idx].last_write_sec >= now_sec { + let t0 = std::time::Instant::now(); + std::thread::sleep(Duration::from_millis(1100)); + t_sleep += t0.elapsed(); + } + + let old_token = live_files[idx].token.clone(); + let new_token = format!("FUZZ_{round:02}_{op_counter:04}"); + let name = &live_files[idx].name; + let domain_idx = rng.next_u32() as usize % DOMAINS.len(); + write_diverse_file_with_domain(base, name, &new_token, domain_idx); + + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker.handle_create_or_modify(base.join(name)).is_some(), + "round {round}: on_create_or_modify({name}) should succeed for edit" + ); + } + + dead_tokens.push(old_token); + live_files[idx].token = new_token; + live_files[idx].last_write_sec = epoch_secs(); + op_counter += 1; + } else if roll < 60 { + // ── CREATE new file (20%) ── + let name = format!("created_{next_file_id:04}.rs"); + let token = format!("FUZZ_{round:02}_{op_counter:04}"); + let domain_idx = rng.next_u32() as usize % DOMAINS.len(); + write_diverse_file_with_domain(base, &name, &token, domain_idx); + + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker.handle_create_or_modify(base.join(&name)).is_some(), + "round {round}: on_create_or_modify({name}) should succeed for create" + ); + } + + live_files.push(FileState { + name, + token, + is_base: false, + last_write_sec: epoch_secs(), + }); + next_file_id += 1; + op_counter += 1; + } else if roll < 75 && !live_files.is_empty() { + // ── DELETE existing file (15%) ── + let idx = rng.next_u32() as usize % live_files.len(); + let removed = live_files.swap_remove(idx); + let path = base.join(&removed.name); + fs::remove_file(&path).unwrap(); + + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker.remove_file_by_path(&path), + "round {round}: remove_file_by_path({}) should succeed", + removed.name + ); + } + + dead_tokens.push(removed.token); + op_counter += 1; + } else if roll < 85 && !live_files.is_empty() { + // ── RENAME file (10%) ── + let idx = rng.next_u32() as usize % live_files.len(); + let old_name = live_files[idx].name.clone(); + let old_path = base.join(&old_name); + let content = fs::read_to_string(&old_path).unwrap(); + + // Remove old file from disk + picker. + fs::remove_file(&old_path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&old_path); + } + + // Create new file with same content but different name. + let new_name = format!("renamed_{next_file_id:04}.rs"); + fs::write(base.join(&new_name), &content).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker + .handle_create_or_modify(base.join(&new_name)) + .is_some(), + "round {round}: on_create_or_modify({new_name}) should succeed for rename" + ); + } + + live_files[idx].name = new_name; + live_files[idx].is_base = false; + live_files[idx].last_write_sec = epoch_secs(); + next_file_id += 1; + op_counter += 1; + } + // else: no-op / read-only (15%) — just run verification below. + + // ── VERIFY after every round ── + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + for fs in &live_files { + // Plain text grep: every live token must be found. + let t0 = std::time::Instant::now(); + let plain_count = grep_plain_count(picker, &fs.token); + t_grep_plain += t0.elapsed(); + grep_plain_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + assert!( + plain_count >= 1, + "round {round}: plain grep should find live token {} in {} (got {plain_count})", + fs.token, + fs.name + ); + + // Regex grep: search with `{first5}.*{last5}` pattern. + let regex_pattern = build_regex_pattern(&fs.token); + let t0 = std::time::Instant::now(); + let regex_count = grep_regex_count(picker, ®ex_pattern); + t_grep_regex += t0.elapsed(); + grep_regex_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + assert!( + regex_count >= 1, + "round {round}: regex grep '{}' should find live token {} in {} (got {regex_count})", + regex_pattern, + fs.token, + fs.name + ); + + // Fuzzy file search: every live file must be findable by name. + let stem = extract_stem(&fs.name); + let t0 = std::time::Instant::now(); + let fuzzy_results = fuzzy_search_paths(picker, &stem); + t_fuzzy += t0.elapsed(); + fuzzy_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + assert!( + fuzzy_results.iter().any(|p| p.contains(&fs.name)), + "round {round}: fuzzy search '{}' should find file {} in results: {:?}", + stem, + fs.name, + fuzzy_results + ); + } + + // Dead tokens must return 0 grep results. + for dead in &dead_tokens { + let t0 = std::time::Instant::now(); + let count = grep_plain_count(picker, dead); + t_dead_check += t0.elapsed(); + dead_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + assert_eq!( + count, 0, + "round {round}: dead token {dead} should NOT be findable (got {count})" + ); + } + } + } + + let total = test_start.elapsed(); + let t_overhead = t_sleep + t_bigram_wait + t_git; + let t_search = t_grep_plain + t_grep_regex + t_fuzzy + t_dead_check; + let t_mutations = total.saturating_sub(t_overhead + t_search); + let n_grep_plain = grep_plain_calls.load(std::sync::atomic::Ordering::Relaxed); + let n_grep_regex = grep_regex_calls.load(std::sync::atomic::Ordering::Relaxed); + let n_fuzzy = fuzzy_calls.load(std::sync::atomic::Ordering::Relaxed); + let n_dead = dead_calls.load(std::sync::atomic::Ordering::Relaxed); + eprintln!("\n╔══════════════════════════════════════════════════════╗"); + eprintln!("║ Fuzz Test Performance Breakdown ║"); + eprintln!("╠══════════════════════════════════════════════════════╣"); + eprintln!( + "║ Total wall time: {:>8.1}ms ║", + total.as_secs_f64() * 1000.0 + ); + eprintln!("║ ── Overhead ─────────────────────────────────────── ║"); + eprintln!( + "║ Sleep (mtime waits): {:>8.1}ms ║", + t_sleep.as_secs_f64() * 1000.0 + ); + eprintln!( + "║ Git init+commit: {:>8.1}ms ║", + t_git.as_secs_f64() * 1000.0 + ); + eprintln!( + "║ Bigram index build+scan: {:>8.1}ms ║", + t_bigram_wait.as_secs_f64() * 1000.0 + ); + eprintln!( + "║ ── Search ({:>3} live files, {:>3} dead tokens) ────── ║", + live_files.len(), + dead_tokens.len() + ); + eprintln!( + "║ Plain grep: {:>4} calls {:>8.1}ms ({:>6.1}µs/call) ║", + n_grep_plain, + t_grep_plain.as_secs_f64() * 1000.0, + t_grep_plain.as_secs_f64() * 1_000_000.0 / n_grep_plain.max(1) as f64 + ); + eprintln!( + "║ Regex grep: {:>4} calls {:>8.1}ms ({:>6.1}µs/call) ║", + n_grep_regex, + t_grep_regex.as_secs_f64() * 1000.0, + t_grep_regex.as_secs_f64() * 1_000_000.0 / n_grep_regex.max(1) as f64 + ); + eprintln!( + "║ Fuzzy find: {:>4} calls {:>8.1}ms ({:>6.1}µs/call) ║", + n_fuzzy, + t_fuzzy.as_secs_f64() * 1000.0, + t_fuzzy.as_secs_f64() * 1_000_000.0 / n_fuzzy.max(1) as f64 + ); + eprintln!( + "║ Dead checks: {:>4} calls {:>8.1}ms ({:>6.1}µs/call) ║", + n_dead, + t_dead_check.as_secs_f64() * 1000.0, + t_dead_check.as_secs_f64() * 1_000_000.0 / n_dead.max(1) as f64 + ); + eprintln!("║ ── Other ────────────────────────────────────────── ║"); + eprintln!( + "║ Mutations + FS I/O: {:>8.1}ms ║", + t_mutations.as_secs_f64() * 1000.0 + ); + eprintln!("╚══════════════════════════════════════════════════════╝"); +} + +fn write_diverse_file(dir: &Path, name: &str, token: &str, index: usize) { + let domain_idx = index % DOMAINS.len(); + write_diverse_file_with_domain(dir, name, token, domain_idx); +} + +fn write_diverse_file_with_domain(dir: &Path, name: &str, token: &str, domain_idx: usize) { + let domain = DOMAINS[domain_idx % DOMAINS.len()]; + let content = format!( + "// File: {name}\n\ + // Domain content for bigram diversity\n\ + {domain}\n\ + // === Unique searchable token below ===\n\ + const MARKER: &str = \"{token}\";\n\ + fn marker_function_{token}() {{ println!(\"{token}\"); }}\n" + ); + + if let Some(parent) = PathBuf::from(name).parent() { + if !parent.as_os_str().is_empty() { + fs::create_dir_all(dir.join(parent)).unwrap(); + } + } + fs::write(dir.join(name), content).unwrap(); +} + +// ═══════════════════════════════════════════════════════════════════════ +// Search helpers +// ═══════════════════════════════════════════════════════════════════════ + +fn grep_plain_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 500, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +fn grep_regex_opts() -> GrepSearchOptions { + GrepSearchOptions { + mode: GrepMode::Regex, + ..grep_plain_opts() + } +} + +fn grep_plain_count(picker: &FilePicker, query: &str) -> usize { + let parsed = parse_grep_query(query); + picker.grep(&parsed, &grep_plain_opts()).matches.len() +} + +fn grep_regex_count(picker: &FilePicker, regex_query: &str) -> usize { + let parsed = parse_grep_query(regex_query); + picker.grep(&parsed, &grep_regex_opts()).matches.len() +} + +/// Build a regex pattern from a token: `{first5}.*{last5}`. +/// For tokens shorter than 10 chars, just use the literal (escaped). +fn build_regex_pattern(token: &str) -> String { + if token.len() >= 10 { + let first5 = &token[..5]; + let last5 = &token[token.len() - 5..]; + format!("{}.*{}", regex_escape(first5), regex_escape(last5)) + } else { + regex_escape(token) + } +} + +/// Escape regex metacharacters in a string. +fn regex_escape(s: &str) -> String { + let mut escaped = String::with_capacity(s.len() + 4); + for ch in s.chars() { + match ch { + '.' | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '\\' | '^' | '$' | '|' => { + escaped.push('\\'); + escaped.push(ch); + } + _ => escaped.push(ch), + } + } + escaped +} + +/// Extract a fuzzy-searchable stem from a filename. +/// Strips the extension and any leading path components, keeping the bare name. +fn extract_stem(name: &str) -> String { + let p = PathBuf::from(name); + p.file_stem() + .unwrap_or_default() + .to_string_lossy() + .to_string() +} + +fn fuzzy_search_paths(picker: &FilePicker, query: &str) -> Vec { + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 200, + }, + ..Default::default() + }, + ); + result + .items + .iter() + .map(|f| f.relative_path(picker)) + .collect() +} + +fn wait_for_bigram(shared_picker: &SharedFilePicker) { + let deadline = std::time::Instant::now() + Duration::from_secs(10); + loop { + std::thread::sleep(Duration::from_millis(50)); + let ready = shared_picker + .read() + .ok() + .map(|guard| { + guard + .as_ref() + .map_or(false, |p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } +} + +fn git_run(dir: &Path, args: &[&str]) { + let out = Command::new("git") + .args(args) + .current_dir(dir) + .env("GIT_AUTHOR_NAME", "test") + .env("GIT_AUTHOR_EMAIL", "test@test.com") + .env("GIT_COMMITTER_NAME", "test") + .env("GIT_COMMITTER_EMAIL", "test@test.com") + .output() + .unwrap_or_else(|e| panic!("git {:?} failed: {}", args, e)); + assert!( + out.status.success(), + "git {:?} failed: {}", + args, + String::from_utf8_lossy(&out.stderr) + ); +} + +fn epoch_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +} + +fn git_init_and_commit(dir: &Path) { + git_run(dir, &["init"]); + git_run(dir, &["add", "-A"]); + git_run(dir, &["commit", "-m", "initial"]); +} + +/// Proves that dropping the picker while post-scan (warmup + bigram build) +/// is actively iterating raw pointers does NOT segfault. The Drop impl +/// sets `cancelled`, waits for `post_scan_indexing_active` to clear, and +/// only then frees the backing Vec. +/// +/// Runs 10 iterations to exercise the race window reliably. +#[test] +fn drop_during_post_scan_does_not_crash() { + let mut caught_active = 0u32; + + for round in 0..10 { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + // Create enough files so bigram build takes measurable time + for i in 0..2000 { + let dir = base.join(format!("d_{:02}", i % 20)); + fs::create_dir_all(&dir).unwrap(); + let content = format!( + "fn func_{i}() {{ let x = {i}; println!(\"{{x}}\"); }}\n\ + const T_{i}: &str = \"TOKEN_{i}\";\n" + ); + fs::write(dir.join(format!("f_{i:04}.rs")), content).unwrap(); + } + + git_init_and_commit(base); + + let shared_picker = SharedFilePicker::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + SharedFrecency::noop(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + watch: false, + mode: FFFMode::Neovim, + ..Default::default() + }, + ) + .unwrap(); + + // Wait for scan but NOT for bigram — drop while post-scan is active + shared_picker.wait_for_scan(Duration::from_secs(10)); + + // Poll until post_scan_indexing_active is true (bigram started) + let deadline = std::time::Instant::now() + Duration::from_secs(5); + let mut was_active = false; + loop { + if let Ok(guard) = shared_picker.read() { + if let Some(picker) = guard.as_ref() { + if picker.is_post_scan_active() { + was_active = true; + break; + } + } + } + if std::time::Instant::now() > deadline { + break; + } + std::thread::sleep(Duration::from_millis(1)); + } + + if was_active { + caught_active += 1; + } + + // Drop the picker while post_scan_indexing_active is set. + // Take it out of the shared handle first, then drop outside the lock — + // Drop spins until post-scan finishes, which needs the write lock for + // bigram install, so we can't hold it during Drop. + let old_picker = shared_picker.write().unwrap().take(); + drop(old_picker); // Drop fires here — spins until post-scan exits + + assert!( + shared_picker.read().unwrap().is_none(), + "round {round}: picker should be None after drop" + ); + } + + // The primary invariant — dropping while post-scan may be active must not + // crash — is exercised every round regardless. Catching the active window + // is timing-dependent: with a fast walker/scan the post-scan phase can + // complete before the poll observes it, especially on loaded CI runners. + // So we only warn (not fail) if no round observed it. + if caught_active == 0 { + eprintln!( + "warning: never observed post_scan_indexing_active=true; \ + drop-safety was still exercised in all rounds ({caught_active}/10)" + ); + } + eprintln!("Caught post-scan active in {caught_active}/10 rounds"); +} diff --git a/crates/fff-core/tests/fuzz_git_watcher_stress.proptest-regressions b/crates/fff-core/tests/fuzz_git_watcher_stress.proptest-regressions new file mode 100644 index 0000000..b092012 --- /dev/null +++ b/crates/fff-core/tests/fuzz_git_watcher_stress.proptest-regressions @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 2c9d1ea2efbf6161f84b69598e884dbf1bde6039c70625adde0374817e20e2ea +cc 1ac0f8f02b160dce13ca3f3630266abd24bd32b4e36d72a6a0a5365139ded3a8 diff --git a/crates/fff-core/tests/fuzz_git_watcher_stress.rs b/crates/fff-core/tests/fuzz_git_watcher_stress.rs new file mode 100644 index 0000000..dda7368 --- /dev/null +++ b/crates/fff-core/tests/fuzz_git_watcher_stress.rs @@ -0,0 +1,1671 @@ +//! Vibe coded stress test: randomized file + git operations driven against the *real* +//! `BackgroundWatcher`, asserting the git-status invariant after every mutation. +//! +//! ## What this test is trying to catch +//! +//! The background watcher is supposed to keep every indexed file's +//! `git_status` in sync with the actual repository state at all times. +//! There are two code paths that can drift: +//! +//! 1. **Per-file updates** — when regular files are created / modified the +//! watcher queries `git_status_for_paths(&[changed_file])`. If the git +//! state changes for files that weren't in the batch (rename detection, +//! submodule paths, index modifications applied by a sibling process), +//! those other files keep a stale `git_status` until the next full +//! rescan is triggered. +//! +//! 2. **Full rescans** — triggered by changes under `.git/` (index, HEAD, +//! MERGE_HEAD, etc.) and by `.gitignore` edits. A missed event here is +//! the most common failure mode: the watcher coalesced or dropped the +//! `.git/index` notification and the picker never learns that e.g. a +//! `git commit` cleared every `WT_MODIFIED`. +//! +//! ## How the test works +//! +//! * Spin up a real `FilePicker` with `watch: true` on a fresh temp repo. +//! * Use `proptest` to generate a sequence of 20–40 randomized ops (heavy on +//! git mutations: add / commit / reset / stash / gitignore edits). +//! * After **every** op, poll until the picker's per-file `git_status` agrees +//! with `git2::Repository::statuses()` verbatim, or bail with a rich diff. +//! * If there is ever a divergence that doesn't resolve within +//! `CONVERGE_TIMEOUT`, the test fails with the list of `(path, truth, +//! picker)` mismatches. +//! +//! The shape of the scenario is shrinkable: on failure proptest will shrink +//! the `Vec` so the reported diff corresponds to the smallest +//! surviving sequence. +//! +//! ## Runtime +//! +//! Each scenario does a real filesystem scan + watcher setup (~0.5–1 s) and +//! then runs 20–40 ops each of which waits for event propagation +//! (~100–500 ms on macOS FSEvents). With `cases = 2` the test takes +//! ~30–45 s on a typical dev machine — too slow to run as part of the +//! default `cargo test`, so it's gated behind the `stress` cfg. +//! +//! Run it explicitly with: +//! ```sh +//! RUSTFLAGS="--cfg stress" cargo test -p fff-search --test fuzz_git_watcher_stress -- --nocapture +//! ``` +//! +//! Or increase coverage via env: +//! ```sh +//! FFF_STRESS_CASES=8 FFF_STRESS_MAX_OPS=60 \ +//! RUSTFLAGS="--cfg stress" cargo test -p fff-search --test fuzz_git_watcher_stress -- --nocapture +//! ``` + +#![cfg(stress)] +use fff_search::file_picker::{FFFMode, FilePicker}; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{ + FilePickerOptions, FuzzySearchOptions, PaginationArgs, QueryParser, SharedFilePicker, + SharedFrecency, +}; +use git2::{Repository, Status, StatusOptions}; +use proptest::prelude::*; +use proptest::strategy::ValueTree; +use proptest::test_runner::{ + Config as ProptestConfig, FileFailurePersistence, RngAlgorithm, TestRng, TestRunner, +}; +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{Duration, Instant}; +use tempfile::TempDir; + +/// Upper bound on how long we wait for the picker to converge after any op. +/// The watcher debounce is 50 ms; worst-case FSEvents propagation + queued +/// full-rescan can easily reach several seconds. 15 s is very generous. +const CONVERGE_TIMEOUT: Duration = Duration::from_secs(15); + +/// Poll interval while waiting for convergence. +const CONVERGE_POLL: Duration = Duration::from_millis(50); + +/// Small pause between back-to-back ops to simulate real user behavior +const PER_OP_SETTLE: Duration = Duration::from_millis(10); + +fn stress_cases() -> u32 { + std::env::var("FFF_STRESS_CASES") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(2) +} + +fn stress_max_ops() -> usize { + std::env::var("FFF_STRESS_MAX_OPS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(40) +} + +fn stress_min_ops() -> usize { + std::env::var("FFF_STRESS_MIN_OPS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(20) +} + +/// A single randomized action. The runtime interprets abstract handles +/// (`idx`) by modulo against the *current* list of live files, so the same +/// abstract op sequence is always runnable regardless of which files exist. +#[derive(Debug, Clone)] +enum AbstractOp { + CreateFile { + seed: u32, + content_seed: u32, + }, + EditFile { + idx: usize, + content_seed: u32, + }, + DeleteFile { + idx: usize, + }, + RenameFile { + idx: usize, + new_seed: u32, + }, + CreateSubdirFile { + dir_seed: u16, + file_seed: u16, + content_seed: u32, + }, + GitignoreAppend { + pattern_seed: u16, + }, + GitAddAll, + GitCommit { + msg_seed: u16, + }, + GitResetHard, + GitStashThenPop, + Touch { + idx: usize, + }, // rewrite with same content; should still fire Modify + Noop, +} + +fn op_strategy() -> impl Strategy { + // Weights tuned to mirror a real editing session rather than a + // contrived git-heavy workload. In descending order: + // + // * edits dominate (~60%) — a real user bangs on Save all day + // * creates are moderate (~15%) — new files appear occasionally + // * deletes and renames are rare (~10%) — destructive ops happen + // but far less often than edits + // * explicit git operations are the rarest (~8%) — users stage / + // commit / reset in bursts between long stretches of editing + // + // The file-mutation path still exercises the per-path git-status + // update on every op via the watcher, so the git-status updater + // is *always* under load — the rare explicit `GitAddAll` / + // `GitCommit` / `GitResetHard` ops layer in coverage of the + // `.git/index` event → full-rescan path specifically. A 40-op + // scenario fires that path ~3 times, which is enough to keep the + // race between worktree writes and `.git/*` updates in rotation. + prop_oneof![ + 40 => (any::(), any::()).prop_map(|(i, c)| AbstractOp::EditFile { + idx: i, content_seed: c, + }), + 5 => any::().prop_map(|i| AbstractOp::Touch { idx: i }), + 8 => (any::(), any::()).prop_map(|(a, b)| AbstractOp::CreateFile { + seed: a, content_seed: b, + }), + 3 => (any::(), any::(), any::()).prop_map( + |(d, f, c)| AbstractOp::CreateSubdirFile { + dir_seed: d, file_seed: f, content_seed: c, + } + ), + 4 => any::().prop_map(|i| AbstractOp::DeleteFile { idx: i }), + 3 => (any::(), any::()).prop_map(|(i, s)| AbstractOp::RenameFile { + idx: i, new_seed: s, + }), + 1 => Just(AbstractOp::GitAddAll), + 1 => any::().prop_map(|m| AbstractOp::GitCommit { msg_seed: m }), + 1 => Just(AbstractOp::GitStashThenPop), + 1 => Just(AbstractOp::GitResetHard), + 1 => any::().prop_map(|p| AbstractOp::GitignoreAppend { pattern_seed: p }), + 2 => Just(AbstractOp::Noop), + ] +} + +fn ops_strategy() -> impl Strategy> { + ops_strategy_bounded(stress_min_ops(), stress_max_ops()) +} + +fn ops_strategy_bounded(min: usize, max: usize) -> impl Strategy> { + prop::collection::vec(op_strategy(), min..=max) +} + +const DEFAULT_STRESS_SEED: u64 = 0xDEAD_BEEF_CAFE_BABE; + +fn proptest_config() -> ProptestConfig { + ProptestConfig { + cases: stress_cases(), + // Cap shrinking because each shrink iteration replays the whole + // scenario (seconds of real IO). Proptest's default 4096 is wild. + max_shrink_iters: 16, + // `fork: true` would isolate runs but swallows the panic payload + // and printed diff through `rusty-fork`'s wire format — we want + // the convergence report visible on stderr, so we keep in-process. + // The watcher is fully torn down between cases via `Drop`. + fork: false, + // Integration tests don't live alongside a `lib.rs`/`main.rs`, so + // proptest's default `SourceParallel` persistence strategy can't + // locate the source tree and logs a noisy warning on every run. + // Pin the regression file to an explicit path next to this test + // so failing seeds get checked in and reproduced in CI. + failure_persistence: Some(Box::new(FileFailurePersistence::Direct(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fuzz_git_watcher_stress.proptest-regressions", + )))), + ..ProptestConfig::default() + } +} + +proptest! { + #![proptest_config(proptest_config())] + + /// Random-seeded scenario. Proptest picks a fresh seed every run from + /// system entropy, so CI sees different trajectories on every build + /// while known-bad seeds remain pinned in the regressions file. + #[test] + fn stress_random(ops in ops_strategy()) { + run_stress_scenario(&ops); + } +} + +/// Deterministic scenario keyed off `FFF_STRESS_SEED` (or +/// [`DEFAULT_STRESS_SEED`] if the env var is not set). Runs the same +/// proptest `ops_strategy()` but uses a ChaCha RNG seeded by the expanded +/// u64, so the exact case sequence is reproducible across machines and CI +/// runs. +/// +/// When this test panics, the panic message includes the seed so you can +/// re-run the failing case locally via: +/// +/// ```sh +/// RUSTFLAGS="--cfg stress" FFF_STRESS_SEED=0xDEADBEEFCAFEBABE \ +/// cargo test -p fff-search --test fuzz_git_watcher_stress seeded -- --nocapture +/// ``` +#[test] +fn stress_seeded() { + let seed = parse_stress_seed(); + let seed_bytes = expand_u64_seed(seed); + + eprintln!("stress_seeded: using deterministic seed {seed:#018x}"); + + let mut config = proptest_config(); + // The seeded run should never write to the shared regressions file — + // its failures are reproducible from the env var alone, and polluting + // the shared file with the deterministic seed would mask regressions + // for the random run that actually needs persistence. + config.failure_persistence = Some(Box::new(FileFailurePersistence::Off)); + + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed_bytes); + let mut runner = TestRunner::new_with_rng(config, rng); + let strategy = ops_strategy(); + + // Mimic `proptest!`'s case loop: run `Config::cases` independent draws + // from the strategy, failing the test as soon as any one scenario + // diverges. We drive this ourselves because `runner.run()` can't + // accept a non-Fn closure, and our scenario runner is side-effectful. + for case_idx in 0..runner.config().cases { + let tree = strategy + .new_tree(&mut runner) + .expect("ops_strategy::new_tree"); + let ops = tree.current(); + eprintln!( + " case {}/{}: {} ops", + case_idx + 1, + runner.config().cases, + ops.len() + ); + run_stress_scenario(&ops); + } +} + +/// Pinned deterministic regression for the git-status divergence found on +/// Windows CI (run 28264744320): after a `GitCommit` the picker retained stale +/// `INDEX_*` bits because a pre-commit per-path status snapshot was applied +/// after the post-commit full rescan. +/// +/// The op sequence is regenerated from the proptest seed persisted in the +/// regressions file (`cc 2c9d...`) using the CI op bounds (30..=60) that were +/// in effect when the failure was found. The fingerprint assertion fails +/// loudly if `ops_strategy()` ever changes shape — a changed strategy would +/// silently decode the same seed into a *different* scenario, turning this +/// regression guard into a no-op. +#[test] +fn stress_regression_stale_index_after_commit() { + let ops = ops_from_chacha_seed(REGRESSION_SEED_HEX, 30, 60); + assert_eq!( + (ops.len(), fingerprint_ops(&ops)), + (59, 0xc73f_16ce_b249_78eb), + "ops_strategy() changed shape: the pinned seed no longer decodes to \ + the original Windows-CI scenario. Either revert the strategy change \ + or re-pin this regression (the original literal op list is in git \ + history of this file).", + ); + run_stress_scenario(&ops); +} + +/// 32-byte ChaCha seed persisted by proptest for the Windows CI failure +/// (the `cc 2c9d...` entry in the regressions file). +const REGRESSION_SEED_HEX: &str = + "2c9d1ea2efbf6161f84b69598e884dbf1bde6039c70625adde0374817e20e2ea"; + +/// Regenerate an op sequence from a persisted proptest ChaCha seed by +/// replaying `ops_strategy()` the same way proptest does for regressions. +/// `min`/`max` must match the `FFF_STRESS_{MIN,MAX}_OPS` bounds that were +/// in effect when the seed was persisted — the strategy's value tree +/// depends on them. +fn ops_from_chacha_seed(seed_hex: &str, min: usize, max: usize) -> Vec { + let seed_bytes: Vec = (0..seed_hex.len() / 2) + .map(|i| u8::from_str_radix(&seed_hex[2 * i..2 * i + 2], 16).expect("valid hex seed")) + .collect(); + let mut config = proptest_config(); + config.failure_persistence = Some(Box::new(FileFailurePersistence::Off)); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed_bytes); + let mut runner = TestRunner::new_with_rng(config, rng); + ops_strategy_bounded(min, max) + .new_tree(&mut runner) + .expect("ops_strategy::new_tree") + .current() +} + +/// FNV-1a over the debug repr of the ops; stable across platforms and runs. +fn fingerprint_ops(ops: &[AbstractOp]) -> u64 { + let mut h = 0xcbf2_9ce4_8422_2325u64; + for b in format!("{ops:?}").bytes() { + h ^= b as u64; + h = h.wrapping_mul(0x0000_0100_0000_01b3); + } + h +} + +/// Parse `FFF_STRESS_SEED` as either decimal or `0x`-prefixed hex. +fn parse_stress_seed() -> u64 { + match std::env::var("FFF_STRESS_SEED") { + Ok(raw) => { + let trimmed = raw.trim(); + if let Some(hex) = trimmed + .strip_prefix("0x") + .or_else(|| trimmed.strip_prefix("0X")) + { + u64::from_str_radix(hex, 16) + .unwrap_or_else(|e| panic!("FFF_STRESS_SEED={raw:?} is not valid hex: {e}")) + } else { + trimmed + .parse::() + .unwrap_or_else(|e| panic!("FFF_STRESS_SEED={raw:?} is not a valid u64: {e}")) + } + } + Err(_) => DEFAULT_STRESS_SEED, + } +} + +/// Expand a u64 into the 32-byte seed ChaCha20 wants, by repeating the +/// little-endian bytes four times. The cycle is deliberate: two different +/// u64 seeds produce completely different byte sequences, so collisions +/// across the expansion are irrelevant in practice. +fn expand_u64_seed(seed: u64) -> [u8; 32] { + let le = seed.to_le_bytes(); + let mut out = [0u8; 32]; + for i in 0..4 { + out[i * 8..(i + 1) * 8].copy_from_slice(&le); + } + out +} + +#[derive(Debug)] +struct Live { + /// Path relative to the repo root (forward slashes on all platforms). + relative: String, + abs: PathBuf, +} + +fn run_stress_scenario(ops: &[AbstractOp]) { + // Opt-in tracing so `RUST_LOG=fff_search=debug` shows the watcher / + // refresh / update trail when debugging a failing run. Uses + // `try_init` so proptest can run many scenarios in the same process + // without double-initialising the subscriber. + let _ = tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + // Default to DEBUG for fff crates so CI failures on + // flaky OSes (Windows) include the watcher/event trail + // without needing to re-run with RUST_LOG set. + .unwrap_or_else(|_| { + tracing_subscriber::EnvFilter::new( + "warn,fff_search=debug,notify=debug,notify_debouncer_full=debug", + ) + }), + ) + .with_test_writer() + .try_init(); + + let tmp = TempDir::new().unwrap(); + let base = tmp.path().canonicalize().unwrap(); + + seed_repo(&base); + + let (shared_picker, _frecency) = start_watched_picker(&base); + wait_ready(&shared_picker); + + // Reconcile `live` from disk: after `git reset --hard` or similar the + // worktree can change under us, so we resync at every step instead of + // trusting our in-memory list. + let mut live: Vec = get_baseline_status_from_git(&base); + + // Sleep past the coarse (1 s) mtime tick so subsequent edits definitely + // advance mtime. Not strictly required for git status correctness but + // matches what real users experience. + std::thread::sleep(Duration::from_millis(1100)); + + for (step, op) in ops.iter().enumerate() { + apply_op(op, &base, &mut live); + + // Re-sync live from disk — ops like GitResetHard / GitStashThenPop + // may have created or removed files behind our back. + live = get_baseline_status_from_git(&base); + std::thread::sleep(PER_OP_SETTLE); + + if let Err(err) = converge_git_status(&shared_picker, &base, &live) { + panic!( + "\n──────────────────────────────────────────────────────────\n\ + ❌ Picker git_status diverged from repository truth.\n\ + ──────────────────────────────────────────────────────────\n\ + step: {step}\n\ + op: {op:?}\n\ + scenario ops:\n{trace}\n\ + ──────────────────────────────────────────────────────────\n\ + {err}\n", + trace = format_ops_trace(ops, step), + ); + } + } +} + +fn format_ops_trace(ops: &[AbstractOp], up_to_and_including: usize) -> String { + let mut s = String::new(); + for (i, op) in ops.iter().enumerate().take(up_to_and_including + 1) { + s.push_str(&format!(" [{i:>3}] {op:?}\n")); + } + s +} + +fn seed_repo(base: &Path) { + fs::create_dir_all(base.join("src")).unwrap(); + fs::write(base.join("README.md"), "# seed\n").unwrap(); + fs::write(base.join("src/main.rs"), "fn main() {}\n").unwrap(); + fs::write(base.join("src/lib.rs"), "// lib\n").unwrap(); + fs::write(base.join(".gitignore"), "*.log\ntmp/\n").unwrap(); + + git(base, &["init", "-b", "main"]); + git(base, &["config", "user.email", "fuzz@fff.test"]); + git(base, &["config", "user.name", "fuzz"]); + // Disable rename detection noise — libgit2 still computes ranks, but + // keeping the porcelain behaviour deterministic helps with diff reading. + git(base, &["config", "status.renames", "false"]); + git(base, &["add", "-A"]); + git(base, &["commit", "-m", "seed", "--no-gpg-sign"]); +} + +fn apply_op(op: &AbstractOp, base: &Path, live: &mut [Live]) { + use AbstractOp::*; + + match op { + CreateFile { seed, content_seed } => { + let rel = format!("f_{seed:08x}.rs"); + let abs = base.join(&rel); + if abs.exists() { + return; + } + fs::write(&abs, content_for(*content_seed, &rel)).unwrap(); + } + EditFile { idx, content_seed } => { + if live.is_empty() { + return; + } + let i = idx % live.len(); + let abs = &live[i].abs; + if !abs.is_file() { + return; + } + // Body must differ so git sees WT_MODIFIED (not just atime touch). + let body = format!( + "// edited seed={content_seed:08x}\n{}", + content_for(*content_seed, &live[i].relative) + ); + fs::write(abs, body).unwrap(); + } + Touch { idx } => { + // Rewrite with the same bytes we currently hold on disk. Still + // generates a Modify event; git status stays the same if the + // content matches the index, or WT_MODIFIED if it differs. + if live.is_empty() { + return; + } + let i = idx % live.len(); + let abs = &live[i].abs; + if let Ok(contents) = fs::read(abs) { + let _ = fs::write(abs, contents); + } + } + DeleteFile { idx } => { + if live.is_empty() { + return; + } + let i = idx % live.len(); + let _ = fs::remove_file(&live[i].abs); + } + RenameFile { idx, new_seed } => { + if live.is_empty() { + return; + } + let i = idx % live.len(); + let old = &live[i].abs; + if !old.is_file() { + return; + } + let new_rel = format!("r_{new_seed:08x}.rs"); + let new_abs = base.join(&new_rel); + if new_abs.exists() { + return; + } + let _ = fs::rename(old, &new_abs); + } + CreateSubdirFile { + dir_seed, + file_seed, + content_seed, + } => { + let rel = format!("d_{dir_seed:04x}/inside_{file_seed:04x}.rs"); + let abs = base.join(&rel); + if abs.exists() { + return; + } + fs::create_dir_all(abs.parent().unwrap()).unwrap(); + fs::write(&abs, content_for(*content_seed, &rel)).unwrap(); + } + GitignoreAppend { pattern_seed } => { + // Introduce NEW ignore patterns for a namespace not used by any + // other op so we never accidentally re-ignore a file under test. + let pattern = format!("__ignored_{pattern_seed:x}/\n"); + let gi = base.join(".gitignore"); + let mut cur = fs::read_to_string(&gi).unwrap_or_default(); + cur.push_str(&pattern); + fs::write(&gi, cur).unwrap(); + } + GitAddAll => { + git_allow_fail(base, &["add", "-A"]); + } + GitCommit { msg_seed } => { + // `--allow-empty` so we don't depend on there actually being + // staged changes; this still bumps HEAD and rewrites .git/index. + let _ = git_output( + base, + &[ + "commit", + "-m", + &format!("fuzz-{msg_seed:x}"), + "--allow-empty", + "--allow-empty-message", + "--no-gpg-sign", + ], + ); + } + GitResetHard => { + git_allow_fail(base, &["reset", "--hard"]); + } + GitStashThenPop => { + git_allow_fail(base, &["add", "-A"]); + let stash = git_output(base, &["stash", "push", "-u", "-m", "fuzz"]); + let had_stash = stash + .as_ref() + .map(|o| { + o.status.success() + && !String::from_utf8_lossy(&o.stdout).contains("No local changes") + }) + .unwrap_or(false); + if had_stash { + git_allow_fail(base, &["stash", "pop"]); + } + } + Noop => {} + } +} + +/// Block until the picker's git status agrees with `git2::Repository::statuses`. +/// +/// Poll until the picker's git-status view matches libgit2's truth AND +/// the real-query probe succeeds, or bail with a rich diff. +/// +/// On timeout, returns an `Err` describing every disagreement. +fn converge_git_status( + shared_picker: &SharedFilePicker, + base: &Path, + live: &[Live], +) -> Result<(), String> { + let deadline = Instant::now() + CONVERGE_TIMEOUT; + let mut last_mismatches: Vec; + let mut last_probe_err: Option = None; + + loop { + let truth = read_truth_status(base); + let picker_view = read_picker_status(shared_picker); + last_mismatches = diff_statuses(&truth, &picker_view); + + // Real-query probe: one random live file per round via fuzzy + grep. + // We only consider the probe authoritative once the main git-status + // enumeration agrees — otherwise a probe failure might just be + // the same debouncer-lag we're already waiting out. + let probe = if last_mismatches.is_empty() { + probe_real_queries(shared_picker, live) + } else { + None + }; + + match (last_mismatches.is_empty(), probe) { + (true, None) | (true, Some(Ok(()))) => return Ok(()), + (true, Some(Err(msg))) => last_probe_err = Some(msg), + _ => {} + } + + if Instant::now() >= deadline { + let mut report = if !last_mismatches.is_empty() { + format_mismatches(&last_mismatches, shared_picker) + } else { + String::from("git_status enumeration converged, but real-query probe failed:\n") + }; + if let Some(probe_msg) = last_probe_err { + report.push_str("\n── real-query probe ──\n"); + report.push_str(&probe_msg); + report.push('\n'); + } + report.push_str(&debug_dump_environment( + base, + shared_picker, + &last_mismatches, + )); + return Err(report); + } + std::thread::sleep(CONVERGE_POLL); + } +} + +/// On-failure diagnostic dump. Includes: +/// * base path (raw + OS encoding) so we can spot UNC/`\\?\` prefixes on Windows +/// * picker's own `base_path()` for comparison +/// * direct libgit2 `status_file` probes for every mismatched path +/// * full picker enumeration (first 20 rows) so we can see what keys +/// the picker is returning vs. what git2 reports +fn debug_dump_environment( + base: &Path, + shared_picker: &SharedFilePicker, + mismatches: &[Mismatch], +) -> String { + let mut s = String::new(); + s.push_str("\n── diagnostic dump ──\n"); + s.push_str(&format!( + "test base path (display) : {}\n", + base.display() + )); + s.push_str(&format!("test base path (debug) : {:?}\n", base)); + s.push_str(&format!( + "test base path (os bytes) : {:?}\n", + base.as_os_str() + )); + + if let Ok(guard) = shared_picker.read() + && let Some(picker) = guard.as_ref() + { + s.push_str(&format!( + "picker base_path (display) : {}\n", + picker.base_path().display() + )); + s.push_str(&format!( + "picker base_path (debug) : {:?}\n", + picker.base_path() + )); + } + + // Probe libgit2 directly for each mismatched path. + if let Ok(repo) = Repository::open(base) { + s.push_str("\nlibgit2 status_file probes (per mismatch path):\n"); + for m in mismatches { + let path = match m { + Mismatch::Disagree { path, .. } => path, + Mismatch::ExtraInPicker { path, .. } => path, + }; + match repo.status_file(std::path::Path::new(path)) { + Ok(st) => s.push_str(&format!(" • {path} -> {st:?}\n")), + Err(e) => { + s.push_str(&format!( + " • {path} -> ERROR {} (class={:?}, code={:?})\n", + e.message(), + e.class(), + e.code() + )); + } + } + } + } else { + s.push_str("\nlibgit2: could not open repo at base path\n"); + } + + // Dump the raw picker enumeration for comparison — up to 20 rows. + s.push_str("\npicker enumeration (first 20 entries, with byte-repr of each relative path):\n"); + if let Ok(guard) = shared_picker.read() + && let Some(picker) = guard.as_ref() + { + let parser = QueryParser::default(); + let parsed = parser.parse(""); + let result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + ..Default::default() + }, + ); + for (i, f) in result.items.iter().take(20).enumerate() { + let raw = f.relative_path(picker); + let norm = normalize(raw.clone()); + s.push_str(&format!( + " [{i:>2}] raw={raw:?} norm={norm:?} status={:?}\n", + f.git_status + )); + } + s.push_str(&format!(" (total: {} items)\n", result.items.len())); + } + + s +} + +#[derive(Debug)] +enum Mismatch { + /// Git knows about this path with `truth`, picker has `picker` (or None = missing). + Disagree { + path: String, + truth: Status, + picker: Option>, + }, + /// Picker has a non-clean entry for a path git doesn't report at all. + ExtraInPicker { path: String, picker: Status }, +} + +fn read_truth_status(base: &Path) -> BTreeMap { + let repo = Repository::open(base).expect("open repo for truth"); + let mut opts = StatusOptions::new(); + opts.include_untracked(true) + .recurse_untracked_dirs(true) + .include_unmodified(true) + .exclude_submodules(true); + let statuses = repo.statuses(Some(&mut opts)).expect("read statuses"); + + let mut out = BTreeMap::new(); + for entry in statuses.iter() { + if let Some(p) = entry.path() { + // git2 returns forward-slash paths; accept as-is. + out.insert(p.to_string(), entry.status()); + } + } + out +} + +fn read_picker_status(shared: &SharedFilePicker) -> BTreeMap> { + let guard = shared.read().expect("picker read lock"); + let picker = guard.as_ref().expect("picker initialized"); + + // Use the user-facing `fuzzy_search` API with an empty query to enumerate + // every file a user could ever see in the picker. An empty fuzzy query + // falls through to the frecency-only scoring path which returns ALL + // non-deleted files (base + overflow). A very large `limit` makes sure + // we don't silently paginate anything away for scenarios with many files. + // + // This intentionally mirrors what a real Neovim user observes — soft- + // deleted tombstones, files filtered out by search-side predicates, etc. + // are all excluded from the picker's user-facing view. + let parser = QueryParser::default(); + let parsed = parser.parse(""); + let result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 100_000, + }, + ..Default::default() + }, + ); + + let mut out = BTreeMap::new(); + for f in &result.items { + out.insert(normalize(f.relative_path(picker)), f.git_status); + } + out +} + +fn normalize(s: String) -> String { + #[cfg(windows)] + { + if s.contains('\\') { + return s.replace('\\', "/"); + } + } + s +} + +/// Probe a single file by name via the public `fuzzy_search` API and return +/// its status, or `None` if the file is not findable. Used for diagnostics +/// when the top-level enumeration reports a disagreement — confirms that +/// the mismatch reproduces via the exact user-facing lookup path. +fn probe_single_file_status(shared: &SharedFilePicker, relative: &str) -> Option> { + let guard = shared.read().ok()?; + let picker = guard.as_ref()?; + let parser = QueryParser::default(); + let parsed = parser.parse(relative); + let result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 200, + }, + ..Default::default() + }, + ); + result + .items + .iter() + .find(|f| normalize(f.relative_path(picker)) == relative) + .map(|f| f.git_status) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Real-query probes +// ═══════════════════════════════════════════════════════════════════════════ +// +// These exercise the **user-facing** fuzzy + grep surfaces with queries that +// look like what a human actually types, on top of a live file picked at +// random from the on-disk truth. They run once per convergence round in +// addition to the primary git-status enumeration check, and add coverage +// for three paths the empty-query enumeration never touches: +// +// * fuzzy matching with a non-empty query (bigram prefilter + score) +// * path-constraint fuzzy queries ("foo src/") +// * live grep (mmap content cache + bigram overlay for overflow files) +// +// An atomic counter drives the rotation across rounds so the probe spreads +// its attention across every live file over the course of a scenario. +static PROBE_COUNTER: AtomicU64 = AtomicU64::new(0); + +/// Read a file on disk and pull out the `FFF_STRESS_MARKER_` token +/// that [`content_for`] embedded in it. Returns `None` for files we never +/// wrote (seed README.md, .gitignore) — the caller skips the grep probe +/// in that case but still runs the fuzzy probe. +fn extract_marker(abs: &Path) -> Option { + let content = fs::read_to_string(abs).ok()?; + let start = content.find("FFF_STRESS_MARKER_")?; + // Marker is exactly `FFF_STRESS_MARKER_` + 8 hex chars. + const MARKER_LEN: usize = "FFF_STRESS_MARKER_".len() + 8; + if start + MARKER_LEN > content.len() { + return None; + } + let marker = &content[start..start + MARKER_LEN]; + // Sanity: the trailing 8 chars must all be hex. + if !marker.as_bytes()[MARKER_LEN - 8..] + .iter() + .all(|b| b.is_ascii_hexdigit()) + { + return None; + } + Some(marker.to_string()) +} + +/// File-stem → fuzzy search query. Strips extension + leading path +/// components so the probe feeds the picker a typical "I know roughly +/// what I'm looking for" query. +fn stem_for_query(relative: &str) -> String { + PathBuf::from(relative) + .file_stem() + .unwrap_or_default() + .to_string_lossy() + .into_owned() +} + +/// Run a one-shot fuzzy_search and return the matched items' relative +/// paths paired with their `git_status`. Uses only public APIs. +fn fuzzy_search_items(shared: &SharedFilePicker, query: &str) -> Vec<(String, Option)> { + let guard = match shared.read() { + Ok(g) => g, + Err(_) => return Vec::new(), + }; + let Some(picker) = guard.as_ref() else { + return Vec::new(); + }; + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 500, + }, + ..Default::default() + }, + ); + result + .items + .iter() + .map(|f| (normalize(f.relative_path(picker)), f.git_status)) + .collect() +} + +/// Run live grep (plain-text mode) and return the unique set of matched +/// file paths. A file appears in the set iff at least one line inside it +/// matches the query. Uses only public APIs. +fn grep_plain_matches(shared: &SharedFilePicker, query: &str) -> Vec { + let guard = match shared.read() { + Ok(g) => g, + Err(_) => return Vec::new(), + }; + let Some(picker) = guard.as_ref() else { + return Vec::new(); + }; + let parsed = parse_grep_query(query); + let opts = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 500, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + let result = picker.grep(&parsed, &opts); + // `GrepResult::files` is the already-deduplicated list of files that + // contained at least one match — exactly what we want. + result + .files + .iter() + .map(|f| normalize(f.relative_path(picker))) + .collect() +} + +/// Run live grep (fuzzy mode) and return matched file paths. +/// Exercises the `fuzzy_grep_search` code path which resolves content +/// via arena pointers — the path that was silently broken for overflow +/// files before the overflow_arena fix. +fn grep_fuzzy_matches(shared: &SharedFilePicker, query: &str) -> Vec { + let guard = match shared.read() { + Ok(g) => g, + Err(_) => return Vec::new(), + }; + let Some(picker) = guard.as_ref() else { + return Vec::new(); + }; + let parsed = parse_grep_query(query); + let opts = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 500, + mode: GrepMode::Fuzzy, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + let result = picker.grep(&parsed, &opts); + result + .files + .iter() + .map(|f| normalize(f.relative_path(picker))) + .collect() +} + +/// Run live grep (regex mode) and return matched file paths. +fn grep_regex_matches(shared: &SharedFilePicker, query: &str) -> Vec { + let guard = match shared.read() { + Ok(g) => g, + Err(_) => return Vec::new(), + }; + let Some(picker) = guard.as_ref() else { + return Vec::new(); + }; + let parsed = parse_grep_query(query); + let opts = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 500, + mode: GrepMode::Regex, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + let result = picker.grep(&parsed, &opts); + result + .files + .iter() + .map(|f| normalize(f.relative_path(picker))) + .collect() +} + +/// Report from [`probe_real_queries`]. `None` means "nothing to probe this +/// round" (empty live set). `Some(Err)` means a probe disagreed with truth +/// — convergence should not treat this as success. +type ProbeOutcome = Option>; + +/// Real-query verification: for one rotated-live-file per round, run a +/// fuzzy search by stem and (when a marker is present) a grep for its +/// embedded token. Asserts both return the file *and* that its +/// `git_status` matches the picker's main view. +/// +/// Return values: +/// * `None` — nothing to probe (no live files). +/// * `Some(Ok(()))` — the probe agreed with truth on both surfaces. +/// * `Some(Err(s))` — diagnostic describing the disagreement. +fn probe_real_queries(shared: &SharedFilePicker, live: &[Live]) -> ProbeOutcome { + if live.is_empty() { + return None; + } + let idx = (PROBE_COUNTER.fetch_add(1, Ordering::Relaxed) as usize) % live.len(); + let target = &live[idx]; + + // --- Fuzzy probe: search by file stem --- + let stem = stem_for_query(&target.relative); + // Tiny stems (< 2 chars) are rejected by the fuzzy scorer and + // surface via the frecency fallback — skip the assertion in that + // case, nothing meaningful to verify. + if stem.len() >= 2 { + let fuzzy_hits = fuzzy_search_items(shared, &stem); + let found = fuzzy_hits.iter().find(|(p, _)| p == &target.relative); + if found.is_none() { + return Some(Err(format!( + "fuzzy_search({stem:?}) did not return expected live file {:?}\n\ + got {} results; first few: {:?}", + target.relative, + fuzzy_hits.len(), + fuzzy_hits.iter().take(5).collect::>(), + ))); + } + } + + // --- Grep probe: search for the content marker using a randomly + // rotated grep strategy. Each round picks one of PlainText / Fuzzy / + // Regex so over many rounds all three code paths get exercised, + // including the overflow-arena resolution that was previously broken + // in fuzzy grep. + if let Some(marker) = extract_marker(&target.abs) { + let probe_round = PROBE_COUNTER.load(Ordering::Relaxed); + let (mode_name, matches) = match probe_round % 3 { + 0 => ("plain", grep_plain_matches(shared, &marker)), + 1 => ("fuzzy", grep_fuzzy_matches(shared, &marker)), + _ => ("regex", grep_regex_matches(shared, &marker)), + }; + if !matches.contains(&target.relative) { + return Some(Err(format!( + "grep[{mode_name}]({marker:?}) did not return expected live file {:?}\n\ + got {} matched files; first few: {:?}", + target.relative, + matches.len(), + matches.iter().take(5).collect::>(), + ))); + } + } + + Some(Ok(())) +} + +/// Returns the full list of disagreements. Empty means "in sync". +/// +/// We deliberately exclude a few categories from being considered bugs: +/// * Paths that git marks as `WT_DELETED` / `INDEX_DELETED` but the picker +/// has already dropped — this is by design (deleted files leave the +/// index immediately). +/// * Paths that git marks as `IGNORED` but the picker has already dropped. +/// * Paths under `.git/` — never tracked by the picker. +fn diff_statuses( + truth: &BTreeMap, + picker: &BTreeMap>, +) -> Vec { + let mut out = Vec::new(); + + for (path, &truth_status) in truth { + if path.starts_with(".git/") || path == ".git" { + continue; + } + match picker.get(path) { + Some(&p) => { + if !status_equivalent(truth_status, p) { + out.push(Mismatch::Disagree { + path: path.clone(), + truth: truth_status, + picker: Some(p), + }); + } + } + None => { + // Tolerate picker not having deleted-from-disk files. + let only_absence_reasons = + Status::WT_DELETED | Status::INDEX_DELETED | Status::IGNORED; + if !truth_status.intersects(only_absence_reasons) { + out.push(Mismatch::Disagree { + path: path.clone(), + truth: truth_status, + picker: None, + }); + } + } + } + } + + for (path, &p) in picker { + if path.starts_with(".git/") || path == ".git" { + continue; + } + if !truth.contains_key(path) { + // Picker thinks a file exists that git has never heard of. + // Only a bug if the picker also thinks it has a non-clean + // status for it — otherwise it's a transient during which the + // picker has indexed a file before the next truth snapshot. + let non_clean = match p { + None => false, + Some(s) => !(s.is_empty() || s == Status::CURRENT), + }; + if non_clean { + out.push(Mismatch::ExtraInPicker { + path: path.clone(), + picker: p.unwrap_or(Status::CURRENT), + }); + } + } + } + + out +} + +/// `None` and `Some(CURRENT|empty)` are both "clean"; otherwise the bitsets +/// must match exactly. +fn status_equivalent(truth: Status, picker: Option) -> bool { + let picker_bits = picker.unwrap_or(Status::CURRENT); + let truth_clean = truth.is_empty() || truth == Status::CURRENT; + let picker_clean = picker_bits.is_empty() || picker_bits == Status::CURRENT; + if truth_clean && picker_clean { + return true; + } + truth == picker_bits +} + +fn format_mismatches(mismatches: &[Mismatch], shared: &SharedFilePicker) -> String { + let mut s = String::new(); + s.push_str(&format!( + "{} mismatch(es) after {} of wait:\n", + mismatches.len(), + humantime(CONVERGE_TIMEOUT), + )); + for m in mismatches { + match m { + Mismatch::Disagree { + path, + truth, + picker, + } => { + let probe = probe_single_file_status(shared, path); + s.push_str(&format!( + " • {path}\n truth : {}\n picker(enum): {}\n picker(probe): {}\n", + format_status(Some(*truth)), + match picker { + Some(p) => format_status(*p), + None => "".into(), + }, + match probe { + Some(p) => format_status(p), + None => "".into(), + } + )); + } + Mismatch::ExtraInPicker { path, picker } => { + let probe = probe_single_file_status(shared, path); + s.push_str(&format!( + " • {path}\n truth : \n picker(enum) : {}\n picker(probe): {}\n", + format_status(Some(*picker)), + match probe { + Some(p) => format_status(p), + None => "".into(), + } + )); + } + } + } + s +} + +fn format_status(s: Option) -> String { + match s { + None => "None (= clean)".into(), + Some(st) if st.is_empty() || st == Status::CURRENT => "CURRENT (= clean)".into(), + Some(st) => format!("{st:?}"), + } +} + +fn humantime(d: Duration) -> String { + format!("{:.1}s", d.as_secs_f64()) +} + +fn get_baseline_status_from_git(base: &Path) -> Vec { + let mut out = Vec::new(); + let repo = match Repository::open(base) { + Ok(r) => r, + Err(_) => return out, + }; + let mut opts = StatusOptions::new(); + opts.include_untracked(true) + .recurse_untracked_dirs(true) + .include_unmodified(true) + .exclude_submodules(true); + let statuses = match repo.statuses(Some(&mut opts)) { + Ok(s) => s, + Err(_) => return out, + }; + for entry in statuses.iter() { + if let Some(p) = entry.path() { + let abs = base.join(p); + // Must be a real file *right now* — ignore stale WT_DELETED rows. + if abs.is_file() { + out.push(Live { + relative: p.to_string(), + abs, + }); + } + } + } + out +} + +/// Content marker embedded in every generated file body. The probing +/// layer reads this back via `grep` to exercise the live-grep path +/// (which hits the bigram index / mmap cache / overflow content store — +/// code paths the empty-query fuzzy enumeration never touches). +/// +/// Format: `FFF_STRESS_MARKER_<32-bit hex>` — namespaced so a repo +/// search for "MARKER" finds nothing from this test outside files we +/// wrote ourselves, which keeps the grep assertion unambiguous. +fn marker_for(seed: u32) -> String { + format!("FFF_STRESS_MARKER_{seed:08x}") +} + +fn content_for(seed: u32, name: &str) -> String { + let marker = marker_for(seed); + // Keep it small but distinct so adjacent edits produce different SHAs. + // The marker appears twice — once in a comment, once in a string + // literal — so a grep for it always has at least two hits per file + // and one of them is guaranteed to be on its own line for easy + // assertion. + format!( + "// file: {name}\n\ + // seed: {seed:08x}\n\ + // anchor: {marker}\n\ + pub fn anchor_{seed:x}() {{ let _ = \"{marker}\"; }}\n" + ) +} + +fn start_watched_picker(base: &Path) -> (SharedFilePicker, SharedFrecency) { + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::noop(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: false, + mode: FFFMode::Neovim, + watch: true, + ..Default::default() + }, + ) + .expect("FilePicker::new_with_shared_state"); + + (shared_picker, shared_frecency) +} + +fn wait_ready(p: &SharedFilePicker) { + assert!( + p.wait_for_scan(Duration::from_secs(15)), + "timed out waiting for initial scan" + ); + assert!( + p.wait_for_watcher(Duration::from_secs(15)), + "timed out waiting for watcher" + ); + // macOS FSEvents sometimes delivers a burst of "warmup" events right + // after the stream opens — let them drain before we start fuzzing. + std::thread::sleep(Duration::from_millis(200)); +} + +fn git_env() -> [(&'static str, &'static str); 4] { + [ + ("GIT_AUTHOR_NAME", "fuzz"), + ("GIT_AUTHOR_EMAIL", "fuzz@fff.test"), + ("GIT_COMMITTER_NAME", "fuzz"), + ("GIT_COMMITTER_EMAIL", "fuzz@fff.test"), + ] +} + +fn git(base: &Path, args: &[&str]) { + let out = Command::new("git") + .args(args) + .current_dir(base) + .envs(git_env()) + .output() + .unwrap_or_else(|e| panic!("git {args:?}: {e}")); + assert!( + out.status.success(), + "git {args:?} failed:\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr) + ); +} + +fn git_allow_fail(base: &Path, args: &[&str]) { + let _ = Command::new("git") + .args(args) + .current_dir(base) + .envs(git_env()) + .output(); +} + +fn git_output(base: &Path, args: &[&str]) -> Option { + Command::new("git") + .args(args) + .current_dir(base) + .envs(git_env()) + .output() + .ok() +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Merge-conflict scenario +// ═══════════════════════════════════════════════════════════════════════════ +// +// The fuzz alphabet above intentionally avoids branch operations — merge +// conflicts need a very specific topology (two divergent edits to the same +// hunk) that random sampling would almost never hit. This scripted test +// fills that gap with a deterministic end-to-end conflict flow: +// +// 1. seed a single tracked file `conflict.rs` +// 2. branch to `feature`, rewrite the file's inner expression, commit +// 3. back on `main`, rewrite the same hunk differently, commit +// 4. `git merge feature` — leaves `.git/MERGE_HEAD` + conflict markers +// 5. picker must observe `Status::CONFLICTED` for `conflict.rs`, and the +// same real-query surfaces (fuzzy + grep for `<<<<<<<`) must return +// the conflicted file +// 6. resolve by writing the merged content, `git add`, `git commit` +// 7. picker must converge back to `Status::CURRENT` +// +// On top of the `--cfg stress` gate the test inherits (so it never runs +// under plain `cargo test`), this is cross-platform: it uses libgit2 +// internals + the `git` CLI, both of which work identically on macOS +// FSEvents, Linux inotify, and Windows ReadDirectoryChangesW (if we ever +// add Windows to the matrix). The convergence window is a bit larger than +// the fuzz case because `git merge` touches several `.git/*` files at +// once, producing a heftier event burst. + +/// Convergence timeout for the conflict flow. `git merge` fires more FS +/// events than a plain `git add` — `.git/ORIG_HEAD`, `.git/MERGE_HEAD`, +/// `.git/MERGE_MSG`, the worktree file itself — so the debouncer has +/// more work than a one-off refresh. 30 s is still very generous. +const CONFLICT_CONVERGE_TIMEOUT: Duration = Duration::from_secs(30); + +#[test] +fn stress_merge_conflict_convergence() { + // Opt-in tracing for parity with the other stress tests. + let _ = tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + // Default to DEBUG for fff crates so CI failures on + // flaky OSes (Windows) include the watcher/event trail + // without needing to re-run with RUST_LOG set. + .unwrap_or_else(|_| { + tracing_subscriber::EnvFilter::new( + "warn,fff_search=debug,notify=debug,notify_debouncer_full=debug", + ) + }), + ) + .with_test_writer() + .try_init(); + + let tmp = TempDir::new().expect("mktemp"); + let base = tmp.path().canonicalize().expect("canonicalize tmp"); + + seed_conflict_repo(&base); + + let (shared_picker, _frecency) = start_watched_picker(&base); + wait_ready(&shared_picker); + + // ───────────────────────────────────────────────────────────────── + // Stage 1: create divergent commits on `feature` and `main` + // ───────────────────────────────────────────────────────────────── + // + // `conflict.rs` on `main` contains `BASE` text at line 2. We rewrite + // that line two different ways on two branches, so a merge can't + // pick a side automatically. + // + // Each rewrite is preceded by a 1.1 s sleep so the file's mtime + // advances past the previous write's — the watcher's mmap cache + // invalidation is mtime-triggered at 1 s granularity, and without + // the sleep a follow-up grep on the picker sees stale content from + // whichever variant was written last-but-one. + + std::thread::sleep(Duration::from_millis(1100)); + git(&base, &["checkout", "-b", "feature"]); + fs::write( + base.join("conflict.rs"), + "fn flavour() {\n \"FEATURE_VARIANT\"\n}\n", + ) + .unwrap(); + git(&base, &["add", "conflict.rs"]); + git( + &base, + &["commit", "-m", "feature: rewrite", "--no-gpg-sign"], + ); + + std::thread::sleep(Duration::from_millis(1100)); + git(&base, &["checkout", "main"]); + fs::write( + base.join("conflict.rs"), + "fn flavour() {\n \"MAIN_VARIANT\"\n}\n", + ) + .unwrap(); + git(&base, &["add", "conflict.rs"]); + git(&base, &["commit", "-m", "main: rewrite", "--no-gpg-sign"]); + + // Also sleep before the merge itself so the post-merge worktree + // write lands in a fresh second. + std::thread::sleep(Duration::from_millis(1100)); + + // Let the divergent commits settle in the picker before we merge. + expect_file_status( + &shared_picker, + &base, + "conflict.rs", + |s| { + // Post-commit: should be clean (CURRENT or None = no row). + s.is_none() || s.unwrap().is_empty() || s.unwrap().contains(Status::CURRENT) + }, + Duration::from_secs(10), + "pre-merge clean", + ) + .expect("pre-merge state should be clean"); + + // ───────────────────────────────────────────────────────────────── + // Stage 2: trigger the conflicting merge + // ───────────────────────────────────────────────────────────────── + + // `git merge feature` returns non-zero on conflict — that's fine. + // We don't use `git(..)` (asserts success) — conflict IS the happy + // path for this test. + let merge = git_output(&base, &["merge", "feature", "--no-edit", "--no-gpg-sign"]) + .expect("git merge didn't launch"); + assert!( + !merge.status.success(), + "expected `git merge feature` to conflict, but it succeeded:\nstdout:{}\nstderr:{}", + String::from_utf8_lossy(&merge.stdout), + String::from_utf8_lossy(&merge.stderr) + ); + + // libgit2 marks both sides' modifications with `CONFLICTED`. We + // wait for the picker to match. + expect_file_status( + &shared_picker, + &base, + "conflict.rs", + |s| s.is_some_and(|st| st.contains(Status::CONFLICTED)), + CONFLICT_CONVERGE_TIMEOUT, + "CONFLICTED after merge", + ) + .expect("picker must surface CONFLICTED after merge"); + + // ───────────────────────────────────────────────────────────────── + // Stage 3: real-query surfaces must still work in conflict state + // ───────────────────────────────────────────────────────────────── + + // Fuzzy search by stem returns the conflicted file. + let fuzzy_hits = fuzzy_search_items(&shared_picker, "conflict"); + assert!( + fuzzy_hits.iter().any(|(p, _)| p == "conflict.rs"), + "fuzzy_search(\"conflict\") during conflict state returned: {:?}", + fuzzy_hits + ); + + // Grep for the diff conflict marker `<<<<<<<` — it's literally in + // the worktree file right now, so live grep must find it. + // The watcher rewrites the file on-disk during merge, so the mmap + // cache needs to have been invalidated for this to succeed. + let grep_hits = grep_plain_matches(&shared_picker, "<<<<<<< "); + assert!( + grep_hits.contains(&"conflict.rs".to_string()), + "grep(\"<<<<<<< \") during conflict state returned: {:?}", + grep_hits + ); + + // ───────────────────────────────────────────────────────────────── + // Stage 4: resolve + commit, expect return to clean + // ───────────────────────────────────────────────────────────────── + + // `on_create_or_modify` invalidates the mmap cache only when `mtime` + // advances, and mtime has 1 s resolution on every filesystem we + // ship on. `git merge` in stage 2 just wrote to `conflict.rs`; if + // we re-write it within the same second the cache keeps its + // pre-resolve bytes (complete with `<<<<<<<` markers) and grep will + // keep finding them. Sleep past the mtime tick before re-writing. + // This matches the pattern in `fuzz_file_operations.rs`. + std::thread::sleep(Duration::from_millis(1100)); + + fs::write( + base.join("conflict.rs"), + "fn flavour() {\n \"RESOLVED_VARIANT\"\n}\n", + ) + .unwrap(); + git(&base, &["add", "conflict.rs"]); + git( + &base, + &[ + "commit", + "-m", + "resolve merge", + "--no-gpg-sign", + "--no-edit", + ], + ); + + expect_file_status( + &shared_picker, + &base, + "conflict.rs", + |s| s.is_none() || s.unwrap().is_empty() || s.unwrap().contains(Status::CURRENT), + CONFLICT_CONVERGE_TIMEOUT, + "CURRENT after resolve", + ) + .expect("picker must converge back to CURRENT after conflict resolution"); + + // Sanity: conflict markers are gone from both the worktree AND the + // picker's view (grep for `<<<<<<<` now returns 0 hits). + // + // The worktree check is an independent truth source — if it fails, + // the test harness itself is buggy (the resolve write didn't apply). + // The picker check can lag briefly: `expect_file_status` returned + // once `.git/index` events made git_status = CURRENT, but the + // `conflict.rs` Modify event from our resolve `fs::write` can land + // in a separate debounced batch that hasn't been processed yet. + // Poll until the mmap cache is flushed rather than asserting once. + let on_disk = fs::read_to_string(base.join("conflict.rs")).unwrap(); + assert!( + !on_disk.contains("<<<<<<<"), + "worktree still has conflict markers after resolve — test harness bug\n\ + on-disk content:\n{on_disk}" + ); + let deadline = Instant::now() + CONFLICT_CONVERGE_TIMEOUT; + loop { + let grep_hits = grep_plain_matches(&shared_picker, "<<<<<<< "); + if !grep_hits.contains(&"conflict.rs".to_string()) { + break; + } + if Instant::now() >= deadline { + panic!( + "picker grep(\"<<<<<<< \") after resolve still returns conflict.rs \ + after {} — mmap/overlay cache is stale\n\ + (worktree on disk has no conflict markers — this is a real \ + content-invalidation bug)\n\ + last grep hits: {:?}", + humantime(CONFLICT_CONVERGE_TIMEOUT), + grep_hits, + ); + } + std::thread::sleep(CONVERGE_POLL); + } +} + +/// Seed a repo with a single file that we'll later produce a conflict in. +fn seed_conflict_repo(base: &Path) { + fs::write(base.join("README.md"), "# merge conflict test\n").unwrap(); + fs::write( + base.join("conflict.rs"), + "fn flavour() {\n \"BASE\"\n}\n", + ) + .unwrap(); + git(base, &["init", "-b", "main"]); + git(base, &["config", "user.email", "fuzz@fff.test"]); + git(base, &["config", "user.name", "fuzz"]); + // Conflict behaviour must be deterministic regardless of merge.tool. + git(base, &["config", "merge.conflictstyle", "merge"]); + git(base, &["add", "-A"]); + git(base, &["commit", "-m", "seed", "--no-gpg-sign"]); +} + +/// Poll the picker for `relative` until `predicate` holds on its +/// `git_status`, or the timeout expires. On expiry returns an `Err` with +/// the last observed status + truth status for diagnostics. +fn expect_file_status( + shared: &SharedFilePicker, + base: &Path, + relative: &str, + predicate: impl Fn(Option) -> bool, + timeout: Duration, + what: &str, +) -> Result<(), String> { + let deadline = Instant::now() + timeout; + let mut last_picker; + let mut last_truth; + loop { + let picker_status = probe_single_file_status(shared, relative).flatten(); + let truth_status = read_truth_status(base).get(relative).copied(); + last_picker = picker_status; + last_truth = truth_status; + if predicate(picker_status) { + return Ok(()); + } + if Instant::now() >= deadline { + return Err(format!( + "timed out after {} waiting for `{relative}` to satisfy `{what}`\n\ + last picker status: {:?}\n\ + last truth status : {:?}", + humantime(timeout), + last_picker, + last_truth, + )); + } + std::thread::sleep(CONVERGE_POLL); + } +} diff --git a/crates/fff-core/tests/fuzz_real_repos.proptest-regressions b/crates/fff-core/tests/fuzz_real_repos.proptest-regressions new file mode 100644 index 0000000..ae5f635 --- /dev/null +++ b/crates/fff-core/tests/fuzz_real_repos.proptest-regressions @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 2bd1032403c66aac8b9bc62a50fe04cdc6a4e65e7f00c0f8cd99839a9469f3ba # shrinks to ops = [EditRandom { seed: 3973979185 }, IgnoredBurst { count: 19, seed: 511160782 }, EditTracked { seed: 985198351 }, IgnoredBurst { count: 3, seed: 1322513110 }, IgnoredBurst { count: 8, seed: 1671662344 }, EditTracked { seed: 1561297312 }, Verify, DeleteTracked, RevertTracked, EditRandom { seed: 579281187 }, IgnoredBurst { count: 2, seed: 3742469221 }, EditRandom { seed: 3676092681 }, EditRandom { seed: 2193336478 }, EditTracked { seed: 3160375285 }, Verify, DeleteTracked, CreateFile { seed: 2245201073 }, EditRandom { seed: 1752067380 }, EditTracked { seed: 3600913031 }, EditTracked { seed: 1180012156 }, DeleteTracked, RevertTracked, IgnoredBurst { count: 11, seed: 4074832553 }, Verify, EditRandom { seed: 89716519 }, EditRandom { seed: 2578352041 }, RevertTracked, CreateFile { seed: 831766394 }, DeleteTracked, EditTracked { seed: 2532322292 }, Verify, EditTracked { seed: 3896368397 }, DeleteTracked, Verify, EditRandom { seed: 239317770 }, EditRandom { seed: 3784639684 }, EditRandom { seed: 3020447078 }, RevertTracked, CreateFile { seed: 2240840307 }, Verify, EditRandom { seed: 644157265 }, Verify, CreateFile { seed: 650224092 }, EditTracked { seed: 4211001586 }, Verify, CreateFile { seed: 594290450 }, RevertTracked, Verify, Verify, EditTracked { seed: 1256892446 }, EditRandom { seed: 2896781441 }, CreateFile { seed: 3744921304 }, Verify, Verify, RevertTracked, EditRandom { seed: 3929541705 }, RevertTracked, CreateFile { seed: 2225889515 }, IgnoredBurst { count: 4, seed: 296850726 }, EditRandom { seed: 1925610793 }, EditRandom { seed: 3903819872 }, EditRandom { seed: 3948605713 }, EditRandom { seed: 590113476 }, EditTracked { seed: 3088835824 }, EditTracked { seed: 1669401121 }, EditTracked { seed: 3243754165 }, IgnoredBurst { count: 4, seed: 3552112106 }, EditTracked { seed: 4218311739 }, RevertTracked, DeleteTracked, EditRandom { seed: 2695626522 }, Verify, DeleteTracked, Verify, Verify, CreateFile { seed: 1129013495 }, EditTracked { seed: 2692665451 }, EditRandom { seed: 1936337204 }, EditTracked { seed: 3314964290 }, CreateFile { seed: 479382716 }, Verify, DeleteTracked, Verify, CreateFile { seed: 2200064089 }, EditRandom { seed: 1120920528 }, EditTracked { seed: 12145115 }, EditTracked { seed: 305976052 }, RevertTracked, RevertTracked, DeleteTracked, CreateFile { seed: 3971122115 }, RevertTracked, DeleteTracked, DeleteTracked, Verify, RevertTracked, CreateFile { seed: 357392470 }, Verify, DeleteTracked, EditTracked { seed: 3744458445 }, CreateFile { seed: 269948916 }, RevertTracked, DeleteTracked, EditRandom { seed: 2375982740 }, CreateFile { seed: 4078337323 }, EditTracked { seed: 991718099 }, DeleteTracked, EditRandom { seed: 3561974390 }, Verify, EditTracked { seed: 1719995704 }, Verify, EditTracked { seed: 1455984559 }, CreateFile { seed: 3576941322 }, Verify, Verify, Verify, RevertTracked, EditTracked { seed: 2875597788 }, IgnoredBurst { count: 7, seed: 3483740109 }, Verify, RevertTracked, EditRandom { seed: 4148423322 }, Verify, IgnoredBurst { count: 10, seed: 403314845 }, EditTracked { seed: 426978255 }, EditRandom { seed: 2231943361 }, EditRandom { seed: 3794886001 }, CreateFile { seed: 405760745 }, EditRandom { seed: 2787015738 }, DeleteTracked, EditTracked { seed: 4162235798 }, CreateFile { seed: 54301828 }, EditTracked { seed: 257320116 }, EditTracked { seed: 2968447638 }, RevertTracked, EditRandom { seed: 2281122562 }, EditTracked { seed: 3848349126 }, EditTracked { seed: 2392477458 }, CreateFile { seed: 578024549 }, Verify, EditTracked { seed: 3148899221 }, Verify, EditTracked { seed: 3039340310 }, Verify, DeleteTracked, IgnoredBurst { count: 19, seed: 710316948 }, EditRandom { seed: 1706703248 }, Verify, EditTracked { seed: 1192341874 }, EditTracked { seed: 1921337292 }, Verify, IgnoredBurst { count: 3, seed: 1595406682 }, Verify, IgnoredBurst { count: 17, seed: 1007383845 }, CreateFile { seed: 3522710380 }, EditTracked { seed: 469984332 }, DeleteTracked, IgnoredBurst { count: 18, seed: 2443321062 }, EditTracked { seed: 1035896127 }, RevertTracked, EditRandom { seed: 1055440338 }, Verify, Verify, EditRandom { seed: 763883156 }, Verify, CreateFile { seed: 1137224999 }, EditRandom { seed: 1633958196 }, EditTracked { seed: 3738737459 }, Verify, DeleteTracked, CreateFile { seed: 2788340683 }, Verify, RevertTracked, EditTracked { seed: 1909221278 }, Verify, IgnoredBurst { count: 17, seed: 3499099003 }, Verify, EditRandom { seed: 1144453800 }, RevertTracked, CreateFile { seed: 2109075736 }, EditTracked { seed: 712682621 }, IgnoredBurst { count: 12, seed: 3721302250 }, DeleteTracked, RevertTracked, EditRandom { seed: 4226785671 }, RevertTracked, IgnoredBurst { count: 11, seed: 219666167 }, Verify, EditTracked { seed: 2098222640 }, Verify, IgnoredBurst { count: 3, seed: 3816911240 }, EditRandom { seed: 2863901373 }, EditTracked { seed: 60488794 }, CreateFile { seed: 1367832198 }, EditTracked { seed: 1458745135 }, EditRandom { seed: 1049137857 }, Verify, EditRandom { seed: 4180835914 }, DeleteTracked, EditRandom { seed: 2165041122 }, Verify, Verify, EditRandom { seed: 222010849 }, EditTracked { seed: 2550494177 }, Verify, Verify, RevertTracked, EditTracked { seed: 434134171 }, Verify, EditRandom { seed: 395799315 }, IgnoredBurst { count: 6, seed: 4071480810 }, EditRandom { seed: 3074820789 }, EditRandom { seed: 124124793 }, EditRandom { seed: 1040834453 }, EditTracked { seed: 3416392407 }, EditRandom { seed: 3536050357 }, Verify, EditRandom { seed: 2878426631 }, IgnoredBurst { count: 9, seed: 4182247654 }, CreateFile { seed: 3190392188 }, Verify, IgnoredBurst { count: 12, seed: 315132273 }, RevertTracked, CreateFile { seed: 39441597 }, Verify, EditTracked { seed: 3827879271 }, Verify, EditTracked { seed: 3359133055 }, CreateFile { seed: 3105787734 }, IgnoredBurst { count: 7, seed: 3183850029 }, EditRandom { seed: 3334300529 }, EditTracked { seed: 1744613429 }, RevertTracked, EditRandom { seed: 1573019480 }, CreateFile { seed: 652479131 }, Verify, RevertTracked, RevertTracked, EditRandom { seed: 3675395215 }, DeleteTracked, Verify, Verify, RevertTracked, EditRandom { seed: 2541437719 }, DeleteTracked, EditRandom { seed: 3037544534 }, EditTracked { seed: 2302622735 }, EditRandom { seed: 1812723857 }, RevertTracked, DeleteTracked, EditRandom { seed: 4043600818 }, Verify, IgnoredBurst { count: 16, seed: 44679182 }, Verify, Verify, DeleteTracked, CreateFile { seed: 3008740222 }, DeleteTracked, EditRandom { seed: 2914149802 }, EditRandom { seed: 667956316 }, CreateFile { seed: 3617631237 }, DeleteTracked, EditRandom { seed: 1362877831 }, DeleteTracked, EditRandom { seed: 3523649677 }, DeleteTracked, Verify, CreateFile { seed: 1403756910 }, Verify, DeleteTracked, EditRandom { seed: 2820228808 }, CreateFile { seed: 53108399 }, EditRandom { seed: 2889329358 }, EditTracked { seed: 4292504954 }, RevertTracked, Verify, Verify, EditRandom { seed: 2606229962 }, EditRandom { seed: 1857034562 }, EditRandom { seed: 3320336332 }, CreateFile { seed: 339796935 }, EditTracked { seed: 422591184 }, Verify, IgnoredBurst { count: 2, seed: 2871058542 }, CreateFile { seed: 1314463008 }, Verify, RevertTracked, EditTracked { seed: 1598357304 }, EditRandom { seed: 2158855095 }, Verify, Verify, EditTracked { seed: 1075525192 }, EditTracked { seed: 3948788493 }, EditTracked { seed: 676679568 }, Verify, Verify, EditTracked { seed: 3915678914 }, EditTracked { seed: 933469287 }, EditTracked { seed: 322191859 }, RevertTracked, CreateFile { seed: 1571061118 }, EditTracked { seed: 3844952748 }, EditTracked { seed: 3965010100 }, EditTracked { seed: 1584126813 }, Verify, EditRandom { seed: 17391951 }, Verify, IgnoredBurst { count: 14, seed: 3720249162 }, EditTracked { seed: 1131322973 }, RevertTracked, Verify, EditTracked { seed: 430594834 }, CreateFile { seed: 2763590962 }, Verify, IgnoredBurst { count: 15, seed: 1426969464 }, Verify, IgnoredBurst { count: 15, seed: 3646366033 }, EditTracked { seed: 552179910 }, CreateFile { seed: 1921856193 }, CreateFile { seed: 1102669100 }, EditTracked { seed: 3277958796 }, IgnoredBurst { count: 13, seed: 1012655776 }, Verify, EditRandom { seed: 427753982 }, CreateFile { seed: 3334360159 }, EditTracked { seed: 340831448 }, EditTracked { seed: 2131800022 }, EditRandom { seed: 2353283979 }, EditRandom { seed: 3450629600 }, Verify, Verify, EditRandom { seed: 4139689825 }, EditTracked { seed: 682373982 }, IgnoredBurst { count: 8, seed: 2882909824 }, Verify, IgnoredBurst { count: 19, seed: 4002673086 }, EditRandom { seed: 2715554126 }, DeleteTracked, EditRandom { seed: 4179922296 }, Verify, EditTracked { seed: 109690251 }, DeleteTracked, EditTracked { seed: 3472667504 }, EditRandom { seed: 1714341831 }, EditRandom { seed: 667083687 }, Verify, Verify, IgnoredBurst { count: 10, seed: 3128910574 }, CreateFile { seed: 2540247207 }, Verify, IgnoredBurst { count: 10, seed: 1511807149 }, RevertTracked, EditTracked { seed: 78824091 }, DeleteTracked, EditTracked { seed: 1898400293 }, EditRandom { seed: 3755754295 }, EditRandom { seed: 4121118104 }, EditRandom { seed: 432269839 }, EditRandom { seed: 145482715 }, EditRandom { seed: 3236540540 }, EditRandom { seed: 565508118 }, EditTracked { seed: 825926783 }, Verify, Verify, Verify, EditTracked { seed: 3586835935 }, EditRandom { seed: 2740340414 }, Verify, Verify, EditRandom { seed: 3940654502 }, RevertTracked, DeleteTracked, EditRandom { seed: 2108439768 }, IgnoredBurst { count: 2, seed: 1508577991 }, Verify, Verify, EditRandom { seed: 184009805 }, EditTracked { seed: 997928525 }, IgnoredBurst { count: 6, seed: 3249975968 }, Verify, EditRandom { seed: 2001160028 }, CreateFile { seed: 108745163 }, EditRandom { seed: 2863438947 }, EditTracked { seed: 4080526352 }, IgnoredBurst { count: 16, seed: 3652136205 }, EditRandom { seed: 33573748 }, IgnoredBurst { count: 1, seed: 2202984205 }, EditRandom { seed: 87597533 }, Verify, IgnoredBurst { count: 7, seed: 2388341587 }, Verify, Verify, EditRandom { seed: 2790460089 }, CreateFile { seed: 450719257 }, Verify, Verify, EditRandom { seed: 1611849709 }, CreateFile { seed: 1223048280 }, Verify, Verify, CreateFile { seed: 925249148 }, Verify, DeleteTracked, CreateFile { seed: 370097360 }, Verify, IgnoredBurst { count: 8, seed: 2407036983 }, EditRandom { seed: 1564783191 }, RevertTracked, Verify, CreateFile { seed: 3474431609 }, CreateFile { seed: 2854085921 }, EditRandom { seed: 1850806274 }, Verify, CreateFile { seed: 4017183086 }, IgnoredBurst { count: 1, seed: 3017226653 }, DeleteTracked, DeleteTracked, RevertTracked, CreateFile { seed: 3267789236 }, EditRandom { seed: 1402969627 }, CreateFile { seed: 402825408 }, CreateFile { seed: 243351288 }, EditRandom { seed: 4086766584 }, EditTracked { seed: 2303712775 }, EditRandom { seed: 2539341008 }, EditRandom { seed: 2943360338 }, Verify, Verify, Verify, EditTracked { seed: 1678472286 }, IgnoredBurst { count: 18, seed: 1113256590 }, EditTracked { seed: 3844639989 }, EditRandom { seed: 3152676663 }, EditTracked { seed: 1805905969 }, Verify, IgnoredBurst { count: 9, seed: 290834906 }, EditRandom { seed: 567725288 }, EditTracked { seed: 3191796340 }, CreateFile { seed: 1367118511 }, EditRandom { seed: 3207404862 }, CreateFile { seed: 113773843 }, Verify, DeleteTracked, Verify, Verify, RevertTracked, RevertTracked, Verify, Verify, CreateFile { seed: 2149772306 }, EditTracked { seed: 2188875472 }, IgnoredBurst { count: 1, seed: 1153978335 }, EditTracked { seed: 2377669181 }, Verify, DeleteTracked, Verify, EditRandom { seed: 949191429 }, EditTracked { seed: 3358882450 }, CreateFile { seed: 3525359493 }, Verify, EditRandom { seed: 2805471848 }, CreateFile { seed: 352194141 }, EditRandom { seed: 2734771285 }, EditTracked { seed: 1609339806 }, Verify, DeleteTracked, RevertTracked, IgnoredBurst { count: 7, seed: 394177225 }, RevertTracked, EditRandom { seed: 707035856 }, EditRandom { seed: 1163161576 }, EditRandom { seed: 3784310466 }, Verify, IgnoredBurst { count: 14, seed: 352157784 }, DeleteTracked, CreateFile { seed: 3671052303 }, Verify, EditTracked { seed: 768235780 }, Verify, Verify, Verify, RevertTracked, Verify, IgnoredBurst { count: 11, seed: 1652987278 }, EditRandom { seed: 1690752738 }, CreateFile { seed: 3889196883 }, EditRandom { seed: 3861916787 }, EditRandom { seed: 3494186120 }, EditRandom { seed: 2435178635 }, Verify, EditTracked { seed: 4194577038 }, CreateFile { seed: 1759771201 }, Verify, EditRandom { seed: 2205234621 }, CreateFile { seed: 1967348885 }, RevertTracked, DeleteTracked, EditTracked { seed: 311618443 }, IgnoredBurst { count: 9, seed: 1690509918 }, RevertTracked, EditRandom { seed: 2772778767 }, EditRandom { seed: 681531201 }, EditTracked { seed: 2951485348 }, Verify, Verify, EditTracked { seed: 1236848693 }, Verify, Verify, Verify, EditTracked { seed: 3790271369 }, CreateFile { seed: 297372231 }, CreateFile { seed: 55480484 }, CreateFile { seed: 2770541116 }, Verify, DeleteTracked, EditRandom { seed: 1397524124 }, IgnoredBurst { count: 9, seed: 1957554657 }, DeleteTracked, Verify, EditRandom { seed: 3150743676 }, Verify, Verify, CreateFile { seed: 3019311451 }, RevertTracked, Verify, EditTracked { seed: 1315394490 }, Verify, DeleteTracked, EditRandom { seed: 2728101238 }, Verify, CreateFile { seed: 2614566715 }, Verify, CreateFile { seed: 2856960945 }, CreateFile { seed: 2079561091 }, CreateFile { seed: 2874265061 }, IgnoredBurst { count: 14, seed: 2194472929 }, DeleteTracked, EditRandom { seed: 2355490907 }, EditTracked { seed: 2265313504 }, EditTracked { seed: 3433250532 }, RevertTracked, EditRandom { seed: 2310360272 }, EditTracked { seed: 3073533473 }, EditRandom { seed: 2761103227 }, EditRandom { seed: 1890282854 }, DeleteTracked, CreateFile { seed: 2927938188 }, EditRandom { seed: 2665233940 }, IgnoredBurst { count: 4, seed: 3140578760 }, RevertTracked, Verify, RevertTracked, Verify, EditTracked { seed: 3866635085 }, EditTracked { seed: 4188006370 }, Verify, DeleteTracked, DeleteTracked, CreateFile { seed: 703990393 }, EditRandom { seed: 1941957214 }, EditTracked { seed: 3643328766 }, RevertTracked, EditTracked { seed: 3075292415 }, EditTracked { seed: 2804574049 }, RevertTracked, EditRandom { seed: 2626551763 }, CreateFile { seed: 2386531338 }, Verify, Verify, EditRandom { seed: 1446652070 }, EditRandom { seed: 3664099977 }, IgnoredBurst { count: 4, seed: 555692681 }, EditRandom { seed: 4055639373 }, EditRandom { seed: 1739864219 }, EditRandom { seed: 131450651 }, CreateFile { seed: 2141237411 }, EditTracked { seed: 2520434680 }, CreateFile { seed: 2310992469 }, Verify, Verify, EditTracked { seed: 4033983783 }, RevertTracked, EditRandom { seed: 1681952326 }, IgnoredBurst { count: 12, seed: 1097702745 }, EditRandom { seed: 1894538168 }, EditTracked { seed: 2519177745 }, IgnoredBurst { count: 15, seed: 3900852461 }, CreateFile { seed: 4216761454 }, Verify, EditTracked { seed: 2786480369 }, EditTracked { seed: 2610087916 }, CreateFile { seed: 3452677131 }, Verify, DeleteTracked, Verify, IgnoredBurst { count: 15, seed: 3534407303 }, RevertTracked, EditRandom { seed: 2772921212 }, Verify, RevertTracked, Verify, IgnoredBurst { count: 6, seed: 2671801194 }, CreateFile { seed: 1602036010 }, EditTracked { seed: 172157022 }, Verify, Verify, EditRandom { seed: 3172236186 }, EditRandom { seed: 4287742109 }, EditRandom { seed: 2303139980 }, CreateFile { seed: 3373411723 }, EditTracked { seed: 3989385178 }, Verify, Verify, CreateFile { seed: 2288233942 }, CreateFile { seed: 649467595 }, DeleteTracked, CreateFile { seed: 2260528871 }, EditTracked { seed: 1272569946 }, RevertTracked, IgnoredBurst { count: 6, seed: 2334681169 }, Verify, IgnoredBurst { count: 9, seed: 348714819 }, IgnoredBurst { count: 19, seed: 4225071110 }, EditRandom { seed: 3340241965 }, EditRandom { seed: 1399849636 }, EditRandom { seed: 2941945108 }, EditRandom { seed: 1942382488 }, EditRandom { seed: 615375177 }, Verify, Verify, Verify, CreateFile { seed: 2534444143 }, EditRandom { seed: 3618247244 }, IgnoredBurst { count: 5, seed: 3576302117 }, EditRandom { seed: 3307892620 }, DeleteTracked, EditRandom { seed: 696665600 }, EditRandom { seed: 1140355867 }, EditTracked { seed: 600058135 }, EditRandom { seed: 3806797232 }, EditTracked { seed: 2433638424 }, CreateFile { seed: 4237702083 }, EditTracked { seed: 140970332 }, Verify, CreateFile { seed: 2239691394 }, EditRandom { seed: 1848489369 }, DeleteTracked, EditRandom { seed: 497457813 }, RevertTracked, DeleteTracked, Verify, Verify, EditTracked { seed: 277259202 }, EditTracked { seed: 1302835984 }, EditTracked { seed: 3296549517 }, DeleteTracked, DeleteTracked, EditTracked { seed: 990378599 }, EditRandom { seed: 4132447016 }, Verify, CreateFile { seed: 3273200468 }, EditRandom { seed: 3131291007 }, DeleteTracked, EditTracked { seed: 136055294 }, CreateFile { seed: 922492021 }, Verify, EditRandom { seed: 3886932299 }, DeleteTracked, Verify, IgnoredBurst { count: 11, seed: 2271499450 }, EditRandom { seed: 1336524362 }, Verify, CreateFile { seed: 3678901020 }, EditTracked { seed: 1418403844 }, RevertTracked, EditRandom { seed: 357261235 }, EditTracked { seed: 2080083529 }, CreateFile { seed: 2527380729 }, EditRandom { seed: 2698033681 }, EditRandom { seed: 3171739318 }, CreateFile { seed: 1781002527 }, EditTracked { seed: 1784082393 }, IgnoredBurst { count: 3, seed: 4019080271 }, Verify, RevertTracked, DeleteTracked, EditRandom { seed: 3732302386 }, IgnoredBurst { count: 11, seed: 2337704333 }, RevertTracked, RevertTracked, EditRandom { seed: 2745474813 }, CreateFile { seed: 1087861635 }, RevertTracked, EditRandom { seed: 550119362 }, Verify, DeleteTracked, EditTracked { seed: 2953390169 }, EditTracked { seed: 312315369 }, CreateFile { seed: 2908563734 }, DeleteTracked, EditRandom { seed: 3691372883 }, Verify, EditTracked { seed: 972966981 }, Verify, Verify, Verify, EditTracked { seed: 1626103600 }, IgnoredBurst { count: 5, seed: 2315330318 }, Verify, EditRandom { seed: 3586656111 }, CreateFile { seed: 688252447 }, EditRandom { seed: 838915923 }, Verify, RevertTracked, IgnoredBurst { count: 19, seed: 89221717 }, RevertTracked, IgnoredBurst { count: 4, seed: 1301067071 }, RevertTracked, Verify, Verify, DeleteTracked, EditTracked { seed: 64414269 }, EditTracked { seed: 1843259484 }, CreateFile { seed: 2462516468 }, RevertTracked, EditRandom { seed: 1920198788 }, IgnoredBurst { count: 11, seed: 4108018572 }, EditRandom { seed: 2411133397 }, DeleteTracked, IgnoredBurst { count: 10, seed: 3790025154 }, Verify, EditTracked { seed: 3436377394 }, EditTracked { seed: 1655435972 }, EditRandom { seed: 1392326518 }, Verify, CreateFile { seed: 3315861162 }, EditRandom { seed: 150884464 }, DeleteTracked, EditTracked { seed: 2236197900 }, CreateFile { seed: 2828168112 }, EditTracked { seed: 2665204653 }, EditTracked { seed: 1940831956 }, CreateFile { seed: 1635014651 }, Verify, Verify, CreateFile { seed: 3842785448 }, EditTracked { seed: 2119639642 }, Verify, CreateFile { seed: 3426033077 }, IgnoredBurst { count: 8, seed: 346077584 }, CreateFile { seed: 921384285 }, EditTracked { seed: 976176242 }, EditRandom { seed: 3031324220 }, Verify, EditRandom { seed: 1723898752 }, IgnoredBurst { count: 15, seed: 1981351212 }, Verify, RevertTracked, Verify, DeleteTracked, Verify, EditTracked { seed: 2638382288 }, EditTracked { seed: 1857175060 }, Verify, IgnoredBurst { count: 3, seed: 2748574242 }, RevertTracked, Verify, Verify, CreateFile { seed: 2512869688 }, EditTracked { seed: 2657306817 }, RevertTracked, CreateFile { seed: 8259479 }, IgnoredBurst { count: 15, seed: 4080748634 }, CreateFile { seed: 1155353711 }, EditRandom { seed: 2918320957 }, RevertTracked, EditTracked { seed: 991716558 }, DeleteTracked, CreateFile { seed: 809356199 }, EditTracked { seed: 1311363291 }, CreateFile { seed: 2211676489 }, Verify, DeleteTracked, Verify, Verify, EditTracked { seed: 1982412926 }, CreateFile { seed: 893631077 }, IgnoredBurst { count: 19, seed: 3870859262 }, Verify, IgnoredBurst { count: 18, seed: 3693193320 }, IgnoredBurst { count: 11, seed: 859782653 }, CreateFile { seed: 4032832134 }, Verify, Verify, Verify, CreateFile { seed: 1237648188 }, EditTracked { seed: 790755363 }, DeleteTracked, CreateFile { seed: 3774128312 }, CreateFile { seed: 3496526883 }, Verify, Verify, CreateFile { seed: 2392690045 }, EditRandom { seed: 136991493 }, EditRandom { seed: 716025343 }, EditTracked { seed: 2768444468 }, EditTracked { seed: 3767294926 }, EditTracked { seed: 3878258241 }, EditTracked { seed: 2539521037 }, Verify, EditRandom { seed: 2547660594 }, EditTracked { seed: 3759817499 }, EditRandom { seed: 1951826285 }, Verify, Verify, DeleteTracked, DeleteTracked, EditTracked { seed: 2439960371 }, DeleteTracked, Verify, EditTracked { seed: 1987151839 }, Verify, Verify, IgnoredBurst { count: 15, seed: 1291201007 }, EditTracked { seed: 3302767204 }, Verify, RevertTracked, RevertTracked, CreateFile { seed: 3841399227 }, EditRandom { seed: 1214895765 }, RevertTracked, IgnoredBurst { count: 12, seed: 4039255953 }, EditRandom { seed: 2614543638 }, CreateFile { seed: 299987642 }, Verify, Verify, Verify, RevertTracked, Verify, EditRandom { seed: 1283295384 }, RevertTracked, Verify, EditTracked { seed: 46420909 }, EditTracked { seed: 1628766460 }, CreateFile { seed: 4154494839 }, Verify, EditTracked { seed: 3373320246 }, RevertTracked, EditRandom { seed: 856537039 }, EditRandom { seed: 1817682140 }, EditRandom { seed: 2206806797 }, EditRandom { seed: 289110096 }, Verify, Verify, CreateFile { seed: 3692675069 }, EditTracked { seed: 523023608 }, Verify, Verify, Verify, IgnoredBurst { count: 10, seed: 140155456 }, EditRandom { seed: 3133447078 }, EditRandom { seed: 3319878723 }, EditTracked { seed: 4260223503 }, EditRandom { seed: 4207695806 }, CreateFile { seed: 1278913232 }, IgnoredBurst { count: 12, seed: 4225600424 }, IgnoredBurst { count: 9, seed: 2043569765 }, EditTracked { seed: 735759191 }, RevertTracked, RevertTracked, EditRandom { seed: 393746024 }, EditRandom { seed: 220590000 }, EditTracked { seed: 2851513302 }, EditTracked { seed: 1229017144 }, Verify, RevertTracked, Verify, RevertTracked, EditRandom { seed: 3936474989 }, EditRandom { seed: 2848675989 }, EditTracked { seed: 4215219303 }, IgnoredBurst { count: 11, seed: 2716022848 }, EditRandom { seed: 3984196562 }, IgnoredBurst { count: 13, seed: 3730558620 }, EditTracked { seed: 2661261439 }, EditTracked { seed: 2925705555 }, Verify, Verify, EditTracked { seed: 4021944889 }, CreateFile { seed: 1216886816 }, Verify, IgnoredBurst { count: 12, seed: 1799542122 }, DeleteTracked, EditRandom { seed: 613987795 }, CreateFile { seed: 1658013150 }, EditRandom { seed: 4140043948 }, EditRandom { seed: 765648248 }, CreateFile { seed: 2609875282 }, EditRandom { seed: 3319858271 }, CreateFile { seed: 2887454198 }, RevertTracked, EditRandom { seed: 2784515957 }, EditRandom { seed: 3602498417 }, Verify, RevertTracked, EditTracked { seed: 3923974619 }, Verify, IgnoredBurst { count: 7, seed: 330814107 }, EditTracked { seed: 3180111664 }, RevertTracked, Verify, EditTracked { seed: 2951386327 }, EditRandom { seed: 3639523727 }, CreateFile { seed: 869675643 }, Verify, EditTracked { seed: 2457361631 }, EditTracked { seed: 2131481382 }, Verify, EditTracked { seed: 976959385 }, Verify, RevertTracked, Verify, Verify, CreateFile { seed: 3046411249 }, Verify, IgnoredBurst { count: 9, seed: 4057256828 }, EditRandom { seed: 2486674547 }, Verify, EditTracked { seed: 4005659397 }, EditRandom { seed: 187353431 }, DeleteTracked, IgnoredBurst { count: 4, seed: 1705845615 }, IgnoredBurst { count: 13, seed: 715928698 }, DeleteTracked, Verify, Verify, Verify, EditTracked { seed: 434107973 }, Verify, IgnoredBurst { count: 8, seed: 1330520255 }, EditRandom { seed: 577680838 }, DeleteTracked, IgnoredBurst { count: 13, seed: 2572052106 }, CreateFile { seed: 1696727672 }, RevertTracked, EditRandom { seed: 3244995465 }, EditTracked { seed: 2800898077 }, RevertTracked, IgnoredBurst { count: 14, seed: 1565410589 }, Verify, CreateFile { seed: 2943907148 }, DeleteTracked, EditTracked { seed: 2646376363 }, Verify, EditRandom { seed: 3036192888 }, Verify, Verify, CreateFile { seed: 2469990092 }, EditRandom { seed: 2978934652 }, IgnoredBurst { count: 2, seed: 2188722887 }, EditTracked { seed: 1404626711 }, EditTracked { seed: 293022366 }, EditRandom { seed: 3512422701 }, CreateFile { seed: 2188607306 }, RevertTracked, EditTracked { seed: 2949408700 }, Verify, CreateFile { seed: 1898823355 }, RevertTracked, IgnoredBurst { count: 1, seed: 415191658 }, EditTracked { seed: 1379253955 }, CreateFile { seed: 205139567 }, DeleteTracked, Verify, EditTracked { seed: 1475569649 }, Verify, Verify, IgnoredBurst { count: 4, seed: 2175087566 }, CreateFile { seed: 1869660380 }, EditTracked { seed: 2445065907 }, EditRandom { seed: 1297287114 }, CreateFile { seed: 1591017483 }, Verify, RevertTracked, Verify, EditTracked { seed: 4250852275 }, EditRandom { seed: 3420951535 }, CreateFile { seed: 441887310 }, Verify, CreateFile { seed: 912190474 }, CreateFile { seed: 4204393654 }, CreateFile { seed: 190294792 }, EditRandom { seed: 1797322486 }, IgnoredBurst { count: 16, seed: 1983460563 }, EditRandom { seed: 2869330392 }, Verify, Verify, EditTracked { seed: 2474416608 }, EditTracked { seed: 2950234376 }, EditRandom { seed: 3016870946 }, CreateFile { seed: 3761033320 }, EditRandom { seed: 2572493295 }, Verify, DeleteTracked, DeleteTracked, CreateFile { seed: 879154639 }, RevertTracked, EditTracked { seed: 566787732 }, DeleteTracked, RevertTracked, EditRandom { seed: 1102270291 }, EditRandom { seed: 1464122371 }, IgnoredBurst { count: 11, seed: 1458095823 }, RevertTracked, EditTracked { seed: 3228026905 }, CreateFile { seed: 443749662 }, EditTracked { seed: 3761711397 }, IgnoredBurst { count: 13, seed: 2254170089 }, EditRandom { seed: 946853736 }, Verify, IgnoredBurst { count: 16, seed: 1400991987 }, DeleteTracked, Verify, EditRandom { seed: 1102454881 }, EditRandom { seed: 2965648286 }, CreateFile { seed: 2275451067 }, Verify, RevertTracked, IgnoredBurst { count: 16, seed: 4060283928 }, Verify, EditTracked { seed: 541869992 }, Verify, RevertTracked, Verify, CreateFile { seed: 978823575 }, EditRandom { seed: 1046098564 }, Verify, IgnoredBurst { count: 4, seed: 4247423185 }, RevertTracked, DeleteTracked, Verify, CreateFile { seed: 3072367945 }, IgnoredBurst { count: 5, seed: 4286542781 }, Verify, EditTracked { seed: 2185156042 }, CreateFile { seed: 2633303415 }, EditRandom { seed: 1200407260 }, RevertTracked, Verify, EditTracked { seed: 3764092935 }, Verify, EditRandom { seed: 3993565107 }, RevertTracked, IgnoredBurst { count: 8, seed: 4247583803 }, EditTracked { seed: 764064935 }, Verify, IgnoredBurst { count: 3, seed: 427540255 }, DeleteTracked, EditTracked { seed: 3200654914 }, Verify, CreateFile { seed: 3478313745 }, DeleteTracked, Verify, EditTracked { seed: 621116656 }, Verify, IgnoredBurst { count: 8, seed: 1579392476 }, Verify, EditTracked { seed: 414177081 }, Verify, EditRandom { seed: 221224112 }, EditTracked { seed: 4153706515 }, EditTracked { seed: 3045227977 }, Verify, EditRandom { seed: 2201776860 }, EditTracked { seed: 854449003 }, EditTracked { seed: 2205500297 }, EditRandom { seed: 1792458012 }, DeleteTracked, CreateFile { seed: 3424010010 }, IgnoredBurst { count: 14, seed: 1053904815 }, Verify, Verify, EditTracked { seed: 3485208950 }, DeleteTracked, Verify, EditRandom { seed: 187308236 }, EditRandom { seed: 4013451250 }, Verify, RevertTracked, IgnoredBurst { count: 6, seed: 2875702227 }, Verify, EditTracked { seed: 1296920911 }, EditTracked { seed: 291002508 }, CreateFile { seed: 506226131 }, Verify, CreateFile { seed: 3271713113 }, EditTracked { seed: 2146096155 }, Verify, EditTracked { seed: 3956111552 }, DeleteTracked, EditTracked { seed: 2909615671 }, Verify, IgnoredBurst { count: 1, seed: 3727031870 }, Verify, EditRandom { seed: 364130991 }, EditTracked { seed: 1312620454 }, RevertTracked, EditRandom { seed: 2344142345 }, Verify, RevertTracked, DeleteTracked, EditTracked { seed: 2871097942 }, Verify, EditTracked { seed: 3391171011 }, RevertTracked, CreateFile { seed: 779962761 }, EditTracked { seed: 521727725 }, IgnoredBurst { count: 11, seed: 2963591162 }, CreateFile { seed: 2729936486 }, Verify, CreateFile { seed: 656884245 }, IgnoredBurst { count: 16, seed: 276111816 }, IgnoredBurst { count: 16, seed: 1051645651 }, CreateFile { seed: 169222333 }, Verify, Verify, EditRandom { seed: 3273679269 }, Verify, Verify, EditTracked { seed: 2298741199 }, EditTracked { seed: 3460711926 }, EditTracked { seed: 2552630546 }, RevertTracked, Verify, CreateFile { seed: 2104811303 }, Verify, Verify, EditTracked { seed: 3622390374 }, EditRandom { seed: 1687431801 }, CreateFile { seed: 972620881 }, EditTracked { seed: 3470648145 }, DeleteTracked, EditTracked { seed: 2377088473 }, CreateFile { seed: 574076790 }, EditRandom { seed: 3293958247 }, EditRandom { seed: 1965906926 }, Verify, EditRandom { seed: 3684049561 }, RevertTracked, EditRandom { seed: 831878737 }, DeleteTracked, DeleteTracked, CreateFile { seed: 3771609943 }, DeleteTracked, EditTracked { seed: 3467773777 }, Verify, EditRandom { seed: 1108734627 }, EditRandom { seed: 636568845 }, CreateFile { seed: 908148475 }, EditTracked { seed: 2781804085 }, Verify, EditTracked { seed: 1537491028 }, EditTracked { seed: 3345235088 }, Verify, IgnoredBurst { count: 17, seed: 660298101 }, EditRandom { seed: 3490918256 }, EditTracked { seed: 527328431 }, EditRandom { seed: 3786143098 }, Verify, RevertTracked, Verify, EditTracked { seed: 538972755 }, CreateFile { seed: 3411292422 }, DeleteTracked, Verify, Verify, CreateFile { seed: 1670343000 }, Verify, CreateFile { seed: 3854157427 }, EditTracked { seed: 2132270146 }, EditRandom { seed: 585747811 }, EditRandom { seed: 1067438812 }, CreateFile { seed: 2842991605 }, RevertTracked, EditTracked { seed: 3437840971 }, EditTracked { seed: 1416890816 }, EditRandom { seed: 180177728 }, RevertTracked, Verify, IgnoredBurst { count: 12, seed: 196787470 }, EditRandom { seed: 926497172 }, Verify, RevertTracked, EditRandom { seed: 3016908824 }, Verify, Verify, Verify, EditRandom { seed: 2473410717 }, CreateFile { seed: 2588911915 }, DeleteTracked, IgnoredBurst { count: 16, seed: 1671443891 }, EditRandom { seed: 2899222965 }, EditRandom { seed: 3437102041 }, EditTracked { seed: 1660505442 }, Verify, CreateFile { seed: 1980030743 }, Verify, DeleteTracked, RevertTracked, EditTracked { seed: 4213383365 }, Verify, Verify, IgnoredBurst { count: 18, seed: 1866959486 }, EditRandom { seed: 906433071 }, Verify, RevertTracked, CreateFile { seed: 1354848006 }, CreateFile { seed: 2122890842 }, IgnoredBurst { count: 4, seed: 2944787547 }, EditTracked { seed: 853545580 }, RevertTracked, CreateFile { seed: 570341134 }, IgnoredBurst { count: 12, seed: 4248027101 }, EditTracked { seed: 3959338075 }, Verify, RevertTracked, EditRandom { seed: 1825554916 }, IgnoredBurst { count: 4, seed: 2413101420 }, Verify, EditRandom { seed: 3423767791 }, CreateFile { seed: 1486338580 }, EditRandom { seed: 1019557191 }, CreateFile { seed: 878976235 }, EditRandom { seed: 636852074 }, Verify, Verify, DeleteTracked, CreateFile { seed: 2946302446 }, EditTracked { seed: 836121738 }, CreateFile { seed: 1658335766 }, DeleteTracked, EditTracked { seed: 1650413622 }, Verify, EditRandom { seed: 853416845 }, CreateFile { seed: 3739132611 }, Verify, CreateFile { seed: 658006215 }, EditTracked { seed: 2210563609 }, Verify, RevertTracked, RevertTracked, EditTracked { seed: 913167404 }, IgnoredBurst { count: 18, seed: 3417417595 }, EditTracked { seed: 2520452154 }, EditTracked { seed: 2904246191 }, IgnoredBurst { count: 12, seed: 2179597638 }, RevertTracked, DeleteTracked, DeleteTracked, IgnoredBurst { count: 17, seed: 946825186 }, Verify, IgnoredBurst { count: 16, seed: 2662246565 }, IgnoredBurst { count: 4, seed: 1975237070 }, Verify, EditTracked { seed: 4125498183 }, Verify, RevertTracked, EditRandom { seed: 269387628 }, RevertTracked, EditTracked { seed: 547912836 }, EditTracked { seed: 3826598792 }, CreateFile { seed: 2534052416 }, EditTracked { seed: 1924596928 }, EditTracked { seed: 3241272543 }, EditRandom { seed: 2693571079 }, EditRandom { seed: 680678920 }, Verify, CreateFile { seed: 2929370980 }, EditRandom { seed: 62474154 }, EditTracked { seed: 2333194885 }, CreateFile { seed: 3602762209 }, DeleteTracked, DeleteTracked, RevertTracked, Verify, Verify, CreateFile { seed: 2419934392 }, EditRandom { seed: 3392361341 }, Verify, CreateFile { seed: 2315145468 }, Verify, EditRandom { seed: 4082510829 }, EditRandom { seed: 2670016769 }, CreateFile { seed: 372946636 }, IgnoredBurst { count: 1, seed: 3503961121 }, EditRandom { seed: 2623647210 }, Verify, EditRandom { seed: 613162070 }, Verify, EditTracked { seed: 392521965 }, Verify, Verify, EditRandom { seed: 3971027359 }, CreateFile { seed: 2413917220 }, EditTracked { seed: 2457512291 }, EditRandom { seed: 3045165889 }, EditRandom { seed: 846514065 }, EditTracked { seed: 1718690337 }, RevertTracked, Verify, IgnoredBurst { count: 6, seed: 2885715679 }, Verify, EditRandom { seed: 3707696231 }, RevertTracked, Verify, Verify, EditTracked { seed: 3910481912 }, RevertTracked, Verify, Verify, Verify, CreateFile { seed: 2647237027 }, CreateFile { seed: 154550653 }, Verify, DeleteTracked, EditTracked { seed: 3160016772 }, EditTracked { seed: 3777969260 }, EditRandom { seed: 386540721 }, RevertTracked, Verify, CreateFile { seed: 1246336374 }, EditTracked { seed: 2844303427 }, EditRandom { seed: 1094520810 }, Verify, Verify, CreateFile { seed: 1059724883 }, RevertTracked, Verify, CreateFile { seed: 3433364675 }, EditTracked { seed: 360509448 }, Verify, RevertTracked, RevertTracked, CreateFile { seed: 2213268263 }, Verify, Verify, Verify, Verify, EditTracked { seed: 4152429785 }, IgnoredBurst { count: 6, seed: 381853723 }, EditRandom { seed: 1962783045 }, Verify, EditRandom { seed: 1665382956 }, EditRandom { seed: 2709955475 }, RevertTracked, RevertTracked, EditTracked { seed: 3271796791 }, Verify, Verify, EditTracked { seed: 3824419611 }, EditRandom { seed: 2812898222 }, EditRandom { seed: 2689333177 }, EditRandom { seed: 122504226 }, Verify, EditTracked { seed: 928034260 }, EditRandom { seed: 3150634864 }, RevertTracked, IgnoredBurst { count: 3, seed: 138702265 }, EditTracked { seed: 1953792973 }, Verify, Verify, Verify, EditTracked { seed: 3139680678 }, DeleteTracked, CreateFile { seed: 2671266006 }, CreateFile { seed: 3798669949 }, RevertTracked, Verify, CreateFile { seed: 1751116722 }, CreateFile { seed: 1367295218 }, EditRandom { seed: 689124306 }, IgnoredBurst { count: 5, seed: 1057474153 }, EditRandom { seed: 3654782391 }, EditRandom { seed: 905410224 }, EditRandom { seed: 650131199 }, CreateFile { seed: 1644459558 }, CreateFile { seed: 3886687299 }, EditRandom { seed: 2499775116 }, RevertTracked, DeleteTracked, DeleteTracked, IgnoredBurst { count: 11, seed: 734941190 }, RevertTracked, RevertTracked, IgnoredBurst { count: 17, seed: 3297250774 }, CreateFile { seed: 825440208 }, CreateFile { seed: 2389128304 }, Verify, Verify, EditTracked { seed: 3719945320 }, Verify, CreateFile { seed: 3121352196 }, EditTracked { seed: 568124310 }, EditTracked { seed: 2547898740 }, EditRandom { seed: 664131757 }, EditRandom { seed: 2209103600 }, EditRandom { seed: 2122119482 }, Verify, IgnoredBurst { count: 3, seed: 1674489070 }, EditRandom { seed: 298454037 }, Verify, EditRandom { seed: 937087471 }, CreateFile { seed: 667119880 }, Verify, Verify, EditTracked { seed: 4140605495 }, EditRandom { seed: 907239544 }, RevertTracked, IgnoredBurst { count: 16, seed: 2754839097 }, EditTracked { seed: 2855478163 }, Verify, DeleteTracked, EditTracked { seed: 306432568 }, EditTracked { seed: 256510315 }, DeleteTracked, Verify, EditTracked { seed: 746220962 }, EditTracked { seed: 757599865 }, DeleteTracked, IgnoredBurst { count: 11, seed: 2160096413 }, EditRandom { seed: 3082716150 }, Verify, Verify, EditRandom { seed: 3620166526 }, Verify, IgnoredBurst { count: 14, seed: 2001536715 }, EditRandom { seed: 1502961340 }, EditRandom { seed: 3266625643 }, IgnoredBurst { count: 13, seed: 202899898 }, IgnoredBurst { count: 10, seed: 3407140075 }, Verify, EditRandom { seed: 1173910848 }, IgnoredBurst { count: 13, seed: 263215691 }, EditTracked { seed: 3955125598 }, EditTracked { seed: 3599717689 }, EditRandom { seed: 2927094252 }, Verify, Verify, IgnoredBurst { count: 13, seed: 35568006 }, Verify, Verify, EditTracked { seed: 2738769783 }, Verify, DeleteTracked, EditRandom { seed: 3923140387 }, DeleteTracked, EditTracked { seed: 822626989 }, Verify, Verify, EditRandom { seed: 2882342013 }, EditRandom { seed: 485956923 }, CreateFile { seed: 861742348 }, RevertTracked, EditRandom { seed: 492042665 }, EditTracked { seed: 585247444 }, EditRandom { seed: 4009444343 }, EditRandom { seed: 3860326443 }, EditRandom { seed: 246620796 }, EditRandom { seed: 1800926521 }, EditTracked { seed: 163090557 }, EditTracked { seed: 3206994326 }, Verify, RevertTracked, EditTracked { seed: 32160828 }, Verify, EditRandom { seed: 216975427 }, EditTracked { seed: 2151294415 }, Verify, Verify, IgnoredBurst { count: 17, seed: 4193562270 }, EditTracked { seed: 2346409672 }, EditRandom { seed: 2146273073 }, CreateFile { seed: 3814649567 }, EditTracked { seed: 2293264676 }, Verify, IgnoredBurst { count: 6, seed: 556908727 }, EditTracked { seed: 815136352 }, EditRandom { seed: 534097288 }, EditRandom { seed: 4206964754 }, RevertTracked, RevertTracked, CreateFile { seed: 1672388267 }, IgnoredBurst { count: 14, seed: 2106323223 }, Verify, EditRandom { seed: 4034317240 }, RevertTracked, Verify, Verify, CreateFile { seed: 664034020 }, EditTracked { seed: 2468485008 }, EditTracked { seed: 2963811379 }, IgnoredBurst { count: 4, seed: 3482405570 }, EditTracked { seed: 844543737 }, Verify, RevertTracked, CreateFile { seed: 2706694030 }, EditTracked { seed: 829300683 }, EditTracked { seed: 1825525759 }, Verify, RevertTracked, CreateFile { seed: 700629431 }, EditTracked { seed: 1020266595 }, Verify, RevertTracked, Verify, RevertTracked, Verify, IgnoredBurst { count: 6, seed: 1441230570 }, DeleteTracked, EditTracked { seed: 505269583 }, EditRandom { seed: 1762669515 }, RevertTracked, EditRandom { seed: 3932299170 }, CreateFile { seed: 202967737 }, IgnoredBurst { count: 3, seed: 853745304 }, EditTracked { seed: 3512894440 }, RevertTracked, EditRandom { seed: 1740737793 }, Verify, EditRandom { seed: 2045064956 }, IgnoredBurst { count: 12, seed: 4149572720 }, EditTracked { seed: 4201940131 }, CreateFile { seed: 1207901796 }, Verify, RevertTracked, Verify, EditTracked { seed: 951304066 }, CreateFile { seed: 2341411362 }, EditRandom { seed: 772262634 }, EditRandom { seed: 4148708385 }, IgnoredBurst { count: 13, seed: 764892389 }, EditTracked { seed: 1670413201 }, EditTracked { seed: 917463944 }, CreateFile { seed: 2795796280 }, EditRandom { seed: 2213125394 }, IgnoredBurst { count: 3, seed: 596798658 }, EditTracked { seed: 3997764374 }, EditTracked { seed: 2284564959 }, EditTracked { seed: 2178105257 }, CreateFile { seed: 1140107653 }, EditRandom { seed: 525123852 }, EditRandom { seed: 3917131157 }, EditRandom { seed: 3744551257 }, RevertTracked, CreateFile { seed: 1379408053 }, Verify, Verify, EditRandom { seed: 1081136646 }, DeleteTracked, EditTracked { seed: 1330472276 }, Verify, EditRandom { seed: 281179811 }, Verify, EditTracked { seed: 2045581787 }, DeleteTracked, EditRandom { seed: 3273155612 }, DeleteTracked, EditRandom { seed: 1695472697 }, EditRandom { seed: 257937003 }, Verify, EditRandom { seed: 3861307694 }, EditTracked { seed: 111190497 }, IgnoredBurst { count: 6, seed: 3522403470 }, EditRandom { seed: 1648818862 }, Verify, Verify, RevertTracked, Verify, IgnoredBurst { count: 11, seed: 3281563920 }, Verify, Verify, EditTracked { seed: 1584012553 }, DeleteTracked, EditRandom { seed: 1902833174 }, Verify, DeleteTracked, CreateFile { seed: 176916229 }, Verify, EditRandom { seed: 3339928290 }, Verify, CreateFile { seed: 2918766702 }, RevertTracked, EditTracked { seed: 1102055727 }, DeleteTracked, RevertTracked, EditRandom { seed: 2519140580 }, EditRandom { seed: 3257459587 }, Verify, EditRandom { seed: 4120041633 }, EditTracked { seed: 414351915 }, EditRandom { seed: 4052048038 }, EditTracked { seed: 1672775475 }, CreateFile { seed: 4045750635 }, Verify, DeleteTracked, IgnoredBurst { count: 11, seed: 2200913013 }, EditTracked { seed: 1455308882 }, DeleteTracked, Verify, DeleteTracked, EditTracked { seed: 2263201844 }, EditRandom { seed: 1649542395 }, CreateFile { seed: 2236719406 }, CreateFile { seed: 2683205360 }, RevertTracked, Verify, RevertTracked, CreateFile { seed: 594419243 }, EditRandom { seed: 3264041697 }, EditRandom { seed: 3708333463 }, CreateFile { seed: 1066002394 }, IgnoredBurst { count: 16, seed: 766551587 }, Verify, DeleteTracked, EditRandom { seed: 2912753053 }, EditTracked { seed: 1960938336 }, RevertTracked, DeleteTracked, Verify, Verify, EditRandom { seed: 469573721 }, RevertTracked, CreateFile { seed: 2411710260 }, CreateFile { seed: 2825262381 }, Verify, CreateFile { seed: 696597544 }, EditTracked { seed: 113378479 }, EditTracked { seed: 2079922246 }, Verify, EditTracked { seed: 389922696 }, EditTracked { seed: 119484527 }, RevertTracked, EditRandom { seed: 4080248257 }, EditRandom { seed: 282117367 }, RevertTracked, Verify, Verify, EditTracked { seed: 3816149941 }, RevertTracked, EditTracked { seed: 2580541526 }, EditRandom { seed: 931526831 }, DeleteTracked, IgnoredBurst { count: 2, seed: 2250707158 }, RevertTracked, EditTracked { seed: 72334749 }, DeleteTracked, CreateFile { seed: 4254515667 }, Verify, EditTracked { seed: 2977825108 }, CreateFile { seed: 2118623053 }, Verify, EditRandom { seed: 1752472528 }, DeleteTracked, Verify, EditRandom { seed: 927104512 }, Verify, Verify, EditTracked { seed: 3900220506 }, CreateFile { seed: 2507457466 }, Verify, Verify, RevertTracked, EditTracked { seed: 3828752965 }, EditRandom { seed: 3515611587 }, EditRandom { seed: 3949720206 }, RevertTracked, Verify, DeleteTracked, Verify, EditRandom { seed: 4087835278 }, DeleteTracked, Verify, Verify, Verify, RevertTracked, RevertTracked, EditRandom { seed: 596424732 }, EditTracked { seed: 1140716885 }, Verify, EditTracked { seed: 558373333 }, RevertTracked, EditTracked { seed: 2026552879 }, Verify, EditTracked { seed: 645364163 }, RevertTracked, DeleteTracked, EditTracked { seed: 4217761095 }, Verify, RevertTracked, EditRandom { seed: 2482782805 }, EditRandom { seed: 2613240720 }, EditTracked { seed: 834291878 }, EditTracked { seed: 4039197305 }, RevertTracked, RevertTracked, EditRandom { seed: 1474885024 }, EditTracked { seed: 3957783681 }, EditRandom { seed: 2642243967 }, Verify, EditTracked { seed: 3492681076 }, EditTracked { seed: 860448737 }, EditRandom { seed: 1922334772 }, Verify, CreateFile { seed: 963656693 }, EditTracked { seed: 224488887 }, CreateFile { seed: 3902106102 }, Verify, EditRandom { seed: 2723002705 }, Verify, Verify, CreateFile { seed: 2754356156 }, RevertTracked, Verify, EditRandom { seed: 1126492744 }, Verify, EditTracked { seed: 1706446609 }, Verify, EditRandom { seed: 4116800247 }, CreateFile { seed: 2387839606 }, DeleteTracked, CreateFile { seed: 1607764878 }, DeleteTracked, EditRandom { seed: 223778629 }, Verify, IgnoredBurst { count: 2, seed: 1176689445 }, EditRandom { seed: 808391150 }, CreateFile { seed: 1901698439 }, IgnoredBurst { count: 5, seed: 728641430 }, CreateFile { seed: 1907099913 }, EditTracked { seed: 2537863683 }, IgnoredBurst { count: 8, seed: 1023238415 }, EditRandom { seed: 4041706823 }, EditRandom { seed: 1602784916 }, EditTracked { seed: 1549314113 }, EditTracked { seed: 4033324071 }, EditTracked { seed: 2182470899 }, EditRandom { seed: 2276047190 }, Verify, DeleteTracked, EditRandom { seed: 927506698 }, EditTracked { seed: 3149375890 }, Verify, Verify, EditRandom { seed: 4277504923 }, RevertTracked, EditTracked { seed: 1975228644 }, CreateFile { seed: 2082003949 }, DeleteTracked, RevertTracked, EditRandom { seed: 3830488050 }, EditRandom { seed: 1076924959 }, EditTracked { seed: 1386713996 }, EditTracked { seed: 3210991949 }, EditTracked { seed: 3078541723 }, EditRandom { seed: 2343918484 }, EditRandom { seed: 3635830388 }, EditTracked { seed: 2434224085 }, EditRandom { seed: 3520012572 }, IgnoredBurst { count: 6, seed: 392731601 }, Verify, DeleteTracked, RevertTracked, RevertTracked, RevertTracked, EditTracked { seed: 1220306616 }, IgnoredBurst { count: 7, seed: 481148563 }, RevertTracked, RevertTracked, EditRandom { seed: 2546373421 }, Verify, DeleteTracked, RevertTracked, Verify, RevertTracked, Verify, Verify, IgnoredBurst { count: 4, seed: 1743420290 }, EditRandom { seed: 1671286671 }, EditRandom { seed: 3302143154 }, Verify, Verify, Verify, IgnoredBurst { count: 4, seed: 2099457533 }, IgnoredBurst { count: 14, seed: 3096449830 }, Verify, EditRandom { seed: 995587542 }, Verify, CreateFile { seed: 4239505407 }, DeleteTracked, EditTracked { seed: 3604289508 }, EditRandom { seed: 1916994892 }, IgnoredBurst { count: 13, seed: 1880112792 }, Verify, EditRandom { seed: 3422881599 }, CreateFile { seed: 484865746 }, Verify, CreateFile { seed: 3651205812 }, Verify, RevertTracked, RevertTracked, EditTracked { seed: 972766116 }, EditTracked { seed: 1207515475 }, EditRandom { seed: 3201008215 }, EditRandom { seed: 3479803416 }, IgnoredBurst { count: 10, seed: 1399322284 }, Verify, RevertTracked, EditRandom { seed: 3887992924 }, RevertTracked, EditRandom { seed: 418702839 }, Verify, RevertTracked, Verify, EditTracked { seed: 1065210807 }, Verify, RevertTracked, Verify, EditRandom { seed: 826912833 }, Verify, EditTracked { seed: 2872237440 }, CreateFile { seed: 2563313293 }, EditTracked { seed: 852514814 }, EditTracked { seed: 3478661577 }, EditTracked { seed: 3286523340 }, EditRandom { seed: 4123252795 }, EditRandom { seed: 1952651556 }, Verify, DeleteTracked, EditRandom { seed: 1527255102 }, EditRandom { seed: 3567122844 }, Verify, EditTracked { seed: 3588747821 }, CreateFile { seed: 1195654490 }, Verify, Verify, EditTracked { seed: 3472290727 }, Verify, RevertTracked, EditTracked { seed: 2941042700 }, CreateFile { seed: 2287920678 }, CreateFile { seed: 3550162613 }, EditTracked { seed: 3631399778 }, EditTracked { seed: 2128456710 }, EditRandom { seed: 3521715884 }, EditRandom { seed: 955894777 }, CreateFile { seed: 1908433814 }, EditRandom { seed: 1869362800 }, RevertTracked, EditRandom { seed: 2644729877 }, Verify, Verify, IgnoredBurst { count: 1, seed: 1175536885 }, Verify, EditRandom { seed: 2445799630 }, EditTracked { seed: 3448441725 }, Verify, RevertTracked, EditTracked { seed: 2683709987 }, IgnoredBurst { count: 2, seed: 3536509227 }, IgnoredBurst { count: 1, seed: 2527940078 }, RevertTracked, EditTracked { seed: 895354678 }, CreateFile { seed: 99616131 }, EditRandom { seed: 113656732 }, EditRandom { seed: 320283757 }, IgnoredBurst { count: 17, seed: 1979448930 }, EditRandom { seed: 1810766813 }, IgnoredBurst { count: 17, seed: 1897616006 }, Verify, EditTracked { seed: 3513974371 }, DeleteTracked, Verify, Verify, EditRandom { seed: 3520385208 }, DeleteTracked, EditRandom { seed: 2158730202 }, EditTracked { seed: 1113953409 }, DeleteTracked, Verify, EditTracked { seed: 1700902146 }, CreateFile { seed: 1512862897 }, CreateFile { seed: 2140989124 }, EditTracked { seed: 4091224770 }, RevertTracked, IgnoredBurst { count: 7, seed: 1907437660 }, CreateFile { seed: 3001916767 }, EditRandom { seed: 297303762 }, EditRandom { seed: 2311955576 }, DeleteTracked, CreateFile { seed: 3985453137 }, Verify, Verify, Verify, RevertTracked, EditRandom { seed: 1809874124 }, Verify, RevertTracked, EditTracked { seed: 2235849743 }, Verify, EditRandom { seed: 1735992863 }, RevertTracked, EditTracked { seed: 2112545184 }, EditTracked { seed: 2348992401 }, RevertTracked, Verify, RevertTracked, CreateFile { seed: 3818034267 }, Verify, EditTracked { seed: 1840036803 }, CreateFile { seed: 2403107875 }, IgnoredBurst { count: 13, seed: 2506272199 }, CreateFile { seed: 3865626955 }, EditTracked { seed: 2826614713 }, EditTracked { seed: 3363985451 }, EditRandom { seed: 1212975417 }, EditRandom { seed: 2152041037 }, EditTracked { seed: 793681097 }, EditTracked { seed: 1997627309 }, EditTracked { seed: 3071923675 }, Verify, EditTracked { seed: 1799630046 }, EditRandom { seed: 884045638 }, CreateFile { seed: 1145072754 }, EditRandom { seed: 3990868217 }, Verify, EditTracked { seed: 115769826 }, DeleteTracked, CreateFile { seed: 1294002809 }, CreateFile { seed: 4127199573 }, RevertTracked, Verify, EditTracked { seed: 2704632093 }, IgnoredBurst { count: 11, seed: 1756693692 }, Verify, IgnoredBurst { count: 5, seed: 1127075575 }, Verify, Verify, EditTracked { seed: 240115701 }, Verify, Verify, EditRandom { seed: 349322801 }, EditRandom { seed: 621026824 }, RevertTracked, IgnoredBurst { count: 4, seed: 1287979947 }, Verify, Verify, Verify, Verify, EditRandom { seed: 249751148 }, EditTracked { seed: 57306234 }, Verify, RevertTracked, RevertTracked, IgnoredBurst { count: 10, seed: 4248317531 }, EditRandom { seed: 356090260 }, EditTracked { seed: 2360822711 }, Verify, DeleteTracked, CreateFile { seed: 2579991617 }, EditRandom { seed: 3746166339 }, Verify, EditTracked { seed: 1149677200 }, EditTracked { seed: 1908760634 }, EditRandom { seed: 422070904 }, CreateFile { seed: 2276869479 }, EditRandom { seed: 2352413156 }, RevertTracked, EditRandom { seed: 1730733757 }, IgnoredBurst { count: 12, seed: 3274703388 }, IgnoredBurst { count: 18, seed: 1187502331 }, RevertTracked, Verify, Verify, CreateFile { seed: 3281095308 }, Verify, EditTracked { seed: 100517685 }, DeleteTracked, EditRandom { seed: 2857109687 }, DeleteTracked, EditRandom { seed: 3573073694 }, EditRandom { seed: 2588775720 }, EditTracked { seed: 3409550332 }, RevertTracked, EditRandom { seed: 3476087259 }, CreateFile { seed: 1810301050 }, DeleteTracked, CreateFile { seed: 4248530530 }, RevertTracked, Verify, CreateFile { seed: 4082784410 }] diff --git a/crates/fff-core/tests/fuzz_real_repos.rs b/crates/fff-core/tests/fuzz_real_repos.rs new file mode 100644 index 0000000..b06851a --- /dev/null +++ b/crates/fff-core/tests/fuzz_real_repos.rs @@ -0,0 +1,712 @@ +//! Proptest-driven fuzz test against real GitHub repos with a live watcher. +//! +//! Clones real repository, runs the simulated close to real user sereies of file system ewvents and +//! verifies that fff can still find the correct files. Test cases are randomized and preserved +//! using proptest +#![cfg(stress)] + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use proptest::prelude::*; +use proptest::test_runner::{Config as ProptestConfig, FileFailurePersistence}; + +use fff_search::file_picker::{FFFMode, FilePicker, is_known_binary_extension}; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{FilePickerOptions, SharedFilePicker, SharedFrecency}; + +const REPO_POOL: &[(&str, &str)] = &[ + ("dmtrKovalenko/fff", "fff"), + ("BurntSushi/ripgrep", "ripgrep"), + ("sharkdp/fd", "fd"), + ("ogham/exa", "exa"), + ("casey/just", "just"), + ("ajeetdsouza/zoxide", "zoxide"), + ("helix-editor/helix", "helix"), + ("astral-sh/ruff", "ruff"), + ("biomejs/biome", "biome"), + ("denoland/deno_lint", "deno_lint"), + ("nickel-lang/nickel", "nickel"), + ("typst/typst", "typst"), + ("gleam-lang/gleam", "gleam"), + ("pretzelhammer/rust-blog", "rust-blog"), + ("tokio-rs/mini-redis", "mini-redis"), +]; + +const CACHE_DIR: &str = "/tmp/fff_fuzz_repos"; +/// Fixed settle time for watcher event propagation. +const WATCHER_SETTLE: Duration = Duration::from_millis(100); +/// Maximum time to wait for watcher to process all pending events. +const CONVERGE_TIMEOUT: Duration = Duration::from_secs(30); + +fn fuzz_cases() -> u32 { + std::env::var("FFF_FUZZ_CASES") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(2) +} + +fn fuzz_max_ops() -> usize { + std::env::var("FFF_FUZZ_MAX_OPS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(30) +} + +fn fuzz_min_ops() -> usize { + std::env::var("FFF_FUZZ_MIN_OPS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(15) +} + +fn ensure_repo_cloned(repo_url: &str, local_name: &str) -> PathBuf { + let cache = PathBuf::from(CACHE_DIR); + fs::create_dir_all(&cache).unwrap(); + let repo_path = cache.join(local_name); + if repo_path.join(".git").exists() { + return repo_path; + } + + let full_url = format!("https://github.com/{}.git", repo_url); + eprintln!(" Cloning {} ...", full_url); + let out = Command::new("git") + .args(["clone", "--depth=1", "--single-branch", &full_url]) + .arg(&repo_path) + .output() + .expect("git clone failed"); + assert!( + out.status.success(), + "git clone {} failed: {}", + full_url, + String::from_utf8_lossy(&out.stderr) + ); + repo_path +} + +fn copy_repo_to_workdir(cached: &Path, workdir: &Path) { + let out = Command::new("cp") + .args(["-r"]) + .arg(cached) + .arg(workdir) + .output() + .expect("cp -r failed"); + assert!( + out.status.success(), + "cp -r failed: {}", + String::from_utf8_lossy(&out.stderr) + ); +} + +fn collect_text_files(base: &Path) -> Vec { + // Use `git ls-files` without --cached to get only files that are both + // tracked AND not gitignored. Files like Cargo.lock that are committed + // but in .gitignore would appear with --cached but the fff picker skips + // them during walk (respects .gitignore), causing false test failures. + let out = Command::new("git") + .args(["ls-files", "--others", "--exclude-standard", "-z"]) + .current_dir(base) + .output() + .unwrap(); + + // Get tracked files that aren't ignored + let tracked = Command::new("git") + .args(["ls-files", "-z"]) + .current_dir(base) + .output() + .unwrap(); + + // Check which tracked files are actually ignored + let ignored_check = Command::new("git") + .args(["check-ignore", "--stdin", "-z"]) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .current_dir(base) + .spawn(); + + let mut ignored_set: std::collections::HashSet = std::collections::HashSet::new(); + if let Ok(mut child) = ignored_check { + use std::io::Write; + if let Some(ref mut stdin) = child.stdin { + let _ = stdin.write_all(&tracked.stdout); + } + if let Ok(output) = child.wait_with_output() { + for path in output.stdout.split(|&b| b == 0) { + if !path.is_empty() { + if let Ok(s) = std::str::from_utf8(path) { + ignored_set.insert(s.to_string()); + } + } + } + } + } + + // Combine: tracked non-ignored non-binary files + let mut files: Vec = Vec::new(); + for path in tracked.stdout.split(|&b| b == 0) { + if path.is_empty() { + continue; + } + let Ok(s) = std::str::from_utf8(path) else { + continue; + }; + if ignored_set.contains(s) { + continue; + } + let full = base.join(s); + if full.is_file() && !is_known_binary_extension(&full) { + files.push(full); + } + } + files +} + +/// Edit a file by injecting a marker line at a deterministic position, +/// preserving the rest of the content. Returns the original line that was +/// replaced so it can be restored on revert. +fn inject_marker(path: &Path, marker: &str, seed: u32) -> Option { + let content = fs::read_to_string(path).ok()?; + let lines: Vec<&str> = content.lines().collect(); + if lines.is_empty() { + fs::write(path, format!("// {marker}\n")).ok()?; + return Some(String::new()); + } + + // Pick a stable line position based on seed and file length + let line_idx = seed as usize % lines.len(); + let original_line = lines[line_idx].to_string(); + + let mut result = String::with_capacity(content.len() + marker.len() + 10); + for (i, line) in lines.iter().enumerate() { + if i == line_idx { + result.push_str(&format!("// {marker}")); + } else { + result.push_str(line); + } + result.push('\n'); + } + fs::write(path, &result).ok()?; + Some(original_line) +} + +/// Revert a file by restoring the original line at the same position +/// where inject_marker placed the marker. +fn revert_marker(path: &Path, marker: &str, original_line: &str) { + let Ok(content) = fs::read_to_string(path) else { + return; + }; + let marker_line = format!("// {marker}"); + let result: String = content + .lines() + .map(|l| if l == marker_line { original_line } else { l }) + .collect::>() + .join("\n") + + "\n"; + let _ = fs::write(path, result); +} + +fn grep_opts(mode: GrepMode) -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 500, + mode, + time_budget_ms: 5000, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +fn grep_finds(picker: &FilePicker, query: &str, mode: GrepMode) -> bool { + let parsed = parse_grep_query(query); + let result = picker.grep(&parsed, &grep_opts(mode)); + !result.matches.is_empty() +} + +fn grep_file_list(picker: &FilePicker, query: &str, mode: GrepMode) -> Vec { + let parsed = parse_grep_query(query); + let result = picker.grep(&parsed, &grep_opts(mode)); + result + .files + .iter() + .map(|f| f.relative_path(picker)) + .collect() +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Infrastructure +// ═══════════════════════════════════════════════════════════════════════════ + +fn wait_for_bigram(sp: &SharedFilePicker) { + let deadline = Instant::now() + Duration::from_secs(120); + loop { + std::thread::sleep(Duration::from_millis(50)); + let ready = sp + .read() + .ok() + .map(|g| { + g.as_ref() + .map_or(false, |p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + return; + } + assert!( + Instant::now() < deadline, + "Timed out waiting for bigram index" + ); + } +} + +fn epoch_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +} + +struct TrackedFile { + relative: String, + marker: String, + /// The original line content that was replaced, for revert + original_line: String, + is_created: bool, + last_write_sec: u64, +} + +fn run_scenario(ops: &[Op]) { + // Stream fff logs at info+ level by default. Override with RUST_LOG. + let _ = tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn,fff_search=info")), + ) + .with_test_writer() + .try_init(); + + // Allow forcing a specific repo via env for reproduction + let repo_idx = std::env::var("FFF_FUZZ_REPO_IDX") + .ok() + .and_then(|v| v.parse::().ok()) + .unwrap_or_else(|| ops.len() % REPO_POOL.len()); + let (repo_url, local_name) = REPO_POOL[repo_idx]; + eprintln!("=== fuzz_real_repos: repo={repo_url} ops={} ===", ops.len()); + + let scenario_start = Instant::now(); + let cached = ensure_repo_cloned(repo_url, local_name); + let tmp = tempfile::TempDir::new().unwrap(); + let workdir = tmp.path().join(local_name); + copy_repo_to_workdir(&cached, &workdir); + + // Ensure target/ is gitignored + let gitignore = workdir.join(".gitignore"); + let mut gi = fs::read_to_string(&gitignore).unwrap_or_default(); + if !gi.contains("target/") { + gi.push_str("\ntarget/\n"); + fs::write(&gitignore, &gi).unwrap(); + } + + let shared_picker = SharedFilePicker::default(); + FilePicker::new_with_shared_state( + shared_picker.clone(), + SharedFrecency::noop(), + FilePickerOptions { + base_path: workdir.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + watch: true, + mode: FFFMode::Neovim, + ..Default::default() + }, + ) + .expect("FilePicker init"); + + let t0 = Instant::now(); + wait_for_bigram(&shared_picker); + let bigram_ms = t0.elapsed().as_secs_f64() * 1000.0; + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let file_count = picker.get_files().len(); + eprintln!(" indexed {file_count} files, bigram ready in {bigram_ms:.0}ms"); + } + + // Advance mtime past scan timestamp + std::thread::sleep(Duration::from_millis(1100)); + + let mut tracked: Vec = Vec::new(); + let mut dead_markers: Vec = Vec::new(); + let mut ignored_markers: Vec = Vec::new(); + let mut ops_since_verify: usize = 0; + let mut text_files: Option> = None; + + for (op_idx, op) in ops.iter().enumerate() { + match op { + Op::CreateFile { seed } => { + let name = format!("fff_fuzz_new_{seed:08x}.rs"); + let marker = format!("FFF_FUZZ_NEW_{seed:08x}"); + // Marker appears only once on its own line + let content = format!("// {marker}\nfn placeholder() {{}}\n"); + fs::write(workdir.join(&name), content).unwrap(); + tracked.push(TrackedFile { + relative: name, + marker, + original_line: String::new(), + is_created: true, + last_write_sec: epoch_secs(), + }); + ops_since_verify += 1; + } + Op::EditTracked { seed } => { + if tracked.is_empty() { + continue; + } + let idx = *seed as usize % tracked.len(); + if tracked[idx].last_write_sec >= epoch_secs() { + std::thread::sleep(Duration::from_millis(1100)); + } + let new_marker = format!("FFF_FUZZ_EDIT_{seed:08x}"); + let path = workdir.join(&tracked[idx].relative); + // Replace the line containing our old marker with the new one + let old_marker_line = format!("// {}", tracked[idx].marker); + let content = fs::read_to_string(&path).unwrap_or_default(); + let new_content = content + .lines() + .map(|l| { + if l == old_marker_line { + format!("// {new_marker}") + } else { + l.to_string() + } + }) + .collect::>() + .join("\n") + + "\n"; + fs::write(&path, new_content).unwrap(); + + dead_markers.push(tracked[idx].marker.clone()); + tracked[idx].marker = new_marker; + tracked[idx].last_write_sec = epoch_secs(); + ops_since_verify += 1; + } + Op::EditRandom { seed } => { + let files = text_files.get_or_insert_with(|| collect_text_files(&workdir)); + if files.is_empty() { + continue; + } + let target = &files[*seed as usize % files.len()]; + let relative = target + .strip_prefix(&workdir) + .unwrap() + .to_string_lossy() + .to_string(); + + if let Some(t) = tracked.iter().find(|t| t.relative == relative) { + if t.last_write_sec >= epoch_secs() { + std::thread::sleep(Duration::from_millis(1100)); + } + } + + let marker = format!("FFF_FUZZ_RAND_{seed:08x}"); + + // If already tracked, replace old marker line + if let Some(pos) = tracked.iter().position(|t| t.relative == relative) { + let old_marker_line = format!("// {}", tracked[pos].marker); + let content = fs::read_to_string(target).unwrap_or_default(); + let new_content = content + .lines() + .map(|l| { + if l == old_marker_line { + format!("// {marker}") + } else { + l.to_string() + } + }) + .collect::>() + .join("\n") + + "\n"; + fs::write(target, new_content).unwrap(); + dead_markers.push(tracked[pos].marker.clone()); + tracked[pos].marker = marker; + tracked[pos].last_write_sec = epoch_secs(); + } else { + // First edit: inject marker at a deterministic line + let original = inject_marker(target, &marker, *seed).unwrap_or_default(); + tracked.push(TrackedFile { + relative, + marker, + original_line: original, + is_created: false, + last_write_sec: epoch_secs(), + }); + } + ops_since_verify += 1; + } + Op::DeleteTracked => { + if tracked.is_empty() { + continue; + } + let removed = tracked.swap_remove(0); + let abs = workdir.join(&removed.relative); + if abs.exists() { + if removed.is_created { + fs::remove_file(&abs).ok(); + } else { + let _ = Command::new("git") + .args(["rm", "-f", &removed.relative]) + .current_dir(&workdir) + .output(); + } + } + dead_markers.push(removed.marker); + text_files = None; // invalidate cache after deletion + ops_since_verify += 1; + } + Op::RevertTracked => { + // Revert a non-created tracked file using `git checkout` + // (restores original content, marker disappears) + let revertable = tracked.iter().position(|t| !t.is_created); + let Some(idx) = revertable else { continue }; + + if tracked[idx].last_write_sec >= epoch_secs() { + std::thread::sleep(Duration::from_millis(1100)); + } + + let _ = Command::new("git") + .args(["checkout", "--", &tracked[idx].relative]) + .current_dir(&workdir) + .output(); + + let reverted = tracked.swap_remove(idx); + dead_markers.push(reverted.marker); + text_files = None; // invalidate cache after revert + ops_since_verify += 1; + } + Op::IgnoredBurst { count, seed } => { + let dir = workdir.join("target/debug/build"); + fs::create_dir_all(&dir).unwrap(); + for i in 0..*count { + let marker = format!("FFF_IGN_{seed:08x}_{i}"); + fs::write( + dir.join(format!("ign_{seed:08x}_{i}.rs")), + format!("// {marker}\nfn {marker}() {{}}\n"), + ) + .unwrap(); + ignored_markers.push(marker); + } + ops_since_verify += 1; + } + Op::Verify => { + if tracked.is_empty() && dead_markers.is_empty() { + continue; + } + + // Poll until the watcher has propagated all pending events: + // all live markers findable, all dead markers gone, no ignored leaks. + let modes = [ + (GrepMode::PlainText, "Plain"), + (GrepMode::Regex, "Regex"), + (GrepMode::Fuzzy, "Fuzzy"), + ]; + let (mode, mode_name) = modes[op_idx % modes.len()]; + + let deadline = Instant::now() + CONVERGE_TIMEOUT; + let mut last_failure: Option = None; + + loop { + std::thread::sleep(WATCHER_SETTLE); + + // Write trigger to force a watcher batch + let trigger = workdir.join("fff_fuzz_trigger.rs"); + let _ = fs::write(&trigger, format!("// trigger {}\n", op_idx)); + + std::thread::sleep(WATCHER_SETTLE); + + let mut all_ok = true; + + // Check live markers (drop lock between each grep) + for tf in &tracked { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let found = grep_finds(picker, &tf.marker, mode); + drop(guard); + if !found { + last_failure = Some(format!( + "{mode_name} grep for {:?} in {:?} not found\n\ + is_created={} exists={} on_disk_has_marker={}", + tf.marker, + tf.relative, + tf.is_created, + workdir.join(&tf.relative).exists(), + fs::read_to_string(workdir.join(&tf.relative)) + .map(|c| c.contains(&tf.marker)) + .unwrap_or(false), + )); + all_ok = false; + break; + } + } + + // Check dead markers (only sample a few per iteration to + // avoid holding the lock too long with many dead markers) + if all_ok { + let sample_size = dead_markers.len().min(20); + for dead in dead_markers.iter().take(sample_size) { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let found = grep_finds(picker, dead, GrepMode::PlainText); + drop(guard); + if found { + last_failure = Some(format!("dead marker {dead:?} still findable")); + all_ok = false; + break; + } + } + } + + // Check ignored markers (sample first 5) + if all_ok { + for ig in ignored_markers.iter().take(5) { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let files = grep_file_list(picker, ig, GrepMode::PlainText); + drop(guard); + if !files.is_empty() { + last_failure = + Some(format!("ignored marker {ig:?} found in {files:?}")); + all_ok = false; + break; + } + } + } + + if all_ok { + eprintln!( + " op[{op_idx}] verify OK: {mode_name} mode, {} live, {} dead, {} ignored", + tracked.len(), + dead_markers.len(), + ignored_markers.len(), + ); + break; + } + + if Instant::now() >= deadline { + panic!( + "op[{op_idx}] verify TIMEOUT after {CONVERGE_TIMEOUT:?}:\n {}\n ops_since_last_verify={}", + last_failure.unwrap_or_default(), + ops_since_verify, + ); + } + } + + ops_since_verify = 0; + } + } + } + + // Final convergence: poll until everything is consistent + let deadline = Instant::now() + CONVERGE_TIMEOUT; + loop { + std::thread::sleep(WATCHER_SETTLE); + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let live_ok = tracked + .iter() + .all(|tf| grep_finds(picker, &tf.marker, GrepMode::PlainText)); + let dead_ok = dead_markers + .iter() + .all(|d| !grep_finds(picker, d, GrepMode::PlainText)); + drop(guard); + + if live_ok && dead_ok { + break; + } + assert!( + Instant::now() < deadline, + "final verify TIMEOUT: live_ok={live_ok} dead_ok={dead_ok}" + ); + } + + // Teardown + shared_picker.wait_for_indexing_complete(Duration::from_secs(30)); + if let Ok(mut guard) = shared_picker.write() { + if let Some(mut picker) = guard.take() { + picker.stop_background_monitor(); + } + } + + eprintln!( + " PASSED: {} ops, {} tracked, {} dead, {} ignored ({:.1}s)", + ops.len(), + tracked.len(), + dead_markers.len(), + ignored_markers.len(), + scenario_start.elapsed().as_secs_f64(), + ); +} + +// ================ +// Proptest harness +// ================= +// +fn proptest_config() -> ProptestConfig { + ProptestConfig { + cases: fuzz_cases(), + max_shrink_iters: 0, + fork: false, + failure_persistence: Some(Box::new(FileFailurePersistence::Direct(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fuzz_real_repos.proptest-regressions", + )))), + ..ProptestConfig::default() + } +} + +#[derive(Debug, Clone)] +enum Op { + CreateFile { seed: u32 }, + EditTracked { seed: u32 }, + EditRandom { seed: u32 }, + DeleteTracked, + RevertTracked, + IgnoredBurst { count: u8, seed: u32 }, + Verify, +} + +fn op_strategy() -> impl Strategy { + prop_oneof![ + 12 => any::().prop_map(|s| Op::CreateFile { seed: s }), + 18 => any::().prop_map(|s| Op::EditTracked { seed: s }), + 18 => any::().prop_map(|s| Op::EditRandom { seed: s }), + 8 => Just(Op::DeleteTracked), + 10 => Just(Op::RevertTracked), + 9 => (1u8..20, any::()).prop_map(|(c, s)| Op::IgnoredBurst { count: c, seed: s }), + // Explicit verification rounds + 25 => Just(Op::Verify), + ] +} + +fn ops_strategy() -> impl Strategy> { + let min = fuzz_min_ops(); + let max = fuzz_max_ops(); + prop::collection::vec(op_strategy(), min..=max) +} + +proptest! { + #![proptest_config(proptest_config())] + + #[test] + fn fuzz_real_repos_proptest(ops in ops_strategy()) { + run_scenario(&ops); + } +} diff --git a/crates/fff-core/tests/grep_integration.rs b/crates/fff-core/tests/grep_integration.rs new file mode 100644 index 0000000..2765de3 --- /dev/null +++ b/crates/fff-core/tests/grep_integration.rs @@ -0,0 +1,1837 @@ +use std::fs; +use std::path::Path; +use tempfile::TempDir; + +use fff_search::FilePickerOptions; +use fff_search::file_picker::FilePicker; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; + +/// Build a batch of test files and return a FilePicker with them indexed. +fn create_picker(base: &Path, specs: &[(&str, &str)]) -> FilePicker { + for (rel, contents) in specs { + let full_path = base.join(rel); + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).unwrap(); + } + fs::write(&full_path, contents).unwrap(); + } + let mut picker = FilePicker::new(FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + watch: false, + ..Default::default() + }) + .expect("Failed to create FilePicker"); + picker.collect_files().expect("Failed to collect files"); + picker +} + +/// Shorthand to build default options for plain text mode. +fn plain_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 200, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +/// Shorthand to build default options for regex mode. +fn regex_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 200, + mode: GrepMode::Regex, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +/// Shorthand to build default options for fuzzy mode. +fn fuzzy_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 200, + mode: GrepMode::Fuzzy, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +#[test] +fn plain_text_finds_exact_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("hello.txt", "Hello, World!\nGoodbye, World!\n")], + ); + + let parsed = parse_grep_query("Hello"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); + assert_eq!(result.matches[0].line_number, 1); + assert!(result.matches[0].line_content.contains("Hello")); +} + +#[test] +fn plain_text_smart_case_insensitive() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "Hello World\nhello world\nHELLO WORLD\n")], + ); + + // All lowercase query → smart case → case-insensitive + let parsed = parse_grep_query("hello"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 3, + "smart case should match all 3 lines" + ); +} + +#[test] +fn plain_text_smart_case_sensitive_with_uppercase() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "Hello World\nhello world\nHELLO WORLD\n")], + ); + + // Query has uppercase → smart case → case-sensitive + let parsed = parse_grep_query("Hello"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 1, + "uppercase in query should trigger case-sensitive" + ); + assert_eq!(result.matches[0].line_number, 1); +} + +#[test] +fn plain_text_regex_metacharacters_are_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "code.rs", + "fn main() {\n println!(\"test\");\n}\nfn foo() {}\n", + )], + ); + + // In plain text mode, these regex metacharacters should be literal + let parsed = parse_grep_query("fn main()"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); + assert_eq!(result.matches[0].line_number, 1); + + // Parentheses should NOT be treated as regex groups + let parsed2 = parse_grep_query("(\"test\")"); + let result2 = picker.grep(&parsed2, &plain_opts()); + assert_eq!(result2.matches.len(), 1); + assert_eq!(result2.matches[0].line_number, 2); +} + +#[test] +fn plain_text_dot_is_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "config.toml", + "version = \"1.0\"\nname = \"foo\"\nversion_major = 1\n", + )], + ); + + // In plain text mode, dot should be literal, not "any char" + let parsed = parse_grep_query("1.0"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 1, + "dot should be literal, not 'any char'" + ); + assert!(result.matches[0].line_content.contains("1.0")); +} + +#[test] +fn plain_text_asterisk_is_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "doc.md", + "Use **bold** text\nUse *italic* text\nUse normal text\n", + )], + ); + + let parsed = parse_grep_query("**bold**"); + let result = picker.grep(&parsed, &plain_opts()); + assert_eq!(result.matches.len(), 1); + assert_eq!(result.matches[0].line_number, 1); +} + +#[test] +fn plain_text_backslash_is_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("paths.txt", "C:\\Users\\foo\\bar\n/home/user/bin\n")], + ); + + let parsed = parse_grep_query("C:\\Users"); + let result = picker.grep(&parsed, &plain_opts()); + assert_eq!(result.matches.len(), 1); +} + +#[test] +fn plain_text_across_multiple_files() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("a.txt", "use std::io;\nuse std::fs;\n"), + ("b.txt", "use std::path;\nuse serde;\n"), + ("c.txt", "no match here\n"), + ], + ); + + let parsed = parse_grep_query("use std"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 3); + // Should match in files a.txt and b.txt + assert_eq!(result.files.len(), 2); +} + +#[test] +fn plain_text_highlight_offsets_are_correct() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "foo bar foo baz foo\n")]); + + let parsed = parse_grep_query("foo"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); + let m = &result.matches[0]; + // "foo" appears at byte offsets 0, 8, 16 + assert_eq!(m.match_byte_offsets.len(), 3); + assert_eq!(m.match_byte_offsets[0], (0, 3)); + assert_eq!(m.match_byte_offsets[1], (8, 11)); + assert_eq!(m.match_byte_offsets[2], (16, 19)); + assert_eq!(m.col, 0); +} + +#[test] +fn plain_text_empty_query_returns_no_content_matches() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "some content\n")]); + + let parsed = parse_grep_query(""); + let result = picker.grep(&parsed, &plain_opts()); + + // Empty query in grep returns git-modified welcome state (no content matches) + // Since our test files have no git status, we expect 0 matches + assert_eq!(result.matches.len(), 0); +} + +#[test] +fn plain_text_binary_files_are_skipped() { + let tmp = TempDir::new().unwrap(); + let mut bin_content = b"match this text\n".to_vec(); + bin_content.extend_from_slice(&[0u8; 100]); // NUL bytes make it binary + bin_content.extend_from_slice(b"match this text\n"); + + // Initialize a git repo so the picker indexes binary-extension files + // (marking them as binary) rather than skipping them entirely. + std::process::Command::new("git") + .args(["init", "--quiet"]) + .current_dir(tmp.path()) + .status() + .expect("git init failed"); + + // Use a known binary extension so the picker's scan-time heuristic marks + // it as binary without needing mutable post-hoc access. + fs::write(tmp.path().join("binary.png"), &bin_content).unwrap(); + + // create_picker writes text.txt and then scans the directory, picking up + // both binary.png (already on disk) and text.txt. + let picker = create_picker(tmp.path(), &[("text.txt", "match this text\n")]); + + // binary.png should have been auto-detected as binary by extension heuristic + let has_binary = picker + .get_files() + .iter() + .any(|f| f.relative_path(&picker).contains("binary.png") && f.is_binary()); + assert!(has_binary, "binary.png should be detected as binary"); + + let parsed = parse_grep_query("match this text"); + let result = picker.grep(&parsed, &plain_opts()); + + // Only the text file should be searched, not the binary one + assert_eq!(result.files.len(), 1); + assert!(result.files[0].relative_path(&picker).contains("text.txt")); +} + +#[test] +fn binary_payload_after_long_ascii_header_is_detected() { + // Mimics formats like Radiance .hdr / Apple bplist / Adobe .ai where the + // first ~1KB is plain ASCII and the binary payload (NULs) starts later. + // The legacy 512-byte sniff missed these; the bigram-build memchr scan + // over the whole indexed buffer must catch them. + use fff_search::file_picker::FFFMode; + use fff_search::{SharedFilePicker, SharedFrecency}; + use std::time::Duration; + + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let mut content = Vec::new(); + // 1 KiB of plain ASCII header — escapes any small fixed-window NUL sniff. + content.extend(std::iter::repeat_n(b'A', 1024)); + content.extend_from_slice(b"\nmatch this text\n"); + // Binary payload: NUL bytes that prove the file is not text. + content.extend(std::iter::repeat_n(0u8, 256)); + content.extend_from_slice(b"\nmatch this text\n"); + + // Use a *text* extension so the scan-time heuristic does NOT pre-flag it. + // Only the bigram-time content scan can mark it binary. + fs::write(base.join("header.txt"), &content).unwrap(); + fs::write(base.join("plain.txt"), b"match this text\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + let deadline = std::time::Instant::now() + Duration::from_secs(10); + loop { + std::thread::sleep(Duration::from_millis(25)); + let ready = shared_picker + .read() + .ok() + .and_then(|g| { + g.as_ref() + .map(|p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } + + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let was_flagged = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("header.txt") && f.is_binary()); + assert!( + was_flagged, + "header.txt with NULs past 512 bytes must be flagged binary by the whole-buffer memchr scan" + ); + + let parsed = parse_grep_query("match this text"); + let result = picker.grep(&parsed, &plain_opts()); + assert_eq!( + result.files.len(), + 1, + "only plain.txt should be searched; header.txt must be skipped as binary" + ); + assert!( + result.files[0].relative_path(picker).contains("plain.txt"), + "the only match should come from plain.txt" + ); +} + +#[test] +fn unknown_extension_binary_added_after_scan_is_reclassified() { + // The initial-scan path runs detect_binary_content as part of bigram build, + // but the watcher path used to fall back to extension-only triage and + // missed binary files with unknown extensions like `.codex`. + use fff_search::file_picker::FFFMode; + use fff_search::{SharedFilePicker, SharedFrecency}; + use std::time::Duration; + + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + // Seed one tracked text file so the initial scan has something to work with. + fs::write(base.join("seed.txt"), b"seed\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + let deadline = std::time::Instant::now() + Duration::from_secs(10); + loop { + std::thread::sleep(Duration::from_millis(25)); + let ready = shared_picker + .read() + .ok() + .and_then(|g| { + g.as_ref() + .map(|p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } + + // Drop the file on disk after indexing finished, then announce it through + // the watcher entry point. `.codex` is intentionally not in the extension + // allow-list — only a content sniff can flag it. + let mut payload = vec![0x03u8, 0x00, 0x04, 0x05]; + payload.extend(std::iter::repeat_n(0u8, 256)); + let new_path = base.join("snapshot.codex"); + fs::write(&new_path, &payload).unwrap(); + + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker.handle_create_or_modify(&new_path).is_some(), + "handle_create_or_modify must accept the new file" + ); + } + + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let was_flagged = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("snapshot.codex") && f.is_binary()); + assert!( + was_flagged, + "snapshot.codex must be flagged binary when added via the watcher path" + ); +} + +#[test] +fn text_file_modified_to_binary_is_reclassified() { + // A file that started life as text and later got rewritten with NUL bytes + // (e.g. a generator overwrote a .log) must lose its text classification. + use fff_search::file_picker::FFFMode; + use fff_search::{SharedFilePicker, SharedFrecency}; + use std::time::Duration; + + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + // Start as plain text with a known extension. + fs::write(base.join("notes.txt"), b"hello world\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + let deadline = std::time::Instant::now() + Duration::from_secs(10); + loop { + std::thread::sleep(Duration::from_millis(25)); + let ready = shared_picker + .read() + .ok() + .and_then(|g| { + g.as_ref() + .map(|p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } + + // Sanity: it's text right now. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let is_text = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("notes.txt") && !f.is_binary()); + assert!(is_text, "notes.txt should start as text"); + } + + // Overwrite with binary content and replay through the watcher entry point. + // Bump mtime so update_metadata records it as a real change. + std::thread::sleep(Duration::from_secs(1)); + let mut payload = b"header text\n".to_vec(); + payload.extend(std::iter::repeat_n(0u8, 256)); + fs::write(base.join("notes.txt"), &payload).unwrap(); + + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker + .handle_create_or_modify(base.join("notes.txt")) + .is_some(), + "handle_create_or_modify must succeed for the modify case" + ); + } + + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let now_binary = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("notes.txt") && f.is_binary()); + assert!( + now_binary, + "notes.txt must flip to binary after being overwritten with NULs" + ); +} + +#[test] +fn large_unknown_extension_binary_is_classified_at_scan_time() { + // Files larger than MAX_INDEXABLE_FILE_SIZE never enter build_bigram_index, + // so without a separate header sniff they default to is_binary=false and + // pollute grep results with NUL-laden lines (e.g. a committed ELF blob + // named `codex_view` with no extension). + use fff_search::file_picker::FFFMode; + use fff_search::grep::{GrepSearchOptions, parse_grep_query}; + use fff_search::{SharedFilePicker, SharedFrecency}; + use std::time::Duration; + + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + // 3 MiB: above the 2 MiB bigram cap and below the 10 MiB grep cap. + // ELF-like header with NULs at the very start, then ASCII filler so a + // grep for "match this text" would otherwise return polluted lines. + let mut blob = Vec::new(); + blob.extend_from_slice(b"\x7fELF\x02\x01\x01\x00"); + blob.extend(std::iter::repeat_n(0u8, 256)); + blob.extend_from_slice(b"\nmatch this text\n"); + blob.extend(std::iter::repeat_n(b'A', 3 * 1024 * 1024)); + blob.extend_from_slice(b"\nmatch this text\n"); + fs::write(base.join("codex_view"), &blob).unwrap(); + fs::write(base.join("plain.txt"), b"match this text\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + let deadline = std::time::Instant::now() + Duration::from_secs(15); + loop { + std::thread::sleep(Duration::from_millis(25)); + let ready = shared_picker + .read() + .ok() + .and_then(|g| { + g.as_ref() + .map(|p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } + + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let was_flagged = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("codex_view") && f.is_binary()); + assert!( + was_flagged, + "large no-extension binary must be flagged via the header sniff" + ); + + let parsed = parse_grep_query("match this text"); + let opts = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + ..plain_opts() + }; + let result = picker.grep(&parsed, &opts); + assert_eq!( + result.files.len(), + 1, + "only plain.txt should be searched; codex_view must be skipped as binary" + ); + assert!( + result.files[0].relative_path(picker).contains("plain.txt"), + "the only match should come from plain.txt" + ); +} + +#[test] +fn large_binary_with_nuls_past_header_is_classified() { + // Guards the streaming sniff: a >2 MB file that is pure ASCII well past any + // fixed header window (the old code only checked the first 8 KB) but has + // NULs deeper in. Grep reads the whole file up to max_file_size, so the + // detector must scan the same range or the binary tail leaks as "text". + use fff_search::file_picker::FFFMode; + use fff_search::grep::{GrepSearchOptions, parse_grep_query}; + use fff_search::{SharedFilePicker, SharedFrecency}; + use std::time::Duration; + + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + // 1 MiB of clean ASCII (with a grep marker) — dwarfs any header sniff — + // then NUL bytes, keeping the total above the 2 MiB non-indexable cap. + let mut blob = Vec::new(); + blob.extend_from_slice(b"match this text\n"); + blob.extend(std::iter::repeat_n(b'A', 1024 * 1024)); + blob.extend_from_slice(b"match this text\n"); + blob.extend(std::iter::repeat_n(0u8, 1024 * 1024 + 4096)); // NULs start ~1 MiB in + blob.extend_from_slice(b"match this text\n"); + assert!(blob.len() > 2 * 1024 * 1024); + fs::write(base.join("late_nul.dat"), &blob).unwrap(); + fs::write(base.join("plain.txt"), b"match this text\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + let deadline = std::time::Instant::now() + Duration::from_secs(15); + loop { + std::thread::sleep(Duration::from_millis(25)); + let ready = shared_picker + .read() + .ok() + .and_then(|g| { + g.as_ref() + .map(|p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } + + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let flagged = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("late_nul.dat") && f.is_binary()); + assert!( + flagged, + "NULs past the 8 KB header window must still be detected by the streaming scan" + ); + + let parsed = parse_grep_query("match this text"); + let opts = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + ..plain_opts() + }; + let result = picker.grep(&parsed, &opts); + assert_eq!( + result.files.len(), + 1, + "only plain.txt should match; late_nul.dat must be skipped as binary" + ); + assert!(result.files[0].relative_path(picker).contains("plain.txt")); +} + +#[test] +fn plain_text_max_matches_per_file() { + let tmp = TempDir::new().unwrap(); + let mut content = String::new(); + for i in 0..50 { + content.push_str(&format!("line {} match_target\n", i)); + } + fs::write(tmp.path().join("many.txt"), &content).unwrap(); + let picker = create_picker(tmp.path(), &[("many.txt", &content)]); + + let mut opts = plain_opts(); + opts.max_matches_per_file = 5; + + let parsed = parse_grep_query("match_target"); + let result = picker.grep(&parsed, &opts); + + assert_eq!( + result.matches.len(), + 5, + "should cap at max_matches_per_file" + ); +} + +#[test] +fn plain_text_page_limit() { + let tmp = TempDir::new().unwrap(); + let mut content = String::new(); + for i in 0..100 { + content.push_str(&format!("line {} target\n", i)); + } + fs::write(tmp.path().join("big.txt"), &content).unwrap(); + let picker = create_picker(tmp.path(), &[("big.txt", &content)]); + + let mut opts = plain_opts(); + opts.page_limit = 10; + + let parsed = parse_grep_query("target"); + let result = picker.grep(&parsed, &opts); + + // page_limit is a soft minimum: we always finish the current file, so we + // get at least page_limit matches (no data loss) and at most + // max_matches_per_file (200) from a single file. + assert!( + result.matches.len() >= opts.page_limit, + "should return at least page_limit matches: got {}", + result.matches.len() + ); + assert!( + result.matches.len() <= opts.max_matches_per_file, + "should never exceed max_matches_per_file: got {}", + result.matches.len() + ); + // Single file with 100 lines all matching — all should be returned. + assert_eq!(result.matches.len(), 100, "all 100 lines must be returned"); +} + +#[test] +fn plain_text_file_offset_pagination() { + let tmp = TempDir::new().unwrap(); + // Create many files (1 match per file) so file-based pagination exercises + // offset tracking across files with and without matches. + let specs: Vec<(String, String)> = (0..20) + .map(|i| { + ( + format!("file_{:02}.txt", i), + format!("unique_token_{}\n", i), + ) + }) + .collect(); + let spec_refs: Vec<(&str, &str)> = specs + .iter() + .map(|(a, b)| (a.as_str(), b.as_str())) + .collect(); + let picker = create_picker(tmp.path(), &spec_refs); + + let mut opts = plain_opts(); + opts.page_limit = 5; + + // Collect ALL matches across all pages and verify no duplicates and full coverage. + let mut all_line_texts: Vec = Vec::new(); + let mut pages = 0; + let max_pages = 20; // safety limit + + loop { + let parsed = parse_grep_query("unique_token"); + let result = picker.grep(&parsed, &opts); + + for m in &result.matches { + let text = m.line_content.trim().to_string(); + assert!( + !all_line_texts.contains(&text), + "duplicate match across pages: '{}'", + text + ); + all_line_texts.push(text); + } + + pages += 1; + assert!(pages <= max_pages, "pagination did not terminate"); + + if result.next_file_offset == 0 { + break; + } + + // Offset must strictly advance + assert!( + result.next_file_offset > opts.file_offset, + "next_file_offset ({}) did not advance past current ({})", + result.next_file_offset, + opts.file_offset + ); + opts.file_offset = result.next_file_offset; + } + + assert_eq!( + all_line_texts.len(), + 20, + "pagination should find all 20 matches across all pages, got {}", + all_line_texts.len() + ); + assert!( + pages > 1, + "should require multiple pages with page_limit=5 and 20 files" + ); +} + +#[test] +fn plain_text_line_numbers_are_correct() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "line one\nline two\nline three\nline four\n")], + ); + + let parsed = parse_grep_query("line"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 4); + assert_eq!(result.matches[0].line_number, 1); + assert_eq!(result.matches[1].line_number, 2); + assert_eq!(result.matches[2].line_number, 3); + assert_eq!(result.matches[3].line_number, 4); +} + +#[test] +fn plain_text_max_file_size_filter() { + let tmp = TempDir::new().unwrap(); + // Create a file larger than 100 bytes + let big_content = "a".repeat(200) + "\nmatch_me\n"; + fs::write(tmp.path().join("big.txt"), &big_content).unwrap(); + let picker = create_picker(tmp.path(), &[("big.txt", &big_content)]); + + let mut opts = plain_opts(); + opts.max_file_size = 100; // Only allow files up to 100 bytes + + let parsed = parse_grep_query("match_me"); + let result = picker.grep(&parsed, &opts); + + assert_eq!(result.matches.len(), 0, "large file should be filtered out"); + assert_eq!(result.filtered_file_count, 0); +} + +// ── Regex mode tests ─────────────────────────────────────────────────── + +#[test] +fn regex_basic_pattern() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "foo123\nbar456\nbaz789\nfoo_bar\n")], + ); + + let parsed = parse_grep_query("foo\\d+"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(result.matches.len(), 1); + assert_eq!(result.matches[0].line_number, 1); + assert!(result.matches[0].line_content.contains("foo123")); +} + +#[test] +fn regex_capture_group_matching() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "foobar\nfoobaz\nfoo123\n")]); + + // Use a capturing group (not lookahead, which regex crate doesn't support) + let parsed = parse_grep_query("foo(bar|baz)"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(result.matches.len(), 2); + let contents: Vec<&str> = result + .matches + .iter() + .map(|m| m.line_content.as_str()) + .collect(); + assert!(contents.contains(&"foobar")); + assert!(contents.contains(&"foobaz")); +} + +#[test] +fn regex_dot_matches_any_char() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "v1.0\nv1x0\nv1-0\nv100\nv2.0\n")]); + + // In regex mode, . matches any character, so v1.0 matches v1.0, v1x0, v1-0, and v100 + let parsed = parse_grep_query("v1.0"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!( + result.matches.len(), + 4, + "regex dot should match v1.0, v1x0, v1-0, and v100 (dot matches any char)" + ); +} + +#[test] +fn regex_alternation() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "apple\nbanana\ncherry\napricot\n")]); + + let parsed = parse_grep_query("apple|cherry"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(result.matches.len(), 2); + let lines: Vec = result.matches.iter().map(|m| m.line_number).collect(); + assert!(lines.contains(&1)); + assert!(lines.contains(&3)); +} + +#[test] +fn regex_character_class() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "cat\ncut\ncot\ncit\ncxt\n")]); + + let parsed = parse_grep_query("c[aou]t"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(result.matches.len(), 3); + let contents: Vec<&str> = result + .matches + .iter() + .map(|m| m.line_content.as_str()) + .collect(); + assert!(contents.contains(&"cat")); + assert!(contents.contains(&"cut")); + assert!(contents.contains(&"cot")); +} + +#[test] +fn regex_quantifiers() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "fo\nfoo\nfooo\nfoooo\nbar\n")]); + + let parsed = parse_grep_query("fo{2,3}"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(result.matches.len(), 3, "should match foo, fooo, foooo"); +} + +#[test] +fn regex_anchors() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "start of line\nmiddle start end\nend of line\n")], + ); + + let parsed = parse_grep_query("^start"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(result.matches.len(), 1); + assert_eq!(result.matches[0].line_number, 1); +} + +#[test] +fn regex_anchors_multiword() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "test.c", + "int ff_function(void);\nstatic int ff_other(void);\nint main(void);\nint ff_another(void);\n", + )], + ); + + // ^int ff_ should match lines starting with "int ff_" + let parsed = parse_grep_query("^int ff_"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!( + result.matches.len(), + 2, + "should match 2 lines starting with 'int ff_'" + ); + assert!(result.matches[0].line_content.contains("ff_function")); + assert!(result.matches[1].line_content.contains("ff_another")); +} + +#[test] +fn regex_highlight_offsets_variable_length() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "aab aaab aaaab\n")]); + + let parsed = parse_grep_query("a+b"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(result.matches.len(), 1); + let m = &result.matches[0]; + // Regex "a+b" matches "aab" (3 bytes), "aaab" (4 bytes), "aaaab" (5 bytes) + assert_eq!(m.match_byte_offsets.len(), 3); + // Verify the match spans have different lengths (variable-length regex) + assert_eq!(m.match_byte_offsets[0], (0, 3)); // "aab" + assert_eq!(m.match_byte_offsets[1], (4, 8)); // "aaab" + assert_eq!(m.match_byte_offsets[2], (9, 14)); // "aaaab" +} + +#[test] +fn regex_invalid_pattern_falls_back_to_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "call name(arg)\nother line\n")]); + + // Invalid regex: unmatched group — should fall back to literal search + let parsed = parse_grep_query("name("); + let result = picker.grep(&parsed, ®ex_opts()); + + // Fallback to literal: finds "name(" in "call name(arg)" + assert_eq!( + result.matches.len(), + 1, + "invalid regex should fall back to literal and find the match" + ); + assert!( + result.regex_fallback_error.is_some(), + "should report the regex compilation error" + ); + assert!(result.matches[0].line_content.contains("name(")); + + // A pattern that doesn't exist anywhere — still falls back but finds nothing + let parsed2 = parse_grep_query("zzz("); + let result2 = picker.grep(&parsed2, ®ex_opts()); + assert_eq!(result2.matches.len(), 0); + assert!(result2.regex_fallback_error.is_some()); +} + +#[test] +fn regex_smart_case() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "Foo bar\nfoo bar\nFOO BAR\n")]); + + // Lowercase query → case-insensitive + let parsed_lower = parse_grep_query("foo"); + let result_lower = picker.grep(&parsed_lower, ®ex_opts()); + assert_eq!(result_lower.matches.len(), 3); + + // Query with uppercase → case-sensitive + let parsed_upper = parse_grep_query("Foo"); + let result_upper = picker.grep(&parsed_upper, ®ex_opts()); + assert_eq!(result_upper.matches.len(), 1); +} + +#[test] +fn regex_across_multiple_files() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("lib.rs", "fn main() {}\nfn helper() {}\nstruct Foo;\n"), + ( + "test.rs", + "fn test_one() {}\nfn test_two() {}\nmod tests;\n", + ), + ("readme.md", "# Title\nSome text\n"), + ], + ); + + let parsed = parse_grep_query("fn \\w+\\(\\)"); + let result = picker.grep(&parsed, ®ex_opts()); + + // Should match: fn main(), fn helper(), fn test_one(), fn test_two() + assert_eq!(result.matches.len(), 4); + assert_eq!(result.files.len(), 2, "matches in 2 .rs files, not readme"); +} + +// ── Mode comparison tests ────────────────────────────────────────────── + +#[test] +fn plain_text_and_regex_agree_on_simple_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "hello world\ngoodbye world\nhello again\n")], + ); + + let parsed = parse_grep_query("hello"); + let plain_result = picker.grep(&parsed, &plain_opts()); + let regex_result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!(plain_result.matches.len(), regex_result.matches.len()); + for (p, r) in plain_result.matches.iter().zip(regex_result.matches.iter()) { + assert_eq!(p.line_number, r.line_number); + assert_eq!(p.line_content, r.line_content); + } +} + +#[test] +fn plain_text_escapes_what_regex_does_not() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "price is $100\nprice is 100\nprice is $200\n")], + ); + + // "$100" — in plain text, $ is literal; in regex, $ is anchor + let parsed_plain = parse_grep_query("$100"); + let plain_result = picker.grep(&parsed_plain, &plain_opts()); + let parsed_regex = parse_grep_query("\\$100"); + let regex_result = picker.grep(&parsed_regex, ®ex_opts()); + + // Plain text should find "$100" literally + assert_eq!(plain_result.matches.len(), 1); + assert!(plain_result.matches[0].line_content.contains("$100")); + + // Regex with escaped $ should also find "$100" + assert_eq!(regex_result.matches.len(), 1); +} + +// ── Constraint integration tests ─────────────────────────────────────── + +#[test] +fn grep_with_extension_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("a.rs", "use std::io;\nfn main() {}\n"), + ("b.txt", "use std::io;\nsome text\n"), + ("c.rs", "use std::fs;\n"), + ], + ); + + let parsed = parse_grep_query("use std *.rs"); + let result = picker.grep(&parsed, &plain_opts()); + + // Should only search .rs files + for file in &result.files { + assert!( + file.relative_path(&picker).ends_with(".rs"), + "should only match .rs files, got: {}", + file.relative_path(&picker) + ); + } + assert!( + result.matches.len() >= 2, + "should find matches in .rs files" + ); +} + +// ── Bracket / glob character tests ───────────────────────────────────── + +#[test] +fn plain_text_bracket_is_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "code.rs", + "let x = arr[0];\nlet y = arr[1];\nlet z = something;\n", + )], + ); + + let parsed = parse_grep_query("arr[0]"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 1, + "brackets should be literal in plain text mode" + ); + assert_eq!(result.matches[0].line_number, 1); +} + +// ── Backslash escape tests ───────────────────────────────────────────── + +#[test] +fn grep_backslash_escapes_extension_filter() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("a.rs", "contains *.rs pattern\n"), + ("b.txt", "also has *.rs here\n"), + ], + ); + + // Without escape: "*.rs" is an extension filter, so only .rs files are searched + let parsed = parse_grep_query("pattern *.rs"); + let result_filter = picker.grep(&parsed, &plain_opts()); + assert_eq!( + result_filter.files.len(), + 1, + "*.rs should filter to .rs files" + ); + + // With escape: "\*.rs" is literal text, both files are searched + let parsed_escaped = parse_grep_query("\\*.rs"); + let result_literal = picker.grep(&parsed_escaped, &plain_opts()); + assert_eq!( + result_literal.matches.len(), + 2, + "\\*.rs should search for literal *.rs in all files" + ); +} + +#[test] +fn grep_backslash_escapes_path_segment() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("src/main.rs", "search for /src/ path\n"), + ("lib/utils.rs", "also /src/ mentioned\n"), + ], + ); + + // With escape: "\\/src/" is literal text, not a path constraint + let parsed = parse_grep_query("\\/src/"); + let result = picker.grep(&parsed, &plain_opts()); + assert_eq!( + result.matches.len(), + 2, + "\\/src/ should search for literal /src/ in all files" + ); +} + +#[test] +fn grep_backslash_escapes_negation() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "the !test macro\nother stuff\n")]); + + // With escape: "\\!test" is literal text "!test" + let parsed = parse_grep_query("\\!test"); + let result = picker.grep(&parsed, &plain_opts()); + assert_eq!(result.matches.len(), 1); + assert!(result.matches[0].line_content.contains("!test")); +} + +#[test] +fn grep_with_path_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("src/lib.rs", "target_text\n"), + ("tests/test.rs", "target_text\n"), + ("src/main.rs", "other content\n"), + ], + ); + + let parsed = parse_grep_query("target_text /src/"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); + let rel = result.files[0].relative_path(&picker); + assert!(rel.starts_with("src/") || rel.starts_with("src\\")); +} + +// ── Negated constraint tests ─────────────────────────────────────────── + +#[test] +fn grep_with_negated_extension_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("src/lib.rs", "target_text\n"), + ("src/app.ts", "target_text\n"), + ("src/main.rs", "target_text\n"), + ], + ); + + let query = "target_text !*.rs"; + let parsed = parse_grep_query(query); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 1, + "should only find matches in non-.rs files, got {} matches", + result.matches.len() + ); + assert!( + result.files[0].relative_path(&picker).ends_with(".ts"), + "should only match .ts file, got: {}", + result.files[0].relative_path(&picker) + ); +} + +#[test] +fn grep_with_negated_path_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("src/lib.rs", "target_text\n"), + ("tests/test.rs", "target_text\n"), + ("src/main.rs", "other content\n"), + ], + ); + + let query = "target_text !/src/"; + let parsed = parse_grep_query(query); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 1, + "should only find matches outside src/, got {} matches", + result.matches.len() + ); + assert!( + result.files[0].relative_path(&picker).starts_with("tests/") + || result.files[0] + .relative_path(&picker) + .starts_with("tests\\"), + "should only match tests/ file, got: {}", + result.files[0].relative_path(&picker) + ); +} + +#[test] +fn grep_with_negated_text_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("src/lib.rs", "target_text\n"), + ("tests/helper.rs", "target_text\n"), + ("docs/readme.md", "target_text\n"), + ], + ); + + let query = "target_text !test"; + let parsed = parse_grep_query(query); + let result = picker.grep(&parsed, &plain_opts()); + + // "tests/helper.rs" contains "test" in path, should be excluded + assert_eq!( + result.matches.len(), + 2, + "should find matches in files without 'test' in path, got {} matches", + result.matches.len() + ); + for file in &result.files { + assert!( + !file.relative_path(&picker).contains("test"), + "should not match files with 'test' in path, got: {}", + file.relative_path(&picker) + ); + } +} + +// ── Edge case tests ──────────────────────────────────────────────────── + +#[test] +fn grep_empty_file_is_skipped() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("empty.txt", ""), ("text.txt", "findme\n")]); + + let parsed = parse_grep_query("findme"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); +} + +#[test] +fn grep_single_line_no_trailing_newline() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "no newline at end")]); + + let parsed = parse_grep_query("no newline"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); + assert_eq!(result.matches[0].line_number, 1); +} + +#[test] +fn grep_unicode_content() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("utf8.txt", "日本語テスト\nrégulière\nñoño\n")], + ); + + let parsed = parse_grep_query("régulière"); + let result = picker.grep(&parsed, &plain_opts()); + assert_eq!(result.matches.len(), 1); + assert_eq!(result.matches[0].line_number, 2); + + let parsed2 = parse_grep_query("ñoño"); + let result2 = picker.grep(&parsed2, &plain_opts()); + assert_eq!(result2.matches.len(), 1); + assert_eq!(result2.matches[0].line_number, 3); +} + +#[test] +fn grep_long_line_is_truncated() { + let tmp = TempDir::new().unwrap(); + let long_line = format!("{}NEEDLE{}", "x".repeat(1000), "y".repeat(1000)); + fs::write(tmp.path().join("long.txt"), &long_line).unwrap(); + let picker = create_picker(tmp.path(), &[("long.txt", &long_line)]); + + let parsed = parse_grep_query("NEEDLE"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); + // The line_content should be truncated to MAX_LINE_DISPLAY_LEN (512) + assert!( + result.matches[0].line_content.len() <= 512, + "line should be truncated: len={}", + result.matches[0].line_content.len() + ); +} + +#[test] +fn regex_word_boundary() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "foo\nfoobar\nbarfoo\nfoo_baz\n")]); + + let parsed = parse_grep_query("\\bfoo\\b"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!( + result.matches.len(), + 1, + "only exact word 'foo' should match" + ); + assert_eq!(result.matches[0].line_number, 1); +} + +#[test] +fn plain_text_question_mark_is_literal() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "a.txt", + "what is this?\nhow does it work?\nno question here\nwhat?\n", + )], + ); + + let parsed = parse_grep_query("?"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 3, + "question mark should be literal in plain text mode" + ); +} + +#[test] +fn plain_text_query_with_question_mark_in_word() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "code.rs", + "let x = foo?;\nlet y = bar.baz();\nfoo?.unwrap()\n", + )], + ); + + let parsed = parse_grep_query("foo?"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 2, + "should find 'foo?' literally in both lines" + ); +} + +#[test] +fn regex_question_mark_is_quantifier() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "color\ncolour\ncolouur\n")]); + + // In regex mode, ? means "zero or one of preceding" + let parsed = parse_grep_query("colou?r"); + let result = picker.grep(&parsed, ®ex_opts()); + + assert_eq!( + result.matches.len(), + 2, + "regex ? should match 'color' and 'colour' but not 'colouur'" + ); +} + +// ── Fuzzy mode tests ─────────────────────────────────────────────────── + +#[test] +fn fuzzy_finds_exact_substring() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("a.txt", "hello world\ngoodbye world\nhello again\n")], + ); + + let parsed = parse_grep_query("hello"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + assert_eq!( + result.matches.len(), + 2, + "fuzzy should find 'hello' in both lines" + ); + assert!(result.matches[0].line_content.contains("hello")); + assert!(result.matches[1].line_content.contains("hello")); +} + +#[test] +fn fuzzy_finds_scattered_characters() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "code.rs", + "fn mutex_lock() {}\nfn main() {}\nfn mutex_unlock() {}\n", + )], + ); + + // "mutex" should fuzzy match "mutex_lock" (contiguous prefix) + let parsed = parse_grep_query("mutex"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + assert!( + !result.matches.is_empty(), + "fuzzy should find 'mutex' in 'mutex_lock'" + ); + assert!(result.matches[0].line_content.contains("mutex_lock")); +} + +#[test] +fn fuzzy_highlight_offsets_correct() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "hello world\n")]); + + let parsed = parse_grep_query("hell"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + assert_eq!(result.matches.len(), 1); + let m = &result.matches[0]; + + // "hell" should match 'h'(0), 'e'(1), 'l'(2), 'l'(3) in "hello" + // These should be converted to byte offsets + assert!( + !m.match_byte_offsets.is_empty(), + "should have highlight offsets" + ); +} + +#[test] +fn fuzzy_unicode_char_indices() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("utf8.txt", "日本語テスト\nrégulière\nñoño\n")], + ); + + // Use "guli" which is a contiguous ASCII substring within "régulière" + // (the chars g-u-l-i appear contiguously between the two accented chars) + let parsed = parse_grep_query("guli"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + // Should fuzzy match "régulière" (with multi-byte é and è) + // This tests that character-to-byte offset conversion works with UTF-8 + assert!(!result.matches.is_empty()); + assert!(result.matches[0].line_content.contains("régulière")); +} + +#[test] +fn fuzzy_empty_query_returns_empty() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("a.txt", "some content\n")]); + + let parsed = parse_grep_query(""); + let result = picker.grep(&parsed, &fuzzy_opts()); + + // Empty query returns git-modified files, not fuzzy matches + assert_eq!(result.matches.len(), 0); +} + +#[test] +fn fuzzy_with_extension_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("a.rs", "use std::io;\nfn main() {}\n"), + ("b.txt", "use std::io;\nsome text\n"), + ("c.rs", "use std::fs;\n"), + ], + ); + + let parsed = parse_grep_query("use std *.rs"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + // Should only search .rs files + for file in &result.files { + assert!( + file.relative_path(&picker).ends_with(".rs"), + "should only match .rs files, got: {}", + file.relative_path(&picker) + ); + } +} + +#[test] +fn fuzzy_respects_page_limit() { + let tmp = TempDir::new().unwrap(); + let mut content = String::new(); + for i in 0..100 { + content.push_str(&format!("line {} target\n", i)); + } + fs::write(tmp.path().join("big.txt"), &content).unwrap(); + let picker = create_picker(tmp.path(), &[("big.txt", &content)]); + + let mut opts = fuzzy_opts(); + opts.page_limit = 10; + opts.max_matches_per_file = 50; + + let parsed = parse_grep_query("target"); + let result = picker.grep(&parsed, &opts); + + // page_limit is a soft minimum: we always finish the current file, so we + // get at least page_limit matches (no data loss) and at most + // max_matches_per_file (200) from a single file. + assert!( + result.matches.len() >= opts.page_limit, + "should return at least page_limit matches: got {}", + result.matches.len() + ); + assert!( + result.matches.len() <= opts.max_matches_per_file, + "should never exceed max_matches_per_file: got {}", + result.matches.len() + ); + + assert_eq!( + result.matches.len(), + opts.max_matches_per_file, + "all limit of lines must be returned" + ); +} + +#[test] +fn fuzzy_respects_max_matches_per_file() { + let tmp = TempDir::new().unwrap(); + let mut content = String::new(); + for i in 0..50 { + content.push_str(&format!("line {} match_target\n", i)); + } + fs::write(tmp.path().join("many.txt"), &content).unwrap(); + let picker = create_picker(tmp.path(), &[("many.txt", &content)]); + + let mut opts = fuzzy_opts(); + opts.max_matches_per_file = 5; + + let parsed = parse_grep_query("match"); + let result = picker.grep(&parsed, &opts); + + assert_eq!( + result.matches.len(), + 5, + "should cap at max_matches_per_file" + ); +} + +#[test] +fn fuzzy_filters_low_quality_matches() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "code.rs", + "fn mutex_lock() {}\nfn xyz() {}\nfn abc_def_ghi() {}\nfn abcdefghij() {}\n", + )], + ); + + // Search for "abc" - should match "abc_def_ghi" and "abcdefghij" with high scores, + // but NOT "xyz" (no relation) or "mutex_lock" (only weak letter overlap) + let parsed = parse_grep_query("abc"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + // Should only get high-quality matches + assert!( + result.matches.len() <= 2, + "should filter out low-quality fuzzy matches, got {} matches", + result.matches.len() + ); + + // All matches should contain reasonable character overlap + for m in &result.matches { + assert!( + m.line_content.contains("abc") || m.line_content.contains("abc_"), + "match '{}' should be high-quality", + m.line_content + ); + } +} + +#[test] +fn fuzzy_exact_match_always_passes() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[("test.txt", "exact match line\nno match here\n")], + ); + + // Exact matches should always pass regardless of score threshold + let parsed = parse_grep_query("exact"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + assert_eq!( + result.matches.len(), + 1, + "exact match should always pass score threshold" + ); + assert!(result.matches[0].line_content.contains("exact")); +} + +#[test] +fn fuzzy_score_is_captured() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("test.txt", "hello world\ngoodbye world\n")]); + + let parsed = parse_grep_query("hello"); + let result = picker.grep(&parsed, &fuzzy_opts()); + + assert_eq!(result.matches.len(), 1); + let m = &result.matches[0]; + + // Fuzzy score should be set (Some) for fuzzy mode matches + assert!( + m.fuzzy_score.is_some(), + "fuzzy_score should be set in fuzzy grep mode" + ); + assert!( + m.fuzzy_score.unwrap() > 0, + "fuzzy_score should be positive for a good match" + ); +} + +#[test] +fn fuzzy_score_is_none_in_plain_mode() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker(tmp.path(), &[("test.txt", "hello world\n")]); + + let parsed = parse_grep_query("hello"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!(result.matches.len(), 1); + let m = &result.matches[0]; + + // fuzzy_score should be None for plain text mode + assert!( + m.fuzzy_score.is_none(), + "fuzzy_score should be None in plain text mode" + ); +} + +/// Regression: memmem prefilter rejected files where content casing differed +/// from the query, even under smart_case. E.g. "vfio-kvm" failed to find +/// "VFIO-KVM" because the lowercased finder did a case-sensitive scan. +#[test] +fn plain_text_smart_case_finds_uppercase_content_with_lowercase_query() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[( + "driver.c", + "// VFIO-KVM integration\nstatic int init(void) {}\n", + )], + ); + + let parsed = parse_grep_query("vfio-kvm"); + let result = picker.grep(&parsed, &plain_opts()); + + assert_eq!( + result.matches.len(), + 1, + "lowercase query should case-insensitively match 'VFIO-KVM'" + ); +} diff --git a/crates/fff-core/tests/grep_overflow_path_constraint_segfault.rs b/crates/fff-core/tests/grep_overflow_path_constraint_segfault.rs new file mode 100644 index 0000000..0cad79c --- /dev/null +++ b/crates/fff-core/tests/grep_overflow_path_constraint_segfault.rs @@ -0,0 +1,57 @@ +use std::fs; + +use fff_search::file_picker::FilePicker; +use fff_search::grep::{GrepMode, GrepSearchOptions}; +use fff_search::{AiGrepConfig, FFFQuery, FilePickerOptions}; +use tempfile::TempDir; + +// bug pinning https://github.com/dmtrKovalenko/fff/issues/618 +#[test] +fn grep_path_constraint_on_overflow_file_does_not_segfault() { + let tmp = TempDir::new().expect("tempdir"); + let base = tmp.path(); + let spec_dir = base.join("specs"); + fs::create_dir_all(&spec_dir).expect("mkdir specs"); + + let file = spec_dir.join("annotation-plan.md"); + fs::write(&file, "dependency\n").expect("write test file"); + + // Intentionally do NOT call `collect_files()`. This leaves the base path + // arena unset/null. `handle_create_or_modify` then adds the file as an + // overflow file whose path chunks live in the overflow arena. + let mut picker = FilePicker::new(FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + watch: false, + enable_home_dir_scanning: true, + ..Default::default() + }) + .expect("create picker"); + + let is_overflow = picker + .handle_create_or_modify(&file) + .expect("add overflow file") + .is_overflow(); + assert!(is_overflow, "file must be added to the overflow arena"); + + // Mirrors the `ffgrep({ pattern: "dependency", path: "specs/annotation-plan.md" })` + // call that crashed: in AI grep mode the path token becomes a FilePath + // constraint and `dependency` becomes the grep text. + let parsed = FFFQuery::parse("specs/annotation-plan.md dependency", AiGrepConfig); + + let opts = GrepSearchOptions { + mode: GrepMode::PlainText, + page_limit: 20, + smart_case: true, + ..Default::default() + }; + + // original issue: + // Debug builds typically abort with Rust's unsafe precondition check at + // simd_path.rs: ChunkedString::write_to_string. Release builds may SIGSEGV + // in memmove from an address like 0x30 (null arena + chunk_index * 16). + let result = picker.grep(&parsed, &opts); + + assert_eq!(result.files.len(), 1, "the overflow file should match"); + assert_eq!(result.matches.len(), 1, "`dependency` should match once"); +} diff --git a/crates/fff-core/tests/lmdb_stale_lock_deadlock.rs b/crates/fff-core/tests/lmdb_stale_lock_deadlock.rs new file mode 100644 index 0000000..a27e784 --- /dev/null +++ b/crates/fff-core/tests/lmdb_stale_lock_deadlock.rs @@ -0,0 +1,455 @@ +//! Reproduces the deadlock/hang caused by LMDB writer mutex contention. +//! +//! When another process holds the LMDB writer mutex (via a long-running write +//! transaction or because it crashed without releasing it), any call to +//! `write_txn()` blocks indefinitely — including on the neovim main thread +//! during `QueryTracker::open()` or frecency `track_access()`. +//! +//! In production this manifests as neovim hanging on startup: +//! require('fff.core').ensure_initialized() +//! → init_db() → QueryTracker::open() → write_txn() → HANGS +//! +//! Or during normal use when BufEnter fires: +//! track_access → frecency.track_access() → write_txn() → HANGS +//! +//! Reproduction: fork a child process that holds the LMDB write lock +//! indefinitely, then attempt to use the same database from the parent. +//! The parent's `write_txn()` blocks on the cross-process writer mutex. +//! +//! This test confirms that the current code has NO timeout or fallback when the +//! LMDB writer mutex is unavailable — making it vulnerable to indefinite hangs +//! whenever another process (fff-mcp, another neovim, or a crashed instance) +//! holds or has stuck the mutex. + +#![cfg(unix)] + +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::mpsc; +use std::time::Duration; + +use fff_search::frecency::FrecencyTracker; +use fff_search::query_tracker::QueryTracker; + +/// Returns whether `f` completes within `timeout`. +fn completes_within( + label: &'static str, + timeout: Duration, + f: impl FnOnce() + Send + 'static, +) -> bool { + let (tx, rx) = mpsc::channel::<()>(); + let _worker = std::thread::Builder::new() + .name(format!("deadlock-repro-{label}")) + .spawn(move || { + f(); + let _ = tx.send(()); + }) + .expect("spawn worker"); + + rx.recv_timeout(timeout).is_ok() +} + +/// Fork a child that opens the LMDB env and holds a write transaction +/// indefinitely (simulating a stuck/long-running process). Returns the +/// child PID so the parent can kill it during cleanup. +fn fork_child_holding_write_lock(db_path: &Path) -> libc::pid_t { + let db_path_str = db_path.to_str().unwrap().to_owned(); + + let mut pipe_fds: [libc::c_int; 2] = [0; 2]; + assert_eq!(unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }, 0); + let read_fd = pipe_fds[0]; + let write_fd = pipe_fds[1]; + + let child_pid = unsafe { libc::fork() }; + match child_pid { + -1 => panic!("fork() failed: {}", std::io::Error::last_os_error()), + 0 => { + // === CHILD PROCESS === + unsafe { libc::close(read_fd) }; + + let env = unsafe { + let mut opts = heed::EnvOpenOptions::new(); + opts.map_size(10 * 1024 * 1024); + opts.open(Path::new(&db_path_str)).expect("child: open env") + }; + + // Acquire the cross-process writer mutex via write_txn + let _wtxn = env.write_txn().expect("child: write_txn"); + + // Signal parent that the lock is held + unsafe { libc::write(write_fd, b"R".as_ptr() as *const libc::c_void, 1) }; + + // Hold the lock forever — parent will eventually kill us + loop { + unsafe { libc::pause() }; + } + } + pid => { + // === PARENT PROCESS === + unsafe { libc::close(write_fd) }; + + // Wait for child to confirm it holds the write lock + let mut buf = [0u8; 1]; + let n = unsafe { libc::read(read_fd, buf.as_mut_ptr() as *mut libc::c_void, 1) }; + assert_eq!(n, 1, "child didn't signal readiness"); + assert_eq!(buf[0], b'R'); + unsafe { libc::close(read_fd) }; + + pid + } + } +} + +/// Kill and reap the child process. +fn kill_child(pid: libc::pid_t) { + unsafe { + libc::kill(pid, libc::SIGKILL); + let mut status: libc::c_int = 0; + libc::waitpid(pid, &mut status, 0); + } +} + +/// Verify QueryTracker works correctly after close+reopen — the +/// open_database_safe path must find existing named databases via read txn. +#[test] +fn lmdb_reopen_finds_existing_databases() { + let tmp = tempfile::TempDir::new().unwrap(); + let db_path = tmp.path().join("lmdb_reopen"); + fs::create_dir_all(&db_path).unwrap(); + + // First open: creates the databases via write_txn fallback + { + let mut tracker = QueryTracker::open(&db_path).unwrap(); + let project = Path::new("/test/project"); + let file = Path::new("/test/project/src/main.rs"); + tracker + .track_query_completion("hello", project, file) + .unwrap(); + } + + // Second open: must find existing databases via read txn (no write_txn needed) + { + let tracker = QueryTracker::open(&db_path).unwrap(); + let project = Path::new("/test/project"); + let result = tracker.get_historical_query(project, 0).unwrap(); + assert_eq!( + result, + Some("hello".to_string()), + "Query history should persist across close/reopen" + ); + } +} + +/// Env var the test binary checks on startup. When set, the binary skips the +/// test harness and runs as a child worker instead. This avoids fork() in a +/// multi-threaded parent — which copies mutex/allocator state from threads +/// that no longer exist in the child and can deadlock heed/libc. +const CHILD_MODE_ENV: &str = "FFF_PARALLEL_OPEN_CLOSE_CHILD"; + +/// Runs before the test harness when `CHILD_MODE_ENV` is set. Re-exec of +/// the test binary lets us start child workers without forking from a +/// multi-threaded parent. +#[ctor::ctor] +fn maybe_enter_child_mode() { + if let Ok(spec) = std::env::var(CHILD_MODE_ENV) { + let code = run_child_from_spec(&spec); + std::process::exit(code); + } +} + +/// Spec format: `db_path|idx|iterations|writer(0|1)` +fn run_child_from_spec(spec: &str) -> i32 { + let parts: Vec<&str> = spec.split('|').collect(); + if parts.len() != 4 { + return CHILD_BAD_SPEC; + } + let db_path = parts[0]; + let idx: usize = match parts[1].parse() { + Ok(v) => v, + Err(_) => return CHILD_BAD_SPEC, + }; + let iterations: usize = match parts[2].parse() { + Ok(v) => v, + Err(_) => return CHILD_BAD_SPEC, + }; + let is_writer = parts[3] == "1"; + child_open_close_loop(db_path, iterations, idx, is_writer) +} + +const CHILD_OK: i32 = 0; +const CHILD_OPEN_FAILED: i32 = 10; +const CHILD_READ_FAILED: i32 = 11; +const CHILD_WRITE_FAILED: i32 = 12; +const CHILD_BAD_SPEC: i32 = 13; + +fn child_open_close_loop(db_path: &str, iterations: usize, idx: usize, is_writer: bool) -> i32 { + let project = Path::new("/test/project"); + for i in 0..iterations { + let tracker = match QueryTracker::open(Path::new(db_path)) { + Ok(t) => t, + Err(e) => { + eprintln!("child {idx} iter {i} reader open failed: {e:?}"); + return CHILD_OPEN_FAILED; + } + }; + if let Err(e) = tracker.get_historical_query(project, 0) { + eprintln!("child {idx} iter {i} read failed: {e:?}"); + return CHILD_READ_FAILED; + } + drop(tracker); + + if is_writer { + let mut tracker = match QueryTracker::open(Path::new(db_path)) { + Ok(t) => t, + Err(e) => { + eprintln!("child {idx} iter {i} writer open failed: {e:?}"); + return CHILD_OPEN_FAILED; + } + }; + let file = PathBuf::from(format!("/test/project/c{idx}_{i}.rs")); + if let Err(e) = tracker.track_query_completion(&format!("q{idx}_{i}"), project, &file) { + eprintln!("child {idx} iter {i} write failed: {e:?}"); + return CHILD_WRITE_FAILED; + } + drop(tracker); + } + } + CHILD_OK +} + +/// Spawn `n` child processes via `Command::new(current_exe)`. No fork, so +/// mutex/allocator state is not inherited. `writers` children also issue +/// writes; the rest only read. +fn spawn_open_close_children( + db_path: &Path, + n: usize, + writers: usize, + ops_per_child: usize, +) -> Vec { + assert!(writers <= n); + let exe = std::env::current_exe().expect("current_exe"); + let db_path_str = db_path.to_str().unwrap().to_owned(); + + (0..n) + .map(|idx| { + let is_writer = idx < writers; + let spec = format!( + "{db_path_str}|{idx}|{ops_per_child}|{}", + if is_writer { 1 } else { 0 } + ); + std::process::Command::new(&exe) + .env(CHILD_MODE_ENV, spec) + .env_remove("RUST_LOG") + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::inherit()) + .spawn() + .expect("spawn child") + }) + .collect() +} + +/// Wait for every child with a per-call deadline. On timeout, kill and reap +/// remaining children and return an Err describing the stuck set. +fn wait_all_with_deadline( + mut children: Vec, + deadline: std::time::Instant, +) -> Result<(), String> { + let mut failures: Vec<(u32, Option)> = Vec::new(); + let mut remaining: Vec = Vec::new(); + + for mut child in children.drain(..) { + loop { + match child.try_wait() { + Ok(Some(status)) => { + let code = status.code(); + if code != Some(CHILD_OK) { + failures.push((child.id(), code)); + } + break; + } + Ok(None) => { + if std::time::Instant::now() >= deadline { + remaining.push(child); + break; + } + std::thread::sleep(Duration::from_millis(25)); + } + Err(e) => { + failures.push((child.id(), None)); + let _ = e; + break; + } + } + } + } + + if !remaining.is_empty() { + let stuck: Vec = remaining.iter().map(|c| c.id()).collect(); + for child in &mut remaining { + let _ = child.kill(); + let _ = child.wait(); + } + return Err(format!( + "deadline exceeded; children still running: {stuck:?}" + )); + } + + if !failures.is_empty() { + return Err(format!("children failed: {failures:?}")); + } + Ok(()) +} + +/// Many processes open/close `QueryTracker` against the same DB path. +/// Readers only: seeds once, then spawns N reader children. +/// +/// heed 0.22 forbids opening the same env twice *within* one process +/// (EnvAlreadyOpened), so cross-process contention is the right axis. +#[test] +fn query_tracker_many_parallel_open_close_same_path_readers() { + let tmp = tempfile::TempDir::new().unwrap(); + let db_path = tmp.path().join("parallel_open_close_readers"); + fs::create_dir_all(&db_path).unwrap(); + + { + let mut tracker = QueryTracker::open(&db_path).unwrap(); + tracker + .track_query_completion( + "seed", + Path::new("/test/project"), + Path::new("/test/project/src/main.rs"), + ) + .unwrap(); + } + + const N: usize = 8; + const OPS: usize = 4; + + let children = spawn_open_close_children(&db_path, N, 0, OPS); + let deadline = std::time::Instant::now() + Duration::from_secs(30); + wait_all_with_deadline(children, deadline).expect("parallel open/close (readers)"); + + let tracker = QueryTracker::open(&db_path).expect("post-storm reopen"); + let project = Path::new("/test/project"); + let result = tracker.get_historical_query(project, 0).unwrap(); + assert_eq!( + result, + Some("seed".to_string()), + "Seed query should still be readable after parallel open/close storm" + ); +} + +/// Stronger variant: multiple processes race opens that both read AND write. +/// LMDB serializes writers via a cross-process mutex; test that serialization +/// makes forward progress and open/close pairs don't deadlock. +#[test] +fn query_tracker_parallel_open_write_close_same_path() { + let tmp = tempfile::TempDir::new().unwrap(); + let db_path = tmp.path().join("parallel_open_write_close"); + fs::create_dir_all(&db_path).unwrap(); + + { + let mut tracker = QueryTracker::open(&db_path).unwrap(); + tracker + .track_query_completion( + "seed", + Path::new("/test/project"), + Path::new("/test/project/src/main.rs"), + ) + .unwrap(); + } + + const N: usize = 4; + const WRITERS: usize = 4; + const OPS: usize = 3; + + let children = spawn_open_close_children(&db_path, N, WRITERS, OPS); + let deadline = std::time::Instant::now() + Duration::from_secs(30); + wait_all_with_deadline(children, deadline).expect("parallel open/write/close"); + + let tracker = QueryTracker::open(&db_path).expect("post-storm reopen"); + let project = Path::new("/test/project"); + let seed = tracker.get_historical_query(project, 0).unwrap(); + assert!( + seed.is_some(), + "Env unreadable after parallel open/write/close storm" + ); +} + +/// Within a single process, opening the same env path twice concurrently is +/// forbidden by heed — but a strict sequential open→use→drop→open loop must +/// succeed every iteration. Regression guard for the reopen path. +#[test] +fn query_tracker_sequential_reopen_loop_same_path() { + let tmp = tempfile::TempDir::new().unwrap(); + let db_path = tmp.path().join("sequential_reopen_loop"); + fs::create_dir_all(&db_path).unwrap(); + + { + let mut tracker = QueryTracker::open(&db_path).unwrap(); + tracker + .track_query_completion( + "seed", + Path::new("/test/project"), + Path::new("/test/project/src/main.rs"), + ) + .unwrap(); + } + + for i in 0..64 { + let mut tracker = QueryTracker::open(&db_path).expect("sequential reopen"); + let project = Path::new("/test/project"); + let file = PathBuf::from(format!("/test/project/iter_{i}.rs")); + tracker + .track_query_completion(&format!("iter_{i}"), project, &file) + .expect("sequential track"); + drop(tracker); + } + + let tracker = QueryTracker::open(&db_path).expect("final reopen"); + let project = Path::new("/test/project"); + assert!(tracker.get_historical_query(project, 0).unwrap().is_some()); +} + +/// When the frecency DB doesn't exist yet, `FrecencyTracker::open()` falls +/// through to `write_txn()` + `create_database()`. This blocks if another +/// process holds the writer mutex. This is the first-launch path. +/// +/// NOTE: this test is disabled because heed 0.22 appears to use a +/// try-then-create pattern for unnamed databases that doesn't always block. +/// The QueryTracker test above (named databases, always needs write_txn) +/// reliably demonstrates the same underlying issue. +#[test] +#[ignore = "heed 0.22 unnamed db creation may not require writer mutex in all cases"] +fn frecency_open_blocks_on_fresh_db_when_another_process_holds_write_lock() { + let tmp = tempfile::TempDir::new().unwrap(); + let db_path = tmp.path().join("frecency_fresh_deadlock"); + fs::create_dir_all(&db_path).unwrap(); + + let env = unsafe { + let mut opts = heed::EnvOpenOptions::new(); + opts.map_size(10 * 1024 * 1024); + opts.open(&db_path).unwrap() + }; + drop(env); + + let child_pid = fork_child_holding_write_lock(&db_path); + + let db_path_clone = db_path.clone(); + let completed = completes_within( + "FrecencyTracker::open (fresh db) while writer held", + Duration::from_secs(3), + move || { + let _result = FrecencyTracker::open(&db_path_clone); + }, + ); + + kill_child(child_pid); + + assert!( + !completed, + "Expected FrecencyTracker::open() on a fresh DB to block (writer mutex \ + held by another process), but it completed." + ); +} diff --git a/crates/fff-core/tests/new_directory_watcher_test.rs b/crates/fff-core/tests/new_directory_watcher_test.rs new file mode 100644 index 0000000..9cb9fc1 --- /dev/null +++ b/crates/fff-core/tests/new_directory_watcher_test.rs @@ -0,0 +1,539 @@ +//! Integration test: verifying that the background watcher dynamically detects +//! newly created directories and picks up files written inside them. +//! +//! This covers the NonRecursive watching behavior where: +//! 1. The watcher starts with watches on directories discovered during the +//! initial scan. +//! 2. A brand-new subdirectory is created at runtime (after the scan). +//! 3. The watcher's event handler detects the directory Create event, +//! collects it, and sends it to the owner thread via `watch_tx`. +//! 4. The owner thread adds a NonRecursive watch on the new directory and +//! does a flat (non-recursive) read_dir to inject files that already +//! exist (race-window coverage). +//! 5. Files created *after* the watch is established are picked up via +//! normal event delivery. +//! +//! The test uses the real `BackgroundWatcher` (via `watch: true`) and polls +//! the picker until the expected files appear or a timeout expires. + +use std::fs; +use std::path::Path; +use std::process::Command; +use std::time::{Duration, Instant}; +use tempfile::TempDir; + +use fff_search::file_picker::{FFFMode, FilePicker}; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{ + FilePickerOptions, PaginationArgs, QueryParser, SharedFilePicker, SharedFrecency, +}; + +// ═══════════════════════════════════════════════════════════════════════ +// Helpers +// ═══════════════════════════════════════════════════════════════════════ + +fn git_run(dir: &Path, args: &[&str]) { + let out = Command::new("git") + .args(args) + .current_dir(dir) + .env("GIT_AUTHOR_NAME", "test") + .env("GIT_AUTHOR_EMAIL", "test@test.com") + .env("GIT_COMMITTER_NAME", "test") + .env("GIT_COMMITTER_EMAIL", "test@test.com") + .output() + .unwrap_or_else(|e| panic!("git {:?} failed: {}", args, e)); + assert!( + out.status.success(), + "git {:?} failed: {}", + args, + String::from_utf8_lossy(&out.stderr) + ); +} + +fn git_init_and_commit(dir: &Path) { + git_run(dir, &["init", "-b", "main"]); + git_run(dir, &["add", "-A"]); + git_run(dir, &["commit", "-m", "initial"]); +} + +fn make_watched_picker(base: &Path) -> (SharedFilePicker, SharedFrecency) { + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::noop(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + mode: FFFMode::Neovim, + watch: true, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + (shared_picker, shared_frecency) +} + +/// Wait for the initial scan + watcher to be fully ready. +fn wait_ready(shared_picker: &SharedFilePicker) { + assert!( + shared_picker.wait_for_scan(Duration::from_secs(10)), + "Timed out waiting for initial scan" + ); + assert!( + shared_picker.wait_for_watcher(Duration::from_secs(10)), + "Timed out waiting for watcher" + ); +} + +/// Poll the picker until `predicate` returns true or timeout expires. +/// Returns the elapsed duration if successful, panics on timeout. +fn poll_until( + shared_picker: &SharedFilePicker, + timeout: Duration, + description: &str, + predicate: impl Fn(&FilePicker) -> bool, +) -> Duration { + let start = Instant::now(); + loop { + { + let guard = shared_picker.read().unwrap(); + if let Some(ref picker) = *guard { + if predicate(picker) { + return start.elapsed(); + } + } + } + if start.elapsed() >= timeout { + // One final attempt to give a useful error message. + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let file_count = picker.get_files().len(); + let paths: Vec = picker + .get_files() + .iter() + .map(|f| f.relative_path(picker)) + .collect(); + panic!( + "Timed out after {:?} waiting for: {}\n\ + Current file count: {}\n\ + Current files: {:?}", + timeout, description, file_count, paths + ); + } + std::thread::sleep(Duration::from_millis(50)); + } +} + +fn grep_plain_count(picker: &FilePicker, query: &str) -> usize { + let parsed = parse_grep_query(query); + let opts = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 500, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + picker.grep(&parsed, &opts).matches.len() +} + +fn fuzzy_search_paths(picker: &FilePicker, query: &str) -> Vec { + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let result = picker.fuzzy_search( + &parsed, + None, + fff_search::FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 200, + }, + ..Default::default() + }, + ); + result + .items + .iter() + .map(|f| f.relative_path(picker)) + .collect() +} + +/// Debounce timeout in the watcher is 250ms. Events need to propagate through +/// the debouncer, the owner thread park loop (1s), and the picker write lock. +/// We use a generous timeout for CI environments. +const WATCHER_TIMEOUT: Duration = Duration::from_secs(10); + +// ═══════════════════════════════════════════════════════════════════════ +// Tests +// ═══════════════════════════════════════════════════════════════════════ + +/// Create a new directory and immediately write a file inside it. +/// The file is written before the watch is registered, so the flat +/// inject_existing_files scan in the owner thread must catch it. +#[test] +fn new_directory_and_file_detected_by_watcher() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path().canonicalize().unwrap(); + + // Seed the repo with some initial files so the scan has something. + fs::create_dir_all(base.join("src")).unwrap(); + fs::write( + base.join("src/main.rs"), + "fn main() { println!(\"INITIAL_MARKER\"); }\n", + ) + .unwrap(); + fs::write(base.join("README.md"), "# Test project\n").unwrap(); + + git_init_and_commit(&base); + + let (shared_picker, _frecency) = make_watched_picker(&base); + wait_ready(&shared_picker); + + // Sanity: initial file is indexed. + poll_until( + &shared_picker, + Duration::from_secs(5), + "initial file src/main.rs indexed", + |picker| { + picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("main.rs")) + }, + ); + + // Create a new directory and write a file into it immediately. + // The file exists before the watch is registered — inject_existing_files + // in the owner thread catches it via a flat read_dir. + let new_dir = base.join("src/components"); + fs::create_dir_all(&new_dir).unwrap(); + fs::write( + new_dir.join("button.rs"), + "pub struct Button;\nconst TOKEN: &str = \"NEW_DIR_BUTTON_TOKEN\";\n", + ) + .unwrap(); + + // Wait for the watcher to detect the new directory + file. + let elapsed = poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "file src/components/button.rs in new directory", + |picker| { + picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("button.rs")) + }, + ); + eprintln!( + " New directory + file detected in {:.0}ms", + elapsed.as_secs_f64() * 1000.0 + ); + + // Also verify via grep that the content is accessible. + poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "grep finds NEW_DIR_BUTTON_TOKEN", + |picker| grep_plain_count(picker, "NEW_DIR_BUTTON_TOKEN") >= 1, + ); + + // And via fuzzy search. + poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "fuzzy search finds button.rs", + |picker| { + let results = fuzzy_search_paths(picker, "button"); + results.iter().any(|p| p.contains("button.rs")) + }, + ); +} + +/// Create a new directory, then create files AFTER a delay to ensure the +/// watch was established on the directory. +#[test] +fn file_created_after_directory_watch_established() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path().canonicalize().unwrap(); + + fs::create_dir_all(base.join("lib")).unwrap(); + fs::write(base.join("lib/utils.rs"), "pub fn helper() {}\n").unwrap(); + + git_init_and_commit(&base); + + let (shared_picker, _frecency) = make_watched_picker(&base); + wait_ready(&shared_picker); + + // Create the directory first, wait for the watcher to register it. + let new_dir = base.join("lib/models"); + fs::create_dir(&new_dir).unwrap(); + + // Wait long enough for the debouncer to flush + owner thread to add watch. + std::thread::sleep(Duration::from_millis(2000)); + + // Now write a file into the already-watched directory. + fs::write( + new_dir.join("user.rs"), + "pub struct User { name: String }\nconst TOKEN: &str = \"POST_WATCH_USER_TOKEN\";\n", + ) + .unwrap(); + + let elapsed = poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "file lib/models/user.rs created after directory watch", + |picker| { + picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("user.rs")) + }, + ); + eprintln!( + " Post-watch file detected in {:.0}ms", + elapsed.as_secs_f64() * 1000.0 + ); + + // Grep sanity. + poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "grep finds POST_WATCH_USER_TOKEN", + |picker| grep_plain_count(picker, "POST_WATCH_USER_TOKEN") >= 1, + ); +} + +/// Create a deeply nested directory tree all at once with create_dir_all +/// and write a file at the leaf. The watcher must detect the top-level +/// directory via the parent's watch, inject_existing_files finds the file +/// at the leaf (and intermediate dirs get their own watches from Create +/// events on subsequent levels). +#[test] +fn deeply_nested_new_directories_detected() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path().canonicalize().unwrap(); + + fs::write(base.join("root.txt"), "root file\n").unwrap(); + + git_init_and_commit(&base); + + let (shared_picker, _frecency) = make_watched_picker(&base); + wait_ready(&shared_picker); + + // Create each level one at a time, waiting for each watch to register. + // inject_existing_files is flat (non-recursive), so deeply nested dirs + // need each parent to be watched before we can see files at the leaf. + fs::create_dir(base.join("app")).unwrap(); + std::thread::sleep(Duration::from_millis(2000)); + + fs::create_dir(base.join("app/services")).unwrap(); + std::thread::sleep(Duration::from_millis(2000)); + + fs::create_dir(base.join("app/services/auth")).unwrap(); + // Write the file immediately — inject_existing_files catches it. + fs::write( + base.join("app/services/auth/jwt.rs"), + "pub fn verify_token() {}\nconst TOKEN: &str = \"DEEP_NESTED_JWT_TOKEN\";\n", + ) + .unwrap(); + + let elapsed = poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "deeply nested file app/services/auth/jwt.rs", + |picker| { + picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("jwt.rs")) + }, + ); + eprintln!( + " Deeply nested file detected in {:.0}ms", + elapsed.as_secs_f64() * 1000.0 + ); + + // Verify content is grepable. + poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "grep finds DEEP_NESTED_JWT_TOKEN", + |picker| grep_plain_count(picker, "DEEP_NESTED_JWT_TOKEN") >= 1, + ); + + // Now create a sibling at the same depth — the parent (app/services) + // is already watched, so this just needs the flat inject. + let sibling_dir = base.join("app/services/database"); + fs::create_dir(&sibling_dir).unwrap(); + fs::write( + sibling_dir.join("pool.rs"), + "pub struct ConnectionPool;\nconst TOKEN: &str = \"SIBLING_POOL_TOKEN\";\n", + ) + .unwrap(); + + let elapsed = poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "sibling nested file app/services/database/pool.rs", + |picker| { + picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("pool.rs")) + }, + ); + eprintln!( + " Sibling nested file detected in {:.0}ms", + elapsed.as_secs_f64() * 1000.0 + ); + + poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "grep finds SIBLING_POOL_TOKEN", + |picker| grep_plain_count(picker, "SIBLING_POOL_TOKEN") >= 1, + ); +} + +/// Create a new directory and immediately burst-write multiple files. +/// inject_existing_files catches all of them in one flat read_dir. +#[test] +fn burst_file_creation_in_new_directory() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path().canonicalize().unwrap(); + + fs::create_dir_all(base.join("src")).unwrap(); + fs::write(base.join("src/lib.rs"), "// lib\n").unwrap(); + + git_init_and_commit(&base); + + let (shared_picker, _frecency) = make_watched_picker(&base); + wait_ready(&shared_picker); + + // Create a new directory and immediately write 5 files. + let batch_dir = base.join("src/batch"); + fs::create_dir(&batch_dir).unwrap(); + + let file_count = 5; + for i in 0..file_count { + fs::write( + batch_dir.join(format!("item_{i}.rs")), + format!("pub struct Item{i};\nconst TOKEN: &str = \"BATCH_ITEM_{i}\";\n"), + ) + .unwrap(); + } + + // Wait for ALL files to appear. + let elapsed = poll_until( + &shared_picker, + WATCHER_TIMEOUT, + &format!("all {file_count} batch files in src/batch/"), + |picker| { + let batch_count = picker + .get_files() + .iter() + .filter(|f| { + let p = f.relative_path(picker); + p.starts_with("src/batch/") || p.starts_with("src\\batch\\") + }) + .count(); + batch_count >= file_count + }, + ); + eprintln!( + " All {} burst files detected in {:.0}ms", + file_count, + elapsed.as_secs_f64() * 1000.0 + ); + + // Verify each file's content is grepable. + for i in 0..file_count { + let token = format!("BATCH_ITEM_{i}"); + poll_until( + &shared_picker, + WATCHER_TIMEOUT, + &format!("grep finds {token}"), + |picker| grep_plain_count(picker, &token) >= 1, + ); + } +} + +/// Verify that gitignored directories created at runtime are NOT watched +/// and their files do NOT appear in the index. +#[test] +fn gitignored_new_directory_excluded() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path().canonicalize().unwrap(); + + fs::write(base.join("main.rs"), "fn main() {}\n").unwrap(); + // Ignore the build/ directory. + fs::write(base.join(".gitignore"), "build/\n").unwrap(); + + git_init_and_commit(&base); + + let (shared_picker, _frecency) = make_watched_picker(&base); + wait_ready(&shared_picker); + + // Create a gitignored directory with files. + let ignored_dir = base.join("build"); + fs::create_dir(&ignored_dir).unwrap(); + fs::write( + ignored_dir.join("output.rs"), + "const TOKEN: &str = \"IGNORED_BUILD_TOKEN\";\n", + ) + .unwrap(); + + // Also create a non-ignored directory to confirm the watcher works. + let good_dir = base.join("src"); + fs::create_dir(&good_dir).unwrap(); + fs::write( + good_dir.join("app.rs"), + "const TOKEN: &str = \"GOOD_SRC_TOKEN\";\n", + ) + .unwrap(); + + // Wait for the non-ignored file to appear (proves watcher is working). + poll_until( + &shared_picker, + WATCHER_TIMEOUT, + "non-ignored file src/app.rs appears", + |picker| { + picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("app.rs")) + }, + ); + + // Give extra time for any straggler events from the ignored dir. + std::thread::sleep(Duration::from_secs(2)); + + // The gitignored file must NOT be in the index. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let has_ignored = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).contains("output.rs")); + assert!( + !has_ignored, + "Gitignored file build/output.rs should NOT be in the index" + ); + + let grep_count = grep_plain_count(picker, "IGNORED_BUILD_TOKEN"); + assert_eq!(grep_count, 0, "Gitignored content should NOT be grepable"); + } +} diff --git a/crates/fff-core/tests/overflow_frecency_segfault.rs b/crates/fff-core/tests/overflow_frecency_segfault.rs new file mode 100644 index 0000000..1a9af6a --- /dev/null +++ b/crates/fff-core/tests/overflow_frecency_segfault.rs @@ -0,0 +1,93 @@ +use fff_search::file_picker::{FFFMode, FilePicker}; +use fff_search::{ + FFFQuery, FilePickerOptions, FileSearchConfig, FrecencyTracker, FuzzySearchOptions, + SharedFilePicker, SharedFrecency, +}; +use std::fs; +use std::path::Path; +use std::time::Duration; +use tempfile::TempDir; + +// regression pinning test: if base count=0 updating frecency should not panic +#[test] +fn update_single_file_frecency_on_overflow_file_does_not_segfault() { + let base = TempDir::new().expect("mktemp base"); + let db = TempDir::new().expect("mktemp db"); + + // EMPTY base tree. The initial scan indexes zero files, so `base_count == 0` + // and the base path arena is an empty store whose pointer is the dangling, + // 16-byte-aligned `0x10`. This is the exact state observed at the crash: + // base_count=0 is_overflow=true base_arena=0x10 overflow_arena=0x9f5400000 + let base_path = base.path().canonicalize().expect("canonicalize base"); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + let tracker = FrecencyTracker::open(db.path().join("frecency.mdb")).expect("open frecency db"); + shared_frecency.init(tracker).expect("init frecency"); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base_path.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: false, + // AI mode is the mode that drives per-file frecency updates from the + // watcher; we exercise the same picker method directly. + mode: FFFMode::Ai, + // No watcher: we add an overflow file and update frecency by hand so + // the repro is deterministic and timing-independent. + watch: false, + ..Default::default() + }, + ) + .expect("FilePicker::new_with_shared_state"); + + assert!( + shared_picker.wait_for_scan(Duration::from_secs(10)), + "initial scan did not complete" + ); + + // Create ONE new file after the (empty) scan. It is added to the overflow + // arena (`FileItem::is_overflow()` is true), with valid overflow chunk + // indices into the overflow arena. + let new_file = base_path.join("created_after_scan.txt"); + fs::write(&new_file, "hello\n").unwrap(); + + { + let mut guard = shared_picker.write().expect("picker write lock"); + let picker: &mut FilePicker = guard.as_mut().expect("picker initialized"); + picker.handle_create_or_modify(&new_file); + } + + // simulate watcher events + { + let frecency_guard = shared_frecency.read().expect("frecency read lock"); + let frecency = frecency_guard.as_ref().expect("frecency initialized"); + + let mut guard = shared_picker.write().expect("picker write lock"); + let picker: &mut FilePicker = guard.as_mut().expect("picker initialized"); + + let _ = frecency.track_access(new_file.as_path()); + let _ = picker.update_single_file_frecency(new_file.as_path(), frecency); + } + + // Reaching here means the overflow file was read through the correct arena. + let _: &Path = base_path.as_path(); + + { + let mut guard = shared_picker.write().expect("picker write lock"); + let picker: &mut FilePicker = guard.as_mut().expect("picker initialized"); + let results = picker.fuzzy_search( + &FFFQuery::parse("created_after_scan *.txt", FileSearchConfig), + None, + FuzzySearchOptions::default(), + ); + + assert_eq!(results.total_matched, 1); + } + + drop(shared_picker); + drop(shared_frecency); +} diff --git a/crates/fff-core/tests/path_separator_constraint_test.rs b/crates/fff-core/tests/path_separator_constraint_test.rs new file mode 100644 index 0000000..9519ef8 --- /dev/null +++ b/crates/fff-core/tests/path_separator_constraint_test.rs @@ -0,0 +1,248 @@ +//! Regression test for https://github.com/dmtrKovalenko/fff/issues/381 +//! +//! Directory (`PathSegment`) and file-path (`FilePath`) constraints must +//! return results on every platform. Indexed paths on Windows use native +//! backslash separators, so constraint matching has to accept either `/` +//! or `\\` as a path boundary. + +use std::fs; +use std::path::Path; +use tempfile::TempDir; + +use fff_search::file_picker::FilePicker; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{Constraint, FilePickerOptions, FuzzySearchOptions, PaginationArgs, QueryParser}; + +fn create_picker(base: &Path, specs: &[(&str, &str)]) -> FilePicker { + for (rel, contents) in specs { + let full_path = base.join(rel); + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).unwrap(); + } + fs::write(&full_path, contents).unwrap(); + } + let mut picker = FilePicker::new(FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + watch: false, + ..Default::default() + }) + .expect("failed to create FilePicker"); + picker.collect_files().expect("failed to collect files"); + picker +} + +fn plain_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 200, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +fn fuzzy_search_paths(picker: &FilePicker, query: &str) -> Vec { + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 200, + }, + ..Default::default() + }, + ); + result + .items + .iter() + .map(|f| f.relative_path(picker)) + .collect() +} + +/// Treat a relative path as a sequence of components regardless of the +/// native separator so assertions are portable across Linux, macOS, Windows. +fn has_segment(path: &str, segment: &str) -> bool { + path.split(['/', '\\']).any(|s| s == segment) +} + +/// `grep handleRequest src/` — PathSegment constraint must match a nested +/// `src` directory on every platform. +#[test] +fn grep_with_path_segment_constraint_nested() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("app/modules/src/services/handler.lua", "handleRequest()\n"), + ("app/modules/lib/util.lua", "handleRequest()\n"), + ("src/main.rs", "fn handleRequest() {}\n"), + ], + ); + + let parsed = parse_grep_query("handleRequest src/"); + let result = picker.grep(&parsed, &plain_opts()); + + let matched_paths: Vec = result + .files + .iter() + .map(|f| f.relative_path(&picker)) + .collect(); + assert_eq!( + result.matches.len(), + 2, + "expected matches in two src/ files, got {matched_paths:?}" + ); + for p in &matched_paths { + assert!( + has_segment(p, "src"), + "every matched file must live under a `src` segment, got {p:?}" + ); + } +} + +/// `multi_grep` with a `PathSegment` constraint. +#[test] +fn multi_grep_with_path_segment_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ( + "app/modules/src/controller.lua", + "handleRequest\nprocessJob\n", + ), + ("app/modules/lib/helper.lua", "handleRequest\n"), + ("app/src/legacy.lua", "processJob\n"), + ], + ); + + let constraints = [Constraint::PathSegment("src")]; + let patterns = ["handleRequest", "processJob"]; + let result = picker.multi_grep(&patterns, &constraints, &plain_opts()); + + assert!( + !result.matches.is_empty(), + "multi_grep with `src/` constraint should return matches" + ); + + let matched_paths: Vec = result + .files + .iter() + .map(|f| f.relative_path(&picker)) + .collect(); + for p in &matched_paths { + assert!( + has_segment(p, "src"), + "every matched file must live under a `src` segment, got {p:?}" + ); + } + assert!(matched_paths.iter().any(|p| p.contains("controller.lua"))); + assert!(matched_paths.iter().any(|p| p.contains("legacy.lua"))); +} + +/// Fuzzy search (`find_files src/ Controller`) must apply the path-segment +/// filter to paths stored during indexing. +#[test] +fn fuzzy_search_with_path_segment_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("app/modules/src/services/BaseController.lua", "base\n"), + ("app/modules/src/services/UserController.lua", "user\n"), + ("app/modules/lib/BaseController.lua", "lib base\n"), + ("tests/src/MockController.lua", "mock\n"), + ], + ); + + let results = fuzzy_search_paths(&picker, "src/ Controller"); + + assert!( + !results.is_empty(), + "fuzzy search with `src/` constraint should return results" + ); + for p in &results { + assert!( + has_segment(p, "src"), + "every result must live under `src`, got {p:?}" + ); + } + assert!(results.iter().any(|p| p.contains("BaseController"))); + assert!(results.iter().any(|p| p.contains("UserController"))); + assert!(results.iter().any(|p| p.contains("MockController"))); +} + +/// `FilePath` suffix constraint must match stored paths even when components +/// are separated by the platform-native separator during indexing. +#[test] +fn multi_grep_with_file_path_suffix_constraint() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("app/modules/src/services/handler.lua", "handleRequest\n"), + ("other/src/services/handler.lua", "handleRequest\n"), + ("app/modules/src/services/other.lua", "handleRequest\n"), + ], + ); + + let constraints = [Constraint::FilePath("services/handler.lua")]; + let patterns = ["handleRequest"]; + let result = picker.multi_grep(&patterns, &constraints, &plain_opts()); + + let paths: Vec = result + .files + .iter() + .map(|f| f.relative_path(&picker)) + .collect(); + assert_eq!( + paths.len(), + 2, + "expected two matches for services/handler.lua, got {paths:?}" + ); + for p in &paths { + let ends_with_services_handler = + p.ends_with("services/handler.lua") || p.ends_with("services\\handler.lua"); + assert!( + ends_with_services_handler, + "matched path must end with services/handler.lua, got {p:?}" + ); + } +} + +/// Glob constraints must match native Windows paths — the picker normalises +/// separators when handing paths to the glob matcher. +#[test] +fn fuzzy_search_with_glob_constraint_matches_on_windows_paths() { + let tmp = TempDir::new().unwrap(); + let picker = create_picker( + tmp.path(), + &[ + ("app/src/components/Button.lua", "\n"), + ("app/src/services/handler.lua", "\n"), + ("app/lib/components/Ignored.lua", "\n"), + ], + ); + + let results = fuzzy_search_paths(&picker, "**/src/**/*.lua"); + assert!( + results.iter().any(|p| p.contains("Button.lua")), + "glob `**/src/**/*.lua` must match files below any `src/`, got {results:?}" + ); + assert!( + results.iter().any(|p| p.contains("handler.lua")), + "glob `**/src/**/*.lua` must match services/handler.lua, got {results:?}" + ); +} diff --git a/crates/fff-core/tests/real_binary_fixtures.rs b/crates/fff-core/tests/real_binary_fixtures.rs new file mode 100644 index 0000000..e4da069 --- /dev/null +++ b/crates/fff-core/tests/real_binary_fixtures.rs @@ -0,0 +1,214 @@ +//! Real-world binary fixture regression. +//! +//! Reproduces the exact bug chain we hit with `codex_view` (4.5 MB ELF, no +//! extension) and `codex_view.codex` (127 KB, unknown extension): both are +//! binary by content but slip past extension-only triage, so a plain grep +//! used to surface their NUL-laden bytes as "text" matches. +//! +//! The fixtures live in `tests/fixtures/binaries/`. `MARKER` is a string that +//! is present (as raw bytes) in BOTH binaries — the test first asserts that, +//! then drops the two binaries plus a single plain-text file containing the +//! same marker into a closed temp dir and greps for it. Only the text file may +//! come back; if binary detection ever regresses, a binary file re-enters the +//! results and this test fails. + +use std::fs; +use std::path::PathBuf; +use std::time::Duration; + +use fff_search::file_picker::{FFFMode, FilePicker}; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{FilePickerOptions, SharedFilePicker, SharedFrecency}; + +const MARKER: &str = "__jai_runtime_init"; + +fn fixtures_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/binaries") +} + +fn plain_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 200, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +#[test] +fn real_binary_fixtures_are_detected_and_excluded_from_grep() { + let fixtures = fixtures_dir(); + let large = fixtures.join("codex_view"); // 4.5 MB ELF, no extension (> 2 MB) + let small = fixtures.join("codex_view.codex"); // 127 KB, unknown extension (< 2 MB) + + assert!( + large.exists() && small.exists(), + "missing binary fixtures in {}", + fixtures.display() + ); + + // Both fixtures must really contain the marker bytes, otherwise the grep + // exclusion assertion below would be vacuous. + let large_bytes = fs::read(&large).unwrap(); + let small_bytes = fs::read(&small).unwrap(); + assert!( + contains_subslice(&large_bytes, MARKER.as_bytes()), + "fixture codex_view no longer contains the marker {MARKER:?}" + ); + assert!( + contains_subslice(&small_bytes, MARKER.as_bytes()), + "fixture codex_view.codex no longer contains the marker {MARKER:?}" + ); + // Sanity on the size split that drives the two distinct code paths. + assert!( + large_bytes.len() > 2 * 1024 * 1024, + "codex_view must exceed the 2 MB non-indexable threshold" + ); + assert!( + small_bytes.len() < 2 * 1024 * 1024, + "codex_view.codex must stay under the 2 MB bigram cap" + ); + + // Closed environment: the two real binaries + one plain-text file that + // legitimately contains the marker. + let tmp = tempfile::TempDir::new().unwrap(); + let base = tmp.path(); + fs::copy(&large, base.join("codex_view")).unwrap(); + fs::copy(&small, base.join("codex_view.codex")).unwrap(); + fs::write( + base.join("marker.txt"), + format!("the only legitimate hit lives here: {MARKER}\n"), + ) + .unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("failed to create FilePicker"); + + assert!( + shared_picker.wait_for_indexing_complete(Duration::from_secs(10)), + "indexing/post-scan did not complete in time — binary classification may not have run yet" + ); + + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + // Both binaries must be classified binary. + for name in ["codex_view", "codex_view.codex"] { + let flagged = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).ends_with(name) && f.is_binary()); + assert!(flagged, "{name} must be flagged is_binary"); + } + + // we need to make sure that marker.txt ONLY can match as we have to match + // grep as binaries are excluded from the matching process + let parsed = parse_grep_query(MARKER); + let result = picker.grep(&parsed, &plain_opts()); + + let matched: Vec = result + .files + .iter() + .map(|f| f.relative_path(picker)) + .collect(); + + assert_eq!( + result.files.len(), + 1, + "exactly one file should match {MARKER:?}, got: {matched:?}" + ); + assert!( + matched[0].ends_with("marker.txt"), + "the only match must be marker.txt, got {:?}", + matched[0] + ); +} + +/// Tiny substring search over raw bytes (the marker may be surrounded by NULs). +fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool { + if needle.is_empty() || haystack.len() < needle.len() { + return false; + } + haystack + .windows(needle.len()) + .any(|window| window == needle) +} + +/// Deterministic regression for the Windows-CI failure where `codex_view` +/// (a >2 MB no-extension binary) was not flagged `is_binary`. Root cause was a +/// readiness-signal gap: `scanning` was cleared before `post_scan_indexing_active` +/// was set, so `wait_for_indexing_complete` could return before the binary sniff +/// ran. Uses synthetic fixtures (no repo/fixture dependency) covering both the +/// >2 MB non-indexable sniff path and the <2 MB bigram path, repeated to stress +/// the signal ordering. With the fix it must pass every iteration. +#[test] +fn binary_classification_done_before_indexing_wait_returns() { + const ITERATIONS: usize = 8; + // NUL bytes => `detect_binary_content` classifies as binary on every path. + let large = vec![0u8; 3 * 1024 * 1024]; // > 2 MB -> non-indexable sniff + let small = vec![0u8; 64 * 1024]; // < 2 MB -> bigram path + + for iteration in 0..ITERATIONS { + let tmp = tempfile::TempDir::new().unwrap(); + let base = tmp.path(); + fs::write(base.join("large_binary_no_ext"), &large).unwrap(); + fs::write(base.join("small.unknownext"), &small).unwrap(); + fs::write(base.join("readme.txt"), "hello world\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("failed to create FilePicker"); + + assert!( + shared_picker.wait_for_indexing_complete(Duration::from_secs(10)), + "iteration {iteration}: indexing/post-scan did not complete in time" + ); + + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + for name in ["large_binary_no_ext", "small.unknownext"] { + let flagged = picker + .get_files() + .iter() + .any(|f| f.relative_path(picker).ends_with(name) && f.is_binary()); + assert!( + flagged, + "iteration {iteration}: {name} must be flagged is_binary once \ + wait_for_indexing_complete returns" + ); + } + } +} diff --git a/crates/fff-core/tests/watcher_stop_under_lock.rs b/crates/fff-core/tests/watcher_stop_under_lock.rs new file mode 100644 index 0000000..42bc9fe --- /dev/null +++ b/crates/fff-core/tests/watcher_stop_under_lock.rs @@ -0,0 +1,165 @@ +//! Regression test: stopping the background watcher while the caller +//! holds the [`SharedFilePicker`] write lock must NOT deadlock. +//! +//! There are two lock-ordering hazards the watcher has to handle: +//! +//! 1. The debouncer's event thread calls our handler, which wants +//! `shared_picker.write()` to apply events. `stop()` used to +//! `join()` that thread under the caller's write guard. +//! +//! 2. The owner thread registers new-directory watches and injects +//! their existing files. Previously it held the debouncer mutex +//! across `shared_picker.write()`, while `stop()` takes the +//! debouncer mutex under the caller's write guard — inverse +//! lock orders, classic deadlock. +//! +//! macOS FSEvents is the reliable reproducer for (1) because fresh +//! `fs::write()` calls inside a just-watched temp dir queue events +//! faster than the debounce tick can drain them. Creating new +//! subdirectories exercises (2) via the owner thread's `watch_tx`. + +use std::fs; +use std::sync::mpsc; +use std::time::Duration; +use tempfile::TempDir; + +use fff_search::file_picker::{FFFMode, FilePicker}; +use fff_search::{FilePickerOptions, SharedFilePicker, SharedFrecency}; + +/// Run `f` on a worker thread, require it to finish within `timeout`, +/// panic with `msg` otherwise. The caller gets to describe what the +/// worker is doing so a hung test produces an actionable message. +fn run_with_deadlock_guard( + msg: &'static str, + timeout: Duration, + f: impl FnOnce() + Send + 'static, +) { + let (done_tx, done_rx) = mpsc::channel::<()>(); + let worker = std::thread::Builder::new() + .name("deadlock-guard-worker".into()) + .spawn(move || { + f(); + let _ = done_tx.send(()); + }) + .expect("spawn worker"); + + match done_rx.recv_timeout(timeout) { + Ok(()) => {} + Err(_) => panic!("{msg}"), + } + worker.join().expect("worker panicked"); +} + +fn make_watched_picker(base: &std::path::Path) -> (SharedFilePicker, SharedFrecency) { + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: false, + mode: FFFMode::Neovim, + watch: true, + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + assert!( + shared_picker.wait_for_scan(Duration::from_secs(10)), + "initial scan never completed" + ); + assert!( + shared_picker.wait_for_watcher(Duration::from_secs(10)), + "watcher never installed" + ); + + (shared_picker, shared_frecency) +} + +/// Hazard (1): debouncer event handler is waiting on `shared_picker.write()` +/// while the caller joins it from under the same guard. +#[test] +fn stop_background_monitor_under_write_lock_does_not_deadlock_file_events() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path().to_path_buf(); + + for i in 0..4 { + fs::write(base.join(format!("file_{i}.txt")), format!("seed {i}\n")).unwrap(); + } + + let (shared_picker, _shared_frecency) = make_watched_picker(&base); + + // Produce enough filesystem churn that the debouncer has events + // queued and is likely mid-handler by the time we call stop. + for round in 0..8 { + for i in 0..4 { + let path = base.join(format!("file_{i}.txt")); + fs::write(&path, format!("edit {round}-{i}\n")).unwrap(); + } + } + + // Give the kernel time to deliver events into the debouncer queue + // (50 ms = default debouncer tick). + std::thread::sleep(Duration::from_millis(60)); + + let sp = shared_picker.clone(); + run_with_deadlock_guard( + "stop_background_monitor() deadlocked under shared_picker.write() — \ + the debouncer thread is likely waiting on the same write lock \ + while we join it", + Duration::from_secs(5), + move || { + let mut guard = sp.write().expect("write lock"); + if let Some(ref mut picker) = *guard { + picker.stop_background_monitor(); + } + }, + ); +} + +/// Hazard (2): owner thread holds the debouncer mutex while waiting +/// on `shared_picker.write()`, and `stop()` takes the debouncer mutex +/// under the caller's write guard. +#[test] +fn stop_background_monitor_under_write_lock_does_not_deadlock_new_dirs() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path().to_path_buf(); + + fs::write(base.join("seed.txt"), "seed\n").unwrap(); + + let (shared_picker, _shared_frecency) = make_watched_picker(&base); + + // Create a burst of new subdirectories with files inside. On Linux + // the watcher event thread sends each new dir to `watch_tx`, and + // the owner thread processes them (taking the debouncer mutex + + // `shared_picker.write()`). On macOS the owner thread still runs + // `track_files_from_new_directories`, which takes the write lock. + for d in 0..8 { + let sub = base.join(format!("sub_{d}")); + fs::create_dir(&sub).unwrap(); + for f in 0..4 { + fs::write(sub.join(format!("f_{f}.txt")), format!("{d}-{f}\n")).unwrap(); + } + } + + std::thread::sleep(Duration::from_millis(120)); + + let sp = shared_picker.clone(); + run_with_deadlock_guard( + "stop_background_monitor() deadlocked under shared_picker.write() — \ + the watcher owner thread is likely holding the debouncer mutex and \ + waiting on the same write lock while we try to take the debouncer \ + mutex to tear it down", + Duration::from_secs(5), + move || { + let mut guard = sp.write().expect("write lock"); + if let Some(ref mut picker) = *guard { + picker.stop_background_monitor(); + } + }, + ); +} diff --git a/crates/fff-core/tests/watcher_thread_lifecycle_test.rs b/crates/fff-core/tests/watcher_thread_lifecycle_test.rs new file mode 100644 index 0000000..c7624f9 --- /dev/null +++ b/crates/fff-core/tests/watcher_thread_lifecycle_test.rs @@ -0,0 +1,197 @@ +#![cfg(target_os = "linux")] + +use std::fs; +use std::path::PathBuf; +use std::time::{Duration, Instant}; + +use tempfile::TempDir; + +use fff_search::file_picker::{FFFMode, FilePicker}; +use fff_search::{FilePickerOptions, SharedFilePicker, SharedFrecency}; + +/// Thread comm names Linux exposes via `/proc/self/task/*/comm` are +/// capped at `TASK_COMM_LEN - 1 = 15` bytes. Our owner thread is named +/// `"fff-watcher-owner"` (17 bytes), so what actually appears in +/// `/proc` is the 15-byte truncation below. +const WATCHER_OWNER_THREAD_NAME: &str = "fff-watcher-own"; + +/// Walk `/proc/self/task/*/comm` and return how many live threads +/// carry `name` as their `comm`. +fn count_live_threads_named(name: &str) -> usize { + let Ok(dir) = fs::read_dir("/proc/self/task") else { + return 0; + }; + let mut count = 0usize; + for entry in dir.flatten() { + let comm_path = entry.path().join("comm"); + if let Ok(content) = fs::read_to_string(&comm_path) { + if content.trim_end() == name { + count += 1; + } + } + } + count +} + +/// Poll until the thread count matches `expected` or we hit `timeout`. +fn wait_for_thread_count(name: &str, expected: usize, timeout: Duration) -> usize { + let deadline = Instant::now() + timeout; + loop { + let count = count_live_threads_named(name); + if count == expected { + return count; + } + if Instant::now() >= deadline { + return count; + } + std::thread::sleep(Duration::from_millis(25)); + } +} + +fn seed_repo(base: &std::path::Path) { + fs::create_dir_all(base.join("src")).unwrap(); + fs::write(base.join("README.md"), "# seed\n").unwrap(); + fs::write(base.join("src/main.rs"), "fn main() {}\n").unwrap(); + fs::write(base.join("src/lib.rs"), "// lib\n").unwrap(); + + let _ = std::process::Command::new("git") + .args(["init", "-q", "-b", "main"]) + .current_dir(base) + .output(); +} + +fn spawn_watched_picker(base: PathBuf) -> (SharedFilePicker, SharedFrecency) { + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: false, + enable_content_indexing: false, + mode: FFFMode::Neovim, + watch: true, + ..Default::default() + }, + ) + .expect("FilePicker::new_with_shared_state"); + + assert!( + shared_picker.wait_for_scan(Duration::from_secs(10)), + "initial scan did not complete" + ); + assert!( + shared_picker.wait_for_watcher(Duration::from_secs(10)), + "watcher did not install" + ); + + (shared_picker, shared_frecency) +} + +#[test] +fn watcher_threads_do_not_leak_across_picker_lifetimes() { + // this is needed because I run this within neovim with it's own fff owner thread lmao + let baseline = count_live_threads_named(WATCHER_OWNER_THREAD_NAME); + + const PICKER_COUNT: usize = 4; + + let mut tmpdirs: Vec = (0..PICKER_COUNT) + .map(|_| TempDir::new().expect("mktemp")) + .collect(); + for td in &tmpdirs { + seed_repo(td.path()); + } + + let mut pickers: Vec<(SharedFilePicker, SharedFrecency)> = tmpdirs + .iter() + .map(|td| spawn_watched_picker(td.path().canonicalize().expect("canonicalize tmp"))) + .collect(); + + let peak = wait_for_thread_count( + WATCHER_OWNER_THREAD_NAME, + baseline + PICKER_COUNT, + Duration::from_secs(5), + ); + assert_eq!( + peak, + baseline + PICKER_COUNT, + "expected {} watcher-owner threads alive (baseline {} + {} pickers), saw {}", + baseline + PICKER_COUNT, + baseline, + PICKER_COUNT, + peak, + ); + + for i in 0..PICKER_COUNT { + let expected_remaining = baseline + PICKER_COUNT - (i + 1); + let (sp, sf) = pickers.remove(0); + drop(sp); + drop(sf); + let count = wait_for_thread_count( + WATCHER_OWNER_THREAD_NAME, + expected_remaining, + Duration::from_secs(5), + ); + assert_eq!( + count, + expected_remaining, + "after dropping picker {}/{}: expected {} owner threads, saw {}", + i + 1, + PICKER_COUNT, + expected_remaining, + count, + ); + } + tmpdirs.clear(); + + let after_stage1 = count_live_threads_named(WATCHER_OWNER_THREAD_NAME); + assert_eq!( + after_stage1, baseline, + "stage 1 leaked watcher-owner threads: baseline {}, observed {}", + baseline, after_stage1, + ); + + const ROUNDS: usize = 3; + + for round in 0..ROUNDS { + let tmp = TempDir::new().expect("mktemp"); + seed_repo(tmp.path()); + let base = tmp.path().canonicalize().expect("canonicalize tmp"); + + let (sp, sf) = spawn_watched_picker(base); + + let during = wait_for_thread_count( + WATCHER_OWNER_THREAD_NAME, + baseline + 1, + Duration::from_secs(5), + ); + assert_eq!( + during, + baseline + 1, + "round {round}: expected 1 owner thread during run, saw {during} \ + (baseline {baseline})", + ); + + drop(sp); + drop(sf); + drop(tmp); + + let after = + wait_for_thread_count(WATCHER_OWNER_THREAD_NAME, baseline, Duration::from_secs(5)); + assert_eq!( + after, baseline, + "round {round}: owner thread leaked after teardown \ + (baseline {baseline}, observed {after})", + ); + } + + let final_count = count_live_threads_named(WATCHER_OWNER_THREAD_NAME); + assert_eq!( + final_count, baseline, + "watcher-owner threads leaked past the end of the test \ + (baseline {}, final {})", + baseline, final_count, + ); +} diff --git a/crates/fff-grep/Cargo.toml b/crates/fff-grep/Cargo.toml new file mode 100644 index 0000000..98aa8d4 --- /dev/null +++ b/crates/fff-grep/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fff-grep" +description = "File grepping logic for fff" +license = "MIT" +authors = ["Dmitriy Kovalenko "] +version = "0.9.6" +edition = "2024" + +[dependencies] +bstr = { version = "1.6.2", default-features = false, features = ["std"] } +memchr = "2.6.3" diff --git a/crates/fff-grep/src/lib.rs b/crates/fff-grep/src/lib.rs new file mode 100644 index 0000000..53e7ae5 --- /dev/null +++ b/crates/fff-grep/src/lib.rs @@ -0,0 +1,19 @@ +/*! +Simplified grep-searcher for fff.nvim. + +Provides line-oriented search over byte slices with optional multi-line support. +Only `search_slice` is supported -- no file/reader/mmap search. +*/ + +#![deny(missing_docs)] + +pub use crate::{ + matcher::{LineTerminator, Match, Matcher, NoError}, + searcher::{Searcher, SearcherBuilder}, + sink::{Sink, SinkError, SinkFinish, SinkMatch}, +}; + +pub mod lines; +pub mod matcher; +mod searcher; +mod sink; diff --git a/crates/fff-grep/src/lines.rs b/crates/fff-grep/src/lines.rs new file mode 100644 index 0000000..0d7fabf --- /dev/null +++ b/crates/fff-grep/src/lines.rs @@ -0,0 +1,232 @@ +/*! +A collection of routines for performing operations on lines. +*/ + +use bstr::ByteSlice; + +use crate::matcher::{LineTerminator, Match}; + +/// An explicit iterator over lines in a particular slice of bytes. +/// +/// This iterator avoids borrowing the bytes themselves, and instead requires +/// callers to explicitly provide the bytes when moving through the iterator. +/// +/// Line terminators are considered part of the line they terminate. All lines +/// yielded by the iterator are guaranteed to be non-empty. +#[derive(Debug)] +pub struct LineStep { + line_term: u8, + pos: usize, + end: usize, +} + +impl LineStep { + /// Create a new line iterator over the given range of bytes using the + /// given line terminator. + pub fn new(line_term: u8, start: usize, end: usize) -> LineStep { + LineStep { + line_term, + pos: start, + end, + } + } + + /// Like next, but returns a `Match` instead of a tuple. + #[inline(always)] + pub fn next_match(&mut self, bytes: &[u8]) -> Option { + self.next_impl(bytes).map(|(s, e)| Match::new(s, e)) + } + + #[inline(always)] + fn next_impl(&mut self, mut bytes: &[u8]) -> Option<(usize, usize)> { + bytes = &bytes[..self.end]; + match bytes[self.pos..].find_byte(self.line_term) { + None => { + if self.pos < bytes.len() { + let m = (self.pos, bytes.len()); + assert!(m.0 <= m.1); + + self.pos = m.1; + Some(m) + } else { + None + } + } + Some(line_end) => { + let m = (self.pos, self.pos + line_end + 1); + assert!(m.0 <= m.1); + + self.pos = m.1; + Some(m) + } + } + } +} + +/// Count the number of occurrences of `line_term` in `bytes`. +pub fn count(bytes: &[u8], line_term: u8) -> u64 { + memchr::memchr_iter(line_term, bytes).count() as u64 +} + +/// Given a line that possibly ends with a terminator, return that line without the terminator. +#[inline(always)] +pub fn without_terminator(bytes: &[u8], line_term: LineTerminator) -> &[u8] { + let line_term = line_term.as_bytes(); + let start = bytes.len().saturating_sub(line_term.len()); + if bytes.get(start..) == Some(line_term) { + return &bytes[..bytes.len() - line_term.len()]; + } + bytes +} + +/// Return the start and end offsets of the lines containing the given range +/// of bytes. +/// +/// Line terminators are considered part of the line they terminate. +#[inline(always)] +pub fn locate(bytes: &[u8], line_term: u8, range: Match) -> Match { + let line_start = bytes[..range.start()] + .rfind_byte(line_term) + .map_or(0, |i| i + 1); + let line_end = if range.end() > line_start && bytes[range.end() - 1] == line_term { + range.end() + } else { + bytes[range.end()..] + .find_byte(line_term) + .map_or(bytes.len(), |i| range.end() + i + 1) + }; + Match::new(line_start, line_end) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SHERLOCK: &str = "\ +For the Doctor Watsons of this world, as opposed to the Sherlock +Holmeses, success in the province of detective work must always +be, to a very large extent, the result of luck. Sherlock Holmes +can extract a clew from a wisp of straw or a flake of cigar ash; +but Doctor Watson has to have it taken out for him and dusted, +and exhibited clearly, with a label attached.\ +"; + + fn m(start: usize, end: usize) -> Match { + Match::new(start, end) + } + + fn lines(text: &str) -> Vec<&str> { + let mut results = vec![]; + let mut it = LineStep::new(b'\n', 0, text.len()); + while let Some(m) = it.next_match(text.as_bytes()) { + results.push(&text[m]); + } + results + } + + fn line_ranges(text: &str) -> Vec> { + let mut results = vec![]; + let mut it = LineStep::new(b'\n', 0, text.len()); + while let Some(m) = it.next_match(text.as_bytes()) { + results.push(m.start()..m.end()); + } + results + } + + fn loc(text: &str, start: usize, end: usize) -> Match { + locate(text.as_bytes(), b'\n', Match::new(start, end)) + } + + #[test] + fn line_count() { + assert_eq!(0, count(b"", b'\n')); + assert_eq!(1, count(b"\n", b'\n')); + assert_eq!(2, count(b"\n\n", b'\n')); + assert_eq!(2, count(b"a\nb\nc", b'\n')); + } + + #[test] + fn line_locate() { + let t = SHERLOCK; + let lines = line_ranges(t); + + assert_eq!( + loc(t, lines[0].start, lines[0].end), + m(lines[0].start, lines[0].end) + ); + assert_eq!( + loc(t, lines[0].start + 1, lines[0].end), + m(lines[0].start, lines[0].end) + ); + assert_eq!( + loc(t, lines[0].end - 1, lines[0].end), + m(lines[0].start, lines[0].end) + ); + assert_eq!( + loc(t, lines[0].end, lines[0].end), + m(lines[1].start, lines[1].end) + ); + + assert_eq!( + loc(t, lines[5].start, lines[5].end), + m(lines[5].start, lines[5].end) + ); + assert_eq!( + loc(t, lines[5].start + 1, lines[5].end), + m(lines[5].start, lines[5].end) + ); + assert_eq!( + loc(t, lines[5].end - 1, lines[5].end), + m(lines[5].start, lines[5].end) + ); + assert_eq!( + loc(t, lines[5].end, lines[5].end), + m(lines[5].start, lines[5].end) + ); + } + + #[test] + fn line_locate_weird() { + assert_eq!(loc("", 0, 0), m(0, 0)); + + assert_eq!(loc("\n", 0, 1), m(0, 1)); + assert_eq!(loc("\n", 1, 1), m(1, 1)); + + assert_eq!(loc("\n\n", 0, 0), m(0, 1)); + assert_eq!(loc("\n\n", 0, 1), m(0, 1)); + assert_eq!(loc("\n\n", 1, 1), m(1, 2)); + assert_eq!(loc("\n\n", 1, 2), m(1, 2)); + assert_eq!(loc("\n\n", 2, 2), m(2, 2)); + + assert_eq!(loc("a\nb\nc", 0, 1), m(0, 2)); + assert_eq!(loc("a\nb\nc", 1, 2), m(0, 2)); + assert_eq!(loc("a\nb\nc", 2, 3), m(2, 4)); + assert_eq!(loc("a\nb\nc", 3, 4), m(2, 4)); + assert_eq!(loc("a\nb\nc", 4, 5), m(4, 5)); + assert_eq!(loc("a\nb\nc", 5, 5), m(4, 5)); + } + + #[test] + fn line_iter() { + assert_eq!(lines("abc"), vec!["abc"]); + + assert_eq!(lines("abc\n"), vec!["abc\n"]); + assert_eq!(lines("abc\nxyz"), vec!["abc\n", "xyz"]); + assert_eq!(lines("abc\nxyz\n"), vec!["abc\n", "xyz\n"]); + + assert_eq!(lines("abc\n\n"), vec!["abc\n", "\n"]); + assert_eq!(lines("abc\n\n\n"), vec!["abc\n", "\n", "\n"]); + assert_eq!(lines("abc\n\nxyz"), vec!["abc\n", "\n", "xyz"]); + assert_eq!(lines("abc\n\nxyz\n"), vec!["abc\n", "\n", "xyz\n"]); + assert_eq!(lines("abc\nxyz\n\n"), vec!["abc\n", "xyz\n", "\n"]); + + assert_eq!(lines("\n"), vec!["\n"]); + assert_eq!(lines(""), Vec::<&str>::new()); + } + + #[test] + fn line_iter_empty() { + let mut it = LineStep::new(b'\n', 0, 0); + assert_eq!(it.next_match(b"abc"), None); + } +} diff --git a/crates/fff-grep/src/matcher.rs b/crates/fff-grep/src/matcher.rs new file mode 100644 index 0000000..60fa1db --- /dev/null +++ b/crates/fff-grep/src/matcher.rs @@ -0,0 +1,175 @@ +//! Matcher trait inspired by ripgrep's `Matcher` just simpler + +/// A byte range representing a match. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Match { + start: usize, + end: usize, +} + +impl Match { + /// Create a new match from start/end byte offsets. + #[inline] + pub fn new(start: usize, end: usize) -> Match { + debug_assert!(start <= end); + Match { start, end } + } + + /// Create a zero-width match at `offset`. + #[inline] + pub fn zero(offset: usize) -> Match { + Match { + start: offset, + end: offset, + } + } + + /// Start byte offset. + #[inline] + pub fn start(&self) -> usize { + self.start + } + + /// End byte offset (exclusive). + #[inline] + pub fn end(&self) -> usize { + self.end + } + + /// Return a copy with a different end offset. + #[inline] + pub fn with_end(&self, end: usize) -> Match { + debug_assert!(self.start <= end); + Match { end, ..*self } + } + + /// Shift both offsets forward by `amount`. + #[inline] + pub fn offset(&self, amount: usize) -> Match { + Match { + start: self.start + amount, + end: self.end + amount, + } + } + + /// Byte length of the match. + #[inline] + pub fn len(&self) -> usize { + self.end - self.start + } + + /// True if this is a zero-width match. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl std::ops::Index for [u8] { + type Output = [u8]; + + #[inline] + fn index(&self, index: Match) -> &[u8] { + &self[index.start..index.end] + } +} + +impl std::ops::IndexMut for [u8] { + #[inline] + fn index_mut(&mut self, index: Match) -> &mut [u8] { + &mut self[index.start..index.end] + } +} + +impl std::ops::Index for str { + type Output = str; + + #[inline] + fn index(&self, index: Match) -> &str { + &self[index.start..index.end] + } +} + +/// A line terminator (always a single byte for fff — no CRLF support needed). +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct LineTerminator(u8); + +impl LineTerminator { + /// Create a line terminator from a single byte. + #[inline] + pub fn byte(byte: u8) -> LineTerminator { + LineTerminator(byte) + } + + /// Return the terminator byte. + #[inline] + pub fn as_byte(&self) -> u8 { + self.0 + } + + /// Return the terminator as a single-element byte slice. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + std::slice::from_ref(&self.0) + } +} + +impl Default for LineTerminator { + #[inline] + fn default() -> LineTerminator { + LineTerminator(b'\n') + } +} + +/// An error type for matchers that never produce errors. +#[derive(Debug, Eq, PartialEq)] +pub struct NoError(()); + +impl std::error::Error for NoError {} + +impl std::fmt::Display for NoError { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + unreachable!("NoError should never be instantiated") + } +} + +/// A matcher finds byte-level matches in a haystack. +pub trait Matcher { + /// The error type (use [`NoError`] for infallible matchers). + type Error: std::fmt::Display; + + /// Find the first match at or after `at` in `haystack`. + fn find_at(&self, haystack: &[u8], at: usize) -> Result, Self::Error>; + + /// Find the first match in `haystack`. + #[inline] + fn find(&self, haystack: &[u8]) -> Result, Self::Error> { + self.find_at(haystack, 0) + } + + /// The line terminator this matcher guarantees will never appear in a match. + /// Return `None` if the matcher can match across lines. + #[inline] + fn line_terminator(&self) -> Option { + None + } +} + +impl Matcher for &M { + type Error = M::Error; + + #[inline] + fn find_at(&self, haystack: &[u8], at: usize) -> Result, Self::Error> { + (*self).find_at(haystack, at) + } + + #[inline] + fn find(&self, haystack: &[u8]) -> Result, Self::Error> { + (*self).find(haystack) + } + + #[inline] + fn line_terminator(&self) -> Option { + (*self).line_terminator() + } +} diff --git a/crates/fff-grep/src/searcher/core.rs b/crates/fff-grep/src/searcher/core.rs new file mode 100644 index 0000000..c182ff0 --- /dev/null +++ b/crates/fff-grep/src/searcher/core.rs @@ -0,0 +1,139 @@ +use crate::{ + lines, + matcher::Matcher, + searcher::{Config, Range, Searcher}, + sink::{Sink, SinkError, SinkFinish, SinkMatch}, +}; + +#[derive(Debug)] +pub(crate) struct Core<'s, M: 's, S> { + config: &'s Config, + matcher: M, + searcher: &'s Searcher, + sink: S, + pos: usize, + absolute_byte_offset: u64, + line_number: Option, + last_line_counted: usize, + last_line_visited: usize, +} + +impl<'s, M: Matcher, S: Sink> Core<'s, M, S> { + pub(crate) fn new(searcher: &'s Searcher, matcher: M, sink: S) -> Core<'s, M, S> { + let line_number = if searcher.config.line_number { + Some(1) + } else { + None + }; + Core { + config: &searcher.config, + matcher, + searcher, + sink, + pos: 0, + absolute_byte_offset: 0, + line_number, + last_line_counted: 0, + last_line_visited: 0, + } + } + + pub(crate) fn pos(&self) -> usize { + self.pos + } + + pub(crate) fn set_pos(&mut self, pos: usize) { + self.pos = pos; + } + + pub(crate) fn matched(&mut self, buf: &[u8], range: &Range) -> Result { + self.sink_matched(buf, range) + } + + pub(crate) fn find(&mut self, slice: &[u8]) -> Result, S::Error> { + self.matcher.find(slice).map_err(S::Error::error_message) + } + + pub(crate) fn begin(&mut self) -> Result { + self.sink.begin(self.searcher) + } + + pub(crate) fn finish(&mut self, byte_count: u64) -> Result<(), S::Error> { + self.sink.finish(self.searcher, &SinkFinish { byte_count }) + } + + pub(crate) fn match_by_line(&mut self, buf: &[u8]) -> Result { + while !buf[self.pos()..].is_empty() { + if let Some(line) = self.find_by_line(buf)? { + self.set_pos(line.end()); + if !self.sink_matched(buf, &line)? { + return Ok(false); + } + } else { + break; + } + } + self.set_pos(buf.len()); + Ok(true) + } + + #[inline(always)] + fn find_by_line(&mut self, buf: &[u8]) -> Result, S::Error> { + let mut pos = self.pos(); + while !buf[pos..].is_empty() { + let mat = match self + .matcher + .find(&buf[pos..]) + .map_err(S::Error::error_message)? + { + None => return Ok(None), + Some(m) => m, + }; + let line = lines::locate( + buf, + self.config.line_term.as_byte(), + Range::zero(mat.start()).offset(pos), + ); + if line.start() == buf.len() { + pos = buf.len(); + continue; + } + return Ok(Some(line)); + } + Ok(None) + } + + #[inline(always)] + fn sink_matched(&mut self, buf: &[u8], range: &Range) -> Result { + self.count_lines(buf, range.start()); + let offset = self.absolute_byte_offset + range.start() as u64; + let linebuf = &buf[*range]; + let keepgoing = self.sink.matched( + self.searcher, + &SinkMatch { + bytes: linebuf, + absolute_byte_offset: offset, + line_number: self.line_number, + buffer: buf, + bytes_range_in_buffer: range.start()..range.end(), + }, + )?; + if !keepgoing { + return Ok(false); + } + self.last_line_visited = range.end(); + Ok(true) + } + + fn count_lines(&mut self, buf: &[u8], upto: usize) { + if let Some(ref mut line_number) = self.line_number { + if self.last_line_counted >= upto { + return; + } + let slice = &buf[self.last_line_counted..upto]; + let count = lines::count(slice, self.config.line_term.as_byte()); + *line_number += count; + self.last_line_counted = upto; + } + } +} diff --git a/crates/fff-grep/src/searcher/glue.rs b/crates/fff-grep/src/searcher/glue.rs new file mode 100644 index 0000000..fbdc643 --- /dev/null +++ b/crates/fff-grep/src/searcher/glue.rs @@ -0,0 +1,127 @@ +use crate::{ + lines, + matcher::Matcher, + searcher::{Config, Range, Searcher, core::Core}, + sink::Sink, +}; + +#[derive(Debug)] +pub(crate) struct SliceByLine<'s, M, S> { + core: Core<'s, M, S>, + slice: &'s [u8], +} + +impl<'s, M: Matcher, S: Sink> SliceByLine<'s, M, S> { + pub(crate) fn new( + searcher: &'s Searcher, + matcher: M, + slice: &'s [u8], + write_to: S, + ) -> SliceByLine<'s, M, S> { + debug_assert!(!searcher.multi_line_with_matcher(&matcher)); + + SliceByLine { + core: Core::new(searcher, matcher, write_to), + slice, + } + } + + pub(crate) fn run(mut self) -> Result<(), S::Error> { + if self.core.begin()? { + while !self.slice[self.core.pos()..].is_empty() + && self.core.match_by_line(self.slice)? + {} + } + let byte_count = self.slice.len() as u64; + self.core.finish(byte_count) + } +} + +#[derive(Debug)] +pub(crate) struct MultiLine<'s, M, S> { + config: &'s Config, + core: Core<'s, M, S>, + slice: &'s [u8], + last_match: Option, +} + +impl<'s, M: Matcher, S: Sink> MultiLine<'s, M, S> { + pub(crate) fn new( + searcher: &'s Searcher, + matcher: M, + slice: &'s [u8], + write_to: S, + ) -> MultiLine<'s, M, S> { + debug_assert!(searcher.multi_line_with_matcher(&matcher)); + + MultiLine { + config: &searcher.config, + core: Core::new(searcher, matcher, write_to), + slice, + last_match: None, + } + } + + pub(crate) fn run(mut self) -> Result<(), S::Error> { + if self.core.begin()? { + let mut keepgoing = true; + while !self.slice[self.core.pos()..].is_empty() && keepgoing { + keepgoing = self.sink()?; + } + if keepgoing && let Some(last_match) = self.last_match.take() { + self.sink_matched(&last_match)?; + } + } + let byte_count = self.slice.len() as u64; + self.core.finish(byte_count) + } + + fn sink(&mut self) -> Result { + let mat = match self.find()? { + Some(range) => range, + None => { + self.core.set_pos(self.slice.len()); + return Ok(true); + } + }; + self.advance(&mat); + + let line = lines::locate(self.slice, self.config.line_term.as_byte(), mat); + match self.last_match.take() { + None => { + self.last_match = Some(line); + Ok(true) + } + Some(last_match) => { + if last_match.end() >= line.start() { + self.last_match = Some(last_match.with_end(line.end())); + Ok(true) + } else { + self.last_match = Some(line); + self.sink_matched(&last_match) + } + } + } + } + + fn sink_matched(&mut self, range: &Range) -> Result { + if range.is_empty() { + return Ok(false); + } + self.core.matched(self.slice, range) + } + + fn find(&mut self) -> Result, S::Error> { + self.core + .find(&self.slice[self.core.pos()..]) + .map(|m| m.map(|m| m.offset(self.core.pos()))) + } + + fn advance(&mut self, range: &Range) { + self.core.set_pos(range.end()); + if range.is_empty() && self.core.pos() < self.slice.len() { + let newpos = self.core.pos() + 1; + self.core.set_pos(newpos); + } + } +} diff --git a/crates/fff-grep/src/searcher/mod.rs b/crates/fff-grep/src/searcher/mod.rs new file mode 100644 index 0000000..b8aa006 --- /dev/null +++ b/crates/fff-grep/src/searcher/mod.rs @@ -0,0 +1,194 @@ +use crate::{ + matcher::{LineTerminator, Match, Matcher}, + searcher::glue::{MultiLine, SliceByLine}, + sink::{Sink, SinkError}, +}; + +mod core; +mod glue; + +/// We use this type alias since we want the ergonomics of a matcher's `Match` +/// type, but in practice, we use it for arbitrary ranges, so give it a more +/// accurate name. This is only used in the searcher's internals. +type Range = Match; + +/// An error that can occur when building a searcher. +#[derive(Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub(crate) enum ConfigError { + /// Occurs when a matcher reports a line terminator that is different than + /// the one configured in the searcher. + MismatchedLineTerminators { + /// The matcher's line terminator. + matcher: LineTerminator, + /// The searcher's line terminator. + searcher: LineTerminator, + }, +} + +impl std::error::Error for ConfigError {} + +impl std::fmt::Display for ConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + ConfigError::MismatchedLineTerminators { matcher, searcher } => { + write!( + f, + "grep config error: mismatched line terminators, \ + matcher has {:?} but searcher has {:?}", + matcher, searcher + ) + } + } + } +} + +/// The internal configuration of a searcher. +#[derive(Clone, Debug)] +pub(crate) struct Config { + /// The line terminator to use. + pub(crate) line_term: LineTerminator, + /// Whether to count line numbers. + pub(crate) line_number: bool, + /// Whether to enable matching across multiple lines. + multi_line: bool, +} + +impl Default for Config { + fn default() -> Config { + Config { + line_term: LineTerminator::default(), + line_number: true, + multi_line: false, + } + } +} + +/// A builder for configuring a searcher. +#[derive(Clone, Debug)] +pub struct SearcherBuilder { + config: Config, +} + +impl Default for SearcherBuilder { + fn default() -> SearcherBuilder { + SearcherBuilder::new() + } +} + +impl SearcherBuilder { + /// Create a new searcher builder with a default configuration. + pub fn new() -> SearcherBuilder { + SearcherBuilder { + config: Config::default(), + } + } + + /// Build a searcher. + pub fn build(&self) -> Searcher { + Searcher { + config: self.config.clone(), + } + } + + /// Whether to count and include line numbers with matching lines. + pub fn line_number(&mut self, yes: bool) -> &mut SearcherBuilder { + self.config.line_number = yes; + self + } + + /// Whether to enable multi line search or not. + pub fn multi_line(&mut self, yes: bool) -> &mut SearcherBuilder { + self.config.multi_line = yes; + self + } +} + +/// A searcher executes searches over a haystack and writes results to a caller +/// provided sink. +#[derive(Clone, Debug)] +pub struct Searcher { + pub(crate) config: Config, +} + +impl Searcher { + /// Create a new searcher with a default configuration. + pub fn new() -> Searcher { + SearcherBuilder::new().build() + } + + /// Execute a search over the given slice and write the results to the + /// given sink. + pub fn search_slice(&self, matcher: M, slice: &[u8], write_to: S) -> Result<(), S::Error> + where + M: Matcher, + S: Sink, + { + self.check_config(&matcher) + .map_err(S::Error::error_message)?; + + if self.multi_line_with_matcher(&matcher) { + MultiLine::new(self, matcher, slice, write_to).run() + } else { + SliceByLine::new(self, matcher, slice, write_to).run() + } + } + + /// Check that the searcher's configuration and the matcher are consistent. + fn check_config(&self, matcher: M) -> Result<(), ConfigError> { + let matcher_line_term = match matcher.line_terminator() { + None => return Ok(()), + Some(line_term) => line_term, + }; + if matcher_line_term != self.config.line_term { + return Err(ConfigError::MismatchedLineTerminators { + matcher: matcher_line_term, + searcher: self.config.line_term, + }); + } + Ok(()) + } +} + +impl Default for Searcher { + fn default() -> Self { + Self::new() + } +} + +/// Configuration query methods used by the sink and internal search core. +impl Searcher { + /// Returns the line terminator used by this searcher. + #[inline] + pub fn line_terminator(&self) -> LineTerminator { + self.config.line_term + } + + /// Returns true if and only if this searcher is configured to count line + /// numbers. + #[inline] + pub fn line_number(&self) -> bool { + self.config.line_number + } + + /// Returns true if and only if this searcher is configured to perform + /// multi line search. + #[inline] + pub fn multi_line(&self) -> bool { + self.config.multi_line + } + + /// Returns true if and only if this searcher will choose a multi-line + /// strategy given the provided matcher. + pub fn multi_line_with_matcher(&self, matcher: M) -> bool { + if !self.multi_line() { + return false; + } + if let Some(line_term) = matcher.line_terminator() + && line_term == self.line_terminator() + { + return false; + } + true + } +} diff --git a/crates/fff-grep/src/sink.rs b/crates/fff-grep/src/sink.rs new file mode 100644 index 0000000..ba0e29f --- /dev/null +++ b/crates/fff-grep/src/sink.rs @@ -0,0 +1,138 @@ +use std::io; + +use crate::searcher::Searcher; + +/// A trait that describes errors that can be reported by searchers and +/// implementations of `Sink`. +pub trait SinkError: Sized { + /// A constructor for converting any value that satisfies the + /// `std::fmt::Display` trait into an error. + fn error_message(message: T) -> Self; + + /// A constructor for converting I/O errors that occur while searching into + /// an error of this type. + fn error_io(err: io::Error) -> Self { + Self::error_message(err) + } +} + +impl SinkError for io::Error { + fn error_message(message: T) -> io::Error { + io::Error::other(message.to_string()) + } + + fn error_io(err: io::Error) -> io::Error { + err + } +} + +/// A trait that defines how results from searchers are handled. +/// +/// The searcher follows the "push" model: the searcher drives execution and +/// pushes results back to the caller via this trait. +pub trait Sink { + /// The type of an error that should be reported by a searcher. + type Error: SinkError; + + /// This method is called whenever a match is found. + /// + /// If this returns `true`, then searching continues. If this returns + /// `false`, then searching is stopped immediately and `finish` is called. + fn matched(&mut self, _searcher: &Searcher, _mat: &SinkMatch<'_>) -> Result; + + /// This method is called when a search has begun, before any search is + /// executed. By default, this does nothing. + #[inline] + fn begin(&mut self, _searcher: &Searcher) -> Result { + Ok(true) + } + + /// This method is called when a search has completed. By default, this + /// does nothing. + #[inline] + fn finish(&mut self, _searcher: &Searcher, _: &SinkFinish) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Sink for &mut S { + type Error = S::Error; + + #[inline] + fn matched(&mut self, searcher: &Searcher, mat: &SinkMatch<'_>) -> Result { + (**self).matched(searcher, mat) + } + + #[inline] + fn begin(&mut self, searcher: &Searcher) -> Result { + (**self).begin(searcher) + } + + #[inline] + fn finish(&mut self, searcher: &Searcher, sink_finish: &SinkFinish) -> Result<(), S::Error> { + (**self).finish(searcher, sink_finish) + } +} + +/// Summary data reported at the end of a search. +#[derive(Clone, Debug)] +pub struct SinkFinish { + pub(crate) byte_count: u64, +} + +impl SinkFinish { + /// Return the total number of bytes searched. + #[inline] + pub fn byte_count(&self) -> u64 { + self.byte_count + } +} + +/// A type that describes a match reported by a searcher. +#[derive(Clone, Debug)] +pub struct SinkMatch<'b> { + pub(crate) bytes: &'b [u8], + pub(crate) absolute_byte_offset: u64, + pub(crate) line_number: Option, + pub(crate) buffer: &'b [u8], + pub(crate) bytes_range_in_buffer: std::ops::Range, +} + +impl<'b> SinkMatch<'b> { + /// Returns the bytes for all matching lines, including the line + /// terminators, if they exist. + #[inline] + pub fn bytes(&self) -> &'b [u8] { + self.bytes + } + + /// Returns the absolute byte offset of the start of this match. This + /// offset is absolute in that it is relative to the very beginning of the + /// input in a search. + #[inline] + pub fn absolute_byte_offset(&self) -> u64 { + self.absolute_byte_offset + } + + /// Returns the line number of the first line in this match, if available. + /// + /// Line numbers are only available when the search builder is instructed + /// to compute them. + #[inline] + pub fn line_number(&self) -> Option { + self.line_number + } + + /// Exposes as much of the underlying buffer that was searched as possible. + #[inline] + pub fn buffer(&self) -> &'b [u8] { + self.buffer + } + + /// Returns a range that corresponds to where [`SinkMatch::bytes`] appears + /// in [`SinkMatch::buffer`]. + #[inline] + pub fn bytes_range_in_buffer(&self) -> std::ops::Range { + self.bytes_range_in_buffer.clone() + } +} diff --git a/crates/fff-mcp/Cargo.toml b/crates/fff-mcp/Cargo.toml new file mode 100644 index 0000000..9f46651 --- /dev/null +++ b/crates/fff-mcp/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "fff-mcp" +version = "0.9.6" +edition = "2024" +description = "MCP server for FFF file finder - drop-in replacement for AI code assistant search tools" +license = "MIT" + +[[bin]] +name = "fff-mcp" +path = "src/main.rs" + +[features] +# Pure-Rust walker by default; opt into zlob explicitly (needs Zig). +default = ["ripgrep"] +ripgrep = ["fff/ripgrep", "fff-query-parser/ripgrep"] +zlob = ["fff/zlob", "fff-query-parser/zlob"] + +[dependencies] +fff = { package = "fff-search", path = "../fff-core", default-features = false , version = "0.9.6", features = ["definitions"] } +fff-query-parser = { path = "../fff-query-parser", default-features = false , version = "0.9.6" } +mimalloc = { workspace = true } +rmcp = { version = "1.7.0", features = ["server", "transport-io"] } +schemars = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1", features = ["full"] } +tracing = { workspace = true } +git2 = { workspace = true } +clap = { version = "4", features = ["derive", "env"] } diff --git a/crates/fff-mcp/build.rs b/crates/fff-mcp/build.rs new file mode 100644 index 0000000..30a1d0e --- /dev/null +++ b/crates/fff-mcp/build.rs @@ -0,0 +1,15 @@ +fn main() { + // Embed the git commit hash at build time for update checking. + let hash = std::process::Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .ok() + .filter(|o| o.status.success()) + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + + println!("cargo:rustc-env=FFF_GIT_HASH={}", hash); + println!("cargo:rerun-if-changed=../../.git/HEAD"); + println!("cargo:rerun-if-changed=../../.git/refs/"); +} diff --git a/crates/fff-mcp/server/.gitkeep b/crates/fff-mcp/server/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/crates/fff-mcp/src/cursor.rs b/crates/fff-mcp/src/cursor.rs new file mode 100644 index 0000000..fc5ad65 --- /dev/null +++ b/crates/fff-mcp/src/cursor.rs @@ -0,0 +1,52 @@ +//! Cursor store for grep pagination. +//! +//! Maintains an in-memory map of opaque cursor IDs to file offsets. +//! Cursors are evicted LRU-style when the store exceeds capacity. + +use std::collections::{HashMap, VecDeque}; + +const MAX_CURSORS: usize = 20; + +/// Stores cursor state for paginated grep results. +pub struct CursorStore { + counter: u64, + /// Map from cursor ID string → file offset for next page. + cursors: HashMap, + /// Insertion order for LRU eviction. + insertion_order: VecDeque, +} + +impl CursorStore { + pub fn new() -> Self { + Self { + counter: 0, + cursors: HashMap::new(), + insertion_order: VecDeque::new(), + } + } + + /// Store a cursor and return its opaque ID string. + pub fn store(&mut self, file_offset: usize) -> String { + self.counter = self.counter.wrapping_add(1); + let id = self.counter.to_string(); + + self.cursors.insert(id.clone(), file_offset); + self.insertion_order.push_back(id.clone()); + + // Evict oldest cursors + while self.cursors.len() > MAX_CURSORS { + if let Some(oldest) = self.insertion_order.pop_front() { + self.cursors.remove(&oldest); + } else { + break; + } + } + + id + } + + /// Retrieve the file offset for a cursor ID. + pub fn get(&self, id: &str) -> Option { + self.cursors.get(id).copied() + } +} diff --git a/crates/fff-mcp/src/healthcheck.rs b/crates/fff-mcp/src/healthcheck.rs new file mode 100644 index 0000000..cc7db9e --- /dev/null +++ b/crates/fff-mcp/src/healthcheck.rs @@ -0,0 +1,117 @@ +use crate::Args; +use git2::Repository; + +fn check(label: &str, ok: bool, detail: &str) -> bool { + let marker = if ok { "+" } else { "x" }; + println!(" [{marker}] {label}: {detail}"); + ok +} + +fn warn(label: &str, detail: &str) { + println!(" [!] {label}: {detail}"); +} + +pub fn run_healthcheck(args: &Args) -> Result<(), Box> { + let version = concat!(env!("CARGO_PKG_VERSION"), " (", env!("FFF_GIT_HASH"), ")"); + println!("fff-mcp {version}\n"); + + let mut all_ok = true; + + // 1. Base path + let base_path = args.base_path.clone().unwrap_or_else(|| { + std::env::current_dir() + .unwrap_or_default() + .to_string_lossy() + .to_string() + }); + + let path_exists = std::path::Path::new(&base_path).is_dir(); + all_ok &= check( + "Base path", + path_exists, + if path_exists { + &base_path + } else { + "directory does not exist" + }, + ); + + // 2. Git repository + match Repository::discover(&base_path) { + Ok(repo) => { + if let Some(workdir) = repo.workdir() { + all_ok &= check("Git repository", true, &format!("{}", workdir.display())); + } else { + all_ok &= check("Git repository", true, "bare repository"); + } + } + Err(_) => { + // Not fatal — fff-mcp works without git, but worth flagging. + warn( + "Git repository", + "not found (fff-mcp will still work, but git-status features are disabled)", + ); + } + } + + // 3. Frecency database + if let Some(ref db_path) = args.frecency_db_path { + let parent_ok = std::path::Path::new(db_path) + .parent() + .is_some_and(|p| p.is_dir()); + all_ok &= check( + "Frecency DB", + parent_ok, + if parent_ok { + db_path + } else { + "parent directory does not exist" + }, + ); + } else { + check("Frecency DB", false, "path not resolved"); + } + + // 4. Query history database + if let Some(ref db_path) = args.history_db_path { + let parent_ok = std::path::Path::new(db_path) + .parent() + .is_some_and(|p| p.is_dir()); + all_ok &= check( + "History DB", + parent_ok, + if parent_ok { + db_path + } else { + "parent directory does not exist" + }, + ); + } else { + check("History DB", false, "path not resolved"); + } + + // 5. Log path hint (per-session files written next to this path) + if let Some(ref log_path) = args.log_file { + let parent_ok = std::path::Path::new(log_path) + .parent() + .is_some_and(|p| p.is_dir() || p.parent().is_some()); + all_ok &= check( + "Log path", + parent_ok, + if parent_ok { + log_path + } else { + "parent directory does not exist" + }, + ); + } else { + check("Log path", false, "path not resolved"); + } + + if all_ok { + println!("All checks passed."); + Ok(()) + } else { + Err("Some checks failed — review the items marked [x] above.".into()) + } +} diff --git a/crates/fff-mcp/src/main.rs b/crates/fff-mcp/src/main.rs new file mode 100644 index 0000000..1777282 --- /dev/null +++ b/crates/fff-mcp/src/main.rs @@ -0,0 +1,372 @@ +mod cursor; +mod healthcheck; +mod output; +mod server; +mod update_check; + +use clap::Parser; +use fff::file_picker::FilePicker; +use fff::frecency::FrecencyTracker; +use fff::{FFFMode, SharedFilePicker, SharedFrecency}; +use git2::Repository; +use mimalloc::MiMalloc; +use rmcp::{ServiceExt, transport::stdio}; +use server::FffServer; + +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +pub const MCP_INSTRUCTIONS: &str = concat!( + "FFF is a fast file finder with frecency-ranked results (frequent/recent files first, git-dirty files boosted).\n", + "\n", + "## Which Tool Should I Use?\n", + "\n", + "- **grep**: DEFAULT tool. Searches file CONTENTS -- definitions, usage, patterns. Use when you have a specific name or pattern.\n", + "- **find_files**: Explores which files/modules exist for a topic. Use when you DON'T have a specific identifier or LOOKING FOR A FILE.\n", + "- **multi_grep**: OR logic across multiple patterns. Use for case variants (e.g. ['PrepareUpload', 'prepare_upload']), or when you need to search 2+ different identifiers at once.\n", + "\n", + "## Core Rules\n", + "\n", + "### 1. Search BARE IDENTIFIERS only\n", + "Grep matches single lines. Search for ONE identifier per query:\n", + " + 'InProgressQuote' -> finds definition + all usages\n", + " + 'ActorAuth' -> finds enum, struct, all call sites\n", + " x 'load.*metadata.*InProgressQuote' -> regex spanning multiple tokens, 0 results\n", + " x 'ctx.data::' -> code syntax, too specific, 0 results\n", + " x 'struct ActorAuth' -> adding keywords narrows results, misses enums/traits/type aliases\n", + " x 'TODO.*#\\d+' -> complex regex, use simple 'TODO' then filter visually\n", + "\n", + "### 2. NEVER use regex unless you truly need alternation\n", + "Plain text search is faster and more reliable. Regex patterns like `.*`, `\\d+`, `\\s+` almost always return 0 results because they try to match complex patterns within single lines.\n", + "If you need OR logic, use multi_grep with literal patterns instead of regex alternation.\n", + "\n", + "### 3. Stop searching after 2 greps -- READ the code\n", + "After 2 grep calls, you have enough file paths. Read the top result to understand the code.\n", + "Do NOT keep grepping with variations. More greps != better understanding.\n", + "\n", + "### 4. Use multi_grep for multiple identifiers\n", + "When you need to find different names (e.g. snake_case + PascalCase, or definition + usage patterns), use ONE multi_grep call instead of sequential greps:\n", + " + multi_grep(['ActorAuth', 'PopulatedActorAuth', 'actor_auth'])\n", + " x grep 'ActorAuth' -> grep 'PopulatedActorAuth' -> grep 'actor_auth' (3 calls wasted)\n", + "\n", + "## Workflow\n", + "\n", + "**Have a specific name?** -> grep the bare identifier.\n", + "**Need multiple name variants?** -> multi_grep with all variants in one call.\n", + "**Exploring a topic / finding files?** -> find_files.\n", + "**Got results?** -> Read the top file. Don't grep again.\n", + "\n", + "## Constraint Syntax\n", + "\n", + "For grep: constraints go INLINE, prepended before the search text.\n", + "For multi_grep: constraints go in the separate 'constraints' parameter.\n", + "\n", + "Constraints MUST match one of these formats:\n", + " Extension: '*.rs', '*.{ts,tsx}'\n", + " Directory: 'src/', 'quotes/'\n", + " Filename: 'schema.rs', 'src/main.rs'\n", + " Exclude: '!test/', '!*.spec.ts'\n", + "\n", + "! Bare words without extensions are NOT constraints. 'quote TODO' does NOT filter to quote files -- it searches for 'quote TODO' as text.\n", + " + 'schema.rs TODO' -> searches for 'TODO' in files schema.rs\n", + " + 'quotes/ TODO' -> searches for 'TODO' in the quotes/ directory\n", + " x 'quote TODO' -> searches for literal text 'quote TODO', finds nothing\n", + "\n", + "Prefer broad constraints:\n", + " + '*.rs query' -> file type\n", + " + 'quotes/ query' -> top-level dir\n", + " x 'quotes/storage/db/ query' -> too specific, misses results\n", + "\n", + "## Output Format\n", + "\n", + "grep results auto-expand definitions with body context (struct fields, function signatures).\n", + "This often provides enough information WITHOUT a follow-up Read call.\n", + "Lines marked with | are definition body context. [def] marks definition files.\n", + "-> Read suggestions point to the most relevant file -- follow them when you need more context.\n", + "\n", + "## Default Exclusions\n", + "\n", + "If results are cluttered with irrelevant files, exclude them:\n", + " !tests/ - exclude tests directory\n", + " !*.spec.ts - exclude test files\n", + " !generated/ - exclude generated code", +); + +/// FFF MCP Server -- a high performance & accuracy file finder for AI code assistants. +#[derive(Parser)] +#[command(name = "fff-mcp", version = concat!(env!("CARGO_PKG_VERSION"), " (", env!("FFF_GIT_HASH"), ")"))] +pub(crate) struct Args { + /// Base directory to index. Defaults to the current working directory. + #[arg(value_name = "PATH")] + base_path: Option, + + /// Path to the frecency database. + #[arg(long = "frecency-db")] + frecency_db_path: Option, + + /// Path to the query history database. + #[arg(long = "history-db")] + #[allow(dead_code)] + history_db_path: Option, + + /// Path-shape hint for per-session log files. + /// Each fff-mcp startup writes a fresh sibling file `++.` + #[arg(long = "log-file")] + log_file: Option, + + /// Log level (e.g. trace, debug, info, warn, error). + #[arg(long = "log-level")] + log_level: Option, + + /// Disable automatic update checks on startup. + #[arg(long = "no-update-check")] + no_update_check: bool, + + /// Disable eager mmap warmup after the initial scan. Grep results will + /// still work (files are mmap'd lazily on first access), but the first + /// search may be slightly slower. Useful on very large repos where the + /// warmup would consume too many kernel resources. + #[arg(long = "no-warmup")] + no_warmup: bool, + + /// Disable the content index built after the initial scan. + /// This makes grep calls slower but consumes less RAM (recommended to not turn off) + no_content_indexing: bool, + + /// Explicitly enable content indexing even when `--no-warmup` is set. + #[arg(long = "content-indexing")] + content_indexing: bool, + + /// Disable the background file-system watcher. Files are scanned once + /// at startup but not monitored for changes. + #[arg(long = "no-watch")] + no_watch: bool, + + /// Maximum number of files whose content is kept persistently in memory. + /// Files beyond this limit are still searchable via temporary mmaps that + /// are released after each grep. Defaults to 30 000. + /// Also settable via the FFF_MAX_CACHED_FILES environment variable. + #[arg(long = "max-cached-files", env = "FFF_MAX_CACHED_FILES")] + max_cached_files: Option, + + /// Follow symlinks during scan and watcher walks. Off by default — + /// enabling on cyclic symlink layouts can wedge the watcher. + #[arg(long = "follow-symlinks")] + follow_symlinks: bool, + + /// Run a health check and print diagnostic information, then exit. + #[arg(long = "healthcheck")] + pub(crate) healthcheck: bool, + + /// Exit after this many seconds of inactivity. 0 = never exit. + #[arg( + long = "idle-timeout-secs", + env = "FFF_MCP_IDLE_TIMEOUT_SECS", + default_value_t = 900 + )] + idle_timeout_secs: u64, +} + +/// Resolve default paths for the log file. +/// Database paths (frecency, history) must be explicitly provided via flags. +fn resolve_defaults(args: &mut Args) { + // Ensure parent directories exist for database paths when provided + for path in [&args.frecency_db_path, &args.history_db_path] + .into_iter() + .flatten() + { + if let Some(parent) = std::path::Path::new(path).parent() { + let _ = std::fs::create_dir_all(parent); + } + } + + if args.log_file.is_none() { + let home = dirs_home(); + let is_windows = cfg!(target_os = "windows"); + args.log_file = Some(if is_windows { + format!("{}\\AppData\\Local\\fff_mcp.log", home) + } else { + format!("{}/.cache/fff_mcp.log", home) + }); + } +} + +fn dirs_home() -> String { + std::env::var("HOME") + .or_else(|_| std::env::var("USERPROFILE")) + .unwrap_or_else(|_| "/tmp".to_string()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut args = Args::parse(); + resolve_defaults(&mut args); + + if args.healthcheck { + return healthcheck::run_healthcheck(&args); + } + + let log_file = args.log_file.as_deref().unwrap_or(""); + if let Err(e) = fff::log::init_tracing(log_file, args.log_level.as_deref(), None) { + eprintln!("Warning: Failed to init tracing: {}", e); + } + + let base_path = args.base_path.unwrap_or_else(|| { + std::env::current_dir() + .unwrap_or_default() + .to_string_lossy() + .to_string() + }); + + let base_path = match Repository::discover(&base_path) { + Ok(repo) => { + if let Some(workdir) = repo.workdir() { + let git_root = workdir.to_string_lossy().to_string(); + tracing::info!("Discovered git root: {}", git_root); + git_root + } else { + tracing::info!("Git repository is bare, using base path: {}", base_path); + base_path + } + } + Err(_) => { + tracing::info!( + "No git repository found, indexing from base path: {}", + base_path + ); + base_path + } + }; + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + if let Some(frecency_db_path) = args.frecency_db_path { + match FrecencyTracker::open(&frecency_db_path) { + Ok(tracker) => { + let _ = shared_frecency.init(tracker); + } + Err(e) => { + eprintln!("Warning: Failed to init frecency db: {}", e); + } + } + } + + // Content indexing follows warmup by default (backward compat), unless + // the user explicitly opts in via --content-indexing or out via + // --no-content-indexing. + let enable_content_indexing = if args.content_indexing { + true + } else if args.no_content_indexing { + false + } else { + !args.no_warmup + }; + + // Initialize file picker (spawns background scan + watcher) + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency, + fff::FilePickerOptions { + base_path, + enable_mmap_cache: !args.no_warmup, + enable_content_indexing, + watch: !args.no_watch, + mode: FFFMode::Ai, + cache_budget: args + .max_cached_files + .map(fff::ContentCacheBudget::new_for_repo), + follow_symlinks: args.follow_symlinks, + ..Default::default() + }, + ) + .map_err(|e| format!("Failed to init file picker: {}", e))?; + + if !args.no_update_check { + update_check::spawn_update_check(); + } + + // Create and start the MCP server + let server = FffServer::new(shared_picker.clone()); + let last_activity = server.last_activity(); + let idle_timeout_secs = args.idle_timeout_secs; + + // Wait for initial scan in background — don't block server startup + let picker_clone_for_scan = shared_picker.clone(); + tokio::task::spawn_blocking(move || { + let start = std::time::Instant::now(); + loop { + let is_scanning = picker_clone_for_scan + .read() + .ok() + .and_then(|g| g.as_ref().map(|p| p.is_scan_active())) + .unwrap_or(true); + + if !is_scanning { + tracing::info!("Initial scan completed in {:?}", start.elapsed()); + break; + } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + }); + + const STARTUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60); + let service = match tokio::time::timeout(STARTUP_TIMEOUT, server.serve(stdio())).await { + Ok(res) => res.map_err(|e| format!("Failed to start MCP server: {}", e))?, + Err(_) => { + return Err("MCP initialize handshake did not complete within 60s".into()); + } + }; + + if idle_timeout_secs > 0 { + last_activity.store( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0), + std::sync::atomic::Ordering::Relaxed, + ); + + let last_activity_for_watchdog = last_activity.clone(); + tokio::spawn(async move { + let tick = std::time::Duration::from_secs(60); + loop { + tokio::time::sleep(tick).await; + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + + let last = last_activity_for_watchdog.load(std::sync::atomic::Ordering::Relaxed); + if now.saturating_sub(last) >= idle_timeout_secs { + tracing::info!( + "Exiting after {}s of inactivity (idle_timeout_secs={})", + now.saturating_sub(last), + idle_timeout_secs + ); + std::process::exit(0); + } + } + }); + } + + let picker_for_shutdown = shared_picker.clone(); + tokio::spawn(async move { + tokio::signal::ctrl_c().await.ok(); + if let Ok(mut guard) = picker_for_shutdown.write() + && let Some(ref mut picker) = *guard + { + picker.stop_background_monitor(); + } + std::process::exit(0); + }); + + service.waiting().await?; + + if let Ok(mut guard) = shared_picker.write() + && let Some(ref mut picker) = *guard + { + picker.stop_background_monitor(); + } + + Ok(()) +} diff --git a/crates/fff-mcp/src/output.rs b/crates/fff-mcp/src/output.rs new file mode 100644 index 0000000..21ba7ec --- /dev/null +++ b/crates/fff-mcp/src/output.rs @@ -0,0 +1,576 @@ +//! Output formatting for MCP grep/search results. + +use fff::GrepMatch; +use fff::file_picker::FilePicker; +use fff::git::format_git_status_opt; +use fff::grep::is_import_line; +use fff::types::FileItem; + +use crate::cursor::CursorStore; + +fn frecency_word(score: i32) -> Option<&'static str> { + if score >= 100 { + Some("hot") + } else if score >= 50 { + Some("warm") + } else if score >= 10 { + Some("frequent") + } else { + None + } +} + +pub fn file_suffix(git_status: Option, frecency_score: i32) -> String { + match ( + frecency_word(frecency_score), + format_git_status_opt(git_status), + ) { + (Some(f), Some(g)) => format!(" - {f} git:{g}"), + (Some(f), None) => format!(" - {f}"), + (None, Some(g)) => format!(" git:{g}"), + (None, None) => String::new(), + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputMode { + Content, + FilesWithMatches, + Count, + Usage, +} + +impl OutputMode { + pub fn new(s: Option<&str>) -> Self { + match s { + Some("files_with_matches") => Self::FilesWithMatches, + Some("count") => Self::Count, + Some("usage") => Self::Usage, + _ => Self::Content, + } + } +} + +const LARGE_FILE_BYTES: u64 = 20_000; + +fn size_tag(bytes: u64) -> String { + if bytes < LARGE_FILE_BYTES { + String::new() + } else { + let kb = (bytes + 512) / 1024; // round + format!(" ({}KB - use offset to read relevant section)", kb) + } +} + +const MAX_PREVIEW: usize = 120; +const MAX_LINE_LEN: usize = 180; +const MAX_DEF_EXPAND_FIRST: usize = 8; +const MAX_DEF_EXPAND: usize = 5; +const MAX_FIRST_MATCH_EXPAND: usize = 8; + +fn trauncate_line_for_ai( + line: &str, + match_ranges: Option<&[(u32, u32)]>, + max_len: usize, +) -> String { + // Leading whitespace is already stripped by core (trim_whitespace option). + // Only strip trailing whitespace here. + let trimmed = line.trim_end(); + if trimmed.is_empty() { + return String::new(); + } + + if trimmed.len() <= max_len { + return trimmed.to_string(); + } + + // Use first match range to center the window + if let Some(ranges) = match_ranges + && let Some(&(match_start, match_end)) = ranges.first() + { + let match_start = match_start as usize; + let match_end = match_end as usize; + let match_len = match_end.saturating_sub(match_start); + + let budget = max_len.saturating_sub(match_len); + let before = budget / 3; + let after = budget - before; + + let win_start = match_start.saturating_sub(before); + let win_end = (match_end + after).min(trimmed.len()); + + // Clamp to char boundaries + let win_start = floor_char_boundary(trimmed, win_start); + let win_end = ceil_char_boundary(trimmed, win_end); + + let mut result = trimmed[win_start..win_end].to_string(); + if win_start > 0 { + result.insert(0, '…'); + } + if win_end < trimmed.len() { + result.push('…'); + } + return result; + } + + // No match ranges — truncate from start + let end = ceil_char_boundary(trimmed, max_len); + format!("{}…", &trimmed[..end]) +} + +fn floor_char_boundary(s: &str, index: usize) -> usize { + if index >= s.len() { + return s.len(); + } + let mut i = index; + while i > 0 && !s.is_char_boundary(i) { + i -= 1; + } + i +} + +fn ceil_char_boundary(s: &str, index: usize) -> usize { + if index >= s.len() { + return s.len(); + } + let mut i = index; + while i < s.len() && !s.is_char_boundary(i) { + i += 1; + } + i +} + +struct FileMeta<'a> { + file: &'a FileItem, + line_number: u64, + line_content: String, + is_definition: bool, + match_ranges: Vec<(u32, u32)>, + context_after: Vec, +} + +pub struct GrepFormatter<'a> { + pub matches: &'a [GrepMatch], + pub files: &'a [&'a FileItem], + pub total_matched: usize, + pub next_file_offset: usize, + pub output_mode: OutputMode, + pub max_results: usize, + pub show_context: bool, + pub auto_expand_defs: bool, + pub picker: &'a FilePicker, +} + +impl GrepFormatter<'_> { + pub fn format(&self, cursor_store: &mut CursorStore) -> String { + let GrepFormatter { + matches, + files, + total_matched, + next_file_offset, + output_mode, + max_results, + show_context, + auto_expand_defs, + picker, + } = *self; + + let items = if matches.len() > max_results { + &matches[..max_results] + } else { + matches + }; + + if output_mode == OutputMode::FilesWithMatches { + return format_files_with_matches( + items, + files, + next_file_offset, + auto_expand_defs, + cursor_store, + picker, + ); + } + + if output_mode == OutputMode::Count { + return format_count(items, files, next_file_offset, cursor_store, picker); + } + + // output_mode == usage + let mut lines: Vec = Vec::new(); + let unique_files = { + let mut seen = std::collections::HashSet::new(); + for m in items { + seen.insert(m.file_index); + } + seen.len() + }; + + let max_output_chars: usize = if output_mode == OutputMode::Usage || unique_files <= 3 { + 5000 + } else if unique_files <= 8 { + 3500 + } else { + 2500 + }; + + // File overview: collect first match per file + let file_preview = collect_file_preview(items, files, picker); + let mut content_def_file = String::new(); + let mut content_first_file = String::new(); + for fm in &file_preview { + if content_first_file.is_empty() { + content_first_file = fm.file.relative_path(picker); + } + if content_def_file.is_empty() && fm.is_definition { + content_def_file = fm.file.relative_path(picker); + } + } + + let content_suggest = if !content_def_file.is_empty() { + &content_def_file + } else { + &content_first_file + }; + if !content_suggest.is_empty() { + let file_count = file_preview.len(); + if file_count == 1 { + lines.push(format!("→ Read {} (only match)", content_suggest)); + } else if !content_def_file.is_empty() { + lines.push(format!("→ Read {} [def]", content_suggest)); + } else if file_count <= 3 { + lines.push(format!("→ Read {} (best match)", content_suggest)); + } + } + + if total_matched > items.len() { + lines.push(format!("{}/{} matches shown", items.len(), total_matched)); + } + + // Track which files already had a definition expanded + let mut def_expanded_files = std::collections::HashSet::new(); + + // Detailed content (subject to budget) + let mut char_count = 0usize; + let mut shown_count = 0usize; + let mut current_file = String::new(); + + // Reorder: definitions first, then usages, then imports (when auto-expanding) + let sorted_items: Vec = if auto_expand_defs { + let mut indices: Vec = (0..items.len()).collect(); + indices.sort_unstable_by_key(|&i| { + if items[i].is_definition { + 0 + } else if is_import_line(&items[i].line_content) { + 2 + } else { + 1 + } + }); + + indices + } else { + (0..items.len()).collect() + }; + + for &idx in &sorted_items { + let m = &items[idx]; + let file = files[m.file_index]; + let mut match_lines: Vec = Vec::new(); + + let file_rel_path = file.relative_path(picker); + if file_rel_path != current_file { + current_file = file_rel_path; + match_lines.push(current_file.to_string()); + } + + // Skip import-only lines when we already have definitions + if auto_expand_defs && is_import_line(&m.line_content) && !def_expanded_files.is_empty() + { + continue; + } + + // Context before (only when explicitly requested) + if show_context && !m.context_before.is_empty() { + let start_line = m.line_number.saturating_sub(m.context_before.len() as u64); + for (i, ctx) in m.context_before.iter().enumerate() { + match_lines.push(format!( + " {}-{}", + start_line + i as u64, + trauncate_line_for_ai(ctx, None, MAX_LINE_LEN) + )); + } + } + + // Match line + match_lines.push(format!( + " {}: {}", + m.line_number, + trauncate_line_for_ai( + &m.line_content, + Some(m.match_byte_offsets.as_ref()), + MAX_LINE_LEN + ) + )); + + // Context after (only when explicitly requested via context parameter) + if show_context && !m.context_after.is_empty() { + let start_line = m.line_number + 1; + for (i, ctx) in m.context_after.iter().enumerate() { + match_lines.push(format!( + " {}-{}", + start_line + i as u64, + trauncate_line_for_ai(ctx, None, MAX_LINE_LEN) + )); + } + match_lines.push("--".to_string()); + } + + // Auto-expand definitions with body context + let file_rel_for_expand = file.relative_path(picker); + if auto_expand_defs + && !show_context + && m.is_definition + && !m.context_after.is_empty() + && !def_expanded_files.contains(&file_rel_for_expand) + { + let expand_limit = if def_expanded_files.is_empty() { + MAX_DEF_EXPAND_FIRST + } else { + MAX_DEF_EXPAND + }; + def_expanded_files.insert(file_rel_for_expand); + let start_line = m.line_number + 1; + for (i, ctx) in m.context_after.iter().take(expand_limit).enumerate() { + if ctx.trim().is_empty() { + break; + } + match_lines.push(format!( + " {}| {}", + start_line + i as u64, + trauncate_line_for_ai(ctx, None, MAX_LINE_LEN) + )); + } + } + + let chunk = match_lines.join("\n"); + if char_count + chunk.len() > max_output_chars && shown_count > 0 { + break; + } + + char_count += chunk.len(); + lines.push(chunk); + shown_count += 1; + } + + if next_file_offset > 0 { + let cursor_id = cursor_store.store(next_file_offset); + lines.push(format!("\ncursor: {}", cursor_id)); + } + + lines.join("\n") + } +} + +fn format_files_with_matches( + items: &[GrepMatch], + files: &[&FileItem], + next_file_offset: usize, + auto_expand_defs: bool, + cursor_store: &mut CursorStore, + picker: &FilePicker, +) -> String { + let file_map = collect_file_preview(items, files, picker); + + let mut lines: Vec = Vec::new(); + let file_count = file_map.len(); + + // Find best Read target + let mut first_def_file = String::new(); + let mut first_file = String::new(); + for fm in &file_map { + if first_file.is_empty() { + first_file = fm.file.relative_path(picker); + } + if first_def_file.is_empty() && fm.is_definition { + first_def_file = fm.file.relative_path(picker); + } + } + let suggest_path = if !first_def_file.is_empty() { + &first_def_file + } else { + &first_file + }; + + if !suggest_path.is_empty() { + if file_count == 1 { + lines.push(format!( + "→ Read {} (only match — no need to search further)", + suggest_path + )); + } else if !first_def_file.is_empty() && file_count <= 5 { + lines.push(format!("→ Read {} (definition found)", suggest_path)); + } else if !first_def_file.is_empty() { + lines.push(format!("→ Read {} (definition)", suggest_path)); + } else if file_count <= 3 { + lines.push(format!("→ Read {} (best match)", suggest_path)); + } else { + lines.push(format!("→ Read {}", suggest_path)); + } + } + + let is_small_set = file_count <= 5; + let mut def_expanded_count = 0usize; + + for (file_idx, fm) in file_map.iter().enumerate() { + let is_def = fm.is_definition; + let def_tag = if is_def { " [def]" } else { "" }; + lines.push(format!( + "{}{}{}", + fm.file.relative_path(picker), + def_tag, + size_tag(fm.file.size) + )); + + // Show preview + if !fm.line_content.is_empty() && (is_def || file_idx == 0 || is_small_set) { + let ranges_ref: Option<&[(u32, u32)]> = if fm.match_ranges.is_empty() { + None + } else { + Some(&fm.match_ranges) + }; + lines.push(format!( + " {}: {}", + fm.line_number, + trauncate_line_for_ai(&fm.line_content, ranges_ref, MAX_PREVIEW) + )); + + // Auto-expand body context + if auto_expand_defs && !fm.context_after.is_empty() { + let expand_limit = if is_def { + let limit = if def_expanded_count == 0 { + MAX_DEF_EXPAND_FIRST + } else { + MAX_DEF_EXPAND + }; + def_expanded_count += 1; + limit + } else if is_small_set && file_idx == 0 { + MAX_FIRST_MATCH_EXPAND + } else if is_small_set { + MAX_DEF_EXPAND + } else { + 0 + }; + + if expand_limit > 0 { + let start_line = fm.line_number + 1; + for (i, ctx) in fm.context_after.iter().take(expand_limit).enumerate() { + if ctx.trim().is_empty() { + break; + } + lines.push(format!( + " {}| {}", + start_line + i as u64, + trauncate_line_for_ai(ctx, None, MAX_PREVIEW) + )); + } + } + } + } + } + + if next_file_offset > 0 { + let cursor_id = cursor_store.store(next_file_offset); + lines.push(format!("\ncursor: {}", cursor_id)); + } + + lines.join("\n") +} + +fn format_count( + items: &[GrepMatch], + files: &[&FileItem], + next_file_offset: usize, + cursor_store: &mut CursorStore, + picker: &FilePicker, +) -> String { + let mut counts: std::collections::HashMap = std::collections::HashMap::new(); + let mut order: Vec = Vec::new(); + for m in items { + let file = files[m.file_index]; + let path = file.relative_path(picker); + let count = counts.entry(path.to_string()).or_insert_with(|| { + order.push(path.to_string()); + 0 + }); + *count += 1; + } + + let mut lines: Vec = Vec::new(); + for path in &order { + lines.push(format!("{}: {}", path, counts[path.as_str()])); + } + if next_file_offset > 0 { + let cursor_id = cursor_store.store(next_file_offset); + lines.push(format!("\ncursor: {}", cursor_id)); + } + lines.join("\n") +} + +fn collect_file_preview<'a>( + items: &[GrepMatch], + files: &[&'a FileItem], + picker: &FilePicker, +) -> Vec> { + let mut file_preview: Vec> = Vec::new(); + let mut seen = std::collections::HashSet::new(); + for m in items { + let file = files[m.file_index]; + if seen.insert(file.relative_path(picker)) { + file_preview.push(FileMeta { + file, + line_number: m.line_number, + line_content: m.line_content.clone(), + is_definition: m.is_definition, + match_ranges: m.match_byte_offsets.iter().copied().collect(), + context_after: m.context_after.clone(), + }); + } + } + file_preview +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn trunc_strips_trailing_whitespace() { + // Leading whitespace is now stripped by core's trim_whitespace option. + // This function only strips trailing whitespace. + assert_eq!(trauncate_line_for_ai("foo()", None, 180), "foo()"); + assert_eq!(trauncate_line_for_ai("bar ", None, 180), "bar"); + assert_eq!(trauncate_line_for_ai(" ", None, 180), ""); + } + + #[test] + fn trunc_preserves_pre_trimmed_match_ranges() { + // Core already stripped leading whitespace and adjusted offsets, + // so "hello" arrives with match at bytes 0..5. + let line = "hello"; + let ranges = [(0, 5)]; + let result = trauncate_line_for_ai(line, Some(&ranges), 180); + assert_eq!(result, "hello"); + } + + #[test] + fn trunc_long_line_centered() { + // Core already stripped leading whitespace; offsets are pre-adjusted. + let line = format!("match_here{}", "x".repeat(200)); + let ranges = [(0u32, 10u32)]; + let result = trauncate_line_for_ai(&line, Some(&ranges), 50); + assert!(result.contains("match_here")); + assert!(result.len() <= 55); // budget + ellipsis chars + } +} diff --git a/crates/fff-mcp/src/server.rs b/crates/fff-mcp/src/server.rs new file mode 100644 index 0000000..c4affa3 --- /dev/null +++ b/crates/fff-mcp/src/server.rs @@ -0,0 +1,768 @@ +use crate::cursor::CursorStore; +use crate::output::{GrepFormatter, OutputMode, file_suffix}; +use fff::grep::{GrepMode, GrepSearchOptions, has_regex_metacharacters}; +use fff::types::{FileItem, PaginationArgs}; +use fff::{FuzzySearchOptions, QueryParser, SharedFilePicker}; +use fff_query_parser::AiGrepConfig; +use rmcp::handler::server::wrapper::Parameters; +use rmcp::model::*; +use rmcp::{ServerHandler, schemars, tool, tool_handler, tool_router}; +use std::borrow::Cow; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::{SystemTime, UNIX_EPOCH}; + +const SCAN_READY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); + +fn normalize_max_results(raw: Option, default: usize) -> usize { + match raw { + None => default, + Some(v) if v <= 0.0 || !v.is_finite() => default, + Some(v) => (v.round() as usize).max(1), + } +} + +fn cleanup_fuzzy_query(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for c in s.chars() { + if !matches!(c, ':' | '-' | '_') { + out.extend(c.to_lowercase()); + } + } + out +} + +fn make_grep_options( + output_mode: OutputMode, + mode: GrepMode, + file_offset: usize, + context: Option, +) -> (GrepSearchOptions, bool) { + let is_usage = output_mode == OutputMode::Usage; + let matches_per_file = match output_mode { + OutputMode::FilesWithMatches => 1, + _ if is_usage => 8, + _ => 10, + }; + let ctx_lines = if is_usage { + context.unwrap_or(1) + } else { + context.unwrap_or(0) + }; + let auto_expand = !is_usage && ctx_lines == 0; + let after_ctx = if auto_expand { 8 } else { ctx_lines }; + + ( + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: matches_per_file, + smart_case: true, + file_offset, + page_limit: 50, + mode, + time_budget_ms: 0, + before_context: ctx_lines, + after_context: after_ctx, + classify_definitions: true, + trim_whitespace: true, + abort_signal: None, + }, + auto_expand, + ) +} + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct FindFilesParams { + /// Fuzzy search query. Supports path prefixes and glob constraints. + // `pattern` alias for consistency with grep's alias and the common + // file-search parameter name (#311). + #[serde(alias = "pattern")] + pub query: String, + /// Max results (default 20). + #[serde(rename = "maxResults")] + // this has to be float because llms are stupid + pub max_results: Option, + /// Cursor from previous result. Only use if previous results weren't sufficient. + pub cursor: Option, +} + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct GrepParams { + /// Search text or regex query with optional constraint prefixes. + /// Matches within single lines only — use ONE specific term, not multiple words. + // `pattern` alias: LLMs that have seen multi_grep (which uses `patterns`) + // routinely call grep with `pattern`; accept it instead of erroring out + // with an unhelpful "missing field `query`" (#311). + #[serde(alias = "pattern")] + pub query: String, + /// Max matching lines (default 20). + #[serde(rename = "maxResults")] + pub max_results: Option, // this has to be float because llms are stupid + /// Cursor from previous result. Only use if previous results weren't sufficient. + pub cursor: Option, + /// Output format (default 'content'). + pub output_mode: Option, +} + +fn deserialize_patterns<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + use serde::de; + + struct PatternsVisitor; + + impl<'de> de::Visitor<'de> for PatternsVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string, an array of strings, or a stringified JSON array") + } + + fn visit_str(self, v: &str) -> Result { + // Try to parse as JSON array first + if v.starts_with('[') + && let Ok(parsed) = serde_json::from_str::>(v) + { + return Ok(parsed); + } + Ok(vec![v.to_string()]) + } + + fn visit_string(self, v: String) -> Result { + if v.starts_with('[') + && let Ok(parsed) = serde_json::from_str::>(&v) + { + return Ok(parsed); + } + Ok(vec![v]) + } + + fn visit_seq>(self, mut seq: A) -> Result { + let mut values = Vec::new(); + while let Some(value) = seq.next_element::()? { + values.push(value); + } + Ok(values) + } + } + + deserializer.deserialize_any(PatternsVisitor) +} + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct MultiGrepParams { + /// Patterns to match (OR logic). Include all naming conventions: snake_case, PascalCase, camelCase. + #[serde(deserialize_with = "deserialize_patterns")] + pub patterns: Vec, + /// File constraints (e.g. '*.{ts,tsx} !test/'). ALWAYS provide when possible. + pub constraints: Option, + /// Max matching lines (default 20). + #[serde(rename = "maxResults")] + pub max_results: Option, + /// Cursor from previous result. + pub cursor: Option, + /// Output format (default 'content'). + pub output_mode: Option, + /// Context lines before/after each match. + pub context: Option, +} + +#[derive(Clone)] +pub struct FffServer { + picker: SharedFilePicker, + cursor_store: Arc>, + update_notice_sent: Arc, + last_activity: Arc, + scan_ready: Arc, +} + +fn now_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0) +} + +impl FffServer { + pub fn new(picker: SharedFilePicker) -> Self { + Self { + picker, + cursor_store: Arc::new(Mutex::new(CursorStore::new())), + update_notice_sent: Arc::new(AtomicBool::new(false)), + last_activity: Arc::new(AtomicU64::new(now_secs())), + scan_ready: Arc::new(AtomicBool::new(false)), + } + } + + pub fn last_activity(&self) -> Arc { + self.last_activity.clone() + } + + fn bump_activity(&self) { + self.last_activity.store(now_secs(), Ordering::Relaxed); + } + + fn wait_for_scan(&self, timeout: std::time::Duration) -> Result<(), ErrorData> { + if self.scan_ready.load(Ordering::Relaxed) { + return Ok(()); + } + + let deadline = std::time::Instant::now() + timeout; + + loop { + let is_scanning = self + .picker + .read() + .ok() + .as_ref() + .and_then(|g| g.as_ref()) + .map(|p| p.is_scan_active()) + .unwrap_or(true); + + if !is_scanning { + self.scan_ready.store(true, Ordering::Relaxed); + return Ok(()); + } + if std::time::Instant::now() >= deadline { + return Err(ErrorData::internal_error( + "Index is still building; retry shortly", + None, + )); + } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + } + + fn lock_cursors(&self) -> Result, ErrorData> { + self.cursor_store.lock().map_err(|e| { + ErrorData::internal_error(format!("Failed to acquire cursor store lock: {e}"), None) + }) + } + + fn maybe_append_update_notice(&self, result: &mut CallToolResult) { + if self.update_notice_sent.swap(true, Ordering::Relaxed) { + return; + } + let notice = crate::update_check::get_update_notice(); + if notice.is_empty() { + // Reset so the next call can try again (check may still be in flight) + self.update_notice_sent.store(false, Ordering::Relaxed); + return; + } + result.content.push(Content::text(notice)); + } + + fn perform_grep( + &self, + query: &str, + mode: GrepMode, + max_results: usize, + cursor_id: Option<&str>, + output_mode: OutputMode, + context: Option, + ) -> Result { + let file_offset = cursor_id + .and_then(|id| self.cursor_store.lock().ok()?.get(id)) + .unwrap_or(0); + + let (options, auto_expand) = make_grep_options(output_mode, mode, file_offset, context); + let ctx_lines = options.before_context; + + // Acquire picker lock once for the entire operation. + let guard = self.picker.read().map_err(|e| { + ErrorData::internal_error(format!("Failed to acquire picker lock: {e}"), None) + })?; + let picker = guard + .as_ref() + .ok_or_else(|| ErrorData::internal_error("File picker not initialized", None))?; + + let parser = QueryParser::new(AiGrepConfig); + let parsed = parser.parse(query); + let result = picker.grep(&parsed, &options); + + if result.matches.is_empty() && file_offset == 0 { + // Auto-retry: try broadening multi-word queries by dropping first non-constraint word + let parts: Vec<&str> = query.split_whitespace().collect(); + if parts.len() >= 2 { + let first_word = parts[0]; + let is_valid_constraint = first_word.starts_with('!') + || first_word.starts_with('*') + || first_word.ends_with('/'); + + if !is_valid_constraint { + let rest_query = parts[1..].join(" "); + let rest_parsed = parser.parse(&rest_query); + + let rest_text = rest_parsed.grep_text(); + let retry_mode = if has_regex_metacharacters(&rest_text) { + GrepMode::Regex + } else { + mode + }; + + let (retry_options, _) = make_grep_options(output_mode, retry_mode, 0, context); + let retry_result = picker.grep(&rest_parsed, &retry_options); + + if !retry_result.matches.is_empty() && retry_result.matches.len() <= 10 { + let mut cs = self.lock_cursors()?; + let text = &GrepFormatter { + matches: &retry_result.matches, + files: &retry_result.files, + total_matched: retry_result.matches.len(), + next_file_offset: retry_result.next_file_offset, + output_mode, + max_results, + show_context: ctx_lines > 0, + auto_expand_defs: auto_expand, + picker, + } + .format(&mut cs); + return Ok(CallToolResult::success(vec![Content::text(format!( + "0 matches for '{}'. Auto-broadened to '{}':\n{}", + query, rest_query, text + ))])); + } + } + } + + // Fuzzy fallback for typo tolerance + let fuzzy_query = cleanup_fuzzy_query(query); + let (fuzzy_options, _) = make_grep_options(output_mode, GrepMode::Fuzzy, 0, Some(0)); + let fuzzy_parsed = parser.parse(&fuzzy_query); + let fuzzy_result = picker.grep(&fuzzy_parsed, &fuzzy_options); + + if !fuzzy_result.matches.is_empty() { + let mut lines: Vec = Vec::new(); + lines.push(format!( + "0 exact matches. {} approximate:", + fuzzy_result.matches.len() + )); + let mut current_file = String::new(); + for m in fuzzy_result.matches.iter().take(3) { + let file = fuzzy_result.files[m.file_index]; + let file_rel = file.relative_path(picker); + if file_rel != current_file { + current_file = file_rel; + lines.push(current_file.to_string()); + } + lines.push(format!(" {}: {}", m.line_number, m.line_content)); + } + return Ok(CallToolResult::success(vec![Content::text( + lines.join("\n"), + )])); + } + + // File path fallback: if query looks like a path, suggest the matching file + if query.contains('/') { + let file_parser = QueryParser::default(); + let file_query = file_parser.parse(query); + let file_opts = FuzzySearchOptions { + max_threads: 0, + current_file: None, + project_path: Some(picker.base_path()), + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 1, + }, + }; + let file_result = picker.fuzzy_search(&file_query, None, file_opts); + if let (Some(top), Some(score)) = + (file_result.items.first(), file_result.scores.first()) + { + // Only suggest when the match is strong enough. + let query_len = query.len() as i32; + if score.base_score > query_len * 10 { + return Ok(CallToolResult::success(vec![Content::text(format!( + "0 content matches. But there is a relevant file path: {}", + top.relative_path(picker) + ))])); + } + } + } + + return Ok(CallToolResult::success(vec![Content::text( + "0 matches.".to_string(), + )])); + } + + if result.matches.is_empty() { + return Ok(CallToolResult::success(vec![Content::text( + "0 matches.".to_string(), + )])); + } + + let mut cs = self.lock_cursors()?; + let text = &GrepFormatter { + matches: &result.matches, + files: &result.files, + total_matched: result.matches.len(), + next_file_offset: result.next_file_offset, + output_mode, + max_results, + show_context: ctx_lines > 0, + auto_expand_defs: auto_expand, + picker, + } + .format(&mut cs); + + Ok(CallToolResult::success(vec![Content::text(text)])) + } +} + +#[tool_router] +impl FffServer { + /// Fuzzy file search by name. Searches FILE NAMES, not file contents. + /// Use it when you need to find a file, not a definition. + /// Use grep instead for searching code content (definitions, usage patterns). + /// Supports fuzzy matching, path prefixes ('shc/'), and glob constraints. + /// IMPORTANT: Keep queries SHORT — prefer 1-2 terms max. + #[tool( + name = "find_files", + description = "Fuzzy file search by name. Searches FILE NAMES, not file contents. Use it when you need to find a file, not a definition. Use grep instead for searching code content (definitions, usage patterns). Supports fuzzy matching, path prefixes ('src/'), and glob constraints ('name **/src/*.{ts,tsx} !test/'). IMPORTANT: Keep queries SHORT — prefer 1-2 terms max. Multiple words are a waterfall (each narrows results), NOT OR. If unsure, start broad with 1 term and refine." + )] + fn find_files( + &self, + Parameters(params): Parameters, + ) -> Result { + self.bump_activity(); + self.wait_for_scan(SCAN_READY_TIMEOUT)?; + + let max_results = normalize_max_results(params.max_results, 20); + let query = ¶ms.query; + + let page_offset = params + .cursor + .as_deref() + .and_then(|id| self.cursor_store.lock().ok()?.get(id)) + .unwrap_or(0); + + let guard = self.picker.read().map_err(|e| { + ErrorData::internal_error(format!("Failed to acquire picker lock: {e}"), None) + })?; + let picker = guard + .as_ref() + .ok_or_else(|| ErrorData::internal_error("File picker not initialized", None))?; + let base_path = picker.base_path(); + let make_opts = |offset: usize| FuzzySearchOptions { + max_threads: 0, + current_file: None, + project_path: Some(base_path), + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset, + limit: max_results, + }, + }; + + let parser = QueryParser::default(); + let fff_query = parser.parse(query); + let result = picker.fuzzy_search(&fff_query, None, make_opts(page_offset)); + let total_files = result.total_files; + + // Auto-retry with fewer terms if 3+ words return 0 results + let words: Vec<&str> = query.split_whitespace().collect(); + let shorter = words.get(..2).map(|w| w.join(" ")); + + let (items, scores, total_matched) = + if result.items.is_empty() && words.len() >= 3 && page_offset == 0 { + if let Some(shorter) = &shorter { + let shorter_query = parser.parse(shorter); + let retry = picker.fuzzy_search(&shorter_query, None, make_opts(0)); + + (retry.items, retry.scores, retry.total_matched) + } else { + (result.items, result.scores, result.total_matched) + } + } else { + (result.items, result.scores, result.total_matched) + }; + + if items.is_empty() { + return Ok(CallToolResult::success(vec![Content::text(format!( + "0 results ({} indexed)", + total_files + ))])); + } + + let mut lines: Vec = Vec::new(); + let top_item = items[0]; + let is_exact_match = scores[0].exact_match; + + if page_offset == 0 { + if is_exact_match { + lines.push(format!( + "→ Read {} (exact match!)", + top_item.relative_path(picker) + )); + } else if scores.len() < 2 || scores[0].total > scores[1].total.saturating_mul(2) { + lines.push(format!( + "→ Read {} (best match — Read this file directly)", + top_item.relative_path(picker) + )); + } + } + + let next_offset = page_offset + items.len(); + let has_more = next_offset < total_matched; + + if has_more { + lines.push(format!("{}/{} matches", items.len(), total_matched)); + } + + for item in &items { + lines.push(format!( + "{}{}", + item.relative_path(picker), + file_suffix(item.git_status, item.total_frecency_score()) + )); + } + + if has_more { + let mut cs = self.lock_cursors()?; + let cursor_id = cs.store(next_offset); + lines.push(format!("cursor: {}", cursor_id)); + } + + let mut result = CallToolResult::success(vec![Content::text(lines.join("\n"))]); + self.maybe_append_update_notice(&mut result); + Ok(result) + } + + /// Search file contents for text patterns. This is the DEFAULT search tool. + /// Prefer plain text over regex. Filter files with constraints. + #[tool( + name = "grep", + description = "Search file contents. Search for bare identifiers (e.g. 'InProgressQuote', 'ActorAuth'), NOT code syntax or regex. Filter files with constraints (e.g. '*.rs query', 'src/ query'). Use filename, directory (ending with /) or glob expressions to prefilter. See server instructions for constraint syntax and core rules." + )] + fn grep( + &self, + Parameters(params): Parameters, + ) -> Result { + self.bump_activity(); + self.wait_for_scan(SCAN_READY_TIMEOUT)?; + + let max_results = normalize_max_results(params.max_results, 20); + let output_mode = OutputMode::new(params.output_mode.as_deref()); + + let parsed = QueryParser::new(AiGrepConfig).parse(¶ms.query); + let grep_text = parsed.grep_text(); + + let mode = if has_regex_metacharacters(&grep_text) { + GrepMode::Regex + } else { + GrepMode::PlainText + }; + + let mut result = self.perform_grep( + ¶ms.query, + mode, + max_results, + params.cursor.as_deref(), + output_mode, + None, + )?; + self.maybe_append_update_notice(&mut result); + Ok(result) + } + + /// Search file contents for lines matching ANY of multiple patterns (OR logic). + /// Patterns are literal text — NEVER escape special characters. + #[tool( + name = "multi_grep", + description = "Search file contents for lines matching ANY of multiple patterns (OR logic). IMPORTANT: This returns files where ANY query matches, NOT all patterns. Patterns are literal text — NEVER escape special characters (no \\( \\) \\. etc). Faster than regex alternation for literal text. See server instructions for constraint syntax." + )] + fn multi_grep( + &self, + Parameters(params): Parameters, + ) -> Result { + self.bump_activity(); + self.wait_for_scan(SCAN_READY_TIMEOUT)?; + + let mut result = self.multi_grep_inner(params)?; + self.maybe_append_update_notice(&mut result); + Ok(result) + } +} + +impl FffServer { + fn multi_grep_inner(&self, params: MultiGrepParams) -> Result { + let max_results = normalize_max_results(params.max_results, 20); + let context = params.context.map(|v| v.round() as usize); + let output_mode = OutputMode::new(params.output_mode.as_deref()); + + let file_offset = params + .cursor + .as_deref() + .and_then(|id| self.cursor_store.lock().ok()?.get(id)) + .unwrap_or(0); + + let (options, auto_expand) = + make_grep_options(output_mode, GrepMode::PlainText, file_offset, context); + + let ctx_lines = options.before_context; + let constraint_query = params.constraints.as_deref().unwrap_or(""); + let guard = self.picker.read().map_err(|e| { + ErrorData::internal_error(format!("Failed to acquire picker lock: {e}"), None) + })?; + let picker = guard + .as_ref() + .ok_or_else(|| ErrorData::internal_error("File picker not initialized", None))?; + let patterns_refs: Vec<&str> = params.patterns.iter().map(|s| s.as_str()).collect(); + + let parser = fff_query_parser::QueryParser::new(fff_query_parser::AiGrepConfig); + let parsed_constraints = parser.parse(constraint_query); + let constraints = parsed_constraints.constraints.as_slice(); + + let result = picker.multi_grep(&patterns_refs, constraints, &options); + let file_refs: Vec<&FileItem> = result.files.to_vec(); + + if result.matches.is_empty() && file_offset == 0 { + // Fallback: try individual patterns with plain grep + let (fallback_options, _) = + make_grep_options(output_mode, GrepMode::PlainText, 0, context); + + let fallback_options = GrepSearchOptions { + time_budget_ms: 3000, + before_context: 0, + ..fallback_options + }; + + for pat in ¶ms.patterns { + let full_query: Cow = if !constraint_query.is_empty() { + Cow::Owned(format!("{} {}", constraint_query, pat)) + } else { + Cow::Borrowed(pat) + }; + + let parsed = parser.parse(&full_query); + let fb_result = picker.grep(&parsed, &fallback_options); + + if !fb_result.matches.is_empty() { + let fb_file_refs: Vec<&FileItem> = fb_result.files.to_vec(); + let mut cs = self.lock_cursors()?; + let text = &GrepFormatter { + matches: &fb_result.matches, + files: &fb_file_refs, + total_matched: fb_result.matches.len(), + next_file_offset: fb_result.next_file_offset, + output_mode, + max_results, + show_context: false, + auto_expand_defs: auto_expand, + picker, + } + .format(&mut cs); + return Ok(CallToolResult::success(vec![Content::text(format!( + "0 multi-pattern matches. Plain grep fallback for \"{}\":\n{}", + pat, text + ))])); + } + } + + return Ok(CallToolResult::success(vec![Content::text( + "0 matches.".to_string(), + )])); + } + + if result.matches.is_empty() { + return Ok(CallToolResult::success(vec![Content::text( + "0 matches.".to_string(), + )])); + } + + let mut cs = self.lock_cursors()?; + let text = &GrepFormatter { + matches: &result.matches, + files: &file_refs, + total_matched: result.matches.len(), + next_file_offset: result.next_file_offset, + output_mode, + max_results, + show_context: ctx_lines > 0, + auto_expand_defs: auto_expand, + picker, + } + .format(&mut cs); + + Ok(CallToolResult::success(vec![Content::text(text)])) + } +} + +#[tool_handler] +impl ServerHandler for FffServer { + fn get_info(&self) -> ServerInfo { + let notice = crate::update_check::get_update_notice(); + let instructions = if notice.is_empty() { + crate::MCP_INSTRUCTIONS.to_string() + } else { + format!("{}{}", crate::MCP_INSTRUCTIONS, notice) + }; + + ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + .with_server_info(Implementation::new("fff", env!("CARGO_PKG_VERSION"))) + .with_instructions(instructions) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn normalize_max_results_none_uses_default() { + assert_eq!(normalize_max_results(None, 20), 20); + } + + #[test] + fn normalize_max_results_zero_uses_default() { + // Issue #400: `maxResults: 0` must not return zero items for grep + // while `find_files` returns the full set. Both tools now map 0 to + // the default limit. + assert_eq!(normalize_max_results(Some(0.0), 20), 20); + } + + #[test] + fn normalize_max_results_negative_uses_default() { + assert_eq!(normalize_max_results(Some(-5.0), 20), 20); + } + + #[test] + fn normalize_max_results_non_finite_uses_default() { + assert_eq!(normalize_max_results(Some(f64::NAN), 20), 20); + assert_eq!(normalize_max_results(Some(f64::INFINITY), 20), 20); + } + + #[test] + fn normalize_max_results_rounds_and_clamps() { + assert_eq!(normalize_max_results(Some(0.4), 20), 1); + assert_eq!(normalize_max_results(Some(10.0), 20), 10); + assert_eq!(normalize_max_results(Some(10.7), 20), 11); + } + + #[test] + fn grep_params_accepts_pattern_alias() { + // Issue #311: LLMs flip between `query` and `pattern`; accept both. + let via_query: GrepParams = + serde_json::from_str(r#"{"query":"foo"}"#).expect("query field"); + assert_eq!(via_query.query, "foo"); + + let via_pattern: GrepParams = + serde_json::from_str(r#"{"pattern":"foo"}"#).expect("pattern alias"); + assert_eq!(via_pattern.query, "foo"); + } + + #[test] + fn find_files_params_accepts_pattern_alias() { + let via_query: FindFilesParams = + serde_json::from_str(r#"{"query":"foo"}"#).expect("query field"); + assert_eq!(via_query.query, "foo"); + + let via_pattern: FindFilesParams = + serde_json::from_str(r#"{"pattern":"foo"}"#).expect("pattern alias"); + assert_eq!(via_pattern.query, "foo"); + } +} diff --git a/crates/fff-mcp/src/update_check.rs b/crates/fff-mcp/src/update_check.rs new file mode 100644 index 0000000..0cfd909 --- /dev/null +++ b/crates/fff-mcp/src/update_check.rs @@ -0,0 +1,78 @@ +//! Background update checker — compares the embedded build hash against +//! the latest GitHub release tag to surface upgrade notices in MCP instructions. + +use std::sync::OnceLock; + +const REPO: &str = "dmtrKovalenko/fff.nvim"; +const BUILD_HASH: &str = env!("FFF_GIT_HASH"); + +/// Holds the result of the update check (empty string = up to date or check failed). +static UPDATE_NOTICE: OnceLock = OnceLock::new(); + +/// Returns the update notice if the check has completed, empty string otherwise. +pub fn get_update_notice() -> &'static str { + UPDATE_NOTICE.get().map(|s| s.as_str()).unwrap_or("") +} + +/// Kick off the update check in a background thread so it never blocks the server. +pub fn spawn_update_check() { + std::thread::spawn(|| { + let notice = check_latest_release(); + let _ = UPDATE_NOTICE.set(notice); + }); +} + +/// Fetch the latest release tag from GitHub and compare against the build hash. +fn check_latest_release() -> String { + match fetch_latest_tag() { + Ok(tag) => compare_versions(BUILD_HASH, &tag), + Err(_) => String::new(), + } +} + +/// Compare a build hash against a release tag. +/// Returns an update notice string, or empty if up-to-date. +fn compare_versions(build_hash: &str, release_tag: &str) -> String { + let tag = release_tag.trim(); + if tag.is_empty() || build_hash == "unknown" { + return String::new(); + } + + let our_short = &build_hash[..build_hash.len().min(tag.len())]; + if our_short == tag { + return String::new(); + } + + format!( + "\n[fff update available: `curl -fsSL https://raw.githubusercontent.com/{REPO}/main/install-mcp.sh | bash`]\n" + ) +} + +/// Shell out to curl to fetch the latest release tag name from GitHub API. +fn fetch_latest_tag() -> Result> { + let output = std::process::Command::new("curl") + .args([ + "-fsSL", + "--max-time", + "5", + "-H", + "Accept: application/vnd.github.v3+json", + &format!("https://api.github.com/repos/{REPO}/releases?per_page=1"), + ]) + .output()?; + + if !output.status.success() { + return Err("curl failed".into()); + } + + let body = String::from_utf8(output.stdout)?; + let releases: Vec = serde_json::from_str(&body)?; + let tag = releases + .first() + .and_then(|r| r.get("tag_name")) + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + Ok(tag) +} diff --git a/crates/fff-nvim/Cargo.toml b/crates/fff-nvim/Cargo.toml new file mode 100644 index 0000000..eb24826 --- /dev/null +++ b/crates/fff-nvim/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "fff-nvim" +version = "0.9.6" +edition = "2024" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] + +[features] +# Pure-Rust walker by default; zlob is opt-in (needs Zig). +default = ["ripgrep"] +ripgrep = ["fff/ripgrep", "fff-query-parser/ripgrep", "dep:ignore"] +zlob = ["fff/zlob", "fff-query-parser/zlob", "dep:zlob"] + +[dependencies] +# Workspace dependencies +ahash = { workspace = true } +tracing = { workspace = true } + +# Local crates +fff = { package = "fff-search", path = "../fff-core", version = "0.9.6", default-features = false, features = [ + "mimalloc-collect", +] } +fff-query-parser = { path = "../fff-query-parser", version = "0.9.6", default-features = false } +chrono = { version = "0.4", features = ["serde"] } +ctrlc = "3.4.2" +git2 = { workspace = true } +ignore = { version = "0.4.22", optional = true } +mimalloc = { version = "0.1.47", features = ["local_dynamic_tls"] } +mlua = { version = "0.11.1", features = ["module", "luajit"] } +once_cell = "1.20.2" +zlob = { workspace = true, optional = true } + +[dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } +rand = { version = "0.8", features = ["small_rng"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[[bench]] +name = "fuzzy_search" +path = "benches/fuzzy_search_bench.rs" +harness = false + +[[bench]] +name = "grep_bench" +path = "benches/grep_bench.rs" +harness = false + +[[bench]] +name = "query_tracker" +path = "benches/query_tracker_bench.rs" +harness = false + +[[bench]] +name = "scan" +path = "benches/scan_bench.rs" +harness = false diff --git a/crates/fff-nvim/benches/fuzzy_search_bench.rs b/crates/fff-nvim/benches/fuzzy_search_bench.rs new file mode 100644 index 0000000..e0ccfee --- /dev/null +++ b/crates/fff-nvim/benches/fuzzy_search_bench.rs @@ -0,0 +1,661 @@ +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use fff::file_picker::{FFFMode, FilePicker}; +use fff::types::PaginationArgs; +use fff::{ + FilePickerOptions, FuzzySearchOptions, GrepMode, GrepSearchOptions, QueryParser, + SharedFilePicker, SharedFrecency, +}; +use std::path::PathBuf; +use std::time::Duration; + +fn init_tracing() { + // use tracing_subscriber::EnvFilter; + // use tracing_subscriber::fmt; + // let _ = fmt() + // .with_env_filter( + // EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), + // ) + // .with_target(false) + // .with_thread_ids(true) + // .with_line_number(true) + // .try_init(); +} + +fn init_file_picker_internal( + path: &str, + shared_picker: &SharedFilePicker, + shared_frecency: &SharedFrecency, +) -> Result<(), String> { + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: path.to_string(), + enable_mmap_cache: false, + mode: FFFMode::Neovim, + ..Default::default() + }, + ) + .map_err(|e| format!("Failed to create FilePicker: {:?}", e)) +} + +fn wait_for_scan_completion( + shared_picker: &SharedFilePicker, + timeout_secs: u64, +) -> Result { + let start = std::time::Instant::now(); + let timeout = Duration::from_secs(timeout_secs); + let mut last_log = std::time::Instant::now(); + let mut iteration = 0; + + loop { + iteration += 1; + + { + let picker_guard = shared_picker + .read() + .map_err(|_| "Failed to acquire read lock")?; + if let Some(ref picker) = *picker_guard { + let is_scanning = picker.is_scan_active(); + let file_count = picker.get_files().len(); + + // Log progress every 2 seconds + if last_log.elapsed() >= Duration::from_secs(2) { + eprintln!( + " [{:.1}s] Scanning: {}, Files: {}, Iterations: {}", + start.elapsed().as_secs_f32(), + is_scanning, + file_count, + iteration + ); + last_log = std::time::Instant::now(); + } + + if !is_scanning && file_count > 0 { + eprintln!( + " ✓ Scan complete after {:.2}s: {} files found", + start.elapsed().as_secs_f32(), + file_count + ); + return Ok(file_count); + } + } else { + if iteration % 100 == 0 { + eprintln!( + " [{:.1}s] FilePicker is None (iteration {})", + start.elapsed().as_secs_f32(), + iteration + ); + } + } + } + + if start.elapsed() > timeout { + return Err(format!( + "Scan timed out after {} seconds (iteration {})", + timeout_secs, iteration + )); + } + + std::thread::sleep(Duration::from_millis(50)); + } +} + +fn setup_once() -> Result<(SharedFilePicker, SharedFrecency), String> { + init_tracing(); + + let big_repo_path = PathBuf::from("./big-repo"); + if !big_repo_path.exists() { + return Err("./big-repo directory does not exist. Run git clone https://github.com/torvalds/linux.git big-repo".to_string()); + } + + let canonical_path = fff::path_utils::canonicalize(&big_repo_path) + .map_err(|e| format!("Failed to canonicalize path: {}", e))?; + eprintln!(" Path: {:?}", canonical_path); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + init_file_picker_internal( + &canonical_path.to_string_lossy(), + &shared_picker, + &shared_frecency, + )?; + + eprintln!(" Waiting for background scan to complete..."); + let file_count = wait_for_scan_completion(&shared_picker, 120)?; + eprintln!( + " ✓ Indexed {} files (will be reused for all benchmarks)\n", + file_count + ); + + Ok((shared_picker, shared_frecency)) +} + +fn bench_search_queries(c: &mut Criterion) { + let (sp, _sf) = match setup_once() { + Ok(result) => result, + Err(e) => { + eprint!("Failed to setup picker {e:?}"); + return; + } + }; + + let guard = sp.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let mut group = c.benchmark_group("search"); + group.sample_size(100); + + let test_queries = vec![ + ("short", "mod"), + ("medium", "controller"), + ("long", "user_authentication"), + ("typo", "contrlr"), + ("partial", "src/lib"), + ]; + + let parser = QueryParser::default(); + + for (name, query) in test_queries { + let parsed = parser.parse(query); + group.bench_with_input(BenchmarkId::new("query", name), &query, |b, &_query| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }, + ); + results.total_matched + }); + }); + } + + group.finish(); +} + +/// Benchmark search with different thread counts +fn bench_search_thread_scaling(c: &mut Criterion) { + let (sp, _sf) = match setup_once() { + Ok(result) => result, + Err(e) => { + eprintln!("Skipping thread scaling benchmarks: {}", e); + return; + } + }; + + let guard = sp.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let mut group = c.benchmark_group("thread_scaling"); + group.sample_size(100); + + let query = "controller"; + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let thread_counts = vec![1, 2, 4, 8]; + + for threads in thread_counts { + group.bench_with_input( + BenchmarkId::from_parameter(threads), + &threads, + |b, &threads| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed), + None, + FuzzySearchOptions { + max_threads: threads, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }, + ); + results.total_matched + }); + }, + ); + } + + group.finish(); +} + +/// Benchmark search with different result limits +fn bench_search_result_limits(c: &mut Criterion) { + let (sp, _sf) = match setup_once() { + Ok(result) => result, + Err(e) => { + eprintln!("Skipping result limit benchmarks: {}", e); + return; + } + }; + + let guard = sp.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let mut group = c.benchmark_group("result_limits"); + group.sample_size(100); + + let query = "mod"; + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let result_limits = vec![10, 50, 100, 500]; + + for limit in result_limits { + group.bench_with_input(BenchmarkId::from_parameter(limit), &limit, |b, &limit| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { offset: 0, limit }, + }, + ); + results.total_matched + }); + }); + } + + group.finish(); +} + +/// Benchmark search algorithm performance with queries of varying selectivity +fn bench_search_scalability(c: &mut Criterion) { + let (sp, _sf) = match setup_once() { + Ok(result) => result, + Err(e) => { + eprintln!("Skipping scalability benchmarks: {}", e); + return; + } + }; + + let guard = sp.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + if picker.get_files().len() < 1000 { + eprintln!( + "Skipping scalability benchmark: need at least 1000 files, got {}", + picker.get_files().len() + ); + return; + } + + let mut group = c.benchmark_group("search_scalability"); + group.sample_size(50); + + let parser = QueryParser::default(); + let selectivity_queries = vec![ + ("broad_a", "a"), + ("medium_mod", "mod"), + ("narrow_controller", "controller"), + ("very_narrow_user_auth", "user_authentication"), + ]; + + for (name, query) in selectivity_queries { + let parsed = parser.parse(query); + group.bench_with_input(BenchmarkId::from_parameter(name), &name, |b, _| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }, + ); + results.total_matched + }); + }); + } + + group.finish(); +} + +/// Benchmark search performance with different ordering modes +fn bench_search_ordering(c: &mut Criterion) { + let (sp, _sf) = match setup_once() { + Ok(result) => result, + Err(e) => { + eprintln!("Skipping ordering benchmarks: {}", e); + return; + } + }; + + let guard = sp.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let mut group = c.benchmark_group("ordering"); + group.sample_size(100); + + let parser = QueryParser::default(); + let parsed_controller = parser.parse("controller"); + let parsed_mod = parser.parse("mod"); + + // Benchmark normal order (descending) + group.bench_function("normal_order", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed_controller), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }, + ); + results.total_matched + }); + }); + + // Benchmark reverse order (ascending) + group.bench_function("reverse_order", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed_controller), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }, + ); + results.total_matched + }); + }); + + // Benchmark with large result set + group.bench_function("normal_order_large", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed_mod), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 500, + }, + }, + ); + results.total_matched + }); + }); + + group.bench_function("reverse_order_large", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed_mod), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 500, + }, + }, + ); + results.total_matched + }); + }); + + // Benchmark with small result set + group.bench_function("normal_order_small", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed_controller), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 10, + }, + }, + ); + results.total_matched + }); + }); + + group.bench_function("reverse_order_small", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed_controller), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 10, + }, + }, + ); + results.total_matched + }); + }); + + group.finish(); +} + +/// Benchmark pagination: first page vs deep page +fn bench_pagination_performance(c: &mut Criterion) { + let (sp, _sf) = match setup_once() { + Ok(result) => result, + Err(e) => { + eprintln!("Skipping pagination benchmarks: {}", e); + return; + } + }; + + let guard = sp.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let mut group = c.benchmark_group("pagination"); + group.sample_size(100); + + let query = "mod"; + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let page_size = 40; + + // Benchmark first page (uses partial sort optimization) + group.bench_function("page_0_size_40", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: page_size, + }, + }, + ); + results.total_matched + }); + }); + + // Benchmark 10th page (requires full sort, no optimization) + group.bench_function("page_10_size_40", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 10, + limit: page_size, + }, + }, + ); + results.total_matched + }); + }); + + // Benchmark 50th page (even deeper pagination) + group.bench_function("page_50_size_40", |b| { + b.iter(|| { + let results = picker.fuzzy_search( + black_box(&parsed), + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 50, + limit: page_size, + }, + }, + ); + results.total_matched + }); + }); + + group.finish(); +} + +/// Benchmark grep search via the FilePicker public API +fn bench_grep_search(c: &mut Criterion) { + let (sp, _sf) = match setup_once() { + Ok(result) => result, + Err(e) => { + eprintln!("Skipping grep benchmarks: {}", e); + return; + } + }; + + let guard = sp.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let mut group = c.benchmark_group("grep"); + group.sample_size(50); + + let options = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 0, + smart_case: true, + file_offset: 0, + page_limit: 100, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + + let test_queries = vec![ + ("common", "struct"), + ("specific", "DEFINE_MUTEX"), + ("path_filter", "*.h mutex"), + ]; + + let grep_parser = fff::QueryParser::new(fff::GrepConfig); + + for (name, query) in &test_queries { + let parsed = grep_parser.parse(query); + + group.bench_with_input(BenchmarkId::new("grep", name), query, |b, _| { + b.iter(|| { + let result = picker.grep(black_box(&parsed), black_box(&options)); + result.matches.len() + }); + }); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_search_queries, + bench_search_thread_scaling, + bench_search_result_limits, + bench_search_scalability, + bench_search_ordering, + bench_pagination_performance, + bench_grep_search, +); + +criterion_main!(benches); diff --git a/crates/fff-nvim/benches/grep_bench.rs b/crates/fff-nvim/benches/grep_bench.rs new file mode 100644 index 0000000..6493994 --- /dev/null +++ b/crates/fff-nvim/benches/grep_bench.rs @@ -0,0 +1,236 @@ +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use fff::file_picker::{FFFMode, FilePicker}; +use fff::{ + FilePickerOptions, GrepMode, GrepSearchOptions, SharedFilePicker, SharedFrecency, + parse_grep_query, +}; +use std::sync::OnceLock; +use std::time::Duration; + +struct TestData { + shared_picker: SharedFilePicker, +} + +static SETUP: OnceLock = OnceLock::new(); +static SETUP_NO_INDEX: OnceLock = OnceLock::new(); + +fn big_repo_path() -> String { + if let Some(path) = std::env::var_os("BIG_REPO_PATH") { + return path.to_string_lossy().into_owned(); + } + + let candidates = ["./big-repo", "../../big-repo"]; + for p in &candidates { + if std::path::Path::new(p).exists() { + return p.to_string(); + } + } + panic!( + "./big-repo not found. Run from workspace root:\n \ + git clone --depth 1 https://github.com/torvalds/linux.git big-repo" + ); +} + +fn setup() -> &'static TestData { + SETUP.get_or_init(|| { + let path = big_repo_path(); + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + eprintln!("Initializing FilePicker for {:?}...", path); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: path, + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + ..Default::default() + }, + ) + .expect("create picker"); + + eprintln!("Waiting for scan completion..."); + shared_picker.wait_for_scan(Duration::from_secs(120)); + + eprintln!("Waiting for warmup (bigram index)..."); + loop { + let guard = shared_picker.read().expect("read lock"); + let picker = guard.as_ref().expect("picker present"); + let progress = picker.get_scan_progress(); + if progress.is_warmup_complete { + let file_count = picker.get_files().len(); + eprintln!("Ready: {} files indexed, bigram built", file_count); + break; + } + drop(guard); + std::thread::sleep(Duration::from_millis(100)); + } + + TestData { shared_picker } + }) +} + +/// Persistent picker with the bigram index disabled — every grep scans all +/// candidate files. Isolates raw scan throughput from bigram prefilter wins. +fn setup_no_index() -> &'static TestData { + SETUP_NO_INDEX.get_or_init(|| { + let path = big_repo_path(); + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + eprintln!("Initializing FilePicker (no bigram) for {:?}...", path); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: path, + enable_mmap_cache: false, + enable_content_indexing: false, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("create picker"); + + shared_picker.wait_for_scan(Duration::from_secs(120)); + let file_count = { + let guard = shared_picker.read().expect("read lock"); + guard.as_ref().expect("picker present").get_files().len() + }; + eprintln!("Ready (no bigram): {} files indexed", file_count); + + TestData { shared_picker } + }) +} + +fn plain_options() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 50, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + ..Default::default() + } +} + +fn fuzzy_options() -> GrepSearchOptions { + GrepSearchOptions { + mode: GrepMode::Fuzzy, + ..plain_options() + } +} + +/// One query per selectivity bucket: single-char, common, medium, rare, +/// multi-word, path-constrained. +const PLAIN_QUERIES: &[(&str, &str)] = &[ + ("single_char_x", "x"), + ("common_return", "return"), + ("func_mutex_lock", "mutex_lock"), + ("rare_phylink_ethtool", "phylink_ethtool"), + ("long_static_int_init", "static int __init"), + ("path_printk_c", "printk *.c"), +]; + +/// Fuzzy is expensive (>1s/iter even on warm). Keep three: exact, typo, abbrev. +const FUZZY_QUERIES: &[(&str, &str)] = &[ + ("exact_mutex_lock", "mutex_lock"), + ("typo_mutx_lock", "mutx_lock"), + ("abbrev_sched_rt", "sched_rt"), +]; + +fn bench_plain_warm(c: &mut Criterion) { + let data = setup(); + let opts = plain_options(); + + let mut group = c.benchmark_group("plain_warm"); + group.sample_size(15); + group.warm_up_time(Duration::from_secs(1)); + group.measurement_time(Duration::from_secs(3)); + + for (name, query) in PLAIN_QUERIES { + group.bench_with_input(BenchmarkId::from_parameter(name), query, |b, q| { + let guard = data.shared_picker.read().expect("read lock"); + let picker = guard.as_ref().expect("picker present"); + b.iter(|| { + let parsed = parse_grep_query(q); + black_box(picker.grep(&parsed, &opts)) + }); + }); + } + + group.finish(); +} + +fn bench_fuzzy_warm(c: &mut Criterion) { + let data = setup(); + let opts = fuzzy_options(); + + let mut group = c.benchmark_group("fuzzy_warm"); + // Fuzzy iters cost >1s; small sample + tight window keeps the suite fast. + group.sample_size(10); + group.warm_up_time(Duration::from_secs(1)); + group.measurement_time(Duration::from_secs(5)); + + for (name, query) in FUZZY_QUERIES { + group.bench_with_input(BenchmarkId::from_parameter(name), query, |b, q| { + let guard = data.shared_picker.read().expect("read lock"); + let picker = guard.as_ref().expect("picker present"); + b.iter(|| { + let parsed = parse_grep_query(q); + black_box(picker.grep(&parsed, &opts)) + }); + }); + } + + group.finish(); +} + +/// `bench_plain_warm` with the bigram index off. Side-by-side with the warm +/// group it shows the per-query bigram-prefilter contribution. +fn bench_plain_no_index(c: &mut Criterion) { + let data = setup_no_index(); + let opts = plain_options(); + + // Common + medium only; rare queries cost 10-15s/iter without bigram. + let queries: &[(&str, &str)] = &[ + ("common_return", "return"), + ("func_mutex_lock", "mutex_lock"), + ]; + + let mut group = c.benchmark_group("plain_no_index"); + group.sample_size(10); + group.warm_up_time(Duration::from_secs(1)); + group.measurement_time(Duration::from_secs(5)); + + for (name, query) in queries { + group.bench_with_input(BenchmarkId::from_parameter(name), query, |b, q| { + let guard = data.shared_picker.read().expect("read lock"); + let picker = guard.as_ref().expect("picker present"); + b.iter(|| { + let parsed = parse_grep_query(q); + black_box(picker.grep(&parsed, &opts)) + }); + }); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_plain_warm, + bench_fuzzy_warm, + bench_plain_no_index, +); + +criterion_main!(benches); diff --git a/crates/fff-nvim/benches/query_tracker_bench.rs b/crates/fff-nvim/benches/query_tracker_bench.rs new file mode 100644 index 0000000..d56e699 --- /dev/null +++ b/crates/fff-nvim/benches/query_tracker_bench.rs @@ -0,0 +1,224 @@ +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use fff::query_tracker::QueryTracker; +use rand::distributions::Alphanumeric; +use rand::prelude::*; +use std::path::PathBuf; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn generate_random_string(len: usize) -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(len) + .map(char::from) + .collect() +} + +// Test data structure for benchmarks +struct TestQueryEntry { + query: String, + project_path: PathBuf, + file_path: PathBuf, + open_count: u32, + last_opened: u64, +} + +fn generate_test_data(num_entries: usize) -> Vec { + let mut rng = thread_rng(); + let mut entries = Vec::with_capacity(num_entries); + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + // Generate some common queries that will be reused + let common_queries = vec![ + "main", + "test", + "config", + "utils", + "lib", + "mod", + "index", + "init", + "server", + "client", + "api", + "service", + "controller", + "model", + "view", + "component", + "handler", + "middleware", + "router", + "database", + "auth", + ]; + + // Generate some common project paths + let project_paths = vec![ + "/home/user/project1", + "/home/user/project2", + "/home/user/web-app", + "/home/user/cli-tool", + "/home/user/library", + ]; + + for _ in 0..num_entries { + let query = if rng.gen_bool(0.7) { + // 70% chance to use common query + common_queries.choose(&mut rng).unwrap().to_string() + } else { + // 30% chance to use random query + generate_random_string(rng.gen_range(3..15)) + }; + + let project_path = project_paths.choose(&mut rng).unwrap(); + let file_name = format!( + "{}.{}", + generate_random_string(rng.gen_range(5..20)), + if rng.gen_bool(0.5) { "rs" } else { "js" } + ); + let file_path = PathBuf::from(format!("{}/src/{}", project_path, file_name)); + + let entry = TestQueryEntry { + query: query.into(), + project_path: PathBuf::from(project_path), + file_path, + open_count: rng.gen_range(1..10), + last_opened: now - rng.gen_range(0..30 * 24 * 3600), // Random time within last 30 days + }; + + entries.push(entry); + } + + entries +} + +fn setup_tracker_with_data(entries: &[TestQueryEntry]) -> (QueryTracker, PathBuf) { + use std::time::{SystemTime, UNIX_EPOCH}; + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let temp_dir = + std::env::temp_dir().join(format!("fff_bench_{}_{}", timestamp, rand::random::())); + let mut tracker = QueryTracker::open(temp_dir.to_str().unwrap()).unwrap(); + + // Insert all test data + for entry in entries { + for _ in 0..entry.open_count { + tracker + .track_query_completion(&entry.query, &entry.project_path, &entry.file_path) + .unwrap(); + } + } + + (tracker, temp_dir) +} + +fn cleanup_tracker_dir(dir: PathBuf) { + if dir.exists() { + let _ = std::fs::remove_dir_all(dir); + } +} + +fn bench_track_query_completion(c: &mut Criterion) { + let mut group = c.benchmark_group("track_query_completion"); + + for size in &[100, 1000, 10000] { + let entries = generate_test_data(*size); + let (mut tracker, temp_dir) = setup_tracker_with_data(&entries[..*size / 2]); // Pre-populate with half + + group.bench_with_input(BenchmarkId::new("entries", size), size, |b, _| { + let mut rng = thread_rng(); + b.iter(|| { + let entry = entries.choose(&mut rng).unwrap(); + black_box( + tracker + .track_query_completion( + black_box(&entry.query), + black_box(&entry.project_path), + black_box(&entry.file_path), + ) + .unwrap(), + ); + }); + }); + + drop(tracker); + cleanup_tracker_dir(temp_dir); + } + + group.finish(); +} + +fn bench_realistic_workload(c: &mut Criterion) { + let mut group = c.benchmark_group("realistic_workload"); + + for size in &[1000, 10000] { + let entries = generate_test_data(*size); + let (mut tracker, temp_dir) = setup_tracker_with_data(&entries); + + group.bench_with_input(BenchmarkId::new("mixed_operations", size), size, |b, _| { + let mut rng = thread_rng(); + b.iter(|| { + let entry = entries.choose(&mut rng).unwrap(); + + // Simulate realistic usage: 70% lookups, 25% tracking, 5% history + match rng.gen_range(0..100) { + 0..70 => { + // Query entry lookup (most common operation) + let entry_result = black_box( + tracker + .get_last_query_entry( + black_box(&entry.query), + black_box(&entry.project_path), + 3, + ) + .unwrap(), + ); + black_box(entry_result); + } + 70..95 => { + // Track completion (when user opens file) + black_box( + tracker + .track_query_completion( + black_box(&entry.query), + black_box(&entry.project_path), + black_box(&entry.file_path), + ) + .unwrap(), + ); + } + 95..100 => { + // Get historical query (least common) + let history = black_box( + tracker + .get_historical_query(black_box(&entry.project_path), black_box(5)) + .unwrap(), + ); + black_box(history); + } + _ => unreachable!(), + } + }); + }); + + drop(tracker); + cleanup_tracker_dir(temp_dir); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_track_query_completion, + // Commented out - methods removed/changed in refactor: + // bench_get_query_boost, + // bench_cleanup_old_entries, + bench_realistic_workload +); +criterion_main!(benches); diff --git a/crates/fff-nvim/benches/scan_bench.rs b/crates/fff-nvim/benches/scan_bench.rs new file mode 100644 index 0000000..2b93518 --- /dev/null +++ b/crates/fff-nvim/benches/scan_bench.rs @@ -0,0 +1,220 @@ +//! Benchmarks capturing performance of index building and fs walking +use criterion::{Criterion, criterion_group, criterion_main}; +use fff::file_picker::{FFFMode, FilePicker}; +use fff::{FilePickerOptions, SharedFilePicker, SharedFrecency}; +use std::path::PathBuf; +use std::sync::Once; +use std::time::{Duration, Instant}; + +const WAIT_TIMEOUT: Duration = Duration::from_secs(300); + +static TRACING_INIT: Once = Once::new(); + +fn init_tracing() { + TRACING_INIT.call_once(|| { + use tracing_subscriber::EnvFilter; + use tracing_subscriber::fmt::format::FmtSpan; + + let _ = tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new("warn,fff_search=info")), + ) + .with_span_events(FmtSpan::CLOSE) + .with_target(true) + .with_writer(std::io::stderr) + .try_init(); + }); +} + +fn resolve_repo() -> Option { + if let Ok(env_path) = std::env::var("FFF_BENCH_REPO") { + let p = PathBuf::from(env_path); + if p.exists() { + return fff::path_utils::canonicalize(&p).ok(); + } + } + // Resolve relative to the workspace root (two levels up from this crate). + let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."); + let default = workspace_root.join("big-repo"); + if default.exists() { + return fff::path_utils::canonicalize(&default).ok(); + } + None +} + +fn init_picker( + path: &str, + content_indexing: bool, + warmup: bool, +) -> (SharedFilePicker, SharedFrecency) { + let sp = SharedFilePicker::default(); + let sf = SharedFrecency::default(); + FilePicker::new_with_shared_state( + sp.clone(), + sf.clone(), + FilePickerOptions { + base_path: path.to_string(), + enable_mmap_cache: warmup, + enable_content_indexing: content_indexing, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("init FilePicker"); + (sp, sf) +} + +/// Portable wait across branches (no dependency on `wait_for_indexing_complete`). +/// Polls until the scan is inactive AND the bigram index has been installed — this +/// is the end of `run_post_scan` for content-indexing builds. +fn wait_for_post_scan(sp: &SharedFilePicker, timeout: Duration) -> bool { + let deadline = Instant::now() + timeout; + loop { + let ready = sp + .read() + .ok() + .and_then(|guard| { + guard + .as_ref() + .map(|p| !p.is_scan_active() && !p.is_post_scan_active()) + }) + .unwrap_or(false); + if ready { + return true; + } + if Instant::now() >= deadline { + return false; + } + std::thread::sleep(Duration::from_millis(10)); + } +} + +/// Portable wait for walk-only builds (content_indexing=false, no bigram). +fn wait_for_scan_done(sp: &SharedFilePicker, timeout: Duration) -> bool { + let deadline = Instant::now() + timeout; + loop { + let done = sp + .read() + .ok() + .and_then(|guard| guard.as_ref().map(|p| !p.is_scan_active())) + .unwrap_or(false); + if done { + return true; + } + if Instant::now() >= deadline { + return false; + } + std::thread::sleep(Duration::from_millis(10)); + } +} + +fn cleanup(sp: SharedFilePicker) { + // Clean teardown: wait for scan + post-scan to finish, then drop. + // The PostScanUnsafeSnapshot holds Arc-shared data, so dropping the + // picker while post-scan threads run is memory-safe, but we wait + // for completion to avoid detached git-status threads from causing + // I/O contention on the next benchmark iteration. + sp.wait_for_indexing_complete(WAIT_TIMEOUT); + if let Ok(mut guard) = sp.write() + && let Some(mut picker) = guard.take() + { + picker.stop_background_monitor(); + } +} + +fn bench_full_init(c: &mut Criterion) { + init_tracing(); + let Some(repo) = resolve_repo() else { + eprintln!("skip: set FFF_BENCH_REPO or clone a repo to ./big-repo"); + return; + }; + let path = repo.to_string_lossy().to_string(); + eprintln!("post_scan_bench repo: {}", path); + + let mut group = c.benchmark_group("post_scan"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(120)); + + group.bench_function("full_init", |b| { + b.iter_custom(|iters| { + let mut total = Duration::ZERO; + for _ in 0..iters { + let start = Instant::now(); + let (sp, _sf) = init_picker(&path, true, true); + wait_for_post_scan(&sp, WAIT_TIMEOUT); + total += start.elapsed(); + cleanup(sp); + } + total + }); + }); + + group.finish(); +} + +fn bench_post_scan_only(c: &mut Criterion) { + init_tracing(); + let Some(repo) = resolve_repo() else { + return; + }; + let path = repo.to_string_lossy().to_string(); + + let mut group = c.benchmark_group("post_scan"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(120)); + + group.bench_function("post_scan_only", |b| { + b.iter_custom(|iters| { + let mut total = Duration::ZERO; + for _ in 0..iters { + let (sp, _sf) = init_picker(&path, true, true); + wait_for_scan_done(&sp, WAIT_TIMEOUT); + let start = Instant::now(); + wait_for_post_scan(&sp, WAIT_TIMEOUT); + total += start.elapsed(); + cleanup(sp); + } + total + }); + }); + + group.finish(); +} + +fn bench_walk_only(c: &mut Criterion) { + init_tracing(); + let Some(repo) = resolve_repo() else { + return; + }; + let path = repo.to_string_lossy().to_string(); + + let mut group = c.benchmark_group("post_scan"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(60)); + + group.bench_function("walk_only", |b| { + b.iter_custom(|iters| { + let mut total = Duration::ZERO; + for _ in 0..iters { + let start = Instant::now(); + let (sp, _sf) = init_picker(&path, false, false); + wait_for_scan_done(&sp, WAIT_TIMEOUT); + total += start.elapsed(); + cleanup(sp); + } + total + }); + }); + + group.finish(); +} + +criterion_group!( + benches, + bench_full_init, + bench_post_scan_only, + bench_walk_only +); +criterion_main!(benches); diff --git a/crates/fff-nvim/src/bin/bench_grep_query.rs b/crates/fff-nvim/src/bin/bench_grep_query.rs new file mode 100644 index 0000000..063d081 --- /dev/null +++ b/crates/fff-nvim/src/bin/bench_grep_query.rs @@ -0,0 +1,138 @@ +/// Single-query grep benchmark with bigram index profiling. +/// +/// Usage: +/// cargo build --release --bin bench_grep_query +/// ./target/release/bench_grep_query --path ~/dev/chromium --query "MAX_FILE_SIZE" --iters 3 +/// ./target/release/bench_grep_query --path ~/dev/chromium --query "TODO" +use fff::file_picker::FilePicker; +use fff::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use std::time::Instant; + +fn fmt_dur(us: u128) -> String { + if us > 1_000_000 { + format!("{:.2}s", us as f64 / 1_000_000.0) + } else if us > 1000 { + format!("{:.2}ms", us as f64 / 1000.0) + } else { + format!("{}us", us) + } +} + +fn run_grep(picker: &FilePicker, query: &str, iters: usize) { + let options = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: usize::MAX, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + + let parsed = parse_grep_query(query); + let mut times_us = Vec::with_capacity(iters); + + for i in 0..iters { + let t = Instant::now(); + let result = picker.grep(&parsed, &options); + let us = t.elapsed().as_micros(); + times_us.push(us); + + eprintln!( + " iter {}: {} ({} matches in {} files, {}/{} searched)", + i + 1, + fmt_dur(us), + result.matches.len(), + result.files_with_matches, + result.total_files_searched, + result.total_files, + ); + } + + if times_us.len() > 1 { + times_us.sort(); + let sum: u128 = times_us.iter().sum(); + let mean = sum / times_us.len() as u128; + let median = times_us[times_us.len() / 2]; + let min = times_us[0]; + let max = times_us[times_us.len() - 1]; + eprintln!( + " mean: {} median: {} min: {} max: {}", + fmt_dur(mean), + fmt_dur(median), + fmt_dur(min), + fmt_dur(max) + ); + } +} + +fn main() { + let args: Vec = std::env::args().collect(); + + let path = args + .iter() + .position(|a| a == "--path") + .and_then(|i| args.get(i + 1)) + .map(|s| s.as_str()) + .unwrap_or("."); + + let query = args + .iter() + .position(|a| a == "--query") + .and_then(|i| args.get(i + 1)) + .map(|s| s.as_str()) + .unwrap_or("TODO"); + + let iters: usize = args + .iter() + .position(|a| a == "--iters") + .and_then(|i| args.get(i + 1)) + .and_then(|s| s.parse().ok()) + .unwrap_or(5); + + let repo = std::path::PathBuf::from(path); + if !repo.exists() { + eprintln!("Path not found: {}", path); + eprintln!("Usage: bench_grep_query --path --query [--iters N]"); + std::process::exit(1); + } + + let canonical = fff::path_utils::canonicalize(&repo).expect("Failed to canonicalize path"); + eprintln!("=== bench_grep_query ==="); + eprintln!("Path: {}", canonical.display()); + eprintln!("Query: \"{}\"", query); + eprintln!("Iters: {}", iters); + eprintln!(); + + // ── 1. Scan files ────────────────────────────────────────────────── + eprint!("[1/2] Scanning files... "); + let t = Instant::now(); + let mut picker = FilePicker::new(fff::FilePickerOptions { + base_path: canonical.to_string_lossy().to_string(), + enable_mmap_cache: false, + mode: fff::FFFMode::Neovim, + ..Default::default() + }) + .expect("Failed to create FilePicker"); + picker.collect_files().expect("Failed to collect files"); + let files = picker.get_files(); + let non_binary = files.iter().filter(|f| !f.is_binary()).count(); + eprintln!( + "{} files in {:.2}s ({} non-binary)", + files.len(), + t.elapsed().as_secs_f64(), + non_binary, + ); + + // ── 2. Grep ─────────────────────────────────────────────────────── + eprintln!( + "\n[2/2] Running grep \"{}\" x {} iterations\n", + query, iters + ); + run_grep(&picker, query, iters); +} diff --git a/crates/fff-nvim/src/bin/bench_lmdb_parallel.rs b/crates/fff-nvim/src/bin/bench_lmdb_parallel.rs new file mode 100644 index 0000000..5f283d2 --- /dev/null +++ b/crates/fff-nvim/src/bin/bench_lmdb_parallel.rs @@ -0,0 +1,150 @@ +/// Parallel LMDB contention bench. +/// +/// Spawns N child processes that all open the same frecency + query-tracker +/// dbs and hammer them. Used to measure the cost (or absence of cost) of +/// dropping the `MDB_NOLOCK | NO_SYNC | NO_META_SYNC` env flags. +/// +/// Usage: +/// cargo build --release --bin bench_lmdb_parallel +/// ./target/release/bench_lmdb_parallel --procs 4 --iters 5000 +/// +/// Env var FFF_BENCH_ROLE=worker turns the binary into a worker that talks +/// to a db path passed via FFF_BENCH_DB. +use fff::frecency::FrecencyTracker; +use fff::query_tracker::QueryTracker; +use std::env; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::time::Instant; + +fn parse_args() -> (usize, usize, Option) { + let mut procs = 4usize; + let mut iters = 2000usize; + let mut db_path: Option = None; + let mut args = env::args().skip(1); + while let Some(a) = args.next() { + match a.as_str() { + "--procs" => procs = args.next().and_then(|s| s.parse().ok()).unwrap_or(procs), + "--iters" => iters = args.next().and_then(|s| s.parse().ok()).unwrap_or(iters), + "--db" => db_path = args.next(), + _ => {} + } + } + (procs, iters, db_path) +} + +fn worker_main(db: &Path, iters: usize, worker_id: u32) { + let frecency_path = db.join("frecency"); + let history_path = db.join("history"); + + let frecency = FrecencyTracker::open(&frecency_path).expect("frecency open"); + let mut query_tracker = QueryTracker::open(&history_path).expect("query tracker open"); + + let project = PathBuf::from("/bench/project"); + let started = Instant::now(); + + for i in 0..iters { + let file = PathBuf::from(format!( + "/bench/project/src/file_{}_{}.rs", + worker_id, + i % 256 + )); + // Frecency write path — same call as track_access on BufEnter. + frecency.track_access(&file).expect("frecency track_access"); + + // Query tracker write path — same as track_query_completion on file open. + let query = format!("q{}", i % 32); + query_tracker + .track_query_completion(&query, &project, &file) + .expect("query tracker track_query_completion"); + + // A read every few iterations. + if i % 8 == 0 { + let _ = frecency.seconds_since_last_access(&file).ok(); + let _ = query_tracker + .get_historical_query(&project, 0) + .ok() + .flatten(); + } + } + + let elapsed = started.elapsed(); + eprintln!( + "worker {} done: {} iters in {:?} ({:.0} ops/s)", + worker_id, + iters, + elapsed, + iters as f64 * 3.0 / elapsed.as_secs_f64() + ); +} + +fn driver_main(procs: usize, iters: usize, db_override: Option) { + let db = db_override.map(PathBuf::from).unwrap_or_else(|| { + std::env::temp_dir().join(format!("fff_bench_lmdb_{}", std::process::id())) + }); + let _ = std::fs::remove_dir_all(&db); + std::fs::create_dir_all(&db).expect("create db dir"); + + let exe = env::current_exe().expect("current_exe"); + + eprintln!( + "driver: launching {} workers, {} iters each, db={}", + procs, + iters, + db.display() + ); + + let started = Instant::now(); + let mut children = Vec::with_capacity(procs); + for worker_id in 0..procs { + let child = Command::new(&exe) + .env("FFF_BENCH_ROLE", "worker") + .env("FFF_BENCH_DB", &db) + .env("FFF_BENCH_ITERS", iters.to_string()) + .env("FFF_BENCH_WORKER_ID", worker_id.to_string()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .expect("spawn worker"); + children.push(child); + } + + let mut failures = 0u32; + for mut child in children { + let status = child.wait().expect("wait"); + if !status.success() { + failures += 1; + } + } + + let elapsed = started.elapsed(); + let total_ops = procs as f64 * iters as f64 * 3.0; + eprintln!( + "driver: all workers done in {:?}, ~{:.0} ops/s aggregate, failures={}", + elapsed, + total_ops / elapsed.as_secs_f64(), + failures + ); + + let _ = std::fs::remove_dir_all(&db); +} + +fn main() { + // Worker branch: spawned by driver to contend on the same db. + if env::var("FFF_BENCH_ROLE").as_deref() == Ok("worker") { + let db = env::var("FFF_BENCH_DB").expect("FFF_BENCH_DB"); + let iters: usize = env::var("FFF_BENCH_ITERS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(1000); + let worker_id: u32 = env::var("FFF_BENCH_WORKER_ID") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + worker_main(Path::new(&db), iters, worker_id); + return; + } + + let (procs, iters, db) = parse_args(); + driver_main(procs, iters, db); +} diff --git a/crates/fff-nvim/src/bin/bench_search_only.rs b/crates/fff-nvim/src/bin/bench_search_only.rs new file mode 100644 index 0000000..3dc7623 --- /dev/null +++ b/crates/fff-nvim/src/bin/bench_search_only.rs @@ -0,0 +1,117 @@ +/// Simple search profiler that directly uses scan_filesystem without background thread overhead +use fff::file_picker::FilePicker; +use fff::{FuzzySearchOptions, PaginationArgs, QueryParser}; +use std::time::Instant; + +fn load_picker(path: &std::path::Path) -> FilePicker { + let mut picker = FilePicker::new(fff::FilePickerOptions { + base_path: path.to_string_lossy().to_string(), + enable_mmap_cache: false, + mode: fff::FFFMode::Neovim, + ..Default::default() + }) + .expect("Failed to create FilePicker"); + picker.collect_files().expect("Failed to collect files"); + picker +} + +fn main() { + let big_repo_path = std::path::PathBuf::from("./big-repo"); + + if !big_repo_path.exists() { + eprintln!( + "./big-repo directory does not exist. Run git clone https://github.com/torvalds/linux.git big-repo" + ); + return; + } + + let canonical_path = + fff::path_utils::canonicalize(&big_repo_path).expect("Failed to canonicalize path"); + + eprintln!("Loading files from: {:?}", canonical_path); + + let start = Instant::now(); + let picker = load_picker(&canonical_path); + eprintln!( + "✓ Loaded {} files in {:.2}s\n", + picker.get_files().len(), + start.elapsed().as_secs_f64() + ); + + // Test queries + let test_queries = vec![ + ("short_common", "mod", 500), + ("medium_specific", "controller", 200), + ("long_rare", "user_authentication", 100), + ("typo_resistant", "contrlr", 200), + ("path_like", "src/lib", 150), + ("two_char", "st", 300), + ("partial_word", "test", 200), + ("deep_path", "drivers/net", 100), + ("extension", ".rs", 200), + ]; + + eprintln!("Running search profiler..."); + eprintln!("Query | Iterations | Total Time | Avg Time | Matches"); + eprintln!("----------------------|------------|------------|-----------|--------"); + + let global_start = Instant::now(); + let mut total_iterations = 0; + + for (name, query, iterations) in test_queries { + let start = Instant::now(); + let mut match_count = 0; + + for _ in 0..iterations { + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let results = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }, + ); + match_count += results.total_matched; + } + + let elapsed = start.elapsed(); + let avg_time = elapsed / iterations as u32; + + eprintln!( + "{:<21} | {:>10} | {:>9.2}s | {:>7}µs | {}", + name, + iterations, + elapsed.as_secs_f64(), + avg_time.as_micros(), + match_count / iterations + ); + + total_iterations += iterations; + } + + let total_time = global_start.elapsed(); + + eprintln!("\n=== Summary ==="); + eprintln!("Total searches: {}", total_iterations); + eprintln!("Total time: {:.2}s", total_time.as_secs_f64()); + eprintln!( + "Average per search: {}µs", + (total_time.as_micros() as usize) / total_iterations + ); + eprintln!( + "Searches per sec: {:.0}", + total_iterations as f64 / total_time.as_secs_f64() + ); + eprintln!( + "\nYou can now run: perf record -g --call-graph dwarf -F 999 ./target/release/search_only" + ); +} diff --git a/crates/fff-nvim/src/bin/fuzzy_grep_test.rs b/crates/fff-nvim/src/bin/fuzzy_grep_test.rs new file mode 100644 index 0000000..e3bceac --- /dev/null +++ b/crates/fff-nvim/src/bin/fuzzy_grep_test.rs @@ -0,0 +1,183 @@ +/// Fuzzy grep quality test against ~/dev/lightsource +/// +/// Runs queries through the fuzzy grep pipeline and prints results +/// so we can verify match quality. +/// +/// Usage: +/// cargo run --release --bin fuzzy_grep_test # runs default test queries +/// cargo run --release --bin fuzzy_grep_test -- "query" # runs a single user query +use fff::file_picker::FilePicker; +use fff::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use std::path::Path; +use std::time::Instant; + +fn create_picker(path: &Path) -> FilePicker { + let mut picker = FilePicker::new(fff::FilePickerOptions { + base_path: path.to_string_lossy().to_string(), + enable_mmap_cache: false, + mode: fff::FFFMode::Neovim, + ..Default::default() + }) + .expect("Failed to create FilePicker"); + picker.collect_files().expect("Failed to collect files"); + picker +} + +fn run_fuzzy_query(picker: &FilePicker, query: &str, label: &str) { + let options = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 100, + mode: GrepMode::Fuzzy, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + + let parsed = parse_grep_query(query); + let start = Instant::now(); + let result = picker.grep(&parsed, &options); + let elapsed = start.elapsed(); + + eprintln!("══════════════════════════════════════════════════════════════"); + eprintln!(" Query: \"{}\" ({})", query, label); + eprintln!( + " Results: {} matches in {} files ({:.2}ms)", + result.matches.len(), + result.total_files_searched, + elapsed.as_secs_f64() * 1000.0, + ); + eprintln!("══════════════════════════════════════════════════════════════"); + + if result.matches.is_empty() { + eprintln!(" (no matches)\n"); + return; + } + + // Group by file for readability + let mut current_file_idx = usize::MAX; + for (i, m) in result.matches.iter().enumerate() { + if m.file_index != current_file_idx { + current_file_idx = m.file_index; + let file = &result.files[m.file_index]; + eprintln!("\n ┌─ {}", file.relative_path(picker)); + } + + // Truncate long lines for display + let display_line = if m.line_content.len() > 100 { + format!("{}...", &m.line_content[..100]) + } else { + m.line_content.clone() + }; + + let score_str = m + .fuzzy_score + .map(|s| format!("score={}", s)) + .unwrap_or_else(|| "no-score".to_string()); + + let offsets_str = if m.match_byte_offsets.is_empty() { + String::new() + } else { + // Show what text fragments are highlighted + let fragments: Vec = m + .match_byte_offsets + .iter() + .filter_map(|&(s, e)| { + m.line_content + .get(s as usize..e as usize) + .map(|frag| format!("\"{}\"", frag)) + }) + .collect(); + format!(" hl=[{}]", fragments.join(",")) + }; + + eprintln!( + " │ L{:<5} [{}{}] {}", + m.line_number, + score_str, + offsets_str, + display_line.trim(), + ); + + // Cap output at 50 lines + if i >= 49 { + let remaining = result.matches.len() - 50; + if remaining > 0 { + eprintln!(" │ ... and {} more matches", remaining); + } + break; + } + } + eprintln!(); +} + +fn main() { + let args: Vec = std::env::args().skip(1).collect(); + + let (repo_path, queries) = if let Some(idx) = args.iter().position(|a| a == "--path") { + let path = args + .get(idx + 1) + .map(std::path::PathBuf::from) + .unwrap_or_else(|| { + eprintln!("--path requires an argument"); + std::process::exit(1); + }); + let queries: Vec = args + .iter() + .enumerate() + .filter(|(i, _)| *i != idx && *i != idx + 1) + .map(|(_, s)| s.clone()) + .collect(); + (path, queries) + } else { + let path = std::path::PathBuf::from( + std::env::var("HOME").unwrap_or_else(|_| "/Users/neogoose".to_string()), + ) + .join("dev/lightsource"); + (path, args) + }; + + if !repo_path.exists() { + eprintln!("Repository not found at: {:?}", repo_path); + std::process::exit(1); + } + + let canonical = fff::path_utils::canonicalize(&repo_path).expect("Failed to canonicalize path"); + eprintln!("=== Fuzzy Grep Quality Test ==="); + eprintln!("Repository: {:?}\n", canonical); + + eprintln!("Loading files..."); + let load_start = Instant::now(); + let picker = create_picker(&canonical); + let files = picker.get_files(); + let non_binary = files.iter().filter(|f| !f.is_binary()).count(); + eprintln!( + "Loaded {} files ({} non-binary) in {:.2}s\n", + files.len(), + non_binary, + load_start.elapsed().as_secs_f64() + ); + + if queries.is_empty() { + // Run default test queries + run_fuzzy_query(&picker, "shcema", "transposition of 'schema'"); + run_fuzzy_query(&picker, "SortedMap", "should match SortedArrayMap"); + run_fuzzy_query( + &picker, + "struct SortedMap", + "should NOT match SourcingProjectMetadataParts", + ); + } else { + // Run user-provided queries + for query in &queries { + run_fuzzy_query(&picker, query, "user query"); + } + } + + eprintln!("=== Done ==="); +} diff --git a/crates/fff-nvim/src/bin/grep_profiler.rs b/crates/fff-nvim/src/bin/grep_profiler.rs new file mode 100644 index 0000000..4c0386b --- /dev/null +++ b/crates/fff-nvim/src/bin/grep_profiler.rs @@ -0,0 +1,453 @@ +/// Live grep benchmark profiler for fff.nvim +/// +/// Benchmarks the full grep pipeline against a large repository (Linux kernel). +/// Measures cold-cache, warm-cache, and incremental typing latencies to simulate +/// real user interaction patterns. +/// +/// Uses FilePicker::collect_files for synchronous scanning (no background thread). +/// +/// Usage: +/// cargo build --release --bin grep_profiler +/// ./target/release/grep_profiler [--path /path/to/repo] +use fff::file_picker::FilePicker; +use fff::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use std::time::{Duration, Instant}; + +fn create_picker(path: &std::path::Path) -> FilePicker { + let mut picker = FilePicker::new(fff::FilePickerOptions { + base_path: path.to_string_lossy().to_string(), + enable_mmap_cache: false, + mode: fff::FFFMode::Neovim, + ..Default::default() + }) + .expect("Failed to create FilePicker"); + picker.collect_files().expect("Failed to collect files"); + picker +} + +struct BenchStats { + times: Vec, +} + +impl BenchStats { + fn new() -> Self { + Self { times: Vec::new() } + } + + fn push(&mut self, d: Duration) { + self.times.push(d); + } + + fn mean(&self) -> Duration { + let total: Duration = self.times.iter().sum(); + total / self.times.len() as u32 + } + + fn median(&self) -> Duration { + let mut sorted = self.times.clone(); + sorted.sort(); + sorted[sorted.len() / 2] + } + + fn p95(&self) -> Duration { + let mut sorted = self.times.clone(); + sorted.sort(); + let idx = ((sorted.len() as f64) * 0.95) as usize; + sorted[idx.min(sorted.len() - 1)] + } + + fn p99(&self) -> Duration { + let mut sorted = self.times.clone(); + sorted.sort(); + let idx = ((sorted.len() as f64) * 0.99) as usize; + sorted[idx.min(sorted.len() - 1)] + } + + fn min(&self) -> Duration { + *self.times.iter().min().unwrap() + } + + fn max(&self) -> Duration { + *self.times.iter().max().unwrap() + } +} + +struct GrepBench<'a> { + picker: &'a FilePicker, + options: GrepSearchOptions, +} + +impl<'a> GrepBench<'a> { + fn new(picker: &'a FilePicker) -> Self { + Self::with_mode(picker, GrepMode::PlainText) + } + + fn with_mode(picker: &'a FilePicker, mode: GrepMode) -> Self { + Self { + picker, + options: GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 50, + mode, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }, + } + } + + /// Run a single grep search, return (duration, match_count, files_searched) + fn run_once(&self, query: &str) -> (Duration, usize, usize) { + let parsed = parse_grep_query(query); + let start = Instant::now(); + let result = self.picker.grep(&parsed, &self.options); + let elapsed = start.elapsed(); + (elapsed, result.matches.len(), result.total_files_searched) + } + + /// Benchmark a query with multiple iterations + fn bench_query(&self, query: &str, iterations: usize) -> (BenchStats, usize, usize) { + let mut stats = BenchStats::new(); + let mut last_matches = 0; + let mut last_files_searched = 0; + + for _ in 0..iterations { + let (elapsed, matches, files_searched) = self.run_once(query); + stats.push(elapsed); + last_matches = matches; + last_files_searched = files_searched; + } + + (stats, last_matches, last_files_searched) + } +} + +fn fmt_dur(d: Duration) -> String { + let us = d.as_micros(); + if us > 1_000_000 { + format!("{:.2}s", d.as_secs_f64()) + } else if us > 1000 { + format!("{:.2}ms", us as f64 / 1000.0) + } else { + format!("{}us", us) + } +} + +fn print_row(name: &str, stats: &BenchStats, matches: usize, files_searched: usize, iters: usize) { + eprintln!( + " {:<24} | {:>8} | {:>8} | {:>8} | {:>8} | {:>8} | {:>8} | {:>6} | {:>6} | {:>4}", + name, + fmt_dur(stats.mean()), + fmt_dur(stats.median()), + fmt_dur(stats.p95()), + fmt_dur(stats.p99()), + fmt_dur(stats.min()), + fmt_dur(stats.max()), + matches, + files_searched, + iters, + ); +} + +fn print_header() { + eprintln!( + " {:<24} | {:>8} | {:>8} | {:>8} | {:>8} | {:>8} | {:>8} | {:>6} | {:>6} | {:>4}", + "Name", "Mean", "Median", "P95", "P99", "Min", "Max", "Match", "Files", "Iter" + ); + eprintln!( + " {:-<24}-+-{:-<8}-+-{:-<8}-+-{:-<8}-+-{:-<8}-+-{:-<8}-+-{:-<8}-+-{:-<6}-+-{:-<6}-+-{:-<4}", + "", "", "", "", "", "", "", "", "", "" + ); +} + +fn main() { + // Parse args + let args: Vec = std::env::args().collect(); + let repo_path = if let Some(idx) = args.iter().position(|a| a == "--path") { + args.get(idx + 1) + .map(|s| s.as_str()) + .unwrap_or("./big-repo") + } else { + "./big-repo" + }; + + let repo = std::path::PathBuf::from(repo_path); + if !repo.exists() { + eprintln!("Repository not found at: {}", repo_path); + eprintln!("Usage: grep_profiler [--path /path/to/large/repo]"); + std::process::exit(1); + } + + let canonical = fff::path_utils::canonicalize(&repo).expect("Failed to canonicalize path"); + eprintln!("=== FFF Live Grep Profiler ==="); + eprintln!("Repository: {:?}", canonical); + + // Direct file loading (no background thread) + eprintln!("\n[1/7] Loading files..."); + let load_start = Instant::now(); + let picker = create_picker(&canonical); + let load_time = load_start.elapsed(); + let files = picker.get_files(); + let non_binary = files.iter().filter(|f| !f.is_binary()).count(); + let large_files = files.iter().filter(|f| f.size > 10 * 1024 * 1024).count(); + eprintln!( + " Loaded {} files in {:.2}s ({} non-binary, {} >10MB skipped)\n", + files.len(), + load_time.as_secs_f64(), + non_binary, + large_files, + ); + + let bench = GrepBench::new(&picker); + + eprintln!("[2/7] Cold cache benchmarks (first search, mmap not yet loaded)"); + eprintln!(" Each query runs once with fresh FileItem mmaps.\n"); + print_header(); + + let cold_queries: Vec<(&str, &str)> = vec![ + ("cold_common_2char", "if"), + ("cold_common_word", "return"), + ("cold_specific_func", "mutex_lock"), + ("cold_struct_name", "inode_operations"), + ("cold_define", "MODULE_LICENSE"), + ("cold_rare_string", "phylink_ethtool"), + ("cold_path_filter", "printk *.c"), + ("cold_long_query", "static int __init"), + ]; + + for (name, query) in &cold_queries { + // Re-load files to get fresh FileItems with no cached mmaps + let fresh_picker = create_picker(&canonical); + let fresh_bench = GrepBench::new(&fresh_picker); + let (stats, matches, files_searched) = fresh_bench.bench_query(query, 1); + print_row(name, &stats, matches, files_searched, 1); + } + + eprintln!("\n[3/7] Warm cache benchmarks (plain text, mmap cache populated)"); + eprintln!(" Running 3 warmup iterations, then measuring.\n"); + print_header(); + + let warm_queries: Vec<(&str, &str, usize)> = vec![ + ("warm_2char", "if", 10), + ("warm_common_word", "return", 10), + ("warm_function_call", "mutex_lock", 15), + ("warm_struct_name", "inode_operations", 15), + ("warm_define", "MODULE_LICENSE", 15), + ("warm_rare_string", "phylink_ethtool", 20), + ("warm_include", "#include", 10), + ("warm_comment", "TODO", 15), + ("warm_type_decl", "struct file", 15), + ("warm_error_path", "err = -EINVAL", 15), + ("warm_long_pattern", "static int __init", 15), + ("warm_very_common", "int", 10), + ("warm_single_char", "x", 10), + ("warm_path_constraint", "printk *.c", 15), + ("warm_dir_constraint", "mutex /kernel/", 15), + ]; + + // Warmup pass - populate mmap cache + for (_, query, _) in &warm_queries { + for _ in 0..3 { + bench.run_once(query); + } + } + + for (name, query, iters) in &warm_queries { + let (stats, matches, files_searched) = bench.bench_query(query, *iters); + print_row(name, &stats, matches, files_searched, *iters); + } + + // Bigram-related benchmarks are omitted: the bigram index is built + // asynchronously by the picker and is exercised through picker.grep(). + + // ── Fuzzy grep benchmarks ───────────────────────────────────────────── + eprintln!("\n[4/7] Fuzzy grep warm benchmarks"); + eprintln!(" Running 3 warmup iterations, then measuring.\n"); + print_header(); + + let fuzzy_bench = GrepBench::with_mode(&picker, GrepMode::Fuzzy); + + let fuzzy_queries: Vec<(&str, &str, usize)> = vec![ + ("fuzzy_exact", "mutex_lock", 15), + ("fuzzy_typo", "mutx_lock", 15), + ("fuzzy_camel", "InodeOps", 15), + ("fuzzy_abbrev", "sched_rt", 15), + ("fuzzy_short", "kfr", 15), + ("fuzzy_common", "return", 10), + ("fuzzy_define", "MODULE_LICENSE", 15), + ("fuzzy_struct", "file_operations", 15), + ("fuzzy_long", "static_int_init", 15), + ("fuzzy_path", "printk *.c", 15), + ]; + + // Warmup + for (_, query, _) in &fuzzy_queries { + for _ in 0..3 { + fuzzy_bench.run_once(query); + } + } + + for (name, query, iters) in &fuzzy_queries { + let (stats, matches, files_searched) = fuzzy_bench.bench_query(query, *iters); + print_row(name, &stats, matches, files_searched, *iters); + } + + // ── Fuzzy incremental typing ──────────────────────────────────────── + eprintln!("\n[5/7] Fuzzy incremental typing simulation"); + eprintln!(" Simulates user typing character by character (fuzzy mode).\n"); + + let fuzzy_typing_sequences: Vec<(&str, Vec<&str>)> = vec![ + ( + "mutex_lock", + vec![ + "m", + "mu", + "mut", + "mute", + "mutex", + "mutex_", + "mutex_l", + "mutex_lo", + "mutex_loc", + "mutex_lock", + ], + ), + ("printk", vec!["p", "pr", "pri", "prin", "print", "printk"]), + ("kfree", vec!["k", "kf", "kfr", "kfre", "kfree"]), + ]; + + for (name, sequence) in &fuzzy_typing_sequences { + eprintln!(" Typing '{}' ({} keystrokes):", name, sequence.len()); + eprintln!( + " {:>16} | {:>8} | {:>6} | {:>6}", + "Query", "Latency", "Match", "Files" + ); + eprintln!(" {:-<16}-+-{:-<8}-+-{:-<6}-+-{:-<6}", "", "", "", ""); + + for prefix in sequence { + let (elapsed, matches, files_searched) = fuzzy_bench.run_once(prefix); + eprintln!( + " {:>16} | {:>8} | {:>6} | {:>6}", + format!("\"{}\"", prefix), + fmt_dur(elapsed), + matches, + files_searched, + ); + } + eprintln!(); + } + + eprintln!("[6/7] Incremental typing simulation (plain text)"); + eprintln!(" Simulates user typing character by character.\n"); + + let bench = GrepBench::new(&picker); + let typing_sequences: Vec<(&str, Vec<&str>)> = vec![ + ( + "mutex_lock", + vec![ + "m", + "mu", + "mut", + "mute", + "mutex", + "mutex_", + "mutex_l", + "mutex_lo", + "mutex_loc", + "mutex_lock", + ], + ), + ("printk", vec!["p", "pr", "pri", "prin", "print", "printk"]), + ("inode", vec!["i", "in", "ino", "inod", "inode"]), + ("kfree", vec!["k", "kf", "kfr", "kfre", "kfree"]), + ]; + + for (name, sequence) in &typing_sequences { + eprintln!(" Typing '{}' ({} keystrokes):", name, sequence.len()); + eprintln!( + " {:>16} | {:>8} | {:>6} | {:>6}", + "Query", "Latency", "Match", "Files" + ); + eprintln!(" {:-<16}-+-{:-<8}-+-{:-<6}-+-{:-<6}", "", "", "", ""); + + for prefix in sequence { + let (elapsed, matches, files_searched) = bench.run_once(prefix); + eprintln!( + " {:>16} | {:>8} | {:>6} | {:>6}", + format!("\"{}\"", prefix), + fmt_dur(elapsed), + matches, + files_searched, + ); + } + eprintln!(); + } + + eprintln!("[7/7] Pagination benchmark"); + eprintln!(" Testing page_offset performance for common query.\n"); + + let pagination_query = "return"; + eprintln!(" Query: \"{}\"", pagination_query); + eprintln!( + " {:>6} | {:>12} | {:>8} | {:>6} | {:>12}", + "Page", "File offset", "Latency", "Matches", "Next offset" + ); + eprintln!( + " {:-<6}-+-{:-<12}-+-{:-<8}-+-{:-<6}-+-{:-<12}", + "", "", "", "", "" + ); + + let mut file_offset = 0usize; + for page in 0..10 { + let parsed = parse_grep_query(pagination_query); + let opts = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset, + page_limit: 50, + mode: Default::default(), + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + let start = Instant::now(); + let result = picker.grep(&parsed, &opts); + + let elapsed = start.elapsed(); + eprintln!( + " {:>6} | {:>12} | {:>8} | {:>6} | {:>12}", + page, + file_offset, + fmt_dur(elapsed), + result.matches.len(), + result.next_file_offset, + ); + + if result.next_file_offset == 0 || result.matches.is_empty() { + eprintln!(" (no more results)"); + break; + } + file_offset = result.next_file_offset; + } + + eprintln!("\n=== Summary ==="); + eprintln!(" Total indexed files: {}", files.len()); + eprintln!(" Non-binary files: {}", non_binary); + eprintln!(" Files > 10MB (skipped): {}", large_files); + + std::thread::sleep(Duration::from_millis(100)); + + eprintln!("\nDone. For perf profiling:"); + eprintln!(" perf record -g --call-graph dwarf -F 999 ./target/release/grep_profiler"); + eprintln!(" perf report --no-children"); +} diff --git a/crates/fff-nvim/src/bin/grep_vs_rg.rs b/crates/fff-nvim/src/bin/grep_vs_rg.rs new file mode 100644 index 0000000..f202fc1 --- /dev/null +++ b/crates/fff-nvim/src/bin/grep_vs_rg.rs @@ -0,0 +1,443 @@ +use fff::file_picker::FilePicker; +/// FFF vs ripgrep comparison benchmark +/// +/// Demonstrates why a persistent in-process search engine (fff) is fundamentally +/// faster than shelling out to ripgrep on every keystroke (telescope/fzf-lua). +/// +/// Each query is run N iterations to show the real-world advantage: +/// - fff: pre-indexed files + cached mmaps = near-zero overhead per search +/// - rg: fork/exec + directory traversal + gitignore parsing + file opens per invocation +/// +/// Sections: +/// 1. Raw engine speed — fff count-only vs rg --count-matches (N iterations) +/// 2. Full results — fff collect-all vs rg full line output (N iterations) +/// 3. First-page — fff paginated (50 results) vs rg telescope-style +/// (spawn, stream 50 lines, kill) — the real UI scenario (N iterations) +/// +/// The rg commands use telescope's default vimgrep_arguments: +/// rg --color=never --no-heading --with-filename --line-number --column --smart-case +/// +/// Usage: +/// cargo build --release --bin grep_vs_rg +/// ./target/release/grep_vs_rg [--path /path/to/repo] [--iters 5] +use fff::grep::{GrepSearchOptions, parse_grep_query}; +use std::path::Path; +use std::process::Command; +use std::time::{Duration, Instant}; + +/// Number of times each query is repeated (overridable with --iters). +const DEFAULT_ITERS: usize = 5; + +fn create_picker(path: &Path) -> FilePicker { + let mut picker = FilePicker::new(fff::FilePickerOptions { + base_path: path.to_string_lossy().to_string(), + enable_mmap_cache: false, + mode: fff::FFFMode::Neovim, + ..Default::default() + }) + .expect("Failed to create FilePicker"); + picker.collect_files().expect("Failed to collect files"); + picker +} + +/// Telescope's default vimgrep_arguments applied to any rg command. +/// Also limits rg's thread count to match rayon's pool (fair comparison). +fn apply_telescope_args(cmd: &mut Command, threads: usize) { + cmd.arg("--color=never") + .arg("--no-heading") + .arg("--with-filename") + .arg("--line-number") + .arg("--column") + .arg("--smart-case") + .arg("--fixed-strings") + .arg("--max-filesize") + .arg("10M") + .arg("--threads") + .arg(threads.to_string()); +} + +/// Run ripgrep counting matches via --count-matches. +fn run_rg_count( + repo_path: &Path, + pattern: &str, + case_insensitive: bool, + threads: usize, +) -> (usize, Duration) { + let start = Instant::now(); + let mut cmd = Command::new("rg"); + cmd.arg("--count-matches").arg("--no-filename"); + apply_telescope_args(&mut cmd, threads); + if case_insensitive { + cmd.arg("--ignore-case"); + } + cmd.arg(pattern).current_dir(repo_path); + + let output = cmd.output().expect("Failed to run rg"); + let elapsed = start.elapsed(); + let stdout = String::from_utf8_lossy(&output.stdout); + let count: usize = stdout + .lines() + .filter_map(|l| l.trim().parse::().ok()) + .sum(); + (count, elapsed) +} + +/// Run ripgrep collecting full line output. +fn run_rg_lines( + repo_path: &Path, + pattern: &str, + case_insensitive: bool, + threads: usize, +) -> (usize, Duration) { + let start = Instant::now(); + let mut cmd = Command::new("rg"); + apply_telescope_args(&mut cmd, threads); + if case_insensitive { + cmd.arg("--ignore-case"); + } + cmd.arg(pattern).current_dir(repo_path); + + let output = cmd.output().expect("Failed to run rg"); + let elapsed = start.elapsed(); + let count = bytecount(&output.stdout, b'\n'); + (count, elapsed) +} + +/// Run ripgrep the way telescope/fzf-lua actually do it: spawn rg as a +/// streaming subprocess, read stdout line-by-line, and kill the process +/// after `limit` lines. This is the realistic "first page" scenario. +fn run_rg_page( + repo_path: &Path, + pattern: &str, + case_insensitive: bool, + limit: usize, + threads: usize, +) -> (usize, Duration) { + use std::io::{BufRead, BufReader}; + use std::process::Stdio; + + let start = Instant::now(); + let mut rg_cmd = Command::new("rg"); + apply_telescope_args(&mut rg_cmd, threads); + if case_insensitive { + rg_cmd.arg("--ignore-case"); + } + rg_cmd + .arg(pattern) + .current_dir(repo_path) + .stdout(Stdio::piped()) + .stderr(Stdio::null()); + + let mut child = rg_cmd.spawn().expect("Failed to spawn rg"); + let stdout = child.stdout.take().expect("Failed to get rg stdout"); + let reader = BufReader::new(stdout); + + let mut count = 0; + for _line in reader.lines() { + if _line.is_err() { + break; + } + count += 1; + if count >= limit { + break; + } + } + + // Kill rg immediately --- this is what telescope does when the picker + // closes or the query changes (plenary.job:shutdown). + let _ = child.kill(); + let _ = child.wait(); + let elapsed = start.elapsed(); + + (count, elapsed) +} + +fn bytecount(bytes: &[u8], needle: u8) -> usize { + bytes.iter().filter(|&&b| b == needle).count() +} + +/// fff full: collects all GrepMatch structs (what the UI uses). +fn run_fff_full(picker: &FilePicker, query: &str) -> (usize, Duration) { + let parsed = parse_grep_query(query); + let options = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: usize::MAX, + smart_case: true, + file_offset: 0, + page_limit: usize::MAX, + mode: Default::default(), + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + let start = Instant::now(); + let result = picker.grep(&parsed, &options); + let elapsed = start.elapsed(); + (result.matches.len(), elapsed) +} + +/// fff paginated: first 50 results only (real UI scenario). +fn run_fff_page(picker: &FilePicker, query: &str) -> (usize, Duration) { + let parsed = parse_grep_query(query); + let options = GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 50, + mode: Default::default(), + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + let start = Instant::now(); + let result = picker.grep(&parsed, &options); + let elapsed = start.elapsed(); + (result.matches.len(), elapsed) +} + +#[allow(dead_code)] +struct IterStats { + min: Duration, + avg: Duration, + count: usize, +} + +fn run_n (usize, Duration)>(f: F, n: usize) -> IterStats { + let mut times = Vec::with_capacity(n); + let mut count = 0; + for _ in 0..n { + let (c, d) = f(); + count = c; + times.push(d); + } + times.sort(); + let min = times[0]; + let avg = times.iter().sum::() / n as u32; + IterStats { min, avg, count } +} + +fn fmt_dur(d: Duration) -> String { + let us = d.as_micros(); + if us > 1_000_000 { + format!("{:.2}s", d.as_secs_f64()) + } else if us > 1000 { + format!("{:.1}ms", us as f64 / 1000.0) + } else { + format!("{}us", us) + } +} + +fn ratio_str(a: Duration, b: Duration) -> String { + if a.is_zero() || b.is_zero() { + return "-".to_string(); + } + let r = b.as_secs_f64() / a.as_secs_f64(); + format!("{:.1}x", r) +} + +fn main() { + let args: Vec = std::env::args().collect(); + let repo_path = if let Some(idx) = args.iter().position(|a| a == "--path") { + args.get(idx + 1) + .map(|s| s.as_str()) + .unwrap_or("./big-repo") + } else { + "./big-repo" + }; + let iters = if let Some(idx) = args.iter().position(|a| a == "--iters") { + args.get(idx + 1) + .and_then(|s| s.parse().ok()) + .unwrap_or(DEFAULT_ITERS) + } else { + DEFAULT_ITERS + }; + + let repo = std::path::PathBuf::from(repo_path); + if !repo.exists() { + eprintln!("Repository not found at: {}", repo_path); + std::process::exit(1); + } + + let canonical = fff::path_utils::canonicalize(&repo).expect("Failed to canonicalize path"); + + let rg_version = Command::new("rg") + .arg("--version") + .output() + .expect("ripgrep (rg) not found in PATH"); + let rg_ver = String::from_utf8_lossy(&rg_version.stdout); + + // Match rg's thread count to rayon's (both default to logical CPU count). + let threads = std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(4); + + eprintln!("=== FFF vs ripgrep (telescope-style) ==="); + eprintln!("Repo: {:?}", canonical); + eprintln!("rg: {}", rg_ver.lines().next().unwrap_or("?")); + eprintln!("Threads: {} (rg -j{} = rayon default)", threads, threads); + eprintln!("Iterations: {} per query", iters); + eprintln!(); + + eprintln!("[1/5] Indexing files..."); + let picker = create_picker(&canonical); + let files = picker.get_files(); + let non_binary = files.iter().filter(|f| !f.is_binary()).count(); + eprintln!(" {} files ({} searchable)\n", files.len(), non_binary); + + eprintln!("[2/5] Warming caches (fff mmap + OS page cache)..."); + for q in &["return", "mutex", "struct", "include", "if", "int"] { + let _ = run_fff_page(&picker, q); + let _ = run_rg_count(&canonical, q, true, threads); + } + eprintln!(" mmap cache: warmed\n"); + + // (name, query, case_insensitive_for_rg) + let queries: Vec<(&str, &str, bool)> = vec![ + ("single_char", "x", true), + ("short_common", "if", true), + ("very_common", "int", true), + ("common_keyword", "return", true), + ("preprocessor", "#include", true), + ("function_call", "mutex_lock", true), + ("multi_word", "static int __init", true), + ("type_decl", "struct file", true), + ("macro_define", "MODULE_LICENSE", false), + ("kernel_api", "EXPORT_SYMBOL", false), + ("error_path", "err = -EINVAL", false), + ("comment_tag", "TODO", false), + ("struct_name", "inode_operations", true), + ("rare_symbol", "phylink_ethtool", true), + ("long_literal", "This program is free software", true), + ]; + + eprintln!( + "\n[4/5] Full results: fff (collect all) vs rg (full line output) ({} iters, showing min)\n", + iters + ); + eprintln!( + " {:<22} | {:>9} {:>10} | {:>9} {:>10} | {:>7}", + "Query", "fff min", "count", "rg min", "count", "fff/rg" + ); + eprintln!( + " {:-<22}-+-{:-<9}-{:-<10}-+-{:-<9}-{:-<10}-+-{:-<7}", + "", "", "", "", "", "" + ); + + let mut fff_full_total = Duration::ZERO; + let mut rg_full_total = Duration::ZERO; + + for (name, query, ci) in &queries { + let q = *query; + let ci = *ci; + let fs = run_n(|| run_fff_full(&picker, q), iters); + let rs = run_n(|| run_rg_lines(&canonical, q, ci, threads), iters); + + eprintln!( + " {:<22} | {:>9} {:>10} | {:>9} {:>10} | {:>7}", + name, + fmt_dur(fs.min), + fs.count, + fmt_dur(rs.min), + rs.count, + ratio_str(fs.min, rs.min), + ); + + fff_full_total += fs.min; + rg_full_total += rs.min; + } + + eprintln!( + " {:<22} | {:>9} {:>10} | {:>9} {:>10} | {:>7}", + "TOTAL", + fmt_dur(fff_full_total), + "", + fmt_dur(rg_full_total), + "", + ratio_str(fff_full_total, rg_full_total), + ); + + eprintln!( + "\n[5/5] First-page latency --- the real UI scenario ({} iters, showing min)", + iters + ); + eprintln!(" fff: paginated search (50 matches) from warm mmap cache"); + eprintln!(" rg: telescope-style (spawn, stream 50 lines, kill) --- per-keystroke cost\n"); + eprintln!( + " {:<22} | {:>9} {:>10} | {:>9} {:>10} | {:>7}", + "Query", "fff min", "matches", "rg min", "matches", "fff/rg" + ); + eprintln!( + " {:-<22}-+-{:-<9}-{:-<10}-+-{:-<9}-{:-<10}-+-{:-<7}", + "", "", "", "", "", "" + ); + + let mut fff_page_total = Duration::ZERO; + let mut rg_page_total = Duration::ZERO; + + for (name, query, ci) in &queries { + let q = *query; + let ci = *ci; + let fs = run_n(|| run_fff_page(&picker, q), iters); + let rs = run_n(|| run_rg_page(&canonical, q, ci, 50, threads), iters); + + eprintln!( + " {:<22} | {:>9} {:>10} | {:>9} {:>10} | {:>7}", + name, + fmt_dur(fs.min), + fs.count, + fmt_dur(rs.min), + rs.count, + ratio_str(fs.min, rs.min), + ); + + fff_page_total += fs.min; + rg_page_total += rs.min; + } + + eprintln!( + " {:<22} | {:>9} {:>10} | {:>9} {:>10} | {:>7}", + "TOTAL", + fmt_dur(fff_page_total), + "", + fmt_dur(rg_page_total), + "", + ratio_str(fff_page_total, rg_page_total), + ); + + eprintln!( + "\n=== Summary (total min across all queries, {} iterations) ===\n", + iters + ); + eprintln!( + " {:>25} | {:>12} | {:>12} | {:>7}", + "", "fff", "rg", "speedup" + ); + eprintln!(" {:->25}-+-{:->12}-+-{:->12}-+-{:->7}", "", "", "", ""); + eprintln!( + " {:>25} | {:>12} | {:>12} | {:>7}", + "full results (collect)", + fmt_dur(fff_full_total), + fmt_dur(rg_full_total), + ratio_str(fff_full_total, rg_full_total), + ); + eprintln!( + " {:>25} | {:>12} | {:>12} | {:>7}", + "first-page (UI latency)", + fmt_dur(fff_page_total), + fmt_dur(rg_page_total), + ratio_str(fff_page_total, rg_page_total), + ); + + eprintln!(); + eprintln!(" Note: rg cost includes fork/exec + directory traversal + gitignore parsing"); + eprintln!(" on EVERY invocation (= every keystroke in telescope/fzf-lua)."); + eprintln!(" fff pays this cost once at startup, then searches from warm cached mmaps."); + eprintln!(); +} diff --git a/crates/fff-nvim/src/bin/jemalloc_profile.rs b/crates/fff-nvim/src/bin/jemalloc_profile.rs new file mode 100644 index 0000000..8ec63bf --- /dev/null +++ b/crates/fff-nvim/src/bin/jemalloc_profile.rs @@ -0,0 +1,289 @@ +use fff::file_picker::{FFFMode, FilePicker}; +use fff::{FuzzySearchOptions, PaginationArgs, QueryParser, SharedFilePicker, SharedFrecency}; +use std::env; +use std::thread; +use std::time::Duration; + +fn get_mem_stat() -> Result<(usize, usize, usize), Box> { + // Use system memory info since jemalloc-ctl conflicts + #[cfg(target_os = "macos")] + { + use std::process::Command; + let pid = std::process::id(); + let output = Command::new("ps") + .args(["-o", "rss=", "-p", &pid.to_string()]) + .output()?; + + let rss_str = String::from_utf8(output.stdout)?; + let rss_kb: usize = rss_str.trim().parse()?; + let rss_bytes = rss_kb * 1024; + Ok((rss_bytes, rss_bytes, rss_bytes)) + } + + #[cfg(target_os = "linux")] + { + let pid = std::process::id(); + let status_path = format!("/proc/{}/status", pid); + let content = std::fs::read_to_string(status_path)?; + + for line in content.lines() { + if line.starts_with("VmRSS:") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if let Ok(rss_kb) = parts[1].parse::() + && parts.len() >= 2 + { + let rss_bytes = rss_kb * 1024; + return Ok((rss_bytes, rss_bytes, rss_bytes)); + } + } + } + Err("Could not find VmRSS in /proc/pid/status".into()) + } + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + Ok((0, 0, 0)) + } +} + +fn format_bytes(bytes: usize) -> String { + const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; + let mut size = bytes as f64; + let mut unit_index = 0; + + while size >= 1024.0 && unit_index < UNITS.len() - 1 { + size /= 1024.0; + unit_index += 1; + } + + format!("{:.2} {}", size, UNITS[unit_index]) +} + +fn test_search_memory_pattern( + shared_picker: &SharedFilePicker, + name: &str, + iterations: usize, + query_pattern: impl Fn(usize) -> String, +) -> Result<(), Box> { + println!("🧪 {}", name); + + let (initial_allocated, initial_active, initial_resident) = get_mem_stat()?; + println!( + " Initial - Allocated: {}, Active: {}, Resident: {}", + format_bytes(initial_allocated), + format_bytes(initial_active), + format_bytes(initial_resident) + ); + + let mut max_allocated = initial_allocated; + let mut max_active = initial_active; + let mut max_resident = initial_resident; + + for i in 0..iterations { + let query = query_pattern(i); + + let (result_count, _total_matched) = { + let guard = shared_picker.read().unwrap(); + if let Some(ref picker) = *guard { + let parser = QueryParser::default(); + let parsed = parser.parse(&query); + let search_result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1 + (i % 4), + current_file: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 50 + (i % 50), + }, + }, + ); + (search_result.items.len(), search_result.total_matched) + } else { + continue; + } + }; + + if i % 100 == 0 { + let (allocated, active, resident) = get_mem_stat()?; + max_allocated = max_allocated.max(allocated); + max_active = max_active.max(active); + max_resident = max_resident.max(resident); + + if i % 500 == 0 { + println!( + " Iteration {}: {} results, Allocated: {} (+{})", + i, + result_count, + format_bytes(allocated), + format_bytes(allocated.saturating_sub(initial_allocated)) + ); + } + } + + if i % 200 == 199 { + thread::sleep(Duration::from_millis(1)); + } + } + + let (final_allocated, final_active, final_resident) = get_mem_stat()?; + + println!( + " Final - Allocated: {} (+{}), Active: {} (+{}), Resident: {} (+{})", + format_bytes(final_allocated), + format_bytes(final_allocated.saturating_sub(initial_allocated)), + format_bytes(final_active), + format_bytes(final_active.saturating_sub(initial_active)), + format_bytes(final_resident), + format_bytes(final_resident.saturating_sub(initial_resident)) + ); + + println!( + " Peak - Allocated: {} (+{}), Active: {} (+{}), Resident: {} (+{})", + format_bytes(max_allocated), + format_bytes(max_allocated.saturating_sub(initial_allocated)), + format_bytes(max_active), + format_bytes(max_active.saturating_sub(initial_active)), + format_bytes(max_resident), + format_bytes(max_resident.saturating_sub(initial_resident)) + ); + + let growth_per_search = + (final_allocated.saturating_sub(initial_allocated)) as f64 / iterations as f64; + println!( + " Average growth per search: {:.2} bytes", + growth_per_search + ); + println!(); + + Ok(()) +} + +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + let base_path = if args.len() > 1 { + args[1].clone() + } else { + env::current_dir()?.to_str().unwrap_or(".").to_string() + }; + + println!("🧪 FFF.nvim Jemalloc Memory Profiler"); + println!("===================================="); + println!("Test directory: {}", base_path); + println!(); + + // Create shared state + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + // Initialize FilePicker + println!("Initializing FilePicker..."); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + fff::FilePickerOptions { + base_path: base_path.clone(), + enable_mmap_cache: false, + mode: FFFMode::Neovim, + ..Default::default() + }, + )?; + + // Wait for initial scan + println!("Waiting for file scan..."); + loop { + if let Ok(guard) = shared_picker.read() + && let Some(ref picker) = *guard + && !picker.is_scan_active() + && !picker.get_files().is_empty() + { + break; + } + thread::sleep(Duration::from_millis(100)); + } + + let file_count = { + let guard = shared_picker.read().unwrap(); + guard.as_ref().unwrap().get_files().len() + }; + + println!("📊 Found {} files", file_count); + + let (initial_allocated, initial_active, initial_resident) = get_mem_stat()?; + println!("🧠 Baseline memory:"); + println!(" Allocated: {}", format_bytes(initial_allocated)); + println!(" Active: {}", format_bytes(initial_active)); + println!(" Resident: {}", format_bytes(initial_resident)); + println!(); + + // Test different memory patterns + + // 1. Repeated same query - should have minimal growth if caching works + test_search_memory_pattern(&shared_picker, "Same Query Repeated (1000x)", 1000, |_| { + "test".to_string() + })?; + + // 2. Cycling through different queries + test_search_memory_pattern(&shared_picker, "Cycling Queries (1000x)", 1000, |i| { + let queries = [ + "test", "main", "lib", "src", "mod", "file", "picker", "fuzzy", "search", + ]; + queries[i % queries.len()].to_string() + })?; + + // 3. Unique queries each time - worst case for any caching + test_search_memory_pattern(&shared_picker, "Unique Queries (500x)", 500, |i| { + format!("unique_query_{}", i) + })?; + + // 4. Queries that return many results + test_search_memory_pattern( + &shared_picker, + "High Result Count (500x)", + 500, + |_| "a".to_string(), // Single character likely to match many files + )?; + + // 5. Queries with no results + test_search_memory_pattern(&shared_picker, "No Results (500x)", 500, |_| { + "zzzz_no_match_expected".to_string() + })?; + + // 6. Long intensive test + test_search_memory_pattern(&shared_picker, "Long Intensive Test (2000x)", 2000, |i| { + let patterns = [ + "rs", "lua", "toml", "mod", "lib", "main", "test", "src", "file", + ]; + format!("{}{}", patterns[i % patterns.len()], i % 100) + })?; + + let (final_allocated, final_active, final_resident) = get_mem_stat()?; + + println!("🏁 Final Memory Stats:"); + println!( + " Allocated: {} (growth: {})", + format_bytes(final_allocated), + format_bytes(final_allocated.saturating_sub(initial_allocated)) + ); + println!( + " Active: {} (growth: {})", + format_bytes(final_active), + format_bytes(final_active.saturating_sub(initial_active)) + ); + println!( + " Resident: {} (growth: {})", + format_bytes(final_resident), + format_bytes(final_resident.saturating_sub(initial_resident)) + ); + + let total_searches = 5500; + let avg_growth = + (final_allocated.saturating_sub(initial_allocated)) as f64 / total_searches as f64; + println!(" Average growth per search: {:.2} bytes", avg_growth); + + Ok(()) +} diff --git a/crates/fff-nvim/src/bin/search_profiler.rs b/crates/fff-nvim/src/bin/search_profiler.rs new file mode 100644 index 0000000..869393a --- /dev/null +++ b/crates/fff-nvim/src/bin/search_profiler.rs @@ -0,0 +1,140 @@ +use fff::file_picker::{FFFMode, FilePicker}; +use fff::{FuzzySearchOptions, PaginationArgs, QueryParser, SharedFilePicker, SharedFrecency}; +use std::time::{Duration, Instant}; + +/// Wait for background scan to complete +fn wait_for_scan(shared_picker: &SharedFilePicker, timeout_secs: u64) -> Result { + let timeout = Duration::from_secs(timeout_secs); + if !shared_picker.wait_for_scan(timeout) { + return Err(format!("Scan timed out after {} seconds", timeout_secs)); + } + + let picker_guard = shared_picker + .read() + .map_err(|e| format!("Failed to acquire read lock: {}", e))?; + if let Some(ref picker) = *picker_guard { + Ok(picker.get_files().len()) + } else { + Err("FilePicker not initialized".to_string()) + } +} + +fn main() { + let big_repo_path = std::path::PathBuf::from("./big-repo"); + + if !big_repo_path.exists() { + eprintln!( + "./big-repo directory does not exist. Run git clone https://github.com/torvalds/linux.git big-repo" + ); + return; + } + + let canonical_path = + fff::path_utils::canonicalize(&big_repo_path).expect("Failed to canonicalize path"); + + // Create shared state + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + eprintln!("Initializing FilePicker for: {:?}", canonical_path); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + fff::FilePickerOptions { + base_path: canonical_path.to_string_lossy().to_string(), + enable_mmap_cache: false, + mode: FFFMode::Neovim, + ..Default::default() + }, + ) + .expect("Failed to init FilePicker with shared state"); + + // Give background thread time to start + std::thread::sleep(Duration::from_millis(200)); + + eprintln!("Waiting for scan to complete..."); + let file_count = wait_for_scan(&shared_picker, 120).expect("Failed to wait for scan"); + eprintln!("✓ Indexed {} files\n", file_count); + + let picker_guard = shared_picker.read().expect("Failed to acquire read lock"); + let picker = picker_guard.as_ref().expect("FilePicker not initialized"); + + // Test queries representing different search patterns + let test_queries = vec![ + ("short_common", "mod", 100), + ("medium_specific", "controller", 100), + ("long_rare", "user_authentication", 100), + ("typo_resistant", "contrlr", 100), + ("path_like", "src/lib", 100), + ("single_char", "a", 100), + ("two_char", "st", 100), + ("partial_word", "test", 100), + ("deep_path", "drivers/net", 100), + ("extension", ".rs", 100), + ]; + + eprintln!("Running search profiler..."); + eprintln!("Query | Iterations | Total Time | Avg Time | Matches"); + eprintln!("----------------------|------------|------------|-----------|--------"); + + let global_start = Instant::now(); + let mut total_iterations = 0; + + for (name, query, iterations) in test_queries { + let start = Instant::now(); + let mut match_count = 0; + let parser = QueryParser::default(); + + for _ in 0..iterations { + let parsed = parser.parse(query); + let results = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 4, + current_file: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }, + ); + + match_count += results.total_matched; + } + + let elapsed = start.elapsed(); + let avg_time = elapsed / iterations as u32; + + eprintln!( + "{:<21} | {:>10} | {:>9.2}s | {:>7}µs | {}", + name, + iterations, + elapsed.as_secs_f64(), + avg_time.as_micros(), + match_count / iterations + ); + + total_iterations += iterations; + } + + let total_time = global_start.elapsed(); + + eprintln!("\n=== Summary ==="); + eprintln!("Total searches: {}", total_iterations); + eprintln!("Total time: {:.2}s", total_time.as_secs_f64()); + eprintln!( + "Average per search: {}µs", + (total_time.as_micros() as usize) / total_iterations + ); + eprintln!( + "Searches per sec: {:.0}", + total_iterations as f64 / total_time.as_secs_f64() + ); + + // Keep the program alive briefly so perf can capture everything + std::thread::sleep(Duration::from_millis(100)); +} diff --git a/crates/fff-nvim/src/bin/test_memory_leak.rs b/crates/fff-nvim/src/bin/test_memory_leak.rs new file mode 100644 index 0000000..67e1c47 --- /dev/null +++ b/crates/fff-nvim/src/bin/test_memory_leak.rs @@ -0,0 +1,282 @@ +use fff::file_picker::{FFFMode, FilePicker}; +use fff::{FuzzySearchOptions, PaginationArgs, QueryParser, SharedFilePicker, SharedFrecency}; +use std::env; +use std::io::{self, Write}; +use std::thread; +use std::time::{Duration, Instant}; + +fn get_memory_usage() -> Result> { + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + return Err("Memory usage check is only supported on Linux and macOS".into()); + } + + #[cfg(target_os = "macos")] + { + let pid = std::process::id(); + use std::process::Command; + let output = Command::new("ps") + .args(["-o", "rss=", "-p", &pid.to_string()]) + .output()?; + + let rss_str = String::from_utf8(output.stdout)?; + let rss_kb: u64 = rss_str.trim().parse()?; + + Ok(rss_kb * 1024) + } + + #[cfg(target_os = "linux")] + { + let pid = std::process::id(); + let status_path = format!("/proc/{}/status", pid); + let content = std::fs::read_to_string(status_path)?; + + for line in content.lines() { + if line.starts_with("VmRSS:") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if let Ok(rss_kb) = parts[1].parse::() + && parts.len() >= 2 + { + return Ok(rss_kb * 1024); // Convert KB to bytes + } + } + } + + Err("Could not find VmRSS in /proc/pid/status".into()) + } +} + +fn format_bytes(bytes: u64) -> String { + const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; + let mut size = bytes as f64; + let mut unit_index = 0; + + while size >= 1024.0 && unit_index < UNITS.len() - 1 { + size /= 1024.0; + unit_index += 1; + } + + format!("{:.2} {}", size, UNITS[unit_index]) +} + +fn main() -> Result<(), Box> { + if !cfg!(target_os = "linux") && !cfg!(target_os = "macos") { + eprintln!("This test is only supported on Linux and macOS."); + std::process::exit(1); + } + + let args: Vec = env::args().collect(); + let base_path = if args.len() > 1 { + args[1].clone() + } else { + env::current_dir()?.to_str().unwrap_or(".").to_string() + }; + + println!("🧪 FFF.nvim Memory Leak Test (Using Crate)"); + println!("=========================================="); + println!("Test directory: {}", base_path); + println!(); + + // Create shared state + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + // Initialize the file picker + println!("📁 Initializing FilePicker..."); + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + fff::FilePickerOptions { + base_path: base_path.clone(), + enable_mmap_cache: false, + mode: FFFMode::Neovim, + ..Default::default() + }, + )?; + + // Wait for initial scan to complete + println!("⏳ Waiting for initial file scan to complete..."); + let mut wait_time = 0; + let mut scan_completed = false; + + loop { + if let Ok(guard) = shared_picker.read() + && let Some(ref picker) = *guard + { + if !picker.is_scan_active() { + println!("Scan inactive, checking file count..."); + let file_count = picker.get_files().len(); + if file_count > 0 { + println!("Async scan found {} files", file_count); + scan_completed = true; + break; + } + } else { + println!("Scan active, waiting..."); + } + } + thread::sleep(Duration::from_millis(100)); + wait_time += 100; + if wait_time > 10000 { + println!("Timeout waiting for async scan, triggering manual scan..."); + break; + } + } + + // If async scan didn't work, trigger a manual scan + if !scan_completed { + println!("Triggering manual rescan..."); + let _ = shared_picker.trigger_full_rescan_async(&shared_frecency); + } + + let initial_file_count = { + let guard = shared_picker.read().unwrap(); + if let Some(ref picker) = *guard { + let files = picker.get_files(); + println!("Found {} files in picker", files.len()); + if !files.is_empty() { + println!("Sample files:"); + for (i, file) in files.iter().take(5).enumerate() { + println!(" {}. {}", i + 1, file.relative_path(picker)); + } + } + files.len() + } else { + println!("No picker found!"); + 0 + } + }; + + println!( + "📊 Initial scan complete: {} files found", + initial_file_count + ); + + if initial_file_count == 0 { + eprintln!("❌ No files found in directory. Cannot proceed with memory test."); + std::process::exit(1); + } + + // Record initial memory usage + let initial_memory = get_memory_usage().unwrap_or(0); + println!("🧠 Initial memory usage: {}", format_bytes(initial_memory)); + println!(); + + // Test queries to cycle through + let test_queries = vec![ + "rs", "mod", "lib", "main", "test", "src", "lua", "cargo", "toml", "init", "config", + "util", "file", "picker", "fuzzy", "search", "git", "fn", "struct", "impl", "pub", "use", + "let", "match", "if", "for", "async", "await", "Result", "Error", "Vec", "String", + "Option", + ]; + + let mut last_memory_check = Instant::now(); + let mut peak_memory = initial_memory; + let mut memory_samples = Vec::new(); + + println!("🔥 Starting intensive search test..."); + println!("Press Ctrl+C to stop the test"); + println!(); + let mut search_count = 0; + + while search_count < 1000 { + search_count += 1; + + // Perform search directly on FilePicker + let query = test_queries[search_count % test_queries.len()]; + let max_results = 50 + (search_count % 100); // Vary result count + let max_threads = 1 + (search_count % 8); // Vary thread count + + let search_start = Instant::now(); + let parser = QueryParser::default(); + let (result_count, search_duration) = { + let guard = shared_picker.read().unwrap(); + if let Some(ref picker) = *guard { + let parsed = parser.parse(query); + let search_result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads, + current_file: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: max_results, + }, + }, + ); + let duration = search_start.elapsed(); + (search_result.items.len(), duration) + } else { + (0, search_start.elapsed()) + } + }; + + search_count += 1; + + // Check memory every 100 searches or every 5 seconds + let now = Instant::now(); + if (search_count % 100 == 0 + || now.duration_since(last_memory_check) > Duration::from_secs(5)) + && let Ok(current_memory) = get_memory_usage() + { + memory_samples.push(current_memory); + + if current_memory > peak_memory { + peak_memory = current_memory; + } + + let memory_growth = current_memory.saturating_sub(initial_memory); + + println!( + "🔍 Search #{}: '{}' -> {} results in {:?} | Memory: {} (+{}) | Peak: {}", + search_count, + query, + result_count, + search_duration, + format_bytes(current_memory), + format_bytes(memory_growth), + format_bytes(peak_memory) + ); + + last_memory_check = now; + + // Calculate memory growth trend over last 10 samples + if memory_samples.len() >= 10 { + let recent_samples = &memory_samples[memory_samples.len() - 10..]; + let first_recent = recent_samples[0]; + let last_recent = recent_samples[recent_samples.len() - 1]; + + if last_recent > first_recent { + let recent_growth = last_recent - first_recent; + if recent_growth > 1024 * 1024 { + // More than 1MB growth in recent samples + println!( + "⚠️ POTENTIAL LEAK: Recent memory growth: {}", + format_bytes(recent_growth) + ); + } + } + } + + io::stdout().flush().unwrap(); + } + + // Brief pause to prevent overwhelming the system + if search_count % 10 == 0 { + thread::sleep(Duration::from_millis(1)); + } + + // Force cleanup test every 1000 searches + if search_count % 1000 == 0 { + println!("🧹 Forcing potential cleanup at search #{}", search_count); + // Force garbage collection by creating and dropping temporary data + thread::sleep(Duration::from_millis(10)); + } + } + + Ok(()) +} diff --git a/crates/fff-nvim/src/bin/test_watcher.rs b/crates/fff-nvim/src/bin/test_watcher.rs new file mode 100644 index 0000000..be717b0 --- /dev/null +++ b/crates/fff-nvim/src/bin/test_watcher.rs @@ -0,0 +1,205 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(clippy::enum_variant_names)] + +use fff::file_picker::FilePicker; +use fff::git::format_git_status; +use fff::{ + FFFMode, FuzzySearchOptions, PaginationArgs, QueryParser, SharedFilePicker, SharedFrecency, +}; +use std::env; +use std::io::{self, Write}; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::Duration; + +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + let base_path = if args.len() > 1 { + args[1].clone() + } else { + env::current_dir()?.to_str().unwrap_or(".").to_string() + }; + + // Set up signal handler for graceful shutdown + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + + // Create shared state + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + // Clone for signal handler + let picker_for_cleanup = shared_picker.clone(); + ctrlc::set_handler(move || { + println!("\n🛑 Received interrupt signal, shutting down..."); + if let Ok(mut guard) = picker_for_cleanup.write() { + guard.take(); + } + println!("🧹 FilePicker cleaned up"); + r.store(false, Ordering::SeqCst); + std::process::exit(0); + })?; + + let mut git_stats = std::collections::HashMap::new(); + + // Initialize the file picker using shared state + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + fff::FilePickerOptions { + base_path: base_path.clone(), + enable_mmap_cache: false, + mode: FFFMode::default(), + ..Default::default() + }, + )?; + + // Get initial file count from shared state + let initial_count = { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let files = picker.get_files(); + println!("Initial file count: {}", files.len()); + + if !files.is_empty() { + println!("Sample files:"); + for (i, file) in files.iter().take(5).enumerate() { + println!( + " {}. {} ({})", + i + 1, + file.relative_path(picker), + format_git_status(file.git_status) + ); + } + if files.len() > 5 { + println!(" ... and {} more files", files.len() - 5); + } + } + files.len() + }; + + println!("{:=<60}", ""); + println!("🔴 LIVE FILE MONITORING - Press Ctrl+C to stop"); + println!("{:=<60}", ""); + + let mut last_count = initial_count; + let mut iteration = 0; + + while running.load(Ordering::SeqCst) { + thread::sleep(Duration::from_millis(500)); + iteration += 1; + + let current_count = { + let guard = shared_picker.read().unwrap(); + guard.as_ref().unwrap().get_files().len() + }; + + if current_count != last_count { + let timestamp = chrono::Local::now().format("%H:%M:%S%.3f"); + + if current_count > last_count { + let added = current_count - last_count; + println!( + "🟢 [{}] +{} files added | Total: {}", + timestamp, added, current_count + ); + + // Show some recently added files + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let files = picker.get_files(); + let newest_files = files.iter().rev().take(added.min(3)); + for file in newest_files { + println!(" ➕ {}", file.relative_path(picker)); + } + } else { + let removed = last_count - current_count; + println!( + "🔴 [{}] -{} files removed | Total: {}", + timestamp, removed, current_count + ); + } + + last_count = current_count; + } + + if iteration % 20 == 0 { + let timestamp = chrono::Local::now().format("%H:%M:%S"); + println!( + "💓 [{}] Heartbeat - {} files cached, watcher active", + timestamp, current_count + ); + + let guard = shared_picker.read().unwrap(); + let current_files = guard.as_ref().unwrap().get_files(); + + git_stats.clear(); + for file in current_files { + let status = format_git_status(file.git_status); + *git_stats.entry(status).or_insert(0) += 1; + } + + if !git_stats.is_empty() { + print!(" 📊 Git status: "); + for (status, count) in &git_stats { + print!("{}:{} ", status, count); + } + println!(); + } + } + + if iteration % 40 == 0 { + let timestamp = chrono::Local::now().format("%H:%M:%S"); + let guard = shared_picker.read().unwrap(); + let picker_ref = guard.as_ref().unwrap(); + let parser = QueryParser::default(); + let parsed = parser.parse("rs"); + let search_results = picker_ref.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 2, + current_file: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 5, + }, + }, + ); + + println!( + "🔍 [{}] Search test 'rs': {} matches", + timestamp, + search_results.items.len() + ); + for (i, (file, score)) in search_results + .items + .iter() + .zip(search_results.scores.iter()) + .take(3) + .enumerate() + { + println!( + " {}. {} (score: {})", + i + 1, + file.relative_path(picker_ref), + score.total + ); + } + } + + io::stdout().flush().unwrap(); + } + + // Clean up before exit + if let Ok(mut guard) = shared_picker.write() { + guard.take(); + } + + Ok(()) +} diff --git a/crates/fff-nvim/src/error.rs b/crates/fff-nvim/src/error.rs new file mode 100644 index 0000000..91a09e7 --- /dev/null +++ b/crates/fff-nvim/src/error.rs @@ -0,0 +1,26 @@ +//! Error handling for fff-nvim +//! +//! This module provides utilities for converting fff errors to mlua errors. + +use fff::Error as CoreError; + +/// Convert a fff::Error to mlua::Error +/// +/// This function is used because we can't implement From for mlua::Error +/// due to Rust's orphan rules (both types are foreign to this crate). +pub fn to_lua_error(err: CoreError) -> mlua::Error { + let string_value = err.to_string(); + ::tracing::error!(string_value); + mlua::Error::RuntimeError(string_value) +} + +/// Extension trait for Result to convert to LuaResult +pub trait IntoLuaResult { + fn into_lua_result(self) -> mlua::Result; +} + +impl IntoLuaResult for Result { + fn into_lua_result(self) -> mlua::Result { + self.map_err(to_lua_error) + } +} diff --git a/crates/fff-nvim/src/hex_dump.rs b/crates/fff-nvim/src/hex_dump.rs new file mode 100644 index 0000000..05c1193 --- /dev/null +++ b/crates/fff-nvim/src/hex_dump.rs @@ -0,0 +1,190 @@ +use mlua::prelude::*; +use std::fmt::Write as _; +use std::io::{Read, Seek, SeekFrom}; + +// Byte category colors (matching hexyl's default theme) +const COLOR_OFFSET: &str = "#888888"; +const COLOR_NULL: &str = "#555753"; +const COLOR_ASCII_PRINTABLE: &str = "#06989a"; +const COLOR_ASCII_WHITESPACE: &str = "#4e9a06"; +const COLOR_ASCII_OTHER: &str = "#4e9a06"; +const COLOR_NON_ASCII: &str = "#c4a000"; + +fn byte_color(b: u8) -> &'static str { + match b { + 0x00 => COLOR_NULL, + 0x20 | 0x09 | 0x0a | 0x0d => COLOR_ASCII_WHITESPACE, + 0x21..=0x7e => COLOR_ASCII_PRINTABLE, + 0x01..=0x1f | 0x7f => COLOR_ASCII_OTHER, + _ => COLOR_NON_ASCII, + } +} + +fn byte_char(b: u8) -> char { + match b { + 0x20..=0x7e => b as char, + _ => '.', + } +} + +const BYTES_PER_LINE: usize = 16; + +struct Span { + line: usize, + col_start: usize, + col_end: usize, + color: &'static str, +} + +/// Push a span, merging with the previous one if same line and color. +fn push_span( + spans: &mut Vec, + line: usize, + col_start: usize, + col_end: usize, + color: &'static str, +) { + if let Some(last) = spans.last_mut() { + // Merge if same line, same color, and adjacent (allow small gaps for spaces between hex pairs) + if last.line == line && std::ptr::eq(last.color, color) && col_start <= last.col_end + 1 { + last.col_end = col_end; + return; + } + } + spans.push(Span { + line, + col_start, + col_end, + color, + }); +} + +/// Format raw bytes into hex dump lines with coalesced highlight spans. +/// +/// Layout per line: +/// ```text +/// XXXXXXXX HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH HH CCCCCCCCCCCCCCCC +/// ``` +fn format_hex_dump(raw_bytes: &[u8], base_offset: u64) -> (Vec, Vec) { + let mut lines = Vec::new(); + let mut spans = Vec::new(); + + for (chunk_idx, chunk) in raw_bytes.chunks(BYTES_PER_LINE).enumerate() { + let addr = base_offset + (chunk_idx * BYTES_PER_LINE) as u64; + let mut line = format!("{addr:08x} "); + + // Offset label highlight + push_span(&mut spans, chunk_idx, 0, 8, COLOR_OFFSET); + + // Hex pairs with a gap after 8 bytes + for (i, &b) in chunk.iter().enumerate() { + if i == 8 { + line.push(' '); + } + let col = line.len(); + push_span(&mut spans, chunk_idx, col, col + 2, byte_color(b)); + write!(line, "{b:02x} ").unwrap(); + } + + // Pad if the last line is short + if chunk.len() < BYTES_PER_LINE { + let missing = BYTES_PER_LINE - chunk.len(); + let mut pad = missing * 3; + if chunk.len() <= 8 { + pad += 1; + } + for _ in 0..pad { + line.push(' '); + } + } + + // Separator before char panel + line.push(' '); + + // Character panel — consecutive same-color chars merge automatically + let char_start = line.len(); + for (i, &b) in chunk.iter().enumerate() { + let col = char_start + i; + push_span(&mut spans, chunk_idx, col, col + 1, byte_color(b)); + line.push(byte_char(b)); + } + + lines.push(line); + } + + (lines, spans) +} + +/// Generate a hex dump for a binary file with paging support and highlight data. +/// +/// Returns a Lua table: +/// ```text +/// { +/// lines: string[], +/// highlights: {line_0idx, col_start, col_end, color}[], +/// has_more: bool, +/// next_offset: number, +/// } +/// ``` +pub fn hex_dump( + lua: &Lua, + (file_path, offset, length): (String, Option, Option), +) -> LuaResult { + let offset = offset.unwrap_or(0); + let length = length.unwrap_or(4096); + + let file = std::fs::File::open(&file_path) + .map_err(|e| LuaError::RuntimeError(format!("Failed to open file: {e}")))?; + + let file_size = file + .metadata() + .map_err(|e| LuaError::RuntimeError(format!("Failed to get metadata: {e}")))? + .len(); + + let table = lua.create_table()?; + + if offset >= file_size { + table.set("lines", lua.create_table()?)?; + table.set("highlights", lua.create_table()?)?; + table.set("has_more", false)?; + table.set("next_offset", file_size)?; + return Ok(LuaValue::Table(table)); + } + + let mut reader = std::io::BufReader::new(file); + reader + .seek(SeekFrom::Start(offset)) + .map_err(|e| LuaError::RuntimeError(format!("Failed to seek: {e}")))?; + let mut raw_bytes = Vec::with_capacity(length as usize); + reader + .by_ref() + .take(length) + .read_to_end(&mut raw_bytes) + .map_err(|e| LuaError::RuntimeError(format!("Failed to read: {e}")))?; + + let (plain_lines, hl_spans) = format_hex_dump(&raw_bytes, offset); + + let lines_table = lua.create_table()?; + for (i, line) in plain_lines.iter().enumerate() { + lines_table.set(i + 1, line.as_str())?; + } + table.set("lines", lines_table)?; + + let highlights_table = lua.create_table()?; + for (i, span) in hl_spans.iter().enumerate() { + let hl = lua.create_table()?; + hl.raw_set(1, span.line)?; + hl.raw_set(2, span.col_start)?; + hl.raw_set(3, span.col_end)?; + hl.raw_set(4, span.color)?; + highlights_table.raw_set(i + 1, hl)?; + } + table.set("highlights", highlights_table)?; + + let bytes_read = raw_bytes.len() as u64; + let next_offset = offset + bytes_read; + table.set("has_more", next_offset < file_size)?; + table.set("next_offset", next_offset)?; + + Ok(LuaValue::Table(table)) +} diff --git a/crates/fff-nvim/src/lib.rs b/crates/fff-nvim/src/lib.rs new file mode 100644 index 0000000..a9f3cb2 --- /dev/null +++ b/crates/fff-nvim/src/lib.rs @@ -0,0 +1,1034 @@ +use crate::path_shortening::shorten_path_with_cache; +use error::IntoLuaResult; +use fff::file_picker::FilePicker; +use fff::frecency::FrecencyTracker; +use fff::path_utils::expand_tilde; +use fff::query_tracker::QueryTracker; +use fff::{ + DbHealthChecker, DirSearchConfig, Error, FFFMode, FFFQuery, FileSearchConfig, + FuzzySearchOptions, MixedSearchConfig, PaginationArgs, QueryParser, Score, SearchResult, + SharedFilePicker, SharedFrecency, SharedQueryTracker, +}; +use mimalloc::MiMalloc; +use mlua::prelude::*; +use once_cell::sync::Lazy; +use path_shortening::PathShortenStrategy; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use user_config::{NvimGrepConfig, UserConfigOptions, set_global_user_config}; + +mod error; +mod hex_dump; +mod log; +mod lua_types; +mod path_shortening; +mod user_config; + +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +// the global state for neovim lives here for efficiency +// lua ffi is pretty bad with the overhead of converting raw pointer into tables +pub static FILE_PICKER: Lazy = Lazy::new(SharedFilePicker::default); +pub static FRECENCY: Lazy = Lazy::new(SharedFrecency::default); +pub static QUERY_TRACKER: Lazy = Lazy::new(SharedQueryTracker::default); + +pub fn init_db( + _: &Lua, + (frecency_db_path, history_db_path, _use_unsafe_no_lock): (String, String, bool), +) -> LuaResult { + // Route through SharedFrecency::init / SharedQueryTracker::init so the + // GC thread gets spawned + bound to the shared handle's RwLock. + FRECENCY + .init(FrecencyTracker::open(&frecency_db_path).into_lua_result()?) + .into_lua_result()?; + tracing::info!("Frecency database initialized at {}", frecency_db_path); + + QUERY_TRACKER + .init(QueryTracker::open(&history_db_path).into_lua_result()?) + .into_lua_result()?; + tracing::info!("Query tracker database initialized at {}", history_db_path); + Ok(true) +} + +pub fn destroy_frecency_db(_: &Lua, _: ()) -> LuaResult { + Ok(FRECENCY.destroy().into_lua_result()?.is_some()) +} + +pub fn destroy_query_db(_: &Lua, _: ()) -> LuaResult { + Ok(QUERY_TRACKER.destroy().into_lua_result()?.is_some()) +} + +/// Opts table accepted by `init_file_picker` / `restart_index_in_path`. +/// Backwards compat: positional `follow_symlinks: bool` argument still works +/// — callers that pass a table use the named fields instead. +#[derive(Default)] +struct PickerInitOpts { + follow_symlinks: bool, + enable_fs_root_scanning: bool, + enable_home_dir_scanning: bool, + enable_filename_constraint: bool, +} + +impl PickerInitOpts { + fn from_lua_value(value: Option) -> LuaResult { + let Some(value) = value else { + return Ok(Self::default()); + }; + match value { + mlua::Value::Nil => Ok(Self::default()), + mlua::Value::Boolean(b) => Ok(Self { + follow_symlinks: b, + ..Default::default() + }), + mlua::Value::Table(t) => Ok(Self { + follow_symlinks: t.get::>("follow_symlinks")?.unwrap_or(false), + enable_fs_root_scanning: t + .get::>("enable_fs_root_scanning")? + .unwrap_or(false), + enable_home_dir_scanning: t + .get::>("enable_home_dir_scanning")? + .unwrap_or(false), + enable_filename_constraint: t + .get::>("enable_filename_constraint")? + .unwrap_or(false), + }), + other => Err(LuaError::RuntimeError(format!( + "init opts must be a table, boolean, or nil — got {}", + other.type_name() + ))), + } + } +} + +pub fn init_file_picker( + _: &Lua, + (base_path, opts): (String, Option), +) -> LuaResult { + { + let guard = FILE_PICKER.read().into_lua_result()?; + if guard.is_some() { + return Ok(false); + } + } + + let opts = PickerInitOpts::from_lua_value(opts)?; + set_global_user_config(UserConfigOptions { + enable_filename_constraint: opts.enable_filename_constraint, + }); + + FilePicker::new_with_shared_state( + FILE_PICKER.clone(), + FRECENCY.clone(), + fff::FilePickerOptions { + base_path, + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + follow_symlinks: opts.follow_symlinks, + enable_fs_root_scanning: opts.enable_fs_root_scanning, + enable_home_dir_scanning: opts.enable_home_dir_scanning, + ..Default::default() + }, + ) + .into_lua_result()?; + + Ok(true) +} + +pub fn restart_index_in_path( + _: &Lua, + (new_path, opts): (String, Option), +) -> LuaResult<()> { + let path = std::path::PathBuf::from(&new_path); + if !path.exists() { + return Err(LuaError::RuntimeError(format!( + "Path does not exist: {}", + new_path + ))); + } + + let canonical_path = fff::path_utils::canonicalize(&path).map_err(|e| { + LuaError::RuntimeError(format!("Failed to canonicalize path '{}': {}", new_path, e)) + })?; + + let opts = PickerInitOpts::from_lua_value(opts)?; + set_global_user_config(UserConfigOptions { + enable_filename_constraint: opts.enable_filename_constraint, + }); + + // Spawn a background thread BEFORE touching the picker lock. The + // same-dir short-circuit previously called `FILE_PICKER.read()` on + // the lua/UI thread, which blocks if a reindex writer is already in + // flight — on repeated DirChanged events (e.g. LSP root switching + // right after closing the picker) that could freeze the nvim main + // loop for the entire duration of an in-progress scan. Move the + // check into the spawned worker so the main thread always returns + // instantly; the internal reinit path also re-checks and no-ops if + // the picker is already pointing at `canonical_path`. + std::thread::spawn(move || { + ::tracing::info!( + ?canonical_path, + "restart_index_in_path: spawned worker running" + ); + + // Inherit current picker's scanning flags when caller didn't pass + // explicit opts — otherwise a `:cd ~` after init would silently lose + // the user's `enable_home_dir_scanning = true` setting. + let (follow_symlinks, fs_root, home_dir) = { + let guard = match FILE_PICKER.read() { + Ok(g) => g, + Err(_) => return, + }; + + if let Some(ref picker) = *guard + && picker.base_path() == canonical_path + { + tracing::info!(?canonical_path, "restart_index_in_path: same dir, skipping"); + return; + } + + match guard.as_ref() { + Some(p) => ( + p.follows_symlinks() || opts.follow_symlinks, + p.fs_root_scanning_enabled() || opts.enable_fs_root_scanning, + p.home_dir_scanning_enabled() || opts.enable_home_dir_scanning, + ), + None => ( + opts.follow_symlinks, + opts.enable_fs_root_scanning, + opts.enable_home_dir_scanning, + ), + } + }; + + ::tracing::info!( + ?canonical_path, + "restart_index_in_path: calling reinit_file_picker_internal" + ); + + // this will AUTOMATICALLY drop the old picker within a write lock inside the implementation + // that will stop all the ongoing work and drop all the workeres + if let Err(e) = FilePicker::new_with_shared_state( + FILE_PICKER.clone(), + FRECENCY.clone(), + fff::FilePickerOptions { + base_path: canonical_path.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + follow_symlinks, + enable_fs_root_scanning: fs_root, + enable_home_dir_scanning: home_dir, + ..Default::default() + }, + ) { + tracing::error!( + ?e, + ?canonical_path, + "Failed to index directory after changing" + ); + } + }); + + Ok(()) +} + +pub fn scan_files(_: &Lua, _: ()) -> LuaResult<()> { + // Async: spawns a BG thread that walks off-lock, swaps sync_data + // under a brief write, then applies git + frecency off-lock. Returns + // immediately so the lua/UI thread never waits on the walk. + FILE_PICKER + .trigger_full_rescan_async(&FRECENCY) + .into_lua_result()?; + ::tracing::info!("scan_files rescan spawned"); + Ok(()) +} + +#[allow(clippy::type_complexity)] +pub fn fuzzy_search_files( + lua: &Lua, + ( + query, + max_threads, + current_file, + combo_boost_score_multiplier, + min_combo_count, + page_index, + page_size, + ): ( + String, + usize, + Option, + i32, + Option, + Option, + Option, + ), +) -> LuaResult { + let file_picker_guard = FILE_PICKER.read().into_lua_result()?; + let Some(ref picker) = *file_picker_guard else { + return Err(error::to_lua_error(Error::FilePickerMissing)); + }; + + let base_path = picker.base_path(); + let min_combo_count = min_combo_count.unwrap_or(3); + + let query_tracker_guard = QUERY_TRACKER.read().into_lua_result()?; + + if query_tracker_guard.as_ref().is_none() { + tracing::warn!("Query tracker not initialized"); + } + + tracing::debug!( + ?base_path, + ?query, + ?min_combo_count, + ?page_index, + ?page_size, + "Fuzzy search parameters" + ); + + let parsed_query = FFFQuery::parse(&query, FileSearchConfig); + let results = picker.fuzzy_search( + &parsed_query, + query_tracker_guard.as_ref(), + FuzzySearchOptions { + max_threads, + current_file: current_file.as_deref(), + project_path: Some(picker.base_path()), + combo_boost_score_multiplier, + min_combo_count, + pagination: PaginationArgs { + offset: page_index.unwrap_or(0), + limit: page_size.unwrap_or(0), + }, + }, + ); + + if results.items.is_empty() && query.contains(std::path::MAIN_SEPARATOR) { + let pure_query = match &parsed_query.fuzzy_query { + fff_query_parser::FuzzyQuery::Text(t) => t.trim(), + _ => query.trim(), + }; + + let path = expand_tilde(pure_query); + if path.is_absolute() && path.is_file() { + if let Some(found_file) = picker.get_file_by_path(&path) { + let found = SearchResult { + items: vec![found_file], + scores: vec![Score { + exact_match: true, + match_type: "path", + ..Default::default() + }], + total_matched: 1, + total_files: results.total_files, + location: parsed_query.location, + }; + + return lua_types::SearchResultLua::new(found, picker).into_lua(lua); + } + + return build_file_path_fallback(lua, &path, results.total_files); + } + } + + lua_types::SearchResultLua::new(results, picker).into_lua(lua) +} + +#[allow(clippy::type_complexity)] +pub fn fuzzy_search_directories( + lua: &Lua, + (query, max_threads, current_file, page_index, page_size): ( + String, + usize, + Option, + Option, + Option, + ), +) -> LuaResult { + let file_picker_guard = FILE_PICKER.read().into_lua_result()?; + let Some(ref picker) = *file_picker_guard else { + return Err(error::to_lua_error(Error::FilePickerMissing)); + }; + + let parser = QueryParser::new(DirSearchConfig); + let parsed = parser.parse(&query); + + let results = picker.fuzzy_search_directories( + &parsed, + FuzzySearchOptions { + max_threads, + current_file: current_file.as_deref(), + project_path: Some(picker.base_path()), + combo_boost_score_multiplier: 0, + min_combo_count: 0, + pagination: PaginationArgs { + offset: page_index.unwrap_or(0), + limit: page_size.unwrap_or(0), + }, + }, + ); + + lua_types::DirSearchResultLua::new(results, picker).into_lua(lua) +} + +#[allow(clippy::type_complexity)] +pub fn fuzzy_search_mixed( + lua: &Lua, + ( + query, + max_threads, + current_file, + combo_boost_score_multiplier, + min_combo_count, + page_index, + page_size, + ): ( + String, + usize, + Option, + i32, + Option, + Option, + Option, + ), +) -> LuaResult { + let file_picker_guard = FILE_PICKER.read().into_lua_result()?; + let Some(ref picker) = *file_picker_guard else { + return Err(error::to_lua_error(Error::FilePickerMissing)); + }; + + let query_tracker_guard = QUERY_TRACKER.read().into_lua_result()?; + let parser = QueryParser::new(MixedSearchConfig); + let parsed = parser.parse(&query); + + let results = picker.fuzzy_search_mixed( + &parsed, + query_tracker_guard.as_ref(), + FuzzySearchOptions { + max_threads, + current_file: current_file.as_deref(), + project_path: Some(picker.base_path()), + combo_boost_score_multiplier, + min_combo_count: min_combo_count.unwrap_or(3), + pagination: PaginationArgs { + offset: page_index.unwrap_or(0), + limit: page_size.unwrap_or(0), + }, + }, + ); + + lua_types::MixedSearchResultLua::new(results, picker).into_lua(lua) +} + +#[allow(clippy::type_complexity)] +pub fn live_grep( + lua: &Lua, + ( + query, + file_offset, + page_size, + max_file_size, + max_matches_per_file, + smart_case, + grep_mode, + time_budget_ms, + trim_whitespace, + ): ( + String, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ), +) -> LuaResult { + let file_picker_guard = FILE_PICKER.read().into_lua_result()?; + let Some(ref picker) = *file_picker_guard else { + return Err(error::to_lua_error(Error::FilePickerMissing)); + }; + + let parsed_query = FFFQuery::parse(&query, NvimGrepConfig); + let mode = match grep_mode.as_deref() { + Some("regex") => fff::GrepMode::Regex, + Some("fuzzy") => fff::GrepMode::Fuzzy, + _ => fff::GrepMode::PlainText, // "plain" or nil or unknown + }; + + let options = fff::GrepSearchOptions { + max_file_size: max_file_size.unwrap_or(10 * 1024 * 1024), + max_matches_per_file: max_matches_per_file.unwrap_or(200), + smart_case: smart_case.unwrap_or(true), + file_offset: file_offset.unwrap_or(0), + page_limit: page_size.unwrap_or(50), + mode, + time_budget_ms: time_budget_ms.unwrap_or(0), + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: trim_whitespace.unwrap_or(false), + abort_signal: None, + }; + + let result = picker.grep(&parsed_query, &options); + lua_types::GrepResultLua::new(result, picker).into_lua(lua) +} + +/// Build a file-picker result for an absolute path that exists on disk but +/// isn't in the picker index (e.g. file from a different project). +fn build_file_path_fallback(lua: &Lua, path: &Path, total_files: usize) -> LuaResult { + let table = lua.create_table()?; + + let name = path + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_default(); + let path_str = path.to_string_lossy().to_string(); + + let item = lua.create_table()?; + item.set("relative_path", path_str.as_str())?; + item.set("name", name.as_str())?; + item.set("size", path.metadata().map(|m| m.len()).unwrap_or(0))?; + item.set("modified", 0u64)?; + item.set("access_frecency_score", 0i32)?; + item.set("modification_frecency_score", 0i32)?; + item.set("total_frecency_score", 0i32)?; + item.set("git_status", "")?; + item.set("is_binary", false)?; + + let items_table = lua.create_table()?; + items_table.set(1, item)?; + table.set("items", items_table)?; + + let score = lua.create_table()?; + score.set("total", 0)?; + score.set("base_score", 0)?; + score.set("filename_bonus", 0)?; + score.set("special_filename_bonus", 0)?; + score.set("frecency_boost", 0)?; + score.set("git_status_boost", 0)?; + score.set("distance_penalty", 0)?; + score.set("current_file_penalty", 0)?; + score.set("combo_match_boost", 0)?; + score.set("exact_match", true)?; + score.set("match_type", "path")?; + + let scores_table = lua.create_table()?; + scores_table.set(1, score)?; + table.set("scores", scores_table)?; + + table.set("total_matched", 1)?; + table.set("total_files", total_files)?; + + Ok(LuaValue::Table(table)) +} + +pub fn track_access(_: &Lua, file_path: String) -> LuaResult { + // must be async and capture a local copy of the path because the write lock + // is unsafe and can be held forever which is unavoidable, but at least we can + // prevent users from deadlock of the main thread if someone deletes a lock + let file_path = PathBuf::from(&file_path); + std::thread::spawn(move || { + { + let frecency_guard = match FRECENCY.read() { + Ok(g) => g, + Err(e) => { + ::tracing::debug!(?e, "track_access: frecency read lock poisoned"); + return; + } + }; + let Some(ref frecency) = *frecency_guard else { + return; + }; + if let Err(e) = frecency.track_access(file_path.as_path()) { + ::tracing::debug!(?e, ?file_path, "track_access: frecency DB write failed"); + return; + } + } + + let mut file_picker = match FILE_PICKER.write() { + Ok(g) => g, + Err(e) => { + ::tracing::debug!(?e, "track_access: file picker write lock poisoned"); + return; + } + }; + let Some(ref mut picker) = *file_picker else { + return; + }; + + let frecency_guard = match FRECENCY.read() { + Ok(g) => g, + Err(e) => { + ::tracing::debug!(?e, "track_access: frecency read lock poisoned on update"); + return; + } + }; + let Some(ref frecency) = *frecency_guard else { + return; + }; + if let Err(e) = picker.update_single_file_frecency(&file_path, frecency) { + ::tracing::debug!( + ?e, + ?file_path, + "track_access: update_single_file_frecency failed" + ); + } + }); + + Ok(true) +} + +pub fn get_scan_progress(lua: &Lua, _: ()) -> LuaResult { + let file_picker = FILE_PICKER.read().into_lua_result()?; + let picker = file_picker + .as_ref() + .ok_or(Error::FilePickerMissing) + .into_lua_result()?; + let progress = picker.get_scan_progress(); + + let table = lua.create_table()?; + table.set("scanned_files_count", progress.scanned_files_count)?; + table.set("is_scanning", progress.is_scanning)?; + Ok(LuaValue::Table(table)) +} + +pub fn is_scanning(_: &Lua, _: ()) -> LuaResult { + let file_picker = FILE_PICKER.read().into_lua_result()?; + let picker = file_picker + .as_ref() + .ok_or(Error::FilePickerMissing) + .into_lua_result()?; + Ok(picker.is_scan_active()) +} + +pub fn get_git_root(_: &Lua, _: ()) -> LuaResult> { + let file_picker = FILE_PICKER.read().into_lua_result()?; + let Some(ref picker) = *file_picker else { + return Ok(None); + }; + + Ok(picker.git_root().map(|p| p.to_string_lossy().into_owned())) +} + +pub fn refresh_git_status(_: &Lua, _: ()) -> LuaResult { + FILE_PICKER.refresh_git_status(&FRECENCY).into_lua_result() +} + +pub fn get_file_access_count(_: &Lua, file_path: String) -> LuaResult { + let path = PathBuf::from(&file_path); + let frecency_guard = FRECENCY.read().into_lua_result()?; + let Some(ref frecency) = *frecency_guard else { + return Ok(0); + }; + let count = frecency.access_count(&path).into_lua_result()?; + Ok(count as u64) +} + +pub fn update_single_file_frecency(_: &Lua, file_path: String) -> LuaResult { + let frecency_guard = FRECENCY.read().into_lua_result()?; + let Some(ref frecency) = *frecency_guard else { + return Ok(false); + }; + + let mut file_picker = FILE_PICKER.write().into_lua_result()?; + let Some(ref mut picker) = *file_picker else { + return Err(error::to_lua_error(Error::FilePickerMissing)); + }; + + picker + .update_single_file_frecency(&file_path, frecency) + .into_lua_result()?; + Ok(true) +} + +pub fn stop_background_monitor(_: &Lua, _: ()) -> LuaResult { + // `stop_background_monitor` is non-blocking — the debouncer / + // owner threads exit on their next iteration, so it is safe to + // call under the FILE_PICKER write lock. + let mut file_picker = FILE_PICKER.write().into_lua_result()?; + let Some(ref mut picker) = *file_picker else { + return Err(error::to_lua_error(Error::FilePickerMissing)); + }; + picker.stop_background_monitor(); + Ok(true) +} + +pub fn cleanup_file_picker(_: &Lua, _: ()) -> LuaResult { + let mut file_picker = FILE_PICKER.write().into_lua_result()?; + if let Some(picker) = file_picker.take() { + drop(picker); + ::tracing::info!("FilePicker cleanup completed"); + Ok(true) + } else { + Ok(false) + } +} + +pub fn cancel_scan(_: &Lua, _: ()) -> LuaResult { + Ok(true) +} + +pub fn track_query_completion(_: &Lua, (query, file_path): (String, String)) -> LuaResult { + // Everything runs on a background thread — including the + // `FILE_PICKER.read()` used to fetch `project_path`. Doing that read + // on the main (UI) thread stalled nvim whenever a reindex writer was + // in flight: parking_lot's fair queue makes a read block behind a + // pending writer, so the main thread would freeze for the full + // duration of the scan. + let query_tracker = QUERY_TRACKER.clone(); + std::thread::spawn(move || { + let project_path = match FILE_PICKER.read() { + Ok(guard) => match *guard { + Some(ref picker) => picker.base_path().to_path_buf(), + None => return, + }, + Err(_) => return, + }; + + let file_path = match fff::path_utils::canonicalize(&file_path) { + Ok(path) => path, + Err(e) => { + tracing::warn!(?file_path, error = ?e, "Failed to canonicalize file path for tracking"); + return; + } + }; + + if let Ok(mut guard) = query_tracker.write() + && let Some(tracker) = guard.as_mut() + && let Err(e) = tracker.track_query_completion(&query, &project_path, &file_path) + { + tracing::error!( + query = %query, + file = %file_path.display(), + error = ?e, + "Failed to track query completion" + ); + } + }); + + Ok(true) +} + +pub fn get_historical_query(_: &Lua, offset: usize) -> LuaResult> { + let project_path = { + let file_picker = FILE_PICKER.read().into_lua_result()?; + let Some(ref picker) = *file_picker else { + return Ok(None); + }; + picker.base_path().to_path_buf() + }; + + let query_tracker = QUERY_TRACKER.read().into_lua_result()?; + let Some(ref tracker) = *query_tracker else { + return Ok(None); + }; + + tracker + .get_historical_query(&project_path, offset) + .into_lua_result() +} + +pub fn track_grep_query(_: &Lua, query: String) -> LuaResult { + // Move the `FILE_PICKER.read()` into the spawned worker too — + // see the note on `track_query_completion` above for why. + let query_tracker = QUERY_TRACKER.clone(); + std::thread::spawn(move || { + let project_path = match FILE_PICKER.read() { + Ok(guard) => match *guard { + Some(ref picker) => picker.base_path().to_path_buf(), + None => return, + }, + Err(_) => return, + }; + + if let Ok(mut guard) = query_tracker.write() + && let Some(ref mut tracker) = *guard + && let Err(e) = tracker.track_grep_query(&query, &project_path) + { + tracing::error!( + query = %query, + error = ?e, + "Failed to track grep query" + ); + } + }); + + Ok(true) +} + +pub fn get_historical_grep_query(_: &Lua, offset: usize) -> LuaResult> { + let project_path = { + let file_picker = FILE_PICKER.read().into_lua_result()?; + let Some(ref picker) = *file_picker else { + return Ok(None); + }; + picker.base_path().to_path_buf() + }; + + let query_tracker = QUERY_TRACKER.read().into_lua_result()?; + let Some(ref tracker) = *query_tracker else { + return Ok(None); + }; + + tracker + .get_historical_grep_query(&project_path, offset) + .into_lua_result() +} + +/// Parse a grep query string and return its text portion (with constraints stripped). +/// +/// Uses the Neovim grep parser config so filename-constraint detection follows +/// the user's setting, keeping Lua from re-implementing constraint detection. +pub fn parse_grep_query(lua: &Lua, query: String) -> LuaResult { + let parser = QueryParser::new(NvimGrepConfig); + let parsed = parser.parse(&query); + let table = lua.create_table()?; + table.set("grep_text", parsed.grep_text())?; + Ok(table) +} + +pub fn wait_for_initial_scan(_: &Lua, timeout_ms: Option) -> LuaResult { + let scanned = FILE_PICKER.wait_for_scan(Duration::from_millis(timeout_ms.unwrap_or(500))); + + Ok(scanned) +} + +pub fn init_tracing( + _: &Lua, + (log_file_path, log_level, retain_runs): (String, Option, Option), +) -> LuaResult { + crate::log::init_tracing(&log_file_path, log_level.as_deref(), retain_runs) + .map_err(|e| LuaError::RuntimeError(format!("Failed to initialize tracing: {}", e))) +} + +/// Returns health check information including version, git2 status, and repository detection +pub fn health_check(lua: &Lua, test_path: Option) -> LuaResult { + let table = lua.create_table()?; + table.set("version", env!("CARGO_PKG_VERSION"))?; + + let test_path = test_path + .map(PathBuf::from) + .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()); + + let git_info = lua.create_table()?; + let git_version = git2::Version::get(); + let (major, minor, rev) = git_version.libgit2_version(); + let libgit2_version_str = format!("{}.{}.{}", major, minor, rev); + + match git2::Repository::discover(&test_path) { + Ok(repo) => { + git_info.set("available", true)?; + git_info.set("repository_found", true)?; + if let Some(workdir) = repo.workdir() { + git_info.set("workdir", workdir.to_string_lossy().to_string())?; + } + // Get git2 version info + git_info.set("libgit2_version", libgit2_version_str.clone())?; + } + Err(e) => { + git_info.set("available", true)?; + git_info.set("repository_found", false)?; + git_info.set("error", e.message().to_string())?; + git_info.set("libgit2_version", libgit2_version_str)?; + } + } + table.set("git", git_info)?; + + // Check file picker status + let picker_info = lua.create_table()?; + match FILE_PICKER.read() { + Ok(guard) => { + if let Some(ref picker) = *guard { + picker_info.set("initialized", true)?; + picker_info.set( + "base_path", + picker.base_path().to_string_lossy().to_string(), + )?; + picker_info.set("is_scanning", picker.is_scan_active())?; + let progress = picker.get_scan_progress(); + picker_info.set("indexed_files", progress.scanned_files_count)?; + } else { + picker_info.set("initialized", false)?; + } + } + Err(_) => { + picker_info.set("initialized", false)?; + picker_info.set("error", "Failed to acquire file picker lock")?; + } + } + table.set("file_picker", picker_info)?; + + let frecency_info = lua.create_table()?; + match FRECENCY.read() { + Ok(guard) => { + frecency_info.set("initialized", guard.is_some())?; + + if let Some(ref frecency) = *guard { + match frecency.get_health() { + Ok(health) => { + let healthcheck_table = lua.create_table()?; + healthcheck_table.set("path", health.path)?; + healthcheck_table.set("disk_size", health.disk_size)?; + healthcheck_table.set("healthy", health.healthy)?; + for (name, count) in health.entry_counts { + healthcheck_table.set(name, count)?; + } + frecency_info.set("db_healthcheck", healthcheck_table)?; + } + Err(e) => { + frecency_info.set("db_healthcheck_error", e.to_string())?; + } + } + } + } + Err(_) => { + frecency_info.set("initialized", false)?; + frecency_info.set("error", "Failed to acquire frecency lock")?; + } + } + table.set("frecency", frecency_info)?; + + let query_tracker_info = lua.create_table()?; + match QUERY_TRACKER.read() { + Ok(guard) => { + query_tracker_info.set("initialized", guard.is_some())?; + if let Some(ref query_history) = *guard { + match query_history.get_health() { + Ok(health) => { + let healthcheck_table = lua.create_table()?; + healthcheck_table.set("path", health.path)?; + healthcheck_table.set("disk_size", health.disk_size)?; + healthcheck_table.set("healthy", health.healthy)?; + for (name, count) in health.entry_counts { + healthcheck_table.set(name, count)?; + } + query_tracker_info.set("db_healthcheck", healthcheck_table)?; + } + Err(e) => { + query_tracker_info.set("db_healthcheck_error", e.to_string())?; + } + } + } + } + Err(_) => { + query_tracker_info.set("initialized", false)?; + query_tracker_info.set("error", "Failed to acquire query tracker lock")?; + } + } + table.set("query_tracker", query_tracker_info)?; + + Ok(LuaValue::Table(table)) +} + +pub fn shorten_path( + _: &Lua, + (path, max_size, strategy): (String, usize, Option), +) -> LuaResult { + let strategy = strategy + .map(|v| -> LuaResult { + match v { + mlua::Value::String(ref s) => { + let name = s + .to_str() + .map(|s| s.to_owned()) + .unwrap_or_else(|_| "middle_number".to_string()); + Ok(PathShortenStrategy::from_name(&name)) + } + _ => Ok(PathShortenStrategy::default()), + } + }) + .transpose()? + .unwrap_or_default(); + + shorten_path_with_cache(strategy, max_size, Path::new(&path)).map_err(LuaError::RuntimeError) +} + +fn create_exports(lua: &Lua) -> LuaResult { + let exports = lua.create_table()?; + exports.set("init_db", lua.create_function(init_db)?)?; + exports.set( + "destroy_frecency_db", + lua.create_function(destroy_frecency_db)?, + )?; + exports.set("init_file_picker", lua.create_function(init_file_picker)?)?; + exports.set( + "restart_index_in_path", + lua.create_function(restart_index_in_path)?, + )?; + exports.set("scan_files", lua.create_function(scan_files)?)?; + exports.set( + "fuzzy_search_files", + lua.create_function(fuzzy_search_files)?, + )?; + exports.set( + "fuzzy_search_directories", + lua.create_function(fuzzy_search_directories)?, + )?; + exports.set( + "fuzzy_search_mixed", + lua.create_function(fuzzy_search_mixed)?, + )?; + exports.set("live_grep", lua.create_function(live_grep)?)?; + exports.set("track_access", lua.create_function(track_access)?)?; + exports.set( + "get_file_access_count", + lua.create_function(get_file_access_count)?, + )?; + exports.set("cancel_scan", lua.create_function(cancel_scan)?)?; + exports.set("get_scan_progress", lua.create_function(get_scan_progress)?)?; + exports.set( + "refresh_git_status", + lua.create_function(refresh_git_status)?, + )?; + exports.set("get_git_root", lua.create_function(get_git_root)?)?; + exports.set( + "stop_background_monitor", + lua.create_function(stop_background_monitor)?, + )?; + exports.set("init_tracing", lua.create_function(init_tracing)?)?; + exports.set( + "wait_for_initial_scan", + lua.create_function(wait_for_initial_scan)?, + )?; + exports.set( + "cleanup_file_picker", + lua.create_function(cleanup_file_picker)?, + )?; + exports.set("destroy_query_db", lua.create_function(destroy_query_db)?)?; + exports.set( + "track_query_completion", + lua.create_function(track_query_completion)?, + )?; + exports.set( + "get_historical_query", + lua.create_function(get_historical_query)?, + )?; + exports.set("track_grep_query", lua.create_function(track_grep_query)?)?; + exports.set( + "get_historical_grep_query", + lua.create_function(get_historical_grep_query)?, + )?; + exports.set("health_check", lua.create_function(health_check)?)?; + exports.set("shorten_path", lua.create_function(shorten_path)?)?; + exports.set("hex_dump", lua.create_function(hex_dump::hex_dump)?)?; + exports.set("parse_grep_query", lua.create_function(parse_grep_query)?)?; + + Ok(exports) +} + +// https://github.com/mlua-rs/mlua/issues/318 +#[mlua::lua_module(skip_memory_check)] +fn fff_nvim(lua: &Lua) -> LuaResult { + // Install panic hook + SIGSEGV chain handler IMMEDIATELY on module load. + // Without this, a crash inside fff produces a silent nvim death — neovim's + // own handler does not log a Rust-side backtrace. + crate::log::install_panic_hook(); + + create_exports(lua) +} diff --git a/crates/fff-nvim/src/log.rs b/crates/fff-nvim/src/log.rs new file mode 100644 index 0000000..3c1a5c9 --- /dev/null +++ b/crates/fff-nvim/src/log.rs @@ -0,0 +1,3 @@ +//! Logging setup for fff-nvim — delegates to the shared fff-core::log utilities. + +pub use fff::log::{init_tracing, install_panic_hook}; diff --git a/crates/fff-nvim/src/lua_types.rs b/crates/fff-nvim/src/lua_types.rs new file mode 100644 index 0000000..83ef10a --- /dev/null +++ b/crates/fff-nvim/src/lua_types.rs @@ -0,0 +1,281 @@ +use fff::file_picker::FilePicker; +use fff::git::format_git_status; +use fff::{ + DirItem, DirSearchResult, FileItem, GrepResult, Location, MixedItemRef, MixedSearchResult, + Score, SearchResult, +}; +use mlua::prelude::*; + +pub struct SearchResultLua<'a> { + inner: SearchResult<'a>, + picker: &'a FilePicker, +} + +impl<'a> SearchResultLua<'a> { + pub fn new(inner: SearchResult<'a>, picker: &'a FilePicker) -> Self { + Self { inner, picker } + } +} + +pub struct GrepResultLua<'a> { + inner: GrepResult<'a>, + picker: &'a FilePicker, +} + +impl<'a> GrepResultLua<'a> { + pub fn new(inner: GrepResult<'a>, picker: &'a FilePicker) -> Self { + Self { inner, picker } + } +} + +pub struct DirSearchResultLua<'a> { + inner: DirSearchResult<'a>, + picker: &'a FilePicker, +} + +impl<'a> DirSearchResultLua<'a> { + pub fn new(inner: DirSearchResult<'a>, picker: &'a FilePicker) -> Self { + Self { inner, picker } + } +} + +pub struct MixedSearchResultLua<'a> { + inner: MixedSearchResult<'a>, + picker: &'a FilePicker, +} + +impl<'a> MixedSearchResultLua<'a> { + pub fn new(inner: MixedSearchResult<'a>, picker: &'a FilePicker) -> Self { + Self { inner, picker } + } +} + +struct LuaPosition((i32, i32)); + +impl IntoLua for LuaPosition { + fn into_lua(self, lua: &Lua) -> LuaResult { + let table = lua.create_table()?; + table.set("line", self.0.0)?; + table.set("col", self.0.1)?; + Ok(LuaValue::Table(table)) + } +} + +fn file_item_into_lua(item: &FileItem, lua: &Lua, picker: &FilePicker) -> LuaResult { + let table = lua.create_table()?; + table.set("type", "file")?; + table.set("relative_path", item.relative_path(picker))?; + table.set("name", item.file_name(picker))?; + table.set("size", item.size)?; + table.set("modified", item.modified)?; + table.set("access_frecency_score", item.access_frecency_score)?; + table.set( + "modification_frecency_score", + item.modification_frecency_score, + )?; + table.set("total_frecency_score", item.total_frecency_score())?; + table.set("git_status", format_git_status(item.git_status))?; + table.set("is_binary", item.is_binary())?; + Ok(LuaValue::Table(table)) +} + +fn dir_item_into_lua(item: &DirItem, lua: &Lua, picker: &FilePicker) -> LuaResult { + let table = lua.create_table()?; + let name = item + .dir_name(picker) + .trim_end_matches(std::path::MAIN_SEPARATOR) + .trim_end_matches('/') + .to_owned(); + table.set("type", "directory")?; + table.set("relative_path", item.relative_path(picker))?; + table.set("name", name)?; + table.set("max_access_frecency", item.max_access_frecency())?; + Ok(LuaValue::Table(table)) +} + +fn score_into_lua(score: &Score, lua: &Lua) -> LuaResult { + let table = lua.create_table()?; + table.set("total", score.total)?; + table.set("base_score", score.base_score)?; + table.set("filename_bonus", score.filename_bonus)?; + table.set("special_filename_bonus", score.special_filename_bonus)?; + table.set("frecency_boost", score.frecency_boost)?; + table.set("distance_penalty", score.distance_penalty)?; + table.set("current_file_penalty", score.current_file_penalty)?; + table.set("combo_match_boost", score.combo_match_boost)?; + table.set("path_alignment_bonus", score.path_alignment_bonus)?; + table.set("match_type", score.match_type)?; + table.set("exact_match", score.exact_match)?; + Ok(LuaValue::Table(table)) +} + +fn location_into_lua(location: &Location, lua: &Lua) -> LuaResult { + let table = lua.create_table()?; + match location { + Location::Line(line) => { + table.set("line", *line)?; + } + Location::Position { line, col } => { + table.set("line", *line)?; + table.set("col", *col)?; + } + Location::Range { start, end } => { + table.set("start", LuaPosition(*start))?; + table.set("end", LuaPosition(*end))?; + } + } + Ok(LuaValue::Table(table)) +} + +impl IntoLua for SearchResultLua<'_> { + fn into_lua(self, lua: &Lua) -> LuaResult { + let table = lua.create_table()?; + + // Convert items + let items_table = lua.create_table()?; + for (i, item) in self.inner.items.iter().enumerate() { + items_table.set(i + 1, file_item_into_lua(item, lua, self.picker)?)?; + } + table.set("items", items_table)?; + + // Convert scores + let scores_table = lua.create_table()?; + for (i, score) in self.inner.scores.iter().enumerate() { + scores_table.set(i + 1, score_into_lua(score, lua)?)?; + } + table.set("scores", scores_table)?; + + table.set("total_matched", self.inner.total_matched)?; + table.set("total_files", self.inner.total_files)?; + + if let Some(location) = &self.inner.location { + table.set("location", location_into_lua(location, lua)?)?; + } + + Ok(LuaValue::Table(table)) + } +} + +impl IntoLua for DirSearchResultLua<'_> { + fn into_lua(self, lua: &Lua) -> LuaResult { + let table = lua.create_table()?; + + let items_table = lua.create_table()?; + for (i, item) in self.inner.items.iter().enumerate() { + items_table.set(i + 1, dir_item_into_lua(item, lua, self.picker)?)?; + } + table.set("items", items_table)?; + + let scores_table = lua.create_table()?; + for (i, score) in self.inner.scores.iter().enumerate() { + scores_table.set(i + 1, score_into_lua(score, lua)?)?; + } + table.set("scores", scores_table)?; + + table.set("total_matched", self.inner.total_matched)?; + table.set("total_dirs", self.inner.total_dirs)?; + + Ok(LuaValue::Table(table)) + } +} + +impl IntoLua for MixedSearchResultLua<'_> { + fn into_lua(self, lua: &Lua) -> LuaResult { + let table = lua.create_table()?; + + let items_table = lua.create_table()?; + for (i, item) in self.inner.items.iter().enumerate() { + let lua_item = match item { + MixedItemRef::File(file) => file_item_into_lua(file, lua, self.picker)?, + MixedItemRef::Dir(dir) => dir_item_into_lua(dir, lua, self.picker)?, + }; + items_table.set(i + 1, lua_item)?; + } + table.set("items", items_table)?; + + let scores_table = lua.create_table()?; + for (i, score) in self.inner.scores.iter().enumerate() { + scores_table.set(i + 1, score_into_lua(score, lua)?)?; + } + table.set("scores", scores_table)?; + + table.set("total_matched", self.inner.total_matched)?; + table.set("total_files", self.inner.total_files)?; + table.set("total_dirs", self.inner.total_dirs)?; + + if let Some(location) = &self.inner.location { + table.set("location", location_into_lua(location, lua)?)?; + } + + Ok(LuaValue::Table(table)) + } +} + +impl IntoLua for GrepResultLua<'_> { + fn into_lua(self, lua: &Lua) -> LuaResult { + let table = lua.create_table()?; + + // Convert grep match items — each includes file metadata + match metadata + let items_table = lua.create_table()?; + for (i, m) in self.inner.matches.iter().enumerate() { + let item = lua.create_table()?; + + // File metadata from the deduplicated files vec + let file = self.inner.files[m.file_index]; + item.set("relative_path", file.relative_path(self.picker))?; + item.set("name", file.file_name(self.picker))?; + item.set("is_binary", file.is_binary())?; + item.set("git_status", format_git_status(file.git_status))?; + item.set("size", file.size)?; + item.set("modified", file.modified)?; + item.set("total_frecency_score", file.total_frecency_score())?; + item.set("access_frecency_score", file.access_frecency_score)?; + item.set( + "modification_frecency_score", + file.modification_frecency_score, + )?; + + // Match metadata + item.set("line_number", m.line_number)?; + item.set("col", m.col)?; + item.set("byte_offset", m.byte_offset)?; + + // There is a little race window when fff can return matches inside of a non-binary + // classified entities, the window is minimal but it errors out neovim so guard it + let is_binary_content = m.line_content.as_bytes().contains(&0u8); + item.set("is_binary_content", is_binary_content)?; + item.set("line_content", m.line_content.as_str())?; + + // Match byte ranges within line_content + let ranges = lua.create_table()?; + for (j, &(start, end)) in m.match_byte_offsets.iter().enumerate() { + let range = lua.create_table()?; + range.set(1, start)?; + range.set(2, end)?; + ranges.set(j + 1, range)?; + } + item.set("match_ranges", ranges)?; + + // Fuzzy match score (only set in fuzzy grep mode, nil otherwise) + if let Some(score) = m.fuzzy_score { + item.set("fuzzy_score", score)?; + } + + items_table.set(i + 1, item)?; + } + table.set("items", items_table)?; + + table.set("total_matched", self.inner.matches.len())?; + table.set("total_files_searched", self.inner.total_files_searched)?; + table.set("total_files", self.inner.total_files)?; + table.set("filtered_file_count", self.inner.filtered_file_count)?; + table.set("next_file_offset", self.inner.next_file_offset)?; + + // Pass regex fallback error to Lua (nil if no error) + if let Some(ref err) = self.inner.regex_fallback_error { + table.set("regex_fallback_error", err.as_str())?; + } + + Ok(LuaValue::Table(table)) + } +} diff --git a/crates/fff-nvim/src/path_shortening.rs b/crates/fff-nvim/src/path_shortening.rs new file mode 100644 index 0000000..2252cd1 --- /dev/null +++ b/crates/fff-nvim/src/path_shortening.rs @@ -0,0 +1,745 @@ +//! Path shortening utilities for display in Neovim UI +//! +//! This module provides functionality to shorten file paths for display +//! in the picker UI with various strategies. + +use once_cell::sync::Lazy; +use std::borrow::Cow; +use std::path::{Component, MAIN_SEPARATOR, Path, PathBuf}; +use std::sync::RwLock; + +#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)] +pub enum PathShortenStrategy { + #[default] + MiddleNumber, + Middle, + End, + Start, +} + +struct CacheEntry { + shortened: String, + max_size: usize, + strategy: PathShortenStrategy, +} + +struct PathCache { + map: ahash::AHashMap, + max_entries: usize, +} + +impl PathCache { + fn new(max_entries: usize) -> Self { + Self { + map: ahash::AHashMap::with_capacity(max_entries), + max_entries, + } + } + + #[tracing::instrument(skip(self), fields(path = %path.display(), max_size), level = tracing::Level::TRACE)] + fn get(&self, path: &Path, max_size: usize, strategy: PathShortenStrategy) -> Option<&str> { + self.map.get(path).and_then(|entry| { + // Only return cached value if both max_size and strategy match + if entry.max_size == max_size && entry.strategy == strategy { + Some(entry.shortened.as_str()) + } else { + None + } + }) + } + + fn insert( + &mut self, + path: PathBuf, + shortened: String, + max_size: usize, + strategy: PathShortenStrategy, + ) { + // Simple eviction: clear half the cache when full + if self.map.len() >= self.max_entries { + let keys_to_remove: Vec<_> = self + .map + .keys() + .take(self.max_entries / 2) + .cloned() + .collect(); + for key in keys_to_remove { + self.map.remove(&key); + } + } + self.map.insert( + path, + CacheEntry { + shortened, + max_size, + strategy, + }, + ); + } +} + +// this is the amount of PATHS not entries +const DEFAULT_CACHE_SIZE: usize = 8192; + +static PATH_SHORTEN_CACHE: Lazy> = + Lazy::new(|| RwLock::new(PathCache::new(DEFAULT_CACHE_SIZE))); + +pub fn shorten_path_with_cache( + strategy: PathShortenStrategy, + max_size: usize, + path: &Path, +) -> Result { + { + let cache = PATH_SHORTEN_CACHE + .read() + .map_err(|_| "Failed to acquire path cache lock".to_string())?; + if let Some(cached) = cache.get(path, max_size, strategy) { + tracing::trace!("Cache hit for path '{}'", path.display()); + return Ok(cached.to_string()); + } + } + + let shortened = strategy.shorten_path(path, max_size); + { + let mut cache = PATH_SHORTEN_CACHE + .write() + .map_err(|_| "Failed to acquire path cache lock".to_string())?; + cache.insert(path.to_path_buf(), shortened.clone(), max_size, strategy); + } + + Ok(shortened) +} + +impl PathShortenStrategy { + /// Parse a strategy from a string name + pub fn from_name(name: &str) -> Self { + match name { + "middle_number" => PathShortenStrategy::MiddleNumber, + "middle" => PathShortenStrategy::Middle, + "end" => PathShortenStrategy::End, + "start" => PathShortenStrategy::Start, + _ => PathShortenStrategy::MiddleNumber, + } + } +} + +impl PathShortenStrategy { + pub fn shorten_path(&self, path: &Path, max_size: usize) -> String { + const MIN_SMART_SHORTEN_SIZE: usize = 8; + + let sep = MAIN_SEPARATOR; + + let path_str = path.to_string_lossy(); + if path_str.len() <= max_size { + return path_str.to_string(); + } + + // If max_size is too small for smart shortening, just truncate + if max_size < MIN_SMART_SHORTEN_SIZE { + return match self { + PathShortenStrategy::Start => Self::truncate_str_keep_end(&path_str, max_size), + _ => Self::truncate_str(&path_str, max_size), + }; + } + + let components: Vec<&str> = path + .components() + .filter_map(|c| match c { + Component::Normal(s) => s.to_str(), + _ => None, + }) + .collect(); + + if components.is_empty() { + return path_str.to_string(); + } + + // For single component, just truncate it + if components.len() == 1 { + return match self { + PathShortenStrategy::Start => Self::truncate_str_keep_end(components[0], max_size), + _ => Self::truncate_str(components[0], max_size), + }; + } + + match self { + PathShortenStrategy::End => { + // Simple truncation from the end + let mut result = String::new(); + + for (i, component) in components.iter().enumerate() { + let candidate = if i == 0 { + component.to_string() + } else { + format!("{}{}{}", result, sep, component) + }; + + if candidate.len() <= max_size { + result = candidate; + } else { + break; + } + } + + // If even the first component is too long, truncate it + if result.is_empty() && !components.is_empty() { + return components.first().map_or(String::new(), |component| { + let mut component = component.to_string(); + + component.truncate(max_size); + component + }); + } + + result + } + PathShortenStrategy::Middle | PathShortenStrategy::MiddleNumber => { + let use_number = matches!(self, PathShortenStrategy::MiddleNumber); + self.shorten_middle(&components, max_size, use_number, sep) + } + PathShortenStrategy::Start => Self::shorten_start(&components, max_size, sep), + } + } + + // rust doesn't have an ergonomic way to clone and truncate + fn truncate_str(s: &str, max_len: usize) -> String { + if max_len == 0 { + return String::new(); + } + + let char_count = s.chars().count(); + if char_count <= max_len { + return s.to_string(); + } + + // Just take the first max_len characters - no ".." suffix + s.chars().take(max_len).collect() + } + + fn truncate_str_keep_end(s: &str, max_len: usize) -> String { + if max_len == 0 { + return String::new(); + } + + let char_count = s.chars().count(); + if char_count <= max_len { + return s.to_string(); + } + + s.chars().skip(char_count - max_len).collect() + } + + // e.g. ".../parts/ai_extracted.rs" + fn shorten_start(components: &[&str], max_size: usize, sep: char) -> String { + let total = components.len(); + let last = components[total - 1]; + + // Try smallest hidden_count first to maximize shown context. + for hidden in 1..total { + let right_parts = &components[hidden..]; + let ellipsis = Self::make_ellipsis(hidden, false); + + let right_content_len: usize = right_parts.iter().map(|s| s.len()).sum(); + let right_seps = right_parts.len().saturating_sub(1); + let total_len = ellipsis.len() + 1 + right_content_len + right_seps; + + if total_len <= max_size { + let mut result = String::with_capacity(total_len); + result.push_str(&ellipsis); + result.push(sep); + for (i, part) in right_parts.iter().enumerate() { + if i > 0 { + result.push(sep); + } + result.push_str(part); + } + return result; + } + } + + // Even ellipsis + last won't fit: show the end of the last component. + Self::truncate_str_keep_end(last, max_size) + } + + fn shorten_middle( + &self, + components: &[&str], + max_size: usize, + use_number: bool, + sep: char, + ) -> String { + let total = components.len(); + + // For 2 components, just show both or truncate to fit + if total <= 2 { + let joined = components.join(&sep.to_string()); + if joined.len() <= max_size { + return joined; + } + // Try to keep last intact, truncate first + let last = components[total - 1]; + let available_for_first = max_size.saturating_sub(1 + last.len()); // sep + last + if available_for_first > 0 && last.len() < max_size { + let truncated = Self::truncate_str(components[0], available_for_first); + let mut result = String::with_capacity(truncated.len() + 1 + last.len()); + result.push_str(&truncated); + result.push(sep); + result.push_str(last); + return result; + } + // Last component alone exceeds max_size, must truncate it + return Self::truncate_str(last, max_size); + } + + let first = components[0]; + let last = components[total - 1]; + + let initial_hidden = total - 2; + let ellipsis = Self::make_ellipsis(initial_hidden, use_number); + + // Minimum pattern: first/.../last + let min_overhead = 2 + ellipsis.len(); // two separators + ellipsis + let min_content = first.len() + last.len(); + + if min_content + min_overhead <= max_size { + // We can fit first/.../last, now try to add more components + return self.expand_middle(components, max_size, use_number, sep); + } + + // Need to truncate to fit max_size + // Priority: keep last intact if possible, truncate first, then truncate last if needed + let needed_for_last = last.len() + 1 + ellipsis.len() + 1; // sep + ellipsis + sep + last + if needed_for_last <= max_size { + let available_for_first = max_size - needed_for_last; + let truncated_first = Self::truncate_str(first, available_for_first); + let ellipsis = Self::make_ellipsis(initial_hidden, use_number); + // truncated_first + sep + ellipsis + sep + last + let capacity = truncated_first.len() + 1 + ellipsis.len() + 1 + last.len(); + let mut result = String::with_capacity(capacity); + result.push_str(&truncated_first); + result.push(sep); + result.push_str(&ellipsis); + result.push(sep); + result.push_str(last); + return result; + } + + let needed_for_ellipsis_last = ellipsis.len() + 1 + last.len(); // ellipsis + sep + last + if needed_for_ellipsis_last <= max_size { + let mut result = String::with_capacity(needed_for_ellipsis_last); + result.push_str(&ellipsis); + result.push(sep); + result.push_str(last); + return result; + } + + // Can't fit ellipsis + last, just show as much of last as possible + Self::truncate_str(last, max_size) + } + + fn expand_middle( + &self, + components: &[&str], + max_size: usize, + use_number: bool, + sep: char, + ) -> String { + let total = components.len(); + + // Start with minimum: first/...or..N../last + let mut left_end = 1; // exclusive index for left components + let mut right_start = total - 1; // inclusive index for right components + + // Try to add more components from both sides + loop { + if right_start <= left_end { + break; + } + + let mut added = false; + + // Try adding from RIGHT first (to show more context near the file) + if right_start > left_end + 1 { + let hidden = right_start - 1 - left_end; + let candidate = Self::build_middle_result( + components, + left_end, + right_start - 1, + hidden, + use_number, + sep, + ); + if candidate.len() <= max_size { + right_start -= 1; + added = true; + } + } + + // Try adding from LEFT + if left_end < right_start - 1 { + let hidden = right_start - (left_end + 1); + let candidate = Self::build_middle_result( + components, + left_end + 1, + right_start, + hidden, + use_number, + sep, + ); + if candidate.len() <= max_size { + left_end += 1; + added = true; + } + } + + if !added { + break; + } + } + + let hidden = right_start - left_end; + Self::build_middle_result(components, left_end, right_start, hidden, use_number, sep) + } + + fn build_middle_result( + components: &[&str], + left_end: usize, + right_start: usize, + hidden_count: usize, + use_number: bool, + sep: char, + ) -> String { + let ellipsis = Self::make_ellipsis(hidden_count, use_number); + + let left_parts = &components[..left_end]; + let right_parts = &components[right_start..]; + + // Pre-calculate capacity + let left_len: usize = left_parts.iter().map(|s| s.len()).sum(); + let right_len: usize = right_parts.iter().map(|s| s.len()).sum(); + let left_seps = if left_parts.is_empty() { + 0 + } else { + left_parts.len() - 1 + }; + let right_seps = if right_parts.is_empty() { + 0 + } else { + right_parts.len() - 1 + }; + // +2 for separators around ellipsis (or +1 if left is empty) + let extra_seps = if left_parts.is_empty() { 1 } else { 2 }; + let capacity = left_len + right_len + left_seps + right_seps + ellipsis.len() + extra_seps; + + let mut result = String::with_capacity(capacity); + + // Build left part + for (i, part) in left_parts.iter().enumerate() { + if i > 0 { + result.push(sep); + } + result.push_str(part); + } + + // Add separator before ellipsis (only if left is not empty) + if !left_parts.is_empty() { + result.push(sep); + } + + // Add ellipsis + result.push_str(&ellipsis); + + // Add separator after ellipsis + result.push(sep); + + // Build right part + for (i, part) in right_parts.iter().enumerate() { + if i > 0 { + result.push(sep); + } + result.push_str(part); + } + + result + } + + fn make_ellipsis(hidden_count: usize, use_number: bool) -> Cow<'static, str> { + match hidden_count { + 1 => ".".into(), + 2 => "..".into(), + 3 if use_number => "...".into(), + n if use_number => format!(".{}.", n).into(), + _ => "...".into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path_shorten_strategy_middle() { + // Test with directory paths (not file paths) - this is what Lua passes + let path = Path::new("core_workflow_service/db/model/parts/ai_extracted"); + + // With 25 chars, first component must be truncated + // "core_workflow_service" is 21 chars, so we need to truncate it + let shortened = PathShortenStrategy::Middle.shorten_path(path, 25); + assert!( + shortened.len() <= 25, + "Result '{}' should be <= 25 chars", + shortened + ); + assert!(shortened.contains("..."), "Should contain ellipsis"); + assert!( + shortened.ends_with("ai_extracted"), + "Should end with last component" + ); + + // With 45 chars, can fit more without truncation + let shortened = PathShortenStrategy::Middle.shorten_path(path, 45); + assert!(shortened.len() <= 45); + assert!(shortened.starts_with("core_workflow_service")); + + // Shorter path that fits better + let path2 = Path::new("src/components/ui/buttons"); + let shortened = PathShortenStrategy::Middle.shorten_path(path2, 20); + assert!( + shortened.len() <= 20, + "Result '{}' should be <= 20 chars", + shortened + ); + + // Very small max_size - should still produce something reasonable + let shortened = PathShortenStrategy::Middle.shorten_path(path2, 10); + assert!( + shortened.len() <= 10, + "Result '{}' should be <= 10 chars", + shortened + ); + } + + #[test] + fn test_path_shroten_strategy_middle_number() { + // Test with directory paths (not file paths) + // middle_number uses dots for 1-3 hidden, numbers for 4+ + + // Path with only 2 hidden segments - should use dots + let path = Path::new("core_workflow_service/graphql/types/parts"); + let shortened = PathShortenStrategy::MiddleNumber.shorten_path(path, 40); + assert!( + shortened.len() <= 40, + "Result '{}' should be <= 40 chars", + shortened + ); + // With only 2 hidden, should use dots not numbers + assert!( + shortened.contains('.'), + "Should contain dots, got '{}'", + shortened + ); + + // Path with many segments, small space - should use .N. format when 4+ hidden + let path2 = Path::new("a/b/c/d/e/f/g/h/i/j"); + let shortened = PathShortenStrategy::MiddleNumber.shorten_path(path2, 12); + assert!( + shortened.len() <= 12, + "Result '{}' should be <= 12 chars", + shortened + ); + // With 8 hidden (showing only a and j), should show number + assert!( + shortened.contains('.') && shortened.chars().any(|c| c.is_ascii_digit()), + "Should contain .N. pattern for 4+ hidden, got '{}'", + shortened + ); + + // Very small max_size + let shortened = PathShortenStrategy::MiddleNumber.shorten_path(path2, 5); + assert!( + shortened.len() <= 5, + "Result '{}' should be <= 5 chars", + shortened + ); + } + + #[test] + fn test_path_shroten_strategy_end() { + let path = Path::new("core_workflow_service/db/model/parts/ai_extracted"); + let shortened = PathShortenStrategy::End.shorten_path(path, 25); + assert!(shortened.len() <= 25); + assert!(shortened.starts_with("core_workflow_service")); + + // Shorter constraint - truncates first component + let shortened = PathShortenStrategy::End.shorten_path(path, 15); + assert!( + shortened.len() <= 15, + "Result '{}' should be <= 15 chars", + shortened + ); + } + + #[test] + fn test_path_shorten_strategy_start() { + let path = Path::new("core_workflow_service/db/model/parts/ai_extracted"); + + // Ample space - full path fits + let shortened = PathShortenStrategy::Start.shorten_path(path, 100); + assert_eq!( + shortened, + "core_workflow_service/db/model/parts/ai_extracted" + ); + + // 25 chars - should drop first components, keep tail intact + let shortened = PathShortenStrategy::Start.shorten_path(path, 25); + assert!( + shortened.len() <= 25, + "Result '{}' should be <= 25 chars", + shortened + ); + assert!( + shortened.ends_with("ai_extracted"), + "Should preserve last component, got '{}'", + shortened + ); + assert!( + shortened.starts_with('.'), + "Should start with ellipsis, got '{}'", + shortened + ); + + // Tight space where ellipsis + last won't fit: should still end with + // (a suffix of) the last component, never any leading content. + let shortened = PathShortenStrategy::Start.shorten_path(path, 8); + assert!(shortened.len() <= 8); + assert!( + "ai_extracted".ends_with(shortened.as_str()), + "Tight start shortening must keep end of last component, got '{}'", + shortened + ); + + // Single long component falls back to keeping the end + let path2 = Path::new("very_long_directory_name_with_many_chars"); + let shortened = PathShortenStrategy::Start.shorten_path(path2, 10); + assert_eq!(shortened.len(), 10); + assert!( + "very_long_directory_name_with_many_chars".ends_with(shortened.as_str()), + "Should keep end of single component, got '{}'", + shortened + ); + } + + #[test] + fn test_shorten_path_caching() { + let path = Path::new("home/user/projects/rust/project/src/components/ui"); + + // First call should compute and cache + let result1 = shorten_path_with_cache(PathShortenStrategy::MiddleNumber, 25, path).unwrap(); + + // Second call should hit cache + let result2 = shorten_path_with_cache(PathShortenStrategy::MiddleNumber, 25, path).unwrap(); + + assert_eq!(result1, result2); + + // Different max_size should produce different result (more space = longer result) + let result3 = shorten_path_with_cache(PathShortenStrategy::MiddleNumber, 50, path).unwrap(); + assert!( + result3.len() >= result1.len(), + "More space should allow longer result" + ); + + // Different strategy must NOT return the cached MiddleNumber result. + let result_start = shorten_path_with_cache(PathShortenStrategy::Start, 25, path).unwrap(); + let direct_start = PathShortenStrategy::Start.shorten_path(path, 25); + assert_eq!( + result_start, direct_start, + "Cache must be keyed on strategy, not return stale result" + ); + } + + #[test] + fn test_path_always_fits_max_size() { + // Path must ALWAYS fit within max_size - this is a strict requirement + let paths = [ + "core_workflow_service/db/model/parts/ai_extracted", + "home/user/projects/rust/project/src", + "a/b/c/d/e/f/g/h", + "very_long_directory_name/another_long_one/and_more", + ]; + + for path_str in paths { + let path = Path::new(path_str); + for max_size in [10, 15, 20, 25, 30, 40, 50] { + let shortened = PathShortenStrategy::MiddleNumber.shorten_path(path, max_size); + assert!( + shortened.len() <= max_size, + "Path '{}' with max_size {} produced '{}' ({} chars)", + path_str, + max_size, + shortened, + shortened.len() + ); + + let shortened = PathShortenStrategy::Middle.shorten_path(path, max_size); + assert!( + shortened.len() <= max_size, + "Path '{}' with max_size {} produced '{}' ({} chars)", + path_str, + max_size, + shortened, + shortened.len() + ); + + let shortened = PathShortenStrategy::Start.shorten_path(path, max_size); + assert!( + shortened.len() <= max_size, + "Path '{}' with max_size {} produced '{}' ({} chars)", + path_str, + max_size, + shortened, + shortened.len() + ); + } + } + } + + #[test] + fn test_small_max_size_simple_truncation() { + // When max_size is very small (< MIN_SMART_SHORTEN_SIZE), should just truncate + let path = Path::new("core_workflow_service/db/model/parts"); + + // With max_size=6, should just truncate (below threshold) + let shortened = PathShortenStrategy::MiddleNumber.shorten_path(path, 6); + assert_eq!(shortened.len(), 6); + assert_eq!(shortened, "core_w"); + + // With max_size=10, smart shortening kicks in + let shortened = PathShortenStrategy::Middle.shorten_path(path, 10); + assert!(shortened.len() <= 10); + } + + #[test] + fn test_prioritizes_last_component() { + // When space allows, last component should be shown in full + let path = Path::new("first/medium/last_component"); + + // With enough space, last component should be intact + let shortened = PathShortenStrategy::MiddleNumber.shorten_path(path, 25); + assert!( + shortened.ends_with("last_component"), + "Should preserve last component when space allows, got '{}'", + shortened + ); + assert!(shortened.len() <= 25); + + // When space is too tight, last component may be truncated to fit + let shortened = PathShortenStrategy::MiddleNumber.shorten_path(path, 10); + assert!( + shortened.len() <= 10, + "Must fit within max_size, got '{}' ({} chars)", + shortened, + shortened.len() + ); + } +} diff --git a/crates/fff-nvim/src/user_config.rs b/crates/fff-nvim/src/user_config.rs new file mode 100644 index 0000000..0c8d7e5 --- /dev/null +++ b/crates/fff-nvim/src/user_config.rs @@ -0,0 +1,47 @@ +use fff::{GrepConfig, ParserConfig}; +use std::sync::RwLock; + +#[derive(Debug, Clone, Copy, Default)] +pub struct UserConfigOptions { + pub enable_filename_constraint: bool, +} + +static USER_CONFIG: RwLock = RwLock::new(UserConfigOptions { + enable_filename_constraint: false, +}); + +pub fn set_global_user_config(config: UserConfigOptions) { + if let Ok(mut guard) = USER_CONFIG.write() { + *guard = config; + } +} + +fn get_global_user_config() -> UserConfigOptions { + USER_CONFIG.read().map(|c| *c).unwrap_or_default() +} + +/// Grep query parser config for Neovim configured by user +/// Same as `GrepConfig` but lets the user configure some behavior of query parsing +pub struct NvimGrepConfig; + +impl ParserConfig for NvimGrepConfig { + fn enable_path_segments(&self) -> bool { + true + } + + fn enable_git_status(&self) -> bool { + false + } + + fn enable_location(&self) -> bool { + false + } + + fn enable_filename_constraint(&self) -> bool { + get_global_user_config().enable_filename_constraint + } + + fn is_glob_pattern(&self, token: &str) -> bool { + GrepConfig.is_glob_pattern(token) + } +} diff --git a/crates/fff-python/Cargo.toml b/crates/fff-python/Cargo.toml new file mode 100644 index 0000000..8112f10 --- /dev/null +++ b/crates/fff-python/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "fff-python" +version = "0.9.6" +edition = "2024" + +[lib] +name = "fff_python" +crate-type = ["cdylib"] + +[features] +# Pure-Rust walker by default; zlob is opt-in (needs Zig). +default = ["ripgrep"] +ripgrep = ["fff/ripgrep", "fff-query-parser/ripgrep"] +zlob = ["fff/zlob", "fff-query-parser/zlob"] + +[dependencies] +fff = { package = "fff-search", path = "../fff-core", version = "0.9.6", default-features = false } +fff-query-parser = { path = "../fff-query-parser", version = "0.9.6", default-features = false } +git2 = { workspace = true } +pyo3 = { version = "0.24.0", features = ["extension-module", "abi3-py310"] } diff --git a/crates/fff-python/src/conversions.rs b/crates/fff-python/src/conversions.rs new file mode 100644 index 0000000..1656632 --- /dev/null +++ b/crates/fff-python/src/conversions.rs @@ -0,0 +1,107 @@ +use fff::file_picker::FilePicker; + +use crate::types::{DirItem, FileItem, GrepMatch, MatchRange, MixedDirItem, MixedFileItem, Score}; + +pub enum MixedItem { + File(MixedFileItem), + Dir(MixedDirItem), +} + +impl From<&fff::Score> for Score { + fn from(s: &fff::Score) -> Self { + Self { + total: s.total, + base_score: s.base_score, + filename_bonus: s.filename_bonus, + special_filename_bonus: s.special_filename_bonus, + frecency_boost: s.frecency_boost, + distance_penalty: s.distance_penalty, + current_file_penalty: s.current_file_penalty, + combo_match_boost: s.combo_match_boost, + path_alignment_bonus: s.path_alignment_bonus, + exact_match: s.exact_match, + match_type: s.match_type.to_string(), + } + } +} + +impl From<(&fff::FileItem, &FilePicker)> for FileItem { + fn from((item, picker): (&fff::FileItem, &FilePicker)) -> Self { + Self { + relative_path: item.relative_path(picker), + file_name: item.file_name(picker), + git_status: fff::git::format_git_status(item.git_status).to_string(), + size: item.size, + modified: item.modified, + access_frecency_score: item.access_frecency_score as i64, + modification_frecency_score: item.modification_frecency_score as i64, + total_frecency_score: item.total_frecency_score() as i64, + is_binary: item.is_binary(), + } + } +} + +impl From<(&fff::DirItem, &FilePicker)> for DirItem { + fn from((item, picker): (&fff::DirItem, &FilePicker)) -> Self { + Self { + relative_path: item.relative_path(picker), + dir_name: item.dir_name(picker), + max_access_frecency: item.max_access_frecency(), + } + } +} + +impl From<(&fff::FileItem, &FilePicker)> for MixedFileItem { + fn from((item, picker): (&fff::FileItem, &FilePicker)) -> Self { + Self { + relative_path: item.relative_path(picker), + file_name: item.file_name(picker), + git_status: fff::git::format_git_status(item.git_status).to_string(), + size: item.size, + modified: item.modified, + access_frecency_score: item.access_frecency_score as i64, + modification_frecency_score: item.modification_frecency_score as i64, + total_frecency_score: item.total_frecency_score() as i64, + is_binary: item.is_binary(), + } + } +} + +impl From<(&fff::DirItem, &FilePicker)> for MixedDirItem { + fn from((item, picker): (&fff::DirItem, &FilePicker)) -> Self { + Self { + relative_path: item.relative_path(picker), + dir_name: item.dir_name(picker), + max_access_frecency: item.max_access_frecency(), + } + } +} + +impl From<(&fff::GrepMatch, &fff::FileItem, &FilePicker)> for GrepMatch { + fn from((m, file, picker): (&fff::GrepMatch, &fff::FileItem, &FilePicker)) -> Self { + Self { + relative_path: file.relative_path(picker), + file_name: file.file_name(picker), + git_status: fff::git::format_git_status(file.git_status).to_string(), + line_content: m.line_content.clone(), + match_ranges: m + .match_byte_offsets + .iter() + .map(|&(s, e)| MatchRange { start: s, end: e }) + .collect(), + context_before: m.context_before.clone(), + context_after: m.context_after.clone(), + size: file.size, + modified: file.modified, + total_frecency_score: file.total_frecency_score() as i64, + access_frecency_score: file.access_frecency_score as i64, + modification_frecency_score: file.modification_frecency_score as i64, + line_number: m.line_number, + byte_offset: m.byte_offset, + col: m.col as u32, + fuzzy_score: m.fuzzy_score, + is_definition: m.is_definition, + is_binary: file.is_binary(), + } + } +} diff --git a/crates/fff-python/src/finder.rs b/crates/fff-python/src/finder.rs new file mode 100644 index 0000000..3384ae7 --- /dev/null +++ b/crates/fff-python/src/finder.rs @@ -0,0 +1,982 @@ +use std::path::{Path, PathBuf}; +use std::time::Duration; + +use fff::file_picker::FilePicker; +use fff::frecency::FrecencyTracker; +use fff::query_tracker::QueryTracker; +use fff::{ + FFFMode, FilePickerOptions, FuzzySearchOptions, GrepSearchOptions, PaginationArgs, QueryParser, + SharedFilePicker, SharedFrecency, SharedQueryTracker, +}; +use pyo3::prelude::*; +use pyo3::types::PyDict; + +use crate::conversions::MixedItem; +use crate::types::{ + DirItem, DirSearchResult, FileItem, GrepCursor, GrepMatch, GrepResult, MixedDirItem, + MixedFileItem, MixedSearchResult, ScanProgress, Score, SearchResult, +}; +use crate::{parse_grep_mode, py_err}; + +const DEFAULT_SEARCH_PAGE_SIZE: usize = 100; +// Sentinel-to-default conversion for combo boosting, mirroring the C/Node +// bindings: `0` means "use the engine default", not "disable". +const DEFAULT_COMBO_BOOST_MULTIPLIER: i32 = 100; +const DEFAULT_MIN_COMBO_COUNT: u32 = 3; + +fn defaulted_usize(value: u32, default: usize) -> usize { + if value == 0 { default } else { value as usize } +} + +fn defaulted_u64(value: u64, default: u64) -> u64 { + if value == 0 { default } else { value } +} + +fn defaulted_i32(value: i32, default: i32) -> i32 { + if value == 0 { default } else { value } +} + +fn defaulted_u32(value: u32, default: u32) -> u32 { + if value == 0 { default } else { value } +} + +fn create_parent_dir(path: &Path) -> PyResult<()> { + if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) { + std::fs::create_dir_all(parent).map_err(py_err)?; + } + Ok(()) +} + +fn pagination_args(page_index: u32, page_size: u32) -> PaginationArgs { + PaginationArgs { + offset: page_index as usize, + limit: defaulted_usize(page_size, DEFAULT_SEARCH_PAGE_SIZE), + } +} + +fn fuzzy_options<'a>( + max_threads: u32, + current_file: Option<&'a str>, + project_path: &'a Path, + page_index: u32, + page_size: u32, + combo_boost_score_multiplier: i32, + min_combo_count: u32, +) -> FuzzySearchOptions<'a> { + FuzzySearchOptions { + max_threads: max_threads as usize, + current_file, + project_path: Some(project_path), + combo_boost_score_multiplier, + min_combo_count, + pagination: pagination_args(page_index, page_size), + } +} + +#[allow(clippy::too_many_arguments)] +fn grep_options( + mode: fff::GrepMode, + cursor_offset: usize, + max_file_size: u64, + max_matches_per_file: u32, + smart_case: bool, + page_limit: u32, + time_budget_ms: u64, + before_context: u32, + after_context: u32, + classify_definitions: bool, +) -> GrepSearchOptions { + let defaults = GrepSearchOptions::default(); + GrepSearchOptions { + max_file_size: defaulted_u64(max_file_size, defaults.max_file_size), + max_matches_per_file: max_matches_per_file as usize, + smart_case, + file_offset: cursor_offset, + page_limit: defaulted_usize(page_limit, defaults.page_limit), + mode, + time_budget_ms, + before_context: before_context as usize, + after_context: after_context as usize, + classify_definitions, + trim_whitespace: false, + abort_signal: None, + } +} + +fn convert_scores(scores: &[fff::Score]) -> Vec { + scores.iter().map(Score::from).collect() +} + +fn convert_grep_result(result: fff::grep::GrepResult<'_>, picker: &FilePicker) -> GrepResult { + let items = result + .matches + .iter() + .map(|m| GrepMatch::from((m, result.files[m.file_index], picker))) + .collect(); + + GrepResult { + items, + total_matched: result.matches.len() as u32, + total_files_searched: result.total_files_searched as u32, + total_files: result.total_files as u32, + filtered_file_count: result.filtered_file_count as u32, + next_file_offset: result.next_file_offset as u32, + regex_fallback_error: result.regex_fallback_error, + } +} + +fn clear_shared_state( + picker: &SharedFilePicker, + frecency: &SharedFrecency, + query_tracker: &SharedQueryTracker, +) { + if let Ok(mut guard) = picker.write() { + guard.take(); + } + if let Ok(mut guard) = frecency.write() { + *guard = None; + } + if let Ok(mut guard) = query_tracker.write() { + *guard = None; + } +} + +#[pyclass(subclass)] +pub struct FileFinder { + picker: SharedFilePicker, + frecency: SharedFrecency, + query_tracker: SharedQueryTracker, + cache_budget_max_files: usize, + cache_budget_max_bytes: u64, + cache_budget_max_file_size: u64, +} + +impl Drop for FileFinder { + fn drop(&mut self) { + clear_shared_state(&self.picker, &self.frecency, &self.query_tracker); + } +} + +#[pymethods] +impl FileFinder { + #[new] + #[pyo3(signature = ( + base_path, + *, + frecency_db_path=None, + history_db_path=None, + enable_mmap_cache=true, + enable_content_indexing=true, + watch=true, + ai_mode=false, + log_file_path=None, + log_level=None, + cache_budget_max_files=0, + cache_budget_max_bytes=0, + cache_budget_max_file_size=0, + enable_fs_root_scanning=false, + enable_home_dir_scanning=false, + follow_symlinks=false, + ))] + #[allow(clippy::too_many_arguments)] + fn new( + py: Python<'_>, + base_path: PathBuf, + frecency_db_path: Option, + history_db_path: Option, + enable_mmap_cache: bool, + enable_content_indexing: bool, + watch: bool, + ai_mode: bool, + log_file_path: Option, + log_level: Option, + cache_budget_max_files: u64, + cache_budget_max_bytes: u64, + cache_budget_max_file_size: u64, + enable_fs_root_scanning: bool, + enable_home_dir_scanning: bool, + follow_symlinks: bool, + ) -> PyResult { + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + let query_tracker = SharedQueryTracker::default(); + let cache_budget_max_files = cache_budget_max_files as usize; + + let init_picker = shared_picker.clone(); + let init_frecency = shared_frecency.clone(); + let init_query_tracker = query_tracker.clone(); + + py.allow_threads(move || -> PyResult<()> { + if let Some(path) = frecency_db_path { + create_parent_dir(&path)?; + let tracker = FrecencyTracker::open(&path).map_err(py_err)?; + init_frecency.init(tracker).map_err(py_err)?; + } + + if let Some(path) = history_db_path { + create_parent_dir(&path)?; + let tracker = QueryTracker::open(&path).map_err(py_err)?; + init_query_tracker.init(tracker).map_err(py_err)?; + } + + if let Some(path) = log_file_path { + create_parent_dir(&path)?; + let path = path.to_string_lossy(); + fff::log::init_tracing(&path, log_level.as_deref(), None).map_err(py_err)?; + } + + let mode = if ai_mode { + FFFMode::Ai + } else { + FFFMode::Neovim + }; + + FilePicker::new_with_shared_state( + init_picker, + init_frecency, + FilePickerOptions { + base_path: base_path.to_string_lossy().to_string(), + enable_mmap_cache, + enable_content_indexing, + watch, + mode, + cache_budget: fff::ContentCacheBudget::from_overrides( + cache_budget_max_files, + cache_budget_max_bytes, + cache_budget_max_file_size, + ), + follow_symlinks, + enable_fs_root_scanning, + enable_home_dir_scanning, + }, + ) + .map_err(py_err) + })?; + + Ok(Self { + picker: shared_picker, + frecency: shared_frecency, + query_tracker, + cache_budget_max_files, + cache_budget_max_bytes, + cache_budget_max_file_size, + }) + } + + fn __enter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + + fn __exit__(&mut self, _exc_type: PyObject, _exc_value: PyObject, _traceback: PyObject) { + let _ = self.close(); + } + + fn close(&mut self) -> PyResult<()> { + clear_shared_state(&self.picker, &self.frecency, &self.query_tracker); + Ok(()) + } + + #[getter] + fn closed(&self) -> PyResult { + Ok(self.picker.read().map_err(py_err)?.is_none()) + } + + #[getter] + fn base_path(&self) -> PyResult> { + let guard = self.picker.read().map_err(py_err)?; + Ok(guard + .as_ref() + .map(|p| p.base_path().to_string_lossy().to_string())) + } + + #[getter] + fn scan_progress(&self) -> PyResult { + let guard = self.picker.read().map_err(py_err)?; + let picker = guard + .as_ref() + .ok_or_else(|| py_err("File picker not initialized"))?; + let p = picker.get_scan_progress(); + Ok(ScanProgress { + scanned_files_count: p.scanned_files_count as u64, + is_scanning: p.is_scanning, + is_watcher_ready: p.is_watcher_ready, + is_warmup_complete: p.is_warmup_complete, + }) + } + + fn __repr__(&self) -> PyResult { + let guard = self.picker.read().map_err(py_err)?; + if let Some(ref picker) = *guard { + Ok(format!( + "FileFinder(base_path={:?}, closed=False)", + picker.base_path().to_string_lossy() + )) + } else { + Ok("FileFinder(closed=True)".to_string()) + } + } + + #[allow(clippy::too_many_arguments)] + #[pyo3(signature = ( + query, + *, + current_file=None, + max_threads=0, + page_index=0, + page_size=0, + combo_boost_score_multiplier=0, + min_combo_count=0, + ))] + fn search( + &self, + py: Python<'_>, + query: &str, + current_file: Option, + max_threads: u32, + page_index: u32, + page_size: u32, + combo_boost_score_multiplier: i32, + min_combo_count: u32, + ) -> PyResult { + let picker = self.picker.clone(); + let query_tracker = self.query_tracker.clone(); + let query = query.to_string(); + + py.allow_threads(move || -> PyResult<_> { + let picker_guard = picker.read().map_err(py_err)?; + let picker = picker_guard + .as_ref() + .ok_or_else(|| py_err("File picker not initialized"))?; + let qt_guard = query_tracker.read().map_err(py_err)?; + + let parsed = QueryParser::default().parse(&query); + let result = picker.fuzzy_search( + &parsed, + qt_guard.as_ref(), + fuzzy_options( + max_threads, + current_file.as_deref(), + picker.base_path(), + page_index, + page_size, + defaulted_i32(combo_boost_score_multiplier, DEFAULT_COMBO_BOOST_MULTIPLIER), + defaulted_u32(min_combo_count, DEFAULT_MIN_COMBO_COUNT), + ), + ); + + Ok(SearchResult { + items: result + .items + .iter() + .map(|i| FileItem::from((*i, picker))) + .collect(), + scores: convert_scores(&result.scores), + total_matched: result.total_matched as u32, + total_files: result.total_files as u32, + }) + }) + } + + #[pyo3(signature = ( + pattern, + *, + current_file=None, + max_threads=0, + page_index=0, + page_size=0, + ))] + fn glob( + &self, + py: Python<'_>, + pattern: &str, + current_file: Option, + max_threads: u32, + page_index: u32, + page_size: u32, + ) -> PyResult { + let picker = self.picker.clone(); + let pattern = pattern.to_string(); + + py.allow_threads(move || -> PyResult<_> { + let picker_guard = picker.read().map_err(py_err)?; + let picker = picker_guard + .as_ref() + .ok_or_else(|| py_err("File picker not initialized"))?; + + let result = picker.glob( + &pattern, + fuzzy_options( + max_threads, + current_file.as_deref(), + picker.base_path(), + page_index, + page_size, + 0, + 0, + ), + ); + + Ok(SearchResult { + items: result + .items + .iter() + .map(|i| FileItem::from((*i, picker))) + .collect(), + scores: convert_scores(&result.scores), + total_matched: result.total_matched as u32, + total_files: result.total_files as u32, + }) + }) + } + + #[pyo3(signature = ( + query, + *, + current_file=None, + max_threads=0, + page_index=0, + page_size=0, + ))] + fn directory_search( + &self, + py: Python<'_>, + query: &str, + current_file: Option, + max_threads: u32, + page_index: u32, + page_size: u32, + ) -> PyResult { + let picker = self.picker.clone(); + let query = query.to_string(); + + py.allow_threads(move || -> PyResult<_> { + let picker_guard = picker.read().map_err(py_err)?; + let picker = picker_guard + .as_ref() + .ok_or_else(|| py_err("File picker not initialized"))?; + + let parsed = QueryParser::new(fff_query_parser::DirSearchConfig).parse(&query); + let result = picker.fuzzy_search_directories( + &parsed, + fuzzy_options( + max_threads, + current_file.as_deref(), + picker.base_path(), + page_index, + page_size, + 0, + 0, + ), + ); + + Ok(DirSearchResult { + items: result + .items + .iter() + .map(|i| DirItem::from((*i, picker))) + .collect(), + scores: convert_scores(&result.scores), + total_matched: result.total_matched as u32, + total_dirs: result.total_dirs as u32, + }) + }) + } + + #[allow(clippy::too_many_arguments)] + #[pyo3(signature = ( + query, + *, + current_file=None, + max_threads=0, + page_index=0, + page_size=0, + combo_boost_score_multiplier=0, + min_combo_count=0, + ))] + fn mixed_search( + &self, + py: Python<'_>, + query: &str, + current_file: Option, + max_threads: u32, + page_index: u32, + page_size: u32, + combo_boost_score_multiplier: i32, + min_combo_count: u32, + ) -> PyResult { + let picker = self.picker.clone(); + let query_tracker = self.query_tracker.clone(); + let query = query.to_string(); + + let (items, scores, total_matched, total_files, total_dirs) = + py.allow_threads(move || -> PyResult<_> { + let picker_guard = picker.read().map_err(py_err)?; + let picker = picker_guard + .as_ref() + .ok_or_else(|| py_err("File picker not initialized"))?; + let qt_guard = query_tracker.read().map_err(py_err)?; + + let parsed = QueryParser::new(fff_query_parser::MixedSearchConfig).parse(&query); + let result = picker.fuzzy_search_mixed( + &parsed, + qt_guard.as_ref(), + fuzzy_options( + max_threads, + current_file.as_deref(), + picker.base_path(), + page_index, + page_size, + defaulted_i32(combo_boost_score_multiplier, DEFAULT_COMBO_BOOST_MULTIPLIER), + defaulted_u32(min_combo_count, DEFAULT_MIN_COMBO_COUNT), + ), + ); + + let items: Vec = result + .items + .iter() + .map(|item| match item { + fff::MixedItemRef::File(file) => { + MixedItem::File(MixedFileItem::from((*file, picker))) + } + fff::MixedItemRef::Dir(dir) => { + MixedItem::Dir(MixedDirItem::from((*dir, picker))) + } + }) + .collect(); + + Ok(( + items, + convert_scores(&result.scores), + result.total_matched as u32, + result.total_files as u32, + result.total_dirs as u32, + )) + })?; + + let items: PyResult> = items + .into_iter() + .map(|item| match item { + MixedItem::File(file) => Ok(Py::new(py, file)?.into_any()), + MixedItem::Dir(dir) => Ok(Py::new(py, dir)?.into_any()), + }) + .collect(); + + Ok(MixedSearchResult { + items: items?, + scores, + total_matched, + total_files, + total_dirs, + }) + } + + #[allow(clippy::too_many_arguments)] + #[pyo3(signature = ( + query, + *, + mode="plain", + max_file_size=0, + max_matches_per_file=0, + smart_case=true, + cursor=None, + page_limit=0, + time_budget_ms=0, + before_context=0, + after_context=0, + classify_definitions=false, + ))] + fn grep( + &self, + py: Python<'_>, + query: &str, + mode: &str, + max_file_size: u64, + max_matches_per_file: u32, + smart_case: bool, + cursor: Option<&GrepCursor>, + page_limit: u32, + time_budget_ms: u64, + before_context: u32, + after_context: u32, + classify_definitions: bool, + ) -> PyResult { + let picker = self.picker.clone(); + let query = query.to_string(); + let mode = parse_grep_mode(mode)?; + let cursor_offset = cursor.map(|c| c.offset as usize).unwrap_or(0); + + py.allow_threads(move || -> PyResult<_> { + let picker_guard = picker.read().map_err(py_err)?; + let picker = picker_guard + .as_ref() + .ok_or_else(|| py_err("File picker not initialized"))?; + + let parsed = if picker.mode().is_ai() { + QueryParser::new(fff_query_parser::AiGrepConfig).parse(&query) + } else { + fff::grep::parse_grep_query(&query) + }; + let options = grep_options( + mode, + cursor_offset, + max_file_size, + max_matches_per_file, + smart_case, + page_limit, + time_budget_ms, + before_context, + after_context, + classify_definitions, + ); + + Ok(convert_grep_result(picker.grep(&parsed, &options), picker)) + }) + } + + #[allow(clippy::too_many_arguments)] + #[pyo3(signature = ( + patterns, + *, + constraints=None, + mode="plain", + max_file_size=0, + max_matches_per_file=0, + smart_case=true, + cursor=None, + page_limit=0, + time_budget_ms=0, + before_context=0, + after_context=0, + classify_definitions=false, + ))] + fn multi_grep( + &self, + py: Python<'_>, + patterns: Vec, + constraints: Option, + mode: &str, + max_file_size: u64, + max_matches_per_file: u32, + smart_case: bool, + cursor: Option<&GrepCursor>, + page_limit: u32, + time_budget_ms: u64, + before_context: u32, + after_context: u32, + classify_definitions: bool, + ) -> PyResult { + let picker = self.picker.clone(); + let mode = parse_grep_mode(mode)?; + let cursor_offset = cursor.map(|c| c.offset as usize).unwrap_or(0); + + py.allow_threads(move || -> PyResult<_> { + let picker_guard = picker.read().map_err(py_err)?; + let picker = picker_guard + .as_ref() + .ok_or_else(|| py_err("File picker not initialized"))?; + + if patterns.is_empty() || patterns.iter().all(|p| p.is_empty()) { + return Err(py_err("patterns must not be empty")); + } + let pattern_refs: Vec<&str> = patterns.iter().map(|s| s.as_str()).collect(); + + let parsed_constraints = constraints.as_ref().map(|c| { + if picker.mode().is_ai() { + QueryParser::new(fff_query_parser::AiGrepConfig).parse(c) + } else { + fff::grep::parse_grep_query(c) + } + }); + let constraint_refs: &[fff::Constraint<'_>] = match &parsed_constraints { + Some(q) => &q.constraints, + None => &[], + }; + let options = grep_options( + mode, + cursor_offset, + max_file_size, + max_matches_per_file, + smart_case, + page_limit, + time_budget_ms, + before_context, + after_context, + classify_definitions, + ); + + Ok(convert_grep_result( + picker.multi_grep(&pattern_refs, constraint_refs, &options), + picker, + )) + }) + } + + fn scan_files(&self) -> PyResult<()> { + self.picker + .trigger_full_rescan_async(&self.frecency) + .map_err(py_err) + } + + fn is_scanning(&self) -> PyResult { + let guard = self.picker.read().map_err(py_err)?; + Ok(guard.as_ref().map(|p| p.is_scan_active()).unwrap_or(false)) + } + + #[pyo3(signature = (timeout_ms=5000))] + fn wait_for_scan_blocking(&self, py: Python<'_>, timeout_ms: u64) -> PyResult { + let picker = self.picker.clone(); + py.allow_threads(move || Ok(picker.wait_for_scan(Duration::from_millis(timeout_ms)))) + } + + fn reindex(&self, py: Python<'_>, new_path: PathBuf) -> PyResult<()> { + let picker = self.picker.clone(); + let frecency = self.frecency.clone(); + let cache_budget_max_files = self.cache_budget_max_files; + let cache_budget_max_bytes = self.cache_budget_max_bytes; + let cache_budget_max_file_size = self.cache_budget_max_file_size; + + py.allow_threads(move || -> PyResult<()> { + if !new_path.exists() { + return Err(py_err(format!( + "Path does not exist: {}", + new_path.display() + ))); + } + let canonical = fff::path_utils::canonicalize(&new_path).map_err(py_err)?; + + let (warmup_caches, content_indexing, watch, mode, fs_root, home_dir, follow_symlinks) = { + let guard = picker.read().map_err(py_err)?; + if let Some(ref picker) = *guard { + ( + picker.has_mmap_cache(), + picker.has_content_indexing(), + picker.has_watcher(), + picker.mode(), + picker.fs_root_scanning_enabled(), + picker.home_dir_scanning_enabled(), + picker.follows_symlinks(), + ) + } else { + (false, true, true, FFFMode::default(), false, false, false) + } + }; + + FilePicker::new_with_shared_state( + picker.clone(), + frecency, + FilePickerOptions { + base_path: canonical.to_string_lossy().to_string(), + enable_mmap_cache: warmup_caches, + enable_content_indexing: content_indexing, + watch, + mode, + cache_budget: fff::ContentCacheBudget::from_overrides( + cache_budget_max_files, + cache_budget_max_bytes, + cache_budget_max_file_size, + ), + follow_symlinks, + enable_fs_root_scanning: fs_root, + enable_home_dir_scanning: home_dir, + }, + ) + .map_err(py_err) + }) + } + + fn refresh_git_status(&self, py: Python<'_>) -> PyResult { + let picker = self.picker.clone(); + let frecency = self.frecency.clone(); + py.allow_threads(move || { + picker + .refresh_git_status(&frecency) + .map_err(py_err) + .map(|c| c as i64) + }) + } + + #[pyo3(signature = (query, selected_file_path))] + fn track_query( + &self, + py: Python<'_>, + query: &str, + selected_file_path: PathBuf, + ) -> PyResult { + let picker = self.picker.clone(); + let query_tracker = self.query_tracker.clone(); + let query = query.to_string(); + + py.allow_threads(move || -> PyResult { + let file_path = fff::path_utils::canonicalize(&selected_file_path).map_err(py_err)?; + let project_path = { + let guard = picker.read().map_err(py_err)?; + guard.as_ref().map(|p| p.base_path().to_path_buf()) + }; + let project_path = match project_path { + Some(p) => p, + None => return Ok(false), + }; + + let mut qt_guard = query_tracker.write().map_err(py_err)?; + if let Some(ref mut tracker) = *qt_guard { + tracker + .track_query_completion(&query, &project_path, &file_path) + .map_err(py_err)?; + Ok(true) + } else { + Ok(false) + } + }) + } + + fn get_historical_query(&self, py: Python<'_>, offset: u64) -> PyResult> { + let picker = self.picker.clone(); + let query_tracker = self.query_tracker.clone(); + + py.allow_threads(move || -> PyResult> { + let project_path = { + let guard = picker.read().map_err(py_err)?; + guard.as_ref().map(|p| p.base_path().to_path_buf()) + }; + let project_path = match project_path { + Some(p) => p, + None => return Ok(None), + }; + + let qt_guard = query_tracker.read().map_err(py_err)?; + if let Some(ref tracker) = *qt_guard { + tracker + .get_historical_query(&project_path, offset as usize) + .map_err(py_err) + } else { + Ok(None) + } + }) + } + + #[pyo3(signature = (test_path=None))] + fn health_check(&self, py: Python<'_>, test_path: Option) -> PyResult> { + let picker = self.picker.clone(); + let frecency = self.frecency.clone(); + let query_tracker = self.query_tracker.clone(); + + let ( + git_version, + repository_found, + workdir, + git_error, + picker_initialized, + picker_base_path, + picker_is_scanning, + picker_indexed_files, + frecency_initialized, + query_tracker_initialized, + ) = py.allow_threads(move || -> PyResult<_> { + // Resolve the path to inspect: explicit arg → indexed base path → + // process cwd. Report a cwd-resolution failure instead of silently + // discovering from an empty path. + let (test_path, cwd_error) = match test_path { + Some(p) => (Some(p), None), + None => { + let base = picker + .read() + .ok() + .and_then(|g| g.as_ref().map(|p| p.base_path().to_path_buf())); + match base { + Some(p) => (Some(p), None), + None => match std::env::current_dir() { + Ok(p) => (Some(p), None), + Err(e) => ( + None, + Some(format!("could not determine current directory: {}", e)), + ), + }, + } + } + }; + + let git_version = git2::Version::get(); + let (major, minor, rev) = git_version.libgit2_version(); + let git_version = format!("{}.{}.{}", major, minor, rev); + let (repository_found, workdir, git_error) = match test_path { + None => (false, None, cwd_error), + Some(test_path) => match git2::Repository::discover(&test_path) { + Ok(repo) => ( + true, + repo.workdir().map(|p| p.to_string_lossy().to_string()), + None, + ), + Err(e) => (false, None, Some(e.message().to_string())), + }, + }; + + let (picker_initialized, picker_base_path, picker_is_scanning, picker_indexed_files) = { + let guard = picker.read().map_err(py_err)?; + if let Some(ref picker) = *guard { + let progress = picker.get_scan_progress(); + ( + true, + Some(picker.base_path().to_string_lossy().to_string()), + Some(picker.is_scan_active()), + Some(progress.scanned_files_count), + ) + } else { + (false, None, None, None) + } + }; + let frecency_initialized = frecency.read().map_err(py_err)?.is_some(); + let query_tracker_initialized = query_tracker.read().map_err(py_err)?.is_some(); + + Ok(( + git_version, + repository_found, + workdir, + git_error, + picker_initialized, + picker_base_path, + picker_is_scanning, + picker_indexed_files, + frecency_initialized, + query_tracker_initialized, + )) + })?; + + let dict = PyDict::new(py); + dict.set_item("version", env!("CARGO_PKG_VERSION"))?; + + let git_info = PyDict::new(py); + git_info.set_item("available", true)?; + git_info.set_item("libgit2_version", git_version)?; + git_info.set_item("repository_found", repository_found)?; + if let Some(workdir) = workdir { + git_info.set_item("workdir", workdir)?; + } + if let Some(error) = git_error { + git_info.set_item("error", error)?; + } + dict.set_item("git", git_info)?; + + let picker_info = PyDict::new(py); + picker_info.set_item("initialized", picker_initialized)?; + if let Some(base_path) = picker_base_path { + picker_info.set_item("base_path", base_path)?; + } + if let Some(is_scanning) = picker_is_scanning { + picker_info.set_item("is_scanning", is_scanning)?; + } + if let Some(indexed_files) = picker_indexed_files { + picker_info.set_item("indexed_files", indexed_files)?; + } + dict.set_item("file_picker", picker_info)?; + + let frecency_info = PyDict::new(py); + frecency_info.set_item("initialized", frecency_initialized)?; + dict.set_item("frecency", frecency_info)?; + + let query_info = PyDict::new(py); + query_info.set_item("initialized", query_tracker_initialized)?; + dict.set_item("query_tracker", query_info)?; + + Ok(dict.unbind()) + } +} diff --git a/crates/fff-python/src/lib.rs b/crates/fff-python/src/lib.rs new file mode 100644 index 0000000..94d2194 --- /dev/null +++ b/crates/fff-python/src/lib.rs @@ -0,0 +1,44 @@ +use pyo3::create_exception; +use pyo3::prelude::*; + +mod conversions; +mod finder; +mod types; + +create_exception!(fff_python, FFFException, pyo3::exceptions::PyException); + +fn py_err(e: E) -> PyErr { + PyErr::new::(format!("{}", e)) +} + +pub fn parse_grep_mode(mode: &str) -> PyResult { + match mode { + "plain" => Ok(fff::GrepMode::PlainText), + "regex" => Ok(fff::GrepMode::Regex), + "fuzzy" => Ok(fff::GrepMode::Fuzzy), + _ => Err(py_err(format!( + "invalid grep mode: {:?}. Must be one of: plain, regex, fuzzy", + mode + ))), + } +} + +#[pymodule] +fn _fff_python(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add("FFFException", m.py().get_type::())?; + Ok(()) +} diff --git a/crates/fff-python/src/types.rs b/crates/fff-python/src/types.rs new file mode 100644 index 0000000..32c2f13 --- /dev/null +++ b/crates/fff-python/src/types.rs @@ -0,0 +1,408 @@ +use pyo3::prelude::*; + +#[pyclass] +#[derive(Clone)] +pub struct Score { + #[pyo3(get)] + pub total: i32, + #[pyo3(get)] + pub base_score: i32, + #[pyo3(get)] + pub filename_bonus: i32, + #[pyo3(get)] + pub special_filename_bonus: i32, + #[pyo3(get)] + pub frecency_boost: i32, + #[pyo3(get)] + pub distance_penalty: i32, + #[pyo3(get)] + pub current_file_penalty: i32, + #[pyo3(get)] + pub combo_match_boost: i32, + #[pyo3(get)] + pub path_alignment_bonus: i32, + #[pyo3(get)] + pub exact_match: bool, + #[pyo3(get)] + pub match_type: String, +} + +#[pymethods] +impl Score { + fn __repr__(&self) -> String { + format!( + "Score(total={}, base_score={}, filename_bonus={}, match_type={:?})", + self.total, self.base_score, self.filename_bonus, self.match_type + ) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct FileItem { + #[pyo3(get)] + pub relative_path: String, + #[pyo3(get)] + pub file_name: String, + #[pyo3(get)] + pub git_status: String, + #[pyo3(get)] + pub size: u64, + #[pyo3(get)] + pub modified: u64, + #[pyo3(get)] + pub access_frecency_score: i64, + #[pyo3(get)] + pub modification_frecency_score: i64, + #[pyo3(get)] + pub total_frecency_score: i64, + #[pyo3(get)] + pub is_binary: bool, +} + +#[pymethods] +impl FileItem { + fn __repr__(&self) -> String { + format!( + "FileItem(relative_path={:?}, file_name={:?}, size={})", + self.relative_path, self.file_name, self.size + ) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct DirItem { + #[pyo3(get)] + pub relative_path: String, + #[pyo3(get)] + pub dir_name: String, + #[pyo3(get)] + pub max_access_frecency: i32, +} + +#[pymethods] +impl DirItem { + fn __repr__(&self) -> String { + format!( + "DirItem(relative_path={:?}, dir_name={:?})", + self.relative_path, self.dir_name + ) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct MixedFileItem { + #[pyo3(get)] + pub relative_path: String, + #[pyo3(get)] + pub file_name: String, + #[pyo3(get)] + pub git_status: String, + #[pyo3(get)] + pub size: u64, + #[pyo3(get)] + pub modified: u64, + #[pyo3(get)] + pub access_frecency_score: i64, + #[pyo3(get)] + pub modification_frecency_score: i64, + #[pyo3(get)] + pub total_frecency_score: i64, + #[pyo3(get)] + pub is_binary: bool, +} + +#[pymethods] +impl MixedFileItem { + fn __repr__(&self) -> String { + format!( + "MixedFileItem(relative_path={:?}, file_name={:?}, size={})", + self.relative_path, self.file_name, self.size + ) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct MixedDirItem { + #[pyo3(get)] + pub relative_path: String, + #[pyo3(get)] + pub dir_name: String, + #[pyo3(get)] + pub max_access_frecency: i32, +} + +#[pymethods] +impl MixedDirItem { + fn __repr__(&self) -> String { + format!( + "MixedDirItem(relative_path={:?}, dir_name={:?})", + self.relative_path, self.dir_name + ) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct MatchRange { + #[pyo3(get)] + pub start: u32, + #[pyo3(get)] + pub end: u32, +} + +#[pymethods] +impl MatchRange { + fn __repr__(&self) -> String { + format!("MatchRange(start={}, end={})", self.start, self.end) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct GrepMatch { + #[pyo3(get)] + pub relative_path: String, + #[pyo3(get)] + pub file_name: String, + #[pyo3(get)] + pub git_status: String, + #[pyo3(get)] + pub line_content: String, + #[pyo3(get)] + pub match_ranges: Vec, + #[pyo3(get)] + pub context_before: Vec, + #[pyo3(get)] + pub context_after: Vec, + #[pyo3(get)] + pub size: u64, + #[pyo3(get)] + pub modified: u64, + #[pyo3(get)] + pub total_frecency_score: i64, + #[pyo3(get)] + pub access_frecency_score: i64, + #[pyo3(get)] + pub modification_frecency_score: i64, + #[pyo3(get)] + pub line_number: u64, + #[pyo3(get)] + pub byte_offset: u64, + #[pyo3(get)] + pub col: u32, + #[pyo3(get)] + pub fuzzy_score: Option, + #[pyo3(get)] + pub is_definition: bool, + #[pyo3(get)] + pub is_binary: bool, +} + +#[pymethods] +impl GrepMatch { + fn __repr__(&self) -> String { + format!( + "GrepMatch(relative_path={:?}, line_number={}, line_content={:?})", + self.relative_path, self.line_number, self.line_content + ) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct SearchResult { + #[pyo3(get)] + pub items: Vec, + #[pyo3(get)] + pub scores: Vec, + #[pyo3(get)] + pub total_matched: u32, + #[pyo3(get)] + pub total_files: u32, +} + +#[pymethods] +impl SearchResult { + fn __repr__(&self) -> String { + format!( + "SearchResult(items={}, total_matched={}, total_files={})", + self.items.len(), + self.total_matched, + self.total_files + ) + } + + fn __len__(&self) -> usize { + self.items.len() + } + + fn __bool__(&self) -> bool { + !self.items.is_empty() + } +} + +#[pyclass] +#[derive(Clone)] +pub struct DirSearchResult { + #[pyo3(get)] + pub items: Vec, + #[pyo3(get)] + pub scores: Vec, + #[pyo3(get)] + pub total_matched: u32, + #[pyo3(get)] + pub total_dirs: u32, +} + +#[pymethods] +impl DirSearchResult { + fn __repr__(&self) -> String { + format!( + "DirSearchResult(items={}, total_matched={}, total_dirs={})", + self.items.len(), + self.total_matched, + self.total_dirs + ) + } + + fn __len__(&self) -> usize { + self.items.len() + } + + fn __bool__(&self) -> bool { + !self.items.is_empty() + } +} + +#[pyclass] +pub struct MixedSearchResult { + #[pyo3(get)] + pub items: Vec, + #[pyo3(get)] + pub scores: Vec, + #[pyo3(get)] + pub total_matched: u32, + #[pyo3(get)] + pub total_files: u32, + #[pyo3(get)] + pub total_dirs: u32, +} + +#[pymethods] +impl MixedSearchResult { + fn __repr__(&self) -> String { + format!( + "MixedSearchResult(items={}, total_matched={}, total_files={}, total_dirs={})", + self.items.len(), + self.total_matched, + self.total_files, + self.total_dirs + ) + } + + fn __len__(&self) -> usize { + self.items.len() + } + + fn __bool__(&self) -> bool { + !self.items.is_empty() + } +} + +#[pyclass] +#[derive(Clone)] +pub struct GrepResult { + #[pyo3(get)] + pub items: Vec, + #[pyo3(get)] + pub total_matched: u32, + #[pyo3(get)] + pub total_files_searched: u32, + #[pyo3(get)] + pub total_files: u32, + #[pyo3(get)] + pub filtered_file_count: u32, + #[pyo3(get)] + pub next_file_offset: u32, + #[pyo3(get)] + pub regex_fallback_error: Option, +} + +#[pymethods] +impl GrepResult { + fn __repr__(&self) -> String { + format!( + "GrepResult(items={}, total_matched={}, next_file_offset={})", + self.items.len(), + self.total_matched, + self.next_file_offset + ) + } + + fn __len__(&self) -> usize { + self.items.len() + } + + fn __bool__(&self) -> bool { + !self.items.is_empty() + } + + #[getter] + fn has_more(&self) -> bool { + self.next_file_offset > 0 + } + + fn next_cursor(&self, py: Python<'_>) -> PyResult>> { + if self.next_file_offset > 0 { + Ok(Some(Py::new(py, GrepCursor::new(self.next_file_offset))?)) + } else { + Ok(None) + } + } +} + +#[pyclass] +#[derive(Clone)] +pub struct ScanProgress { + #[pyo3(get)] + pub scanned_files_count: u64, + #[pyo3(get)] + pub is_scanning: bool, + #[pyo3(get)] + pub is_watcher_ready: bool, + #[pyo3(get)] + pub is_warmup_complete: bool, +} + +#[pymethods] +impl ScanProgress { + fn __repr__(&self) -> String { + format!( + "ScanProgress(scanned_files_count={}, is_scanning={})", + self.scanned_files_count, self.is_scanning + ) + } +} + +#[pyclass] +#[derive(Clone)] +pub struct GrepCursor { + #[pyo3(get)] + pub offset: u32, +} + +#[pymethods] +impl GrepCursor { + #[new] + fn new(offset: u32) -> Self { + Self { offset } + } + + fn __repr__(&self) -> String { + format!("GrepCursor(offset={})", self.offset) + } +} diff --git a/crates/fff-query-parser/Cargo.toml b/crates/fff-query-parser/Cargo.toml new file mode 100644 index 0000000..416d2db --- /dev/null +++ b/crates/fff-query-parser/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "fff-query-parser" +version = "0.9.6" +edition = "2024" +description = "Query parser for fff file finder - includes specific syntax for various constraints like globs, extensions, regex etc" +license = "MIT" +authors = ["Dmitriy Kovalenko "] + +[lib] +path = "src/lib.rs" + +[features] +# `ripgrep` = pure-Rust glob detection (default). `zlob` overrides it when set. +default = ["ripgrep"] +ripgrep = [] +zlob = ["dep:zlob"] + +[dependencies] +zlob = { workspace = true, optional = true } + +[dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "parse_bench" +harness = false diff --git a/crates/fff-query-parser/benches/parse_bench.rs b/crates/fff-query-parser/benches/parse_bench.rs new file mode 100644 index 0000000..cd6e8d2 --- /dev/null +++ b/crates/fff-query-parser/benches/parse_bench.rs @@ -0,0 +1,180 @@ +use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main}; +use fff_query_parser::*; + +fn bench_parse_simple(c: &mut Criterion) { + let parser = QueryParser::default(); + + c.bench_function("parse_simple_text", |b| { + b.iter(|| parser.parse(black_box("hello world"))); + }); + + c.bench_function("parse_extension", |b| { + b.iter(|| parser.parse(black_box("*.rs"))); + }); + + c.bench_function("parse_text_with_extension", |b| { + b.iter(|| parser.parse(black_box("name *.rs"))); + }); +} + +fn bench_parse_complex(c: &mut Criterion) { + let parser = QueryParser::default(); + + c.bench_function("parse_complex_mixed", |b| { + b.iter(|| parser.parse(black_box("src name *.rs !test /lib/ status:modified"))); + }); + + c.bench_function("parse_glob", |b| { + b.iter(|| parser.parse(black_box("**/*.rs"))); + }); + + c.bench_function("parse_multiple_constraints", |b| { + b.iter(|| parser.parse(black_box("*.rs *.toml *.md !test !node_modules /src/"))); + }); +} + +fn bench_parse_realistic_queries(c: &mut Criterion) { + let parser = QueryParser::default(); + + let queries = vec![ + "file", + "test", + "mod.rs", + "src/*.rs", + "lib test", + "*.rs !test", + "src/lib/*.rs", + "/src/ name", + "status:modified *.rs", + "type:rust test !node_modules", + ]; + + let mut group = c.benchmark_group("realistic_queries"); + for query in queries.iter() { + group.throughput(Throughput::Bytes(query.len() as u64)); + group.bench_with_input(BenchmarkId::from_parameter(query), query, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + } + group.finish(); +} + +fn bench_parse_various_lengths(c: &mut Criterion) { + let parser = QueryParser::default(); + + let short = "*.rs"; + let medium = "src name *.rs !test"; + let long = "src lib test name *.rs *.toml !node_modules !test /src/ /lib/ status:modified"; + let very_long = + "a b c d e f g h i j k l m n o p q r s t u v w x y z *.rs *.toml *.md *.txt *.js"; + + let mut group = c.benchmark_group("query_lengths"); + + group.throughput(Throughput::Bytes(short.len() as u64)); + group.bench_with_input(BenchmarkId::new("short", short.len()), &short, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + + group.throughput(Throughput::Bytes(medium.len() as u64)); + group.bench_with_input(BenchmarkId::new("medium", medium.len()), &medium, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + + group.throughput(Throughput::Bytes(long.len() as u64)); + group.bench_with_input(BenchmarkId::new("long", long.len()), &long, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + + group.throughput(Throughput::Bytes(very_long.len() as u64)); + group.bench_with_input( + BenchmarkId::new("very_long", very_long.len()), + &very_long, + |b, q| { + b.iter(|| parser.parse(black_box(q))); + }, + ); + + group.finish(); +} + +fn bench_config_comparison(c: &mut Criterion) { + let file_picker = QueryParser::new(FileSearchConfig); + let grep = QueryParser::new(GrepConfig); + + let query = "src name *.rs !test"; + + let mut group = c.benchmark_group("config_comparison"); + + group.bench_function("file_picker_config", |b| { + b.iter(|| file_picker.parse(black_box(query))); + }); + + group.bench_function("grep_config", |b| { + b.iter(|| grep.parse(black_box(query))); + }); + + group.finish(); +} + +fn bench_constraint_types(c: &mut Criterion) { + let parser = QueryParser::default(); + + let mut group = c.benchmark_group("constraint_types"); + + group.bench_function("extension", |b| { + b.iter(|| parser.parse(black_box("*.rs"))); + }); + + group.bench_function("glob", |b| { + b.iter(|| parser.parse(black_box("**/*.rs"))); + }); + + group.bench_function("exclude", |b| { + b.iter(|| parser.parse(black_box("!test"))); + }); + + group.bench_function("path_segment", |b| { + b.iter(|| parser.parse(black_box("/src/"))); + }); + + group.bench_function("git_status", |b| { + b.iter(|| parser.parse(black_box("status:modified"))); + }); + + group.bench_function("file_type", |b| { + b.iter(|| parser.parse(black_box("type:rust"))); + }); + + group.finish(); +} + +fn bench_worst_case(c: &mut Criterion) { + let parser = QueryParser::default(); + + // Worst case: many constraints that all need to be checked + let worst_case = "a b c d e f g h i j k l m n o p q r s t u v w x y z"; + + c.bench_function("worst_case_many_text_tokens", |b| { + b.iter(|| parser.parse(black_box(worst_case))); + }); + + // Many constraints + let many_constraints = "*.rs *.toml *.md *.txt *.js *.ts *.jsx *.tsx *.vue *.svelte"; + + c.bench_function("worst_case_many_constraints", |b| { + b.iter(|| parser.parse(black_box(many_constraints))); + }); +} + +criterion_group!( + benches, + bench_parse_simple, + bench_parse_complex, + bench_parse_realistic_queries, + bench_parse_various_lengths, + bench_config_comparison, + bench_constraint_types, + bench_worst_case, +); + +criterion_main!(benches); diff --git a/crates/fff-query-parser/src/config.rs b/crates/fff-query-parser/src/config.rs new file mode 100644 index 0000000..9516c96 --- /dev/null +++ b/crates/fff-query-parser/src/config.rs @@ -0,0 +1,248 @@ +use crate::constraints::Constraint; +use crate::glob_detect::has_wildcards; + +/// Check if a token looks like a filename/path for use as a `FilePath` constraint. +#[deprecated(note = "use `Constraint::is_filename_constraint_token`")] +pub fn is_filename_constraint_token(token: &str) -> bool { + Constraint::is_filename_constraint_token(token) +} + +/// Parser configuration trait - allows different picker types to customize parsing +pub trait ParserConfig { + fn enable_glob(&self) -> bool { + true + } + + /// Should parse extension shortcuts (e.g., *.rs) + fn enable_extension(&self) -> bool { + true + } + + /// Should parse exclusion patterns (e.g., !test) + fn enable_exclude(&self) -> bool { + true + } + + /// Should parse path segments (e.g., /src/) + fn enable_path_segments(&self) -> bool { + true + } + + /// Should parse type constraints (e.g., type:rust) + fn enable_type_filter(&self) -> bool { + true + } + + /// Should parse git status (e.g., status:modified) + fn enable_git_status(&self) -> bool { + true + } + + /// Should parse location suffixes (e.g., file:12, file:12:4) + /// Disabled for grep modes where colon-number patterns like localhost:8080 + /// are search text, not file locations. + fn enable_location(&self) -> bool { + true + } + + /// Determine whether a token should be treated as a glob constraint. + /// + /// The default implementation delegates to `zlob::has_wildcards` with + /// `RECOMMENDED` flags, which recognises `*`, `?`, `[`, `{…}` etc. + /// + /// Override this in configs where some wildcard characters are common + /// in search text (e.g. grep mode where `?` and `[` appear in code). + fn is_glob_pattern(&self, token: &str) -> bool { + has_wildcards(token) + } + + /// If `true`, a PathSegment constraint that is the ONLY token in the + /// query is demoted to fuzzy text to avoid over filtering + fn treat_lone_path_as_text(&self) -> bool { + true + } + + /// If `true`, tokens that look like filenames (`score.rs`, `src/main.rs`) + /// become `FilePath` constraints that scope the search to matching paths. + /// Off by default: a partial filename like `vite.conf` would otherwise + /// filter out `vite.config.ts` and yield zero results. + fn enable_filename_constraint(&self) -> bool { + false + } + + /// Custom constraint parsers for picker-specific needs. + /// + /// Returns `None` by default. Filename-token detection is handled + /// separately by the parser via `enable_filename_constraint`. + fn parse_custom<'a>(&self, _input: &'a str) -> Option> { + None + } +} + +/// Default configuration for the file picker. +/// +/// Filename-constraint detection is off (trait default), so partial filenames +/// like `vite.conf` don't silently filter out fuzzy matches. The Neovim layer +/// overrides `enable_filename_constraint` to opt in based on user config. +#[derive(Debug, Clone, Copy, Default)] +pub struct FileSearchConfig; + +impl ParserConfig for FileSearchConfig {} + +/// Configuration for full-text search (grep) - file constraints enabled for +/// filtering which files to search, git status disabled since it's not useful +/// when searching file contents. +/// +/// Glob detection is narrowed: only patterns containing a path separator (`/`) +/// or brace expansion (`{…}`) are treated as globs. Characters like `?` and +/// `[` are extremely common in source code and must remain literal search text. +#[derive(Debug, Clone, Copy, Default)] +pub struct GrepConfig; + +impl ParserConfig for GrepConfig { + fn enable_path_segments(&self) -> bool { + true + } + + fn enable_git_status(&self) -> bool { + false + } + + fn enable_location(&self) -> bool { + false + } + + /// Only recognise globs that are clearly directory/path oriented. + /// + /// Characters like `?`, `[`, and bare `*` (without `/`) are extremely + /// common in source code (`foo?`, `arr[0]`, `*ptr`) and must NOT be + /// consumed as glob constraints. We only treat a token as a glob when + /// it contains path-oriented patterns: + /// + /// - Contains `/` → path glob (e.g. `src/**/*.rs`, `*/tests/*`) + /// - Contains `{…}` → brace expansion (e.g. `{src,lib}`) + fn is_glob_pattern(&self, token: &str) -> bool { + // Must contain at least one glob wildcard character + if !has_wildcards(token) { + return false; + } + + let bytes = token.as_bytes(); + + // Contains path separator → clearly a path glob + if bytes.contains(&b'/') { + return true; + } + + // Brace expansion → useful for directory alternatives. + // Require a comma between `{` and `}` AND at least one letter to + // distinguish real glob expansions like `{src,lib}` or `*.{ts,tsx}` + // from code patterns like `format!("{}")` and regex quantifiers `{2,3}`. + // Guard `open < close` so reversed braces like `}{` don't panic on slicing. + if let Some(open) = bytes.iter().position(|&b| b == b'{') + && let Some(close) = bytes.iter().rposition(|&b| b == b'}') + && open < close + { + let inner = &bytes[open + 1..close]; + if inner.contains(&b',') && inner.iter().any(|b| b.is_ascii_alphabetic()) { + return true; + } + } + + // Everything else (?, [, bare * without /) → treat as literal text + false + } +} + +/// Configuration for AI-mode grep — extends `GrepConfig` behavior with +/// automatic file-path constraint detection. +/// +/// Bare filenames with valid extensions (`schema.rs`) and path-prefixed +/// filenames (`libswscale/input.c`) are detected as `FilePath` constraints +/// so the search is scoped to matching files. The caller validates the +/// constraint against the index and drops it if no files match (fallback). +#[derive(Debug, Clone, Copy, Default)] +pub struct AiGrepConfig; + +/// Configuration for directory and mixed search modes. +/// +/// Disables path segment parsing so that trailing `/` is kept as fuzzy text +/// (e.g. `fff-core/` fuzzy-matches directory paths instead of becoming a +/// `PathSegment("fff-core")` constraint with an empty query). Extension and +/// filename constraints are also disabled since they don't apply to directories. +#[derive(Debug, Clone, Copy, Default)] +pub struct DirSearchConfig; + +impl ParserConfig for AiGrepConfig { + fn enable_path_segments(&self) -> bool { + true + } + + fn enable_git_status(&self) -> bool { + false + } + + fn enable_location(&self) -> bool { + false + } + + fn enable_filename_constraint(&self) -> bool { + true + } + + fn is_glob_pattern(&self, token: &str) -> bool { + // First check GrepConfig's strict rules (path globs, brace expansion) + if GrepConfig.is_glob_pattern(token) { + return true; + } + + // AI agents use `*text*` to scope file searches (e.g. `*quote* TODO`). + // Recognise tokens that start AND end with `*` with non-empty text + // between them as glob constraints. Bare `*` or `**` are excluded. + if !has_wildcards(token) { + return false; + } + let bytes = token.as_bytes(); + if bytes.len() >= 3 + && bytes[0] == b'*' + && bytes[bytes.len() - 1] == b'*' + && bytes[1..bytes.len() - 1].iter().all(|&b| b != b'*') + { + return true; + } + + false + } +} + +impl ParserConfig for DirSearchConfig { + fn enable_path_segments(&self) -> bool { + false + } + + fn enable_extension(&self) -> bool { + false + } + + fn enable_type_filter(&self) -> bool { + false + } + + fn enable_git_status(&self) -> bool { + false + } +} + +/// Configuration for mixed (files + directories) search. +/// +/// Like `DirSearchConfig`, disables path segment parsing so trailing `/` +/// triggers dirs-only mode instead of becoming a constraint. Keeps git +/// status and extension filters enabled since files are part of the results. +#[derive(Debug, Clone, Copy, Default)] +pub struct MixedSearchConfig; + +impl ParserConfig for MixedSearchConfig { + fn enable_path_segments(&self) -> bool { + false + } +} diff --git a/crates/fff-query-parser/src/constraints.rs b/crates/fff-query-parser/src/constraints.rs new file mode 100644 index 0000000..74d2c68 --- /dev/null +++ b/crates/fff-query-parser/src/constraints.rs @@ -0,0 +1,83 @@ +use crate::glob_detect::has_wildcards; + +/// Constraint types that can be extracted from a query +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Constraint<'a> { + /// Match file extension: *.rs -> Extension("rs") + Extension(&'a str), + + /// Glob pattern: **/*.rs -> Glob("**/*.rs") + Glob(&'a str), + + /// Multiple text search parts: ["src", "name"] + /// Uses slice to avoid allocation + Parts(&'a [&'a str]), + + /// Single text token (optimized case) + Text(&'a str), + + /// Exclude pattern: !test -> Exclude(&["test"]) + Exclude(&'a [&'a str]), + + /// Path constraint: /src/ -> PathSegment("src") + PathSegment(&'a str), + + /// File path constraint (AI mode): "libswscale/input.c" → FilePath("libswscale/input.c") + /// Matches files whose relative path ends with this suffix at a `/` boundary. + FilePath(&'a str), + + /// File type constraint: type:rust -> FileType("rust") + FileType(&'a str), + + /// Git status constraint: status:modified -> GitStatus(Modified) + GitStatus(GitStatusFilter), + + /// Negation constraint: !extension:rs -> Not(Extension("rs")) + /// Negates the inner constraint + Not(Box>), +} + +impl Constraint<'_> { + #[inline(always)] + pub fn is_filename_constraint_token(token: &str) -> bool { + let bytes = token.as_bytes(); + + // Must NOT end with / or . + if token.is_empty() || (bytes.last() == Some(&b'/') && bytes.first() != Some(&b'.')) { + return false; + } + + // Must NOT contain wildcards (those are globs) + if has_wildcards(token) { + return false; + } + + // Get the filename component (after last /) + let filename = token.rsplit('/').next().unwrap_or(token); + + // Extension must exist and look like a real file extension: + // starts with an ASCII letter (rejects version numbers like "v2.0"), + // followed by alphanumeric chars, max 10 chars total. + match filename.rfind('.') { + None => false, + Some(dot_idx) => { + let extension = &filename[dot_idx + 1..]; + + !extension.is_empty() + && extension.len() <= 10 // just an sassumption + && extension.bytes().all(|b| b.is_ascii_alphanumeric()) + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GitStatusFilter { + Modified, + Untracked, + Staged, + Unmodified, +} + +/// Buffer for text parts during query parsing. +pub(crate) type TextPartsBuffer<'a> = Vec<&'a str>; diff --git a/crates/fff-query-parser/src/glob_detect.rs b/crates/fff-query-parser/src/glob_detect.rs new file mode 100644 index 0000000..3a2fa04 --- /dev/null +++ b/crates/fff-query-parser/src/glob_detect.rs @@ -0,0 +1,18 @@ +//! Glob wildcard detection — delegates to zlob when available, pure-Rust fallback otherwise. +//! +//! All call sites use a single function: `has_wildcards(text) -> bool`. +//! When the `zlob` feature is enabled this calls `zlob::has_wildcards` with +//! `ZlobFlags::RECOMMENDED`; without it we check for the same set of wildcard +//! characters (`*`, `?`, `[`, `{`) in pure Rust. + +#[cfg(feature = "zlob")] +#[inline] +pub fn has_wildcards(s: &str) -> bool { + zlob::has_wildcards(s, zlob::ZlobFlags::RECOMMENDED) +} + +#[cfg(not(feature = "zlob"))] +#[inline] +pub fn has_wildcards(s: &str) -> bool { + s.bytes().any(|b| matches!(b, b'*' | b'?' | b'[' | b'{')) +} diff --git a/crates/fff-query-parser/src/lib.rs b/crates/fff-query-parser/src/lib.rs new file mode 100644 index 0000000..8dfcd4d --- /dev/null +++ b/crates/fff-query-parser/src/lib.rs @@ -0,0 +1,235 @@ +//! Fast query parser for file search +//! +//! This parser takes a search query and extracts structured constraints +//! while preserving text for fuzzy matching. Designed for maximum performance: +//! - Single-pass parsing with minimal branching +//! - Stack-allocated string buffers +//! +//! # Examples +//! +//! ``` +//! use fff_query_parser::{QueryParser, Constraint, FuzzyQuery}; +//! +//! let parser = QueryParser::default(); +//! +//! // Single-token queries return FFFQuery with Text fuzzy query and no constraints +//! let result = parser.parse("hello"); +//! assert!(result.constraints.is_empty()); +//! assert_eq!(result.fuzzy_query, FuzzyQuery::Text("hello")); +//! +//! // Multi-token queries are parsed +//! let result = parser.parse("name *.rs"); +//! match &result.fuzzy_query { +//! FuzzyQuery::Text(text) => assert_eq!(*text, "name"), +//! _ => panic!("Expected text"), +//! } +//! assert!(matches!(result.constraints[0], Constraint::Extension("rs"))); +//! +//! // Parse glob pattern with text +//! let result = parser.parse("**/*.rs foo"); +//! assert!(matches!(result.constraints[0], Constraint::Glob("**/*.rs"))); +//! +//! // Parse negation +//! let result = parser.parse("!*.rs foo"); +//! match &result.constraints[0] { +//! Constraint::Not(inner) => { +//! assert!(matches!(inner.as_ref(), Constraint::Extension("rs"))); +//! } +//! _ => panic!("Expected Not constraint"), +//! } +//! ``` + +mod config; +mod constraints; +pub mod glob_detect; +pub mod location; +mod parser; + +#[allow(deprecated)] +pub use config::is_filename_constraint_token; +pub use config::{ + AiGrepConfig, DirSearchConfig, FileSearchConfig, GrepConfig, MixedSearchConfig, ParserConfig, +}; +pub use constraints::{Constraint, GitStatusFilter}; +pub use location::Location; +pub use parser::{FFFQuery, FuzzyQuery, QueryParser}; + +pub type ConstraintVec<'a> = Vec>; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_query() { + let parser = QueryParser::default(); + let result = parser.parse(""); + assert!(result.constraints.is_empty()); + assert_eq!(result.fuzzy_query, FuzzyQuery::Empty); + } + + #[test] + fn test_whitespace_only() { + let parser = QueryParser::default(); + let result = parser.parse(" "); + assert!(result.constraints.is_empty()); + assert_eq!(result.fuzzy_query, FuzzyQuery::Empty); + } + + #[test] + fn test_single_token() { + let parser = QueryParser::default(); + let result = parser.parse("hello"); + assert!(result.constraints.is_empty()); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("hello")); + } + + #[test] + fn test_simple_text() { + let parser = QueryParser::default(); + let result = parser.parse("hello world"); + + match &result.fuzzy_query { + FuzzyQuery::Parts(parts) => { + assert_eq!(parts.len(), 2); + assert_eq!(parts[0], "hello"); + assert_eq!(parts[1], "world"); + } + _ => panic!("Expected Parts fuzzy query"), + } + + assert_eq!(result.constraints.len(), 0); + } + + #[test] + fn test_extension_only() { + let parser = QueryParser::default(); + // Single constraint token - returns Some so constraint can be applied + let result = parser.parse("*.rs"); + assert!(matches!(result.fuzzy_query, FuzzyQuery::Empty)); + assert_eq!(result.constraints.len(), 1); + assert!(matches!(result.constraints[0], Constraint::Extension("rs"))); + } + + #[test] + fn test_glob_pattern() { + let parser = QueryParser::default(); + let result = parser.parse("**/*.rs foo"); + assert_eq!(result.constraints.len(), 1); + // Glob patterns with ** are treated as globs, not extensions + match &result.constraints[0] { + Constraint::Glob(pattern) => assert_eq!(*pattern, "**/*.rs"), + other => panic!("Expected Glob constraint, got {:?}", other), + } + } + + #[test] + fn test_negation_pattern() { + let parser = QueryParser::default(); + let result = parser.parse("!test foo"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!(matches!(**inner, Constraint::Text("test"))); + } + _ => panic!("Expected Not constraint"), + } + } + + #[test] + fn test_path_segment() { + let parser = QueryParser::default(); + let result = parser.parse("/src/ foo"); + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::PathSegment("src") + )); + } + + #[test] + fn test_git_status() { + let parser = QueryParser::default(); + let result = parser.parse("status:modified foo"); + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::GitStatus(GitStatusFilter::Modified) + )); + } + + #[test] + fn test_file_type() { + let parser = QueryParser::default(); + let result = parser.parse("type:rust foo"); + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::FileType("rust") + )); + } + + #[test] + fn test_complex_query() { + let parser = QueryParser::default(); + let result = parser.parse("src name *.rs !test /lib/ status:modified"); + + // Verify we have fuzzy text + match &result.fuzzy_query { + FuzzyQuery::Parts(parts) => { + assert_eq!(parts.len(), 2); + assert_eq!(parts[0], "src"); + assert_eq!(parts[1], "name"); + } + _ => panic!("Expected Parts fuzzy query"), + } + + // Should have multiple constraints + assert!(result.constraints.len() >= 4); + + // Verify specific constraints exist + let has_extension = result + .constraints + .iter() + .any(|c| matches!(c, Constraint::Extension("rs"))); + let has_not = result + .constraints + .iter() + .any(|c| matches!(c, Constraint::Not(_))); + let has_path = result + .constraints + .iter() + .any(|c| matches!(c, Constraint::PathSegment("lib"))); + let has_git_status = result + .constraints + .iter() + .any(|c| matches!(c, Constraint::GitStatus(_))); + + assert!(has_extension, "Should have Extension constraint"); + assert!(has_not, "Should have Not constraint"); + assert!(has_path, "Should have PathSegment constraint"); + assert!(has_git_status, "Should have GitStatus constraint"); + } + + #[test] + fn test_small_constraint_count() { + let parser = QueryParser::default(); + let result = parser.parse("*.rs *.toml !test"); + assert_eq!(result.constraints.len(), 3); + } + + #[test] + fn test_many_fuzzy_parts() { + let parser = QueryParser::default(); + let result = parser.parse("one two three four five six"); + + match &result.fuzzy_query { + FuzzyQuery::Parts(parts) => { + assert_eq!(parts.len(), 6); + assert_eq!(parts[0], "one"); + assert_eq!(parts[5], "six"); + } + _ => panic!("Expected Parts fuzzy query"), + } + } +} diff --git a/crates/fff-query-parser/src/location.rs b/crates/fff-query-parser/src/location.rs new file mode 100644 index 0000000..9f951bb --- /dev/null +++ b/crates/fff-query-parser/src/location.rs @@ -0,0 +1,265 @@ +//! Location parsing for file:line:col patterns +//! +//! Parses various location formats like: +//! - `file:12` - Line number +//! - `file:12:4` - Line and column +//! - `file:12-114` - Line range +//! - `file:12:4-20` - Column range on same line +//! - `file:12:4-14:20` - Position range +//! - `file(12)` - Visual Studio style line +//! - `file(12,4)` - Visual Studio style line and column + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum Location { + Line(i32), + Range { start: (i32, i32), end: (i32, i32) }, + Position { line: i32, col: i32 }, +} + +fn parse_number_pair(location: &str, split_char: char) -> Option<(i32, i32)> { + let mut iter = location.split(split_char); + + let start_str = iter.next()?; + let end_str = iter.next()?; + + // if there are more than 2 parts it's not the range treat as normal query + if iter.next().is_some() { + return None; + } + + let start = start_str.parse::().ok()?; + let end = end_str.parse::().ok()?; + + Some((start, end)) +} + +/// Parse "line-line" format +fn parse_simple_range(location: &str) -> Option { + let (start, end) = parse_number_pair(location, '-')?; + if end < start { + return Some(Location::Line(start)); + } + + Some(Location::Range { + start: (start, 0), + end: (end, 0), + }) +} + +/// Parse "line:col-col" format (column range on same line) +fn parse_column_range(start_part: &str, end_part: &str) -> Option { + let (line_str, start_col_str) = start_part.split_once(':')?; + let line = line_str.parse::().ok()?; + let start_col = start_col_str.parse::().ok()?; + let end_col = end_part.parse::().ok()?; + + if end_col < start_col { + return Some(Location::Line(line)); + } + + Some(Location::Range { + start: (line, start_col), + end: (line, end_col), + }) +} + +/// Parse "line:col-line:col" format (position range) +fn parse_position_range(start_part: &str, end_part: &str) -> Option { + let (start_line, start_col) = parse_number_pair(start_part, ':')?; + let (end_line, end_col) = parse_number_pair(end_part, ':')?; + + if end_line < start_line || (end_line == start_line && end_col < start_col) { + return Some(Location::Position { + line: start_line, + col: start_col, + }); + } + + Some(Location::Range { + start: (start_line, start_col), + end: (end_line, end_col), + }) +} + +/// Try to parse range patterns (contains '-') +fn try_parse_column_range(location: &str) -> Option { + if !location.contains('-') { + return None; + } + + let (start_part, end_part) = location.split_once('-')?; + + // Try position range (line:col-line:col) + if start_part.contains(':') && end_part.contains(':') { + return parse_position_range(start_part, end_part); + } + + // Try column range (line:col-col) + if start_part.contains(':') { + return parse_column_range(start_part, end_part); + } + + // Try simple line range (line-line) + parse_simple_range(location) +} + +/// Try to parse position patterns (contains ':' but not '-') +fn try_parse_column_position(location: &str) -> Option { + if !location.contains(':') { + return None; + } + + let (line_str, col_str) = location.split_once(':')?; + let line = line_str.parse::().ok()?; + let col = col_str.parse::().ok()?; + + Some(Location::Position { line, col }) +} + +/// Parses various location formats like file:12, file:12:4, file:12-114 +fn parse_column_location(query: &str) -> Option<(&str, Location)> { + let (file_path, location_part) = query.split_once(':')?; + + if let Some(range_location) = try_parse_column_range(location_part) { + return Some((file_path, range_location)); + } + + if let Some(position_location) = try_parse_column_position(location_part) { + return Some((file_path, position_location)); + } + + if let Ok(line_location) = location_part.parse::() { + return Some((file_path, Location::Line(line_location))); + } + + None +} + +fn parse_vstudio_location(query: &str) -> Option<(&str, Location)> { + if !query.ends_with(')') { + return None; + } + + let (file_path, location_with_paren) = query.rsplit_once('(')?; + let location = location_with_paren.trim_end_matches(')'); + + if let Ok(line) = location.parse::() { + return Some((file_path, Location::Line(line))); + } + + if let Some((line, col)) = parse_number_pair(location, ',') { + return Some((file_path, Location::Position { line, col })); + } + + None +} + +/// Parse location from the end of a query string. +/// +/// Returns the query without the location suffix, and the parsed location if found. +/// +/// # Examples +/// ``` +/// use fff_query_parser::location::{parse_location, Location}; +/// +/// let (query, loc) = parse_location("file:12"); +/// assert_eq!(query, "file"); +/// assert_eq!(loc, Some(Location::Line(12))); +/// +/// let (query, loc) = parse_location("search term"); +/// assert_eq!(query, "search term"); +/// assert_eq!(loc, None); +/// ``` +pub fn parse_location(query: &str) -> (&str, Option) { + // simply ignore the last semicolon even if there are no additional location info + let query = query.trim_end_matches([':', '-', '(']); + if let Some((path, location)) = parse_column_location(query) { + return (path, Some(location)); + } + + if let Some((path, location)) = parse_vstudio_location(query) { + return (path, Some(location)); + } + + (query, None) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_location_parsing() { + assert_eq!( + parse_location("new_file:12"), + ("new_file", Some(Location::Line(12))) + ); + assert_eq!(parse_location("new_file:12ab"), ("new_file:12ab", None)); + + assert_eq!(parse_location("something"), ("something", None)); + assert_eq!( + parse_location("file:12:4"), + ("file", Some(Location::Position { line: 12, col: 4 })) + ); + + assert_eq!( + parse_location("file:12-114"), + ( + "file", + Some(Location::Range { + start: (12, 0), + end: (114, 0) + }) + ) + ); + + assert_eq!( + parse_location("file:12:4-20"), + ( + "file", + Some(Location::Range { + start: (12, 4), + end: (12, 20) + }) + ) + ); + + assert_eq!( + parse_location("file:100:4-14:20"), + ("file", Some(Location::Position { line: 100, col: 4 })) + ); + + assert_eq!( + parse_location("file:12:4-14:20"), + ( + "file", + Some(Location::Range { + start: (12, 4), + end: (14, 20) + }) + ) + ); + } + + #[test] + fn test_vstudio_parsing() { + assert_eq!( + parse_location("file(12)"), + ("file", Some(Location::Line(12))) + ); + assert_eq!( + parse_location("file(12,4)"), + ("file", Some(Location::Position { line: 12, col: 4 })) + ); + } + + #[test] + fn trimes_end_character() { + assert_eq!( + parse_location("file:12-"), + ("file", Some(Location::Line(12))) + ); + assert_eq!(parse_location("file:-"), ("file", None)); + assert_eq!(parse_location("file("), ("file", None)); + } +} diff --git a/crates/fff-query-parser/src/parser.rs b/crates/fff-query-parser/src/parser.rs new file mode 100644 index 0000000..88a32e9 --- /dev/null +++ b/crates/fff-query-parser/src/parser.rs @@ -0,0 +1,1467 @@ +use crate::ConstraintVec; +use crate::config::ParserConfig; +use crate::constraints::{Constraint, GitStatusFilter, TextPartsBuffer}; +use crate::glob_detect::has_wildcards; +use crate::location::{Location, parse_location}; + +#[derive(Debug, Clone, PartialEq)] +#[allow(clippy::large_enum_variant)] +pub enum FuzzyQuery<'a> { + Parts(TextPartsBuffer<'a>), + Text(&'a str), + Empty, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FFFQuery<'a> { + /// The original raw query string before parsing + pub raw_query: &'a str, + /// Parsed constraints (stack-allocated for ≤8 constraints) + pub constraints: ConstraintVec<'a>, + pub fuzzy_query: FuzzyQuery<'a>, + /// Parsed location (e.g., file:12:4 -> line 12, col 4) + pub location: Option, +} + +impl<'a> FFFQuery<'a> { + /// Parse query and execute, perfectly paired with configuration presets + /// + /// ``` + /// use fff_query_parser::{FFFQuery, FileSearchConfig}; + /// + /// let query = FFFQuery::parse("file *.rs", FileSearchConfig); + /// ``` + pub fn parse(query: &'a str, config: impl ParserConfig) -> Self { + let query_parser = QueryParser::new(config); + + query_parser.parse(query.as_ref()) + } +} + +/// Main query parser - zero-cost wrapper around configuration +#[derive(Debug)] +pub struct QueryParser { + config: C, +} + +impl QueryParser { + pub fn new(config: C) -> Self { + Self { config } + } + + pub fn parse<'a>(&self, query: &'a str) -> FFFQuery<'a> { + let raw_query = query; + let config: &C = &self.config; + let mut constraints = ConstraintVec::new(); + let query = query.trim(); + + let whitespace_count = query.chars().filter(|c| c.is_whitespace()).count(); + + // Single token - check if it's a constraint or plain text + if whitespace_count == 0 { + // Try to parse as constraint first + if let Some(constraint) = parse_token(query, config) { + // Don't treat filename tokens (FilePath) as constraints in single-token + // queries — the user is fuzzy-searching, not filtering. FilePath constraints + // are only useful as filters in multi-token queries like "score.rs search". + // + // Also skip PathSegment constraints when the token looks like an absolute + // file path with a location suffix (e.g. /Users/.../file.rs:12). Without + // this, the leading `/` causes the entire path to be consumed as a + // PathSegment, preventing location parsing from running. + let has_location_suffix = matches!(constraint, Constraint::PathSegment(_)) + && query.bytes().any(|b| b == b':') + && query + .bytes() + .rev() + .take_while(|&b| b != b':') + .all(|b| b.is_ascii_digit()); + + // for grep we don't want to treat a part of path like pathname + let treat_as_text = matches!(constraint, Constraint::PathSegment(_)) + && config.treat_lone_path_as_text(); + + if !matches!(constraint, Constraint::FilePath(_)) + && !has_location_suffix + && !treat_as_text + { + constraints.push(constraint); + return FFFQuery { + raw_query, + constraints, + fuzzy_query: FuzzyQuery::Empty, + location: None, + }; + } + } + + // Try to extract location from single token (e.g., "file:12") + if config.enable_location() { + let (query_without_loc, location) = parse_location(query); + if location.is_some() { + return FFFQuery { + raw_query, + constraints, + fuzzy_query: FuzzyQuery::Text(query_without_loc), + location, + }; + } + } + + // Plain text single token + return FFFQuery { + raw_query, + constraints, + fuzzy_query: if query.is_empty() { + FuzzyQuery::Empty + } else { + FuzzyQuery::Text(query) + }, + location: None, + }; + } + + let mut text_parts = TextPartsBuffer::new(); + let tokens = query.split_whitespace(); + + let mut has_file_path = false; + // Track the FilePath token position in constraints so we can promote + // it back to text if the final query ends up with no fuzzy text. + let mut file_path_constraint_idx: Option = None; + let mut file_path_token: Option<&str> = None; + for token in tokens { + match parse_token(token, config) { + Some(Constraint::FilePath(_)) => { + if has_file_path { + // Only one FilePath constraint allowed; treat extra path + // tokens as literal text (e.g. an import path the user is + // searching for). + text_parts.push(token); + } else { + file_path_constraint_idx = Some(constraints.len()); + file_path_token = Some(token); + constraints.push(Constraint::FilePath(token)); + has_file_path = true; + } + } + Some(constraint) => { + constraints.push(constraint); + } + None => { + text_parts.push(token); + } + } + } + + // If the query produced a single FilePath and no fuzzy text parts, the + // user isn't filtering by filename suffix — they're fuzzy-searching + // for that name (the only other constraints are path-scoping like + // PathSegment/Extension/Glob). Mirror the single-token rule at + // parser.rs:48-64: promote FilePath → fuzzy text so e.g. `profile.h` + // alongside `chrome/browser/profiles/` fuzzy-matches all `profile*.h` + // files instead of only one file literally ending in `/profile.h`. + if text_parts.is_empty() + && let Some(idx) = file_path_constraint_idx + && let Some(tok) = file_path_token + { + constraints.remove(idx); + text_parts.push(tok); + } + + // Try to extract location from the last fuzzy token + // e.g., "search file:12" -> fuzzy="search file", location=Line(12) + let location = if config.enable_location() && !text_parts.is_empty() { + let last_idx = text_parts.len() - 1; + let (without_loc, loc) = parse_location(text_parts[last_idx]); + if loc.is_some() { + // Update the last part to be without the location suffix + text_parts[last_idx] = without_loc; + loc + } else { + None + } + } else { + None + }; + + let fuzzy_query = if text_parts.is_empty() { + FuzzyQuery::Empty + } else if text_parts.len() == 1 { + // If the only remaining text is empty after location extraction, treat as Empty + if text_parts[0].is_empty() { + FuzzyQuery::Empty + } else { + FuzzyQuery::Text(text_parts[0]) + } + } else { + // Filter out empty parts that might result from location extraction + if text_parts.iter().all(|p| p.is_empty()) { + FuzzyQuery::Empty + } else { + FuzzyQuery::Parts(text_parts) + } + }; + + FFFQuery { + raw_query, + constraints, + fuzzy_query, + location, + } + } +} + +impl<'a> FFFQuery<'a> { + /// Returns the grep search text by joining all non-constraint text tokens. + /// + /// Backslash-escaped tokens (e.g. `\*.rs`) are included as literal text + /// with the leading `\` stripped, since the backslash is only an escape + /// signal to the parser and should not appear in the final pattern. + /// + /// `FuzzyQuery::Empty` → empty string + /// `FuzzyQuery::Text("foo")` → `"foo"` + /// `FuzzyQuery::Parts(["a", "\\*.rs", "b"])` → `"a *.rs b"` + pub fn grep_text(&self) -> String { + match &self.fuzzy_query { + FuzzyQuery::Empty => String::new(), + FuzzyQuery::Text(t) => strip_leading_backslash(t).to_string(), + FuzzyQuery::Parts(parts) => parts + .iter() + .map(|t| strip_leading_backslash(t)) + .collect::>() + .join(" "), + } + } +} + +/// Strip the leading `\` from a backslash-escaped constraint token only. +/// +/// We strip the backslash when the next character is a constraint trigger +/// (`*`, `/`, `!`) — the user typed `\*.rs` to mean literal `*.rs`, not an +/// extension constraint. For regex escape sequences like `\w`, `\b`, `\d`, +/// `\s`, `\n` etc., the backslash is preserved so regex mode works correctly. +#[inline] +fn strip_leading_backslash(token: &str) -> &str { + if token.len() > 1 && token.starts_with('\\') { + let next = token.as_bytes()[1]; + // Only strip if the backslash is escaping a constraint trigger character + if next == b'*' || next == b'/' || next == b'!' { + return &token[1..]; + } + } + token +} + +impl Default for QueryParser { + fn default() -> Self { + Self::new(crate::FileSearchConfig) + } +} + +#[inline] +fn parse_token<'a, C: ParserConfig>(token: &'a str, config: &C) -> Option> { + // Backslash escape: \token → treat as literal text, skip all constraint parsing. + // The leading \ is stripped by the caller when building the search text. + if token.starts_with('\\') && token.len() > 1 { + return None; + } + + let first_byte = token.as_bytes().first()?; + + match first_byte { + b'*' if config.enable_extension() => { + // Ignore incomplete patterns like "*" or "*." + if token == "*" || token == "*." { + return None; + } + + // Try extension first (*.rs) - simple patterns without additional wildcards + if let Some(constraint) = parse_extension(token) { + // Only return Extension if the rest doesn't have wildcards + // e.g., *.rs is Extension, but *.test.* should be Glob + let ext_part = &token[2..]; + if !has_wildcards(ext_part) { + return Some(constraint); + } + } + // Has wildcards -> use config-specific glob detection + if config.enable_glob() && config.is_glob_pattern(token) { + return Some(Constraint::Glob(token)); + } + None + } + b'!' if config.enable_exclude() => parse_negation(token, config), + b'/' if config.enable_path_segments() => parse_path_segment(token), + // Handle trailing slash syntax: www/ -> PathSegment("www") + _ if config.enable_path_segments() && token.ends_with('/') => { + parse_path_segment_trailing(token) + } + // tokens like `file.rs` *if enabled* + _ if config.enable_filename_constraint() + && !token.ends_with('/') + && Constraint::is_filename_constraint_token(token) => + { + Some(Constraint::FilePath(token)) + } + _ => { + // Check for glob patterns using config-specific detection + if config.enable_glob() && config.is_glob_pattern(token) { + return Some(Constraint::Glob(token)); + } + + // Check for key:value patterns + if let Some(colon_idx) = memchr(b':', token.as_bytes()) { + let (key, value_with_colon) = token.split_at(colon_idx); + let value = &value_with_colon[1..]; // Skip the colon + + match key { + "type" if config.enable_type_filter() => { + return Some(Constraint::FileType(value)); + } + "status" | "st" | "g" | "git" if config.enable_git_status() => { + return parse_git_status(value); + } + _ => {} + } + } + + // Try custom parsers + config.parse_custom(token) + } + } +} + +/// Scalar byte find. Queries are tiny (<100 bytes), so this stays dependency-free +/// instead of pulling in the `memchr` crate; fff-core uses `memchr` for real haystacks. +#[inline] +fn memchr(needle: u8, haystack: &[u8]) -> Option { + haystack.iter().position(|&b| b == needle) +} + +/// Parse extension pattern: *.rs -> Extension("rs") +#[inline] +fn parse_extension(token: &str) -> Option> { + if token.len() > 2 && token.starts_with("*.") { + Some(Constraint::Extension(&token[2..])) + } else { + None + } +} + +/// Parse negation pattern: !*.rs -> Not(Extension("rs")), !test -> Not(Text("test")) +/// This allows negating any constraint type +#[inline] +fn parse_negation<'a, C: ParserConfig>(token: &'a str, config: &C) -> Option> { + if token.len() <= 1 { + return None; + } + + let inner_token = &token[1..]; + + // Try to parse the inner token as any constraint + if let Some(inner_constraint) = parse_token_without_negation(inner_token, config) { + // Wrap it in a Not constraint + return Some(Constraint::Not(Box::new(inner_constraint))); + } + + // If it's not a special constraint, treat it as negated text + // For backward compatibility with !test syntax + Some(Constraint::Not(Box::new(Constraint::Text(inner_token)))) +} + +/// Parse a token without checking for negation (to avoid infinite recursion) +#[inline] +fn parse_token_without_negation<'a, C: ParserConfig>( + token: &'a str, + config: &C, +) -> Option> { + // Backslash escape applies here too + if token.starts_with('\\') && token.len() > 1 { + return None; + } + + let first_byte = token.as_bytes().first()?; + + match first_byte { + b'*' if config.enable_extension() => { + // Try extension first (*.rs) - simple patterns without additional wildcards + if let Some(constraint) = parse_extension(token) { + let ext_part = &token[2..]; + if !has_wildcards(ext_part) { + return Some(constraint); + } + } + // Has wildcards -> use config-specific glob detection + if config.enable_glob() && config.is_glob_pattern(token) { + return Some(Constraint::Glob(token)); + } + None + } + b'/' if config.enable_path_segments() => parse_path_segment(token), + _ if config.enable_path_segments() && token.ends_with('/') => { + // Handle trailing slash syntax: www/ -> PathSegment("www") + parse_path_segment_trailing(token) + } + _ => { + // Check for glob patterns using config-specific detection + if config.enable_glob() && config.is_glob_pattern(token) { + return Some(Constraint::Glob(token)); + } + + // Check for key:value patterns + if let Some(colon_idx) = memchr(b':', token.as_bytes()) { + let (key, value_with_colon) = token.split_at(colon_idx); + let value = &value_with_colon[1..]; // Skip the colon + + match key { + "type" if config.enable_type_filter() => { + return Some(Constraint::FileType(value)); + } + "status" | "st" | "g" | "git" if config.enable_git_status() => { + return parse_git_status(value); + } + _ => {} + } + } + + if config.enable_filename_constraint() + && Constraint::is_filename_constraint_token(token) + { + return Some(Constraint::FilePath(token)); + } + + config.parse_custom(token) + } + } +} + +/// Parse path segment: /src/ -> PathSegment("src") +#[inline] +fn parse_path_segment(token: &str) -> Option> { + if token.len() > 1 && token.starts_with('/') { + let segment = token.trim_start_matches('/').trim_end_matches('/'); + if !segment.is_empty() { + Some(Constraint::PathSegment(segment)) + } else { + None + } + } else { + None + } +} + +/// Parse path segment with trailing slash: www/ -> PathSegment("www") +/// Also supports multi-segment paths: libswscale/aarch64/ -> PathSegment("libswscale/aarch64") +#[inline] +fn parse_path_segment_trailing(token: &str) -> Option> { + if token.len() > 1 && token.ends_with('/') { + let segment = token.trim_end_matches('/'); + if !segment.is_empty() { + Some(Constraint::PathSegment(segment)) + } else { + None + } + } else { + None + } +} + +/// Parse git status filter: modified|m|untracked|u|staged|s +#[inline] +fn parse_git_status(value: &str) -> Option> { + if value == "*" { + return None; + } + + if "modified".starts_with(value) { + return Some(Constraint::GitStatus(GitStatusFilter::Modified)); + } + + if "untracked".starts_with(value) { + return Some(Constraint::GitStatus(GitStatusFilter::Untracked)); + } + + if "staged".starts_with(value) { + return Some(Constraint::GitStatus(GitStatusFilter::Staged)); + } + + if "clean".starts_with(value) { + return Some(Constraint::GitStatus(GitStatusFilter::Unmodified)); + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{FileSearchConfig, GrepConfig}; + + /// File-picker-like config with filename-constraint detection enabled, + /// mirroring the Neovim layer's opt-in behavior. + struct FilenameConstraintConfig; + + impl ParserConfig for FilenameConstraintConfig { + fn enable_filename_constraint(&self) -> bool { + true + } + } + + #[test] + fn test_parse_extension() { + assert_eq!(parse_extension("*.rs"), Some(Constraint::Extension("rs"))); + assert_eq!( + parse_extension("*.toml"), + Some(Constraint::Extension("toml")) + ); + assert_eq!(parse_extension("*"), None); + assert_eq!(parse_extension("*."), None); + } + + #[test] + fn test_incomplete_patterns_ignored() { + let config = FileSearchConfig; + // Incomplete patterns should return None and be treated as noise + assert_eq!(parse_token("*", &config), None); + assert_eq!(parse_token("*.", &config), None); + } + + #[test] + fn test_parse_path_segment() { + assert_eq!( + parse_path_segment("/src/"), + Some(Constraint::PathSegment("src")) + ); + assert_eq!( + parse_path_segment("/lib"), + Some(Constraint::PathSegment("lib")) + ); + assert_eq!(parse_path_segment("/"), None); + } + + #[test] + fn test_parse_path_segment_trailing() { + assert_eq!( + parse_path_segment_trailing("www/"), + Some(Constraint::PathSegment("www")) + ); + assert_eq!( + parse_path_segment_trailing("src/"), + Some(Constraint::PathSegment("src")) + ); + // Multi-segment paths should work + assert_eq!( + parse_path_segment_trailing("src/lib/"), + Some(Constraint::PathSegment("src/lib")) + ); + assert_eq!( + parse_path_segment_trailing("libswscale/aarch64/"), + Some(Constraint::PathSegment("libswscale/aarch64")) + ); + // Should not match without trailing slash + assert_eq!(parse_path_segment_trailing("www"), None); + } + + #[test] + fn test_trailing_slash_in_query() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("www/ test"); + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::PathSegment("www") + )); + assert!(matches!(result.fuzzy_query, FuzzyQuery::Text("test"))); + } + + #[test] + fn test_parse_git_status() { + assert_eq!( + parse_git_status("modified"), + Some(Constraint::GitStatus(GitStatusFilter::Modified)) + ); + assert_eq!( + parse_git_status("m"), + Some(Constraint::GitStatus(GitStatusFilter::Modified)) + ); + assert_eq!( + parse_git_status("untracked"), + Some(Constraint::GitStatus(GitStatusFilter::Untracked)) + ); + assert_eq!(parse_git_status("invalid"), None); + } + + #[test] + fn test_memchr() { + assert_eq!(memchr(b':', b"type:rust"), Some(4)); + assert_eq!(memchr(b':', b"nocolon"), None); + assert_eq!(memchr(b':', b":start"), Some(0)); + } + + #[test] + fn test_negation_text() { + let parser = QueryParser::new(FileSearchConfig); + // Need two tokens for parsing to return Some + let result = parser.parse("!test foo"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!(matches!(**inner, Constraint::Text("test"))); + } + _ => panic!("Expected Not constraint"), + } + } + + #[test] + fn test_negation_extension() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("!*.rs foo"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!(matches!(**inner, Constraint::Extension("rs"))); + } + _ => panic!("Expected Not(Extension) constraint"), + } + } + + #[test] + fn test_negation_path_segment() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("!/src/ foo"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!(matches!(**inner, Constraint::PathSegment("src"))); + } + _ => panic!("Expected Not(PathSegment) constraint"), + } + } + + #[test] + fn test_negation_git_status() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("!status:modified foo"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!(matches!( + **inner, + Constraint::GitStatus(GitStatusFilter::Modified) + )); + } + _ => panic!("Expected Not(GitStatus) constraint"), + } + } + + #[test] + fn test_negation_git_status_all_key_aliases() { + let parser = QueryParser::new(FileSearchConfig); + for key in ["status", "st", "g", "git"] { + let query = format!("!{key}:modified foo"); + let result = parser.parse(&query); + assert_eq!( + result.constraints.len(), + 1, + "!{key}:modified should produce exactly one constraint" + ); + match &result.constraints[0] { + Constraint::Not(inner) => assert!( + matches!(**inner, Constraint::GitStatus(GitStatusFilter::Modified)), + "!{key}:modified expected Not(GitStatus(Modified)), got Not({inner:?})" + ), + other => { + panic!("!{key}:modified expected Not(GitStatus), got {other:?}") + } + } + } + } + + #[test] + fn test_backslash_escape_extension() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("\\*.rs foo"); + // \*.rs should NOT be parsed as an Extension constraint + assert_eq!(result.constraints.len(), 0); + // Both tokens should be text + match result.fuzzy_query { + FuzzyQuery::Parts(parts) => { + assert_eq!(parts.len(), 2); + assert_eq!(parts[0], "\\*.rs"); + assert_eq!(parts[1], "foo"); + } + _ => panic!("Expected Parts, got {:?}", result.fuzzy_query), + } + } + + #[test] + fn test_backslash_escape_path_segment() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("\\/src/ foo"); + assert_eq!(result.constraints.len(), 0); + match result.fuzzy_query { + FuzzyQuery::Parts(parts) => { + assert_eq!(parts[0], "\\/src/"); + assert_eq!(parts[1], "foo"); + } + _ => panic!("Expected Parts, got {:?}", result.fuzzy_query), + } + } + + #[test] + fn test_backslash_escape_negation() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("\\!test foo"); + assert_eq!(result.constraints.len(), 0); + } + + #[test] + fn test_grep_text_plain_text() { + // Multi-token plain text — no constraints + let q = QueryParser::new(GrepConfig).parse("name ="); + assert_eq!(q.grep_text(), "name ="); + } + + #[test] + fn test_grep_text_strips_constraint() { + let q = QueryParser::new(GrepConfig).parse("name = *.rs someth"); + assert_eq!(q.grep_text(), "name = someth"); + } + + #[test] + fn test_grep_text_leading_constraint() { + let q = QueryParser::new(GrepConfig).parse("*.rs name ="); + assert_eq!(q.grep_text(), "name ="); + } + + #[test] + fn test_grep_text_only_constraints() { + let q = QueryParser::new(GrepConfig).parse("*.rs /src/"); + assert_eq!(q.grep_text(), ""); + } + + #[test] + fn test_grep_text_path_constraint() { + let q = QueryParser::new(GrepConfig).parse("name /src/ value"); + assert_eq!(q.grep_text(), "name value"); + } + + #[test] + fn test_grep_text_negation_constraint() { + let q = QueryParser::new(GrepConfig).parse("name !*.rs value"); + assert_eq!(q.grep_text(), "name value"); + } + + #[test] + fn test_grep_text_backslash_escape_stripped() { + // \*.rs should be text with the leading \ removed + let q = QueryParser::new(GrepConfig).parse("\\*.rs foo"); + assert_eq!(q.grep_text(), "*.rs foo"); + + let q = QueryParser::new(GrepConfig).parse("\\/src/ foo"); + assert_eq!(q.grep_text(), "/src/ foo"); + + let q = QueryParser::new(GrepConfig).parse("\\!test foo"); + assert_eq!(q.grep_text(), "!test foo"); + } + + #[test] + fn test_grep_text_question_mark_is_text() { + let q = QueryParser::new(GrepConfig).parse("foo? bar"); + assert_eq!(q.grep_text(), "foo? bar"); + } + + #[test] + fn test_grep_text_bracket_is_text() { + let q = QueryParser::new(GrepConfig).parse("arr[0] more"); + assert_eq!(q.grep_text(), "arr[0] more"); + } + + #[test] + fn test_grep_text_path_glob_is_constraint() { + let q = QueryParser::new(GrepConfig).parse("pattern src/**/*.rs"); + assert_eq!(q.grep_text(), "pattern"); + } + + #[test] + fn test_grep_question_mark_is_text() { + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("foo?"); + assert!(result.constraints.is_empty()); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("foo?")); + } + + #[test] + fn test_grep_bracket_is_text() { + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("arr[0] something"); + // arr[0] should NOT be a glob in grep mode + assert_eq!(result.constraints.len(), 0); + } + + #[test] + fn test_grep_path_glob_is_constraint() { + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("pattern src/**/*.rs"); + // src/**/*.rs contains / so it should be treated as a glob + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::Glob("src/**/*.rs") + )); + } + + #[test] + fn test_grep_brace_is_constraint() { + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("pattern {src,lib}"); + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::Glob("{src,lib}") + )); + } + + #[test] + fn test_grep_text_preserves_backslash_escapes() { + // Regex patterns like \w+ and \bfoo\b must survive grep_text() + // The parser sees \w+ as a text token (not a constraint escape), + // but strip_leading_backslash was stripping the \ anyway. + let q = QueryParser::new(GrepConfig).parse("pub struct \\w+"); + assert_eq!( + q.grep_text(), + "pub struct \\w+", + "Backslash-w in regex must be preserved" + ); + + let q = QueryParser::new(GrepConfig).parse("\\bword\\b more"); + assert_eq!( + q.grep_text(), + "\\bword\\b more", + "Backslash-b word boundaries must be preserved" + ); + + // Single-token regex like "fn\\s+\\w+" returns FFFQuery with Text fuzzy query + let result = QueryParser::new(GrepConfig).parse("fn\\s+\\w+"); + assert!(result.constraints.is_empty()); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("fn\\s+\\w+")); + + // But the escaped constraint forms SHOULD still be stripped: + let q = QueryParser::new(GrepConfig).parse("\\*.rs foo"); + assert_eq!( + q.grep_text(), + "*.rs foo", + "Escaped constraint \\*.rs should still have backslash stripped" + ); + + let q = QueryParser::new(GrepConfig).parse("\\/src/ foo"); + assert_eq!( + q.grep_text(), + "/src/ foo", + "Escaped constraint \\/src/ should still have backslash stripped" + ); + } + + #[test] + fn test_grep_bare_star_is_text() { + let parser = QueryParser::new(GrepConfig); + // "a*b" contains * but no / or {} — should be text in grep mode + let result = parser.parse("a*b something"); + assert_eq!( + result.constraints.len(), + 0, + "bare * without / should be text" + ); + } + + #[test] + fn test_grep_negated_text() { + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("pattern !test"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!( + matches!(**inner, Constraint::Text("test")), + "Expected Not(Text(\"test\")), got Not({:?})", + inner + ); + } + other => panic!("Expected Not constraint, got {:?}", other), + } + } + + #[test] + fn test_grep_negated_path_segment() { + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("pattern !/src/"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!( + matches!(**inner, Constraint::PathSegment("src")), + "Expected Not(PathSegment(\"src\")), got Not({:?})", + inner + ); + } + other => panic!("Expected Not constraint, got {:?}", other), + } + } + + #[test] + fn test_grep_negated_extension() { + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("pattern !*.rs"); + assert_eq!(result.constraints.len(), 1); + match &result.constraints[0] { + Constraint::Not(inner) => { + assert!( + matches!(**inner, Constraint::Extension("rs")), + "Expected Not(Extension(\"rs\")), got Not({:?})", + inner + ); + } + other => panic!("Expected Not constraint, got {:?}", other), + } + } + + #[test] + fn test_ai_grep_detects_file_path() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("libswscale/input.c rgba32ToY"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!( + result.constraints[0], + Constraint::FilePath("libswscale/input.c") + ), + "Expected FilePath, got {:?}", + result.constraints[0] + ); + assert_eq!(result.grep_text(), "rgba32ToY"); + } + + #[test] + fn test_ai_grep_detects_nested_file_path() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("src/main.rs fn main"); + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::FilePath("src/main.rs") + )); + assert_eq!(result.grep_text(), "fn main"); + } + + #[test] + fn test_ai_grep_no_false_positive_trailing_slash() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("src/ pattern"); + // Should be PathSegment, NOT FilePath + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::PathSegment("src")), + "Expected PathSegment, got {:?}", + result.constraints[0] + ); + } + + #[test] + fn test_ai_grep_bare_filename_is_file_path() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("main.rs pattern"); + // Bare filename with valid extension → FilePath constraint + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::FilePath("main.rs")), + "Expected FilePath, got {:?}", + result.constraints[0] + ); + assert_eq!(result.grep_text(), "pattern"); + } + + #[test] + fn test_ai_grep_filename_with_pathsegment_only_promotes_to_text() { + // When the ONLY non-text constraints are path-scoping (PathSegment, + // here), a bare filename token like `profile.h` should NOT be used as + // a FilePath filter — the user is fuzzy-searching within that dir, + // not asking for files named exactly `profile.h`. + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("chrome/browser/profiles/ profile.h"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!( + result.constraints[0], + Constraint::PathSegment("chrome/browser/profiles") + ), + "Expected single PathSegment, got {:?}", + result.constraints + ); + assert_eq!(result.grep_text(), "profile.h"); + } + + #[test] + fn test_ai_grep_leading_slash_path_alone_is_text_not_path_segment() { + // A leading-slash multi-segment path like `/api/tests/` or `/api/tests` + // used as the sole query token should be treated as fuzzy text, NOT as + // a PathSegment constraint. The user is searching for files matching + // that path string, not trying to scope results to a directory. + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + + // With trailing slash + let result = parser.parse("/api/tests/"); + assert_eq!( + result.constraints.len(), + 0, + "Expected no constraints for '/api/tests/', got {:?}", + result.constraints + ); + assert!( + matches!(result.fuzzy_query, FuzzyQuery::Text("/api/tests/")), + "Expected FuzzyQuery::Text, got {:?}", + result.fuzzy_query + ); + + // Without trailing slash + let result = parser.parse("/api/tests"); + assert_eq!( + result.constraints.len(), + 0, + "Expected no constraints for '/api/tests', got {:?}", + result.constraints + ); + assert!( + matches!(result.fuzzy_query, FuzzyQuery::Text("/api/tests")), + "Expected FuzzyQuery::Text, got {:?}", + result.fuzzy_query + ); + } + + #[test] + fn test_grep_leading_slash_path_alone_is_text_not_path_segment() { + // Same behavior for regular GrepConfig — single-token path-like + // queries are search terms, not directory filters. + let parser = QueryParser::new(GrepConfig); + + let result = parser.parse("/api/tests/"); + assert_eq!( + result.constraints.len(), + 0, + "GrepConfig: expected no constraints for '/api/tests/', got {:?}", + result.constraints + ); + assert!( + matches!(result.fuzzy_query, FuzzyQuery::Text("/api/tests/")), + "GrepConfig: expected FuzzyQuery::Text, got {:?}", + result.fuzzy_query + ); + + let result = parser.parse("/api/tests"); + assert_eq!( + result.constraints.len(), + 0, + "GrepConfig: expected no constraints for '/api/tests', got {:?}", + result.constraints + ); + assert!( + matches!(result.fuzzy_query, FuzzyQuery::Text("/api/tests")), + "GrepConfig: expected FuzzyQuery::Text, got {:?}", + result.fuzzy_query + ); + } + + #[test] + fn test_ai_grep_filename_with_extension_only_promotes_to_text() { + // Same case with an Extension constraint — no fuzzy text means the + // filename is what the user is searching for. + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("*.h profile.h"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::Extension("h")), + "Expected Extension, got {:?}", + result.constraints + ); + assert_eq!(result.grep_text(), "profile.h"); + } + + #[test] + fn test_ai_grep_filename_with_other_text_keeps_filepath() { + // Sanity: when there IS fuzzy text alongside the filename, the + // filename stays a FilePath filter (the documented multi-token case). + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("main.rs pattern"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::FilePath("main.rs")), + "Expected FilePath, got {:?}", + result.constraints + ); + assert_eq!(result.grep_text(), "pattern"); + } + + #[test] + fn test_ai_grep_bare_filename_schema_rs() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("schema.rs part_revisions"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::FilePath("schema.rs")), + "Expected FilePath(schema.rs), got {:?}", + result.constraints[0] + ); + assert_eq!(result.grep_text(), "part_revisions"); + } + + #[test] + fn test_ai_grep_bare_word_no_extension_not_constraint() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("schema pattern"); + // No extension → not a file path, just text + assert_eq!(result.constraints.len(), 0); + assert_eq!(result.grep_text(), "schema pattern"); + } + + #[test] + fn test_ai_grep_no_false_positive_no_extension() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("src/utils pattern"); + // No extension in last component → not a file path, just text + assert_eq!(result.constraints.len(), 0); + assert_eq!(result.grep_text(), "src/utils pattern"); + } + + #[test] + fn test_ai_grep_wildcard_not_filepath() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("src/**/*.rs pattern"); + // Contains wildcards → should be a Glob, not FilePath + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::Glob("src/**/*.rs")), + "Expected Glob, got {:?}", + result.constraints[0] + ); + } + + #[test] + fn test_ai_grep_star_text_star_is_glob() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("*quote* TODO"); + // `*quote*` should be recognised as a glob constraint in AI mode + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::Glob("*quote*")), + "Expected Glob(*quote*), got {:?}", + result.constraints[0] + ); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("TODO")); + } + + #[test] + fn test_ai_grep_bare_star_not_glob() { + use crate::AiGrepConfig; + let parser = QueryParser::new(AiGrepConfig); + let result = parser.parse("* pattern"); + // Bare `*` should NOT be treated as a glob (too broad) + assert!( + result.constraints.is_empty(), + "Expected no constraints, got {:?}", + result.constraints + ); + } + + #[test] + fn test_grep_no_location_parsing_single_token() { + let parser = QueryParser::new(GrepConfig); + // localhost:8080 should NOT be parsed as location -- it's a search pattern + let result = parser.parse("localhost:8080"); + assert!(result.constraints.is_empty()); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("localhost:8080")); + } + + #[test] + fn test_grep_no_location_parsing_multi_token() { + let q = QueryParser::new(GrepConfig).parse("*.rs localhost:8080"); + assert_eq!( + q.grep_text(), + "localhost:8080", + "Colon-number suffix should be preserved in grep text" + ); + assert!( + q.location.is_none(), + "Grep should not parse location from colon-number" + ); + } + + #[test] + fn test_grep_reversed_braces_does_not_panic() { + // BUG PINING https://github.com/dmtrKovalenko/fff/issues/479 + // we should support any combination of different brackets without crashes + for query in [ + "}{", + "}{ foo", + "foo }{", + "a}{b", + "}}{{", + "} something {{{ {}}}d{ {}}}}{{{ }}}}}d{d something {{}}}}}}", + ] { + let result = QueryParser::new(GrepConfig).parse(query); + // A reversed-brace token must not be promoted to a Glob — there + // is no comma+letters between `{` and `}`, so it's just text. + assert!( + !result + .constraints + .iter() + .any(|c| matches!(c, Constraint::Glob(_))), + "GrepConfig: {query:?} produced a Glob constraint, got {:?}", + result.constraints + ); + + let result = QueryParser::new(crate::AiGrepConfig).parse(query); + assert!( + !result + .constraints + .iter() + .any(|c| matches!(c, Constraint::Glob(_))), + "AiGrepConfig: {query:?} produced a Glob constraint, got {:?}", + result.constraints + ); + } + } + + #[test] + fn test_grep_braces_without_comma_is_text() { + let parser = QueryParser::new(GrepConfig); + // Code patterns like format!("{}") should NOT be treated as brace expansion + let result = parser.parse(r#"format!("{}\\AppData", home)"#); + assert!( + result.constraints.is_empty(), + "Braces without comma should be text, got {:?}", + result.constraints + ); + assert_eq!(result.grep_text(), r#"format!("{}\\AppData", home)"#); + } + + #[test] + fn test_grep_valid_brace_expansion_amid_junk_braces() { + // A query mixing junk-brace tokens (`}{`, `{{}}`, `}}{{`, `{}`) with + // a real brace-expansion glob (`{src,lib}`) must NOT panic and MUST + // still surface the valid glob as a Glob constraint. Regression for + // the `}{` slice-out-of-bounds panic at config.rs:175. + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("}{ {{}} }}{{ {} {src,lib} pattern"); + + let glob_constraints: Vec<&str> = result + .constraints + .iter() + .filter_map(|c| match c { + Constraint::Glob(p) => Some(*p), + _ => None, + }) + .collect(); + assert_eq!( + glob_constraints, + vec!["{src,lib}"], + "Expected exactly one Glob({{src,lib}}), got {:?}", + result.constraints + ); + + // Same scenario for AiGrepConfig (delegates to GrepConfig::is_glob_pattern). + let parser = QueryParser::new(crate::AiGrepConfig); + let result = parser.parse("}{ {{}} }}{{ {} {src,lib} pattern"); + let glob_constraints: Vec<&str> = result + .constraints + .iter() + .filter_map(|c| match c { + Constraint::Glob(p) => Some(*p), + _ => None, + }) + .collect(); + assert_eq!( + glob_constraints, + vec!["{src,lib}"], + "AiGrepConfig: expected Glob({{src,lib}}), got {:?}", + result.constraints + ); + } + + #[test] + fn test_grep_format_braces_not_glob() { + let parser = QueryParser::new(GrepConfig); + // Code like format!("{}\\path", var) must not have tokens eaten as glob constraints. + // The trailing comma on the first token means both { } and , are present, + // but the comma is outside the braces so it should NOT trigger brace expansion. + let input = "format!(\"{}\\\\AppData\", home)"; + let result = parser.parse(input); + assert!( + result.constraints.is_empty(), + "format! pattern should have no constraints, got {:?}", + result.constraints + ); + } + + #[test] + fn test_grep_config_star_text_star_not_glob() { + use crate::GrepConfig; + let parser = QueryParser::new(GrepConfig); + let result = parser.parse("*quote* TODO"); + // Regular grep mode should NOT treat `*quote*` as a glob + assert!( + result.constraints.is_empty(), + "Expected no constraints in GrepConfig, got {:?}", + result.constraints + ); + } + + #[test] + fn test_file_picker_bare_filename_constraint() { + let parser = QueryParser::new(FilenameConstraintConfig); + let result = parser.parse("score.rs file_picker"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::FilePath("score.rs")), + "Expected FilePath(\"score.rs\"), got {:?}", + result.constraints[0] + ); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("file_picker")); + } + + #[test] + fn test_file_picker_path_prefixed_filename_constraint() { + let parser = QueryParser::new(FilenameConstraintConfig); + let result = parser.parse("libswscale/slice.c lum_convert"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!( + result.constraints[0], + Constraint::FilePath("libswscale/slice.c") + ), + "Expected FilePath(\"libswscale/slice.c\"), got {:?}", + result.constraints[0] + ); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("lum_convert")); + } + + #[test] + fn test_file_picker_single_token_filename_stays_fuzzy() { + let parser = QueryParser::new(FileSearchConfig); + // Single-token filename should NOT become a constraint -- it should + // return FFFQuery with Text fuzzy query so the caller uses it for fuzzy matching. + let result = parser.parse("score.rs"); + assert!(result.constraints.is_empty()); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("score.rs")); + } + + #[test] + fn test_absolute_path_with_location_not_path_segment() { + let parser = QueryParser::new(FileSearchConfig); + // Absolute file path with :line should parse as text + location, + // NOT as a PathSegment constraint (which would eat the whole token). + let result = parser.parse("/Users/neogoose/dev/fframes/src/renderer/concatenator.rs:12"); + assert!( + result.constraints.is_empty(), + "Absolute path with location should not become a constraint, got {:?}", + result.constraints + ); + assert_eq!( + result.fuzzy_query, + FuzzyQuery::Text("/Users/neogoose/dev/fframes/src/renderer/concatenator.rs") + ); + assert_eq!(result.location, Some(Location::Line(12))); + } + + #[test] + fn test_file_picker_filename_with_multiple_fuzzy_parts() { + let parser = QueryParser::new(FilenameConstraintConfig); + let result = parser.parse("main.rs src components"); + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::FilePath("main.rs") + )); + assert_eq!( + result.fuzzy_query, + FuzzyQuery::Parts(vec!["src", "components"]) + ); + } + + #[test] + fn test_file_picker_version_number_not_filename() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("v2.0 release"); + // v2.0 extension starts with digit → not a filename constraint + assert!( + result.constraints.is_empty(), + "v2.0 should not be a FilePath constraint, got {:?}", + result.constraints + ); + } + + #[test] + fn test_file_picker_only_one_filepath_constraint() { + let parser = QueryParser::new(FilenameConstraintConfig); + let result = parser.parse("main.rs score.rs"); + // Only first filename becomes a constraint; second is text + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::FilePath("main.rs") + )); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("score.rs")); + } + + #[test] + fn test_file_picker_filename_with_extension_constraint() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("main.rs *.lua"); + // With only path-scoping constraints (Extension) and no fuzzy text, + // `main.rs` is promoted to fuzzy text — the user is fuzzy-searching + // for "main.rs" among `.lua` files, not filtering by literal filename + // suffix. Only the Extension constraint remains. + assert_eq!(result.constraints.len(), 1); + assert!(matches!( + result.constraints[0], + Constraint::Extension("lua") + )); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("main.rs")); + } + + #[test] + fn test_file_picker_dotfile_is_filename() { + let parser = QueryParser::new(FilenameConstraintConfig); + let result = parser.parse(".gitignore src"); + assert_eq!(result.constraints.len(), 1); + assert!( + matches!(result.constraints[0], Constraint::FilePath(".gitignore")), + "Expected FilePath(\".gitignore\"), got {:?}", + result.constraints[0] + ); + assert_eq!(result.fuzzy_query, FuzzyQuery::Text("src")); + } + + #[test] + fn test_file_picker_no_extension_not_filename() { + let parser = QueryParser::new(FileSearchConfig); + let result = parser.parse("Makefile src"); + // No dot → not a filename constraint + assert!( + result.constraints.is_empty(), + "Makefile should not be a FilePath constraint, got {:?}", + result.constraints + ); + } +} diff --git a/doc/.gitkeep b/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/doc/fff.nvim.txt b/doc/fff.nvim.txt new file mode 100644 index 0000000..a8e55b3 --- /dev/null +++ b/doc/fff.nvim.txt @@ -0,0 +1,451 @@ +*fff.nvim.txt* + For Neovim >= 0.10.0 Last change: 2026 June 30 + +============================================================================== +Table of Contents *fff.nvim-table-of-contents* + +1. fff.nvim |fff.nvim-fff.nvim| + +============================================================================== +1. fff.nvim *fff.nvim-fff.nvim* + +The best file search picker for Neovim. Frecency-ranked, typo-resistant, +git-award, very fast. + +Demo on the Linux kernel repo (100k files, 8GB): + + +https://github.com/user-attachments/assets/5d0e1ce9-642c-4c44-aa88-01b05bb86abb + + +INSTALLATION ~ + + +LAZY.NVIM + +>lua + { + 'dmtrKovalenko/fff.nvim', + build = function() + -- downloads a prebuilt binary or falls back to cargo build + require("fff.download").download_or_build_binary() + end, + -- for nixos: + -- build = "nix run .#release", + opts = { + debug = { + enabled = true, + show_scores = true, + }, + }, + lazy = false, -- the plugin lazy-initialises itself + keys = { + { "ff", function() require('fff').find_files() end, desc = 'FFFind files' }, + { "fg", function() require('fff').live_grep() end, desc = 'LiFFFe grep' }, + { "fz", + function() require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } }) end, + desc = 'Live fffuzy grep', + }, + { "fw", + function() require('fff').live_grep_under_cursor() end, + mode = { 'n', 'x' }, + desc = 'Search current word / selection', + }, + }, + } +< + + +VIM.PACK + +>lua + vim.pack.add({ 'https://github.com/dmtrKovalenko/fff.nvim' }) + + vim.api.nvim_create_autocmd('PackChanged', { + callback = function(ev) + local name, kind = ev.data.spec.name, ev.data.kind + if name == 'fff.nvim' and (kind == 'install' or kind == 'update') then + if not ev.data.active then vim.cmd.packadd('fff.nvim') end + require('fff.download').download_or_build_binary() + end + end, + }) + + vim.g.fff = { + lazy_sync = true, + debug = { enabled = true, show_scores = true }, + } + + vim.keymap.set('n', 'ff', function() require('fff').find_files() end, { desc = 'FFFind files' }) +< + + +PUBLIC API ~ + +>lua + require('fff').find_files() -- find files in current repo + require('fff').live_grep() -- live content grep + require('fff').live_grep_under_cursor() -- grep in normal, selection in visual + require('fff').scan_files() -- force rescan + require('fff').refresh_git_status() -- refresh git status + require('fff').find_files_in_dir(path) -- find in a specific dir + require('fff').change_indexing_directory(new_path) -- change root + + -- Programmatic search (no UI). Useful for plugin integrations. + require('fff').file_search(query, opts) -- fuzzy search files / dirs / mixed + require('fff').content_search(query, opts) -- programmatic grep +< + + +FILE_SEARCH(QUERY, OPTS) + +Returns a structured result `{ items, scores, total_matched, total_files?, +total_dirs?, location? }`. Each item has a `type` field (`"file"` or +`"directory"`) and `name` / `relative_path`. File items also expose `size`, +`modified`, `git_status`, `is_binary`, and frecency scores. + +>lua + local r = require('fff').file_search('button', { + mode = 'mixed', -- 'files' (default) | 'directories' | 'mixed' + max_results = 50, + page = 0, -- 0-based pagination + current_file = nil, -- path to deprioritize for distance scoring + max_threads = 4, + cwd = nil, -- switch indexed root if different (see below) + wait_for_index_ms = nil, -- override the default scan wait timeout + }) + for _, item in ipairs(r.items) do + print(item.type, item.relative_path) + end +< + + +CONTENT_SEARCH(QUERY, OPTS) + +Returns a `GrepResult` `{ items, total_matched, total_files_searched, +total_files, filtered_file_count, next_file_offset, regex_fallback_error? }`. +Each match item has `relative_path`, `name`, `line_number`, `col`, +`line_content`, `match_ranges`, plus the same file metadata as `file_search`. + +>lua + local r = require('fff').content_search('TODO', { + mode = 'plain', -- 'plain' (default) | 'regex' | 'fuzzy' + max_file_size = 10 * 1024 * 1024, + max_matches_per_file = 100, + smart_case = true, + page_size = 50, + file_offset = 0, + time_budget_ms = 0, + trim_whitespace = false, + cwd = nil, -- switch indexed root if different + wait_for_index_ms = nil, -- override the default scan wait timeout + }) + for _, m in ipairs(r.items) do + print(string.format('%s:%d %s', m.relative_path, m.line_number, m.line_content)) + end +< + +Both functions accept the same constraint syntax as the UI pickers (e.g. +`git:modified`, `*.rs`, `!test/`, glob patterns). + + +CWD AND INDEXING + +Both `file_search` and `content_search` honour an optional `cwd` field. The +first call to either function lazily initialises the picker at +`config.base_path` (your Neovim cwd by default). + +- If `cwd` matches the currently indexed root, the call returns immediately against the existing index. +- If `cwd` differs, the picker is re-indexed at the new root and the call **blocks** (default up to 10 s) until the new picker is installed and its initial scan completes — so callers always get results from the right tree. +- If the index is still warming up after a `change_indexing_directory`, you can pass `wait_for_index_ms = N` to block for up to `N` ms regardless of whether `cwd` triggered the swap. Pass `0` to skip waiting entirely (useful for fire-and-forget calls where partial results are acceptable). +- Invalid or non-existent `cwd` paths return an empty result and emit an error via `vim.notify`. + + +COMMANDS ~ + +- `:FFFScan`. Rescan files. +- `:FFFRefreshGit`. Refresh git status. +- `:FFFClearCache [all|frecency|files]`. Clear caches. +- `:FFFHealth`. Health check. +- `:FFFDebug [on|off|toggle]`. Toggle the scoring display. +- `:FFFOpenLog`. Open `~/.local/state/nvim/log/fff.log`. + + +CONFIGURATION ~ + +Defaults are sensible. Override only what you care about. + +>lua + require('fff').setup({ + base_path = vim.fn.getcwd(), + prompt = '> ', + title = 'FFFiles', + max_results = 100, + max_threads = 4, + lazy_sync = true, + prompt_vim_mode = false, + follow_symlinks = false, + -- Allow indexing the user's $HOME directory. Enabled by default. + -- Disable if you strictly sure you don't want this, as it makes whole fff error hard + enable_home_dir_scanning = true, + -- Allow indexing a filesystem root (e.g. `/`, `C:\`). Disabled by default + enable_fs_root_scanning = false, + layout = { + height = 0.8, + width = 0.8, + prompt_position = 'bottom', -- or 'top' + preview_position = 'right', -- 'left' | 'right' | 'top' | 'bottom' + preview_size = 0.5, + -- Border style for the picker windows. Leave unset (nil) to follow the + -- global `vim.o.winborder`; set it to override fff's borders independently. + border = nil, -- 'single' | 'double' | 'rounded' | 'solid' | 'shadow' | 'none' + flex = { size = 130, wrap = 'top' }, + min_list_height = 10, -- do not display anything except the list below this threshold + show_scrollbar = true, + path_shorten_strategy = 'middle_number', -- 'middle_number' | 'middle' | 'end' | 'start' + anchor = 'center', + }, + preview = { + enabled = true, + max_size = 10 * 1024 * 1024, + chunk_size = 8192, + binary_file_threshold = 1024, + imagemagick_info_format_str = '%m: %wx%h, %[colorspace], %q-bit', + line_numbers = false, + cursorlineopt = 'both', + wrap_lines = false, + filetypes = { + svg = { wrap_lines = true }, + markdown = { wrap_lines = true }, + text = { wrap_lines = true }, + }, + }, + keymaps = { + close = '', + select = '', + select_split = '', + select_vsplit = '', + select_tab = '', + move_up = { '', '' }, + move_down = { '', '' }, + preview_scroll_up = '', + preview_scroll_down = '', + toggle_debug = '', + cycle_grep_modes = '', + -- grep mode only: jump cursor to first match of next/prev file group + grep_jump_to_next_file = { '', '' }, + grep_jump_to_prev_file = { '', '' }, + cycle_previous_query = '', + toggle_select = '', + send_to_quickfix = '', + focus_list = 'l', + focus_preview = 'p', + }, + frecency = { + enabled = true, + db_path = vim.fn.stdpath('cache') .. '/fff_nvim', + }, + history = { + enabled = true, + db_path = vim.fn.stdpath('data') .. '/fff_queries', + min_combo_count = 3, + combo_boost_score_multiplier = 100, + }, + git = { + status_text_color = false, -- true to color filenames by git status + }, + select = { + -- Return winid to open the chosen file in, or nil to open in the original window + select_window = function(current_buf, action) --[[ default impl ]] end, + }, + grep = { + max_file_size = 10 * 1024 * 1024, + max_matches_per_file = 100, + smart_case = true, + time_budget_ms = 150, + modes = { 'plain', 'regex', 'fuzzy' }, + trim_whitespace = false, + enable_filename_constraint = false, -- treat filename-like tokens (e.g. `score.rs`) in a grep query as a file-path filter scoping the search; off = searched as literal text + location_format = ':%d:%d', -- printf format for line:col prefix in grep results, e.g. ':%d' for line-only + }, + debug = { + enabled = false, -- show the file info panel next to the preview + show_scores = false, -- inline scores in the file list + -- Per-section toggles for the file info panel. Accepts a boolean shorthand + -- (`show_file_info = true|false`) to flip everything at once. The panel + -- adapts to width: narrow renders sections vertically, wide renders them + -- as a two-column grid. Disable a section to also shrink the panel. + show_file_info = { + file_info = true, -- size, type, git status, frecency + score_breakdown = true, -- total + match type, bonuses, modifiers, penalty + -- modified + accessed timestamps; pass a table to hide individual rows: + -- timings = { modified = false, accessed = true } + timings = true, + full_path = true, -- relative path at the bottom (wraps if too long) + }, + }, + logging = { + -- logs will be written in a parent directory of this file path in files like + -- `++.`. Run :FFFOpenLog to open current one + log_file = vim.fn.stdpath('log') .. '/fff.log', + log_level = 'info', + retain_runs = 20, + }, + }) +< + + +LIVE GREP MODES ~ + +`` cycles between `plain`, `regex`, and `fuzzy`. The list is +configurable via `grep.modes`, and single-mode setups hide the indicator +entirely. + +Per-call override: + +>lua + require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } }) + require('fff').live_grep({ query = 'search term' }) -- pre-fill +< + + +CONSTRAINTS ~ + +Both find and grep accept these tokens to refine a query: + +- `git:modified`. One of `modified`, `staged`, `deleted`, `renamed`, `untracked`, `ignored`. +- `test/`. Any deeply nested children of `test/`. +- `!something`, `!test/`, `!git:modified`. Exclusion. +- `./**/*.{rs,lua}`. Any valid glob, powered by zlob . + +Grep-only: + +- `*.md`, `*.{c,h}`. Extension filter. +- `src/main.rs`. Grep inside a single file. + +Mix freely: `git:modified src/**/*.rs !src/**/mod.rs user controller`. + + +OPEN IN INVOKING WINDOW ~ + +By default fff.nvim will try to open a file in the most suitable window, so any +non-file buffers are not affected. You can customize or disable this by +providing: + +>lua + require('fff').setup({ + select = { + select_window = function(_current_buf, _action) return nil end, + }, + }) +< + +Caveat: the chosen file replaces the buffer in the invoking window even if +it’s a non-modifiable / special buftype. `winfixbuf` windows still fall back +to `:split` to avoid `E1513`. + + +MULTI-SELECT AND QUICKFIX ~ + +- ``. Toggle selection (shows a thick `▊` in the signcolumn). +- ``. Send selected files to the quickfix list and close the picker. + + +GIT STATUS HIGHLIGHTING ~ + +Sign-column indicators are on by default. To color filename text by git status, +set `git.status_text_color = true` and adjust the `hl.git_*` groups. See `:help +fff.nvim` for the full list. + + +FLOAT COLORS ~ + +The picker maps its float content to `NormalFloat` (via `hl.normal`) and the +border to `FloatBorder`. Default `FloatBorder` links to `NormalFloat`, so +border and content share a background out of the box and the picker reads as a +single popup. Override `hl.normal = 'Normal'` to make the picker blend with the +editor instead. + +For finer control, set `hl.winhl` to override the per-window `winhighlight`. It +accepts either a single string applied to every picker window, or a table with +optional `prompt`, `list`, `preview`, and `file_info` keys. Missing keys fall +back to the default built from `hl.normal`, `hl.border`, and `hl.title`. + +>lua + -- Apply the same winhighlight to all picker windows + hl = { winhl = 'Normal:NormalFloat,FloatBorder:FloatBorder,FloatTitle:Title' } + + -- Or override specific windows only + hl = { + winhl = { + prompt = 'Normal:Pmenu,FloatBorder:FloatBorder', + list = 'Normal:NormalFloat,FloatBorder:FloatBorder', + preview = 'Normal:NormalFloat,FloatBorder:FloatBorder', + }, + } +< + + +FILE INFO PANEL ~ + +Enable with `debug.enabled = true`. The panel sits above the preview and shows +file metadata, score breakdown, timestamps and the full absolute path. It +adapts to the panel width: at narrow widths sections stack vertically (B2), at +wide widths sections render as a two-column grid (H2). Each section can be +disabled individually via `debug.show_file_info`. + +Customise the panel via `hl`: + + -------------------------------------------------------------------------- + key default used for + ----------------------- ----------------- -------------------------------- + file_info_section Title section header label + + file_info_separator FloatBorder dashes that act as section + borders + + file_info_label Comment row labels (Size, Type, Git, …) + + file_info_value Normal fg plain values + + file_info_value_dim NonText dim values, separators inside + rows + + file_info_size Number file size value + + file_info_type Type filetype value + + file_info_path Directory full path + + file_info_total_score bold + Number total score (bold) + + file_info_match_type bold + Special match type (bold) + + file_info_score_pos DiagnosticOk positive score components + + file_info_score_neg DiagnosticError negative score components + -------------------------------------------------------------------------- + +FILE FILTERING ~ + +FFF honours `.gitignore`. For picker-only ignores that do not touch git, add a +sibling `.ignore` file: + +>gitignore + *.md + docs/archive/**/*.md +< + +Run `:FFFScan` to force a rescan. + + +TROUBLESHOOTING ~ + +- `:FFFHealth` verifies picker init, optional dependencies, and DB connectivity. +- `:FFFOpenLog` opens the current session’s log file. +- Historical log files are stored near the main log file `/log/fff++.log` (up to 20 files) +- For a crash backtrace, run `lldb -- nvim` or `gdb -- nvim` and reproduce + +Generated by panvimdoc + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/empty_config.lua b/empty_config.lua new file mode 100644 index 0000000..8d23d9b --- /dev/null +++ b/empty_config.lua @@ -0,0 +1,62 @@ +-- Single file Neovim config for testing fff.nvim locally +-- Usage: nvim -u /Users/neogoose/dev/fff.nvim/init.lua + +-- Set up lazy.nvim plugin manager +local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' +if not vim.loop.fs_stat(lazypath) then + vim.fn.system({ + 'git', + 'clone', + '--filter=blob:none', + 'https://github.com/folke/lazy.nvim.git', + '--branch=stable', + lazypath, + }) +end +vim.opt.rtp:prepend(lazypath) + +require('lazy').setup({ + { + dir = '~/dev/fff.nvim', + 'https://github.com/dmtrKovalenko/fff.nvim', + build = function() + -- this will download prebuild binary or try to use existing rustup toolchain to build from source + -- (if you are using lazy you can use gb for rebuilding a plugin if needed) + require('fff.download').download_or_build_binary() + end, + dependencies = { + 'nvim-tree/nvim-web-devicons', -- Optional: for file icons + -- { + -- 'nvim-mini/mini.icons', + -- version = false, + -- config = true, + -- }, + }, + config = function() + require('fff').setup({ + -- Configure fff.nvim here + ui = { + width = 0.8, + height = 0.8, + }, + file_picker = { + auto_reload_on_write = true, + frecency_boost = true, + }, + }) + end, + }, +}, { + root = vim.fn.stdpath('data') .. '/fff-empty-test', + lockfile = vim.fn.stdpath('data') .. '/fff-empty-test.json', +}) + +vim.opt.number = true +vim.opt.relativenumber = true + +vim.keymap.set('n', 'ff', function() require('fff').find_files() end, { desc = 'Find files' }) +vim.keymap.set('n', 'fg', function() require('fff').find_in_git_root() end, { desc = 'Find files in git root' }) +vim.keymap.set('n', 'fr', function() require('fff').scan_files() end, { desc = 'Rescan files' }) +vim.keymap.set('n', 'fs', function() require('fff').refresh_git_status() end, { desc = 'Refresh git status' }) + +vim.notify('FFF.nvim local config loaded! Press ff', vim.log.levels.INFO) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..12c3c1a --- /dev/null +++ b/flake.lock @@ -0,0 +1,153 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1773857772, + "narHash": "sha256-5xsK26KRHf0WytBtsBnQYC/lTWDhQuT57HJ7SzuqZcM=", + "owner": "ipetkov", + "repo": "crane", + "rev": "b556d7bbae5ff86e378451511873dfd07e4504cd", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1776329215, + "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b86751bc4085f48661017fa226dee99fab6c651b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "zig-overlay": "zig-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773803479, + "narHash": "sha256-GD6i1F2vrSxbsmbS92+8+x3DbHOJ+yrS78Pm4xigW4M=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "f17186f52e82ec5cf40920b58eac63b78692ac7c", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "flake": false, + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems_2" + }, + "locked": { + "lastModified": 1776789209, + "narHash": "sha256-G6B7Q4TXn7MZ1mB+f9rymjsYF5PLWoSvmbxijb/99bw=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "14fe971844e841297ddd2ce9783d6892b467af39", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6a079f1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,154 @@ +{ + description = "fff.nvim"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + crane.url = "github:ipetkov/crane"; + + flake-utils.url = "github:numtide/flake-utils"; + + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + zig-overlay = { + url = "github:mitchellh/zig-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + crane, + flake-utils, + rust-overlay, + zig-overlay, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + + # zlob requires Zig >= 0.16, but nixpkgs tops out at 0.15. Pull from + # mitchellh/zig-overlay which ships every released version. + zig = zig-overlay.packages.${system}."0.16.0"; + + rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; + + craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; + + cargoToml = builtins.fromTOML (builtins.readFile ./crates/fff-nvim/Cargo.toml); + fffMcpCargoToml = builtins.fromTOML (builtins.readFile ./crates/fff-mcp/Cargo.toml); + + # Common arguments can be set here to avoid repeating them later + # Note: changes here will rebuild all dependency crates + commonArgs = { + pname = cargoToml.package.name; + version = cargoToml.package.version; + src = craneLib.cleanCargoSource ./.; + strictDeps = true; + + nativeBuildInputs = [ pkgs.pkg-config pkgs.perl zig pkgs.llvmPackages.libclang.lib ]; + buildInputs = with pkgs; [ + # Add additional build inputs here + openssl + ]; + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + + # Zig 0.16 insists on writing to its global cache even when the + # zlob build.rs passes --global-cache-dir. In the nix sandbox $HOME + # is /homeless-shelter (unwritable), so redirect to $TMPDIR before + # the build phase runs. + preBuild = '' + export ZIG_GLOBAL_CACHE_DIR="$TMPDIR/zig-global-cache" + export ZIG_LOCAL_CACHE_DIR="$TMPDIR/zig-local-cache" + export XDG_CACHE_HOME="$TMPDIR/cache" + mkdir -p "$ZIG_GLOBAL_CACHE_DIR" "$ZIG_LOCAL_CACHE_DIR" "$XDG_CACHE_HOME" + ''; + }; + + my-crate = craneLib.buildPackage ( + commonArgs + // { + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + doCheck = false; + } + ); + + fff-mcp-args = commonArgs // { + pname = fffMcpCargoToml.package.name; + version = fffMcpCargoToml.package.version; + cargoExtraArgs = "-p fff-mcp --bin fff-mcp"; + }; + + fff-mcp = craneLib.buildPackage ( + fff-mcp-args + // { + cargoArtifacts = craneLib.buildDepsOnly fff-mcp-args; + doCheck = false; + } + ); + + # Copies the dynamic library into the target/release folder + copy-dynamic-library = /* bash */ '' + set -eo pipefail + mkdir -p target/release + if [ "$(uname)" = "Darwin" ]; then + cp -vf ${my-crate}/lib/libfff_nvim.dylib target/release/libfff_nvim.dylib + else + cp -vf ${my-crate}/lib/libfff_nvim.so target/release/libfff_nvim.so + fi + echo "Library copied to target/release/" + ''; + in + { + checks = { + inherit my-crate fff-mcp; + }; + + packages = { + default = my-crate; + inherit fff-mcp; + + # Neovim plugin + fff-nvim = pkgs.vimUtils.buildVimPlugin { + pname = "fff.nvim"; + version = "main"; + src = pkgs.lib.cleanSource ./.; + postPatch = copy-dynamic-library; + doCheck = false; # Skip require check since we have a Rust FFI component + }; + }; + + apps.default = flake-utils.lib.mkApp { + drv = my-crate; + }; + + apps.fff-mcp = flake-utils.lib.mkApp { + drv = fff-mcp; + }; + + # Add the release command + apps.release = flake-utils.lib.mkApp { + drv = pkgs.writeShellScriptBin "release" copy-dynamic-library; + }; + + devShells.default = craneLib.devShell { + # Inherit inputs from checks. + checks = self.checks.${system}; + # Extra inputs can be added here; cargo and rustc are provided by default. + packages = [ + # pkgs.ripgrep + ]; + }; + } + ); +} diff --git a/install-mcp.ps1 b/install-mcp.ps1 new file mode 100644 index 0000000..dfa1090 --- /dev/null +++ b/install-mcp.ps1 @@ -0,0 +1,261 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + FFF MCP Server installer for Windows. +.DESCRIPTION + Pipe usage: + irm https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.ps1 | iex + Direct usage (supports params): + iwr https://.../install-mcp.ps1 -OutFile install-mcp.ps1; .\install-mcp.ps1 -Version v0.1.2 + Env-var fallbacks (for the piped form): + $env:FFF_MCP_VERSION, $env:FFF_MCP_INSTALL_DIR +.PARAMETER Version + Release tag to install (e.g. 'v0.1.2'). Default: latest release containing a Windows MCP asset. +.PARAMETER InstallDir + Target install directory. Default: $env:LOCALAPPDATA\fff-mcp\bin. +.PARAMETER PathScope + How to persist PATH: 'User' (set user env var, default), 'Profile' (append to $PROFILE *nix-style), 'None' (do not persist). + Env-var fallback: $env:FFF_MCP_PATH_SCOPE. +#> +param( + [string]$Version = $env:FFF_MCP_VERSION, + [string]$InstallDir = $env:FFF_MCP_INSTALL_DIR, + [ValidateSet('User', 'Profile', 'None')] + [string]$PathScope = $(if ($env:FFF_MCP_PATH_SCOPE) { $env:FFF_MCP_PATH_SCOPE } else { 'User' }) +) + +$ErrorActionPreference = 'Stop' + +# Force TLS 1.2 — PS 5.1 on older Win10 may default to SSL3/TLS1.0 which GitHub rejects. +[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + +$Repo = 'dmtrKovalenko/fff.nvim' +$BinaryName = 'fff-mcp' +if (-not $InstallDir) { $InstallDir = Join-Path $env:LOCALAPPDATA 'fff-mcp\bin' } + +function Write-Info { param($m) Write-Host $m -ForegroundColor Blue } +function Write-Success { param($m) Write-Host $m -ForegroundColor DarkYellow } +function Write-Warn { param($m) Write-Host $m -ForegroundColor Yellow } + +function Get-Target { + # Read from registry — env vars lie under x86/ARM64 emulation. Same approach Bun uses. + $arch = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment').PROCESSOR_ARCHITECTURE + switch ($arch) { + 'AMD64' { return 'x86_64-pc-windows-msvc' } + 'ARM64' { return 'aarch64-pc-windows-msvc' } + default { throw "Unsupported architecture: $arch" } + } +} + +function Get-LatestReleaseTag { + param([string]$Target) + $asset = "$BinaryName-$Target.exe" + $headers = @{ 'User-Agent' = 'fff-mcp-installer' } + if ($env:GITHUB_TOKEN) { $headers['Authorization'] = "Bearer $env:GITHUB_TOKEN" } + + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases" -Headers $headers + $rel = $releases | Where-Object { $_.assets.name -contains $asset } | Select-Object -First 1 + if (-not $rel) { + throw "No release found containing $asset. The MCP build may not have been released for this platform yet." + } + return $rel.tag_name +} + +function Invoke-Download { + param([string]$Url, [string]$OutFile) + # curl.exe (ships with Win10 1803+) is faster than iwr on PS 5.1. Fall back to iwr. + $curl = Get-Command curl.exe -ErrorAction SilentlyContinue + if ($curl) { + & $curl.Source -fsSL -o $OutFile $Url + if ($LASTEXITCODE -ne 0) { throw "curl.exe exited with $LASTEXITCODE" } + } else { + $prev = $ProgressPreference + try { + # iwr progress bar tanks throughput on PS 5.1. + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing + } finally { + $ProgressPreference = $prev + } + } +} + +function Install-Binary { + param([string]$Target, [string]$Tag) + + $filename = "$BinaryName-$Target.exe" + $url = "https://github.com/$Repo/releases/download/$Tag/$filename" + + Write-Info "Downloading $filename from release $Tag..." + + $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) + New-Item -ItemType Directory -Force -Path $tmp | Out-Null + try { + $tmpFile = Join-Path $tmp $filename + try { + Invoke-Download -Url $url -OutFile $tmpFile + } catch { + Write-Host "" + Write-Host "Error: Failed to download binary for your platform." -ForegroundColor Red + Write-Host " URL: $url" + Write-Host " Release: $Tag" + Write-Host " Platform: $Target" + Write-Host "Check available releases at: https://github.com/$Repo/releases" + throw + } + + New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null + $dest = Join-Path $InstallDir "$BinaryName.exe" + Move-Item -Force -Path $tmpFile -Destination $dest + return $dest + } finally { + Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue + } +} + +function Test-OnPath { + param([string]$Dir) + $paths = $env:PATH -split ';' + return ($paths -contains $Dir) -or ($paths -contains $Dir.TrimEnd('\')) +} + +function Add-ToUserPath { + param([string]$Dir) + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + if (-not $userPath) { $userPath = '' } + $entries = $userPath -split ';' | Where-Object { $_ -ne '' } + if ($entries -notcontains $Dir) { + $newPath = (@($entries + $Dir) -join ';') + [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') + Write-Success "Added $Dir to user PATH." + } +} + +function Add-ToProfilePath { + param([string]$Dir) + $profilePath = $PROFILE.CurrentUserAllHosts + $line = "`$env:PATH += ';$Dir' # added by fff-mcp installer" + if (Test-Path $profilePath) { + $existing = Get-Content $profilePath -Raw -ErrorAction SilentlyContinue + if ($existing -and $existing.Contains($Dir)) { return } + } else { + New-Item -ItemType File -Force -Path $profilePath | Out-Null + } + Add-Content -Path $profilePath -Value "`n$line" + Write-Success "Appended PATH update to $profilePath." +} + +function Set-Path { + param([string]$Dir, [string]$Scope) + switch ($Scope) { + 'User' { Add-ToUserPath $Dir } + 'Profile' { Add-ToProfilePath $Dir } + 'None' { Write-Info "Skipping PATH persistence (-PathScope None)." } + } + # Make available in current session regardless of scope. + if (-not (Test-OnPath $Dir)) { $env:PATH = "$env:PATH;$Dir" } +} + +function Show-SetupInstructions { + param([string]$BinaryPath) + $foundAny = $false + + Write-Host "" + Write-Success "FFF MCP Server installed successfully!" + Write-Host "" + Write-Info "Setup with your AI coding assistant:" + Write-Host "" + + if (Get-Command claude -ErrorAction SilentlyContinue) { + $foundAny = $true + Write-Success "[Claude Code] detected" + Write-Host "" + Write-Host "Global (recommended):" + Write-Host "claude mcp add -s user fff -- $BinaryPath" + Write-Host "" + Write-Host "Or project-level .mcp.json (uses PATH):" + Write-Host @' +{ + "mcpServers": { + "fff": { + "type": "stdio", + "command": "fff-mcp", + "args": [] + } + } +} +'@ + Write-Host "" + } + + if (Get-Command opencode -ErrorAction SilentlyContinue) { + $foundAny = $true + Write-Success "[OpenCode] detected" + Write-Host "Add to your opencode.json:" + Write-Host @' +{ + "mcp": { + "fff": { + "type": "local", + "command": ["fff-mcp"], + "enabled": true + } + } +} +'@ + Write-Host "" + } + + if (Get-Command codex -ErrorAction SilentlyContinue) { + $foundAny = $true + Write-Success "[Codex] detected" + Write-Host "codex mcp add fff -- fff-mcp" + Write-Host "" + } + + if (-not $foundAny) { + Write-Host "No AI coding assistants detected." + Write-Host "Binary path: $BinaryPath" + Write-Host "" + } + + Write-Host "Binary: $BinaryPath" + Write-Host "Docs: https://github.com/$Repo" + Write-Host "" + Write-Info "Tip: Add this to your CLAUDE.md or AGENTS.md to make AI use fff for all searches:" + Write-Host '"Use the fff MCP tools for all file search operations instead of default tools."' +} + +function Main { + $target = Get-Target + + $existing = Join-Path $InstallDir "$BinaryName.exe" + $isUpdate = Test-Path $existing + + if ($isUpdate) { + Write-Info "Updating FFF MCP Server..." + } else { + Write-Info "Installing FFF MCP Server..." + } + Write-Host "" + Write-Info "Detected platform: $target" + + if ($Version) { + $tag = $Version + Write-Info "Using pinned version: $tag" + } else { + $tag = Get-LatestReleaseTag -Target $target + } + $binaryPath = Install-Binary -Target $target -Tag $tag + + if ($isUpdate) { + Write-Host "" + Write-Success "FFF MCP Server updated to $tag!" + Write-Host "" + } else { + Set-Path -Dir $InstallDir -Scope $PathScope + Show-SetupInstructions -BinaryPath $binaryPath + } +} + +Main diff --git a/install-mcp.sh b/install-mcp.sh new file mode 100755 index 0000000..aae6216 --- /dev/null +++ b/install-mcp.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env bash +set -eo pipefail + +# FFF MCP Server installer +# Usage: curl -fsSL https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.sh | bash + +REPO="dmtrKovalenko/fff.nvim" +BINARY_NAME="fff-mcp" +INSTALL_DIR="${FFF_MCP_INSTALL_DIR:-$HOME/.local/bin}" + +PINNED_RELEASE_TAG="v0.9.6" + +SHA256_X86_64_UNKNOWN_LINUX_MUSL="102ceaf173ef776becb3322216e9f6b5caef997c400c5d15f112ce4de40a1f5a" +SHA256_AARCH64_UNKNOWN_LINUX_MUSL="a9810c9056afa6d9e8ac1e7a3f1f15f8ecbfdc16a592e9d26e4b2434ef97a675" +SHA256_X86_64_APPLE_DARWIN="58259324c2c13a1b6f24f13138c2cd3eae9ff20e05201a539beb8f2044a651aa" +SHA256_AARCH64_APPLE_DARWIN="29a7fadeafb062f3e5954b1ab8c69e14dca24f5e061cd8d3b1ea1bab385a3754" +SHA256_X86_64_PC_WINDOWS_MSVC="7ff688d034aa42ff779a61ad12689794bdc253c895152796046f374390fb9cad" +SHA256_AARCH64_PC_WINDOWS_MSVC="94ce316a38775d8ed3b32882d8ef45560740319de8d7ac7bb80c31887b3afb6d" + +expected_sha_for() { + case "$1" in + x86_64-unknown-linux-musl) echo "$SHA256_X86_64_UNKNOWN_LINUX_MUSL" ;; + aarch64-unknown-linux-musl) echo "$SHA256_AARCH64_UNKNOWN_LINUX_MUSL" ;; + x86_64-apple-darwin) echo "$SHA256_X86_64_APPLE_DARWIN" ;; + aarch64-apple-darwin) echo "$SHA256_AARCH64_APPLE_DARWIN" ;; + x86_64-pc-windows-msvc) echo "$SHA256_X86_64_PC_WINDOWS_MSVC" ;; + aarch64-pc-windows-msvc) echo "$SHA256_AARCH64_PC_WINDOWS_MSVC" ;; + esac +} + +info() { printf '\033[1;34m%s\033[0m\n' "$*"; } +success() { printf '\033[1;38;5;208m%s\033[0m\n' "$*"; } +warn() { printf '\033[1;33m%s\033[0m\n' "$*"; } +error() { printf '\033[1;31mError: %s\033[0m\n' "$*" >&2; exit 1; } + +# Print JSON with syntax highlighting via jq if available, plain otherwise +print_json() { + if command -v jq &>/dev/null; then + echo "$1" | jq . + else + echo "$1" + fi +} + +detect_platform() { + local os arch target + + os="$(uname -s)" + arch="$(uname -m)" + + case "$os" in + Linux) + # Prefer musl (static) for maximum compatibility + case "$arch" in + x86_64) target="x86_64-unknown-linux-musl" ;; + aarch64|arm64) target="aarch64-unknown-linux-musl" ;; + *) error "Unsupported architecture: $arch" ;; + esac + ;; + Darwin) + case "$arch" in + x86_64) target="x86_64-apple-darwin" ;; + aarch64|arm64) target="aarch64-apple-darwin" ;; + *) error "Unsupported architecture: $arch" ;; + esac + ;; + MINGW*|MSYS*|CYGWIN*) + case "$arch" in + x86_64) target="x86_64-pc-windows-msvc" ;; + aarch64|arm64) target="aarch64-pc-windows-msvc" ;; + *) error "Unsupported architecture: $arch" ;; + esac + ;; + *) error "Unsupported OS: $os" ;; + esac + + echo "$target" +} + +get_latest_release_tag() { + local target="$1" + + # Honor the pin baked in by `make bump-install-mcp-sh`. Required when SHAs + # are pinned, since fetching /releases would race against newer releases. + if [ -n "$PINNED_RELEASE_TAG" ]; then + echo "$PINNED_RELEASE_TAG" + return + fi + + local releases_json + local curl_args=(-fsSL) + + # Use gh CLI token if available to avoid rate limiting + if command -v gh &>/dev/null; then + local gh_token + gh_token="$(gh auth token 2>/dev/null || true)" + if [ -n "$gh_token" ]; then + curl_args+=(-H "Authorization: token $gh_token") + fi + fi + + releases_json=$(curl "${curl_args[@]}" "https://api.github.com/repos/${REPO}/releases") \ + || error "Failed to fetch releases from https://github.com/${REPO}/releases" + + # Find the first release that contains an fff-mcp binary for our platform + local tag + tag=$(echo "$releases_json" \ + | grep -oE '"(tag_name|name)": *"[^"]*"' \ + | awk -v target="fff-mcp-${target}" ' + /"tag_name":/ { gsub(/.*": *"|"/, ""); current_tag = $0; next } + /"name":/ && index($0, target) { print current_tag; exit } + ') + + if [ -z "$tag" ]; then + error "No release found containing fff-mcp binaries for ${target}. The MCP build may not have been released yet." + fi + echo "$tag" +} + +download_binary() { + local target="$1" + local tag="$2" + local ext="" + + case "$target" in + *windows*) ext=".exe" ;; + esac + + local filename="${BINARY_NAME}-${target}${ext}" + local url="https://github.com/${REPO}/releases/download/${tag}/${filename}" + local checksum_url="${url}.sha256" + + info "Downloading ${filename} from release ${tag}..." + + local tmp_dir + tmp_dir="$(mktemp -d)" + trap 'rm -rf "$tmp_dir"' EXIT + + if ! curl -fsSL -o "${tmp_dir}/${filename}" "$url" 2>/dev/null; then + echo "" >&2 + printf '\033[1;31mError: Failed to download binary for your platform.\033[0m\n' >&2 + echo "" >&2 + echo " URL: ${url}" >&2 + echo " Release: ${tag}" >&2 + echo " Platform: ${target}" >&2 + echo "" >&2 + echo "This likely means the MCP binary hasn't been built for this release yet." >&2 + echo "Check available releases at: https://github.com/${REPO}/releases" >&2 + exit 1 + fi + + # Verify checksum: prefer the SHA pinned in this script (offline, tamper-evident). + # Fall back to the .sha256 file on the release for targets/releases without a pin. + if command -v sha256sum &>/dev/null; then + local pinned_sha + pinned_sha="$(expected_sha_for "$target")" + if [ -n "$pinned_sha" ]; then + info "Verifying checksum against pinned value..." + echo "${pinned_sha} ${filename}" > "${tmp_dir}/${filename}.sha256" + (cd "$tmp_dir" && sha256sum -c "${filename}.sha256") \ + || error "Checksum verification failed!" + elif curl -fsSL -o "${tmp_dir}/${filename}.sha256" "$checksum_url" 2>/dev/null; then + info "Verifying checksum..." + (cd "$tmp_dir" && sha256sum -c "${filename}.sha256") \ + || error "Checksum verification failed!" + else + warn "Checksum file not available, skipping verification." + fi + fi + + # Install + mkdir -p "$INSTALL_DIR" + mv "${tmp_dir}/${filename}" "${INSTALL_DIR}/${BINARY_NAME}${ext}" + chmod +x "${INSTALL_DIR}/${BINARY_NAME}${ext}" + + if [ "$IS_UPDATE" != true ]; then + success "Installed ${BINARY_NAME} to ${INSTALL_DIR}/${BINARY_NAME}${ext}" + fi +} + +check_path() { + case ":$PATH:" in + *":${INSTALL_DIR}:"*) return 0 ;; + esac + + warn "${INSTALL_DIR} is not in your PATH." + echo "" + echo "Add it to your shell profile:" + echo "" + + local shell_name + shell_name="$(basename "${SHELL:-bash}")" + case "$shell_name" in + zsh) + echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.zshrc" + echo " source ~/.zshrc" + ;; + fish) + echo " fish_add_path ${INSTALL_DIR}" + ;; + *) + echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.bashrc" + echo " source ~/.bashrc" + ;; + esac + echo "" +} + +print_setup_instructions() { + local binary_path="${INSTALL_DIR}/${BINARY_NAME}" + local found_any=false + + echo "" + success "FFF MCP Server installed successfully!" + echo "" + info "Setup with your AI coding assistant:" + echo "" + + # Claude Code + if command -v claude &>/dev/null; then + found_any=true + success "[Claude Code] detected" + echo "" + echo "Global (recommended):" + echo "claude mcp add -s user fff -- ${binary_path}" + echo "" + echo "Or project-level .mcp.json (uses PATH):" + echo "" + print_json '{ + "mcpServers": { + "fff": { + "type": "stdio", + "command": "fff-mcp", + "args": [] + } + } +}' + echo "" + fi + + # OpenCode + if command -v opencode &>/dev/null; then + found_any=true + success "[OpenCode] detected" + echo "" + echo "Add to ~/.config/opencode/opencode.json:" + echo "" + print_json '{ + "mcp": { + "fff": { + "type": "local", + "command": ["fff-mcp"], + "enabled": true + } + } +}' + echo "" + fi + + # Codex + if command -v codex &>/dev/null; then + found_any=true + success "[Codex] detected" + echo "" + echo "codex mcp add fff -- fff-mcp" + echo "" + fi + + if [ "$found_any" = false ]; then + echo "No AI coding assistants detected." + echo "" + echo "Binary path: ${binary_path}" + echo "" + fi + + echo "Binary: ${binary_path}" + echo "Docs: https://github.com/${REPO}" + echo "" + info "Tip: Add this to your CLAUDE.md or AGENTS.md to make AI use fff for all searches:" + echo "\"" + echo "Use the fff MCP tools for all file search operations instead of default tools." + echo "\"" + + +} + +main() { + local target + target="$(detect_platform)" + + local existing_binary="${INSTALL_DIR}/${BINARY_NAME}" + IS_UPDATE=false + + if [ -x "$existing_binary" ]; then + IS_UPDATE=true + info "Updating FFF MCP Server..." + else + info "Installing FFF MCP Server..." + fi + echo "" + + info "Detected platform: ${target}" + + local tag + tag="$(get_latest_release_tag "$target")" + + download_binary "$target" "$tag" + + if [ "$IS_UPDATE" = true ]; then + echo "" + success "FFF MCP Server updated to ${tag}!" + echo "" + else + check_path + print_setup_instructions + fi +} + +main diff --git a/lua/fff.lua b/lua/fff.lua new file mode 100644 index 0000000..886502c --- /dev/null +++ b/lua/fff.lua @@ -0,0 +1 @@ +return require('fff.main') diff --git a/lua/fff/conf.lua b/lua/fff/conf.lua new file mode 100644 index 0000000..ab0683f --- /dev/null +++ b/lua/fff/conf.lua @@ -0,0 +1,493 @@ +local M = {} + +--- @class FffLayoutConfig +--- @field height number +--- @field width number +--- @field prompt_position string +--- @field preview_position string +--- @field preview_size number +--- @field min_list_height number +--- @field show_scrollbar boolean +--- @field path_shorten_strategy string +--- @field border? 'single'|'double'|'rounded'|'solid'|'shadow'|'none' Border preset; falls back to `vim.o.winborder` when nil + +--- @class FffPreviewConfig +--- @field enabled boolean +--- @field max_size number +--- @field chunk_size number +--- @field binary_file_threshold number +--- @field imagemagick_info_format_str string +--- @field line_numbers boolean +--- @field cursorlineopt string +--- @field wrap_lines boolean +--- @field filetypes table + +--- @class FffKeymapsConfig +--- @field close string +--- @field select string +--- @field select_split string +--- @field select_vsplit string +--- @field select_tab string +--- @field move_up string|string[] +--- @field move_down string|string[] +--- @field preview_scroll_up string +--- @field preview_scroll_down string +--- @field toggle_debug string +--- @field cycle_grep_modes string +--- @field cycle_previous_query string +--- @field cycle_forward_query string +--- @field grep_jump_to_next_file string|string[] +--- @field grep_jump_to_prev_file string|string[] +--- @field toggle_select string +--- @field send_to_quickfix string +--- @field focus_list string +--- @field focus_preview string + +--- @class FffFrecencyConfig +--- @field enabled boolean +--- @field db_path string + +--- @class FffHistoryConfig +--- @field enabled boolean +--- @field db_path string +--- @field min_combo_count number +--- @field combo_boost_score_multiplier number + +--- @class FffGrepConfig +--- @field max_file_size number +--- @field max_matches_per_file number +--- @field smart_case boolean +--- @field time_budget_ms number +--- @field modes string[] +--- @field trim_whitespace boolean +--- @field location_format string + +--- @alias FffSelectAction 'edit' | 'split' | 'vsplit' | 'tab' + +--- @class FffSelectConfig +--- @field select_window fun(current_buf: integer, action: FffSelectAction): integer|nil + +--- @class FffConfig +--- @field base_path string +--- @field prompt string +--- @field title string +--- @field max_results number +--- @field max_threads number +--- @field lazy_sync boolean +--- @field prompt_vim_mode boolean +--- @field follow_symlinks boolean +--- @field enable_fs_root_scanning boolean +--- @field enable_home_dir_scanning boolean +--- @field layout FffLayoutConfig +--- @field preview FffPreviewConfig +--- @field keymaps FffKeymapsConfig +--- @field hl table +--- @field frecency FffFrecencyConfig +--- @field history FffHistoryConfig +--- @field select FffSelectConfig +--- @field git table +--- @field debug table +--- @field logging table +--- @field wrap_around boolean +--- @field file_picker table +--- @field grep FffGrepConfig + +---@class fff.conf.State +local state = { + ---@type FffConfig|nil + config = nil, +} + +local DEPRECATION_RULES = { + { + -- Top-level width -> layout.width + old_path = { 'width' }, + new_path = { 'layout', 'width' }, + message = 'config.width is deprecated. Use config.layout.width instead.', + }, + { + -- Top-level height -> layout.height + old_path = { 'height' }, + new_path = { 'layout', 'height' }, + message = 'config.height is deprecated. Use config.layout.height instead.', + }, + { + -- preview.width -> layout.preview_size + old_path = { 'preview', 'width' }, + new_path = { 'layout', 'preview_size' }, + message = 'config.preview.width is deprecated. Use config.layout.preview_size instead.', + }, + { + -- layout.preview_width -> layout.preview_size + old_path = { 'layout', 'preview_width' }, + new_path = { 'layout', 'preview_size' }, + message = 'config.layout.preview_width is deprecated. Use config.layout.preview_size instead.', + }, +} + +--- Get value from nested table using path array +--- @param tbl table Source table +--- @param path table Array of keys to traverse +--- @return any|nil Value at path or nil if not found +local function get_nested_value(tbl, path) + local current = tbl + for _, key in ipairs(path) do + if type(current) ~= 'table' or current[key] == nil then return nil end + current = current[key] + end + + return current +end + +--- Set value in nested table using path array, creating intermediate tables +--- @param tbl table Target table +--- @param path table Array of keys to traverse +--- @param value any Value to set +local function set_nested_value(tbl, path, value) + local current = tbl + for i = 1, #path - 1 do + local key = path[i] + if type(current[key]) ~= 'table' then current[key] = {} end + current = current[key] + end + + current[path[#path]] = value +end + +--- Remove value from nested table using path array +--- @param tbl table Target table +--- @param path table Array of keys to traverse +local function remove_nested_value(tbl, path) + if #path == 0 then return end + + local current = tbl + for i = 1, #path - 1 do + local key = path[i] + if type(current[key]) ~= 'table' then return end + current = current[key] + end + + current[path[#path]] = nil +end + +--- Handle deprecated configuration options with migration warnings +--- @param user_config table User provided configuration +--- @return table Migrated configuration +local function handle_deprecated_config(user_config) + if not user_config then return {} end + + local migrated_config = vim.deepcopy(user_config) + + for _, rule in ipairs(DEPRECATION_RULES) do + local old_value = get_nested_value(user_config, rule.old_path) + if old_value ~= nil then + set_nested_value(migrated_config, rule.new_path, old_value) + remove_nested_value(migrated_config, rule.old_path) + + vim.notify('FFF: ' .. rule.message, vim.log.levels.WARN) + end + end + + return migrated_config +end + +---@param name table list of highlight groups to choose from +---@return string one of the provided groups +local function fallback_hl(name) + local resolved_hl + for _, hl in ipairs(name) do + local resolved_group = vim.api.nvim_get_hl(0, { name = hl }) + + if not vim.tbl_isempty(resolved_group) then resolved_hl = hl end + end + + return resolved_hl or name[#name] +end + +local function init() + local config = vim.g.fff or {} + local default_config = { + base_path = vim.fn.getcwd(), + prompt = '🪿 ', + title = 'FFFiles', + max_results = 100, + max_threads = 4, + lazy_sync = true, -- set to false if you want file indexing to start on open + prompt_vim_mode = false, -- set to true to enable vim-mode in the prompt: leaves insert for normal mode bindings (also allows p or l to jump around) the second closes the picker + wrap_around = false, -- set to true to wrap cursor to the opposite end when reaching the first/last item + follow_symlinks = false, -- set to true to follow symbolic links during file indexing + -- Allow fff in the user's $HOME director. + enable_home_dir_scanning = true, + -- Allow fff in a filesystem root (e.g. `/`, `C:\`) + enable_fs_root_scanning = false, + layout = { + height = 0.8, + width = 0.8, + prompt_position = 'bottom', -- or 'top' + preview_position = 'right', -- or 'left', 'right', 'top', 'bottom' + preview_size = 0.5, + -- Border style for the picker windows: 'single', 'double', 'rounded', + -- 'solid', 'shadow' or 'none'. Leave unset (nil) to follow the global + -- `vim.o.winborder` setting. + border = nil, + flex = { -- set to nil to disable flex layout + size = 130, -- column threshold: if screen width >= size, use preview_position; otherwise use wrap + wrap = 'top', -- position to use when screen is narrower than size + }, + -- Minimum list height required to render the preview. When the available + -- list area would drop below this on small terminals, the preview is + -- auto-hidden so the file list stays usable. Set to 0 to disable. + min_list_height = 10, + show_scrollbar = true, -- Show scrollbar for pagination + -- How to shorten long directory paths in the file list: + -- 'middle' (default): always uses dots (a/./b, a/../b, a/.../b) + -- 'middle_number' uses dots for 1-3 hidden (a/./b, a/../b, a/.../b) + -- and numbers for 4+ (a/.4./b, a/.5./b) + -- 'end': truncates from the end, keeps the start (home/user/projects) + -- 'start': truncates from the start, keeps the end (.../parts/ai_extracted) + path_shorten_strategy = 'middle', + }, + preview = { + enabled = true, + max_size = 10 * 1024 * 1024, -- Do not try to read files larger than 10MB + chunk_size = 8192, -- Bytes per chunk for dynamic loading (8kb - fits ~100-200 lines) + binary_file_threshold = 1024, -- amount of bytes to scan for binary content (set 0 to disable) + imagemagick_info_format_str = '%m: %wx%h, %[colorspace], %q-bit', + line_numbers = false, + cursorlineopt = 'both', + wrap_lines = false, + filetypes = { + svg = { wrap_lines = true }, + markdown = { wrap_lines = true }, + text = { wrap_lines = true }, + }, + }, + keymaps = { + close = '', + select = '', + select_split = '', + select_vsplit = '', + select_tab = '', + -- you can assign multiple keys to any action + move_up = { '', '' }, + move_down = { '', '' }, + preview_scroll_up = '', + preview_scroll_down = '', + toggle_debug = '', + -- grep mode: cycle between plain text, regex, and fuzzy search + cycle_grep_modes = '', + -- grep mode only: jump cursor to first item of next/prev file group + grep_jump_to_next_file = { '', '' }, + grep_jump_to_prev_file = { '', '' }, + -- goes to the previous query in history + cycle_previous_query = '', + -- goes to the next query in history (forward) + cycle_forward_query = '', + -- multi-select keymaps for quickfix + toggle_select = '', + send_to_quickfix = '', + -- this are specific for the normal mode (you can exit it using any other keybind like jj) + focus_list = 'l', + focus_preview = 'p', + }, + hl = { + border = 'FloatBorder', + normal = 'NormalFloat', + matched = 'IncSearch', + title = 'Title', + prompt = 'Question', + cursor = fallback_hl({ 'CursorLine', 'Visual' }), + frecency = 'Number', + debug = 'Comment', + combo_header = 'Number', + scrollbar = 'Comment', + directory_path = 'Comment', + -- Multi-select highlights + selected = 'FFFSelected', + selected_active = 'FFFSelectedActive', + -- Git text highlights for file names + git_staged = 'FFFGitStaged', + git_modified = 'FFFGitModified', + git_deleted = 'FFFGitDeleted', + git_renamed = 'FFFGitRenamed', + git_untracked = 'FFFGitUntracked', + git_ignored = 'FFFGitIgnored', + -- Git sign/border highlights + git_sign_staged = 'FFFGitSignStaged', + git_sign_modified = 'FFFGitSignModified', + git_sign_deleted = 'FFFGitSignDeleted', + git_sign_renamed = 'FFFGitSignRenamed', + git_sign_untracked = 'FFFGitSignUntracked', + git_sign_ignored = 'FFFGitSignIgnored', + -- Git sign selected highlights + git_sign_staged_selected = 'FFFGitSignStagedSelected', + git_sign_modified_selected = 'FFFGitSignModifiedSelected', + git_sign_deleted_selected = 'FFFGitSignDeletedSelected', + git_sign_renamed_selected = 'FFFGitSignRenamedSelected', + git_sign_untracked_selected = 'FFFGitSignUntrackedSelected', + git_sign_ignored_selected = 'FFFGitSignIgnoredSelected', + -- Grep highlights + grep_match = 'IncSearch', -- Highlight for matched text in grep results + grep_line_number = 'LineNr', -- Highlight for :line:col location + grep_regex_active = 'DiagnosticInfo', -- Highlight for keybind + label when regex is on + grep_plain_active = 'Comment', -- Highlight for keybind + label when regex is off + grep_fuzzy_active = 'DiagnosticHint', -- Highlight for keybind + label when fuzzy is on + -- Cross-mode suggestion highlights + suggestion_header = 'WarningMsg', -- Highlight for the "No results found. Suggested..." banner + -- File info panel highlights + file_info_section = 'FFFFileInfoSection', -- Section header label (e.g. "file", "score") + file_info_separator = 'FFFFileInfoSeparator', -- Dash dividers used like a border + file_info_label = 'FFFFileInfoLabel', -- Row labels (Size, Type, Git, ...) + file_info_value = 'FFFFileInfoValue', -- Plain values + file_info_value_dim = 'FFFFileInfoValueDim', -- Tertiary values, separators inside rows + file_info_size = 'FFFFileInfoSize', -- File size value + file_info_type = 'FFFFileInfoType', -- Filetype value + file_info_path = 'FFFFileInfoPath', -- Full path value + file_info_total_score = 'FFFFileInfoTotalScore', -- Total score (bold) + file_info_match_type = 'FFFFileInfoMatchType', -- match_type label (bold) + file_info_score_pos = 'FFFFileInfoScorePos', -- Positive score components + file_info_score_neg = 'FFFFileInfoScoreNeg', -- Negative score components / penalties + -- Per-window 'winhighlight' overrides. When nil, falls back to a combination of `normal`, `border`, and `title` above. + -- Accepts either a string applied to every picker window, or a table with optional `prompt`, `list`, `preview`, `file_info` keys. + -- Example: `winhl = 'Normal:NormalFloat,FloatBorder:FloatBorder,FloatTitle:Title'` + -- Example: `winhl = { prompt = 'Normal:Pmenu,...', list = 'Normal:NormalFloat,...' }` + winhl = nil, + }, + -- Store file open frecency + frecency = { + enabled = true, + db_path = vim.fn.stdpath('cache') .. '/fff_nvim', + }, + -- Store successfully opened queries with respective matches + history = { + enabled = true, + db_path = vim.fn.stdpath('data') .. '/fff_queries', + min_combo_count = 3, -- Minimum selections before combo boost applies (3 = boost starts on 3rd selection) + combo_boost_score_multiplier = 100, -- Score multiplier for combo matches (files repeatedly opened with same query) + }, + select = { + --- Returns winid to open the file in. Return nil to open in the invoking + --- window. Default retargets when the invoking window can't host a file + --- buffer (special buftype, non-modifiable, or winfixbuf). + --- @param current_buf integer + --- @param action FffSelectAction + --- @return integer|nil + select_window = function(current_buf, action) + if action ~= 'edit' then return nil end + local current_win = vim.api.nvim_get_current_win() + local buftype = vim.api.nvim_get_option_value('buftype', { buf = current_buf }) + local modifiable = vim.api.nvim_get_option_value('modifiable', { buf = current_buf }) + local winfixbuf = require('fff.utils').window_has_winfixbuf(current_win) + if buftype == '' and modifiable and not winfixbuf then return nil end + return require('fff.utils').find_suitable_window() + end, + }, + -- Git integration + git = { + status_text_color = false, -- Apply git status colors to filename text (default: false, only sign column) + }, + debug = { + enabled = false, -- Show file info panel in preview + show_scores = false, -- Show scores inline in the UI + show_file_info = { + file_info = true, -- Size, type, git status, frecency + score_breakdown = true, -- Total + match type, bonuses, modifiers, penalty + -- Modified + accessed timestamps. Pass a boolean to toggle the + -- whole section, or a table to hide individual rows: + -- timings = { modified = false, accessed = true } + timings = true, + full_path = true, -- Full absolute path at the bottom + }, + }, + logging = { + enabled = true, + -- Path-shape hint: each nvim startup writes a fresh sibling file + -- `++.` next to this path. The literal + -- path itself is never written to — multiple concurrent nvim instances + -- get their own per-pid file with no locking. + log_file = vim.fn.stdpath('log') .. '/fff.log', + log_level = 'info', + -- How many session log files to retain. Newest are kept, older are + -- pruned on the next startup. Set to 0 to disable retention. + retain_runs = 20, + }, + -- find_files settings + file_picker = { + current_file_label = '(current)', + }, + -- grep settings + grep = { + max_file_size = 10 * 1024 * 1024, -- Skip files larger than 10MB + max_matches_per_file = 100, -- Maximum matches per file (set 0 to unlimited) + smart_case = true, -- Case-insensitive unless query has uppercase + time_budget_ms = 150, -- Max search time in ms per call (prevents UI freeze, 0 = no limit) + modes = { 'plain', 'regex', 'fuzzy' }, -- Available grep modes and their cycling order + trim_whitespace = false, -- Strip leading whitespace from matched lines (useful for cleaner display) + -- Treat filename-like tokens (e.g. `score.rs`, `src/main.rs`) in a grep query as a + -- file-path filter, scoping the content search to matching files. When off, such + -- tokens are searched as literal text. A token is a filename if it has a valid-looking + -- extension and no wildcards. + enable_filename_constraint = false, + -- Format string for the line/column location prefix in grep results. + -- Uses vim's printf-style format: %d placeholders for line and column (1-based). + -- Default ':%d:%d' renders as ':356:1'. Use ':%d' for line-only ':356'. + location_format = ':%d:%d', + }, + } + + local migrated_user_config = handle_deprecated_config(config) + local merged_config = vim.tbl_deep_extend('force', default_config, migrated_user_config) + + -- Normalise show_file_info: accept a boolean shorthand or a partial table + local sfi = merged_config.debug and merged_config.debug.show_file_info + local default_sections = { file_info = true, score_breakdown = true, timings = true, full_path = true } + if type(sfi) == 'boolean' then + merged_config.debug.show_file_info = { + file_info = sfi, + score_breakdown = sfi, + timings = sfi, + full_path = sfi, + } + elseif type(sfi) == 'table' then + for k, v in pairs(default_sections) do + if sfi[k] == nil then sfi[k] = v end + end + else + merged_config.debug.show_file_info = default_sections + end + + state.config = merged_config +end + +--- Setup the file picker with the given configuration +--- @param config FffConfig Configuration options +function M.setup(config) vim.g.fff = config end + +--- @return FffConfig the fff configuration +function M.get() + if not state.config then init() end + return state.config +end + +--- True when preview rendering is requested by config. Defaults to `true` +--- when `config` (or its `preview` block) is missing so callers don't have +--- to guard against partial state during init. +--- @param config? FffConfig Optional config; falls back to `M.get()` when nil. +--- @return boolean +function M.preview_enabled(config) + config = config or M.get() + if not config or not config.preview then return true end + return config.preview.enabled +end + +--- @return boolean state_changed +function M.toggle_debug() + local old_debug_state = state.config.debug.show_scores + state.config.debug.show_scores = not state.config.debug.show_scores + state.config.debug.enabled = state.config.debug.show_scores + local status = state.config.debug.show_scores and 'enabled' or 'disabled' + vim.notify('FFF debug scores ' .. status, vim.log.levels.INFO) + return old_debug_state ~= state.config.debug.show_scores +end + +return M diff --git a/lua/fff/core.lua b/lua/fff/core.lua new file mode 100644 index 0000000..1b3f3a1 --- /dev/null +++ b/lua/fff/core.lua @@ -0,0 +1,176 @@ +local fuzzy = require('fff.fuzzy') +if not fuzzy then error('Failed to load fff.fuzzy module. Ensure the Rust backend is compiled and available.') end + +local M = {} + +---@class fff.core.State +local state = { + ---@type boolean + initialized = false, + ---@type boolean + file_picker_initialized = false, +} + +---@param config table +local function setup_global_autocmds(config) + local group = vim.api.nvim_create_augroup('fff_file_tracking', { clear = true }) + + if config.frecency.enabled then + vim.api.nvim_create_autocmd({ 'BufEnter' }, { + group = group, + desc = 'Track file access for FFF frecency', + callback = function(args) + local file_path = args.file + if not (file_path and file_path ~= '' and not vim.startswith(file_path, 'term://')) then return end + + vim.uv.fs_stat(file_path, function(err, stat) + if err or not stat then return end + + vim.uv.fs_realpath(file_path, function(rp_err, real_path) + if rp_err or not real_path then return end + local ok, track_err = pcall(fuzzy.track_access, real_path) + + if not ok then + vim.schedule( + function() vim.notify('FFF: Failed to track file access: ' .. tostring(track_err), vim.log.levels.ERROR) end + ) + end + end) + end) + end, + }) + end + + -- make sure that this won't work correctly if autochdir plugins are enabled + -- using a pure :cd command but will work using lua api or :e command + vim.api.nvim_create_autocmd('DirChanged', { + group = group, + callback = function() + -- Window-local `:lcd` / `:tcd` are per-window — they don't change the + -- effective project root for the picker, so bail before touching + -- anything else. + if vim.v.event.scope == 'window' then return end + if not state.initialized then return end + + local new_cwd = vim.v.event.cwd + if not new_cwd or new_cwd == '' then return end + + -- Canonicalize both sides before comparing. `vim.v.event.cwd` is + -- whatever the caller passed to `:cd` (often unexpanded, sometimes + -- containing `~` or symlinks), while `config.base_path` is the form + -- the picker was last re-indexed against (post-`expand`). Without + -- resolving symlinks + ensuring an absolute path, trivially + -- equivalent paths compare as different (`/private/var/x` vs + -- `/var/x` on macOS, resolved-vs-unresolved symlinks from LSP root + -- detection, etc.) and every such mismatch schedules a 450k-file + -- reindex through the Rust side. + local function canonicalize(p) + if not p or p == '' then return p end + local abs = vim.fn.fnamemodify(vim.fn.expand(p), ':p') + -- `:p` leaves a trailing slash on directories — strip for + -- comparison stability. + abs = abs:gsub('/+$', '') + local ok, resolved = pcall(vim.fn.resolve, abs) + return (ok and resolved ~= '') and resolved or abs + end + + local new_canonical = canonicalize(new_cwd) + local base_canonical = canonicalize(config.base_path) + if new_canonical == base_canonical then return end + + vim.schedule(function() + local change_ok, err = pcall(M.change_indexing_directory, new_canonical) + if not change_ok then + vim.notify('FFF: Failed to change indexing directory: ' .. tostring(err), vim.log.levels.ERROR) + end + end) + end, + desc = 'Automatically sync FFF directory changes', + }) +end + +--- @return boolean +M.is_file_picker_initialized = function() return state.file_picker_initialized end + +--- Change the base directory for the file picker. Triggers a reindex on the +--- Rust side and updates `config.base_path` so subsequent `:cd` events compare +--- against the new root. +--- @param new_path string New directory path to use as base +--- @return boolean ok `true` if the reindex was scheduled, `false` otherwise +M.change_indexing_directory = function(new_path) + if not new_path or new_path == '' then + vim.notify('Directory path is required', vim.log.levels.ERROR) + return false + end + + local expanded_path = vim.fn.expand(new_path) + if vim.fn.isdirectory(expanded_path) ~= 1 then + vim.notify('Directory does not exist: ' .. expanded_path, vim.log.levels.ERROR) + return false + end + + local fff_rust = M.ensure_initialized() + local config = require('fff.conf').get() + local ok, err = pcall(fff_rust.restart_index_in_path, expanded_path, { + follow_symlinks = config.follow_symlinks, + enable_fs_root_scanning = config.enable_fs_root_scanning, + enable_home_dir_scanning = config.enable_home_dir_scanning, + enable_filename_constraint = config.grep and config.grep.enable_filename_constraint, + }) + if not ok then + vim.notify('Failed to change directory: ' .. err, vim.log.levels.ERROR) + return false + end + + require('fff.conf').get().base_path = expanded_path + return true +end + +M.ensure_initialized = function() + if state.initialized then return fuzzy end + state.initialized = true + + local config = require('fff.conf').get() + if config.logging.enabled then + local log_success, log_error = + pcall(fuzzy.init_tracing, config.logging.log_file, config.logging.log_level, config.logging.retain_runs) + if log_success then + M.log_file_path = log_error + else + vim.notify('Failed to initialize logging: ' .. (tostring(log_error) or 'unknown error'), vim.log.levels.WARN) + end + end + + local frecency_db_path = config.frecency.db_path or (vim.fn.stdpath('cache') .. '/fff_frecency') + local history_db_path = config.history.db_path or (vim.fn.stdpath('data') .. '/fff_history') + + local ok, result = pcall(fuzzy.init_db, frecency_db_path, history_db_path, true) + if not ok then vim.notify('Failed to databases: ' .. tostring(result), vim.log.levels.WARN) end + + ok, result = pcall(fuzzy.init_file_picker, config.base_path, { + follow_symlinks = config.follow_symlinks, + enable_fs_root_scanning = config.enable_fs_root_scanning, + enable_home_dir_scanning = config.enable_home_dir_scanning, + enable_filename_constraint = config.grep and config.grep.enable_filename_constraint, + }) + if not ok then + vim.notify('Failed to initialize file picker: ' .. tostring(result), vim.log.levels.ERROR) + return fuzzy + end + + state.file_picker_initialized = true + setup_global_autocmds(config) + + local highlights = require('fff.highlights') + highlights.setup() + + vim.api.nvim_create_autocmd('ColorScheme', { + group = vim.api.nvim_create_augroup('fff_highlights', { clear = true }), + callback = function() highlights.setup() end, + desc = 'Re-apply FFF highlights on colorscheme change', + }) + + return fuzzy +end + +return M diff --git a/lua/fff/download.lua b/lua/fff/download.lua new file mode 100644 index 0000000..175dc4c --- /dev/null +++ b/lua/fff/download.lua @@ -0,0 +1,289 @@ +local M = {} +local system = require('fff.utils.system') +local fs_utils = require('fff.utils.fs') +local fff_version = require('fff.utils.version') + +local GITHUB_REPO = 'dmtrKovalenko/fff.nvim' + +local function get_binary_dir(plugin_dir) return plugin_dir .. '/../target/release' end + +local function get_binary_path(plugin_dir) + local binary_dir = get_binary_dir(plugin_dir) + local extension = system.get_lib_extension() + return binary_dir .. '/libfff_nvim.' .. extension +end + +local function binary_exists(plugin_dir) + local binary_path = get_binary_path(plugin_dir) + local stat = vim.uv.fs_stat(binary_path) + if stat and stat.type == 'file' then return true end + + -- On Windows the rename over a loaded DLL fails, so a verified binary may be + -- left at binary_path .. '.tmp'. Promote it now that the old session is gone. + local tmp_path = binary_path .. '.tmp' + local tmp_stat = vim.uv.fs_stat(tmp_path) + if tmp_stat and tmp_stat.type == 'file' then + -- Verify the .tmp is a valid library before promoting it, in case the + -- process was killed between the loadlib check and the rename attempt + -- during a previous download, leaving a corrupt or partial .tmp on disk. + local loader = package.loadlib(tmp_path, 'luaopen_fff_nvim') + if not loader then + vim.uv.fs_unlink(tmp_path) + return false + end + local ok = vim.uv.fs_rename(tmp_path, binary_path) + return ok ~= nil + end + + return false +end + +local function download_file(url, output_path, opts, callback) + opts = opts or {} + + local dir = vim.fn.fnamemodify(output_path, ':h') + fs_utils.mkdir_recursive(dir, function(mkdir_ok, mkdir_err) + if not mkdir_ok then + callback(false, mkdir_err) + return + end + + local curl_args = { + 'curl', + '--fail', + '--location', + '--silent', + '--show-error', + '--output', + output_path, + } + + if opts.proxy then + table.insert(curl_args, '--proxy') + table.insert(curl_args, opts.proxy) + end + + if opts.extra_curl_args then + for _, arg in ipairs(opts.extra_curl_args) do + table.insert(curl_args, arg) + end + end + + table.insert(curl_args, url) + vim.system(curl_args, {}, function(result) + if result.code ~= 0 then + callback(false, 'Failed to download: ' .. (result.stderr or 'unknown error')) + return + end + callback(true, nil) + end) + end) +end + +local function download_from_github(version, binary_path, opts, callback) + opts = opts or {} + + local triple = system.get_triple() + local extension = system.get_lib_extension() + local binary_name = triple .. '.' .. extension + local url = string.format('https://github.com/%s/releases/download/%s/%s', GITHUB_REPO, version, binary_name) + + vim.schedule(function() + vim.notify(string.format('Downloading fff.nvim binary for ' .. version), vim.log.levels.INFO) + vim.notify(string.format('Do not open fff until you see a success notification.'), vim.log.levels.WARN) + end) + + -- Download to a temp path first so we can validate before replacing the live binary. + -- If we wrote directly to binary_path and the current process already has the old + -- library loaded, package.loadlib() on the same path returns the *cached* handle — + -- meaning a truncated download would pass validation silently. + -- Using a distinct temp path forces dlopen to load the new file for real. + local tmp_path = binary_path .. '.tmp' + + download_file(url, tmp_path, { + proxy = opts.proxy, + extra_curl_args = opts.extra_curl_args, + }, function(success, err) + if not success then + vim.uv.fs_unlink(tmp_path) + callback(false, err) + return + end + + vim.schedule(function() + -- Validate the downloaded binary by actually loading it (temp path is not yet + -- loaded by this process, so dlopen loads the new file for real and catches + -- truncated or corrupt downloads). + -- Note: package.loadlib returns (nil, error_string) on failure rather than throwing. + local loader, load_err = package.loadlib(tmp_path, 'luaopen_fff_nvim') + + if not loader then + vim.uv.fs_unlink(tmp_path) + callback(false, 'Downloaded binary is not valid: ' .. (load_err or 'unknown error')) + return + end + + -- Atomically replace the live binary only after successful validation. + -- On Windows the old .dll may be locked by the current process, so rename can + -- fail if fff is already loaded. In that case, leave the verified .tmp on disk + -- so the next Neovim start can pick it up automatically. + local rename_ok, rename_err = vim.uv.fs_rename(tmp_path, binary_path) + if not rename_ok then + if vim.uv.os_uname().sysname:lower():match('windows') then + vim.notify( + 'fff.nvim binary downloaded to ' + .. tmp_path + .. '.\nThe live binary is locked by the current session — please restart Neovim to apply the update.', + vim.log.levels.WARN + ) + callback(true, nil) + else + vim.uv.fs_unlink(tmp_path) + callback(false, 'Failed to install binary: ' .. (rename_err or 'unknown error')) + end + return + end + + vim.notify('fff.nvim binary downloaded successfully!', vim.log.levels.INFO) + callback(true, nil) + end) + end) +end + +function M.ensure_downloaded(opts, callback) + opts = opts or {} + local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':h:h') + + if binary_exists(plugin_dir) and not opts.force then + callback(true, nil) + return + end + + local function on_release_tag(release_tag) + if not release_tag then + callback(false, 'Could not determine target version') + return + end + + local binary_path = get_binary_path(plugin_dir) + download_from_github(release_tag, binary_path, opts, callback) + end + + if opts.version then + on_release_tag(opts.version) + else + -- plugin_dir is /lua; parent is the repo root + local repo_root = vim.fn.fnamemodify(plugin_dir, ':h') + + -- 1. Try reading the CI-created tag on HEAD (no version computation) + local tag = fff_version.current_release_tag(repo_root) + if tag then + on_release_tag(tag) + return + end + + -- 2. No local tag — construct the nightly version (bumps patch so + -- the prerelease is higher than Cargo.toml base in semver) + local info, err = fff_version.resolve(repo_root) + if info then + on_release_tag(info.release_tag) + return + end + + callback(false, err or 'Could not determine target version') + end +end + +function M.download_binary(callback) + M.ensure_downloaded({ force = true }, function(success, err) + if not success then + if callback then + callback(false, err) + else + vim.schedule( + function() + vim.notify('Failed to download fff.nvim binary: ' .. (err or 'unknown error'), vim.log.levels.ERROR) + end + ) + end + return + end + if callback then callback(true, nil) end + end) +end + +function M.build_binary(callback) + local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':h:h') + local has_rustup = vim.fn.executable('rustup') == 1 + if not has_rustup then + callback( + false, + 'rustup is not found. It is required to build the fff.nvim binary. Install it from https://rustup.rs/' + ) + return + end + + vim.system({ 'cargo', 'build', '--release' }, { cwd = plugin_dir }, function(result) + if result.code ~= 0 then + callback(false, 'Failed to build rust binary: ' .. (result.stderr or 'unknown error')) + return + end + callback(true, nil) + end) +end + +function M.download_or_build_binary() + local done = false + local fatal_error = nil + + M.ensure_downloaded({ force = true }, function(download_success, download_error) + if download_success then + done = true + return + end + + vim.schedule( + function() + vim.notify( + 'Error downloading binary: ' .. (download_error or 'unknown error') .. '\nTrying cargo build --release\n', + vim.log.levels.WARN + ) + end + ) + + M.build_binary(function(build_success, build_error) + if not build_success then + fatal_error = 'Failed to build fff.nvim binary. Build error: ' .. (build_error or 'unknown error') + else + vim.schedule(function() vim.notify('fff.nvim binary built successfully!', vim.log.levels.INFO) end) + end + done = true + end) + end) + + -- Block the caller (and keep the Neovim event loop alive) until the entire + -- download-or-build chain finishes. This is critical for lazy.nvim build + -- hooks: lazy returns from the hook immediately after this function returns, + -- and if Neovim exits before the final rename(tmp → libfff_nvim.{dylib,so,dll}) + -- executes, the binary is never written to disk. vim.wait pumps the event + -- loop so all vim.system / vim.schedule callbacks can fire. + local timeout_ms = 1000 * 60 * 2 -- 2 minutes + local ok, wait_err = vim.wait(timeout_ms, function() return done end, 100) + if not ok and wait_err == -2 then error('fff.nvim: download_or_build_binary timed out') end + + if fatal_error then error(fatal_error) end +end + +function M.get_binary_path() + local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':h:h') + return get_binary_path(plugin_dir) +end + +function M.get_binary_cpath_component() + local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':h:h') + local binary_dir = get_binary_dir(plugin_dir) + local extension = system.get_lib_extension() + return binary_dir .. '/lib?.' .. extension +end + +return M diff --git a/lua/fff/file_picker/file_info.lua b/lua/fff/file_picker/file_info.lua new file mode 100644 index 0000000..52eace9 --- /dev/null +++ b/lua/fff/file_picker/file_info.lua @@ -0,0 +1,415 @@ +local M = {} + +---@class FFFFileInfoExtmark +---@field row integer +---@field col integer +---@field end_col integer|nil +---@field hl_group string|nil + +---@class FFFFileInfoResult +---@field lines string[] +---@field extmarks FFFFileInfoExtmark[] +---@field height integer + +---@class FFFFileInfoSections +---@field file_info boolean +---@field score_breakdown boolean +---@field timings boolean +---@field full_path boolean + +---@class FFFFileInfoFile +---@field relative_path string +---@field absolute_path string|nil +---@field size_formatted string +---@field filetype string +---@field git_status string +---@field access_frecency_score integer +---@field modification_frecency_score integer +---@field times_opened integer +---@field modified_formatted string +---@field accessed_formatted string + +---@class FFFFileInfoScore +---@field total integer +---@field match_type string +---@field base_score integer +---@field filename_bonus integer +---@field special_filename_bonus integer +---@field frecency_boost integer +---@field combo_match_boost integer +---@field distance_penalty integer +---@field current_file_penalty integer + +local Builder = {} +Builder.__index = Builder + +function Builder.new() return setmetatable({ lines = {}, extmarks = {} }, Builder) end + +function Builder:add_line(text) table.insert(self.lines, text or '') end + +function Builder:add_hl(row, col, end_col, hl_group) + if not hl_group or hl_group == '' then return end + table.insert(self.extmarks, { row = row, col = col, end_col = end_col, hl_group = hl_group }) +end + +-- Section header: `─

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 OALch5)x6VILH`B9>_^D} literal 0 HcmV?d00001 diff --git a/crates/fff-c/Cargo.toml b/crates/fff-c/Cargo.toml new file mode 100644 index 0000000..72aea2c --- /dev/null +++ b/crates/fff-c/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "fff-c" +version = "0.9.6" +edition = "2024" +description = "Raw C api of FFF file finder" +license = "MIT" + +[lib] +crate-type = ["cdylib"] + +[features] +default = ["ripgrep"] # use ripgrep base crates to avoid requiring zig for rust crate +ripgrep = ["fff/ripgrep", "fff-query-parser/ripgrep"] +zlob = ["fff/zlob", "fff-query-parser/zlob"] + +[dependencies] +git2.workspace = true + +fff = { package = "fff-search", path = "../fff-core", version = "0.9.6", default-features = false } +fff-query-parser = { path = "../fff-query-parser", version = "0.9.6", default-features = false } +serde_json = "1.0" diff --git a/crates/fff-c/cbindgen.toml b/crates/fff-c/cbindgen.toml new file mode 100644 index 0000000..86c67f9 --- /dev/null +++ b/crates/fff-c/cbindgen.toml @@ -0,0 +1,33 @@ +language = "C" +header = "/* Generated by cbindgen — do not edit manually. */" +include_guard = "FFF_C_H" +include_version = true +no_includes = true +sys_includes = ["stdint.h", "stdbool.h", "stddef.h"] + +[export] +include = [ + "FffResult", + "FffSearchResult", "FffFileItem", "FffScore", "FffLocation", + "FffGrepResult", "FffGrepMatch", "FffMatchRange", + "FffScanProgress", +] + +[export.rename] +"FffResult" = "FffResult" +"FffSearchResult" = "FffSearchResult" +"FffFileItem" = "FffFileItem" +"FffScore" = "FffScore" +"FffLocation" = "FffLocation" +"FffGrepResult" = "FffGrepResult" +"FffGrepMatch" = "FffGrepMatch" +"FffMatchRange" = "FffMatchRange" +"FffScanProgress" = "FffScanProgress" + +[fn] +sort_by = "None" +# Translate `#[deprecated]` on extern "C" fns into a real C compiler +# attribute so callers get a warning when they use a removed/legacy entry. +# `{}` is substituted with the Rust deprecation note as a C string literal +# (already quoted) — do not wrap in extra quotes. +deprecated_with_note = "__attribute__((deprecated({})))" diff --git a/crates/fff-c/include/fff.h b/crates/fff-c/include/fff.h new file mode 100644 index 0000000..8bc4205 --- /dev/null +++ b/crates/fff-c/include/fff.h @@ -0,0 +1,1353 @@ +/* Generated by cbindgen — do not edit manually. */ + +#ifndef FFF_C_H +#define FFF_C_H + +/* Generated with cbindgen:0.29.2 */ + +#include +#include +#include + +/** + * Current used version of [`FffCreateOptions`]. + */ +#define FFF_CREATE_OPTIONS_VERSION 2 + +/** + * Result envelope returned by all `fff_*` functions. + * + * Heap-allocated. The caller must free it with `fff_free_result`. Calling `fff_free_result` + * **does not** deallocate the underlying `handle` pointer. It needs to be cleaned separately. + * see (`fff_destroy`, `fff_free_search_result`, `fff_free_grep_result`, `fff_free_string`, etc.). + * + * Depending on the function, the payload is delivered through different fields: + * + * | Function | Payload field | Type | + * |----------------------------|---------------|-------------------------------| + * | `fff_create_instance` | `handle` | opaque instance pointer | + * | `fff_search` | `handle` | `*mut FffSearchResult` | + * | `fff_live_grep` | `handle` | `*mut FffGrepResult` | + * | `fff_multi_grep` | `handle` | `*mut FffGrepResult` | + * | `fff_get_scan_progress` | `handle` | `*mut FffScanProgress` | + * | `fff_health_check` | `handle` | `*mut c_char` (JSON string) | + * | `fff_get_historical_query` | `handle` | `*mut c_char` (string or null)| + * | `fff_wait_for_scan` | `int_value` | 1 = completed, 0 = timed out | + * | `fff_track_query` | `int_value` | 1 = success, 0 = failure | + * | `fff_refresh_git_status` | `int_value` | number of files updated | + * | `fff_scan_files` | (none) | success flag only | + * | `fff_restart_index` | (none) | success flag only | + * + * On failure, `success` is false and `error` contains the message. + */ +typedef struct FffResult { + /** + * Whether the operation succeeded. + */ + bool success; + /** + * Error message on failure. Null on success. + */ + char *error; + /** + * Opaque pointer payload. May be null. + */ + void *handle; + /** + * Integer payload for simple return values (bool as 0/1, counts, etc.). + */ + int64_t int_value; +} FffResult; + +/** + * Options for `fff_create_instance_with`. + * + * Versioned struct: you populate the struct at your call level, we guarantee that + * the version is stable across the version changes, new fields only appended! + */ +typedef struct FffCreateOptions { + /** + * Set to [`FFF_CREATE_OPTIONS_VERSION`] when allocating. Used by the + * library to determine which trailing fields are populated. + */ + uint32_t version; + /** + * Directory to index (required, non-NULL). + */ + const char *base_path; + /** + * Frecency LMDB database path. NULL/empty to skip frecency tracking. + */ + const char *frecency_db_path; + /** + * Query history LMDB database path. NULL/empty to skip query tracking. + */ + const char *history_db_path; + /** + * Pre-populate mmap caches for top-frecency files after the initial scan. + */ + bool enable_mmap_cache; + /** + * Build content index after the initial scan for faster grep. + */ + bool enable_content_indexing; + /** + * Start a background file-system watcher for live updates. + */ + bool watch; + /** + * Enable AI-agent optimizations. + */ + bool ai_mode; + /** + * Tracing log file path. NULL/empty to skip log init. + */ + const char *log_file_path; + /** + * Log level: `"trace" | "debug" | "info" | "warn" | "error"`. + * NULL/empty defaults to `"info"`. Ignored when `log_file_path` is unset. + */ + const char *log_level; + /** + * Content cache file-count cap. 0 = auto. + */ + uint64_t cache_budget_max_files; + /** + * Content cache byte cap. 0 = auto. + */ + uint64_t cache_budget_max_bytes; + /** + * Per-file byte cap inside the content cache. 0 = auto. + */ + uint64_t cache_budget_max_file_size; + /** + * Allow indexing the filesystem root (`/`). Off by default — root is + * rarely the intended target and floods the watcher with churn. + */ + bool enable_fs_root_scanning; + /** + * Allow indexing the user's home directory. Same trade-off as + * `enable_fs_root_scanning`. + */ + bool enable_home_dir_scanning; + /** + * Follow symlinks during scan and watcher walks. Off by default — + * enabling this without external loop protection can wedge the watcher + * on cyclic symlink graphs. Caller is responsible for the trade-off. + */ + bool follow_symlinks; +} FffCreateOptions; + +/** + * A file item returned by `fff_search`. + * + * All string fields are heap-allocated and owned by the parent `FffSearchResult`. + * Free the entire result with `fff_free_search_result`. + */ +typedef struct FffFileItem { + char *relative_path; + char *file_name; + char *git_status; + uint64_t size; + uint64_t modified; + int64_t access_frecency_score; + int64_t modification_frecency_score; + int64_t total_frecency_score; + bool is_binary; +} FffFileItem; + +/** + * Score breakdown for a search result. + */ +typedef struct FffScore { + int32_t total; + int32_t base_score; + int32_t filename_bonus; + int32_t special_filename_bonus; + int32_t frecency_boost; + int32_t distance_penalty; + int32_t current_file_penalty; + int32_t combo_match_boost; + int32_t path_alignment_bonus; + bool exact_match; + char *match_type; +} FffScore; + +/** + * Location parsed from a query string (e.g. `"file.ts:42:10"`). + * + * `tag` encodes the variant: + * 0 = no location, + * 1 = line only (`line` is set), + * 2 = position (`line` + `col`), + * 3 = range (`line`/`col` = start, `end_line`/`end_col` = end). + */ +typedef struct FffLocation { + uint8_t tag; + int32_t line; + int32_t col; + int32_t end_line; + int32_t end_col; +} FffLocation; + +/** + * Search result returned by `fff_search`. + * + * The caller must free this with `fff_free_search_result`. + */ +typedef struct FffSearchResult { + /** + * Pointer to a heap-allocated array of `FffFileItem` (length = `count`). + */ + struct FffFileItem *items; + /** + * Pointer to a heap-allocated array of `FffScore` (length = `count`). + */ + struct FffScore *scores; + /** + * Number of items/scores in the arrays. + */ + uint32_t count; + /** + * Total number of files that matched the query. + */ + uint32_t total_matched; + /** + * Total number of indexed files. + */ + uint32_t total_files; + /** + * Location parsed from the query string. + */ + struct FffLocation location; +} FffSearchResult; + +/** + * A byte range within a matched line, used for highlighting. + */ +typedef struct FffMatchRange { + uint32_t start; + uint32_t end; +} FffMatchRange; + +/** + * A single grep match with file and line information. + * + * All string fields and arrays are heap-allocated. Free the parent + * `FffGrepResult` with `fff_free_grep_result` to release everything. + */ +typedef struct FffGrepMatch { + char *relative_path; + char *file_name; + char *git_status; + char *line_content; + struct FffMatchRange *match_ranges; + char **context_before; + char **context_after; + uint64_t size; + uint64_t modified; + int64_t total_frecency_score; + int64_t access_frecency_score; + int64_t modification_frecency_score; + uint64_t line_number; + uint64_t byte_offset; + uint32_t col; + uint32_t match_ranges_count; + uint32_t context_before_count; + uint32_t context_after_count; + uint16_t fuzzy_score; + bool has_fuzzy_score; + bool is_binary; + bool is_definition; +} FffGrepMatch; + +/** + * Grep result returned by `fff_live_grep` and `fff_multi_grep`. + * + * The caller must free this with `fff_free_grep_result`. + */ +typedef struct FffGrepResult { + /** + * Pointer to a heap-allocated array of `FffGrepMatch` (length = `count`). + */ + struct FffGrepMatch *items; + /** + * Number of matches in the `items` array. + */ + uint32_t count; + /** + * Total number of matches (always equal to `count`). + */ + uint32_t total_matched; + /** + * Number of files actually opened and searched in this call. + */ + uint32_t total_files_searched; + /** + * Total number of indexed files (before any filtering). + */ + uint32_t total_files; + /** + * Number of files eligible for search after filtering. + */ + uint32_t filtered_file_count; + /** + * File offset for the next page. 0 if all files have been searched. + */ + uint32_t next_file_offset; + /** + * Regex compilation error when falling back to literal matching. Null if none. + */ + char *regex_fallback_error; +} FffGrepResult; + +/** + * Scan progress returned by `fff_get_scan_progress`. + * The caller must free this with `fff_free_scan_progress`. + */ +typedef struct FffScanProgress { + uint64_t scanned_files_count; + bool is_scanning; + bool is_watcher_ready; + bool is_warmup_complete; +} FffScanProgress; + +/** + * A directory item returned by `fff_search_directories`. + * + * All string fields are heap-allocated and owned by the parent `FffDirSearchResult`. + * Free the entire result with `fff_free_dir_search_result`. + */ +typedef struct FffDirItem { + char *relative_path; + char *dir_name; + int32_t max_access_frecency; +} FffDirItem; + +/** + * Directory search result returned by `fff_search_directories`. + * + * The caller must free this with `fff_free_dir_search_result`. + */ +typedef struct FffDirSearchResult { + /** + * Pointer to a heap-allocated array of `FffDirItem` (length = `count`). + */ + struct FffDirItem *items; + /** + * Pointer to a heap-allocated array of `FffScore` (length = `count`). + */ + struct FffScore *scores; + /** + * Number of items/scores in the arrays. + */ + uint32_t count; + /** + * Total number of directories that matched the query. + */ + uint32_t total_matched; + /** + * Total number of indexed directories. + */ + uint32_t total_dirs; +} FffDirSearchResult; + +/** + * A single item in a mixed (files + directories) search result. + * + * `item_type`: 0 = file, 1 = directory. + * All string fields are heap-allocated and owned by the parent `FffMixedSearchResult`. + */ +typedef struct FffMixedItem { + /** + * 0 = file, 1 = directory. + */ + uint8_t item_type; + char *relative_path; + /** + * Filename for files, last directory segment for directories. + */ + char *display_name; + char *git_status; + uint64_t size; + uint64_t modified; + /** + * The access frecency score for files, or max access frecency among all the immediate + * children for directories. + */ + int64_t access_frecency_score; + /** + * Always 0 for directories + */ + int64_t modification_frecency_score; + /** + * Always 0 for directories + */ + int64_t total_frecency_score; + /** + * Always 0 for directories + */ + bool is_binary; +} FffMixedItem; + +/** + * Mixed search result returned by `fff_search_mixed`. + * + * The caller must free this with `fff_free_mixed_search_result`. + */ +typedef struct FffMixedSearchResult { + /** + * Pointer to a heap-allocated array of `FffMixedItem` (length = `count`). + */ + struct FffMixedItem *items; + /** + * Pointer to a heap-allocated array of `FffScore` (length = `count`). + */ + struct FffScore *scores; + /** + * Number of items/scores in the arrays. + */ + uint32_t count; + /** + * Total number of items (files + dirs) that matched the query. + */ + uint32_t total_matched; + /** + * Total number of indexed files. + */ + uint32_t total_files; + /** + * Total number of indexed directories. + */ + uint32_t total_dirs; + /** + * Location parsed from the query string. + */ + struct FffLocation location; +} FffMixedSearchResult; + +/** + * Create a new file finder instance (legacy 8-arg positional signature). + * + * @deprecated Use [`fff_create_instance_with`] (or + * [`fff_create_instance_with_value`] for FFI bindings) — both take the + * versioned [`FffCreateOptions`] struct that evolves without ABI breaks. + * This function delegates to `fff_create_instance_with` internally; the + * `use_unsafe_no_lock` parameter is deprecated and ignored. + * + * ## Safety + * See `fff_create_instance_with`. + */ +__attribute__((deprecated("Use fff_create_instance_with (by pointer) or fff_create_instance_with_value (by value) with FffCreateOptions instead. The struct evolves without ABI breaks."))) +struct FffResult *fff_create_instance(const char *base_path, + const char *frecency_db_path, + const char *history_db_path, + bool _use_unsafe_no_lock, + bool enable_mmap_cache, + bool enable_content_indexing, + bool watch, + bool ai_mode); + +/** + * Create a new file finder instance (legacy 13-arg positional signature). + * + * @deprecated Use [`fff_create_instance_with`] (or + * [`fff_create_instance_with_value`] for FFI bindings) — both take the + * versioned [`FffCreateOptions`] struct that evolves without ABI breaks. + * The `use_unsafe_no_lock` parameter is deprecated and ignored. + * + * ## Safety + * See `fff_create_instance_with`. + */ +__attribute__((deprecated("Use fff_create_instance_with (by pointer) or fff_create_instance_with_value (by value) with FffCreateOptions instead. The struct evolves without ABI breaks."))) +struct FffResult *fff_create_instance2(const char *base_path, + const char *frecency_db_path, + const char *history_db_path, + bool _use_unsafe_no_lock, + bool enable_mmap_cache, + bool enable_content_indexing, + bool watch, + bool ai_mode, + const char *log_file_path, + const char *log_level, + uint64_t cache_budget_max_files, + uint64_t cache_budget_max_bytes, + uint64_t cache_budget_max_file_size); + +/** + * Create a new file finder instance from an [`FffCreateOptions`] struct. + * + * **Direct C consumers** populate the struct (designated initializers + * recommended), set `version` to [`FFF_CREATE_OPTIONS_VERSION`], and pass + * it by pointer. New fields are appended in future versions; old callers + * passing `version = 1` keep working forever. + * + * **FFI consumers** that prefer struct-by-value semantics (e.g. ffi-rs's + * `paramsType: [structDef]`) should use [`fff_create_instance_with_value`] + * instead — it's a thin calling-convention adapter that delegates here. + * + * Required: `opts.base_path` must be non-NULL and non-empty. + * + * When all three `cache_budget_*` values are 0 the budget is auto-computed + * from repo size after the initial scan. Otherwise an explicit budget is + * used: any field left at 0 falls back to its `unlimited()` default. + * + * ## Safety + * * `opts` must be a valid pointer to an `FffCreateOptions` whose `version` + * is in the range `1..=FFF_CREATE_OPTIONS_VERSION`. + * * All string pointers inside `opts` must be valid null-terminated UTF-8 + * or NULL. + */ +struct FffResult *fff_create_instance_with(const struct FffCreateOptions *opts); + +/** + * Calling-convention adapter for [`fff_create_instance_with`]. + * + * Same logic, but takes the [`FffCreateOptions`] struct **by value**. This + * makes the function callable from FFI libraries whose native struct + * support passes structs by value on the wire (e.g. Node's `ffi-rs` with + * `paramsType: [structDef]`). + * + * This is **not** a versioned wrapper — when new fields are appended to + * `FffCreateOptions`, both this function and `fff_create_instance_with` + * pick them up automatically with no signature change. + * + * ## Safety + * All `*const c_char` fields inside `opts` must be valid null-terminated + * UTF-8 or NULL. The struct itself is consumed by value. + */ +struct FffResult *fff_create_instance_with_value(struct FffCreateOptions opts); + +/** + * Destroy a file finder instance and free all its resources. + * + * ## Safety + * `fff_handle` must be a valid pointer returned by `fff_create_instance`, or null (no-op). + */ +void fff_destroy(void *fff_handle); + +/** + * Perform fuzzy search on indexed files. + * + * # Parameters + * + * * `fff_handle` – instance from `fff_create_instance` + * * `query` – search query string + * * `current_file` – path of the currently open file for deprioritization (NULL/empty to skip) + * * `max_threads` – maximum worker threads (0 = auto-detect) + * * `page_index` – pagination offset (0 = first page) + * * `page_size` – results per page (0 = default 100) + * * `combo_boost_multiplier` – score multiplier for combo matches (0 = default 100) + * * `min_combo_count` – minimum combo count before boost applies (0 = default 3) + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `query` and `current_file` must be valid null-terminated UTF-8 strings or NULL. + */ +struct FffResult *fff_search(void *fff_handle, + const char *query, + const char *current_file, + uint32_t max_threads, + uint32_t page_index, + uint32_t page_size, + int32_t combo_boost_multiplier, + uint32_t min_combo_count); + +/** + * Glob-only search: filter indexed files by a single glob pattern, rank by + * frecency, and paginate. Bypasses the regular query parser entirely. + * + * Use this when you already have a literal glob pattern (e.g. `*.rs`, a + * recursive `**` match, or `src/components` prefix) and want neither fuzzy + * matching nor multi-token constraint parsing. Ranking falls back to + * frecency because there is no fuzzy score to combine with. + * + * # Parameters + * + * * `fff_handle` - instance from `fff_create_instance` + * * `pattern` - glob pattern (required, no parsing - passed through verbatim) + * * `current_file` - path of the currently open file for deprioritization (NULL/empty to skip) + * * `max_threads` - maximum worker threads (0 = auto-detect) + * * `page_index` - pagination offset (0 = first page) + * * `page_size` - results per page (0 = default 100) + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `pattern` and `current_file` must be valid null-terminated UTF-8 strings or NULL. + */ +struct FffResult *fff_glob(void *fff_handle, + const char *pattern, + const char *current_file, + uint32_t max_threads, + uint32_t page_index, + uint32_t page_size); + +/** + * Perform fuzzy search on indexed directories. + * + * # Parameters + * + * * `fff_handle` – instance from `fff_create_instance` + * * `query` – search query string + * * `current_file` – path of the currently open file for distance scoring (NULL/empty to skip) + * * `max_threads` – maximum worker threads (0 = auto-detect) + * * `page_index` – pagination offset (0 = first page) + * * `page_size` – results per page (0 = default 100) + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `query` and `current_file` must be valid null-terminated UTF-8 strings or NULL. + */ +struct FffResult *fff_search_directories(void *fff_handle, + const char *query, + const char *current_file, + uint32_t max_threads, + uint32_t page_index, + uint32_t page_size); + +/** + * Perform a mixed fuzzy search across both files and directories. + * + * Returns a single flat list where files and directories are interleaved + * by total score in descending order. Each item has an `item_type` field + * (0 = file, 1 = directory). + * + * # Parameters + * + * * `fff_handle` – instance from `fff_create_instance` + * * `query` – search query string + * * `current_file` – path of the currently open file (NULL/empty to skip) + * * `max_threads` – maximum worker threads (0 = auto-detect) + * * `page_index` – pagination offset (0 = first page) + * * `page_size` – results per page (0 = default 100) + * * `combo_boost_multiplier` – score multiplier for combo matches (0 = default 100) + * * `min_combo_count` – minimum combo count before boost applies (0 = default 3) + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `query` and `current_file` must be valid null-terminated UTF-8 strings or NULL. + */ +struct FffResult *fff_search_mixed(void *fff_handle, + const char *query, + const char *current_file, + uint32_t max_threads, + uint32_t page_index, + uint32_t page_size, + int32_t combo_boost_multiplier, + uint32_t min_combo_count); + +/** + * Perform content search (grep) across indexed files. + * + * # Parameters + * + * * `fff_handle` – instance from `fff_create_instance` + * * `query` – search query (supports constraint syntax like `*.rs pattern`) + * * `mode` – 0 = plain text (SIMD), 1 = regex, 2 = fuzzy + * * `max_file_size` – skip files larger than this in bytes (0 = default 10 MB) + * * `max_matches_per_file` – max matches per file (0 = unlimited) + * * `smart_case` – case-insensitive when query is all lowercase + * * `file_offset` – file-based pagination offset (0 = start) + * * `page_limit` – max matches to return (0 = default 50) + * * `time_budget_ms` – wall-clock budget in ms (0 = unlimited) + * * `before_context` – context lines before each match + * * `after_context` – context lines after each match + * * `classify_definitions` – tag matches that are code definitions + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `query` must be a valid null-terminated UTF-8 string. + */ +struct FffResult *fff_live_grep(void *fff_handle, + const char *query, + uint8_t mode, + uint64_t max_file_size, + uint32_t max_matches_per_file, + bool smart_case, + uint32_t file_offset, + uint32_t page_limit, + uint64_t time_budget_ms, + uint32_t before_context, + uint32_t after_context, + bool classify_definitions); + +/** + * Perform multi-pattern OR search (Aho-Corasick) across indexed files. + * + * Searches for lines matching ANY of the provided patterns using + * SIMD-accelerated multi-needle matching. + * + * # Parameters + * + * * `fff_handle` – instance from `fff_create_instance` + * * `patterns_joined` – patterns separated by `\n` (e.g. `"foo\nbar\nbaz"`) + * * `constraints` – file filter like `"*.rs"` or `"/src/"` (NULL/empty to skip) + * * `max_file_size` – skip files larger than this in bytes (0 = default 10 MB) + * * `max_matches_per_file` – max matches per file (0 = unlimited) + * * `smart_case` – case-insensitive when all patterns are lowercase + * * `file_offset` – file-based pagination offset (0 = start) + * * `page_limit` – max matches to return (0 = default 50) + * * `time_budget_ms` – wall-clock budget in ms (0 = unlimited) + * * `before_context` – context lines before each match + * * `after_context` – context lines after each match + * * `classify_definitions` – tag matches that are code definitions + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `patterns_joined` and `constraints` must be valid null-terminated UTF-8 or NULL. + */ +struct FffResult *fff_multi_grep(void *fff_handle, + const char *patterns_joined, + const char *constraints, + uint64_t max_file_size, + uint32_t max_matches_per_file, + bool smart_case, + uint32_t file_offset, + uint32_t page_limit, + uint64_t time_budget_ms, + uint32_t before_context, + uint32_t after_context, + bool classify_definitions); + +/** + * Trigger a rescan of the file index. + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +struct FffResult *fff_scan_files(void *fff_handle); + +/** + * Check if a scan is currently in progress. + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +bool fff_is_scanning(void *fff_handle); + +/** + * Get the base path of the file picker. + * + * Returns an `FffResult` with a heap-allocated C string in the `handle` + * field. Free the string with `fff_free_string` after reading it. + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +struct FffResult *fff_get_base_path(void *fff_handle); + +/** + * Get scan progress information. + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +struct FffResult *fff_get_scan_progress(void *fff_handle); + +/** + * Wait for initial scan to complete. + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +struct FffResult *fff_wait_for_scan(void *fff_handle, uint64_t timeout_ms); + +/** + * Wait for the background file watcher to be ready. + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +struct FffResult *fff_wait_for_watcher(void *fff_handle, uint64_t timeout_ms); + +/** + * Restart indexing in a new directory. + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `new_path` must be a valid null-terminated UTF-8 string. + */ +struct FffResult *fff_restart_index(void *fff_handle, const char *new_path); + +/** + * Refresh git status cache. + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +struct FffResult *fff_refresh_git_status(void *fff_handle); + +/** + * Track query completion for smart suggestions. + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + * * `query` and `file_path` must be valid null-terminated UTF-8 strings. + */ +struct FffResult *fff_track_query(void *fff_handle, const char *query, const char *file_path); + +/** + * Get historical query by offset (0 = most recent). + * + * ## Safety + * `fff_handle` must be a valid instance pointer from `fff_create_instance`. + */ +struct FffResult *fff_get_historical_query(void *fff_handle, uint64_t offset); + +/** + * Get health check information. + * + * ## Safety + * * `fff_handle` must be a valid instance pointer from `fff_create_instance`, or null for + * a limited health check (version + git only). + * * `test_path` can be null or a valid null-terminated UTF-8 string. + */ +struct FffResult *fff_health_check(void *fff_handle, const char *test_path); + +/** + * Free a search result returned by `fff_search`. + * + * This frees the `FffSearchResult` struct, its `items` and `scores` arrays, + * and all heap-allocated strings within each item and score. + * + * ## Safety + * `result` must be a valid pointer previously returned via `FffResult.handle` + * from `fff_search`, or null (no-op). + */ +void fff_free_search_result(struct FffSearchResult *result); + +/** + * Get a pointer to the `index`-th `FffFileItem` in a search result. + * + * Returns null if `result` is null or `index >= result->count`. + * The returned pointer is valid until the search result is freed. + * + * ## Safety + * `result` must be a valid `FffSearchResult` pointer from `fff_search`. + */ +const struct FffFileItem *fff_search_result_get_item(const struct FffSearchResult *result, + uint32_t index); + +/** + * Get a pointer to the `index`-th `FffScore` in a search result. + * + * Returns null if `result` is null or `index >= result->count`. + * The returned pointer is valid until the search result is freed. + * + * ## Safety + * `result` must be a valid `FffSearchResult` pointer from `fff_search`. + */ +const struct FffScore *fff_search_result_get_score(const struct FffSearchResult *result, + uint32_t index); + +/** + * Free a grep result returned by `fff_live_grep` or `fff_multi_grep`. + * + * This frees the `FffGrepResult` struct, its `items` array, and all + * heap-allocated strings, match ranges, and context arrays within each match. + * + * ## Safety + * `result` must be a valid pointer previously returned via `FffResult.handle` + * from `fff_live_grep` or `fff_multi_grep`, or null (no-op). + */ +void fff_free_grep_result(struct FffGrepResult *result); + +/** + * Get a pointer to the `index`-th `FffGrepMatch` in a grep result. + * + * Returns null if `result` is null or `index >= result->count`. + * The returned pointer is valid until the grep result is freed. + * + * ## Safety + * `result` must be a valid `FffGrepResult` pointer from `fff_live_grep` or `fff_multi_grep`. + */ +const struct FffGrepMatch *fff_grep_result_get_match(const struct FffGrepResult *result, + uint32_t index); + +/** + * Free a scan progress result returned by `fff_get_scan_progress`. + * + * ## Safety + * `result` must be a valid pointer previously returned via `FffResult.handle` + * from `fff_get_scan_progress`, or null (no-op). + */ +void fff_free_scan_progress(struct FffScanProgress *result); + +/** + * Offset a pointer by `byte_offset` bytes. + * + * General-purpose utility for FFI consumers that need pointer arithmetic + * (e.g. iterating over arrays). Returns null if `base` is null. + * + * ## Safety + * The resulting pointer must be within the bounds of the original allocation. + */ +const void *fff_ptr_offset(const void *base, uintptr_t byte_offset); + +/** + * Free a result returned by any `fff_*` function. + * **IMPORTANT:** this doesn't clean the the internal handle, so it is safe to call right after + * you handle the error case. + * + * Note: Many non-libffi implementations are not supporting struct-by-value returns, so it's more + * convenient to have pointer returned at most of the time, though allocating result for every call + * is annoying, so we just rely on the fact that our allocator is good enough. + * + * ## Safety + * `result_ptr` must be a valid pointer returned by a `fff_*` function. + */ +void fff_free_result(struct FffResult *result_ptr); + +/** + * Free a string returned by `fff_*` functions. + * + * ## Safety + * `s` must be a valid C string allocated by this library. + */ +void fff_free_string(char *s); + +/** + * Free a directory search result returned by `fff_search_directories`. + * + * ## Safety + * `result` must be a valid pointer previously returned via `FffResult.handle` + * from `fff_search_directories`, or null (no-op). + */ +void fff_free_dir_search_result(struct FffDirSearchResult *result); + +/** + * Get a pointer to the `index`-th `FffDirItem` in a directory search result. + * + * ## Safety + * `result` must be a valid `FffDirSearchResult` pointer from `fff_search_directories`. + */ +const struct FffDirItem *fff_dir_search_result_get_item(const struct FffDirSearchResult *result, + uint32_t index); + +/** + * Get a pointer to the `index`-th `FffScore` in a directory search result. + * + * ## Safety + * `result` must be a valid `FffDirSearchResult` pointer from `fff_search_directories`. + */ +const struct FffScore *fff_dir_search_result_get_score(const struct FffDirSearchResult *result, + uint32_t index); + +/** + * Free a mixed search result returned by `fff_search_mixed`. + * + * ## Safety + * `result` must be a valid pointer previously returned via `FffResult.handle` + * from `fff_search_mixed`, or null (no-op). + */ +void fff_free_mixed_search_result(struct FffMixedSearchResult *result); + +/** + * Get a pointer to the `index`-th `FffMixedItem` in a mixed search result. + * + * ## Safety + * `result` must be a valid `FffMixedSearchResult` pointer from `fff_search_mixed`. + */ +const struct FffMixedItem *fff_mixed_search_result_get_item(const struct FffMixedSearchResult *result, + uint32_t index); + +/** + * Get a pointer to the `index`-th `FffScore` in a mixed search result. + * + * ## Safety + * `result` must be a valid `FffMixedSearchResult` pointer from `fff_search_mixed`. + */ +const struct FffScore *fff_mixed_search_result_get_score(const struct FffMixedSearchResult *result, + uint32_t index); + +/** + * Returns the relative path of a file item (e.g. `"src/main.rs"`). + * + * Returns null if `item` is null. The returned pointer is valid for the + * lifetime of the owning `FffSearchResult`; do not free it directly. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +const char *fff_file_item_get_relative_path(const struct FffFileItem *item); + +/** + * Returns the file-name component of a file item (e.g. `"main.rs"`). + * + * Returns null if `item` is null. Do not free the returned pointer. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +const char *fff_file_item_get_file_name(const struct FffFileItem *item); + +/** + * Returns the git status string for a file item (e.g. `"M "`, `"??"`) + * or null if git is unavailable, the file is untracked, or `item` is null. + * + * Do not free the returned pointer. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +const char *fff_file_item_get_git_status(const struct FffFileItem *item); + +/** + * Returns the file size in bytes. Returns `0` if `item` is null. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +uint64_t fff_file_item_get_size(const struct FffFileItem *item); + +/** + * Returns the last-modified time as seconds since the UNIX epoch. + * Returns `0` if `item` is null. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +uint64_t fff_file_item_get_modified(const struct FffFileItem *item); + +/** + * Returns the combined frecency score. Returns `0` if `item` is null. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +int64_t fff_file_item_get_total_frecency_score(const struct FffFileItem *item); + +/** + * Returns the access-based frecency score. Returns `0` if `item` is null. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +int64_t fff_file_item_get_access_frecency_score(const struct FffFileItem *item); + +/** + * Returns the modification-based frecency score. Returns `0` if `item` is null. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +int64_t fff_file_item_get_modification_frecency_score(const struct FffFileItem *item); + +/** + * Returns `true` if the file was detected as binary. Returns `false` if `item` is null. + * + * ## Safety + * `item` must be a valid `FffFileItem` pointer or null. + */ +bool fff_file_item_get_is_binary(const struct FffFileItem *item); + +/** + * Returns the relative path of the file containing this grep match. + * + * Returns null if `m` is null. Do not free the returned pointer. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +const char *fff_grep_match_get_relative_path(const struct FffGrepMatch *m); + +/** + * Returns the file-name component of the file containing this grep match. + * + * Returns null if `m` is null. Do not free the returned pointer. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +const char *fff_grep_match_get_file_name(const struct FffGrepMatch *m); + +/** + * Returns the git status string for the matched file (e.g. `"M "`, `"??"`) + * or null if git is unavailable, the file is untracked, or `m` is null. + * + * Do not free the returned pointer. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +const char *fff_grep_match_get_git_status(const struct FffGrepMatch *m); + +/** + * Returns the full text content of the matched line. + * + * Returns null if `m` is null. Do not free the returned pointer. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +const char *fff_grep_match_get_line_content(const struct FffGrepMatch *m); + +/** + * Returns the 1-based line number of the match within its file. + * Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint64_t fff_grep_match_get_line_number(const struct FffGrepMatch *m); + +/** + * Returns the 0-based column of the match start within its line. + * Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint32_t fff_grep_match_get_col(const struct FffGrepMatch *m); + +/** + * Returns the byte offset of the match start from the beginning of the file. + * Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint64_t fff_grep_match_get_byte_offset(const struct FffGrepMatch *m); + +/** + * Returns the file size in bytes for the matched file. Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint64_t fff_grep_match_get_size(const struct FffGrepMatch *m); + +/** + * Returns the combined frecency score for the matched file. + * Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +int64_t fff_grep_match_get_total_frecency_score(const struct FffGrepMatch *m); + +/** + * Returns the access-based frecency score for the matched file. + * Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +int64_t fff_grep_match_get_access_frecency_score(const struct FffGrepMatch *m); + +/** + * Returns the modification-based frecency score for the matched file. + * Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +int64_t fff_grep_match_get_modification_frecency_score(const struct FffGrepMatch *m); + +/** + * Returns the last-modified time as seconds since the UNIX epoch for the matched file. + * Returns `0` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint64_t fff_grep_match_get_modified(const struct FffGrepMatch *m); + +/** + * Returns the number of highlight ranges in this match. Returns `0` if `m` is null. + * + * Use with [`fff_grep_match_get_match_range`] to iterate the highlight spans. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint32_t fff_grep_match_get_match_ranges_count(const struct FffGrepMatch *m); + +/** + * Returns a pointer to the `index`-th [`FffMatchRange`] highlight span. + * + * Returns null if `m` is null, `index >= match_ranges_count`, or the + * ranges array is null. The returned pointer is valid until the owning + * `FffGrepResult` is freed; do not free it directly. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +const struct FffMatchRange *fff_grep_match_get_match_range(const struct FffGrepMatch *m, + uint32_t index); + +/** + * Returns the number of context lines captured before the match. + * Returns `0` if `m` is null. + * + * Use with [`fff_grep_match_get_context_before`] to read each line. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint32_t fff_grep_match_get_context_before_count(const struct FffGrepMatch *m); + +/** + * Returns the `index`-th context line before the match. + * + * Returns null if `m` is null, `index >= context_before_count`, or the + * context array is null. Do not free the returned pointer. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +const char *fff_grep_match_get_context_before(const struct FffGrepMatch *m, uint32_t index); + +/** + * Returns the number of context lines captured after the match. + * Returns `0` if `m` is null. + * + * Use with [`fff_grep_match_get_context_after`] to read each line. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint32_t fff_grep_match_get_context_after_count(const struct FffGrepMatch *m); + +/** + * Returns the `index`-th context line after the match. + * + * Returns null if `m` is null, `index >= context_after_count`, or the + * context array is null. Do not free the returned pointer. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +const char *fff_grep_match_get_context_after(const struct FffGrepMatch *m, uint32_t index); + +/** + * Returns the fuzzy match score. Returns `0` if `m` is null or no fuzzy + * score is present. + * + * Always check [`fff_grep_match_get_has_fuzzy_score`] first; `0` is + * ambiguous without that flag. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +uint16_t fff_grep_match_get_fuzzy_score(const struct FffGrepMatch *m); + +/** + * Returns `true` if this match carries a valid fuzzy score. + * Returns `false` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +bool fff_grep_match_get_has_fuzzy_score(const struct FffGrepMatch *m); + +/** + * Returns `true` if the match was identified as a symbol definition. + * Returns `false` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +bool fff_grep_match_get_is_definition(const struct FffGrepMatch *m); + +/** + * Returns `true` if the matched file was detected as binary. + * Returns `false` if `m` is null. + * + * ## Safety + * `m` must be a valid `FffGrepMatch` pointer or null. + */ +bool fff_grep_match_get_is_binary(const struct FffGrepMatch *m); + +/** + * Returns the number of items in the result. Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffSearchResult` pointer or null. + */ +uint32_t fff_search_result_get_count(const struct FffSearchResult *r); + +/** + * Returns the total number of files that matched before the result was + * truncated to the page size. Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffSearchResult` pointer or null. + */ +uint32_t fff_search_result_get_total_matched(const struct FffSearchResult *r); + +/** + * Returns the total number of indexed files considered during search. + * Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffSearchResult` pointer or null. + */ +uint32_t fff_search_result_get_total_files(const struct FffSearchResult *r); + +/** + * Returns the number of matches in the result. Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffGrepResult` pointer or null. + */ +uint32_t fff_grep_result_get_count(const struct FffGrepResult *r); + +/** + * Returns the total number of matches found across all pages. + * Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffGrepResult` pointer or null. + */ +uint32_t fff_grep_result_get_total_matched(const struct FffGrepResult *r); + +/** + * Returns the number of files actually opened and searched in this call. + * Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffGrepResult` pointer or null. + */ +uint32_t fff_grep_result_get_total_files_searched(const struct FffGrepResult *r); + +/** + * Returns the total number of indexed files before any filtering. + * Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffGrepResult` pointer or null. + */ +uint32_t fff_grep_result_get_total_files(const struct FffGrepResult *r); + +/** + * Returns the number of files eligible for search after path/type filtering. + * Returns `0` if `r` is null. + * + * ## Safety + * `r` must be a valid `FffGrepResult` pointer or null. + */ +uint32_t fff_grep_result_get_filtered_file_count(const struct FffGrepResult *r); + +/** + * Returns the file offset for the next page, or `0` if all files have been + * searched or `r` is null. Pass this value as `file_offset` to a subsequent + * `fff_live_grep` or `fff_multi_grep` call to continue pagination. + * + * ## Safety + * `r` must be a valid `FffGrepResult` pointer or null. + */ +uint32_t fff_grep_result_get_next_file_offset(const struct FffGrepResult *r); + +/** + * Returns the regex compilation error string if the engine fell back to + * literal matching, or null if there was no error or `r` is null. + * + * Do not free the returned pointer. + * + * ## Safety + * `r` must be a valid `FffGrepResult` pointer or null. + */ +const char *fff_grep_result_get_regex_fallback_error(const struct FffGrepResult *r); + +#endif /* FFF_C_H */ diff --git a/crates/fff-c/src/accessors.rs b/crates/fff-c/src/accessors.rs new file mode 100644 index 0000000..e7e5a59 --- /dev/null +++ b/crates/fff-c/src/accessors.rs @@ -0,0 +1,882 @@ +//! Stable accessor functions for `fff-c` FFI struct fields. +//! +//! # Why this exists +//! +//! `fff-c` exposes its result types as plain `#[repr(C)]` structs. External +//! consumers (Emacs Lisp via `emacs-ffi`, Python `ctypes`, etc.) that access +//! fields by hardcoding byte offsets break silently whenever the struct layout +//! changes — a new field shifts every subsequent offset with no compile-time +//! warning. +//! +//! These functions turn field access into a **stable named API**: callers bind +//! to a symbol name once and are fully insulated from layout changes. +//! +//! # Usage from Emacs Lisp (example) +//! +//! ```elisp +//! (define-ffi-function fff--grep-match-line-content +//! "fff_grep_match_get_line_content" :pointer [:pointer] fff--library) +//! +//! (ffi-get-c-string (fff--grep-match-line-content match-ptr)) +//! ``` +//! +//! # Array iteration +//! +//! To walk result arrays use `fff_search_result_get_item`, +//! `fff_grep_result_get_match`, and `fff_search_result_get_score` — these are +//! defined in the main `lib.rs` FFI surface alongside the search functions. + +use std::ffi::c_char; +use std::ptr; + +use crate::ffi_types::{FffFileItem, FffGrepMatch, FffGrepResult, FffMatchRange, FffSearchResult}; + +// ── FffFileItem ────────────────────────────────────────────────────────────── + +/// Returns the relative path of a file item (e.g. `"src/main.rs"`). +/// +/// Returns null if `item` is null. The returned pointer is valid for the +/// lifetime of the owning `FffSearchResult`; do not free it directly. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_relative_path( + item: *const FffFileItem, +) -> *const c_char { + if item.is_null() { + return ptr::null(); + } + unsafe { (*item).relative_path } +} + +/// Returns the file-name component of a file item (e.g. `"main.rs"`). +/// +/// Returns null if `item` is null. Do not free the returned pointer. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_file_name(item: *const FffFileItem) -> *const c_char { + if item.is_null() { + return ptr::null(); + } + unsafe { (*item).file_name } +} + +/// Returns the git status string for a file item (e.g. `"M "`, `"??"`) +/// or null if git is unavailable, the file is untracked, or `item` is null. +/// +/// Do not free the returned pointer. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_git_status(item: *const FffFileItem) -> *const c_char { + if item.is_null() { + return ptr::null(); + } + unsafe { (*item).git_status } +} + +/// Returns the file size in bytes. Returns `0` if `item` is null. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_size(item: *const FffFileItem) -> u64 { + if item.is_null() { + return 0; + } + unsafe { (*item).size } +} + +/// Returns the last-modified time as seconds since the UNIX epoch. +/// Returns `0` if `item` is null. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_modified(item: *const FffFileItem) -> u64 { + if item.is_null() { + return 0; + } + unsafe { (*item).modified } +} + +/// Returns the combined frecency score. Returns `0` if `item` is null. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_total_frecency_score(item: *const FffFileItem) -> i64 { + if item.is_null() { + return 0; + } + unsafe { (*item).total_frecency_score } +} + +/// Returns the access-based frecency score. Returns `0` if `item` is null. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_access_frecency_score(item: *const FffFileItem) -> i64 { + if item.is_null() { + return 0; + } + unsafe { (*item).access_frecency_score } +} + +/// Returns the modification-based frecency score. Returns `0` if `item` is null. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_modification_frecency_score( + item: *const FffFileItem, +) -> i64 { + if item.is_null() { + return 0; + } + unsafe { (*item).modification_frecency_score } +} + +/// Returns `true` if the file was detected as binary. Returns `false` if `item` is null. +/// +/// ## Safety +/// `item` must be a valid `FffFileItem` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_file_item_get_is_binary(item: *const FffFileItem) -> bool { + if item.is_null() { + return false; + } + unsafe { (*item).is_binary } +} + +// ── FffGrepMatch ───────────────────────────────────────────────────────────── + +/// Returns the relative path of the file containing this grep match. +/// +/// Returns null if `m` is null. Do not free the returned pointer. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_relative_path(m: *const FffGrepMatch) -> *const c_char { + if m.is_null() { + return ptr::null(); + } + unsafe { (*m).relative_path } +} + +/// Returns the file-name component of the file containing this grep match. +/// +/// Returns null if `m` is null. Do not free the returned pointer. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_file_name(m: *const FffGrepMatch) -> *const c_char { + if m.is_null() { + return ptr::null(); + } + unsafe { (*m).file_name } +} + +/// Returns the git status string for the matched file (e.g. `"M "`, `"??"`) +/// or null if git is unavailable, the file is untracked, or `m` is null. +/// +/// Do not free the returned pointer. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_git_status(m: *const FffGrepMatch) -> *const c_char { + if m.is_null() { + return ptr::null(); + } + unsafe { (*m).git_status } +} + +/// Returns the full text content of the matched line. +/// +/// Returns null if `m` is null. Do not free the returned pointer. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_line_content(m: *const FffGrepMatch) -> *const c_char { + if m.is_null() { + return ptr::null(); + } + unsafe { (*m).line_content } +} + +/// Returns the 1-based line number of the match within its file. +/// Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_line_number(m: *const FffGrepMatch) -> u64 { + if m.is_null() { + return 0; + } + unsafe { (*m).line_number } +} + +/// Returns the 0-based column of the match start within its line. +/// Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_col(m: *const FffGrepMatch) -> u32 { + if m.is_null() { + return 0; + } + unsafe { (*m).col } +} + +/// Returns the byte offset of the match start from the beginning of the file. +/// Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_byte_offset(m: *const FffGrepMatch) -> u64 { + if m.is_null() { + return 0; + } + unsafe { (*m).byte_offset } +} + +/// Returns the file size in bytes for the matched file. Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_size(m: *const FffGrepMatch) -> u64 { + if m.is_null() { + return 0; + } + unsafe { (*m).size } +} + +/// Returns the combined frecency score for the matched file. +/// Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_total_frecency_score(m: *const FffGrepMatch) -> i64 { + if m.is_null() { + return 0; + } + unsafe { (*m).total_frecency_score } +} + +/// Returns the access-based frecency score for the matched file. +/// Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_access_frecency_score(m: *const FffGrepMatch) -> i64 { + if m.is_null() { + return 0; + } + unsafe { (*m).access_frecency_score } +} + +/// Returns the modification-based frecency score for the matched file. +/// Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_modification_frecency_score( + m: *const FffGrepMatch, +) -> i64 { + if m.is_null() { + return 0; + } + unsafe { (*m).modification_frecency_score } +} + +/// Returns the last-modified time as seconds since the UNIX epoch for the matched file. +/// Returns `0` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_modified(m: *const FffGrepMatch) -> u64 { + if m.is_null() { + return 0; + } + unsafe { (*m).modified } +} + +/// Returns the number of highlight ranges in this match. Returns `0` if `m` is null. +/// +/// Use with [`fff_grep_match_get_match_range`] to iterate the highlight spans. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_match_ranges_count(m: *const FffGrepMatch) -> u32 { + if m.is_null() { + return 0; + } + unsafe { (*m).match_ranges_count } +} + +/// Returns a pointer to the `index`-th [`FffMatchRange`] highlight span. +/// +/// Returns null if `m` is null, `index >= match_ranges_count`, or the +/// ranges array is null. The returned pointer is valid until the owning +/// `FffGrepResult` is freed; do not free it directly. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_match_range( + m: *const FffGrepMatch, + index: u32, +) -> *const FffMatchRange { + if m.is_null() { + return ptr::null(); + } + let m = unsafe { &*m }; + if index >= m.match_ranges_count || m.match_ranges.is_null() { + return ptr::null(); + } + unsafe { m.match_ranges.add(index as usize) } +} + +/// Returns the number of context lines captured before the match. +/// Returns `0` if `m` is null. +/// +/// Use with [`fff_grep_match_get_context_before`] to read each line. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_context_before_count(m: *const FffGrepMatch) -> u32 { + if m.is_null() { + return 0; + } + unsafe { (*m).context_before_count } +} + +/// Returns the `index`-th context line before the match. +/// +/// Returns null if `m` is null, `index >= context_before_count`, or the +/// context array is null. Do not free the returned pointer. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_context_before( + m: *const FffGrepMatch, + index: u32, +) -> *const c_char { + if m.is_null() { + return ptr::null(); + } + let m = unsafe { &*m }; + if index >= m.context_before_count || m.context_before.is_null() { + return ptr::null(); + } + unsafe { *m.context_before.add(index as usize) } +} + +/// Returns the number of context lines captured after the match. +/// Returns `0` if `m` is null. +/// +/// Use with [`fff_grep_match_get_context_after`] to read each line. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_context_after_count(m: *const FffGrepMatch) -> u32 { + if m.is_null() { + return 0; + } + unsafe { (*m).context_after_count } +} + +/// Returns the `index`-th context line after the match. +/// +/// Returns null if `m` is null, `index >= context_after_count`, or the +/// context array is null. Do not free the returned pointer. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_context_after( + m: *const FffGrepMatch, + index: u32, +) -> *const c_char { + if m.is_null() { + return ptr::null(); + } + let m = unsafe { &*m }; + if index >= m.context_after_count || m.context_after.is_null() { + return ptr::null(); + } + unsafe { *m.context_after.add(index as usize) } +} + +/// Returns the fuzzy match score. Returns `0` if `m` is null or no fuzzy +/// score is present. +/// +/// Always check [`fff_grep_match_get_has_fuzzy_score`] first; `0` is +/// ambiguous without that flag. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_fuzzy_score(m: *const FffGrepMatch) -> u16 { + if m.is_null() { + return 0; + } + unsafe { (*m).fuzzy_score } +} + +/// Returns `true` if this match carries a valid fuzzy score. +/// Returns `false` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_has_fuzzy_score(m: *const FffGrepMatch) -> bool { + if m.is_null() { + return false; + } + unsafe { (*m).has_fuzzy_score } +} + +/// Returns `true` if the match was identified as a symbol definition. +/// Returns `false` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_is_definition(m: *const FffGrepMatch) -> bool { + if m.is_null() { + return false; + } + unsafe { (*m).is_definition } +} + +/// Returns `true` if the matched file was detected as binary. +/// Returns `false` if `m` is null. +/// +/// ## Safety +/// `m` must be a valid `FffGrepMatch` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_match_get_is_binary(m: *const FffGrepMatch) -> bool { + if m.is_null() { + return false; + } + unsafe { (*m).is_binary } +} + +// ── FffSearchResult ────────────────────────────────────────────────────────── + +/// Returns the number of items in the result. Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffSearchResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search_result_get_count(r: *const FffSearchResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).count } +} + +/// Returns the total number of files that matched before the result was +/// truncated to the page size. Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffSearchResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search_result_get_total_matched(r: *const FffSearchResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).total_matched } +} + +/// Returns the total number of indexed files considered during search. +/// Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffSearchResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search_result_get_total_files(r: *const FffSearchResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).total_files } +} + +// ── FffGrepResult ───────────────────────────────────────────────────────────── + +/// Returns the number of matches in the result. Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffGrepResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_count(r: *const FffGrepResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).count } +} + +/// Returns the total number of matches found across all pages. +/// Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffGrepResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_total_matched(r: *const FffGrepResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).total_matched } +} + +/// Returns the number of files actually opened and searched in this call. +/// Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffGrepResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_total_files_searched(r: *const FffGrepResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).total_files_searched } +} + +/// Returns the total number of indexed files before any filtering. +/// Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffGrepResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_total_files(r: *const FffGrepResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).total_files } +} + +/// Returns the number of files eligible for search after path/type filtering. +/// Returns `0` if `r` is null. +/// +/// ## Safety +/// `r` must be a valid `FffGrepResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_filtered_file_count(r: *const FffGrepResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).filtered_file_count } +} + +/// Returns the file offset for the next page, or `0` if all files have been +/// searched or `r` is null. Pass this value as `file_offset` to a subsequent +/// `fff_live_grep` or `fff_multi_grep` call to continue pagination. +/// +/// ## Safety +/// `r` must be a valid `FffGrepResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_next_file_offset(r: *const FffGrepResult) -> u32 { + if r.is_null() { + return 0; + } + unsafe { (*r).next_file_offset } +} + +/// Returns the regex compilation error string if the engine fell back to +/// literal matching, or null if there was no error or `r` is null. +/// +/// Do not free the returned pointer. +/// +/// ## Safety +/// `r` must be a valid `FffGrepResult` pointer or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_regex_fallback_error( + r: *const FffGrepResult, +) -> *const c_char { + if r.is_null() { + return ptr::null(); + } + unsafe { (*r).regex_fallback_error } +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + use std::ptr; + + // ── helpers ────────────────────────────────────────────────────────────── + + fn make_file_item(path: &str, name: &str) -> FffFileItem { + FffFileItem { + relative_path: CString::new(path).unwrap().into_raw(), + file_name: CString::new(name).unwrap().into_raw(), + git_status: ptr::null_mut(), + size: 1024, + modified: 1_700_000_000, + access_frecency_score: 10, + modification_frecency_score: 20, + total_frecency_score: 30, + is_binary: false, + } + } + + unsafe fn free_file_item(item: &mut FffFileItem) { + unsafe { + if !item.relative_path.is_null() { + drop(CString::from_raw(item.relative_path)); + } + if !item.file_name.is_null() { + drop(CString::from_raw(item.file_name)); + } + if !item.git_status.is_null() { + drop(CString::from_raw(item.git_status)); + } + } + } + + fn make_grep_match(path: &str, line: &str) -> FffGrepMatch { + FffGrepMatch { + relative_path: CString::new(path).unwrap().into_raw(), + file_name: CString::new("file.rs").unwrap().into_raw(), + git_status: ptr::null_mut(), + line_content: CString::new(line).unwrap().into_raw(), + match_ranges: ptr::null_mut(), + context_before: ptr::null_mut(), + context_after: ptr::null_mut(), + size: 512, + modified: 1_600_000_000, + total_frecency_score: 5, + access_frecency_score: 6, + modification_frecency_score: 7, + line_number: 42, + byte_offset: 100, + col: 8, + match_ranges_count: 0, + context_before_count: 0, + context_after_count: 0, + fuzzy_score: 0, + has_fuzzy_score: false, + is_binary: false, + is_definition: true, + } + } + + unsafe fn free_grep_match(m: &mut FffGrepMatch) { + unsafe { + if !m.relative_path.is_null() { + drop(CString::from_raw(m.relative_path)); + } + if !m.file_name.is_null() { + drop(CString::from_raw(m.file_name)); + } + if !m.line_content.is_null() { + drop(CString::from_raw(m.line_content)); + } + } + } + + fn make_search_result(count: u32, total: u32, files: u32) -> FffSearchResult { + FffSearchResult { + items: ptr::null_mut(), + scores: ptr::null_mut(), + count, + total_matched: total, + total_files: files, + location: crate::ffi_types::FffLocation { + tag: 0, + line: 0, + col: 0, + end_line: 0, + end_col: 0, + }, + } + } + + fn make_grep_result() -> FffGrepResult { + FffGrepResult { + items: ptr::null_mut(), + count: 3, + total_matched: 10, + total_files_searched: 50, + total_files: 200, + filtered_file_count: 80, + next_file_offset: 51, + regex_fallback_error: ptr::null_mut(), + } + } + + // ── null-guard tests: every function returns its zero-value on NULL ─────── + + #[test] + fn null_file_item_returns_null_or_zero() { + let null: *const FffFileItem = ptr::null(); + unsafe { + assert!(fff_file_item_get_relative_path(null).is_null()); + assert!(fff_file_item_get_file_name(null).is_null()); + assert!(fff_file_item_get_git_status(null).is_null()); + assert_eq!(fff_file_item_get_size(null), 0); + assert_eq!(fff_file_item_get_modified(null), 0); + assert_eq!(fff_file_item_get_access_frecency_score(null), 0); + assert_eq!(fff_file_item_get_modification_frecency_score(null), 0); + assert_eq!(fff_file_item_get_total_frecency_score(null), 0); + assert!(!fff_file_item_get_is_binary(null)); + } + } + + #[test] + fn null_grep_match_returns_null_or_zero() { + let null: *const FffGrepMatch = ptr::null(); + unsafe { + assert!(fff_grep_match_get_relative_path(null).is_null()); + assert!(fff_grep_match_get_file_name(null).is_null()); + assert!(fff_grep_match_get_git_status(null).is_null()); + assert!(fff_grep_match_get_line_content(null).is_null()); + assert_eq!(fff_grep_match_get_line_number(null), 0); + assert_eq!(fff_grep_match_get_byte_offset(null), 0); + assert_eq!(fff_grep_match_get_col(null), 0); + assert_eq!(fff_grep_match_get_size(null), 0); + assert_eq!(fff_grep_match_get_modified(null), 0); + assert_eq!(fff_grep_match_get_total_frecency_score(null), 0); + assert_eq!(fff_grep_match_get_access_frecency_score(null), 0); + assert_eq!(fff_grep_match_get_modification_frecency_score(null), 0); + assert_eq!(fff_grep_match_get_match_ranges_count(null), 0); + assert_eq!(fff_grep_match_get_context_before_count(null), 0); + assert_eq!(fff_grep_match_get_context_after_count(null), 0); + assert!(!fff_grep_match_get_has_fuzzy_score(null)); + assert_eq!(fff_grep_match_get_fuzzy_score(null), 0); + assert!(!fff_grep_match_get_is_binary(null)); + assert!(!fff_grep_match_get_is_definition(null)); + assert!(fff_grep_match_get_context_before(null, 0).is_null()); + assert!(fff_grep_match_get_context_after(null, 0).is_null()); + assert!(fff_grep_match_get_match_range(null, 0).is_null()); + } + } + + #[test] + fn null_search_result_returns_zero() { + let null: *const FffSearchResult = ptr::null(); + unsafe { + assert_eq!(fff_search_result_get_count(null), 0); + assert_eq!(fff_search_result_get_total_matched(null), 0); + assert_eq!(fff_search_result_get_total_files(null), 0); + } + } + + #[test] + fn null_grep_result_returns_zero_or_null() { + let null: *const FffGrepResult = ptr::null(); + unsafe { + assert_eq!(fff_grep_result_get_count(null), 0); + assert_eq!(fff_grep_result_get_total_matched(null), 0); + assert_eq!(fff_grep_result_get_total_files_searched(null), 0); + assert_eq!(fff_grep_result_get_total_files(null), 0); + assert_eq!(fff_grep_result_get_filtered_file_count(null), 0); + assert_eq!(fff_grep_result_get_next_file_offset(null), 0); + assert!(fff_grep_result_get_regex_fallback_error(null).is_null()); + } + } + + // ── data correctness tests ──────────────────────────────────────────────── + + #[test] + fn file_item_getters_return_correct_values() { + let mut item = make_file_item("src/main.rs", "main.rs"); + let p = &item as *const FffFileItem; + unsafe { + let path = std::ffi::CStr::from_ptr(fff_file_item_get_relative_path(p)); + assert_eq!(path.to_str().unwrap(), "src/main.rs"); + + let name = std::ffi::CStr::from_ptr(fff_file_item_get_file_name(p)); + assert_eq!(name.to_str().unwrap(), "main.rs"); + + assert!(fff_file_item_get_git_status(p).is_null()); + assert_eq!(fff_file_item_get_size(p), 1024); + assert_eq!(fff_file_item_get_modified(p), 1_700_000_000); + assert_eq!(fff_file_item_get_access_frecency_score(p), 10); + assert_eq!(fff_file_item_get_modification_frecency_score(p), 20); + assert_eq!(fff_file_item_get_total_frecency_score(p), 30); + assert!(!fff_file_item_get_is_binary(p)); + + free_file_item(&mut item); + } + } + + #[test] + fn grep_match_getters_return_correct_values() { + let mut m = make_grep_match("src/lib.rs", "fn hello()"); + let p = &m as *const FffGrepMatch; + unsafe { + let path = std::ffi::CStr::from_ptr(fff_grep_match_get_relative_path(p)); + assert_eq!(path.to_str().unwrap(), "src/lib.rs"); + + let line = std::ffi::CStr::from_ptr(fff_grep_match_get_line_content(p)); + assert_eq!(line.to_str().unwrap(), "fn hello()"); + + assert_eq!(fff_grep_match_get_line_number(p), 42); + assert_eq!(fff_grep_match_get_byte_offset(p), 100); + assert_eq!(fff_grep_match_get_col(p), 8); + assert_eq!(fff_grep_match_get_size(p), 512); + assert_eq!(fff_grep_match_get_modified(p), 1_600_000_000); + assert_eq!(fff_grep_match_get_total_frecency_score(p), 5); + assert_eq!(fff_grep_match_get_access_frecency_score(p), 6); + assert_eq!(fff_grep_match_get_modification_frecency_score(p), 7); + assert_eq!(fff_grep_match_get_match_ranges_count(p), 0); + assert!(!fff_grep_match_get_has_fuzzy_score(p)); + assert!(!fff_grep_match_get_is_binary(p)); + assert!(fff_grep_match_get_is_definition(p)); + + free_grep_match(&mut m); + } + } + + #[test] + fn search_result_getters_return_correct_values() { + let r = make_search_result(5, 20, 100); + let p = &r as *const FffSearchResult; + unsafe { + assert_eq!(fff_search_result_get_count(p), 5); + assert_eq!(fff_search_result_get_total_matched(p), 20); + assert_eq!(fff_search_result_get_total_files(p), 100); + } + } + + #[test] + fn grep_result_getters_return_correct_values() { + let r = make_grep_result(); + let p = &r as *const FffGrepResult; + unsafe { + assert_eq!(fff_grep_result_get_count(p), 3); + assert_eq!(fff_grep_result_get_total_matched(p), 10); + assert_eq!(fff_grep_result_get_total_files_searched(p), 50); + assert_eq!(fff_grep_result_get_total_files(p), 200); + assert_eq!(fff_grep_result_get_filtered_file_count(p), 80); + assert_eq!(fff_grep_result_get_next_file_offset(p), 51); + assert!(fff_grep_result_get_regex_fallback_error(p).is_null()); + } + } +} diff --git a/crates/fff-c/src/ffi_types.rs b/crates/fff-c/src/ffi_types.rs new file mode 100644 index 0000000..6421de7 --- /dev/null +++ b/crates/fff-c/src/ffi_types.rs @@ -0,0 +1,831 @@ +//! FFI-compatible type definitions +//! +//! All result types use `#[repr(C)]` structs for direct memory access from any +//! language with C FFI support. No JSON serialization is used for search or grep +//! results — callers read struct fields directly. + +use std::ffi::{CString, c_char, c_void}; +use std::ptr; + +use fff::file_picker::FilePicker; +use fff::git::format_git_status; +use fff::{ + DirItem, DirSearchResult, FileItem, GrepMatch, GrepResult, Location, MixedItemRef, + MixedSearchResult, Score, SearchResult, +}; + +/// Current used version of [`FffCreateOptions`]. +pub const FFF_CREATE_OPTIONS_VERSION: u32 = 2; + +/// Options for `fff_create_instance_with`. +/// +/// Versioned struct: you populate the struct at your call level, we guarantee that +/// the version is stable across the version changes, new fields only appended! +#[repr(C)] +pub struct FffCreateOptions { + /// Set to [`FFF_CREATE_OPTIONS_VERSION`] when allocating. Used by the + /// library to determine which trailing fields are populated. + pub version: u32, + /// Directory to index (required, non-NULL). + pub base_path: *const c_char, + /// Frecency LMDB database path. NULL/empty to skip frecency tracking. + pub frecency_db_path: *const c_char, + /// Query history LMDB database path. NULL/empty to skip query tracking. + pub history_db_path: *const c_char, + /// Pre-populate mmap caches for top-frecency files after the initial scan. + pub enable_mmap_cache: bool, + /// Build content index after the initial scan for faster grep. + pub enable_content_indexing: bool, + /// Start a background file-system watcher for live updates. + pub watch: bool, + /// Enable AI-agent optimizations. + pub ai_mode: bool, + /// Tracing log file path. NULL/empty to skip log init. + pub log_file_path: *const c_char, + /// Log level: `"trace" | "debug" | "info" | "warn" | "error"`. + /// NULL/empty defaults to `"info"`. Ignored when `log_file_path` is unset. + pub log_level: *const c_char, + /// Content cache file-count cap. 0 = auto. + pub cache_budget_max_files: u64, + /// Content cache byte cap. 0 = auto. + pub cache_budget_max_bytes: u64, + /// Per-file byte cap inside the content cache. 0 = auto. + pub cache_budget_max_file_size: u64, + /// Allow indexing the filesystem root (`/`). Off by default — root is + /// rarely the intended target and floods the watcher with churn. + pub enable_fs_root_scanning: bool, + /// Allow indexing the user's home directory. Same trade-off as + /// `enable_fs_root_scanning`. + pub enable_home_dir_scanning: bool, + // ----- v2 fields ----- + /// Follow symlinks during scan and watcher walks. Off by default — + /// enabling this without external loop protection can wedge the watcher + /// on cyclic symlink graphs. Caller is responsible for the trade-off. + pub follow_symlinks: bool, + // ----- new version 3+ fields go here, ALWAYS appended ----- +} + +impl FffCreateOptions { + /// Default values for a v1 options struct. + pub fn defaults() -> Self { + Self { + version: FFF_CREATE_OPTIONS_VERSION, + base_path: ptr::null(), + frecency_db_path: ptr::null(), + history_db_path: ptr::null(), + enable_mmap_cache: true, + enable_content_indexing: true, + watch: true, + ai_mode: false, + log_file_path: ptr::null(), + log_level: ptr::null(), + cache_budget_max_files: 0, + cache_budget_max_bytes: 0, + cache_budget_max_file_size: 0, + enable_fs_root_scanning: false, + enable_home_dir_scanning: false, + follow_symlinks: false, + } + } +} + +/// Allocate a heap CString from a `&str`, returning a raw pointer. +fn cstring_new(s: &str) -> *mut c_char { + CString::new(s).unwrap_or_default().into_raw() +} + +/// Convert a `Vec` into a raw pointer + count, leaking the memory. +fn vec_to_raw(v: Vec) -> (*mut T, u32) { + if v.is_empty() { + return (ptr::null_mut(), 0); + } + let count = v.len() as u32; + let mut boxed = v.into_boxed_slice(); + let p = boxed.as_mut_ptr(); + std::mem::forget(boxed); + (p, count) +} + +/// Convert a `&[String]` into a heap-allocated array of C strings. +fn strings_to_raw(v: &[String]) -> (*mut *mut c_char, u32) { + if v.is_empty() { + return (ptr::null_mut(), 0); + } + let ptrs: Vec<*mut c_char> = v.iter().map(|s| cstring_new(s)).collect(); + vec_to_raw(ptrs) +} + +/// Free a heap-allocated array of C strings. +/// +/// ## Safety +/// `arr` must have been produced by `strings_to_raw`. +unsafe fn free_cstring_array(arr: *mut *mut c_char, count: u32) { + if arr.is_null() { + return; + } + unsafe { + let ptrs = Vec::from_raw_parts(arr, count as usize, count as usize); + for p in ptrs { + if !p.is_null() { + drop(CString::from_raw(p)); + } + } + } +} + +/// A file item returned by `fff_search`. +/// +/// All string fields are heap-allocated and owned by the parent `FffSearchResult`. +/// Free the entire result with `fff_free_search_result`. +#[repr(C)] +pub struct FffFileItem { + pub relative_path: *mut c_char, + pub file_name: *mut c_char, + pub git_status: *mut c_char, + pub size: u64, + pub modified: u64, + pub access_frecency_score: i64, + pub modification_frecency_score: i64, + pub total_frecency_score: i64, + pub is_binary: bool, +} + +impl FffFileItem { + pub fn from_item(item: &FileItem, picker: &FilePicker) -> Self { + FffFileItem { + relative_path: cstring_new(&item.relative_path(picker)), + file_name: cstring_new(&item.file_name(picker)), + git_status: cstring_new(format_git_status(item.git_status)), + size: item.size, + modified: item.modified, + access_frecency_score: item.access_frecency_score as i64, + modification_frecency_score: item.modification_frecency_score as i64, + total_frecency_score: item.total_frecency_score() as i64, + is_binary: item.is_binary(), + } + } +} + +impl FffFileItem { + /// ## Safety + /// All string pointers must have been allocated by `CString::into_raw`. + pub unsafe fn free_strings(&mut self) { + unsafe { + if !self.relative_path.is_null() { + drop(CString::from_raw(self.relative_path)); + } + if !self.file_name.is_null() { + drop(CString::from_raw(self.file_name)); + } + if !self.git_status.is_null() { + drop(CString::from_raw(self.git_status)); + } + } + } +} + +/// Score breakdown for a search result. +#[repr(C)] +pub struct FffScore { + pub total: i32, + pub base_score: i32, + pub filename_bonus: i32, + pub special_filename_bonus: i32, + pub frecency_boost: i32, + pub distance_penalty: i32, + pub current_file_penalty: i32, + pub combo_match_boost: i32, + pub path_alignment_bonus: i32, + pub exact_match: bool, + pub match_type: *mut c_char, +} + +impl From<&Score> for FffScore { + fn from(score: &Score) -> Self { + FffScore { + total: score.total, + base_score: score.base_score, + filename_bonus: score.filename_bonus, + special_filename_bonus: score.special_filename_bonus, + frecency_boost: score.frecency_boost, + distance_penalty: score.distance_penalty, + current_file_penalty: score.current_file_penalty, + combo_match_boost: score.combo_match_boost, + path_alignment_bonus: score.path_alignment_bonus, + exact_match: score.exact_match, + match_type: cstring_new(score.match_type), + } + } +} + +impl FffScore { + /// ## Safety + /// `match_type` must have been allocated by `CString::into_raw`. + pub unsafe fn free_strings(&mut self) { + unsafe { + if !self.match_type.is_null() { + drop(CString::from_raw(self.match_type)); + } + } + } +} + +/// Location parsed from a query string (e.g. `"file.ts:42:10"`). +/// +/// `tag` encodes the variant: +/// 0 = no location, +/// 1 = line only (`line` is set), +/// 2 = position (`line` + `col`), +/// 3 = range (`line`/`col` = start, `end_line`/`end_col` = end). +#[repr(C)] +pub struct FffLocation { + pub tag: u8, + pub line: i32, + pub col: i32, + pub end_line: i32, + pub end_col: i32, +} + +impl From> for FffLocation { + fn from(loc: Option<&Location>) -> Self { + match loc { + None => FffLocation { + tag: 0, + line: 0, + col: 0, + end_line: 0, + end_col: 0, + }, + Some(Location::Line(line)) => FffLocation { + tag: 1, + line: *line, + col: 0, + end_line: 0, + end_col: 0, + }, + Some(Location::Position { line, col }) => FffLocation { + tag: 2, + line: *line, + col: *col, + end_line: 0, + end_col: 0, + }, + Some(Location::Range { start, end }) => FffLocation { + tag: 3, + line: start.0, + col: start.1, + end_line: end.0, + end_col: end.1, + }, + } + } +} + +/// Search result returned by `fff_search`. +/// +/// The caller must free this with `fff_free_search_result`. +#[repr(C)] +pub struct FffSearchResult { + /// Pointer to a heap-allocated array of `FffFileItem` (length = `count`). + pub items: *mut FffFileItem, + /// Pointer to a heap-allocated array of `FffScore` (length = `count`). + pub scores: *mut FffScore, + /// Number of items/scores in the arrays. + pub count: u32, + /// Total number of files that matched the query. + pub total_matched: u32, + /// Total number of indexed files. + pub total_files: u32, + /// Location parsed from the query string. + pub location: FffLocation, +} + +impl FffSearchResult { + /// Convert a core `SearchResult` into a heap-allocated `FffSearchResult`. + pub fn from_core(result: &SearchResult, picker: &FilePicker) -> *mut Self { + let items: Vec = result + .items + .iter() + .map(|i| FffFileItem::from_item(i, picker)) + .collect(); + let scores: Vec = result.scores.iter().map(FffScore::from).collect(); + let count = items.len() as u32; + + let (items_ptr, _) = vec_to_raw(items); + let (scores_ptr, _) = vec_to_raw(scores); + + Box::into_raw(Box::new(FffSearchResult { + items: items_ptr, + scores: scores_ptr, + count, + total_matched: result.total_matched as u32, + total_files: result.total_files as u32, + location: FffLocation::from(result.location.as_ref()), + })) + } +} + +// --------------------------------------------------------------------------- +// Grep result types +// --------------------------------------------------------------------------- + +/// A byte range within a matched line, used for highlighting. +#[repr(C)] +pub struct FffMatchRange { + pub start: u32, + pub end: u32, +} + +/// A single grep match with file and line information. +/// +/// All string fields and arrays are heap-allocated. Free the parent +/// `FffGrepResult` with `fff_free_grep_result` to release everything. +#[repr(C)] +pub struct FffGrepMatch { + // -- pointers (8 bytes each) -- + pub relative_path: *mut c_char, + pub file_name: *mut c_char, + pub git_status: *mut c_char, + pub line_content: *mut c_char, + pub match_ranges: *mut FffMatchRange, + pub context_before: *mut *mut c_char, + pub context_after: *mut *mut c_char, + // -- 8-byte numeric fields -- + pub size: u64, + pub modified: u64, + pub total_frecency_score: i64, + pub access_frecency_score: i64, + pub modification_frecency_score: i64, + pub line_number: u64, + pub byte_offset: u64, + // -- 4-byte fields -- + pub col: u32, + pub match_ranges_count: u32, + pub context_before_count: u32, + pub context_after_count: u32, + // -- 2-byte fields -- + pub fuzzy_score: u16, + // -- 1-byte fields -- + pub has_fuzzy_score: bool, + pub is_binary: bool, + pub is_definition: bool, +} + +impl FffGrepMatch { + fn from_core_with_file(m: &GrepMatch, file: &FileItem, picker: &FilePicker) -> Self { + let ranges: Vec = m + .match_byte_offsets + .iter() + .map(|&(start, end)| FffMatchRange { start, end }) + .collect(); + let (match_ranges, match_ranges_count) = vec_to_raw(ranges); + let (context_before, context_before_count) = strings_to_raw(&m.context_before); + let (context_after, context_after_count) = strings_to_raw(&m.context_after); + let (has_fuzzy_score, fuzzy_score) = match m.fuzzy_score { + Some(s) => (true, s), + None => (false, 0), + }; + + FffGrepMatch { + relative_path: cstring_new(&file.relative_path(picker)), + file_name: cstring_new(&file.file_name(picker)), + git_status: cstring_new(format_git_status(file.git_status)), + line_content: cstring_new(&m.line_content), + match_ranges, + context_before, + context_after, + size: file.size, + modified: file.modified, + total_frecency_score: file.total_frecency_score() as i64, + access_frecency_score: file.access_frecency_score as i64, + modification_frecency_score: file.modification_frecency_score as i64, + line_number: m.line_number, + byte_offset: m.byte_offset, + col: m.col as u32, + match_ranges_count, + context_before_count, + context_after_count, + fuzzy_score, + has_fuzzy_score, + is_binary: file.is_binary(), + is_definition: m.is_definition, + } + } + + /// ## Safety + /// All pointers must have been allocated by the corresponding `from_core`. + pub unsafe fn free_fields(&mut self) { + unsafe { + if !self.relative_path.is_null() { + drop(CString::from_raw(self.relative_path)); + } + if !self.file_name.is_null() { + drop(CString::from_raw(self.file_name)); + } + if !self.git_status.is_null() { + drop(CString::from_raw(self.git_status)); + } + if !self.line_content.is_null() { + drop(CString::from_raw(self.line_content)); + } + if !self.match_ranges.is_null() { + drop(Vec::from_raw_parts( + self.match_ranges, + self.match_ranges_count as usize, + self.match_ranges_count as usize, + )); + } + free_cstring_array(self.context_before, self.context_before_count); + free_cstring_array(self.context_after, self.context_after_count); + } + } +} + +/// Grep result returned by `fff_live_grep` and `fff_multi_grep`. +/// +/// The caller must free this with `fff_free_grep_result`. +#[repr(C)] +pub struct FffGrepResult { + /// Pointer to a heap-allocated array of `FffGrepMatch` (length = `count`). + pub items: *mut FffGrepMatch, + /// Number of matches in the `items` array. + pub count: u32, + /// Total number of matches (always equal to `count`). + pub total_matched: u32, + /// Number of files actually opened and searched in this call. + pub total_files_searched: u32, + /// Total number of indexed files (before any filtering). + pub total_files: u32, + /// Number of files eligible for search after filtering. + pub filtered_file_count: u32, + /// File offset for the next page. 0 if all files have been searched. + pub next_file_offset: u32, + /// Regex compilation error when falling back to literal matching. Null if none. + pub regex_fallback_error: *mut c_char, +} + +impl FffGrepResult { + /// Convert a core `GrepResult` into a heap-allocated `FffGrepResult`. + pub fn from_core(result: &GrepResult, picker: &FilePicker) -> *mut Self { + let items: Vec = result + .matches + .iter() + .map(|m| { + let file = result.files[m.file_index]; + FffGrepMatch::from_core_with_file(m, file, picker) + }) + .collect(); + let (items_ptr, count) = vec_to_raw(items); + + Box::into_raw(Box::new(FffGrepResult { + items: items_ptr, + count, + total_matched: result.matches.len() as u32, + total_files_searched: result.total_files_searched as u32, + total_files: result.total_files as u32, + filtered_file_count: result.filtered_file_count as u32, + next_file_offset: result.next_file_offset as u32, + regex_fallback_error: match &result.regex_fallback_error { + Some(e) => cstring_new(e), + None => ptr::null_mut(), + }, + })) + } +} + +/// Result envelope returned by all `fff_*` functions. +/// +/// Heap-allocated. The caller must free it with `fff_free_result`. Calling `fff_free_result` +/// **does not** deallocate the underlying `handle` pointer. It needs to be cleaned separately. +/// see (`fff_destroy`, `fff_free_search_result`, `fff_free_grep_result`, `fff_free_string`, etc.). +/// +/// Depending on the function, the payload is delivered through different fields: +/// +/// | Function | Payload field | Type | +/// |----------------------------|---------------|-------------------------------| +/// | `fff_create_instance` | `handle` | opaque instance pointer | +/// | `fff_search` | `handle` | `*mut FffSearchResult` | +/// | `fff_live_grep` | `handle` | `*mut FffGrepResult` | +/// | `fff_multi_grep` | `handle` | `*mut FffGrepResult` | +/// | `fff_get_scan_progress` | `handle` | `*mut FffScanProgress` | +/// | `fff_health_check` | `handle` | `*mut c_char` (JSON string) | +/// | `fff_get_historical_query` | `handle` | `*mut c_char` (string or null)| +/// | `fff_wait_for_scan` | `int_value` | 1 = completed, 0 = timed out | +/// | `fff_track_query` | `int_value` | 1 = success, 0 = failure | +/// | `fff_refresh_git_status` | `int_value` | number of files updated | +/// | `fff_scan_files` | (none) | success flag only | +/// | `fff_restart_index` | (none) | success flag only | +/// +/// On failure, `success` is false and `error` contains the message. +#[repr(C)] +pub struct FffResult { + /// Whether the operation succeeded. + pub success: bool, + /// Error message on failure. Null on success. + pub error: *mut c_char, + /// Opaque pointer payload. May be null. + pub handle: *mut c_void, + /// Integer payload for simple return values (bool as 0/1, counts, etc.). + pub int_value: i64, +} + +impl FffResult { + /// Create a successful result with no payload, returned as heap pointer. + pub fn ok_empty() -> *mut Self { + Box::into_raw(Box::new(FffResult { + success: true, + error: ptr::null_mut(), + handle: ptr::null_mut(), + int_value: 0, + })) + } + + /// Create a successful result with an integer value. + pub fn ok_int(value: i64) -> *mut Self { + Box::into_raw(Box::new(FffResult { + success: true, + error: ptr::null_mut(), + handle: ptr::null_mut(), + int_value: value, + })) + } + + /// Create a successful result carrying an opaque pointer (handle, typed struct, or string). + pub fn ok_handle(handle: *mut c_void) -> *mut Self { + Box::into_raw(Box::new(FffResult { + success: true, + error: ptr::null_mut(), + handle, + int_value: 0, + })) + } + + /// Create a successful result carrying a C string in the `handle` field. + /// The caller must free it with `fff_free_string`. + pub fn ok_string(s: &str) -> *mut Self { + let cstr = CString::new(s).unwrap_or_default().into_raw(); + Box::into_raw(Box::new(FffResult { + success: true, + error: ptr::null_mut(), + handle: cstr as *mut c_void, + int_value: 0, + })) + } + + /// Create an error result, returned as heap pointer. + pub fn err(error: &str) -> *mut Self { + Box::into_raw(Box::new(FffResult { + success: false, + error: CString::new(error).unwrap_or_default().into_raw(), + handle: ptr::null_mut(), + int_value: 0, + })) + } +} + +/// A directory item returned by `fff_search_directories`. +/// +/// All string fields are heap-allocated and owned by the parent `FffDirSearchResult`. +/// Free the entire result with `fff_free_dir_search_result`. +#[repr(C)] +pub struct FffDirItem { + pub relative_path: *mut c_char, + pub dir_name: *mut c_char, + pub max_access_frecency: i32, +} + +impl FffDirItem { + pub fn from_item(item: &DirItem, picker: &FilePicker) -> Self { + FffDirItem { + relative_path: cstring_new(&item.relative_path(picker)), + dir_name: cstring_new(&item.dir_name(picker)), + max_access_frecency: item.max_access_frecency(), + } + } + + /// ## Safety + /// All string pointers must have been allocated by the rust side + pub unsafe fn free_strings(&mut self) { + unsafe { + if !self.relative_path.is_null() { + drop(CString::from_raw(self.relative_path)); + } + if !self.dir_name.is_null() { + drop(CString::from_raw(self.dir_name)); + } + } + } +} + +/// Directory search result returned by `fff_search_directories`. +/// +/// The caller must free this with `fff_free_dir_search_result`. +#[repr(C)] +pub struct FffDirSearchResult { + /// Pointer to a heap-allocated array of `FffDirItem` (length = `count`). + pub items: *mut FffDirItem, + /// Pointer to a heap-allocated array of `FffScore` (length = `count`). + pub scores: *mut FffScore, + /// Number of items/scores in the arrays. + pub count: u32, + /// Total number of directories that matched the query. + pub total_matched: u32, + /// Total number of indexed directories. + pub total_dirs: u32, +} + +impl FffDirSearchResult { + /// Convert a core `DirSearchResult` into a heap-allocated `FffDirSearchResult`. + pub fn from_core(result: &DirSearchResult, picker: &FilePicker) -> *mut Self { + let items: Vec = result + .items + .iter() + .map(|i| FffDirItem::from_item(i, picker)) + .collect(); + let scores: Vec = result.scores.iter().map(FffScore::from).collect(); + let count = items.len() as u32; + + let (items_ptr, _) = vec_to_raw(items); + let (scores_ptr, _) = vec_to_raw(scores); + + Box::into_raw(Box::new(FffDirSearchResult { + items: items_ptr, + scores: scores_ptr, + count, + total_matched: result.total_matched as u32, + total_dirs: result.total_dirs as u32, + })) + } +} + +/// A single item in a mixed (files + directories) search result. +/// +/// `item_type`: 0 = file, 1 = directory. +/// All string fields are heap-allocated and owned by the parent `FffMixedSearchResult`. +#[repr(C)] +pub struct FffMixedItem { + /// 0 = file, 1 = directory. + pub item_type: u8, + pub relative_path: *mut c_char, + /// Filename for files, last directory segment for directories. + pub display_name: *mut c_char, + pub git_status: *mut c_char, + pub size: u64, + pub modified: u64, + /// The access frecency score for files, or max access frecency among all the immediate + /// children for directories. + pub access_frecency_score: i64, + /// Always 0 for directories + pub modification_frecency_score: i64, + /// Always 0 for directories + pub total_frecency_score: i64, + /// Always 0 for directories + pub is_binary: bool, +} + +impl FffMixedItem { + pub fn from_mixed_ref(item: &MixedItemRef<'_>, picker: &FilePicker) -> Self { + match item { + MixedItemRef::File(file) => FffMixedItem { + item_type: 0, + relative_path: cstring_new(&file.relative_path(picker)), + display_name: cstring_new(&file.file_name(picker)), + git_status: cstring_new(format_git_status(file.git_status)), + size: file.size, + modified: file.modified, + access_frecency_score: file.access_frecency_score as i64, + modification_frecency_score: file.modification_frecency_score as i64, + total_frecency_score: file.total_frecency_score() as i64, + is_binary: file.is_binary(), + }, + MixedItemRef::Dir(dir) => FffMixedItem { + item_type: 1, + relative_path: cstring_new(&dir.relative_path(picker)), + display_name: cstring_new(&dir.dir_name(picker)), + git_status: cstring_new(""), + size: 0, + modified: 0, + access_frecency_score: dir.max_access_frecency() as i64, + modification_frecency_score: 0, + total_frecency_score: dir.max_access_frecency() as i64, + is_binary: false, + }, + } + } + + /// ## Safety + /// All string pointers must have been allocated by rust side + pub unsafe fn free_strings(&mut self) { + unsafe { + if !self.relative_path.is_null() { + drop(CString::from_raw(self.relative_path)); + } + if !self.display_name.is_null() { + drop(CString::from_raw(self.display_name)); + } + if !self.git_status.is_null() { + drop(CString::from_raw(self.git_status)); + } + } + } +} + +/// Mixed search result returned by `fff_search_mixed`. +/// +/// The caller must free this with `fff_free_mixed_search_result`. +#[repr(C)] +pub struct FffMixedSearchResult { + /// Pointer to a heap-allocated array of `FffMixedItem` (length = `count`). + pub items: *mut FffMixedItem, + /// Pointer to a heap-allocated array of `FffScore` (length = `count`). + pub scores: *mut FffScore, + /// Number of items/scores in the arrays. + pub count: u32, + /// Total number of items (files + dirs) that matched the query. + pub total_matched: u32, + /// Total number of indexed files. + pub total_files: u32, + /// Total number of indexed directories. + pub total_dirs: u32, + /// Location parsed from the query string. + pub location: FffLocation, +} + +impl FffMixedSearchResult { + /// Convert a core `MixedSearchResult` into a heap-allocated `FffMixedSearchResult`. + pub fn from_core(result: &MixedSearchResult, picker: &FilePicker) -> *mut Self { + let items: Vec = result + .items + .iter() + .map(|i| FffMixedItem::from_mixed_ref(i, picker)) + .collect(); + let scores: Vec = result.scores.iter().map(FffScore::from).collect(); + let count = items.len() as u32; + + let (items_ptr, _) = vec_to_raw(items); + let (scores_ptr, _) = vec_to_raw(scores); + + Box::into_raw(Box::new(FffMixedSearchResult { + items: items_ptr, + scores: scores_ptr, + count, + total_matched: result.total_matched as u32, + total_files: result.total_files as u32, + total_dirs: result.total_dirs as u32, + location: FffLocation::from(result.location.as_ref()), + })) + } +} + +/// Scan progress returned by `fff_get_scan_progress`. +/// The caller must free this with `fff_free_scan_progress`. +#[repr(C)] +pub struct FffScanProgress { + pub scanned_files_count: u64, + pub is_scanning: bool, + pub is_watcher_ready: bool, + pub is_warmup_complete: bool, +} + +impl From for FffScanProgress { + fn from(p: fff::file_picker::ScanProgress) -> Self { + Self { + scanned_files_count: p.scanned_files_count as u64, + is_scanning: p.is_scanning, + is_watcher_ready: p.is_watcher_ready, + is_warmup_complete: p.is_warmup_complete, + } + } +} + +#[cfg(test)] +mod options_layout_tests { + use super::FffCreateOptions; + use std::mem::{align_of, offset_of, size_of}; + + // THIS TEST HAVE TO BE NEVER UPDATED ONLY ADDED NEW FIELDS + // this is needed to ensure ABI backward compatibility + #[test] + #[cfg(target_pointer_width = "64")] + fn fff_create_options_layout_is_stable_64bit() { + assert_eq!(size_of::(), 88); + assert_eq!(align_of::(), 8); + + assert_eq!(offset_of!(FffCreateOptions, version), 0); + assert_eq!(offset_of!(FffCreateOptions, base_path), 8); + assert_eq!(offset_of!(FffCreateOptions, frecency_db_path), 16); + assert_eq!(offset_of!(FffCreateOptions, history_db_path), 24); + assert_eq!(offset_of!(FffCreateOptions, enable_mmap_cache), 32); + assert_eq!(offset_of!(FffCreateOptions, enable_content_indexing), 33); + assert_eq!(offset_of!(FffCreateOptions, watch), 34); + assert_eq!(offset_of!(FffCreateOptions, ai_mode), 35); + assert_eq!(offset_of!(FffCreateOptions, log_file_path), 40); + assert_eq!(offset_of!(FffCreateOptions, log_level), 48); + assert_eq!(offset_of!(FffCreateOptions, cache_budget_max_files), 56); + assert_eq!(offset_of!(FffCreateOptions, cache_budget_max_bytes), 64); + assert_eq!(offset_of!(FffCreateOptions, cache_budget_max_file_size), 72); + assert_eq!(offset_of!(FffCreateOptions, enable_fs_root_scanning), 80); + assert_eq!(offset_of!(FffCreateOptions, enable_home_dir_scanning), 81); + assert_eq!(offset_of!(FffCreateOptions, follow_symlinks), 82); + } +} diff --git a/crates/fff-c/src/lib.rs b/crates/fff-c/src/lib.rs new file mode 100644 index 0000000..56283d7 --- /dev/null +++ b/crates/fff-c/src/lib.rs @@ -0,0 +1,1699 @@ +//! C FFI bindings for fff-core +//! +//! This crate provides C-compatible FFI exports that can be used from any language +//! with C FFI support (Bun, Node.js, Python, Ruby, etc.). +//! +//! # Instance-based API +//! +//! All state is owned by an opaque `FffInstance` fff_handle. Callers create an instance +//! with `fff_create_instance`, pass the fff_handle to every subsequent call, and free it with +//! `fff_destroy`. Multiple independent instances can coexist in the same process. +//! +//! # Memory management +//! +//! * Every `fff_*` function that returns `*mut FffResult` requires the caller to +//! free the result with `fff_free_result`. +//! * The instance itself must be freed with `fff_destroy`. +//! +//! # Parameter conventions +//! +//! * Optional `*const c_char` parameters: pass NULL or an empty string to omit. +//! * Numeric parameters: 0 means "use default" unless documented otherwise. +//! * Grep mode (`u8`): 0 = plain text, 1 = regex, 2 = fuzzy. +//! * Multi-grep patterns are passed as a single newline-separated (`\n`) string. + +use std::ffi::{CStr, CString, c_char, c_void}; +use std::path::PathBuf; +use std::time::Duration; + +use fff::shared::SharedQueryTracker; + +mod accessors; +mod ffi_types; + +use fff::file_picker::FilePicker; +use fff::frecency::FrecencyTracker; +use fff::query_tracker::QueryTracker; +use fff::{DbHealthChecker, FFFMode, FuzzySearchOptions, PaginationArgs, QueryParser}; +use fff::{SharedFilePicker, SharedFrecency}; +use ffi_types::{ + FFF_CREATE_OPTIONS_VERSION, FffCreateOptions, FffDirItem, FffDirSearchResult, FffFileItem, + FffGrepMatch, FffGrepResult, FffMixedItem, FffMixedSearchResult, FffResult, FffScanProgress, + FffScore, FffSearchResult, +}; + +/// Opaque fff_handle holding all per-instance state. +/// +/// The caller receives this as `*mut c_void` and must pass it to every FFI call. +/// The fff_handle is freed by `fff_destroy`. +struct FffInstance { + picker: SharedFilePicker, + frecency: SharedFrecency, + query_tracker: SharedQueryTracker, +} + +/// Helper to convert C string to Rust &str. +/// +/// Returns `None` if the pointer is null or the string is not valid UTF-8. +unsafe fn cstr_to_str<'a>(s: *const c_char) -> Option<&'a str> { + if s.is_null() { + None + } else { + unsafe { CStr::from_ptr(s).to_str().ok() } + } +} + +/// Helper to convert an optional C string parameter. +/// +/// Returns `None` if the pointer is null, empty, or not valid UTF-8. +unsafe fn optional_cstr<'a>(s: *const c_char) -> Option<&'a str> { + unsafe { cstr_to_str(s) }.filter(|s| !s.is_empty()) +} + +/// Recover a `&FffInstance` from the opaque pointer. +/// +/// Returns an error `FffResult` if the pointer is null. +unsafe fn instance_ref<'a>(fff_handle: *mut c_void) -> Result<&'a FffInstance, *mut FffResult> { + if fff_handle.is_null() { + Err(FffResult::err( + "Instance handle is null. Create one with fff_create_instance first.", + )) + } else { + Ok(unsafe { &*(fff_handle as *const FffInstance) }) + } +} + +/// Decode a `u8` grep mode into the core enum. +fn grep_mode_from_u8(mode: u8) -> fff::GrepMode { + match mode { + 1 => fff::GrepMode::Regex, + 2 => fff::GrepMode::Fuzzy, + _ => fff::GrepMode::PlainText, + } +} + +/// Apply "0 means default" convention. +fn default_u32(val: u32, default: u32) -> u32 { + if val == 0 { default } else { val } +} + +fn default_u64(val: u64, default: u64) -> u64 { + if val == 0 { default } else { val } +} + +fn default_i32(val: i32, default: i32) -> i32 { + if val == 0 { default } else { val } +} + +/// Create a new file finder instance (legacy 8-arg positional signature). +/// +/// @deprecated Use [`fff_create_instance_with`] (or +/// [`fff_create_instance_with_value`] for FFI bindings) — both take the +/// versioned [`FffCreateOptions`] struct that evolves without ABI breaks. +/// This function delegates to `fff_create_instance_with` internally; the +/// `use_unsafe_no_lock` parameter is deprecated and ignored. +/// +/// ## Safety +/// See `fff_create_instance_with`. +#[deprecated( + since = "0.8.5", + note = "Use fff_create_instance_with (by pointer) or fff_create_instance_with_value (by value) with FffCreateOptions instead. The struct evolves without ABI breaks." +)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_create_instance( + base_path: *const c_char, + frecency_db_path: *const c_char, + history_db_path: *const c_char, + _use_unsafe_no_lock: bool, + enable_mmap_cache: bool, + enable_content_indexing: bool, + watch: bool, + ai_mode: bool, +) -> *mut FffResult { + let mut opts = FffCreateOptions::defaults(); + opts.base_path = base_path; + opts.frecency_db_path = frecency_db_path; + opts.history_db_path = history_db_path; + opts.enable_mmap_cache = enable_mmap_cache; + opts.enable_content_indexing = enable_content_indexing; + opts.watch = watch; + opts.ai_mode = ai_mode; + unsafe { fff_create_instance_with(&opts as *const FffCreateOptions) } +} + +/// Create a new file finder instance (legacy 13-arg positional signature). +/// +/// @deprecated Use [`fff_create_instance_with`] (or +/// [`fff_create_instance_with_value`] for FFI bindings) — both take the +/// versioned [`FffCreateOptions`] struct that evolves without ABI breaks. +/// The `use_unsafe_no_lock` parameter is deprecated and ignored. +/// +/// ## Safety +/// See `fff_create_instance_with`. +#[deprecated( + since = "0.8.5", + note = "Use fff_create_instance_with (by pointer) or fff_create_instance_with_value (by value) with FffCreateOptions instead. The struct evolves without ABI breaks." +)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_create_instance2( + base_path: *const c_char, + frecency_db_path: *const c_char, + history_db_path: *const c_char, + _use_unsafe_no_lock: bool, + enable_mmap_cache: bool, + enable_content_indexing: bool, + watch: bool, + ai_mode: bool, + log_file_path: *const c_char, + log_level: *const c_char, + cache_budget_max_files: u64, + cache_budget_max_bytes: u64, + cache_budget_max_file_size: u64, +) -> *mut FffResult { + let mut opts = FffCreateOptions::defaults(); + opts.base_path = base_path; + opts.frecency_db_path = frecency_db_path; + opts.history_db_path = history_db_path; + opts.enable_mmap_cache = enable_mmap_cache; + opts.enable_content_indexing = enable_content_indexing; + opts.watch = watch; + opts.ai_mode = ai_mode; + opts.log_file_path = log_file_path; + opts.log_level = log_level; + opts.cache_budget_max_files = cache_budget_max_files; + opts.cache_budget_max_bytes = cache_budget_max_bytes; + opts.cache_budget_max_file_size = cache_budget_max_file_size; + unsafe { fff_create_instance_with(&opts as *const FffCreateOptions) } +} + +/// Create a new file finder instance from an [`FffCreateOptions`] struct. +/// +/// **Direct C consumers** populate the struct (designated initializers +/// recommended), set `version` to [`FFF_CREATE_OPTIONS_VERSION`], and pass +/// it by pointer. New fields are appended in future versions; old callers +/// passing `version = 1` keep working forever. +/// +/// **FFI consumers** that prefer struct-by-value semantics (e.g. ffi-rs's +/// `paramsType: [structDef]`) should use [`fff_create_instance_with_value`] +/// instead — it's a thin calling-convention adapter that delegates here. +/// +/// Required: `opts.base_path` must be non-NULL and non-empty. +/// +/// When all three `cache_budget_*` values are 0 the budget is auto-computed +/// from repo size after the initial scan. Otherwise an explicit budget is +/// used: any field left at 0 falls back to its `unlimited()` default. +/// +/// ## Safety +/// * `opts` must be a valid pointer to an `FffCreateOptions` whose `version` +/// is in the range `1..=FFF_CREATE_OPTIONS_VERSION`. +/// * All string pointers inside `opts` must be valid null-terminated UTF-8 +/// or NULL. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_create_instance_with(opts: *const FffCreateOptions) -> *mut FffResult { + if opts.is_null() { + return FffResult::err("opts is null"); + } + let opts = unsafe { &*opts }; + if opts.version == 0 || opts.version > FFF_CREATE_OPTIONS_VERSION { + return FffResult::err(&format!( + "Unsupported FffCreateOptions version {} (library understands up to {})", + opts.version, FFF_CREATE_OPTIONS_VERSION + )); + } + + let base_path_str = match unsafe { cstr_to_str(opts.base_path) } { + Some(s) if !s.is_empty() => s.to_string(), + _ => return FffResult::err("opts.base_path is null or empty"), + }; + + if let Some(log_path) = unsafe { optional_cstr(opts.log_file_path) } { + let level = unsafe { optional_cstr(opts.log_level) }; + if let Err(e) = fff::log::init_tracing(log_path, level, None) { + return FffResult::err(&format!("Failed to init tracing: {}", e)); + } + } + + let frecency_path = unsafe { optional_cstr(opts.frecency_db_path) }.map(|s| s.to_string()); + let history_path = unsafe { optional_cstr(opts.history_db_path) }.map(|s| s.to_string()); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + let query_tracker = SharedQueryTracker::default(); + + if let Some(ref frecency_path) = frecency_path { + if let Some(parent) = PathBuf::from(frecency_path).parent() { + let _ = std::fs::create_dir_all(parent); + } + + match FrecencyTracker::open(frecency_path) { + Ok(tracker) => { + if let Err(e) = shared_frecency.init(tracker) { + return FffResult::err(&format!("Failed to acquire frecency lock: {}", e)); + } + } + Err(e) => return FffResult::err(&format!("Failed to init frecency db: {}", e)), + } + } + + if let Some(ref history_path) = history_path { + if let Some(parent) = PathBuf::from(history_path).parent() { + let _ = std::fs::create_dir_all(parent); + } + + match QueryTracker::open(history_path) { + Ok(tracker) => { + if let Err(e) = query_tracker.init(tracker) { + return FffResult::err(&format!("Failed to acquire query tracker lock: {}", e)); + } + } + Err(e) => return FffResult::err(&format!("Failed to init query tracker db: {}", e)), + } + } + + let mode = if opts.ai_mode { + FFFMode::Ai + } else { + FFFMode::Neovim + }; + + let cache_budget = fff::ContentCacheBudget::from_overrides( + opts.cache_budget_max_files as usize, + opts.cache_budget_max_bytes, + opts.cache_budget_max_file_size, + ); + + if let Err(e) = FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + fff::FilePickerOptions { + base_path: base_path_str, + enable_mmap_cache: opts.enable_mmap_cache, + enable_content_indexing: opts.enable_content_indexing, + watch: opts.watch, + mode, + cache_budget, + follow_symlinks: opts.version >= 2 && opts.follow_symlinks, + enable_fs_root_scanning: opts.enable_fs_root_scanning, + enable_home_dir_scanning: opts.enable_home_dir_scanning, + }, + ) { + return FffResult::err(&format!("Failed to init file picker: {}", e)); + } + + let instance = Box::new(FffInstance { + picker: shared_picker, + frecency: shared_frecency, + query_tracker, + }); + + let fff_handle = Box::into_raw(instance) as *mut c_void; + FffResult::ok_handle(fff_handle) +} + +/// Calling-convention adapter for [`fff_create_instance_with`]. +/// +/// Same logic, but takes the [`FffCreateOptions`] struct **by value**. This +/// makes the function callable from FFI libraries whose native struct +/// support passes structs by value on the wire (e.g. Node's `ffi-rs` with +/// `paramsType: [structDef]`). +/// +/// This is **not** a versioned wrapper — when new fields are appended to +/// `FffCreateOptions`, both this function and `fff_create_instance_with` +/// pick them up automatically with no signature change. +/// +/// ## Safety +/// All `*const c_char` fields inside `opts` must be valid null-terminated +/// UTF-8 or NULL. The struct itself is consumed by value. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_create_instance_with_value(opts: FffCreateOptions) -> *mut FffResult { + unsafe { fff_create_instance_with(&opts as *const FffCreateOptions) } +} + +/// Destroy a file finder instance and free all its resources. +/// +/// ## Safety +/// `fff_handle` must be a valid pointer returned by `fff_create_instance`, or null (no-op). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_destroy(fff_handle: *mut c_void) { + if fff_handle.is_null() { + return; + } + + let instance = unsafe { Box::from_raw(fff_handle as *mut FffInstance) }; + + if let Ok(mut guard) = instance.picker.write() + && let Some(picker) = guard.take() + { + drop(picker); + } + + if let Ok(mut guard) = instance.frecency.write() { + *guard = None; + } + if let Ok(mut guard) = instance.query_tracker.write() { + *guard = None; + } +} + +/// Perform fuzzy search on indexed files. +/// +/// # Parameters +/// +/// * `fff_handle` – instance from `fff_create_instance` +/// * `query` – search query string +/// * `current_file` – path of the currently open file for deprioritization (NULL/empty to skip) +/// * `max_threads` – maximum worker threads (0 = auto-detect) +/// * `page_index` – pagination offset (0 = first page) +/// * `page_size` – results per page (0 = default 100) +/// * `combo_boost_multiplier` – score multiplier for combo matches (0 = default 100) +/// * `min_combo_count` – minimum combo count before boost applies (0 = default 3) +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `query` and `current_file` must be valid null-terminated UTF-8 strings or NULL. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search( + fff_handle: *mut c_void, + query: *const c_char, + current_file: *const c_char, + max_threads: u32, + page_index: u32, + page_size: u32, + combo_boost_multiplier: i32, + min_combo_count: u32, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let query_str = match unsafe { cstr_to_str(query) } { + Some(s) => s, + None => return FffResult::err("Query is null or invalid UTF-8"), + }; + + let current_file_str = unsafe { optional_cstr(current_file) }; + let page_size = default_u32(page_size, 100) as usize; + let min_combo_count = default_u32(min_combo_count, 3); + let combo_boost_multiplier = default_i32(combo_boost_multiplier, 100); + + let picker_guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match picker_guard.as_ref() { + Some(p) => p, + None => { + return FffResult::err("File picker not initialized. Call fff_create_instance first."); + } + }; + + // Get query tracker ref for combo matching + let qt_guard = match inst.query_tracker.read() { + Ok(q) => q, + Err(_) => return FffResult::err("Failed to acquire query tracker lock"), + }; + let query_tracker_ref = qt_guard.as_ref(); + + let parser = QueryParser::default(); + let parsed = parser.parse(query_str); + + let results = picker.fuzzy_search( + &parsed, + query_tracker_ref, + FuzzySearchOptions { + max_threads: max_threads as usize, + current_file: current_file_str, + project_path: Some(picker.base_path()), + combo_boost_score_multiplier: combo_boost_multiplier, + min_combo_count, + pagination: PaginationArgs { + offset: page_index as usize, + limit: page_size, + }, + }, + ); + + let search_result = FffSearchResult::from_core(&results, picker); + FffResult::ok_handle(search_result as *mut c_void) +} + +/// Glob-only search: filter indexed files by a single glob pattern, rank by +/// frecency, and paginate. Bypasses the regular query parser entirely. +/// +/// Use this when you already have a literal glob pattern (e.g. `*.rs`, a +/// recursive `**` match, or `src/components` prefix) and want neither fuzzy +/// matching nor multi-token constraint parsing. Ranking falls back to +/// frecency because there is no fuzzy score to combine with. +/// +/// # Parameters +/// +/// * `fff_handle` - instance from `fff_create_instance` +/// * `pattern` - glob pattern (required, no parsing - passed through verbatim) +/// * `current_file` - path of the currently open file for deprioritization (NULL/empty to skip) +/// * `max_threads` - maximum worker threads (0 = auto-detect) +/// * `page_index` - pagination offset (0 = first page) +/// * `page_size` - results per page (0 = default 100) +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `pattern` and `current_file` must be valid null-terminated UTF-8 strings or NULL. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_glob( + fff_handle: *mut c_void, + pattern: *const c_char, + current_file: *const c_char, + max_threads: u32, + page_index: u32, + page_size: u32, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let pattern_str = match unsafe { cstr_to_str(pattern) } { + Some(s) if !s.is_empty() => s, + _ => return FffResult::err("Pattern is null, empty, or invalid UTF-8"), + }; + + let current_file_str = unsafe { optional_cstr(current_file) }; + let page_size = default_u32(page_size, 100) as usize; + + let picker_guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match picker_guard.as_ref() { + Some(p) => p, + None => { + return FffResult::err("File picker not initialized. Call fff_create_instance first."); + } + }; + + let results = picker.glob( + pattern_str, + FuzzySearchOptions { + max_threads: max_threads as usize, + current_file: current_file_str, + project_path: Some(picker.base_path()), + combo_boost_score_multiplier: 0, + min_combo_count: 0, + pagination: PaginationArgs { + offset: page_index as usize, + limit: page_size, + }, + }, + ); + + let search_result = FffSearchResult::from_core(&results, picker); + FffResult::ok_handle(search_result as *mut c_void) +} + +/// Perform fuzzy search on indexed directories. +/// +/// # Parameters +/// +/// * `fff_handle` – instance from `fff_create_instance` +/// * `query` – search query string +/// * `current_file` – path of the currently open file for distance scoring (NULL/empty to skip) +/// * `max_threads` – maximum worker threads (0 = auto-detect) +/// * `page_index` – pagination offset (0 = first page) +/// * `page_size` – results per page (0 = default 100) +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `query` and `current_file` must be valid null-terminated UTF-8 strings or NULL. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search_directories( + fff_handle: *mut c_void, + query: *const c_char, + current_file: *const c_char, + max_threads: u32, + page_index: u32, + page_size: u32, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let query_str = match unsafe { cstr_to_str(query) } { + Some(s) => s, + None => return FffResult::err("Query is null or invalid UTF-8"), + }; + + let current_file_str = unsafe { optional_cstr(current_file) }; + let page_size = default_u32(page_size, 100) as usize; + + let picker_guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match picker_guard.as_ref() { + Some(p) => p, + None => { + return FffResult::err("File picker not initialized. Call fff_create_instance first."); + } + }; + + let parser = QueryParser::new(fff_query_parser::DirSearchConfig); + let parsed = parser.parse(query_str); + + let results = picker.fuzzy_search_directories( + &parsed, + FuzzySearchOptions { + max_threads: max_threads as usize, + current_file: current_file_str, + project_path: Some(picker.base_path()), + combo_boost_score_multiplier: 0, + min_combo_count: 0, + pagination: PaginationArgs { + offset: page_index as usize, + limit: page_size, + }, + }, + ); + + let dir_result = FffDirSearchResult::from_core(&results, picker); + FffResult::ok_handle(dir_result as *mut c_void) +} + +/// Perform a mixed fuzzy search across both files and directories. +/// +/// Returns a single flat list where files and directories are interleaved +/// by total score in descending order. Each item has an `item_type` field +/// (0 = file, 1 = directory). +/// +/// # Parameters +/// +/// * `fff_handle` – instance from `fff_create_instance` +/// * `query` – search query string +/// * `current_file` – path of the currently open file (NULL/empty to skip) +/// * `max_threads` – maximum worker threads (0 = auto-detect) +/// * `page_index` – pagination offset (0 = first page) +/// * `page_size` – results per page (0 = default 100) +/// * `combo_boost_multiplier` – score multiplier for combo matches (0 = default 100) +/// * `min_combo_count` – minimum combo count before boost applies (0 = default 3) +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `query` and `current_file` must be valid null-terminated UTF-8 strings or NULL. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search_mixed( + fff_handle: *mut c_void, + query: *const c_char, + current_file: *const c_char, + max_threads: u32, + page_index: u32, + page_size: u32, + combo_boost_multiplier: i32, + min_combo_count: u32, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let query_str = match unsafe { cstr_to_str(query) } { + Some(s) => s, + None => return FffResult::err("Query is null or invalid UTF-8"), + }; + + let current_file_str = unsafe { optional_cstr(current_file) }; + let page_size = default_u32(page_size, 100) as usize; + let min_combo_count = default_u32(min_combo_count, 3); + let combo_boost_multiplier = default_i32(combo_boost_multiplier, 100); + + let picker_guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match picker_guard.as_ref() { + Some(p) => p, + None => { + return FffResult::err("File picker not initialized. Call fff_create_instance first."); + } + }; + + let qt_guard = match inst.query_tracker.read() { + Ok(q) => q, + Err(_) => return FffResult::err("Failed to acquire query tracker lock"), + }; + let query_tracker_ref = qt_guard.as_ref(); + + let parser = QueryParser::new(fff_query_parser::MixedSearchConfig); + let parsed = parser.parse(query_str); + + let results = picker.fuzzy_search_mixed( + &parsed, + query_tracker_ref, + FuzzySearchOptions { + max_threads: max_threads as usize, + current_file: current_file_str, + project_path: Some(picker.base_path()), + combo_boost_score_multiplier: combo_boost_multiplier, + min_combo_count, + pagination: PaginationArgs { + offset: page_index as usize, + limit: page_size, + }, + }, + ); + + let mixed_result = FffMixedSearchResult::from_core(&results, picker); + FffResult::ok_handle(mixed_result as *mut c_void) +} + +/// Perform content search (grep) across indexed files. +/// +/// # Parameters +/// +/// * `fff_handle` – instance from `fff_create_instance` +/// * `query` – search query (supports constraint syntax like `*.rs pattern`) +/// * `mode` – 0 = plain text (SIMD), 1 = regex, 2 = fuzzy +/// * `max_file_size` – skip files larger than this in bytes (0 = default 10 MB) +/// * `max_matches_per_file` – max matches per file (0 = unlimited) +/// * `smart_case` – case-insensitive when query is all lowercase +/// * `file_offset` – file-based pagination offset (0 = start) +/// * `page_limit` – max matches to return (0 = default 50) +/// * `time_budget_ms` – wall-clock budget in ms (0 = unlimited) +/// * `before_context` – context lines before each match +/// * `after_context` – context lines after each match +/// * `classify_definitions` – tag matches that are code definitions +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `query` must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_live_grep( + fff_handle: *mut c_void, + query: *const c_char, + mode: u8, + max_file_size: u64, + max_matches_per_file: u32, + smart_case: bool, + file_offset: u32, + page_limit: u32, + time_budget_ms: u64, + before_context: u32, + after_context: u32, + classify_definitions: bool, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let query_str = match unsafe { cstr_to_str(query) } { + Some(s) => s, + None => return FffResult::err("Query is null or invalid UTF-8"), + }; + + let picker_guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match picker_guard.as_ref() { + Some(p) => p, + None => { + return FffResult::err("File picker not initialized. Call fff_create_instance first."); + } + }; + + let is_ai = picker.mode().is_ai(); + let parsed = if is_ai { + fff::QueryParser::new(fff_query_parser::AiGrepConfig).parse(query_str) + } else { + fff::grep::parse_grep_query(query_str) + }; + + let options = fff::GrepSearchOptions { + max_file_size: default_u64(max_file_size, 10 * 1024 * 1024), + max_matches_per_file: max_matches_per_file as usize, + smart_case, + file_offset: file_offset as usize, + page_limit: default_u32(page_limit, 50) as usize, + mode: grep_mode_from_u8(mode), + time_budget_ms, + before_context: before_context as usize, + after_context: after_context as usize, + classify_definitions, + trim_whitespace: false, + abort_signal: None, + }; + + let result = picker.grep(&parsed, &options); + let grep_result = FffGrepResult::from_core(&result, picker); + FffResult::ok_handle(grep_result as *mut c_void) +} + +/// Perform multi-pattern OR search (Aho-Corasick) across indexed files. +/// +/// Searches for lines matching ANY of the provided patterns using +/// SIMD-accelerated multi-needle matching. +/// +/// # Parameters +/// +/// * `fff_handle` – instance from `fff_create_instance` +/// * `patterns_joined` – patterns separated by `\n` (e.g. `"foo\nbar\nbaz"`) +/// * `constraints` – file filter like `"*.rs"` or `"/src/"` (NULL/empty to skip) +/// * `max_file_size` – skip files larger than this in bytes (0 = default 10 MB) +/// * `max_matches_per_file` – max matches per file (0 = unlimited) +/// * `smart_case` – case-insensitive when all patterns are lowercase +/// * `file_offset` – file-based pagination offset (0 = start) +/// * `page_limit` – max matches to return (0 = default 50) +/// * `time_budget_ms` – wall-clock budget in ms (0 = unlimited) +/// * `before_context` – context lines before each match +/// * `after_context` – context lines after each match +/// * `classify_definitions` – tag matches that are code definitions +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `patterns_joined` and `constraints` must be valid null-terminated UTF-8 or NULL. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_multi_grep( + fff_handle: *mut c_void, + patterns_joined: *const c_char, + constraints: *const c_char, + max_file_size: u64, + max_matches_per_file: u32, + smart_case: bool, + file_offset: u32, + page_limit: u32, + time_budget_ms: u64, + before_context: u32, + after_context: u32, + classify_definitions: bool, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let patterns_str = match unsafe { cstr_to_str(patterns_joined) } { + Some(s) if !s.is_empty() => s, + _ => return FffResult::err("patterns_joined is null or empty"), + }; + + let patterns: Vec<&str> = patterns_str.split('\n').collect(); + if patterns.is_empty() || patterns.iter().all(|p| p.is_empty()) { + return FffResult::err("patterns must not be empty"); + } + + let constraints_str = unsafe { optional_cstr(constraints) }; + + let picker_guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match picker_guard.as_ref() { + Some(p) => p, + None => { + return FffResult::err("File picker not initialized. Call fff_create_instance first."); + } + }; + + let is_ai = picker.mode().is_ai(); + + // Parse constraints from the optional string (e.g. "*.rs /src/") + let parsed_constraints = constraints_str.map(|c| { + if is_ai { + fff::QueryParser::new(fff_query_parser::AiGrepConfig).parse(c) + } else { + fff::grep::parse_grep_query(c) + } + }); + + let constraint_refs: &[fff::Constraint<'_>] = match &parsed_constraints { + Some(q) => &q.constraints, + None => &[], + }; + + let options = fff::GrepSearchOptions { + max_file_size: default_u64(max_file_size, 10 * 1024 * 1024), + max_matches_per_file: max_matches_per_file as usize, + smart_case, + file_offset: file_offset as usize, + page_limit: default_u32(page_limit, 50) as usize, + mode: fff::GrepMode::PlainText, // ignored by multi_grep_search + time_budget_ms, + before_context: before_context as usize, + after_context: after_context as usize, + classify_definitions, + trim_whitespace: false, + abort_signal: None, + }; + + let result = picker.multi_grep(&patterns, constraint_refs, &options); + let grep_result = FffGrepResult::from_core(&result, picker); + FffResult::ok_handle(grep_result as *mut c_void) +} + +/// Trigger a rescan of the file index. +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_scan_files(fff_handle: *mut c_void) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + // Async: rescan runs on a BG thread, caller returns immediately. + // Use `fff_is_scanning` / `fff_wait_for_scan` to observe progress. + match inst.picker.trigger_full_rescan_async(&inst.frecency) { + Ok(()) => FffResult::ok_empty(), + Err(e) => FffResult::err(&format!("Failed to trigger rescan: {}", e)), + } +} + +/// Check if a scan is currently in progress. +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_is_scanning(fff_handle: *mut c_void) -> bool { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(_) => return false, + }; + + inst.picker + .read() + .ok() + .and_then(|guard| guard.as_ref().map(|p| p.is_scan_active())) + .unwrap_or(false) +} + +/// Get the base path of the file picker. +/// +/// Returns an `FffResult` with a heap-allocated C string in the `handle` +/// field. Free the string with `fff_free_string` after reading it. +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_get_base_path(fff_handle: *mut c_void) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match guard.as_ref() { + Some(p) => p, + None => return FffResult::err("File picker not initialized"), + }; + + FffResult::ok_string(&picker.base_path().to_string_lossy()) +} + +/// Get scan progress information. +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_get_scan_progress(fff_handle: *mut c_void) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let guard = match inst.picker.read() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let picker = match guard.as_ref() { + Some(p) => p, + None => return FffResult::err("File picker not initialized"), + }; + + let result = Box::into_raw(Box::new(FffScanProgress::from(picker.get_scan_progress()))); + FffResult::ok_handle(result as *mut c_void) +} + +/// Wait for initial scan to complete. +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_wait_for_scan( + fff_handle: *mut c_void, + timeout_ms: u64, +) -> *mut FffResult { + let FffInstance { picker, .. } = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let completed = picker.wait_for_scan(Duration::from_millis(timeout_ms)); + FffResult::ok_int(completed as i64) +} + +/// Wait for the background file watcher to be ready. +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_wait_for_watcher( + fff_handle: *mut c_void, + timeout_ms: u64, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let completed = inst + .picker + .wait_for_watcher(Duration::from_millis(timeout_ms)); + FffResult::ok_int(completed as i64) +} + +/// Restart indexing in a new directory. +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `new_path` must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_restart_index( + fff_handle: *mut c_void, + new_path: *const c_char, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let path_str = match unsafe { cstr_to_str(new_path) } { + Some(s) => s, + None => return FffResult::err("Path is null or invalid UTF-8"), + }; + + let path = PathBuf::from(&path_str); + if !path.exists() { + return FffResult::err(&format!("Path does not exist: {}", path_str)); + } + + let canonical_path = match fff::path_utils::canonicalize(&path) { + Ok(p) => p, + Err(e) => return FffResult::err(&format!("Failed to canonicalize path: {}", e)), + }; + + let guard = match inst.picker.write() { + Ok(g) => g, + Err(e) => return FffResult::err(&format!("Failed to acquire file picker lock: {}", e)), + }; + + let (warmup_caches, content_indexing, watch, mode, fs_root, home_dir, follow_symlinks) = + if let Some(ref picker) = *guard { + ( + picker.has_mmap_cache(), + picker.has_content_indexing(), + picker.has_watcher(), + picker.mode(), + picker.fs_root_scanning_enabled(), + picker.home_dir_scanning_enabled(), + picker.follows_symlinks(), + ) + } else { + (false, true, true, FFFMode::default(), false, false, false) + }; + + drop(guard); + + match FilePicker::new_with_shared_state( + inst.picker.clone(), + inst.frecency.clone(), + fff::FilePickerOptions { + base_path: canonical_path.to_string_lossy().to_string(), + enable_mmap_cache: warmup_caches, + enable_content_indexing: content_indexing, + watch, + mode, + cache_budget: None, + follow_symlinks, + enable_fs_root_scanning: fs_root, + enable_home_dir_scanning: home_dir, + }, + ) { + Ok(()) => FffResult::ok_empty(), + Err(e) => FffResult::err(&format!("Failed to init file picker: {}", e)), + } +} + +/// Refresh git status cache. +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_refresh_git_status(fff_handle: *mut c_void) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + match inst.picker.refresh_git_status(&inst.frecency) { + Ok(count) => FffResult::ok_int(count as i64), + Err(e) => FffResult::err(&format!("Failed to refresh git status: {}", e)), + } +} + +/// Track query completion for smart suggestions. +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`. +/// * `query` and `file_path` must be valid null-terminated UTF-8 strings. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_track_query( + fff_handle: *mut c_void, + query: *const c_char, + file_path: *const c_char, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let query_str = match unsafe { cstr_to_str(query) } { + Some(s) => s, + None => return FffResult::err("Query is null or invalid UTF-8"), + }; + + let path_str = match unsafe { cstr_to_str(file_path) } { + Some(s) => s, + None => return FffResult::err("File path is null or invalid UTF-8"), + }; + + let file_path = match fff::path_utils::canonicalize(path_str) { + Ok(p) => p, + Err(e) => return FffResult::err(&format!("Failed to canonicalize path: {}", e)), + }; + + let project_path = { + let guard = match inst.picker.read() { + Ok(g) => g, + Err(_) => return FffResult::ok_int(0), + }; + match guard.as_ref() { + Some(p) => p.base_path().to_path_buf(), + None => return FffResult::ok_int(0), + } + }; + + let mut qt_guard = match inst.query_tracker.write() { + Ok(q) => q, + Err(_) => return FffResult::ok_int(0), + }; + + if let Some(ref mut tracker) = *qt_guard + && let Err(e) = tracker.track_query_completion(query_str, &project_path, &file_path) + { + return FffResult::err(&format!("Failed to track query: {}", e)); + } + + FffResult::ok_int(1) +} + +/// Get historical query by offset (0 = most recent). +/// +/// ## Safety +/// `fff_handle` must be a valid instance pointer from `fff_create_instance`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_get_historical_query( + fff_handle: *mut c_void, + offset: u64, +) -> *mut FffResult { + let inst = match unsafe { instance_ref(fff_handle) } { + Ok(i) => i, + Err(e) => return e, + }; + + let project_path = { + let guard = match inst.picker.read() { + Ok(g) => g, + Err(_) => return FffResult::ok_empty(), + }; + match guard.as_ref() { + Some(p) => p.base_path().to_path_buf(), + None => return FffResult::ok_empty(), + } + }; + + let qt_guard = match inst.query_tracker.read() { + Ok(q) => q, + Err(_) => return FffResult::ok_empty(), + }; + + let tracker = match qt_guard.as_ref() { + Some(t) => t, + None => return FffResult::ok_empty(), + }; + + match tracker.get_historical_query(&project_path, offset as usize) { + Ok(Some(query)) => FffResult::ok_string(&query), + Ok(None) => FffResult::ok_empty(), + Err(e) => FffResult::err(&format!("Failed to get historical query: {}", e)), + } +} + +/// Get health check information. +/// +/// ## Safety +/// * `fff_handle` must be a valid instance pointer from `fff_create_instance`, or null for +/// a limited health check (version + git only). +/// * `test_path` can be null or a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_health_check( + fff_handle: *mut c_void, + test_path: *const c_char, +) -> *mut FffResult { + let test_path = unsafe { optional_cstr(test_path) } + .map(PathBuf::from) + .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()); + + let mut health = serde_json::Map::new(); + health.insert( + "version".to_string(), + serde_json::Value::String(env!("CARGO_PKG_VERSION").to_string()), + ); + + // Git info + let mut git_info = serde_json::Map::new(); + let git_version = git2::Version::get(); + let (major, minor, rev) = git_version.libgit2_version(); + git_info.insert( + "libgit2_version".to_string(), + serde_json::Value::String(format!("{}.{}.{}", major, minor, rev)), + ); + + match git2::Repository::discover(&test_path) { + Ok(repo) => { + git_info.insert("available".to_string(), serde_json::Value::Bool(true)); + git_info.insert( + "repository_found".to_string(), + serde_json::Value::Bool(true), + ); + if let Some(workdir) = repo.workdir() { + git_info.insert( + "workdir".to_string(), + serde_json::Value::String(workdir.to_string_lossy().to_string()), + ); + } + } + Err(e) => { + git_info.insert("available".to_string(), serde_json::Value::Bool(true)); + git_info.insert( + "repository_found".to_string(), + serde_json::Value::Bool(false), + ); + git_info.insert( + "error".to_string(), + serde_json::Value::String(e.message().to_string()), + ); + } + } + health.insert("git".to_string(), serde_json::Value::Object(git_info)); + + let inst: Option<&FffInstance> = if fff_handle.is_null() { + None + } else { + Some(unsafe { &*(fff_handle as *const FffInstance) }) + }; + + // File picker info + let mut picker_info = serde_json::Map::new(); + if let Some(inst) = inst { + match inst.picker.read() { + Ok(guard) => { + if let Some(ref picker) = *guard { + picker_info.insert("initialized".to_string(), serde_json::Value::Bool(true)); + picker_info.insert( + "base_path".to_string(), + serde_json::Value::String(picker.base_path().to_string_lossy().to_string()), + ); + picker_info.insert( + "is_scanning".to_string(), + serde_json::Value::Bool(picker.is_scan_active()), + ); + let progress = picker.get_scan_progress(); + picker_info.insert( + "indexed_files".to_string(), + serde_json::Value::Number(progress.scanned_files_count.into()), + ); + } else { + picker_info.insert("initialized".to_string(), serde_json::Value::Bool(false)); + } + } + Err(_) => { + picker_info.insert("initialized".to_string(), serde_json::Value::Bool(false)); + picker_info.insert( + "error".to_string(), + serde_json::Value::String("Failed to acquire lock".to_string()), + ); + } + } + } else { + picker_info.insert("initialized".to_string(), serde_json::Value::Bool(false)); + } + health.insert( + "file_picker".to_string(), + serde_json::Value::Object(picker_info), + ); + + // Frecency info + let mut frecency_info = serde_json::Map::new(); + if let Some(inst) = inst { + match inst.frecency.read() { + Ok(guard) => { + frecency_info.insert( + "initialized".to_string(), + serde_json::Value::Bool(guard.is_some()), + ); + if let Some(ref frecency) = *guard + && let Ok(health_data) = frecency.get_health() + { + let mut db_health = serde_json::Map::new(); + db_health.insert( + "path".to_string(), + serde_json::Value::String(health_data.path), + ); + db_health.insert( + "disk_size".to_string(), + serde_json::Value::Number(health_data.disk_size.into()), + ); + frecency_info.insert( + "db_healthcheck".to_string(), + serde_json::Value::Object(db_health), + ); + } + } + Err(_) => { + frecency_info.insert("initialized".to_string(), serde_json::Value::Bool(false)); + } + } + } else { + frecency_info.insert("initialized".to_string(), serde_json::Value::Bool(false)); + } + health.insert( + "frecency".to_string(), + serde_json::Value::Object(frecency_info), + ); + + // Query tracker info + let mut query_info = serde_json::Map::new(); + if let Some(inst) = inst { + match inst.query_tracker.read() { + Ok(guard) => { + query_info.insert( + "initialized".to_string(), + serde_json::Value::Bool(guard.is_some()), + ); + if let Some(ref tracker) = *guard + && let Ok(health_data) = tracker.get_health() + { + let mut db_health = serde_json::Map::new(); + db_health.insert( + "path".to_string(), + serde_json::Value::String(health_data.path), + ); + db_health.insert( + "disk_size".to_string(), + serde_json::Value::Number(health_data.disk_size.into()), + ); + query_info.insert( + "db_healthcheck".to_string(), + serde_json::Value::Object(db_health), + ); + } + } + Err(_) => { + query_info.insert("initialized".to_string(), serde_json::Value::Bool(false)); + } + } + } else { + query_info.insert("initialized".to_string(), serde_json::Value::Bool(false)); + } + health.insert( + "query_tracker".to_string(), + serde_json::Value::Object(query_info), + ); + + match serde_json::to_string(&health) { + Ok(json) => FffResult::ok_string(&json), + Err(e) => FffResult::err(&format!("Failed to serialize health check: {}", e)), + } +} + +/// Free a search result returned by `fff_search`. +/// +/// This frees the `FffSearchResult` struct, its `items` and `scores` arrays, +/// and all heap-allocated strings within each item and score. +/// +/// ## Safety +/// `result` must be a valid pointer previously returned via `FffResult.handle` +/// from `fff_search`, or null (no-op). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_free_search_result(result: *mut FffSearchResult) { + if result.is_null() { + return; + } + + unsafe { + let result = Box::from_raw(result); + let count = result.count as usize; + + if !result.items.is_null() { + let mut items = Vec::from_raw_parts(result.items, count, count); + for item in &mut items { + item.free_strings(); + } + } + if !result.scores.is_null() { + let mut scores = Vec::from_raw_parts(result.scores, count, count); + for score in &mut scores { + score.free_strings(); + } + } + } +} + +/// Get a pointer to the `index`-th `FffFileItem` in a search result. +/// +/// Returns null if `result` is null or `index >= result->count`. +/// The returned pointer is valid until the search result is freed. +/// +/// ## Safety +/// `result` must be a valid `FffSearchResult` pointer from `fff_search`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search_result_get_item( + result: *const FffSearchResult, + index: u32, +) -> *const FffFileItem { + if result.is_null() { + return std::ptr::null(); + } + let result = unsafe { &*result }; + if index >= result.count || result.items.is_null() { + return std::ptr::null(); + } + unsafe { result.items.add(index as usize) } +} + +/// Get a pointer to the `index`-th `FffScore` in a search result. +/// +/// Returns null if `result` is null or `index >= result->count`. +/// The returned pointer is valid until the search result is freed. +/// +/// ## Safety +/// `result` must be a valid `FffSearchResult` pointer from `fff_search`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_search_result_get_score( + result: *const FffSearchResult, + index: u32, +) -> *const FffScore { + if result.is_null() { + return std::ptr::null(); + } + let result = unsafe { &*result }; + if index >= result.count || result.scores.is_null() { + return std::ptr::null(); + } + unsafe { result.scores.add(index as usize) } +} + +/// Free a grep result returned by `fff_live_grep` or `fff_multi_grep`. +/// +/// This frees the `FffGrepResult` struct, its `items` array, and all +/// heap-allocated strings, match ranges, and context arrays within each match. +/// +/// ## Safety +/// `result` must be a valid pointer previously returned via `FffResult.handle` +/// from `fff_live_grep` or `fff_multi_grep`, or null (no-op). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_free_grep_result(result: *mut FffGrepResult) { + if result.is_null() { + return; + } + + unsafe { + let result = Box::from_raw(result); + let count = result.count as usize; + + if !result.items.is_null() { + let mut items = Vec::from_raw_parts(result.items, count, count); + for item in &mut items { + item.free_fields(); + } + } + if !result.regex_fallback_error.is_null() { + drop(CString::from_raw(result.regex_fallback_error)); + } + } +} + +/// Get a pointer to the `index`-th `FffGrepMatch` in a grep result. +/// +/// Returns null if `result` is null or `index >= result->count`. +/// The returned pointer is valid until the grep result is freed. +/// +/// ## Safety +/// `result` must be a valid `FffGrepResult` pointer from `fff_live_grep` or `fff_multi_grep`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_grep_result_get_match( + result: *const FffGrepResult, + index: u32, +) -> *const FffGrepMatch { + if result.is_null() { + return std::ptr::null(); + } + let result = unsafe { &*result }; + if index >= result.count || result.items.is_null() { + return std::ptr::null(); + } + unsafe { result.items.add(index as usize) } +} + +/// Free a scan progress result returned by `fff_get_scan_progress`. +/// +/// ## Safety +/// `result` must be a valid pointer previously returned via `FffResult.handle` +/// from `fff_get_scan_progress`, or null (no-op). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_free_scan_progress(result: *mut FffScanProgress) { + if !result.is_null() { + unsafe { drop(Box::from_raw(result)) }; + } +} + +/// Offset a pointer by `byte_offset` bytes. +/// +/// General-purpose utility for FFI consumers that need pointer arithmetic +/// (e.g. iterating over arrays). Returns null if `base` is null. +/// +/// ## Safety +/// The resulting pointer must be within the bounds of the original allocation. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_ptr_offset(base: *const c_void, byte_offset: usize) -> *const c_void { + if base.is_null() { + return std::ptr::null(); + } + unsafe { (base as *const u8).add(byte_offset) as *const c_void } +} + +/// Free a result returned by any `fff_*` function. +/// **IMPORTANT:** this doesn't clean the the internal handle, so it is safe to call right after +/// you handle the error case. +/// +/// Note: Many non-libffi implementations are not supporting struct-by-value returns, so it's more +/// convenient to have pointer returned at most of the time, though allocating result for every call +/// is annoying, so we just rely on the fact that our allocator is good enough. +/// +/// ## Safety +/// `result_ptr` must be a valid pointer returned by a `fff_*` function. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_free_result(result_ptr: *mut FffResult) { + if result_ptr.is_null() { + return; + } + + unsafe { + let result = Box::from_raw(result_ptr); + if !result.error.is_null() { + drop(CString::from_raw(result.error)); + } + // Note: `handle` is NOT freed here — the caller must free it + // with the appropriate function (fff_destroy, fff_free_search_result, + // fff_free_grep_result, fff_free_string, fff_free_scan_progress, etc.). + } +} + +/// Free a string returned by `fff_*` functions. +/// +/// ## Safety +/// `s` must be a valid C string allocated by this library. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_free_string(s: *mut c_char) { + unsafe { + if !s.is_null() { + drop(CString::from_raw(s)); + } + } +} + +// --------------------------------------------------------------------------- +// Directory search: free and accessor functions +// --------------------------------------------------------------------------- + +/// Free a directory search result returned by `fff_search_directories`. +/// +/// ## Safety +/// `result` must be a valid pointer previously returned via `FffResult.handle` +/// from `fff_search_directories`, or null (no-op). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_free_dir_search_result(result: *mut FffDirSearchResult) { + if result.is_null() { + return; + } + + unsafe { + let result = Box::from_raw(result); + let count = result.count as usize; + + if !result.items.is_null() { + let mut items = Vec::from_raw_parts(result.items, count, count); + for item in &mut items { + item.free_strings(); + } + } + if !result.scores.is_null() { + let mut scores = Vec::from_raw_parts(result.scores, count, count); + for score in &mut scores { + score.free_strings(); + } + } + } +} + +/// Get a pointer to the `index`-th `FffDirItem` in a directory search result. +/// +/// ## Safety +/// `result` must be a valid `FffDirSearchResult` pointer from `fff_search_directories`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_dir_search_result_get_item( + result: *const FffDirSearchResult, + index: u32, +) -> *const FffDirItem { + if result.is_null() { + return std::ptr::null(); + } + let result = unsafe { &*result }; + if index >= result.count || result.items.is_null() { + return std::ptr::null(); + } + unsafe { result.items.add(index as usize) } +} + +/// Get a pointer to the `index`-th `FffScore` in a directory search result. +/// +/// ## Safety +/// `result` must be a valid `FffDirSearchResult` pointer from `fff_search_directories`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_dir_search_result_get_score( + result: *const FffDirSearchResult, + index: u32, +) -> *const FffScore { + if result.is_null() { + return std::ptr::null(); + } + let result = unsafe { &*result }; + if index >= result.count || result.scores.is_null() { + return std::ptr::null(); + } + unsafe { result.scores.add(index as usize) } +} + +// --------------------------------------------------------------------------- +// Mixed search: free and accessor functions +// --------------------------------------------------------------------------- + +/// Free a mixed search result returned by `fff_search_mixed`. +/// +/// ## Safety +/// `result` must be a valid pointer previously returned via `FffResult.handle` +/// from `fff_search_mixed`, or null (no-op). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_free_mixed_search_result(result: *mut FffMixedSearchResult) { + if result.is_null() { + return; + } + + unsafe { + let result = Box::from_raw(result); + let count = result.count as usize; + + if !result.items.is_null() { + let mut items = Vec::from_raw_parts(result.items, count, count); + for item in &mut items { + item.free_strings(); + } + } + if !result.scores.is_null() { + let mut scores = Vec::from_raw_parts(result.scores, count, count); + for score in &mut scores { + score.free_strings(); + } + } + } +} + +/// Get a pointer to the `index`-th `FffMixedItem` in a mixed search result. +/// +/// ## Safety +/// `result` must be a valid `FffMixedSearchResult` pointer from `fff_search_mixed`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_mixed_search_result_get_item( + result: *const FffMixedSearchResult, + index: u32, +) -> *const FffMixedItem { + if result.is_null() { + return std::ptr::null(); + } + let result = unsafe { &*result }; + if index >= result.count || result.items.is_null() { + return std::ptr::null(); + } + unsafe { result.items.add(index as usize) } +} + +/// Get a pointer to the `index`-th `FffScore` in a mixed search result. +/// +/// ## Safety +/// `result` must be a valid `FffMixedSearchResult` pointer from `fff_search_mixed`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fff_mixed_search_result_get_score( + result: *const FffMixedSearchResult, + index: u32, +) -> *const FffScore { + if result.is_null() { + return std::ptr::null(); + } + let result = unsafe { &*result }; + if index >= result.count || result.scores.is_null() { + return std::ptr::null(); + } + unsafe { result.scores.add(index as usize) } +} diff --git a/crates/fff-c/tests/smoke.c b/crates/fff-c/tests/smoke.c new file mode 100644 index 0000000..b021953 --- /dev/null +++ b/crates/fff-c/tests/smoke.c @@ -0,0 +1,89 @@ +/* + * Smoke test for libfff_c — the smallest possible end-to-end exercise of + * the public C API. We: + * + * 1. Create a picker with an `FffCreateOptions` populated via C99 + * designated initializers (the recommended idiom for direct C use). + * 2. Wait for the initial scan to complete. + * 3. Search for "smoke.c". + * 4. Fail unless this very file appears in the results. + * + * Build + run via `make test-c-smoke`. Override $(CC) to test other + * compilers. + */ + +#include +#include +#include + +int main(int argc, char **argv) { + const char *base_path = argc > 1 ? argv[1] : "."; + + // make sure that FFF C api is designed more for FFI rather than for direct C usage (I'm sorry) + struct FffResult *create_result = fff_create_instance_with(&(struct FffCreateOptions){ + .version = FFF_CREATE_OPTIONS_VERSION, + .base_path = base_path, + .enable_mmap_cache = false, + .enable_content_indexing = false, + .watch = false, + }); + + if (!create_result->success) { + fprintf(stderr, "fff couldn't create instance: %s\n", + create_result->error ? create_result->error : "?"); + fff_free_result(create_result); + return 1; + } + + void *file_picker = create_result->handle; + fff_free_result(create_result); // safe to drop now: handle outlives the envelope + + struct FffResult *scan_result = fff_wait_for_scan(file_picker, 5000); + if (!scan_result->success) { + fprintf(stderr, "wait_for_scan failed: %s\n", + scan_result->error ? scan_result->error : "?"); + fff_free_result(scan_result); + fff_destroy(file_picker); + return 1; + } + // int_value: 1 = scan completed in time, 0 = timed out. + if (scan_result->int_value == 0) { + fprintf(stderr, "wait_for_scan: timed out before initial scan finished\n"); + fff_free_result(scan_result); + fff_destroy(file_picker); + return 1; + } + fff_free_result(scan_result); + + struct FffResult *res = fff_search(file_picker, "smkoe.c", "", 0, 0, 50, 0, 0); + if (!res->success) { + fprintf(stderr, "search failed: %s\n", res->error ? res->error : "?"); + fff_free_result(res); + fff_destroy(file_picker); + return 1; + } + + struct FffSearchResult *sr = (struct FffSearchResult *)res->handle; + uint32_t total = sr->count; + int found = 0; + for (uint32_t i = 0; i < sr->count; i++) { + const char *path = sr->items[i].relative_path; + if (path && strstr(path, "smoke.c")) { + found = 1; + fprintf(stderr, "found self: %s\n", path); + break; + } + } + + fff_free_search_result(sr); + fff_free_result(res); + fff_destroy(file_picker); + + if (!found) { + fprintf(stderr, "FAIL: smoke.c not in search results (count=%u)\n", total); + return 1; + } + + fprintf(stderr, "PASS\n"); + return 0; +} diff --git a/crates/fff-core/Cargo.toml b/crates/fff-core/Cargo.toml new file mode 100644 index 0000000..5600fd3 --- /dev/null +++ b/crates/fff-core/Cargo.toml @@ -0,0 +1,95 @@ +[package] +name = "fff-search" +version = "0.9.6" +edition = "2024" +license = "MIT" +authors = ["Dmitriy Kovalenko "] +description = "Faboulous & Fast File Finder - a fast and extremely correct file finder SDK with typo resistance, SIMD, prefiltering, and more" + +[lib] +path = "src/lib.rs" +crate-type = ["rlib", "staticlib", "cdylib"] + +[[bench]] +name = "parse_bench" +harness = false + +[[bench]] +name = "bigram_bench" +harness = false + +[[bench]] +name = "memmem_bench" +harness = false + +[[bench]] +name = "glob_bench" +harness = false +required-features = ["zlob"] + +[features] +# `ripgrep` is the pure-Rust walker/glob backend and is on by default so +# consumers build without a Zig toolchain. CI/release opt into zlob via +# `--no-default-features --features zlob`. +default = ["ripgrep"] +# Enable C FFI exports +ffi = [] +# Enables POC definition classification for grep result matched lines +definitions = [] +# Pure-Rust filesystem walker + glob matcher (ignore + globset crates). +ripgrep = ["dep:ignore", "dep:globset", "fff-query-parser/ripgrep"] +# Call mi_collect(true) after large allocator churn (bigram build). +# Requires mimalloc to be the global allocator (linked by fff-nvim). +mimalloc-collect = ["dep:libmimalloc-sys"] +# Use zlob (Zig-compiled C globbing library) for glob matching. +# Requires Zig to be installed. When disabled, falls back to globset (pure Rust). +zlob = ["dep:zlob", "fff-query-parser/zlob"] + +[dependencies] +ahash = { workspace = true } +rayon = { workspace = true } +smallvec = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } +fff-query-parser = { workspace = true , version = "0.9.0" } +blake3 = { workspace = true } +dirs = { workspace = true } +libc = "0.2" +git2 = { workspace = true } +glidesort = { workspace = true } +globset = { workspace = true, optional = true } +fff-grep = { workspace = true , version = "0.9.0" } +aho-corasick = "1" +memchr = "2" +heed = { workspace = true } +ignore = { workspace = true, optional = true } +memmap2 = { workspace = true } +neo_frizbee = { workspace = true } +notify = { workspace = true } +notify-debouncer-full = { workspace = true } +parking_lot = { workspace = true } +pathdiff = { workspace = true } +regex = { workspace = true } +regex-syntax = "0.8" +serde = { version = "1.0", features = ["derive"] } +tracing-appender = "0.2" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +zlob = { workspace = true, optional = true } +libmimalloc-sys = { version = "0.1", optional = true, features = ["extended", "local_dynamic_tls"] } +mimalloc = { version = "0.1", optional = true, features = ["local_dynamic_tls"] } +# Platform-specific: dunce for Windows to avoid \\?\ extended path prefix +[target.'cfg(windows)'.dependencies] +dunce = { workspace = true } + +# signal-hook only compiles on unix; we wrap the SIGSEGV handler behind cfg(unix) +[target.'cfg(unix)'.dependencies] +signal-hook-registry = { workspace = true } + +[dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } +ctor = "0.2" +proptest = { version = "1", default-features = false, features = ["std", "fork"] } +rand = { version = "0.8", features = ["small_rng"] } +tempfile = "3.8" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + diff --git a/crates/fff-core/README.md b/crates/fff-core/README.md new file mode 100644 index 0000000..e7ee90e --- /dev/null +++ b/crates/fff-core/README.md @@ -0,0 +1,29 @@ +# fff + +fff is a file search toolkit. It is faster than ripgrep and fzf and designed for a long running applications like file editors, ai agents, or file exploerers. + +> [!Important performance information] +> For the most optimized fff build use `zlob` feature. It requires zig v0.16.0 to be installed on the machine. + +## Features + +- Fuzzy file name search +- Typo resistance +- Frecency and query history ranking +- Native git support via libgit +- Advanced ranking +- Grep functionality with SIMD optimized plain matcher and regex +- Multi grep using aho-corasick algorithm +- Efficient memory mapping for file system +- Cross platform support (Linux, Windows, MacOS) +- Advnaced constraints syntax allowing to prefilter based on git status, glob, extension, size, timing and more + +## Performance + +FFF is designed for high performance and low latency. SIMD optimized where needed, parallelized for multi core systems, efficient sorting and ranking algorithms, memaps and much more. + +On MacOS FFF is about 20-50 times faster than ripgrep for content search and around 10 times faster than fzf for file name search. + +## Documentation + +Refer rust docs https://docs.rs/crate/fff-search/latest diff --git a/crates/fff-core/benches/bigram_bench.rs b/crates/fff-core/benches/bigram_bench.rs new file mode 100644 index 0000000..85c7466 --- /dev/null +++ b/crates/fff-core/benches/bigram_bench.rs @@ -0,0 +1,164 @@ +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use fff_search::bigram_filter::{BigramFilter, BigramIndexBuilder}; + +/// Build a realistic bigram index for benchmarking. +/// Simulates a large repo by generating varied content per file. +fn build_test_index(file_count: usize) -> BigramFilter { + let builder = BigramIndexBuilder::new(file_count); + let skip_builder = BigramIndexBuilder::new(file_count); + + for i in 0..file_count { + // Generate varied content so we get a mix of sparse and dense columns + let content = format!( + "struct File{i} {{ fn process() {{ let controller = read(path); }} }} // module {i}" + ); + builder.add_file_content(&skip_builder, i, content.as_bytes()); + } + + let mut index = builder.compress(None); + let skip_index = skip_builder.compress(Some(12)); + index.set_skip_index(skip_index); + index +} + +fn bench_bigram_query(c: &mut Criterion) { + let file_counts = [10_000, 100_000, 500_000]; + + for &file_count in &file_counts { + let index = build_test_index(file_count); + eprintln!( + "Index ({} files): {} columns", + file_count, + index.columns_used(), + ); + + let mut group = c.benchmark_group(format!("bigram_query_{file_count}")); + group.sample_size(500); + + let queries: &[(&str, &[u8])] = &[ + ("short_2char", b"st"), + ("medium_6char", b"struct"), + ("long_14char", b"let controller"), + ("multi_word", b"fn process"), + ]; + + for (name, query) in queries { + group.bench_with_input(BenchmarkId::from_parameter(name), query, |b, q| { + b.iter(|| { + let result = index.query(black_box(q)); + black_box(&result); + }); + }); + } + + group.finish(); + } +} + +fn bench_bigram_is_candidate(c: &mut Criterion) { + let index = build_test_index(500_000); + let candidates = match index.query(b"struct") { + Some(c) => c, + None => { + // All bigrams ubiquitous at this size — skip candidate benches + eprintln!("Skipping is_candidate bench: query returned None (all bigrams ubiquitous)"); + return; + } + }; + + c.bench_function("is_candidate_500k", |b| { + b.iter(|| { + let mut count = 0u32; + for i in 0..500_000 { + if BigramFilter::is_candidate(black_box(&candidates), i) { + count += 1; + } + } + black_box(count) + }); + }); + + c.bench_function("count_candidates_500k", |b| { + b.iter(|| BigramFilter::count_candidates(black_box(&candidates))); + }); +} + +fn bench_bigram_build(c: &mut Criterion) { + let mut group = c.benchmark_group("bigram_build"); + group.sample_size(10); + + let file_counts = [10_000, 100_000]; + + for &file_count in &file_counts { + // Pre-generate content so we only measure index building. + // Short content (~85 bytes/file) exercises the scalar fast path. + let contents: Vec = (0..file_count) + .map(|i| { + format!( + "struct File{i} {{ fn process() {{ let controller = read(path); }} }} // mod {i}" + ) + }) + .collect(); + + group.bench_with_input( + BenchmarkId::new("short_content", file_count), + &file_count, + |b, &fc| { + b.iter(|| { + let builder = BigramIndexBuilder::new(fc); + let skip_builder = BigramIndexBuilder::new(fc); + for (i, content) in contents.iter().enumerate() { + builder.add_file_content(&skip_builder, i, content.as_bytes()); + } + let index = builder.compress(None); + black_box(index.columns_used()) + }); + }, + ); + + // Long content (~4 KB/file) exercises the SIMD pre-pass path. + // Build a realistic-looking source-like blob by repeating snippets. + let long_contents: Vec = (0..file_count) + .map(|i| { + let mut s = String::with_capacity(4096); + for j in 0..50 { + s.push_str(&format!( + "pub fn handler_{i}_{j}(ctx: &Context) -> Result {{\n" + )); + s.push_str(" let parsed = ctx.parse()?;\n"); + s.push_str(" let validated = parsed.validate()?;\n"); + s.push_str(&format!(" ctx.respond(validated, {}).await\n", j)); + s.push_str("}\n\n"); + } + s + }) + .collect(); + + group.bench_with_input( + BenchmarkId::new("long_content", file_count), + &file_count, + |b, &fc| { + b.iter(|| { + let builder = BigramIndexBuilder::new(fc); + let skip_builder = BigramIndexBuilder::new(fc); + for (i, content) in long_contents.iter().enumerate() { + builder.add_file_content(&skip_builder, i, content.as_bytes()); + } + let index = builder.compress(None); + black_box(index.columns_used()) + }); + }, + ); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_bigram_query, + bench_bigram_is_candidate, + bench_bigram_build, +); + +criterion_main!(benches); diff --git a/crates/fff-core/benches/glob_bench.rs b/crates/fff-core/benches/glob_bench.rs new file mode 100644 index 0000000..914fa0c --- /dev/null +++ b/crates/fff-core/benches/glob_bench.rs @@ -0,0 +1,386 @@ +//! Compare three glob-matching strategies for `match_glob_pattern` in constraints.rs: +//! +//! 1. Current: `zlob_match_paths` -> collect `as_ptr()` into AHashSet, filter paths +//! by pointer to recover indices. +//! 2. Free fn: `zlob_match_paths_indices` (added in zlob 1.4) — indices direct from C. +//! 3. Compiled: `ZlobPattern::compile` + `match_indices` — same indices path, but with +//! a precompiled pattern (reusable). For one-shot it should match (2); the win +//! appears if the pattern is reused (chunked / repeated calls). +use ahash::AHashSet; +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use zlob::{ZlobFlags, ZlobPattern, zlob_match_paths, zlob_match_paths_indices}; + +fn make_paths(n: usize) -> Vec { + let exts = ["rs", "ts", "lua", "md", "toml", "go", "py", "c", "h", "txt"]; + let dirs = [ + "src/core", + "src/ui", + "crates/fff-core/src", + "lua/fff", + "tests/integration", + "vendor/lib", + "node_modules/foo/bar", + "docs/internal", + ]; + let mut out = Vec::with_capacity(n); + for i in 0..n { + let dir = dirs[i % dirs.len()]; + let ext = exts[i % exts.len()]; + out.push(format!("{dir}/file_{i}.{ext}")); + } + out +} + +fn current_impl(pattern: &str, paths: &[&str]) -> AHashSet { + let Ok(Some(matches)) = zlob_match_paths(pattern, paths, ZlobFlags::RECOMMENDED) else { + return AHashSet::new(); + }; + let matched_set: AHashSet = matches.iter().map(|s| s.as_ptr() as usize).collect(); + paths + .iter() + .enumerate() + .filter(|(_, p)| matched_set.contains(&(p.as_ptr() as usize))) + .map(|(i, _)| i) + .collect() +} + +fn indices_free_fn(pattern: &str, paths: &[&str]) -> AHashSet { + let Ok(hits) = zlob_match_paths_indices(pattern, paths, ZlobFlags::RECOMMENDED) else { + return AHashSet::new(); + }; + hits.to_iter().collect() +} + +fn compiled_pattern(pattern: &str, paths: &[&str]) -> AHashSet { + let Ok(p) = ZlobPattern::compile(pattern, ZlobFlags::RECOMMENDED) else { + return AHashSet::new(); + }; + let Ok(hits) = p.match_indices(paths, ZlobFlags::RECOMMENDED) else { + return AHashSet::new(); + }; + hits.to_iter().collect() +} + +fn bench_glob_strategies(c: &mut Criterion) { + let path_counts = [1_000usize, 10_000, 100_000]; + let patterns: &[(&str, &str)] = &[ + ("ext_rs", "**/*.rs"), + ("dir_glob", "src/**/*.{ts,lua}"), + ("literal_seg", "**/node_modules/**"), + ("brace_multi", "**/*.{rs,ts,lua,md}"), + ]; + + for &count in &path_counts { + let owned = make_paths(count); + let paths: Vec<&str> = owned.iter().map(|s| s.as_str()).collect(); + + let mut group = c.benchmark_group(format!("glob_{count}")); + group.sample_size(50); + + for &(name, pat) in patterns { + let id_curr = BenchmarkId::new("current_ptr_trick", name); + group.bench_with_input(id_curr, &pat, |b, &pat| { + b.iter(|| { + let r = current_impl(black_box(pat), black_box(&paths)); + black_box(r); + }); + }); + + let id_idx = BenchmarkId::new("match_indices_fn", name); + group.bench_with_input(id_idx, &pat, |b, &pat| { + b.iter(|| { + let r = indices_free_fn(black_box(pat), black_box(&paths)); + black_box(r); + }); + }); + + let id_comp = BenchmarkId::new("compiled_pattern", name); + group.bench_with_input(id_comp, &pat, |b, &pat| { + b.iter(|| { + let r = compiled_pattern(black_box(pat), black_box(&paths)); + black_box(r); + }); + }); + } + + group.finish(); + } +} + +/// Hot-loop: pattern compiled ONCE, matched many times against fresh path slices. +/// Models a hypothetical change where we cache compiled patterns across calls. +fn bench_compiled_reuse(c: &mut Criterion) { + let owned = make_paths(10_000); + let paths: Vec<&str> = owned.iter().map(|s| s.as_str()).collect(); + let pat = "**/*.{rs,ts,lua,md}"; + + let mut group = c.benchmark_group("glob_reuse_10k"); + group.sample_size(100); + + group.bench_function("recompile_each_time", |b| { + b.iter(|| { + let p = ZlobPattern::compile(black_box(pat), ZlobFlags::RECOMMENDED).unwrap(); + let hits = p + .match_indices(black_box(&paths), ZlobFlags::RECOMMENDED) + .unwrap(); + black_box(hits.len()); + }); + }); + + let compiled = ZlobPattern::compile(pat, ZlobFlags::RECOMMENDED).unwrap(); + group.bench_function("reuse_compiled", |b| { + b.iter(|| { + let hits = compiled + .match_indices(black_box(&paths), ZlobFlags::RECOMMENDED) + .unwrap(); + black_box(hits.len()); + }); + }); + + group.finish(); +} + +/// End-to-end: build the lookup AND iterate items checking membership, modeling the +/// real call shape in `apply_constraints` (filter loop reads the result for every item). +fn bench_full_pipeline(c: &mut Criterion) { + bench_full_pipeline_size(c, 100_000); + bench_full_pipeline_size(c, 500_000); +} + +fn bench_full_pipeline_size(c: &mut Criterion, count: usize) { + let owned = make_paths(count); + let paths: Vec<&str> = owned.iter().map(|s| s.as_str()).collect(); + let pat = "**/*.{rs,ts,lua,md}"; + + let mut group = c.benchmark_group(format!("glob_full_pipeline_{count}")); + group.sample_size(50); + + // (A) current: indices -> AHashSet -> per-item set.contains + group.bench_function("indices_to_ahashset_then_filter", |b| { + b.iter(|| { + let hits = + zlob_match_paths_indices(black_box(pat), &paths, ZlobFlags::RECOMMENDED).unwrap(); + let set: AHashSet = hits.to_iter().collect(); + let count = (0..paths.len()).filter(|i| set.contains(i)).count(); + black_box(count); + }); + }); + + // (B) indices -> Vec bitmap -> per-item array lookup + group.bench_function("indices_to_bitmap_then_filter", |b| { + b.iter(|| { + let hits = + zlob_match_paths_indices(black_box(pat), &paths, ZlobFlags::RECOMMENDED).unwrap(); + let mut mask = vec![false; paths.len()]; + for i in hits.to_iter() { + mask[i] = true; + } + let count = (0..paths.len()).filter(|&i| mask[i]).count(); + black_box(count); + }); + }); + + // (C) compiled pattern + per-item matches() inside the filter loop. No batch. + group.bench_function("compiled_per_item_matches", |b| { + b.iter(|| { + let p = ZlobPattern::compile(black_box(pat), ZlobFlags::RECOMMENDED).unwrap(); + let count = paths.iter().filter(|path| p.matches_default(path)).count(); + black_box(count); + }); + }); + + // (D) compiled pattern + chunked batch -> Vec bitmap. Best of both: + // SIMD batch wins inside chunks, no global allocation pressure, O(1) lookup. + group.bench_function("compiled_chunked_to_bitmap", |b| { + b.iter(|| { + let p = ZlobPattern::compile(black_box(pat), ZlobFlags::RECOMMENDED).unwrap(); + let mut mask = vec![false; paths.len()]; + for (chunk_idx, chunk) in paths.chunks(512).enumerate() { + let base = chunk_idx * 512; + let hits = p.match_indices(chunk, ZlobFlags::RECOMMENDED).unwrap(); + for i in hits.to_iter() { + mask[base + i] = true; + } + } + let count = (0..paths.len()).filter(|&i| mask[i]).count(); + black_box(count); + }); + }); + + // (E') indices -> bit-packed Vec -> per-item bit test + group.bench_function("indices_to_bitset_then_filter", |b| { + b.iter(|| { + let hits = + zlob_match_paths_indices(black_box(pat), &paths, ZlobFlags::RECOMMENDED).unwrap(); + let words = paths.len().div_ceil(64); + let mut bits = vec![0u64; words]; + for i in hits.to_iter() { + bits[i >> 6] |= 1u64 << (i & 63); + } + let count = (0..paths.len()) + .filter(|&i| (bits[i >> 6] >> (i & 63)) & 1 == 1) + .count(); + black_box(count); + }); + }); + + // (E) (D) but larger chunk + group.bench_function("compiled_chunked_4096_to_bitmap", |b| { + b.iter(|| { + let p = ZlobPattern::compile(black_box(pat), ZlobFlags::RECOMMENDED).unwrap(); + let mut mask = vec![false; paths.len()]; + for (chunk_idx, chunk) in paths.chunks(4096).enumerate() { + let base = chunk_idx * 4096; + let hits = p.match_indices(chunk, ZlobFlags::RECOMMENDED).unwrap(); + for i in hits.to_iter() { + mask[base + i] = true; + } + } + let count = (0..paths.len()).filter(|&i| mask[i]).count(); + black_box(count); + }); + }); + + group.finish(); +} + +/// Mixed-constraint pipeline: glob + ext. Compare pre-pass batch (current) vs +/// inline `ZlobPattern::matches` after the cheap ext check rejects items. +/// +/// Variables: ext rejection rate. Extreme cases reveal where each strategy wins. +fn bench_mixed_pipeline(c: &mut Criterion) { + let count = 100_000; + let owned = make_paths(count); + let paths: Vec<&str> = owned.iter().map(|s| s.as_str()).collect(); + let glob_pat = "**/*.{rs,ts,lua,md}"; + + // 4 ext sets: from very selective (1/10 paths kept) to permissive (kept all). + let scenarios: &[(&str, &[&str])] = &[ + ("ext_1of10", &["rs"]), + ("ext_4of10", &["rs", "ts", "lua", "md"]), + ( + "ext_8of10", + &["rs", "ts", "lua", "md", "toml", "go", "py", "c"], + ), + ( + "ext_all", + &["rs", "ts", "lua", "md", "toml", "go", "py", "c", "h", "txt"], + ), + ]; + + fn ext_match(name: &str, exts: &[&str]) -> bool { + exts.iter().any(|e| { + let bytes = name.as_bytes(); + let elen = e.len(); + bytes.len() > elen + 1 + && bytes[bytes.len() - elen - 1] == b'.' + && bytes[bytes.len() - elen..].eq_ignore_ascii_case(e.as_bytes()) + }) + } + + let mut group = c.benchmark_group("glob_mixed_100k"); + group.sample_size(50); + + for &(name, exts) in scenarios { + // (A) PRE-PASS: build bitmap for ALL paths, then per-item ext-then-bitmap. + let id_pre = BenchmarkId::new("prepass_bitmap", name); + group.bench_with_input(id_pre, &exts, |b, &exts| { + b.iter(|| { + let hits = + zlob_match_paths_indices(black_box(glob_pat), &paths, ZlobFlags::RECOMMENDED) + .unwrap(); + let mut mask = vec![false; paths.len()]; + for i in hits.to_iter() { + mask[i] = true; + } + let count = paths + .iter() + .enumerate() + .filter(|&(_, p)| ext_match(p, exts)) + .filter(|&(i, _)| mask[i]) + .count(); + black_box(count); + }); + }); + + // (B) INLINE: compile once, per-item ext check first, then matches() only on survivors. + let id_inline = BenchmarkId::new("inline_compiled", name); + group.bench_with_input(id_inline, &exts, |b, &exts| { + b.iter(|| { + let p = ZlobPattern::compile(black_box(glob_pat), ZlobFlags::RECOMMENDED).unwrap(); + let count = paths + .iter() + .filter(|path| ext_match(path, exts)) + .filter(|path| p.matches_default(path)) + .count(); + black_box(count); + }); + }); + } + + group.finish(); +} + +/// Compare hand-rolled `file_has_extension` byte compare vs compiling extensions +/// into a single brace glob `**/*.{rs,ts,lua,md}` and dispatching through zlob. +/// Both share the same per-item "filter then count" shape. +fn bench_extensions_vs_glob(c: &mut Criterion) { + let owned = make_paths(100_000); + let paths: Vec<&str> = owned.iter().map(|s| s.as_str()).collect(); + let exts = ["rs", "ts", "lua", "md"]; + let glob_pat = "**/*.{rs,ts,lua,md}"; + + fn ext_match(name: &str, exts: &[&str]) -> bool { + let bytes = name.as_bytes(); + exts.iter().any(|e| { + let elen = e.len(); + bytes.len() > elen + 1 + && bytes[bytes.len() - elen - 1] == b'.' + && bytes[bytes.len() - elen..].eq_ignore_ascii_case(e.as_bytes()) + }) + } + + let mut group = c.benchmark_group("ext_vs_glob_100k"); + group.sample_size(50); + + group.bench_function("file_has_extension_loop", |b| { + b.iter(|| { + let count = paths.iter().filter(|p| ext_match(p, &exts)).count(); + black_box(count); + }); + }); + + group.bench_function("compiled_brace_glob_inline", |b| { + b.iter(|| { + let p = ZlobPattern::compile(black_box(glob_pat), ZlobFlags::RECOMMENDED).unwrap(); + let count = paths.iter().filter(|path| p.matches_default(path)).count(); + black_box(count); + }); + }); + + group.bench_function("brace_glob_prepass_bitmap", |b| { + b.iter(|| { + let hits = + zlob_match_paths_indices(black_box(glob_pat), &paths, ZlobFlags::RECOMMENDED) + .unwrap(); + let mut mask = vec![false; paths.len()]; + for i in hits.to_iter() { + mask[i] = true; + } + let count = (0..paths.len()).filter(|&i| mask[i]).count(); + black_box(count); + }); + }); + + group.finish(); +} + +criterion_group!( + benches, + bench_glob_strategies, + bench_compiled_reuse, + bench_full_pipeline, + bench_mixed_pipeline, + bench_extensions_vs_glob +); +criterion_main!(benches); diff --git a/crates/fff-core/benches/memmem_bench.rs b/crates/fff-core/benches/memmem_bench.rs new file mode 100644 index 0000000..cacc786 --- /dev/null +++ b/crates/fff-core/benches/memmem_bench.rs @@ -0,0 +1,85 @@ +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use fff_search::simd_string_utils::memmem; +use std::path::Path; + +/// Load real source files from the repository as benchmark haystacks. +/// Falls back to concatenating all .rs files under crates/ if specific files are missing. +fn load_real_files() -> Vec<(&'static str, Vec)> { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); // crates/fff-core + let repo_root = Path::new(manifest_dir).parent().unwrap().parent().unwrap(); + + let files: &[(&str, &str)] = &[ + ("grep.rs/80KB", "crates/fff-core/src/grep.rs"), + ("file_picker.rs/53KB", "crates/fff-core/src/file_picker.rs"), + ("picker_ui.lua/96KB", "lua/fff/picker_ui.lua"), + ]; + + let mut result = Vec::new(); + for &(label, rel_path) in files { + let full_path = repo_root.join(rel_path); + if let Ok(data) = std::fs::read(&full_path) { + result.push((label, data)); + } + } + + // Also create a large synthetic file by concatenating all three + if result.len() == 3 { + let mut combined = Vec::new(); + for (_, data) in &result { + combined.extend_from_slice(data); + } + // Repeat to get ~1MB + let base = combined.clone(); + while combined.len() < 1024 * 1024 { + combined.extend_from_slice(&base); + } + combined.truncate(1024 * 1024); + result.push(("combined/1MB", combined)); + } + + result +} + +fn bench_memmem(c: &mut Criterion) { + let mut group = c.benchmark_group("simd_string_utils_memmem"); + + let files = load_real_files(); + assert!(!files.is_empty(), "No source files found for benchmarking"); + + // Needles chosen to exercise different false-positive rates: + // + // "hit" needles: strings that actually appear in these source files. + // "miss" needles: strings with common first-bytes (lots of false positives + // for memchr2) but that don't exist in any of the files. + let needles: &[(&str, &[u8])] = &[ + // Hits — real identifiers from the codebase + ("short/hit/fn", b"fn"), + ("short/hit/self", b"self"), + ("medium/hit", b"search_file"), + ("long/hit", b"content_cache_budget"), + // Misses — common first-bytes, guaranteed not in source + ("short/miss", b"zqxjv"), + ("medium/miss", b"fluxcapacitor"), + ("long/miss", b"quantum_entanglement_resolver"), + ]; + + for (file_label, haystack) in &files { + for &(needle_label, needle) in needles { + let needle_lower: Vec = needle.iter().map(|b| b.to_ascii_lowercase()).collect(); + let id = format!("{file_label}/{needle_label}"); + + group.bench_with_input( + BenchmarkId::new("find", &id), + &(haystack, &needle_lower), + |b, &(h, n)| { + b.iter(|| black_box(memmem::find(h, n))); + }, + ); + } + } + + group.finish(); +} + +criterion_group!(benches, bench_memmem); +criterion_main!(benches); diff --git a/crates/fff-core/benches/parse_bench.rs b/crates/fff-core/benches/parse_bench.rs new file mode 100644 index 0000000..cd6e8d2 --- /dev/null +++ b/crates/fff-core/benches/parse_bench.rs @@ -0,0 +1,180 @@ +use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main}; +use fff_query_parser::*; + +fn bench_parse_simple(c: &mut Criterion) { + let parser = QueryParser::default(); + + c.bench_function("parse_simple_text", |b| { + b.iter(|| parser.parse(black_box("hello world"))); + }); + + c.bench_function("parse_extension", |b| { + b.iter(|| parser.parse(black_box("*.rs"))); + }); + + c.bench_function("parse_text_with_extension", |b| { + b.iter(|| parser.parse(black_box("name *.rs"))); + }); +} + +fn bench_parse_complex(c: &mut Criterion) { + let parser = QueryParser::default(); + + c.bench_function("parse_complex_mixed", |b| { + b.iter(|| parser.parse(black_box("src name *.rs !test /lib/ status:modified"))); + }); + + c.bench_function("parse_glob", |b| { + b.iter(|| parser.parse(black_box("**/*.rs"))); + }); + + c.bench_function("parse_multiple_constraints", |b| { + b.iter(|| parser.parse(black_box("*.rs *.toml *.md !test !node_modules /src/"))); + }); +} + +fn bench_parse_realistic_queries(c: &mut Criterion) { + let parser = QueryParser::default(); + + let queries = vec![ + "file", + "test", + "mod.rs", + "src/*.rs", + "lib test", + "*.rs !test", + "src/lib/*.rs", + "/src/ name", + "status:modified *.rs", + "type:rust test !node_modules", + ]; + + let mut group = c.benchmark_group("realistic_queries"); + for query in queries.iter() { + group.throughput(Throughput::Bytes(query.len() as u64)); + group.bench_with_input(BenchmarkId::from_parameter(query), query, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + } + group.finish(); +} + +fn bench_parse_various_lengths(c: &mut Criterion) { + let parser = QueryParser::default(); + + let short = "*.rs"; + let medium = "src name *.rs !test"; + let long = "src lib test name *.rs *.toml !node_modules !test /src/ /lib/ status:modified"; + let very_long = + "a b c d e f g h i j k l m n o p q r s t u v w x y z *.rs *.toml *.md *.txt *.js"; + + let mut group = c.benchmark_group("query_lengths"); + + group.throughput(Throughput::Bytes(short.len() as u64)); + group.bench_with_input(BenchmarkId::new("short", short.len()), &short, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + + group.throughput(Throughput::Bytes(medium.len() as u64)); + group.bench_with_input(BenchmarkId::new("medium", medium.len()), &medium, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + + group.throughput(Throughput::Bytes(long.len() as u64)); + group.bench_with_input(BenchmarkId::new("long", long.len()), &long, |b, q| { + b.iter(|| parser.parse(black_box(q))); + }); + + group.throughput(Throughput::Bytes(very_long.len() as u64)); + group.bench_with_input( + BenchmarkId::new("very_long", very_long.len()), + &very_long, + |b, q| { + b.iter(|| parser.parse(black_box(q))); + }, + ); + + group.finish(); +} + +fn bench_config_comparison(c: &mut Criterion) { + let file_picker = QueryParser::new(FileSearchConfig); + let grep = QueryParser::new(GrepConfig); + + let query = "src name *.rs !test"; + + let mut group = c.benchmark_group("config_comparison"); + + group.bench_function("file_picker_config", |b| { + b.iter(|| file_picker.parse(black_box(query))); + }); + + group.bench_function("grep_config", |b| { + b.iter(|| grep.parse(black_box(query))); + }); + + group.finish(); +} + +fn bench_constraint_types(c: &mut Criterion) { + let parser = QueryParser::default(); + + let mut group = c.benchmark_group("constraint_types"); + + group.bench_function("extension", |b| { + b.iter(|| parser.parse(black_box("*.rs"))); + }); + + group.bench_function("glob", |b| { + b.iter(|| parser.parse(black_box("**/*.rs"))); + }); + + group.bench_function("exclude", |b| { + b.iter(|| parser.parse(black_box("!test"))); + }); + + group.bench_function("path_segment", |b| { + b.iter(|| parser.parse(black_box("/src/"))); + }); + + group.bench_function("git_status", |b| { + b.iter(|| parser.parse(black_box("status:modified"))); + }); + + group.bench_function("file_type", |b| { + b.iter(|| parser.parse(black_box("type:rust"))); + }); + + group.finish(); +} + +fn bench_worst_case(c: &mut Criterion) { + let parser = QueryParser::default(); + + // Worst case: many constraints that all need to be checked + let worst_case = "a b c d e f g h i j k l m n o p q r s t u v w x y z"; + + c.bench_function("worst_case_many_text_tokens", |b| { + b.iter(|| parser.parse(black_box(worst_case))); + }); + + // Many constraints + let many_constraints = "*.rs *.toml *.md *.txt *.js *.ts *.jsx *.tsx *.vue *.svelte"; + + c.bench_function("worst_case_many_constraints", |b| { + b.iter(|| parser.parse(black_box(many_constraints))); + }); +} + +criterion_group!( + benches, + bench_parse_simple, + bench_parse_complex, + bench_parse_realistic_queries, + bench_parse_various_lengths, + bench_config_comparison, + bench_constraint_types, + bench_worst_case, +); + +criterion_main!(benches); diff --git a/crates/fff-core/build.rs b/crates/fff-core/build.rs new file mode 100644 index 0000000..1e4dfaa --- /dev/null +++ b/crates/fff-core/build.rs @@ -0,0 +1,45 @@ +fn main() { + // Opt-in cfg for the long-running randomized stress tests + // used by tests/fuzz_git_watcher_stress.rs + println!("cargo::rustc-check-cfg=cfg(stress)"); + + // When the `zlob` feature is enabled (Zig-compiled C library): + // On Windows MSVC, explicitly link the C runtime libraries. + // Zig-compiled static libraries don't emit /DEFAULTLIB directives for the + // MSVC CRT, so symbols like strcmp, memcpy etc. would be unresolved. + if std::env::var("CARGO_FEATURE_ZLOB").is_ok() { + if !zig_available() { + panic!( + "The `zlob` feature is enabled but Zig is not installed. \ + Install Zig (https://ziglang.org/download/) or build without \ + `--features zlob`." + ); + } + + let target = std::env::var("TARGET").unwrap_or_default(); + if target.contains("windows") && target.contains("msvc") { + println!("cargo:rustc-link-lib=msvcrt"); + println!("cargo:rustc-link-lib=ucrt"); + println!("cargo:rustc-link-lib=vcruntime"); + } + } else if std::env::var("CARGO_PRIMARY_PACKAGE").is_ok() && zig_available() { + // Hint: if Zig is available but the zlob feature wasn't enabled, + // let the developer know they can get faster glob matching. + // Only emit this hint when this crate is the primary package to + // avoid noisy warnings for downstream consumers. + println!( + "cargo:warning=Zig detected but `zlob` feature is not enabled. \ + Build with `--features zlob` for faster glob matching." + ); + } +} + +/// Probe the system for a working Zig installation. +fn zig_available() -> bool { + std::process::Command::new("zig") + .arg("version") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .is_ok() +} diff --git a/crates/fff-core/src/background_watcher.rs b/crates/fff-core/src/background_watcher.rs new file mode 100644 index 0000000..72e1b9f --- /dev/null +++ b/crates/fff-core/src/background_watcher.rs @@ -0,0 +1,853 @@ +use crate::constants::MAX_OVERFLOW_FILES; +use crate::error::Error; +use crate::file_picker::FFFMode; +use crate::git_status_worker::GitStatusWorker; +use crate::shared::{SharedFilePicker, SharedFrecency}; +use crate::sort_buffer::sort_with_buffer; +use git2::Repository; +use notify::event::{AccessKind, AccessMode}; +use notify::{Config, EventKind, EventKindMask, RecursiveMode}; +use notify_debouncer_full::{DebounceEventResult, DebouncedEvent, NoCache, new_debouncer_opt}; +use parking_lot::Mutex; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::sync::mpsc; +use std::time::Duration; +use tracing::{Level, debug, error, info, warn}; + +type Debouncer = notify_debouncer_full::Debouncer; + +/// Owns the file-system watcher and guarantees that all background threads +/// are fully joined before `stop()` / `Drop` returns. +pub struct BackgroundWatcher { + debouncer: Arc>>, + watch_tx: Option>, + owner_thread: Option>, +} + +const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50); +/// On macOS, each `watch()` call creates a separate FSEventStream. When the +/// number of directories exceeds this threshold we fall back to a single +/// recursive watch to avoid exhausting the per-process stream limit. +const MAX_MACOS_NONRECURSIVE_WATCHES: usize = 4096; +/// Minimum seconds between frecency tracks of the same file in AI mode. +/// Prevents score inflation from rapid burst edits by AI agents. +const AI_MODE_COOLDOWN_SECS: u64 = 5 * 60; + +impl BackgroundWatcher { + #[allow(clippy::too_many_arguments)] + pub fn new( + base_path: PathBuf, + git_workdir: Option, + shared_picker: SharedFilePicker, + shared_frecency: SharedFrecency, + mode: FFFMode, + enable_fs_root_scanning: bool, + enable_home_dir_scanning: bool, + git_status_worker: Arc, + trace_span: tracing::Span, + ) -> Result { + info!( + "Initializing background watcher for path: {}, mode: {:?}", + base_path.display(), + mode, + ); + + // by default we do not want to allow users to search their FS root, this is very error prone + // though some consumers would specifically allow that e.g. unikernels, windows disc + // partition or sub file systems. By default - fail, unless user permits + let is_fs_root = base_path.parent().is_none(); + // use rust's path api for maximum reliability of the comparison + let is_home_dir = Some(&base_path) == dirs::home_dir().as_ref(); + + if (is_fs_root && !enable_fs_root_scanning) || (is_home_dir && !enable_home_dir_scanning) { + return Err(Error::FilesystemRoot(base_path)); + } + + // macOS: always use a single recursive FSEvent stream. + // Per-dir NonRecursive watches create one FSEvent stream per dir. + // The per-process FSEvent cap is lower than expected in practice + // (4096 per process, but FFF usually is running within code editors), + // and each failed `watch()` after the cap blocks ~40 ms on kernel retry. + // Yes we pay for filtering events on handler phase but it is usable + // + // Windows doesn't seem to have a hard cap, but in practice non recursive watching + // does a way worse job and often looses events which is not an option for us. + // + // Linux keeps the per-dir NonRecursive strategy: inotify has no + // kernel-level watcher recursion, so we have to manually watch every single interested + // directory for watch events which is in practice stable and fast if system has enough + // spare watcher (configurable by the user, usually 100k - 1m) + let use_recursive = cfg!(any(target_os = "macos", target_os = "windows")); + + let (watch_tx, watch_rx) = mpsc::channel::(); + let watch_tx_for_debouncer = watch_tx.clone(); + + let owner_weak_picker = shared_picker.weaken(); + let owner_git_workdir = git_workdir.clone(); + let owner_git_worker = Arc::clone(&git_status_worker); + + let debouncer = Self::create_debouncer( + base_path, + git_workdir, + shared_picker, + shared_frecency, + mode, + use_recursive, + watch_tx_for_debouncer, + git_status_worker, + )?; + + info!("Background file watcher initialized successfully"); + + // debouncer is shared with the owner thread, once it's dropped the thread is closed + let debouncer = Arc::new(Mutex::new(Some(debouncer))); + // Only the Linux per-dir-watch branch needs this clone; on other + // platforms the owner thread never touches the debouncer. + #[cfg(target_os = "linux")] + let owner_debouncer = Arc::clone(&debouncer); + + let owner_span = trace_span.clone(); + let owner_thread = std::thread::Builder::new() + .name("fff-watcher-own".into()) + .spawn(move || { + let _g = owner_span.enter(); + while let Ok(dir) = watch_rx.recv() { + // if the picker is dropped we do need to exit the loop + let Some(strong_picker) = owner_weak_picker.upgrade() else { + break; + }; + + // Only inotify (Linux) has no kernel-level recursion, so + // it's the only platform that needs a per-subdir watch to + // be registered at runtime. macOS FSEvents and Windows + // ReadDirectoryChangesW are already watching recursively + // from the base path (see `create_debouncer`), and + // registering a second overlapping stream there produces + // duplicate/out-of-order events. + #[cfg(target_os = "linux")] + { + // Register the new directory with the debouncer, then + // drop the mutex BEFORE doing picker-side work — see + // the comment on `BackgroundWatcher::stop` for the + // lock-ordering rationale. + let mut guard = owner_debouncer.lock(); + let Some(debouncer) = guard.as_mut() else { + break; + }; + + if let Err(e) = debouncer.watch(&dir, RecursiveMode::NonRecursive) { + warn!( + ?e, + dir = %dir.display(), + "Failed to init watcher for new directory" + ); + } + } + + track_files_from_new_directories( + &dir, + &strong_picker, + &owner_git_workdir, + &owner_git_worker, + ); + + // Transient strong ref drops here, back + // to weak-only before the next `recv()`. + } + + tracing::info!("Background watcher is stopped"); + }) + .expect("failed to spawn fff-watcher-owner thread"); + + Ok(Self { + debouncer, + watch_tx: Some(watch_tx), + owner_thread: Some(owner_thread), + }) + } + + #[allow(clippy::too_many_arguments)] + fn create_debouncer( + base_path: PathBuf, + git_workdir: Option, + shared_picker: SharedFilePicker, + shared_frecency: SharedFrecency, + mode: FFFMode, + use_recursive: bool, + watch_tx: mpsc::Sender, + git_status_worker: Arc, + ) -> Result { + let config = Config::default() + // do not follow symlinks as then notifiers spawns a bunch of events for symlinked + // files that could be git ignored, we have to property differentiate those and if + // the file was edited through a + .with_follow_symlinks(false) + // only the actual modification events, ignore the open syscals that we can generate by + // our own grep calls and preview window rendering + .with_event_kinds(EventKindMask::CORE); + + let git_workdir_for_handler = git_workdir.clone(); + let base_path_for_handler = base_path.clone(); + let shared_picker_for_watching = shared_picker.clone(); + let file_picker = shared_picker.weaken(); + let mut debouncer = new_debouncer_opt( + DEBOUNCE_TIMEOUT, + Some(DEBOUNCE_TIMEOUT / 2), // tick rate for the event span + { + move |result: DebounceEventResult| match result { + Ok(events) => { + let Some(file_picker) = file_picker.upgrade() else { + return; + }; + + let new_dirs = handle_debounced_events( + mode, + events, + &base_path_for_handler, + &git_workdir_for_handler, + &file_picker, + &shared_frecency, + &git_status_worker, + ); + + // every new directory created has to be reflected in the picker state + for dir in new_dirs { + if let Err(e) = watch_tx.send(dir) { + error!(?e, "Failed to send directory update error"); + } + } + } + Err(errors) => { + error!("File watcher errors: {:?}", errors); + } + } + }, + // There is an issue with recommended cache implementation on macos + // it keeps track of all the files added to the watcher which is not a problem + // for us because any rename to the file will anyway require the removing from the + // ordedred index and adding it back with the new name + NoCache::new(), + config, + )?; + + if use_recursive { + // if the platform supports native watcher recursion + debouncer.watch(base_path.as_path(), RecursiveMode::Recursive)?; + info!( + "File watcher initialized with single recursive watch on {} \ + (exceeded threshold of {})", + base_path.display(), + MAX_MACOS_NONRECURSIVE_WATCHES, + ); + } else { + debouncer.watch(base_path.as_path(), RecursiveMode::NonRecursive)?; + + const MAX_CONSECUTIVE_WATCH_FAILURES: usize = 16; + + let mut watched = 0usize; + let mut consecutive_failures = 0usize; + + // `inotify` is fast-fail: on ENOSPC it returns + // immediately, no kernel retry loop, so holding this lock is free + if let Some(guard) = shared_picker_for_watching.read().ok() + && let Some(picker) = guard.as_ref() + { + use std::ops::ControlFlow; + picker.for_each_dir(|dir| { + match debouncer.watch(dir, RecursiveMode::NonRecursive) { + Ok(()) => { + watched += 1; + consecutive_failures = 0; + ControlFlow::Continue(()) + } + Err(e) => { + consecutive_failures += 1; + if consecutive_failures <= 4 { + warn!("Failed to watch directory {}: {}", dir.display(), e); + } + + if consecutive_failures >= MAX_CONSECUTIVE_WATCH_FAILURES { + warn!( + consecutive_failures, + watched, + "Giving up setting file watcher for all the directories. Check if your system has enough fs watchers limit." + ); + + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + } + } + }); + } + + tracing::info!( + ?watched, + path = ?base_path.display(), + "File watcher initialized" + ); + } + + // The .git directory is excluded from the file list but we still need + // to observe changes that affect git status (staging, unstaging, + // committing, branch switches, merges, etc) + watch_git_status_paths(&mut debouncer, git_workdir.as_ref()); + + Ok(debouncer) + } + + /// Signals the background watcher threads to shut down, doesn't guarantee to deallocate immediately + pub fn stop(&mut self) { + self.watch_tx.take(); + if let Some(debouncer) = self.debouncer.lock().take() { + debouncer.stop_nonblocking(); + } + + self.owner_thread.take(); + + info!("Background file watcher stop signaled"); + } + + pub(crate) fn request_watch_dir(&self, dir: PathBuf) -> bool { + match self.watch_tx.as_ref() { + Some(tx) => tx.send(dir).is_ok(), + None => false, + } + } +} + +impl Drop for BackgroundWatcher { + fn drop(&mut self) { + self.stop(); + } +} + +#[tracing::instrument(name = "fs_events", skip(events, shared_picker, shared_frecency, git_status_worker), level = Level::DEBUG)] +fn handle_debounced_events( + mode: FFFMode, + events: Vec, + base_path: &Path, + git_workdir: &Option, + shared_picker: &SharedFilePicker, + shared_frecency: &SharedFrecency, + git_status_worker: &Arc, +) -> Vec { + // this will be called very often, we have to minimiy the lock time for file picker + let repo = git_workdir.as_ref().and_then(|p| Repository::open(p).ok()); + // Prefer the walker's own ignore rules (zlob); grab a cheap Arc clone once + // per batch so we don't hold the picker lock during filtering. + let walker_rules = shared_picker + .read() + .ok() + .and_then(|g| g.as_ref().and_then(|p| p.ignore_rules())); + let filter = IgnoreFilter::new(base_path, walker_rules, repo.as_ref()); + let mut need_full_rescan = false; + let mut need_full_git_rescan = false; + let mut paths_to_remove = Vec::new(); + let mut dirs_to_remove: Vec = Vec::new(); + let mut paths_to_add_or_modify = Vec::new(); + let mut new_dirs_to_watch = Vec::new(); + let mut affected_paths_count = 0usize; + + for debounced_event in &events { + // It is very important to not react to the access errors because we inevitably + // gonna trigger the sync by our own preview or other unnecessary noise + if matches!( + debounced_event.event.kind, + EventKind::Access( + AccessKind::Read + | AccessKind::Open(_) + | AccessKind::Close(AccessMode::Read | AccessMode::Execute) + ) + ) { + continue; + } + + // When macOS FSEvents (or other backends) overflow their event buffer, the kernel + // drops individual events and emits a rescan flag telling us to re-scan the subtree + if debounced_event.event.need_rescan() { + if debounced_event.event.paths.len() < 16 // this should be usually one event + && debounced_event + .paths + .iter() + // but we are smart enough and not falling into the paths + .all(|p| should_include_file(p, &filter)) + { + break; + } + + warn!( + "Received rescan event for paths {:?}, triggering full rescan", + debounced_event.event.paths + ); + need_full_rescan = true; + break; + } + + tracing::debug!(event = ?debounced_event.event, "Processing FS event"); + for path in &debounced_event.event.paths { + if matches!( + path.file_name().and_then(|f| f.to_str()), + Some(".ignore") | Some(".gitignore") + ) { + info!( + "Detected change in ignore definition file: {}", + path.display() + ); + + need_full_rescan = true; + break; + } + + if is_dotgit_change_affecting_status(path, &repo) { + need_full_git_rescan = true; + } + + if is_git_file(path) { + continue; + } + + // Use a combination of event kind and filesystem state to decide + // whether a path is an addition/modification or a removal. + // + // We cannot rely on `path.exists()` alone because: + // - A freshly created file might not be visible yet (race). + // - macOS FSEvents uses Modify(Name(Any)) for both rename-in + // and rename-out, so we must stat the path to disambiguate. + // + // We cannot rely on event kind alone because: + // - Remove events are not always emitted (macOS often sends + // Modify(Name(Any)) instead of Remove). + let is_removal = matches!(debounced_event.event.kind, EventKind::Remove(_)); + + // Directory-level remove: both fsevents and inotify delivers a single + // `Remove(Folder)` event for a whole directory tree (e.g. + // after `git reset --hard` wipes a dir full of staged-but- + // uncommitted files). + let is_folder_removal = matches!( + debounced_event.event.kind, + EventKind::Remove(notify::event::RemoveKind::Folder) + ); + + if is_folder_removal { + dirs_to_remove.push(path.to_path_buf()); + } else if is_removal || !path.exists() { + paths_to_remove.push(path.as_path()); + } else if path.is_dir() { + if !is_path_ignored(path, &filter) { + new_dirs_to_watch.push(path.to_path_buf()); + } + } else { + // For additions/modifications, still filter gitignored files. + if should_include_file(path, &filter) { + paths_to_add_or_modify.push(path.as_path()); + } + } + } + + affected_paths_count += debounced_event.event.paths.len(); + if affected_paths_count > MAX_OVERFLOW_FILES { + warn!( + ?affected_paths_count, + max = MAX_OVERFLOW_FILES, + "Too many affected paths in a single batch, triggering full rescan", + ); + + need_full_rescan = true; + break; + } + + if need_full_rescan { + break; + } + } + + if need_full_rescan { + info!(?affected_paths_count, "Triggering full rescan"); + if let Err(e) = shared_picker.trigger_full_rescan_async(shared_frecency) { + error!("Failed to trigger full rescan: {:?}", e); + } + return Vec::new(); + } + + // It's important to get the allocated sort + sort_with_buffer(paths_to_add_or_modify.as_mut_slice(), |a, b| { + a.as_os_str().cmp(b.as_os_str()) + }); + paths_to_add_or_modify.dedup_by(|a, b| a.as_os_str().eq(b.as_os_str())); + + info!( + "Event processing summary: {} to remove, {} dirs to remove, {} to add/modify, {} new dirs", + paths_to_remove.len(), + dirs_to_remove.len(), + paths_to_add_or_modify.len(), + new_dirs_to_watch.len() + ); + + if paths_to_remove.is_empty() + && dirs_to_remove.is_empty() + && paths_to_add_or_modify.is_empty() + && !need_full_git_rescan + { + debug!("No file index changes to apply"); + return new_dirs_to_watch; + } + + let mut files_to_update_git_status = Vec::new(); + let mut need_full_rescan = false; + let mut overflow_count = 0; + + if !paths_to_remove.is_empty() + || !dirs_to_remove.is_empty() + || !paths_to_add_or_modify.is_empty() + { + debug!( + "Applying file index changes: {} to remove, {} dirs to remove, {} to add/modify", + paths_to_remove.len(), + dirs_to_remove.len(), + paths_to_add_or_modify.len(), + ); + + let Ok(mut guard) = shared_picker.write() else { + error!("Failed to acquire file picker write lock"); + return new_dirs_to_watch; + }; + let Some(ref mut picker) = *guard else { + error!("File picker not initialized"); + return new_dirs_to_watch; + }; + + for dir in &dirs_to_remove { + let count = picker.remove_all_files_in_dir(dir); + debug!("remove_all_files_in_dir({:?}) -> {} files", dir, count); + } + + for path in &paths_to_remove { + let removed = picker.remove_file_by_path(path); + debug!("remove_file_by_path({:?}) -> {}", path, removed); + } + + files_to_update_git_status.reserve(paths_to_add_or_modify.len()); + for path in &paths_to_add_or_modify { + if picker.handle_create_or_modify(path).is_some() { + files_to_update_git_status.push(path.to_path_buf()); + } else { + need_full_rescan = true; + } + } + + overflow_count = picker.get_overflow_files().len(); + } + + info!( + files_updated = files_to_update_git_status.len(), + overflow_count, "File index changes applied", + ); + if need_full_rescan || overflow_count > MAX_OVERFLOW_FILES { + info!("Watcher faced limit of index overflow. Triggering rescan"); + if let Err(e) = shared_picker.trigger_full_rescan_async(shared_frecency) { + error!("Failed to trigger full rescan: {:?}", e); + } + } + + // AI mode: auto-track frecency for all modified/created files. + // Uses a 5-minute cooldown per file to prevent score inflation from rapid + // burst edits (AI agents often edit the same file many times in minutes). + // This runs after apply_changes so the picker write lock is released. + if mode.is_ai() && !paths_to_add_or_modify.is_empty() { + let mut tracked_count = 0usize; + if let Ok(frecency_guard) = shared_frecency.read() + && let Some(ref frecency) = *frecency_guard + { + for path in &paths_to_add_or_modify { + // Skip if this file was tracked less than 5 minutes ago + let should_track = match frecency.seconds_since_last_access(path) { + Ok(Some(secs)) => secs >= AI_MODE_COOLDOWN_SECS, + Ok(None) => true, // Never tracked before + Err(_) => true, // DB error, track anyway + }; + if !should_track { + continue; + } + + if let Err(e) = frecency.track_access(path) { + error!("Failed to track frecency for {:?}: {:?}", path, e); + } else { + tracked_count += 1; + } + } + if tracked_count > 0 { + info!("AI mode: tracked frecency for {} files", tracked_count); + } + } + + // Update in-memory frecency scores for tracked files + if tracked_count > 0 + && let Ok(mut picker_guard) = shared_picker.write() + && let Some(ref mut picker) = *picker_guard + && let Ok(frecency_guard) = shared_frecency.read() + && let Some(ref frecency) = *frecency_guard + { + for path in &paths_to_add_or_modify { + let _ = picker.update_single_file_frecency(path, frecency); + } + } + } + + // do not try to update the paths if we anyway going to rescan everything from scratch + // no repo => no consumer thread, so don't accumulate paths nobody will drain + if !need_full_rescan && repo.is_some() { + if need_full_git_rescan { + // A full git rescan re-reads every tracked path (including ones that just + // went clean after a commit), so it already subsumes the per-path update. + git_status_worker.request_full_rescan(); + } else if !files_to_update_git_status.is_empty() { + git_status_worker.enqueue_paths(files_to_update_git_status); + } + } + + new_dirs_to_watch +} + +/// After registering a watch on a newly created directory, list its +/// immediate children and add any files to the picker. +fn track_files_from_new_directories( + dir: &Path, + shared_picker: &SharedFilePicker, + git_workdir: &Option, + git_status_worker: &Arc, +) { + let Ok(entries) = std::fs::read_dir(dir) else { + return; + }; + + let repo = git_workdir.as_ref().and_then(|p| Repository::open(p).ok()); + // Prefer the walker's ignore rules; read base_path + rules from the picker. + let (base_path, walker_rules) = match shared_picker.read().ok().and_then(|g| { + g.as_ref() + .map(|p| (p.base_path().to_path_buf(), p.ignore_rules())) + }) { + Some(pair) => pair, + None => return, + }; + + let filter = IgnoreFilter::new(&base_path, walker_rules, repo.as_ref()); + let mut files_to_add = Vec::new(); + + for entry in entries.flatten() { + if entry.file_type().is_ok_and(|ft| ft.is_file()) { + let path = entry.path(); + if should_include_file(&path, &filter) { + files_to_add.push(path); + } + } + } + + if files_to_add.is_empty() { + return; + } + + let added = files_to_add.len(); + + { + let Ok(mut guard) = shared_picker.write() else { + return; + }; + + let Some(ref mut picker) = *guard else { + return; + }; + + for path in &files_to_add { + picker.handle_create_or_modify(path); + } + } + + if repo.is_some() { + git_status_worker.enqueue_paths(files_to_add); + } + + debug!( + "Injected {} existing files from new directory {}", + added, + dir.display(), + ); +} + +fn should_include_file(path: &Path, filter: &IgnoreFilter) -> bool { + // Directories are not indexed — only regular files (and symlinks to files). + if path.is_dir() { + return false; + } + !filter.is_ignored(path) +} + +#[inline] +fn is_path_ignored(path: &Path, filter: &IgnoreFilter) -> bool { + filter.is_ignored(path) +} + +struct IgnoreFilter<'a> { + base_path: &'a Path, + /// Reusable ignore rules from the last walk (zlob backend only). + rules: Option>, + /// libgit2 repo, consulted only when `rules` is `None`. Borrowed from the + /// caller's repo (also used for git-status queries) to avoid re-opening. + repo: Option<&'a Repository>, +} + +impl<'a> IgnoreFilter<'a> { + fn new( + base_path: &'a Path, + rules: Option>, + repo: Option<&'a Repository>, + ) -> Self { + Self { + base_path, + rules, + repo, + } + } + + /// Whether `path` (absolute) is ignored. + fn is_ignored(&self, path: &Path) -> bool { + if let Some(rules) = self.rules.as_ref() { + let Ok(rel) = path.strip_prefix(self.base_path) else { + return false; + }; + // `IgnoreRules::is_ignored` enumerates every ancestor .gitignore + // layer internally, so a leaf under an ignored directory (rule + // `build/`, path `build/out.rs`) is caught in one call. + return rules.is_ignored(rel); + } + match self.repo { + Some(repo) => repo.is_path_ignored(path) == Ok(true), + // No repo and no rules: fall back to the non-code-dir heuristic. + None => crate::ignore::is_non_code_directory(path), + } + } +} + +#[inline] +pub(crate) fn is_git_file(path: &Path) -> bool { + // it could be in submodule + path.components() + .any(|component| component.as_os_str() == ".git") +} + +fn is_dotgit_change_affecting_status(changed: &Path, repo: &Option) -> bool { + let Some(repo) = repo.as_ref() else { + return false; + }; + + let git_dir = repo.path(); + + if let Ok(path_in_git_dir) = changed.strip_prefix(git_dir) { + // Only react to changes that rewrite the worktree state: commits, + // staging, checkouts, merges, conflict resolution. Ref-only updates + // under refs/ (fetch, push, tag writes, pack-refs) do not change + // which files are modified/untracked, so we deliberately skip them + // watching refs/ recursively would cost one inotify watch per ref + // namespace on repos with many branches/remotes. + if path_in_git_dir == Path::new("index") || path_in_git_dir == Path::new("index.lock") { + return true; + } + + if path_in_git_dir == Path::new("HEAD") { + return true; + } + + // some of the git ops are not involving nethier index nor HEAD change, or sometimes + // index updates can arrive too late after the change - that's why we track the log + // the actual user action, once user + if path_in_git_dir == Path::new("logs/HEAD") { + return true; + } + + if let Some(fname) = path_in_git_dir.file_name().and_then(|f| f.to_str()) + && matches!(fname, "MERGE_HEAD" | "CHERRY_PICK_HEAD" | "REVERT_HEAD") + { + return true; + } + } + + false +} + +fn watch_git_status_paths(debouncer: &mut Debouncer, git_workdir: Option<&PathBuf>) { + let Some(workdir) = git_workdir else { + return; + }; + + let git_dir = workdir.join(".git"); + if !git_dir.is_dir() { + return; + } + + // We have tried to be smart about the internal git state but + // it appeared more harmful that it's worth it, so we just watch + // for the most obvious paths like HEAD, MERGE_HEAD, index.lock + if let Err(e) = debouncer.watch(&git_dir, RecursiveMode::NonRecursive) { + warn!("Failed to watch .git directory: {}", e); + } + + // `.git` above is non-recursive, so on Linux (per-dir inotify watches) + // events for `logs/HEAD` — the commit-finished signal used by + // `is_dotgit_change_affecting_status` — would never be delivered without + // watching `.git/logs` itself. On macOS/Windows the recursive base watch + // already covers it; an extra watch is harmless there. + let logs_dir = git_dir.join("logs"); + if logs_dir.is_dir() + && let Err(e) = debouncer.watch(&logs_dir, RecursiveMode::NonRecursive) + { + warn!("Failed to watch .git/logs directory: {}", e); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dotgit_status_filter_matches_worktree_state_changes() { + let tmp = tempfile::tempdir().unwrap(); + let repo = git2::Repository::init(tmp.path()).unwrap(); + let git_dir = repo.path().to_path_buf(); + let repo = Some(repo); + + let affecting = ["index", "index.lock", "HEAD", "logs/HEAD", "MERGE_HEAD"]; + for p in affecting { + assert!( + is_dotgit_change_affecting_status(&git_dir.join(p), &repo), + "{p} must trigger a git status rescan" + ); + } + + // Ref-only updates (fetch/push/tags) and commit scratch files must not. + let non_affecting = [ + "refs/heads/main", + "refs/heads/main.lock", + "logs/refs/remotes/origin/main", + "COMMIT_EDITMSG", + "packed-refs", + ]; + for p in non_affecting { + assert!( + !is_dotgit_change_affecting_status(&git_dir.join(p), &repo), + "{p} must NOT trigger a git status rescan" + ); + } + + // Worktree paths outside .git never match. + assert!(!is_dotgit_change_affecting_status( + &tmp.path().join("src/main.rs"), + &repo + )); + assert!(!is_dotgit_change_affecting_status( + &git_dir.join("index"), + &None + )); + } +} diff --git a/crates/fff-core/src/bigram_filter.rs b/crates/fff-core/src/bigram_filter.rs new file mode 100644 index 0000000..6ce2a31 --- /dev/null +++ b/crates/fff-core/src/bigram_filter.rs @@ -0,0 +1,1227 @@ +use crate::constants::MAX_INDEXABLE_FILE_SIZE; +use ahash::AHashMap; +use rayon::iter::{IndexedParallelIterator, ParallelIterator}; +use rayon::slice::ParallelSlice; +use std::cell::UnsafeCell; +use std::sync::OnceLock; +use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering}; + +use crate::{FileItem, constants}; + +/// Maximum number of distinct bigrams tracked in the inverted index. +/// 95 printable ASCII chars (32..=126) after lowercasing → ~70 distinct → 4900 possible. +/// We cap at 5000 to cover all printable bigrams with margin. +/// 5000 columns × 62.5KB (500k files) = 305MB. For 50k files: 30MB. +const MAX_BIGRAM_COLUMNS: usize = 5000; + +/// Sentinel value: bigram has no allocated column. +const NO_COLUMN: u16 = u16::MAX; + +/// 1024 × u64 = 8 KB covers all 65536 possible bigram keys. +const SEEN_WORDS: usize = 1024; + +/// Content size where the branchless two-pass `add_long_content` overtakes +/// the single-pass `add_short_content`: ~-35% on 4 KB files, but its fixed +/// flush scan dominates files under ~1 KB. See bigram_bench `bigram_build`. +const LONG_CONTENT_MIN_LEN: usize = 1024; + +thread_local! { + static NORM_BUF: std::cell::RefCell> = + std::cell::RefCell::new(Vec::with_capacity(4096)); +} + +/// Temporary sync dense builder for the bigram index. +/// Builds from the many threads reading file contents in parallel +pub struct BigramIndexBuilder { + // we use lookup as atomics only in the builder because it is filled by the rayon threads + // the actual index uses pure u16 for the allocations + lookup: Vec, + /// Flat bitset data, materialised on first use. + col_data: OnceLock>>, + next_column: AtomicU16, + words: usize, + file_count: usize, + populated: AtomicUsize, +} + +// SAFETY: `col_data`'s interior mutability is coordinated via disjoint +// `word_idx` ranges (word-aligned file partitioning in the driver), so +// concurrent access is safe despite the `UnsafeCell`. See builder doc. +unsafe impl Sync for BigramIndexBuilder {} + +impl BigramIndexBuilder { + pub fn new(file_count: usize) -> Self { + let words = file_count.div_ceil(64); + let mut lookup = Vec::with_capacity(65536); + lookup.resize_with(65536, || AtomicU16::new(NO_COLUMN)); + Self { + lookup, + col_data: OnceLock::new(), + next_column: AtomicU16::new(0), + words, + file_count, + populated: AtomicUsize::new(0), + } + } + + /// Lazily materialise the full `MAX_BIGRAM_COLUMNS * words` bitset + /// on first access. + #[inline(always)] + fn col_data_cell(&self) -> &UnsafeCell> { + self.col_data.get_or_init(|| { + let total = MAX_BIGRAM_COLUMNS * self.words; + UnsafeCell::new(vec![0u64; total].into_boxed_slice()) + }) + } + + /// Raw pointer to the start of the bitset slab. Used for in-place + /// `|=` writes under the partitioning invariant. + #[inline(always)] + fn col_data_ptr(&self) -> *mut u64 { + unsafe { (*self.col_data_cell().get()).as_mut_ptr() } + } + + #[inline] + fn get_or_alloc_column(&self, key: u16) -> u16 { + let current = self.lookup[key as usize].load(Ordering::Relaxed); + if current != NO_COLUMN { + return current; + } + let new_col = self.next_column.fetch_add(1, Ordering::Relaxed); + if new_col >= MAX_BIGRAM_COLUMNS as u16 { + return NO_COLUMN; + } + + match self.lookup[key as usize].compare_exchange( + NO_COLUMN, + new_col, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_) => new_col, + Err(existing) => existing, + } + } + + /// Test/bench accessor for a column's raw bitset words. Assumes the + /// caller has joined all writers (no concurrent mutation). + #[cfg(test)] + fn column_bitset(&self, col: u16) -> &[u64] { + let start = col as usize * self.words; + let slab = unsafe { &*self.col_data_cell().get() }; + &slab[start..start + self.words] + } + + #[doc(hidden)] // `pub` (via `#[doc(hidden)]`) only for benchmarking + pub fn add_file_content(&self, skip_builder: &Self, file_idx: usize, content: &[u8]) { + if content.len() < 2 { + return; + } + + debug_assert!(file_idx < self.file_count); + let word_idx = file_idx / 64; + let bit_mask = 1u64 << (file_idx % 64); + + NORM_BUF.with_borrow_mut(|buf| { + let len = content.len(); + if buf.len() < len { + buf.resize(len.next_power_of_two().max(4096), 0); + } + + normalize_bytes(content, &mut buf[..len]); + let n = &buf[..len]; + + // Both paths record the identical bigram set; the split exists + // purely for speed (see LONG_CONTENT_MIN_LEN). + if len >= LONG_CONTENT_MIN_LEN { + self.add_long_content(skip_builder, n, word_idx, bit_mask); + } else { + self.add_short_content(skip_builder, n, word_idx, bit_mask); + } + }); + + self.populated.fetch_add(1, Ordering::Relaxed); + skip_builder.populated.fetch_add(1, Ordering::Relaxed); + } + + // Branchless two-pass: set every pair in stack-local bitmaps, including + // pairs touching the 0 sentinel — flush_seen masks those out. ~-35% vs + // the single pass on 4 KB files. + #[inline(always)] + fn add_long_content(&self, skip_builder: &Self, n: &[u8], word_idx: usize, bit_mask: u64) { + // Stack-local dedup bitsets: 1024 × u64 = 8 KB each, covers all 65536 + // bigram keys. Has to fit in L1 cache. + let mut seen_consec = [0u64; SEEN_WORDS]; + let mut seen_skip = [0u64; SEEN_WORDS]; + + let mut n0 = n[0]; + let mut n1 = n[1]; + + let key = (n0 as usize) << 8 | n1 as usize; + // SAFETY: key < 65536, so key >> 6 < 1024 = SEEN_WORDS. + unsafe { *seen_consec.get_unchecked_mut(key >> 6) |= 1u64 << (key & 63) }; + + for &cur in &n[2..] { + let ck = (n1 as usize) << 8 | cur as usize; + let sk = (n0 as usize) << 8 | cur as usize; + unsafe { + *seen_consec.get_unchecked_mut(ck >> 6) |= 1u64 << (ck & 63); + *seen_skip.get_unchecked_mut(sk >> 6) |= 1u64 << (sk & 63); + } + + n0 = n1; + n1 = cur; + } + + self.flush_seen(&seen_consec, word_idx, bit_mask); + skip_builder.flush_seen(&seen_skip, word_idx, bit_mask); + } + + #[inline(always)] + fn add_short_content(&self, skip_builder: &Self, n: &[u8], word_idx: usize, bit_mask: u64) { + let mut seen_consec = [0u64; SEEN_WORDS]; + let mut seen_skip = [0u64; SEEN_WORDS]; + + let consec_base = self.col_data_ptr(); + let consec_words = self.words; + let skip_base = skip_builder.col_data_ptr(); + let skip_words = skip_builder.words; + + let mut n0 = n[0]; + let mut n1 = n[1]; + + if n0 != 0 && n1 != 0 { + let key = (n0 as u16) << 8 | n1 as u16; + self.record_bigram( + &mut seen_consec, + key, + word_idx, + bit_mask, + consec_base, + consec_words, + ); + } + + for &cur in &n[2..] { + if cur != 0 { + if n1 != 0 { + let key = (n1 as u16) << 8 | cur as u16; + self.record_bigram( + &mut seen_consec, + key, + word_idx, + bit_mask, + consec_base, + consec_words, + ); + } + if n0 != 0 { + let key = (n0 as u16) << 8 | cur as u16; + skip_builder.record_bigram( + &mut seen_skip, + key, + word_idx, + bit_mask, + skip_base, + skip_words, + ); + } + } + n0 = n1; + n1 = cur; + } + } + + #[inline(always)] + fn record_bigram( + &self, + seen: &mut [u64; SEEN_WORDS], + key: u16, + word_idx: usize, + bit_mask: u64, + col_base: *mut u64, + words: usize, + ) { + let k = key as usize; + let w = k >> 6; + let bit = 1u64 << (k & 63); + // SAFETY: w = key/64 with key: u16, so w < 1024 = SEEN_WORDS. + let prev = unsafe { *seen.get_unchecked(w) }; + if prev & bit == 0 { + unsafe { + *seen.get_unchecked_mut(w) = prev | bit; + } + let col = self.get_or_alloc_column(key); + if col != NO_COLUMN { + unsafe { + let p = col_base.add(col as usize * words + word_idx); + *p |= bit_mask; + } + } + } + } + + fn flush_seen(&self, seen: &[u64; SEEN_WORDS], word_idx: usize, bit_mask: u64) { + let col_base = self.col_data_ptr(); + let words = self.words; + for (blk, block) in seen.chunks_exact(8).enumerate() { + // OR-test whole blocks so the mostly-empty bitmap scans fast. + if block.iter().fold(0u64, |a, &w| a | w) == 0 { + continue; + } + for (j, &word_bits) in block.iter().enumerate() { + let w = blk * 8 + j; + let mut bits = match w & 3 { + _ if w < 4 => 0, + 0 => word_bits & !1, + _ => word_bits, + }; + while bits != 0 { + let key = (w << 6 | bits.trailing_zeros() as usize) as u16; + bits &= bits - 1; + let col = self.get_or_alloc_column(key); + if col != NO_COLUMN { + unsafe { + let p = col_base.add(col as usize * words + word_idx); + *p |= bit_mask; + } + } + } + } + } + } + + pub fn is_ready(&self) -> bool { + self.populated.load(Ordering::Relaxed) > 0 + } + + pub fn columns_used(&self) -> u16 { + self.next_column + .load(Ordering::Relaxed) + .min(MAX_BIGRAM_COLUMNS as u16) + } + + /// Compress the dense builder into a compact `BigramFilter`. + /// + /// Retains columns where the bigram appears in ≥`min_density_pct`% (or + /// the default ~3.1% heuristic when `None`) and <90% of indexed files. + /// Sparse columns carry too little data to justify their memory; + /// ubiquitous columns (≥90%) are nearly all-ones and barely filter. + #[inline(always)] + pub fn compress(self, min_density_pct: Option) -> BigramFilter { + let cols = self.columns_used() as usize; + let words = self.words; + let file_count = self.file_count; + let populated = self.populated.load(Ordering::Relaxed); + let dense_bytes = words * 8; // cost of one dense column + + let old_lookup = self.lookup; + // If no file ever populated content, col_data was never + // materialised. Treat as empty — every column falls through. + let col_data: Option> = self.col_data.into_inner().map(UnsafeCell::into_inner); + + let mut lookup: Vec = vec![NO_COLUMN; 65536]; + let mut dense_data: Vec = Vec::with_capacity(cols * words); + let mut dense_count: usize = 0; + + if let Some(col_data) = col_data.as_deref() { + for key in 0..65536usize { + let old_col = old_lookup[key].load(Ordering::Relaxed); + if old_col == NO_COLUMN || old_col as usize >= cols { + continue; + } + + let col_start = old_col as usize * words; + let bitset = &col_data[col_start..col_start + words]; + + // count set bits to decide if this column is worth keeping. + let mut popcount = 0u32; + for &word in bitset.iter().take(words) { + popcount += word.count_ones(); + } + + // drop bigrams appearing in too few files + let not_to_rare = if let Some(min_pct) = min_density_pct { + // Percentage-based: require ≥ min_pct% of populated files. + populated > 0 && (popcount as usize) * 100 >= populated * min_pct as usize + } else { + // Default: popcount ≥ words × 2 (~3.1% of files). + (popcount as usize * 4) >= dense_bytes + }; + + if !not_to_rare { + continue; + } + + // Drop ubiquitous bigrams — columns ≥90% ones carry almost no + // filtering power and just waste memory + AND cycles. + if populated > 0 && (popcount as usize) * 10 >= populated * 9 { + continue; + } + + let dense_idx = dense_count as u16; + lookup[key] = dense_idx; + dense_count += 1; + + dense_data.extend_from_slice(bitset); + } + } + + BigramFilter { + lookup, + dense_data, + dense_count, + words, + file_count, + populated, + skip_index: None, + } + } +} + +unsafe impl Send for BigramIndexBuilder {} + +/// Inverted bigram index with optional "skip-1" extension +/// Copmressed into bitset for minimal usage, the layout of this struct actually matters +#[derive(Debug)] +pub struct BigramFilter { + lookup: Vec, + /// Flat buffer of all dense column data laid out at fixed stride `words`. + /// Column `i` starts at `i * words`. + dense_data: Vec, // do not try to change this to u8 it has to be wordsize + dense_count: usize, + words: usize, + file_count: usize, + populated: usize, + /// Optional skip-1 bigram index (stride 2). Built from character pairs + /// at distance 2, e.g. "ABCDE" → (A,C),(B,D),(C,E). ANDead with the + /// consecutive bigram candidates during query to dramatically reduce + /// false positives. + skip_index: Option>, +} + +/// SIMD-friendly bitwise AND of two equal-length bitsets. +// Auto vectorized (don't touch) +#[inline] +fn bitset_and(result: &mut [u64], bitset: &[u64]) { + result + .iter_mut() + .zip(bitset.iter()) + .for_each(|(r, b)| *r &= *b); +} + +impl BigramFilter { + /// AND the posting lists for all query bigrams (consecutive + skip). + /// Returns None if no query bigrams are tracked. + pub fn query(&self, pattern: &[u8]) -> Option> { + if pattern.len() < 2 { + return None; + } + + let mut result = vec![u64::MAX; self.words]; + if !self.file_count.is_multiple_of(64) { + let last = self.words - 1; + result[last] = (1u64 << (self.file_count % 64)) - 1; + } + + let words = self.words; + let mut has_filter = false; + + let mut prev = pattern[0]; + for &b in &pattern[1..] { + if (32..=126).contains(&prev) && (32..=126).contains(&b) { + let key = (prev.to_ascii_lowercase() as u16) << 8 | b.to_ascii_lowercase() as u16; + let col = self.lookup[key as usize]; + if col != NO_COLUMN { + let offset = col as usize * words; + // SAFETY: compress() guarantees offset + words <= dense_data.len() + let slice = unsafe { self.dense_data.get_unchecked(offset..offset + words) }; + bitset_and(&mut result, slice); + has_filter = true; + } + } + prev = b; + } + + // strid-1 bigrams + if let Some(skip) = &self.skip_index + && pattern.len() >= 3 + && let Some(skip_candidates) = skip.query_skip(pattern) + { + bitset_and(&mut result, &skip_candidates); + has_filter = true; + } + + has_filter.then_some(result) + } + + /// Query using stride-2 bigrams from the pattern. + /// For "ABCDE" queries with keys (A,C), (B,D), (C,E). + fn query_skip(&self, pattern: &[u8]) -> Option> { + let mut result = vec![u64::MAX; self.words]; + if !self.file_count.is_multiple_of(64) { + let last = self.words - 1; + result[last] = (1u64 << (self.file_count % 64)) - 1; + } + + let words = self.words; + let mut has_filter = false; + + for i in 0..pattern.len().saturating_sub(2) { + let a = pattern[i]; + let b = pattern[i + 2]; + if (32..=126).contains(&a) && (32..=126).contains(&b) { + let key = (a.to_ascii_lowercase() as u16) << 8 | b.to_ascii_lowercase() as u16; + let col = self.lookup[key as usize]; + if col != NO_COLUMN { + let offset = col as usize * words; + let slice = unsafe { self.dense_data.get_unchecked(offset..offset + words) }; + bitset_and(&mut result, slice); + has_filter = true; + } + } + } + + has_filter.then_some(result) + } + + /// Attach a skip-1 bigram index for tighter candidate filtering. + pub fn set_skip_index(&mut self, skip: BigramFilter) { + self.skip_index = Some(Box::new(skip)); + } + + #[inline] + pub fn is_candidate(candidates: &[u64], file_idx: usize) -> bool { + let word = file_idx / 64; + let bit = file_idx % 64; + word < candidates.len() && candidates[word] & (1u64 << bit) != 0 + } + + pub fn count_candidates(candidates: &[u64]) -> usize { + candidates.iter().map(|w| w.count_ones() as usize).sum() + } + + pub fn is_ready(&self) -> bool { + self.populated > 0 + } + + pub fn file_count(&self) -> usize { + self.file_count + } + + pub fn columns_used(&self) -> usize { + self.dense_count + } + + /// Total heap bytes used by this index (lookup + dense data + skip). + pub fn heap_bytes(&self) -> usize { + let lookup_bytes = self.lookup.len() * std::mem::size_of::(); + let dense_bytes = self.dense_data.len() * std::mem::size_of::(); + let skip_bytes = self.skip_index.as_ref().map_or(0, |s| s.heap_bytes()); + lookup_bytes + dense_bytes + skip_bytes + } + + /// Check whether a bigram key is present in this index. + pub fn has_key(&self, key: u16) -> bool { + self.lookup[key as usize] != NO_COLUMN + } + + /// Raw lookup table (65536 entries mapping bigram key → column index). + pub fn lookup(&self) -> &[u16] { + &self.lookup + } + + /// Flat dense bitset data at fixed stride `words`. + pub fn dense_data(&self) -> &[u64] { + &self.dense_data + } + + /// Number of u64 words per column (= ceil(file_count / 64)). + pub fn words(&self) -> usize { + self.words + } + + /// Number of dense columns retained after compression. + pub fn dense_count(&self) -> usize { + self.dense_count + } + + /// Number of files that contributed content to the index. + pub fn populated(&self) -> usize { + self.populated + } + + /// Reference to the optional skip-1 bigram sub-index. + pub fn skip_index(&self) -> Option<&BigramFilter> { + self.skip_index.as_deref() + } + + /// Create a new bigram filter from the internal data + pub fn new( + lookup: Vec, + dense_data: Vec, + dense_count: usize, + words: usize, + file_count: usize, + populated: usize, + ) -> Self { + Self { + lookup, + dense_data, + dense_count, + words, + file_count, + populated, + skip_index: None, + } + } +} + +/// Single-byte normalize: 0 for non-printable, lowercased byte otherwise. +/// 0 is a safe sentinel: lowered printable bytes are 32..=126. +#[inline(always)] +fn normalize_byte_scalar(b: u8) -> u8 { + let printable = b.wrapping_sub(32) <= 94; + let lower = b | ((b.wrapping_sub(b'A') < 26) as u8 * 0x20); + if printable { lower } else { 0 } +} + +/// Bulk version: write `dst[i]` = `normalize_byte_scalar(src[i])` for `i` +/// in `0..src.len()`. Inlined-scalar so LLVM auto-vectorises with the +/// build's baseline SIMD; on x86_64 we runtime-dispatch to AVX2. +/// Caller guarantees `dst.len() >= src.len()`. +#[inline(always)] +fn normalize_bytes(src: &[u8], dst: &mut [u8]) { + debug_assert!(dst.len() >= src.len()); + #[cfg(target_arch = "x86_64")] + { + if std::is_x86_feature_detected!("avx2") { + unsafe { normalize_bytes_avx2(src, dst) }; + return; + } + } + + #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] + { + unsafe { normalize_bytes_neon(src, dst) }; + return; + } + + #[allow(unused)] + normalize_bytes_scalar(src, dst); +} + +#[inline(always)] +fn normalize_bytes_scalar(src: &[u8], dst: &mut [u8]) { + for (i, &b) in src.iter().enumerate() { + dst[i] = normalize_byte_scalar(b); + } +} + +/// AVX2 normalize: 32 bytes/iter. AVX2 only has signed cmp, so unsigned +/// range checks use `min(max(v, lo), hi) == v`. +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn normalize_bytes_avx2(src: &[u8], dst: &mut [u8]) { + use std::arch::x86_64::*; + let len = src.len(); + let mut i = 0; + let p_lo = _mm256_set1_epi8(32); + let p_hi = _mm256_set1_epi8(126u8 as i8); + let u_lo = _mm256_set1_epi8(b'A' as i8); + let u_hi = _mm256_set1_epi8(b'Z' as i8); + let or20 = _mm256_set1_epi8(0x20); + while i + 32 <= len { + unsafe { + let v = _mm256_loadu_si256(src.as_ptr().add(i) as *const __m256i); + // printable_mask: v in [32, 126] + let clamp_p = _mm256_min_epu8(_mm256_max_epu8(v, p_lo), p_hi); + let printable = _mm256_cmpeq_epi8(v, clamp_p); + // is_upper_mask: v in [65, 90] + let clamp_u = _mm256_min_epu8(_mm256_max_epu8(v, u_lo), u_hi); + let is_upper = _mm256_cmpeq_epi8(v, clamp_u); + let or_bits = _mm256_and_si256(is_upper, or20); + let lower = _mm256_or_si256(v, or_bits); + let out = _mm256_and_si256(lower, printable); + _mm256_storeu_si256(dst.as_mut_ptr().add(i) as *mut __m256i, out); + } + i += 32; + } + while i < len { + dst[i] = normalize_byte_scalar(src[i]); + i += 1; + } +} + +#[cfg(all(target_arch = "aarch64", target_feature = "neon"))] +#[target_feature(enable = "neon")] +unsafe fn normalize_bytes_neon(src: &[u8], dst: &mut [u8]) { + use std::arch::aarch64::*; + let len = src.len(); + let mut i = 0; + let v32 = vdupq_n_u8(32); + let v127 = vdupq_n_u8(127); + let va = vdupq_n_u8(b'A'); + let vz1 = vdupq_n_u8(b'Z' + 1); + let v20 = vdupq_n_u8(0x20); + + while i + 16 <= len { + unsafe { + let v = vld1q_u8(src.as_ptr().add(i)); + // printable: v >= 32 AND v < 127 + let ge32 = vcgeq_u8(v, v32); + let lt127 = vcltq_u8(v, v127); + let print_mask = vandq_u8(ge32, lt127); + // is_upper: v >= 'A' AND v < 'Z'+1 + let ge_a = vcgeq_u8(v, va); + let lt_z1 = vcltq_u8(v, vz1); + let upper_mask = vandq_u8(ge_a, lt_z1); + let or_bits = vandq_u8(upper_mask, v20); + let lower = vorrq_u8(v, or_bits); + let out = vandq_u8(lower, print_mask); + + vst1q_u8(dst.as_mut_ptr().add(i), out); + } + i += 16; + } + while i < len { + dst[i] = normalize_byte_scalar(src[i]); + i += 1; + } +} + +pub fn extract_bigrams(content: &[u8]) -> Vec { + if content.len() < 2 { + return Vec::new(); + } + // Use a flat bitset (65536 bits = 8 KB) for dedup — faster than HashSet. + let mut seen = vec![0u64; 1024]; // 1024 * 64 = 65536 bits + let mut bigrams = Vec::new(); + + let mut prev = content[0]; + for &b in &content[1..] { + if (32..=126).contains(&prev) && (32..=126).contains(&b) { + let key = (prev.to_ascii_lowercase() as u16) << 8 | b.to_ascii_lowercase() as u16; + let word = key as usize / 64; + let bit = 1u64 << (key as usize % 64); + if seen[word] & bit == 0 { + seen[word] |= bit; + bigrams.push(key); + } + } + prev = b; + } + bigrams +} + +/// Modified and added files store their own bigram sets. Deleted files are +/// tombstoned in a bitset so they can be excluded from base query results. +/// This overlay is updated by the background watcher on every file event +/// and cleared when the base index is rebuilt. +#[derive(Debug)] +pub struct BigramOverlay { + /// Per-file bigram sets for files modified since the base was built. + /// Key = file index in the base `Vec`. + modified: AHashMap>, + + /// Tombstone bitset — one bit per base file. Set bits are excluded + /// from base query results. + tombstones: Vec, + + /// Original files count this overlay was created for. + base_file_count: usize, +} + +impl BigramOverlay { + pub(crate) fn new(base_file_count: usize) -> Self { + let words = base_file_count.div_ceil(64); + Self { + modified: AHashMap::new(), + tombstones: vec![0u64; words], + base_file_count, + } + } + + pub(crate) fn modify_file(&mut self, file_idx: usize, content: &[u8]) { + self.modified.insert(file_idx, extract_bigrams(content)); + } + + pub(crate) fn delete_file(&mut self, file_idx: usize) { + if file_idx < self.base_file_count { + let word = file_idx / 64; + self.tombstones[word] |= 1u64 << (file_idx % 64); + } + self.modified.remove(&file_idx); + } + + /// Return base file indices of modified files whose bigrams match ALL + /// of the given `pattern_bigrams`. + pub(crate) fn query_modified(&self, pattern_bigrams: &[u16]) -> Vec { + if pattern_bigrams.is_empty() { + return self.modified.keys().copied().collect(); + } + self.modified + .iter() + .filter_map(|(&file_idx, bigrams)| { + pattern_bigrams + .iter() + .all(|pb| bigrams.contains(pb)) + .then_some(file_idx) + }) + .collect() + } + + /// Number of base files this overlay was created for. + pub(crate) fn base_file_count(&self) -> usize { + self.base_file_count + } + + /// Get the tombstone bitset for clearing base candidates. + pub(crate) fn tombstones(&self) -> &[u64] { + &self.tombstones + } + + /// Get all modified file indices (for conservative overlay merging when + /// we can't extract precise bigrams, e.g. regex patterns). + pub(crate) fn modified_indices(&self) -> Vec { + self.modified.keys().copied().collect() + } +} + +const BIGRAM_CHUNK_FILES: usize = 4 * 64; + +/// Sparse-column cutoff for the skip-1 sub-index. Rare skip columns add +/// little filtering power but ~25-30% of index memory, so we drop +/// anything appearing in < 12 % of populated files. +const SKIP_INDEX_MIN_DENSITY_PCT: u32 = 12; + +thread_local! { + /// Reusable read buffer that is allocated per thread and used for reading files + static READ_BUF: std::cell::RefCell> = + std::cell::RefCell::new(vec![0u8; MAX_INDEXABLE_FILE_SIZE].into_boxed_slice()); +} + +/// Reads bigram chunk, we *SHOULD NOT* use mmap cache here because bigram is built off-lock +/// if the watcher thread tries to invalidate mmap during the borrow from it - UAB or segfaut +/// +/// mmap should only be used by the locked version of grep which absolutely minimizes any riscs +#[inline] +fn read_bigram_chunk<'a>( + file: &FileItem, + base_fd: libc::c_int, + base_path: &std::path::Path, + arena: crate::simd_path::ArenaPtr, + buf: &'a mut [u8], + path_buf: &mut [u8; crate::simd_path::PATH_BUF_SIZE], +) -> Option<&'a [u8]> { + let want = (file.size as usize).min(MAX_INDEXABLE_FILE_SIZE); + let filled = file.read_trimmed_into_buf(base_fd, base_path, arena, path_buf, &mut buf[..want]); + if filled == 0 { + return None; + } + + let data = &buf[..filled]; + + Some(data) +} + +#[tracing::instrument(skip_all, name = "Building Bigram Index", level = tracing::Level::DEBUG)] +pub(crate) fn build_bigram_index( + files: &[crate::types::FileItem], + base_path: &std::path::Path, + arena: crate::simd_path::ArenaPtr, +) -> BigramFilter { + let builder = BigramIndexBuilder::new(files.len()); + let skip_builder = BigramIndexBuilder::new(files.len()); + + #[cfg(unix)] + let base_fd: libc::c_int = open_base_dir_fd(base_path); + #[cfg(not(unix))] + let base_fd: i32 = -1; + + // Always reads each file into the thread-local READ_BUF — never aliases the + // persistent mmap cache. See `read_bigram_chunk` for the rationale: this + // pass runs detached on the background pool without holding the picker + // read lock, so a watcher event mutating a `FileItem` would race any + // borrow we took from a cached `Mmap`. + crate::parallelism::BACKGROUND_THREAD_POOL.install(|| { + files + .par_chunks(BIGRAM_CHUNK_FILES) + .enumerate() + .for_each(|(chunk_idx, chunk)| { + let base_idx = chunk_idx * BIGRAM_CHUNK_FILES; + for (offset, file) in chunk.iter().enumerate() { + let file_idx = base_idx + offset; + + if file.is_binary() || file.size == 0 { + return; + } + + READ_BUF.with(|read_cell| { + let mut buf = read_cell.borrow_mut(); + let mut path_buf = [0u8; crate::simd_path::PATH_BUF_SIZE]; + + if let Some(content) = read_bigram_chunk( + file, + base_fd, + base_path, + arena, + &mut buf[..], + &mut path_buf, + ) { + // we have to manually ensure that every byte is a valid text byte to + // perform this we have to scan every file, first 512 bytes is not enough + // so basically we rely on the fact that first 2MB will always contain + // an invalid text sequence if this is not a binary file. + // + // Need to find a better way to do this. + file.set_binary(crate::types::detect_binary_content(content)); + + builder.add_file_content(&skip_builder, file_idx, content); + } + }); + } + }); + }); + + #[cfg(unix)] + if base_fd >= 0 { + unsafe { libc::close(base_fd) }; + } + + let mut index = builder.compress(None); + let skip_index = skip_builder.compress(Some(SKIP_INDEX_MIN_DENSITY_PCT)); + index.set_skip_index(skip_index); + + // in progress bigram walk + rust's ignore crate allocates shit ton of garbage memory + // all custom allocators would think this is available resource while we do not allocate + // after the sync, so it's very important to let the unused memory go back to the OS + crate::file_picker::hint_allocator_collect(); + + index +} + +#[tracing::instrument(skip_all, name = "Sniffing Large Files Binary", level = tracing::Level::DEBUG)] +pub(crate) fn sniff_binary_for_non_indexable( + files: &[FileItem], + base_path: &std::path::Path, + arena: crate::simd_path::ArenaPtr, + cancelled: &std::sync::atomic::AtomicBool, +) { + // Non-indexable files are few in a typical repo, so a serial pass with a + // single reused chunk buffer beats spinning up the thread pool. + let mut path_buf = [0u8; crate::simd_path::PATH_BUF_SIZE]; + let mut chunk = vec![0u8; crate::types::BINARY_CLASSIFICATION_CHUNK_SIZE]; + use std::sync::atomic::Ordering; + + for (i, file) in files.iter().enumerate() { + // check every 256 files to avoid useless work + if (i & 0xFF) == 0 && cancelled.load(Ordering::Acquire) { + return; + } + + // check only the files that we are able to grep + if file.size == 0 || file.size > constants::MAX_FFFILE_SIZE { + continue; + } + + let abs = file.write_absolute_path(arena, base_path, &mut path_buf); + file.detect_binary_per_byte(abs, &mut chunk); + } +} + +/// Open the base directory for the `openat` fast path. Returns `-1` on +/// failure — callers interpret a negative fd as "fall back to absolute +/// paths". +#[cfg(unix)] +fn open_base_dir_fd(base_path: &std::path::Path) -> libc::c_int { + use std::os::unix::ffi::OsStrExt; + let mut cstr = [0u8; crate::simd_path::PATH_BUF_SIZE]; + let bytes = base_path.as_os_str().as_bytes(); + if bytes.len() >= cstr.len() { + return -1; + } + cstr[..bytes.len()].copy_from_slice(bytes); + // SAFETY: `cstr` is NUL-terminated by construction (zero-initialised, + // and we only filled up to `bytes.len() < cstr.len()`). + unsafe { + libc::open( + cstr.as_ptr() as *const std::os::raw::c_char, + libc::O_RDONLY | libc::O_DIRECTORY, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Build a key the same way `add_file_content` does: two printable-ASCII + /// bytes, lowercased, packed as `(hi << 8) | lo`. + fn key(a: u8, b: u8) -> u16 { + ((a.to_ascii_lowercase() as u16) << 8) | b.to_ascii_lowercase() as u16 + } + + /// Return the sorted list of (consec, skip) bigram keys that should appear + /// for `content`. Used as the reference implementation. + fn expected_bigrams(content: &[u8]) -> (Vec, Vec) { + let mut consec: std::collections::BTreeSet = Default::default(); + let mut skip: std::collections::BTreeSet = Default::default(); + let printable = |b: u8| (32..=126).contains(&b); + for i in 1..content.len() { + let a = content[i - 1]; + let b = content[i]; + if printable(a) && printable(b) { + consec.insert(key(a, b)); + } + if i >= 2 { + let a = content[i - 2]; + let b = content[i]; + if printable(a) && printable(b) { + skip.insert(key(a, b)); + } + } + } + (consec.into_iter().collect(), skip.into_iter().collect()) + } + + /// Query: does the builder record file 0 as having this bigram set? + fn builder_has_key_for_file_0(b: &BigramIndexBuilder, k: u16) -> bool { + let col = b.lookup[k as usize].load(Ordering::Relaxed); + if col == NO_COLUMN { + return false; + } + b.column_bitset(col)[0] & 1 != 0 + } + + fn run_and_compare(content: &[u8]) { + let consec = BigramIndexBuilder::new(1); + let skip = BigramIndexBuilder::new(1); + consec.add_file_content(&skip, 0, content); + + let (expected_consec, expected_skip) = expected_bigrams(content); + + // Every expected bigram must be recorded. + for k in &expected_consec { + assert!( + builder_has_key_for_file_0(&consec, *k), + "consec bigram 0x{k:04x} missing for content {content:?}", + ); + } + for k in &expected_skip { + assert!( + builder_has_key_for_file_0(&skip, *k), + "skip bigram 0x{k:04x} missing for content {content:?}", + ); + } + + // No unexpected bigrams — iterate lookup for set columns. + for k in 0u32..=0xFFFF { + let recorded_consec = builder_has_key_for_file_0(&consec, k as u16); + let recorded_skip = builder_has_key_for_file_0(&skip, k as u16); + if recorded_consec { + assert!( + expected_consec.contains(&(k as u16)), + "unexpected consec bigram 0x{k:04x} in content {content:?}", + ); + } + if recorded_skip { + assert!( + expected_skip.contains(&(k as u16)), + "unexpected skip bigram 0x{k:04x} in content {content:?}", + ); + } + } + } + + #[test] + fn add_file_empty_is_noop() { + let consec = BigramIndexBuilder::new(1); + let skip = BigramIndexBuilder::new(1); + consec.add_file_content(&skip, 0, b""); + assert_eq!(consec.columns_used(), 0); + assert_eq!(skip.columns_used(), 0); + // populated counter not incremented for empty input + assert_eq!(consec.populated.load(Ordering::Relaxed), 0); + } + + #[test] + fn add_file_single_byte_is_noop() { + let consec = BigramIndexBuilder::new(1); + let skip = BigramIndexBuilder::new(1); + consec.add_file_content(&skip, 0, b"a"); + assert_eq!(consec.columns_used(), 0); + assert_eq!(skip.columns_used(), 0); + } + + #[test] + fn add_file_two_bytes_consec_only() { + // With exactly 2 bytes there's no skip bigram (needs i >= 2 in the loop). + run_and_compare(b"ab"); + } + + #[test] + fn add_file_three_bytes_has_skip() { + // "abc" -> consec {"ab", "bc"}, skip {"ac"} + run_and_compare(b"abc"); + } + + #[test] + fn add_file_ascii_words() { + run_and_compare(b"hello world"); + run_and_compare(b"the quick brown fox jumps over the lazy dog"); + run_and_compare(b"fn main() { println!(\"hi\"); }"); + } + + #[test] + fn add_file_case_is_lowered() { + // Uppercase should be lowercased before keying, so "AB" == "ab". + let upper = BigramIndexBuilder::new(1); + let upper_skip = BigramIndexBuilder::new(1); + upper.add_file_content(&upper_skip, 0, b"ABC"); + + let lower = BigramIndexBuilder::new(1); + let lower_skip = BigramIndexBuilder::new(1); + lower.add_file_content(&lower_skip, 0, b"abc"); + + // Both should have identical bigram keys. + for k in 0u32..=0xFFFF { + let u = builder_has_key_for_file_0(&upper, k as u16); + let l = builder_has_key_for_file_0(&lower, k as u16); + assert_eq!(u, l, "consec 0x{k:04x}: upper={u} lower={l}"); + let u = builder_has_key_for_file_0(&upper_skip, k as u16); + let l = builder_has_key_for_file_0(&lower_skip, k as u16); + assert_eq!(u, l, "skip 0x{k:04x}: upper={u} lower={l}"); + } + } + + #[test] + fn add_file_rejects_non_printable() { + // Bigrams where either byte is outside 32..=126 are rejected. But + // the skip-1 bigram can still connect two printable bytes across a + // non-printable one: for "\0a\0b", consec sees no valid pair but + // skip sees (a,b) at i=3. Use the reference implementation. + run_and_compare(b"\0a\0b"); + + // All-zero input: truly nothing recorded. + let consec = BigramIndexBuilder::new(1); + let skip = BigramIndexBuilder::new(1); + consec.add_file_content(&skip, 0, b"\0\0\0\0"); + assert_eq!(consec.columns_used(), 0); + assert_eq!(skip.columns_used(), 0); + } + + #[test] + fn add_file_mixed_printable_and_control() { + // "a\tb\nc d" — \t (9) and \n (10) are below 32. Consec: + // (a, \t) x, (\t, b) x, (b, \n) x, (\n, c) x, (c, ' ') ok, (' ', d) ok + // Skip (i-2, i): + // (a, b) ok, (\t, \n) x, (b, c) ok, (\n, ' ') x, (c, d) ok + run_and_compare(b"a\tb\nc d"); + } + + #[test] + fn add_file_repeats_are_deduped() { + // "ababab" has many repeats of "ab", "ba" — each unique bigram should + // be recorded exactly once (the stack-local `seen_*` dedup works). + run_and_compare(b"ababababab"); + } + + #[test] + fn add_file_tombstone_separation() { + // Two separate files share no bits; file 1's content doesn't bleed + // into file 0's row and vice-versa. + let consec = BigramIndexBuilder::new(2); + let skip = BigramIndexBuilder::new(2); + consec.add_file_content(&skip, 0, b"xy"); + consec.add_file_content(&skip, 1, b"zw"); + + let key_xy = key(b'x', b'y'); + let key_zw = key(b'z', b'w'); + + // file 0 has "xy" but not "zw" + let col_xy = consec.lookup[key_xy as usize].load(Ordering::Relaxed); + let col_zw = consec.lookup[key_zw as usize].load(Ordering::Relaxed); + let bitset_xy = consec.column_bitset(col_xy)[0]; + let bitset_zw = consec.column_bitset(col_zw)[0]; + assert_eq!(bitset_xy & 0b01, 0b01, "file 0 should have xy"); + assert_eq!(bitset_zw & 0b01, 0, "file 0 should NOT have zw"); + assert_eq!(bitset_xy & 0b10, 0, "file 1 should NOT have xy"); + assert_eq!(bitset_zw & 0b10, 0b10, "file 1 should have zw"); + } + + #[test] + fn add_file_long_content() { + // Stress test: ~8 KB of printable ASCII. Should complete without + // overflowing any stack-local bitset and produce the full set. + let mut buf = Vec::with_capacity(8192); + for i in 0..8192 { + buf.push(32u8 + ((i * 7) % 95) as u8); // cycle through printable range + } + run_and_compare(&buf); + } + + #[test] + fn add_file_simd_and_scalar_agree() { + // Cross-check: both code paths (scalar <128 bytes, SIMD ≥128) must + // produce identical bigram sets for content that straddles the + // threshold. Mix printable ASCII with some non-printable bytes and + // repeats so the non-printable branch in the SIMD path exercises. + let mut mixed = Vec::with_capacity(256); + for i in 0..256usize { + mixed.push(match i % 9 { + 0 => 0, // NUL + 1 => 0x7F, // DEL (just above 126) + 2 => b'\n', // below 32 + _ => 32 + ((i * 13) % 95) as u8, + }); + } + + run_and_compare(&mixed[..127]); // scalar path + run_and_compare(&mixed); // SIMD path (256 bytes) + run_and_compare(&mixed[..192]); // SIMD path with scalar tail + } + + #[test] + fn add_file_long_short_paths_agree() { + // Same mixed content checked just below, at, and above + // LONG_CONTENT_MIN_LEN so both add_short_content and add_long_content + // are validated against the reference implementation. + let mut mixed = Vec::with_capacity(LONG_CONTENT_MIN_LEN * 2); + for i in 0..LONG_CONTENT_MIN_LEN * 2 { + mixed.push(match i % 11 { + 0 => 0, + 1 => 0x7F, + 2 => b'\n', + _ => 32 + ((i * 31) % 95) as u8, + }); + } + run_and_compare(&mixed[..LONG_CONTENT_MIN_LEN - 1]); + run_and_compare(&mixed[..LONG_CONTENT_MIN_LEN]); + run_and_compare(&mixed); + } + + #[test] + fn add_file_respects_file_count_boundary() { + // file_count=100, file_idx=63 (last bit in word 0) and file_idx=64 + // (first bit in word 1). Make sure the word_idx math is right. + let consec = BigramIndexBuilder::new(100); + let skip = BigramIndexBuilder::new(100); + consec.add_file_content(&skip, 63, b"ab"); + consec.add_file_content(&skip, 64, b"cd"); + + let kab = key(b'a', b'b'); + let kcd = key(b'c', b'd'); + let col_ab = consec.lookup[kab as usize].load(Ordering::Relaxed); + let col_cd = consec.lookup[kcd as usize].load(Ordering::Relaxed); + + let ab_bitset = consec.column_bitset(col_ab); + let cd_bitset = consec.column_bitset(col_cd); + // ab in word 0, bit 63 + assert_eq!(ab_bitset[0], 1u64 << 63); + assert_eq!(ab_bitset[1], 0); + // cd in word 1, bit 0 + assert_eq!(cd_bitset[0], 0); + assert_eq!(cd_bitset[1], 1); + } +} diff --git a/crates/fff-core/src/bigram_query.rs b/crates/fff-core/src/bigram_query.rs new file mode 100644 index 0000000..fd7c57f --- /dev/null +++ b/crates/fff-core/src/bigram_query.rs @@ -0,0 +1,998 @@ +use crate::bigram_filter::BigramFilter; +use regex_syntax::hir::{Class, Hir, HirKind}; +use smallvec::SmallVec; +use std::borrow::Cow; + +const MAX_CLASS_EXPAND: usize = 16; + +// stack inlined array padded with 0 and tracked length +#[derive(Clone, Copy, PartialEq, Eq)] +struct InlineArray { + bytes: [u8; MAX_CLASS_EXPAND], + len: usize, +} + +impl InlineArray { + const fn new() -> Self { + Self { + bytes: [0; MAX_CLASS_EXPAND], + len: 0, + } + } + + fn from_byte(b: u8) -> Self { + let mut set = Self::new(); + set.push(b); + set + } + + /// Append a byte; no-op if already full (callers guard against this). + fn push(&mut self, b: u8) { + if self.len < MAX_CLASS_EXPAND { + self.bytes[self.len] = b; + self.len += 1; + } + } +} + +impl std::ops::Deref for InlineArray { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.bytes[..self.len] + } +} + +#[inline] +fn consec_key(a: u8, b: u8) -> Option { + let al = a.to_ascii_lowercase(); + let bl = b.to_ascii_lowercase(); + if (32..=126).contains(&al) && (32..=126).contains(&bl) { + Some((al as u16) << 8 | bl as u16) + } else { + None + } +} + +#[derive(Debug, Clone)] +pub enum BigramQuery { + Any, + /// A consecutive bigram key to look up in the main index. + Consec(u16), + /// A skip-1 bigram key to look up in the skip sub-index. + Skip1(u16), + /// All children must match (intersect posting lists). + And(Vec), + /// At least one child must match (union posting lists). + Or(Vec), +} + +/// SIMD-friendly bitwise OR of two equal-length bitsets. +#[inline] +fn bitset_or(a: &mut [u64], b: &[u64]) { + a.iter_mut().zip(b.iter()).for_each(|(x, y)| *x |= *y); +} + +/// SIMD-friendly bitwise AND of two equal-length bitsets. +#[inline] +fn bitset_and(a: &mut [u64], b: &[u64]) { + a.iter_mut().zip(b.iter()).for_each(|(x, y)| *x &= *y); +} + +impl BigramQuery { + pub fn is_any(&self) -> bool { + matches!(self, BigramQuery::Any) + } + + pub(crate) fn evaluate(&self, index: &BigramFilter) -> Option> { + self.evaluate_cow(index).map(Cow::into_owned) + } + + fn evaluate_cow<'a>(&self, index: &'a BigramFilter) -> Option> { + match self { + BigramQuery::Any => None, + + BigramQuery::Consec(key) => { + let col = index.lookup()[*key as usize]; + if col == u16::MAX { + return None; + } + let words = index.words(); + let offset = col as usize * words; + let data = index.dense_data(); + if offset + words > data.len() { + return None; + } + Some(Cow::Borrowed(&data[offset..offset + words])) + } + + BigramQuery::Skip1(key) => { + let skip = index.skip_index()?; + let col = skip.lookup()[*key as usize]; + if col == u16::MAX { + return None; + } + let words = skip.words(); + let offset = col as usize * words; + let data = skip.dense_data(); + if offset + words > data.len() { + return None; + } + Some(Cow::Borrowed(&data[offset..offset + words])) + } + + BigramQuery::And(children) => { + let mut result: Option> = None; + for child in children { + if let Some(child_bits) = child.evaluate_cow(index) { + result = Some(match result { + None => child_bits.into_owned(), + Some(mut r) => { + bitset_and(&mut r, &child_bits); + r + } + }); + } + } + result.map(Cow::Owned) + } + + BigramQuery::Or(children) => { + if children.is_empty() { + return None; + } + let mut result: Option> = None; + for child in children { + // Any branch can't be filtered -> whole OR can't be filtered + let child_bits = child.evaluate_cow(index)?; + result = Some(match result { + None => child_bits.into_owned(), + Some(mut r) => { + bitset_or(&mut r, &child_bits); + r + } + }); + } + result.map(Cow::Owned) + } + } + } +} + +struct HirInfo { + query: BigramQuery, + first: Option, + last: Option, + can_be_empty: bool, +} + +impl HirInfo { + fn empty() -> Self { + Self { + query: BigramQuery::Any, + first: None, + last: None, + can_be_empty: true, + } + } +} + +/// Prefilter fuzzy query. The algorithm is the following: +/// we allow max_typos = min(len/3,2) every typo destroys at most 2 consecutive bigrams +/// So out of N bigrams at least N - 2 * max_typos have to present in the matching fil +pub(crate) fn fuzzy_to_bigram_query(query: &str, num_probes: usize) -> BigramQuery { + let lower: Vec = query.bytes().map(|b| b.to_ascii_lowercase()).collect(); + + if lower.len() < 2 { + return BigramQuery::Any; + } + + let max_typos = (lower.len() / 3).min(2); + + // Extract all consecutive bigram keys. + let bigram_keys: Vec = lower + .windows(2) + .filter_map(|w| consec_key(w[0], w[1])) + .collect(); + + if bigram_keys.is_empty() { + return BigramQuery::Any; + } + + // the simplest case, just check that every bigram is present either consec or not + if max_typos == 0 { + return simplify_and( + bigram_keys + .iter() + .map(|&k| BigramQuery::Consec(k)) + .collect(), + ); + } + + // Pick evenly-spaced probe bigrams. + let n = num_probes.min(bigram_keys.len()); + if n <= max_typos { + // Too few probes to require anything useful. + return simplify_or( + bigram_keys + .iter() + .map(|&k| BigramQuery::Consec(k)) + .collect(), + ); + } + + let probes: Vec = if n == bigram_keys.len() { + bigram_keys + } else { + (0..n) + .map(|i| { + let idx = i * (bigram_keys.len() - 1) / (n - 1); + bigram_keys[idx] + }) + .collect() + }; + + let required = n - max_typos; + + // If required == n, just AND all probes. + if required >= n { + return simplify_and(probes.iter().map(|&k| BigramQuery::Consec(k)).collect()); + } + + // Generate all C(n, required) subsets as OR(AND(subset), ...) + let mut branches = Vec::new(); + let mut combo = vec![0u16; required]; + combine(&probes, required, 0, 0, &mut combo, &mut branches); + + simplify_or(branches) +} + +/// Build C(n, k) combination branches in-place on a fixed-size slice. +fn combine( + items: &[u16], + k: usize, + start: usize, + depth: usize, + combo: &mut [u16], + branches: &mut Vec, +) { + if depth == k { + branches.push(simplify_and( + combo.iter().map(|&key| BigramQuery::Consec(key)).collect(), + )); + return; + } + let remaining = k - depth; + for i in start..=items.len() - remaining { + combo[depth] = items[i]; + combine(items, k, i + 1, depth + 1, combo, branches); + } +} + +pub(crate) fn regex_to_bigram_query(pattern: &str) -> BigramQuery { + let mut parser = regex_syntax::ParserBuilder::new() + .unicode(false) + .utf8(false) + .build(); + + let hir = match parser.parse(pattern) { + Ok(h) => h, + Err(_) => return BigramQuery::Any, + }; + + decompose(&hir).query +} + +fn decompose(hir: &Hir) -> HirInfo { + let can_be_empty = hir.properties().minimum_len().is_none_or(|n| n == 0); + + match hir.kind() { + HirKind::Empty => HirInfo::empty(), + + HirKind::Literal(lit) => decompose_literal(lit.0.as_ref()), + + HirKind::Class(class) => { + let bytes = expand_class(class); + match bytes { + Some(b) if !b.is_empty() => HirInfo { + query: BigramQuery::Any, + first: Some(b), + last: Some(b), + can_be_empty, + }, + _ => HirInfo { + query: BigramQuery::Any, + first: None, + last: None, + can_be_empty, + }, + } + } + + HirKind::Look(_) => HirInfo::empty(), + + HirKind::Repetition(rep) => { + let inner = decompose(&rep.sub); + if rep.min == 0 { + HirInfo { + query: BigramQuery::Any, + first: inner.first, + last: inner.last, + can_be_empty: true, + } + } else { + // min >= 1: inner bigrams guaranteed + let mut qs = Vec::new(); + if !inner.query.is_any() { + qs.push(inner.query.clone()); + } + // min >= 2: cross-boundary between consecutive occurrences + if rep.min >= 2 { + push_cross_consec(&mut qs, inner.last.as_deref(), inner.first.as_deref()); + } + HirInfo { + query: simplify_and(qs), + first: inner.first, + last: inner.last, + can_be_empty, + } + } + } + + HirKind::Capture(cap) => decompose(&cap.sub), + + HirKind::Concat(parts) => decompose_concat(parts), + + HirKind::Alternation(alts) => decompose_alternation(alts), + } +} + +/// Extract bigrams from a literal byte sequence. +fn decompose_literal(bytes: &[u8]) -> HirInfo { + if bytes.is_empty() { + return HirInfo::empty(); + } + + let lower: SmallVec<[u8; 64]> = bytes.iter().map(|b| b.to_ascii_lowercase()).collect(); + + if lower.len() == 1 { + let b = lower[0]; + let first = if (32..=126).contains(&b) { + Some(InlineArray::from_byte(b)) + } else { + None + }; + return HirInfo { + query: BigramQuery::Any, + first, + last: first, + can_be_empty: false, + }; + } + + let mut qs: Vec = Vec::new(); + + // Consecutive bigrams + for w in lower.windows(2) { + if let Some(k) = consec_key(w[0], w[1]) { + qs.push(BigramQuery::Consec(k)); + } + } + + // Skip-1 bigrams from the literal itself + if lower.len() >= 3 { + for i in 0..lower.len() - 2 { + if let Some(k) = consec_key(lower[i], lower[i + 2]) { + qs.push(BigramQuery::Skip1(k)); + } + } + } + + let first_byte = lower[0]; + let last_byte = *lower.last().unwrap(); + + HirInfo { + query: simplify_and(qs), + first: if (32..=126).contains(&first_byte) { + Some(InlineArray::from_byte(first_byte)) + } else { + None + }, + last: if (32..=126).contains(&last_byte) { + Some(InlineArray::from_byte(last_byte)) + } else { + None + }, + can_be_empty: false, + } +} + +fn decompose_concat(parts: &[Hir]) -> HirInfo { + if parts.is_empty() { + return HirInfo::empty(); + } + + let infos: Vec = parts.iter().map(decompose).collect(); + let mut qs: Vec = Vec::new(); + + for info in &infos { + if !info.query.is_any() { + qs.push(info.query.clone()); + } + } + + // Dense cross-boundary between adjacent mandatory parts + for pair in infos.windows(2) { + if !pair[0].can_be_empty && !pair[1].can_be_empty { + push_cross_consec(&mut qs, pair[0].last.as_deref(), pair[1].first.as_deref()); + } + } + + // Sparse-1 cross-boundary: across a single 1 byte wide middle part + if parts.len() >= 3 { + for i in 0..parts.len() - 2 { + let left = &infos[i]; + let mid = &parts[i + 1]; + let right = &infos[i + 2]; + + let min_len = mid.properties().minimum_len(); + let max_len = mid.properties().maximum_len(); + let is_1byte = min_len == Some(1) && max_len == Some(1); + + if is_1byte && !left.can_be_empty && !right.can_be_empty { + push_cross_skip1(&mut qs, left.last.as_deref(), right.first.as_deref()); + } + } + } + + let first = collect_first(&infos); + let last = collect_last(&infos); + let can_be_empty = infos.iter().all(|i| i.can_be_empty); + + HirInfo { + query: simplify_and(qs), + first, + last, + can_be_empty, + } +} + +fn decompose_alternation(alts: &[Hir]) -> HirInfo { + if alts.is_empty() { + return HirInfo::empty(); + } + + let infos: Vec = alts.iter().map(decompose).collect(); + let query = simplify_or(infos.iter().map(|i| i.query.clone()).collect()); + let first = merge_byte_sets(infos.iter().map(|i| &i.first)); + let last = merge_byte_sets(infos.iter().map(|i| &i.last)); + let can_be_empty = infos.iter().any(|i| i.can_be_empty); + + HirInfo { + query, + first, + last, + can_be_empty, + } +} + +fn expand_class(class: &Class) -> Option { + let mut bytes = InlineArray::new(); + match class { + Class::Bytes(bc) => { + for range in bc.ranges() { + let count = (range.end() as usize) - (range.start() as usize) + 1; + if bytes.len() + count > MAX_CLASS_EXPAND { + return None; + } + + for b in range.start()..=range.end() { + if (32..=126).contains(&b) { + let lower = b.to_ascii_lowercase(); + if !bytes.contains(&lower) { + bytes.push(lower); + } + } + } + } + } + Class::Unicode(uc) => { + for range in uc.ranges() { + let start = range.start() as u32; + let end = range.end() as u32; + if start > 127 { + continue; + } + let ascii_end = end.min(126) as u8; + let ascii_start = start.max(32) as u8; + if ascii_start > ascii_end { + continue; + } + let count = (ascii_end - ascii_start) as usize + 1; + if bytes.len() + count > MAX_CLASS_EXPAND { + return None; + } + for b in ascii_start..=ascii_end { + let lower = b.to_ascii_lowercase(); + if !bytes.contains(&lower) { + bytes.push(lower); + } + } + } + } + } + if bytes.is_empty() { None } else { Some(bytes) } +} + +/// Push consecutive cross-product bigrams into `qs`. +fn push_cross_consec(qs: &mut Vec, last: Option<&[u8]>, first: Option<&[u8]>) { + if let Some(q) = cross_product(last, first, false) { + qs.push(q); + } +} + +/// Push skip-1 cross-product bigrams into `qs`. +fn push_cross_skip1(qs: &mut Vec, last: Option<&[u8]>, first: Option<&[u8]>) { + if let Some(q) = cross_product(last, first, true) { + qs.push(q); + } +} + +fn cross_product(last: Option<&[u8]>, first: Option<&[u8]>, skip: bool) -> Option { + let last = last?; + let first = first?; + let n = last.len() * first.len(); + if n == 0 || n > MAX_CLASS_EXPAND * MAX_CLASS_EXPAND { + return None; + } + + let mut bigrams: Vec = Vec::with_capacity(n); + for &l in last { + for &f in first { + if let Some(k) = consec_key(l, f) { + let node = if skip { + BigramQuery::Skip1(k) + } else { + BigramQuery::Consec(k) + }; + bigrams.push(node); + } + } + } + + match bigrams.len() { + 0 => None, + 1 => Some(bigrams.into_iter().next().unwrap()), + _ => Some(simplify_or(bigrams)), + } +} + +fn collect_first(infos: &[HirInfo]) -> Option { + let mut result = InlineArray::new(); + for info in infos { + if let Some(ref bytes) = info.first { + for &b in bytes.iter() { + if !result.contains(&b) { + if result.len() >= MAX_CLASS_EXPAND { + return None; + } + result.push(b); + } + } + } else if !info.can_be_empty { + return None; + } + if !info.can_be_empty { + break; + } + } + if result.is_empty() { + None + } else { + Some(result) + } +} + +fn collect_last(infos: &[HirInfo]) -> Option { + let mut result = InlineArray::new(); + for info in infos.iter().rev() { + if let Some(ref bytes) = info.last { + for &b in bytes.iter() { + if !result.contains(&b) { + if result.len() >= MAX_CLASS_EXPAND { + return None; + } + result.push(b); + } + } + } else if !info.can_be_empty { + return None; + } + if !info.can_be_empty { + break; + } + } + if result.is_empty() { + None + } else { + Some(result) + } +} + +fn merge_byte_sets<'a>(iter: impl Iterator>) -> Option { + let mut result = InlineArray::new(); + for opt in iter { + let bytes = opt.as_ref()?; + + for &b in bytes.iter() { + if !result.contains(&b) { + if result.len() >= MAX_CLASS_EXPAND { + return None; + } + result.push(b); + } + } + } + if result.is_empty() { + None + } else { + Some(result) + } +} + +fn simplify_and(children: Vec) -> BigramQuery { + let mut flat: Vec = Vec::new(); + for child in children { + match child { + BigramQuery::Any => {} + BigramQuery::And(inner) => flat.extend(inner), + other => flat.push(other), + } + } + match flat.len() { + 0 => BigramQuery::Any, + 1 => flat.into_iter().next().unwrap(), + _ => BigramQuery::And(flat), + } +} + +fn simplify_or(children: Vec) -> BigramQuery { + if children.iter().any(|c| c.is_any()) { + return BigramQuery::Any; + } + let mut flat: Vec = Vec::new(); + for child in children { + match child { + BigramQuery::Or(inner) => flat.extend(inner), + other => flat.push(other), + } + } + match flat.len() { + 0 => BigramQuery::Any, + 1 => flat.into_iter().next().unwrap(), + _ => BigramQuery::Or(flat), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bigram_filter::BigramIndexBuilder; + + /// Build a tiny index from the given file contents for testing. + fn build_test_index(files: &[&[u8]]) -> BigramFilter { + let n = files.len(); + let consec_builder = BigramIndexBuilder::new(n); + let skip_builder = BigramIndexBuilder::new(n); + for (i, content) in files.iter().enumerate() { + consec_builder.add_file_content(&skip_builder, i, content); + } + let mut idx = consec_builder.compress(Some(0)); + idx.set_skip_index(skip_builder.compress(Some(0))); + idx + } + + #[test] + fn literal_pattern() { + let idx = build_test_index(&[ + b"hello world", // 0: contains "hello" + b"goodbye world", // 1: no "hello" + b"say hello there", // 2: contains "hello" + ]); + + let q = regex_to_bigram_query("hello"); + assert!(!q.is_any()); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + assert!(!BigramFilter::is_candidate(&candidates, 1)); + assert!(BigramFilter::is_candidate(&candidates, 2)); + } + + #[test] + fn alternation() { + let idx = build_test_index(&[ + b"has foo in it", // 0 + b"has bar in it", // 1 + b"has xyz in it", // 2 + ]); + + let q = regex_to_bigram_query("foo|bar"); + assert!(!q.is_any()); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + assert!(BigramFilter::is_candidate(&candidates, 1)); + // xyz doesn't contain foo or bar bigrams + assert!(!BigramFilter::is_candidate(&candidates, 2)); + } + + #[test] + fn wildcard_concat() { + let idx = build_test_index(&[ + b"foo something bar", // 0 + b"foo only", // 1: has foo but not bar + b"only bar", // 2: has bar but not foo + ]); + + let q = regex_to_bigram_query("foo.*bar"); + assert!(!q.is_any()); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + // file 1 and 2 should be filtered (missing bigrams from "bar" / "foo") + assert!(!BigramFilter::is_candidate(&candidates, 1)); + assert!(!BigramFilter::is_candidate(&candidates, 2)); + } + + #[test] + fn sparse1_across_dot() { + // "a.b" should produce a skip-1 bigram (a,b) + let idx = build_test_index(&[ + b"axb", // 0: has sparse-1 (a,b) + b"ayb", // 1: has sparse-1 (a,b) + b"xyz", // 2: no (a,b) at all + ]); + + let q = regex_to_bigram_query("a.b"); + assert!(!q.is_any()); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + assert!(BigramFilter::is_candidate(&candidates, 1)); + assert!(!BigramFilter::is_candidate(&candidates, 2)); + } + + #[test] + fn sparse1_across_digit() { + // "foo\dbar" -> sparse-1 (o,b) across \d + let idx = build_test_index(&[ + b"foo3bar baz", // 0: has all bigrams + b"foobar baz", // 1: has consecutive (o,b) but pattern needs sparse-1 + b"xyz only", // 2: no relevant bigrams + ]); + + let q = regex_to_bigram_query(r"foo\dbar"); + assert!(!q.is_any()); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + // file 1 may or may not match depending on what bigrams are in the index + // (it has all the literal bigrams and also o,b as both consec and skip-1) + // The important thing is file 2 is excluded: + assert!(!BigramFilter::is_candidate(&candidates, 2)); + } + + #[test] + fn pure_wildcard_is_any() { + let q = regex_to_bigram_query(".*"); + assert!(q.is_any()); + } + + #[test] + fn single_char_is_any() { + let q = regex_to_bigram_query("a"); + assert!(q.is_any()); + } + + #[test] + fn invalid_regex_is_any() { + let q = regex_to_bigram_query("[invalid"); + assert!(q.is_any()); + } + + #[test] + fn optional_group_excluded() { + let q = regex_to_bigram_query("foo(bar)?baz"); + assert!(!q.is_any()); + + let idx = build_test_index(&[ + b"foobaz content", // 0: has foo+baz bigrams (bar absent) + b"foobarbaz content", // 1: has everything + b"xyz only", // 2: nothing + ]); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + assert!(BigramFilter::is_candidate(&candidates, 1)); + assert!(!BigramFilter::is_candidate(&candidates, 2)); + } + + #[test] + fn repetition_min2_cross_boundary() { + // (ab){2,} -> bigram "ab" + cross-boundary "b","a" + let q = regex_to_bigram_query("(ab){2,}"); + assert!(!q.is_any()); + + let idx = build_test_index(&[ + b"ababab", // 0: has "ab" and "b"->"a" + b"abonly", // 1: has "ab" but not "b"->"a" + b"xyz", // 2: nothing + ]); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + assert!(!BigramFilter::is_candidate(&candidates, 2)); + } + + #[test] + fn two_dots_no_sparse1() { + let q = regex_to_bigram_query("a..b"); + // Single-char literals with 2 unknown bytes between -> Any + assert!(q.is_any()); + } + + #[test] + fn character_class_cross_boundary() { + // [abc]de -> cross-boundary OR(ad,bd,cd) + bigram de + // All three class variants must appear in the corpus so the OR + // branches are tracked in the index (untracked bigrams make the + // OR conservatively return None, which is correct but untestable). + let idx = build_test_index(&[ + b"ade content", // 0: has ad + b"bde content", // 1: has bd + b"cde content", // 2: has cd + b"xde content", // 3: has de but not ad/bd/cd + ]); + + let q = regex_to_bigram_query("[abc]de"); + assert!(!q.is_any()); + + let candidates = q.evaluate(&idx).unwrap(); + assert!(BigramFilter::is_candidate(&candidates, 0)); + assert!(BigramFilter::is_candidate(&candidates, 1)); + assert!(BigramFilter::is_candidate(&candidates, 2)); + // file 3 doesn't have ad/bd/cd so should be filtered + assert!(!BigramFilter::is_candidate(&candidates, 3)); + } + + fn has_consec(q: &BigramQuery, a: u8, b: u8) -> bool { + let Some(key) = consec_key(a, b) else { + return false; + }; + match q { + BigramQuery::Consec(k) => *k == key, + BigramQuery::And(cs) | BigramQuery::Or(cs) => cs.iter().any(|c| has_consec(c, a, b)), + _ => false, + } + } + + fn has_skip1(q: &BigramQuery, a: u8, b: u8) -> bool { + let Some(key) = consec_key(a, b) else { + return false; + }; + match q { + BigramQuery::Skip1(k) => *k == key, + BigramQuery::And(cs) | BigramQuery::Or(cs) => cs.iter().any(|c| has_skip1(c, a, b)), + _ => false, + } + } + + /// Bigram expectation: `("ab", is_skip1)`. + /// The 2-char str is the byte pair; C = consecutive, S = skip-1. + type Bg = (&'static str, bool); + const C: bool = false; + const S: bool = true; + + /// Top 15+ commonly used regex patterns from + /// https://digitalfortress.tech/tips/top-15-commonly-used-regex/ + /// plus typical grep patterns used by agentic tools. + /// + /// Each entry: `(regex, Option<&[Bg]>)`. + /// - `None` -> pure classes / unsupported syntax, Any is acceptable. + /// - `Some(&[..])` -> must be non-Any, and every listed bigram must appear. + #[test] + fn common_regex_patterns() { + #[rustfmt::skip] + let cases: &[(&str, Option<&[Bg]>)] = &[ + (r"^\d+$", None), // 1. whole numbers + (r"^\d*\.\d+$", None), // 2. decimals + (r"^\d*(\.\d+)?$", None), // 3. whole + decimal + (r"^-?\d*(\.\d+)?$", None), // 4. neg/pos decimal + (r"[-]?[0-9]+[,.]?[0-9]*([/][0-9]+[,.]?[0-9]*)*", None), // 5. fractions + (r"^[a-zA-Z0-9]*$", None), // 6. alphanumeric + (r"^[a-zA-Z0-9 ]*$", None), // 7. alphanum + space + (r"^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})*$", None), // 8. email + (r"^([a-z0-9_\.\+-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$", None), // 9. email v2 + (r"(?=(.*[0-9]))(?=.*[!@#$%^&*()\[\]{}\-_+=~`|:;<>,./?\x5c])(?=.*[a-z])(?=(.*[A-Z]))(?=(.*)).{8,}", None), // 10. complex pw + (r"(?=(.*[0-9]))((?=.*[A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z]))^.{8,}$", None), // 11. moderate pw + (r"^[a-z0-9_-]{3,16}$", None), // 12. username + (r"(https?://)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)", None), // 14. URL optional + (r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", None), // 15. IPv4 + (r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))", None), // 16. IPv6 + (r"[12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])", None), // 17. date + (r"^(0?[1-9]|1[0-2]):[0-5][0-9]$", None), // 18. time 12h + (r"((1[0-2]|0?[1-9]):([0-5][0-9]) ?([AaPp][Mm]))", None), // 19. time AM/PM + (r"^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$", None), // 20. time 24h + (r"^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$", None), // 21. time 24h v2 + (r"(?:[01]\d|2[0123]):(?:[012345]\d):(?:[012345]\d)", None), // 22. time+sec + (r"|<.+[\W]>", None), // 23. HTML tag + (r"\bon\w+=\S+(?=.*>)", None), // 24. inline JS + (r"^[a-z0-9]+(?:-[a-z0-9]+)*$", None), // 25. slug + (r"(\b\w+\b)(?=.*\b\1\b)", None), // 26. dup words + (r"^[\w,\s-]+\.[A-Za-z]{3}$", None), // 27. filename + (r"^[A-PR-WY][1-9]\d\s?\d{4}[1-9]$", None), // 28. HK ID + + // 13. URL with required protocol + (r"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)", Some(&[ + ("ht", C), ("tt", C), ("tp", C), // from "http" + ("ht", S), ("tp", S), // from "http" skip-1 + (":/", C), ("//", C), // from "://" + ])), + + // 29. fn\s+\w+ + (r"fn\s+\w+", Some(&[ + ("fn", C), // from "fn" + ("n ", C), // cross-boundary: 'n' -> \s starts ' ' + ])), + + // 30. use\s+crate:: + (r"use\s+crate::", Some(&[ + ("us", C), ("se", C), ("ue", S), // from "use" + ("cr", C), ("ra", C), ("at", C), // from "crate" + ("te", C), ("::", C), + ("ca", S), ("rt", S), ("ae", S), // "crate" skip-1 + ])), + + // 31. unwrap\(\)|expect\( + (r"unwrap\(\)|expect\(", Some(&[ + ("nw", C), ("wr", C), ("ra", C), // "unwrap(" + ("ap", C), ("p(", C), + ("xp", C), ("pe", C), ("ec", C), // "expect(" + ("ct", C), ("t(", C), + ])), + + // 32. TODO|FIXME|HACK + (r"TODO|FIXME|HACK", Some(&[ + ("to", C), ("od", C), ("do", C), // "TODO" + ("fi", C), ("ix", C), ("xm", C), // "FIXME" + ("me", C), + ("ha", C), ("ac", C), ("ck", C), // "HACK" + ("hc", S), ("ak", S), // "HACK" skip-1 + ])), + ]; + + for (i, &(pattern, expected)) in cases.iter().enumerate() { + let q = regex_to_bigram_query(pattern); + + if let Some(bigrams) = expected { + assert!( + !q.is_any(), + "#{i} {pattern:?}: expected bigrams but got Any" + ); + + for &(pair, skip) in bigrams { + let b = pair.as_bytes(); + debug_assert_eq!(b.len(), 2, "bigram must be 2 chars: {pair:?}"); + let found = if skip { + has_skip1(&q, b[0], b[1]) + } else { + has_consec(&q, b[0], b[1]) + }; + let kind = if skip { "skip-1" } else { "consec" }; + assert!(found, "#{i} {pattern:?}: missing {kind} bigram {pair:?}"); + } + } + } + } +} diff --git a/crates/fff-core/src/constants.rs b/crates/fff-core/src/constants.rs new file mode 100644 index 0000000..95cdc2b --- /dev/null +++ b/crates/fff-core/src/constants.rs @@ -0,0 +1,39 @@ +/// Largest file whose full content fff will touch: the default grep read cap +/// (`GrepSearchOptions::max_file_size`) and the content-cache mmap cap +/// (`ContentCacheBudget::max_file_size`). Binary detection also streams up to +/// this far so nothing grep would read is left unclassified. +pub const MAX_FFFILE_SIZE: u64 = 10 * 1024 * 1024; + +/// Upper bound on a file the bigram builder will build, if the file is very large there is a +/// big probability it will only bloat the available bigrams and will anyway pop ut from the prefilter +pub const MAX_INDEXABLE_FILE_SIZE: usize = 2 * 1024 * 1024; + +/// Total bytes the persistent content mmap cache may hold for a small repo. +pub const MAX_CACHED_CONTENT_BYTES: u64 = 512 * 1024 * 1024; + +/// Files below one page waste the remainder when mmapped, so the cache skips +/// them and falls back to chunked reads. Unused on Windows (no content cache). +#[cfg(all(not(target_os = "windows"), target_arch = "aarch64"))] +pub const MMAP_THRESHOLD: u64 = 16 * 1024; +#[cfg(all(not(target_os = "windows"), not(target_arch = "aarch64")))] +pub const MMAP_THRESHOLD: u64 = 4 * 1024; + +/// Capacity reserved for files the watcher discovers after the initial scan; +/// exceeding it forces a full rescan. +pub const MAX_OVERFLOW_FILES: usize = 1024; + +/// Fresh-mmap threshold: files at or above this size get mmapped directly on +/// cache miss instead of chunked reads into Vec. Empirically tuned per-platform. +/// Only referenced on Unix; Windows uses the `std::fs::read` fallback so this +/// constant is gated to non-Windows targets to keep `-D unused-imports` happy. +#[cfg(target_os = "macos")] +pub const FRESH_MMAP_THRESHOLD: u64 = 1024 * 1024; +#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))] +pub const FRESH_MMAP_THRESHOLD: u64 = 256 * 1024; + +// we do not support 32kb path limit on windows +#[cfg(target_os = "windows")] +pub const PATH_BUF_SIZE: usize = 4096; + +#[cfg(not(target_os = "windows"))] +pub const PATH_BUF_SIZE: usize = libc::PATH_MAX as usize; diff --git a/crates/fff-core/src/constraints.rs b/crates/fff-core/src/constraints.rs new file mode 100644 index 0000000..c70e9bb --- /dev/null +++ b/crates/fff-core/src/constraints.rs @@ -0,0 +1,932 @@ +//! Constraint-based prefiltering for search queries. + +use fff_query_parser::{Constraint, GitStatusFilter}; +use smallvec::SmallVec; + +use crate::git::is_modified_status; +use crate::simd_path::ArenaPtr; +use crate::simd_string_utils::memmem::find_case_insensitive_short; + +const PAR_THRESHOLD: usize = 10_000; + +pub(crate) trait Constrainable { + fn write_file_name(&self, arena: ArenaPtr, out: &mut String); + fn git_status(&self) -> Option; + fn write_relative_path(&self, arena: ArenaPtr, out: &mut String); + fn is_overflow(&self) -> bool; +} + +/// Stored/canonical paths use `/`; also accept `\` so a Windows user typing +/// a native separator in a query still matches. +#[inline] +fn is_path_sep(b: u8) -> bool { + b == b'/' || b == b'\\' +} + +#[inline] +fn path_slice_eq(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + a.iter().zip(b).all(|(x, y)| { + if is_path_sep(*x) && is_path_sep(*y) { + true + } else { + x.eq_ignore_ascii_case(y) + } + }) +} + +/// Path ends with suffix at a path-separator boundary (case-insensitive). +#[inline] +pub fn path_ends_with_suffix(path: &str, suffix: &str) -> bool { + let path_bytes = path.as_bytes(); + let suffix_bytes = suffix.as_bytes(); + if path_bytes.len() < suffix_bytes.len() { + return false; + } + + let start = path.len() - suffix.len(); + + // Multi-byte UTF-8 may put `start` inside a char. + if !path.is_char_boundary(start) { + return false; + } + + if !path_slice_eq(&path_bytes[start..], suffix_bytes) { + return false; + } + + // Exact or preceded by a separator. Scan backward past any multi-byte + // continuation bytes to find the preceding ASCII byte. + if start == 0 { + return true; + } + let mut i = start; + while i > 0 { + i -= 1; + if path_bytes[i] < 128 { + return is_path_sep(path_bytes[i]); + } + } + false +} + +#[inline] +pub fn file_has_extension(file_name: &str, ext: &str) -> bool { + let name_bytes = file_name.as_bytes(); + let ext_bytes = ext.as_bytes(); + if name_bytes.len() <= ext_bytes.len() + 1 { + return false; + } + let start = name_bytes.len() - ext_bytes.len() - 1; + if start > 0 && !file_name.is_char_boundary(start) { + return false; + } + name_bytes.get(start) == Some(&b'.') && name_bytes[start + 1..].eq_ignore_ascii_case(ext_bytes) +} + +/// Matches multi-segment queries like `libswscale/aarch64`. +#[inline] +pub fn path_contains_segment(path: &str, segment: &str) -> bool { + let path_bytes = path.as_bytes(); + let segment_bytes = segment.as_bytes(); + let segment_len = segment_bytes.len(); + + if path_bytes.len() > segment_len + && is_path_sep(path_bytes[segment_len]) + && path.is_char_boundary(segment_len) + && path_slice_eq(&path_bytes[..segment_len], segment_bytes) + { + return true; + } + + if path_bytes.len() < segment_len + 2 { + return false; + } + + for i in 0..path_bytes.len().saturating_sub(segment_len + 1) { + if is_path_sep(path_bytes[i]) { + let start = i + 1; + let end = start + segment_len; + if end < path_bytes.len() + && is_path_sep(path_bytes[end]) + && path.is_char_boundary(start) + && path.is_char_boundary(end) + && path_slice_eq(&path_bytes[start..end], segment_bytes) + { + return true; + } + } + } + false +} + +/// Returns `None` if no constraints are present, `Some(filtered)` otherwise. +/// +/// Constraint semantics: +/// - All `Extension` constraints OR together (file matches if ANY extension hits). +/// They're split out up front so the per-item loop reads the OR predicate as a +/// single short-circuit check, not as N AND-merged sub-constraints. +/// - Every other constraint kind ANDs (file matches only if ALL hold). They're +/// evaluated in order with short-circuit on first failure. +pub(crate) fn apply_constraints<'a, T: Constrainable + Sync>( + items: &'a [T], + constraints: &[Constraint<'_>], + base_arena: ArenaPtr, + overflow_arena: ArenaPtr, +) -> Option> { + if constraints.is_empty() { + return None; + } + let plan = ConstraintPlan::build(constraints, items, base_arena, overflow_arena); + Some(plan.run(items, base_arena, overflow_arena)) +} + +#[cfg(feature = "zlob")] +type GlobPattern = zlob::ZlobPattern; +#[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] +type GlobPattern = globset::GlobMatcher; + +/// How `Constraint::Glob` is evaluated for each item. +enum GlobStrategy { + /// No Glob constraint present. + None, + /// Pure-glob workload (no Extension filter to reject items first). + /// Batch all paths through zlob/globset once; per-item check is a Vec lookup. + Prepass(Vec>), + /// Mixed workload (Extension filter present). Compile patterns up front, then + /// only run them on items that survive the cheap Extension OR check. + /// `None` slot = compile failure -> never matches; preserves index alignment. + Inline(Vec>), +} + +/// Bundles preprocessed constraints for the per-item evaluator. +pub(crate) struct ConstraintPlan<'q, 'c> { + /// OR semantics — file passes if ANY extension matches. Empty = no ext filter. + extensions: SmallVec<[&'q str; 8]>, + /// AND semantics — file passes only if ALL match. + rest: SmallVec<[&'c Constraint<'q>; 8]>, + glob: GlobStrategy, +} + +pub(crate) struct ConstraintsBuffers { + fname: String, + path: String, +} + +impl ConstraintsBuffers { + pub(crate) fn new() -> Self { + Self { + fname: String::with_capacity(64), + path: String::with_capacity(64), + } + } +} + +impl<'q, 'c> ConstraintPlan<'q, 'c> { + pub(crate) fn build( + constraints: &'c [Constraint<'q>], + items: &[T], + base_arena: ArenaPtr, + overflow_arena: ArenaPtr, + ) -> Self { + let mut extensions = SmallVec::new(); + let mut rest: SmallVec<[&'c Constraint<'q>; 8]> = SmallVec::new(); + for c in constraints { + match c { + Constraint::Extension(ext) => extensions.push(*ext), + _ => rest.push(c), + } + } + let has_pre_filter = !extensions.is_empty() || rest.iter().any(|&c| !is_glob_node(c)); + let glob = build_glob_strategy(&rest, has_pre_filter, items, base_arena, overflow_arena); + + Self { + extensions, + rest, + glob, + } + } + + fn run<'a, T: Constrainable + Sync>( + &self, + items: &'a [T], + base_arean: ArenaPtr, + overflow_arena: ArenaPtr, + ) -> Vec<&'a T> { + if items.len() >= PAR_THRESHOLD { + use rayon::prelude::*; + items + .par_iter() + .enumerate() + .map_init(ConstraintsBuffers::new, |scratch, (i, item)| { + self.matches(item, i, base_arean, overflow_arena, scratch) + .then_some(item) + }) + .flatten() + .collect() + } else { + let mut scratch = ConstraintsBuffers::new(); + items + .iter() + .enumerate() + .filter_map(|(i, item)| { + self.matches(item, i, base_arean, overflow_arena, &mut scratch) + .then_some(item) + }) + .collect() + } + } + + #[inline] + pub(crate) fn matches( + &self, + item: &T, + index: usize, + base_arena: ArenaPtr, + overflow_arena: ArenaPtr, + scratch: &mut ConstraintsBuffers, + ) -> bool { + let arena = if item.is_overflow() { + overflow_arena + } else { + base_arena + }; + + if !self.passes_extensions(item, arena, scratch) { + return false; + } + + let mut glob_idx = 0; + self.rest.iter().all(|c| { + evaluate( + item, + index, + c, + &self.glob, + &mut glob_idx, + false, + arena, + scratch, + ) + }) + } + + #[inline] + fn passes_extensions( + &self, + item: &T, + arena: ArenaPtr, + scratch: &mut ConstraintsBuffers, + ) -> bool { + if self.extensions.is_empty() { + return true; + } + item.write_file_name(arena, &mut scratch.fname); + self.extensions + .iter() + .any(|ext| file_has_extension(&scratch.fname, ext)) + } +} + +#[inline] +#[allow(clippy::too_many_arguments)] +fn evaluate( + item: &T, + index: usize, + constraint: &Constraint<'_>, + glob: &GlobStrategy, + glob_idx: &mut usize, + negate: bool, + arena: ArenaPtr, + scratch: &mut ConstraintsBuffers, +) -> bool { + let raw = match constraint { + Constraint::Glob(_) => { + let m = match glob { + GlobStrategy::None => true, + GlobStrategy::Prepass(masks) => masks + .get(*glob_idx) + .and_then(|mask| mask.get(index).copied()) + .unwrap_or(false), + GlobStrategy::Inline(patterns) => { + item.write_relative_path(arena, &mut scratch.path); + patterns + .get(*glob_idx) + .and_then(|p| p.as_ref()) + .map(|p| compiled_matches(p, &scratch.path)) + .unwrap_or(false) + } + }; + *glob_idx += 1; + m + } + // Reachable only via `Not(Extension(_))` — bare extensions are split out + // up front and handled in `passes_extensions`. + Constraint::Extension(ext) => { + item.write_file_name(arena, &mut scratch.fname); + file_has_extension(&scratch.fname, ext) + } + Constraint::PathSegment(segment) => { + item.write_relative_path(arena, &mut scratch.path); + path_contains_segment(&scratch.path, segment) + } + Constraint::FilePath(suffix) => { + item.write_relative_path(arena, &mut scratch.path); + path_ends_with_suffix(&scratch.path, suffix) + } + Constraint::Text(text) => { + // Only meaningful under negation (used as exclude filter). + item.write_relative_path(arena, &mut scratch.path); + find_case_insensitive_short(scratch.path.as_bytes(), text.as_bytes()).is_some() + } + Constraint::GitStatus(filter) => matches_git_status(item.git_status(), filter), + Constraint::Not(inner) => { + return evaluate(item, index, inner, glob, glob_idx, !negate, arena, scratch); + } + // Pass-throughs — handled at higher levels. + Constraint::Parts(_) | Constraint::Exclude(_) | Constraint::FileType(_) => true, + }; + if negate { !raw } else { raw } +} + +#[inline] +fn matches_git_status(status: Option, filter: &GitStatusFilter) -> bool { + match (status, filter) { + (Some(s), GitStatusFilter::Modified) => is_modified_status(s), + (Some(s), GitStatusFilter::Untracked) => s.contains(git2::Status::WT_NEW), + (Some(s), GitStatusFilter::Staged) => s.intersects( + git2::Status::INDEX_NEW + | git2::Status::INDEX_MODIFIED + | git2::Status::INDEX_DELETED + | git2::Status::INDEX_RENAMED + | git2::Status::INDEX_TYPECHANGE, + ), + (Some(s), GitStatusFilter::Unmodified) => s.is_empty(), + (None, GitStatusFilter::Unmodified) => true, + (None, _) => false, + } +} + +#[inline] +#[cfg(feature = "zlob")] +fn compiled_matches(p: &GlobPattern, path: &str) -> bool { + p.matches_default(path) +} + +#[inline] +#[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] +fn compiled_matches(p: &GlobPattern, path: &str) -> bool { + p.is_match(path) +} + +/// Decide between batch prepass and inline compiled patterns. +/// +/// `has_pre_filter` = true when something cheaper than glob can reject items first +/// (extensions OR non-glob constraints in `rest`). In that case inline pays glob +/// cost only on survivors and beats prepass on every workload we benched. Pure-glob +/// (no pre-filter) takes prepass — single batched zlob call beats N inline matches. +fn build_glob_strategy( + rest: &[&Constraint<'_>], + has_pre_filter: bool, + items: &[T], + arena: ArenaPtr, + overflow_arena: ArenaPtr, +) -> GlobStrategy { + if !contains_glob(rest) { + return GlobStrategy::None; + } + if has_pre_filter { + return GlobStrategy::Inline(compile_globs(rest)); + } + let buf = PathBuffer::collect(items, arena, overflow_arena); + let path_refs = buf.as_strs(); + GlobStrategy::Prepass(precompute_masks(rest, &path_refs)) +} + +/// `Glob` or `Not(Glob)` — the constraint kinds whose evaluation goes through +/// the GlobStrategy. Everything else can pre-reject items before glob runs. +fn is_glob_node(c: &Constraint<'_>) -> bool { + match c { + Constraint::Glob(_) => true, + Constraint::Not(inner) => is_glob_node(inner), + _ => false, + } +} + +fn contains_glob(rest: &[&Constraint<'_>]) -> bool { + rest.iter().any(|c| is_glob_node(c)) +} + +/// Contiguous byte buffer holding every item's `relative_path`. Single allocation +/// instead of N `String`s. On Windows the in-place pass folds `\\` -> `/` so the +/// glob library sees a canonical separator. +struct PathBuffer { + bytes: Vec, + offsets: Vec<(usize, usize)>, +} + +impl PathBuffer { + fn collect(items: &[T], arena: ArenaPtr, overflow_arena: ArenaPtr) -> Self { + let mut bytes = Vec::::new(); + let mut offsets = Vec::with_capacity(items.len()); + let mut tmp = String::with_capacity(64); + for item in items { + let item_arena = if item.is_overflow() { + overflow_arena + } else { + arena + }; + let start = bytes.len(); + item.write_relative_path(item_arena, &mut tmp); + bytes.extend_from_slice(tmp.as_bytes()); + offsets.push((start, bytes.len() - start)); + } + Self { bytes, offsets } + } + + fn as_strs(&self) -> Vec<&str> { + self.offsets + .iter() + .map(|&(off, len)| unsafe { + std::str::from_utf8_unchecked(&self.bytes[off..off + len]) + }) + .collect() + } +} + +fn precompute_masks(rest: &[&Constraint<'_>], paths: &[&str]) -> Vec> { + let mut out = Vec::new(); + for c in rest { + walk_globs(c, &mut |pattern| { + out.push(match_glob_pattern(pattern, paths)) + }); + } + out +} + +fn compile_globs(rest: &[&Constraint<'_>]) -> Vec> { + let mut out = Vec::new(); + for c in rest { + walk_globs(c, &mut |pattern| out.push(compile_one(pattern))); + } + out +} + +/// Visit every Glob (including ones nested under Not) in constraint walk order. +/// Order matters: `glob_idx` in the per-item evaluator increments by one per Glob node. +fn walk_globs(c: &Constraint<'_>, f: &mut F) { + match c { + Constraint::Glob(p) => f(p), + Constraint::Not(inner) => walk_globs(inner, f), + _ => {} + } +} + +#[cfg(feature = "zlob")] +fn compile_one(pattern: &str) -> Option { + zlob::ZlobPattern::compile(pattern, zlob::ZlobFlags::RECOMMENDED).ok() +} + +#[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] +fn compile_one(pattern: &str) -> Option { + globset::Glob::new(pattern) + .ok() + .map(|g| g.compile_matcher()) +} + +/// Build a `paths.len()`-sized bitmap. Vec beats AHashSet ~2× in the per-item +/// filter loop — no hashing, plain array indexing, sequential prefetcher-friendly. +#[cfg(feature = "zlob")] +fn match_glob_pattern(pattern: &str, paths: &[&str]) -> Vec { + let mut mask = vec![false; paths.len()]; + let Ok(hits) = zlob::zlob_match_paths_indices(pattern, paths, zlob::ZlobFlags::RECOMMENDED) + else { + return mask; + }; + for i in hits.to_iter() { + if i < mask.len() { + mask[i] = true; + } + } + mask +} + +#[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] +fn match_glob_pattern(pattern: &str, paths: &[&str]) -> Vec { + let mut mask = vec![false; paths.len()]; + let Ok(glob) = globset::Glob::new(pattern) else { + return mask; + }; + let matcher = glob.compile_matcher(); + if paths.len() >= PAR_THRESHOLD { + use rayon::prelude::*; + mask.par_iter_mut() + .zip(paths.par_iter()) + .for_each(|(slot, p)| *slot = matcher.is_match(p)); + } else { + for (slot, p) in mask.iter_mut().zip(paths.iter()) { + *slot = matcher.is_match(p); + } + } + mask +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Clone)] + struct TestItem { + relative_path: &'static str, + file_name: &'static str, + } + + impl Constrainable for TestItem { + fn write_file_name(&self, _arena: ArenaPtr, out: &mut String) { + out.clear(); + out.push_str(self.file_name); + } + + fn write_relative_path(&self, _arena: ArenaPtr, out: &mut String) { + out.clear(); + out.push_str(self.relative_path); + } + + fn git_status(&self) -> Option { + None + } + + fn is_overflow(&self) -> bool { + false + } + } + + #[test] + fn test_file_has_extension() { + assert!(file_has_extension("file.rs", "rs")); + assert!(file_has_extension("file.RS", "rs")); // case-insensitive + assert!(file_has_extension("file.test.rs", "rs")); + assert!(file_has_extension("a.rs", "rs")); + + assert!(!file_has_extension("file.tsx", "rs")); + assert!(!file_has_extension("rs", "rs")); // too short + assert!(!file_has_extension(".rs", "rs")); // just extension + assert!(!file_has_extension("file.rsx", "rs")); // different extension + assert!(!file_has_extension("filers", "rs")); // no dot + } + + #[test] + fn test_path_contains_segment() { + // Segment at start + assert!(path_contains_segment("src/lib.rs", "src")); + assert!(path_contains_segment("SRC/lib.rs", "src")); // case-insensitive + + // Segment in middle + assert!(path_contains_segment("app/src/lib.rs", "src")); + assert!(path_contains_segment("app/SRC/lib.rs", "src")); + + // Multiple levels + assert!(path_contains_segment("core/workflow/src/main.rs", "src")); + assert!(path_contains_segment( + "core/workflow/src/main.rs", + "workflow" + )); + assert!(path_contains_segment("core/workflow/src/main.rs", "core")); + + // Should not match partial segments + assert!(!path_contains_segment("source/lib.rs", "src")); + assert!(!path_contains_segment("mysrc/lib.rs", "src")); + + // Should not match filename + assert!(!path_contains_segment("lib/src", "src")); + + // Multi-segment constraints + assert!(path_contains_segment( + "libswscale/aarch64/input.S", + "libswscale/aarch64" + )); + assert!(path_contains_segment( + "foo/libswscale/aarch64/input.S", + "libswscale/aarch64" + )); + assert!(path_contains_segment( + "foo/LibSwscale/AArch64/input.S", + "libswscale/aarch64" + )); // case-insensitive + assert!(!path_contains_segment( + "xlibswscale/aarch64/input.S", + "libswscale/aarch64" + )); // partial match at start + assert!(!path_contains_segment( + "foo/libswscale/aarch64x/input.S", + "libswscale/aarch64" + )); // partial match at end + assert!(path_contains_segment( + "crates/fff-core/src/grep.rs", + "fff-core/src" + )); + + // Edge cases + assert!(!path_contains_segment("", "src")); + assert!(!path_contains_segment("src", "src")); // no trailing slash + } + + #[cfg(windows)] + #[test] + fn test_path_contains_segment_accepts_backslash() { + assert!(path_contains_segment("src\\lib.rs", "src")); + assert!(path_contains_segment( + "app\\modules\\src\\services\\x.lua", + "src" + )); + assert!(path_contains_segment("app\\SRC\\x.lua", "src")); + + assert!(path_contains_segment( + "foo\\libswscale\\aarch64\\input.S", + "libswscale/aarch64" + )); + assert!(path_contains_segment( + "crates\\fff-core\\src\\grep.rs", + "fff-core/src" + )); + + assert!(!path_contains_segment("mysrc\\lib.rs", "src")); + assert!(!path_contains_segment( + "xlibswscale\\aarch64\\in.S", + "libswscale/aarch64" + )); + } + + #[test] + fn test_path_ends_with_suffix() { + // Exact match + assert!(path_ends_with_suffix( + "libswscale/input.c", + "libswscale/input.c" + )); + + // Suffix match at / boundary + assert!(path_ends_with_suffix( + "foo/libswscale/input.c", + "libswscale/input.c" + )); + + // Deep nesting + assert!(path_ends_with_suffix( + "a/b/c/libswscale/input.c", + "libswscale/input.c" + )); + + // No boundary — partial directory name + assert!(!path_ends_with_suffix( + "xlibswscale/input.c", + "libswscale/input.c" + )); + + // Case insensitive + assert!(path_ends_with_suffix( + "foo/LibSwscale/Input.C", + "libswscale/input.c" + )); + + // Single file name + assert!(path_ends_with_suffix("input.c", "input.c")); + assert!(!path_ends_with_suffix("xinput.c", "input.c")); + + // Suffix longer than path + assert!(!path_ends_with_suffix("input.c", "foo/input.c")); + + // Simple path + assert!(path_ends_with_suffix("src/main.rs", "src/main.rs")); + assert!(path_ends_with_suffix("crates/src/main.rs", "src/main.rs")); + } + + #[cfg(windows)] + #[test] + fn test_path_ends_with_suffix_accepts_backslash() { + assert!(path_ends_with_suffix( + "app\\modules\\src\\services\\handler.lua", + "services/handler.lua" + )); + assert!(path_ends_with_suffix( + "foo\\libswscale\\input.c", + "libswscale/input.c" + )); + assert!(!path_ends_with_suffix( + "xlibswscale\\input.c", + "libswscale/input.c" + )); + } + + #[test] + fn test_path_ends_with_suffix_does_not_panic_on_unicode_suffix() { + assert!(!path_ends_with_suffix("유니코드_파일_테스트.csv", "트.c")); + assert!(path_ends_with_suffix( + "data/유니코드_파일_테스트.csv", + "유니코드_파일_테스트.csv" + )); + } + + #[test] + fn test_path_ends_with_suffix_unicode_apostrophe_mismatch() { + assert!(!path_ends_with_suffix( + "dir/\u{2019}bar/file.txt", + "'bar/file.txt" + )); + } + + #[test] + fn test_path_ends_with_suffix_unicode_space_mismatch() { + assert!(!path_ends_with_suffix( + "dir/\u{202f}am/file.txt", + " am/file.txt" + )); + } + + #[test] + fn test_path_contains_segment_does_not_panic_on_unicode_segment() { + assert!(!path_contains_segment("문서/notes.txt", "문x")); + assert!(path_contains_segment("프로젝트/문서/notes.txt", "문서")); + } + + #[test] + fn test_path_contains_segment_unicode_no_panic() { + assert!(!path_contains_segment( + "Library/Cloud/Project\u{2019}s Folder/books.ttl", + "Project's Folder" + )); + } + + #[test] + fn test_file_has_extension_unicode_no_panic() { + assert!(!file_has_extension("cat\u{00e9}.rs", "s")); + } + + #[test] + fn test_file_has_extension_unicode_filename() { + assert!(file_has_extension("운영-가이드.md", "md")); + assert!(file_has_extension("테스트.csv", "csv")); + assert!(!file_has_extension("테스트.csv", "md")); + } + + #[test] + fn test_apply_constraints_file_path_with_unicode_suffix() { + let arena_ptr = ArenaPtr(std::ptr::null()); + + let item = TestItem { + relative_path: "data/유니코드_파일_테스트.csv", + file_name: "유니코드_파일_테스트.csv", + }; + + let exact = [Constraint::FilePath("유니코드_파일_테스트.csv")]; + let mismatch = [Constraint::FilePath("트.c")]; + + let exact_items = [item.clone()]; + let exact_matches = apply_constraints(&exact_items, &exact, arena_ptr, arena_ptr) + .expect("constraints applied"); + assert_eq!(exact_matches.len(), 1); + + let mismatch_items = [item]; + let mismatch_matches = apply_constraints(&mismatch_items, &mismatch, arena_ptr, arena_ptr) + .expect("constraints applied"); + assert!(mismatch_matches.is_empty()); + } + + #[test] + fn test_unicode_path_no_panic_real_korean_cases() { + // Real Korean paths that caused panics + let path1 = "Downloads/(커리큘럼) hermes agent_정승현님 - 1차 커리큘럼 (강사님 작성).csv"; + let path2 = "hermes-agent-lecture-materials/세부_커리큘럼_최종.csv"; + let path3 = "projects/fastcampus-hermes-agent-curriculum/chapters/part-02-Hermes-설치-및-기본-사용/section-02-doctor로-설치-상태-검증/research/03-fix가-자동-수정하는-것과-못하는-것.md"; + + // These must not panic regardless of segment/suffix used + assert!(!path_contains_segment(path1, "작성")); + assert!(!path_ends_with_suffix(path1, "작성.csv")); + assert!(!path_contains_segment(path2, "최종")); + assert!(!path_ends_with_suffix(path2, "최종.csv")); + assert!(!path_contains_segment(path3, "수정")); + assert!(!path_ends_with_suffix(path3, "것.md")); + + // Positive cases should still work + assert!(path_contains_segment( + path2, + "hermes-agent-lecture-materials" + )); + assert!(path_ends_with_suffix( + path1, + "(커리큘럼) hermes agent_정승현님 - 1차 커리큘럼 (강사님 작성).csv" + )); + assert!(path_ends_with_suffix(path2, "세부_커리큘럼_최종.csv")); + } + + #[test] + fn test_negated_glob_excludes_matching_files() { + let arena_ptr = ArenaPtr(std::ptr::null()); + + let items = vec![ + TestItem { + relative_path: "src/main.rs", + file_name: "main.rs", + }, + TestItem { + relative_path: "src/lib.ts", + file_name: "lib.ts", + }, + TestItem { + relative_path: "include/fff.h", + file_name: "fff.h", + }, + ]; + + // Not(Glob("**/*.rs")) should exclude .rs files + let constraints = vec![Constraint::Not(Box::new(Constraint::Glob("**/*.rs")))]; + let result = apply_constraints(&items, &constraints, arena_ptr, arena_ptr).unwrap(); + let paths: Vec<&str> = result.iter().map(|i| i.relative_path).collect(); + assert!( + !paths.contains(&"src/main.rs"), + "rs file should be excluded" + ); + assert!(paths.contains(&"src/lib.ts"), "ts file should be included"); + assert!( + paths.contains(&"include/fff.h"), + "h file should be included" + ); + } + + #[test] + fn test_inline_glob_path_matches_prepass() { + // Mixed (extensions + glob) takes the inline-compiled path. + // Pure glob takes the prepass bitmap path. Both must give identical results. + let arena_ptr = ArenaPtr(std::ptr::null()); + let items = vec![ + TestItem { + relative_path: "src/main.rs", + file_name: "main.rs", + }, + TestItem { + relative_path: "src/lib.ts", + file_name: "lib.ts", + }, + TestItem { + relative_path: "tests/foo.rs", + file_name: "foo.rs", + }, + TestItem { + relative_path: "docs/readme.md", + file_name: "readme.md", + }, + ]; + + let mixed = vec![Constraint::Extension("rs"), Constraint::Glob("src/**")]; + let mixed_paths: Vec<&str> = apply_constraints(&items, &mixed, arena_ptr, arena_ptr) + .unwrap() + .iter() + .map(|i| i.relative_path) + .collect(); + assert_eq!(mixed_paths, vec!["src/main.rs"]); + + let pure_glob = vec![Constraint::Glob("src/**")]; + let glob_paths: Vec<&str> = apply_constraints(&items, &pure_glob, arena_ptr, arena_ptr) + .unwrap() + .iter() + .map(|i| i.relative_path) + .collect(); + assert!(glob_paths.contains(&"src/main.rs")); + assert!(glob_paths.contains(&"src/lib.ts")); + assert_eq!(glob_paths.len(), 2); + } + + #[test] + fn test_inline_negated_glob_with_extension() { + // Mixed Not(Glob) on inline path — exercise the negate=true branch in + // glob_matches_inline through the Not->Glob recursion. + let arena_ptr = ArenaPtr(std::ptr::null()); + let items = vec![ + TestItem { + relative_path: "src/main.rs", + file_name: "main.rs", + }, + TestItem { + relative_path: "vendor/foo.rs", + file_name: "foo.rs", + }, + TestItem { + relative_path: "vendor/foo.ts", + file_name: "foo.ts", + }, + ]; + + let constraints = vec![ + Constraint::Extension("rs"), + Constraint::Not(Box::new(Constraint::Glob("vendor/**"))), + ]; + let paths: Vec<&str> = apply_constraints(&items, &constraints, arena_ptr, arena_ptr) + .unwrap() + .iter() + .map(|i| i.relative_path) + .collect(); + assert_eq!(paths, vec!["src/main.rs"]); + } +} diff --git a/crates/fff-core/src/dbs/db_healthcheck.rs b/crates/fff-core/src/dbs/db_healthcheck.rs new file mode 100644 index 0000000..77d8349 --- /dev/null +++ b/crates/fff-core/src/dbs/db_healthcheck.rs @@ -0,0 +1,39 @@ +use crate::error::Result; + +/// Health information about a database +#[derive(Debug, Clone)] +pub struct DbHealth { + /// Path to the database file + pub path: String, + /// Size on disk in bytes + pub disk_size: u64, + /// Entry counts by table name + pub entry_counts: Vec<(&'static str, u64)>, + /// Set to `false` if can not acquire the write lock + pub healthy: bool, +} + +pub trait DbHealthChecker { + fn get_env(&self) -> &heed::Env; + fn is_healthy(&self) -> bool; + /// Entries per database, each group has a static string label + fn count_entries(&self) -> Result>; + + /// Health summary of the database, returns summary struct + fn get_health(&self) -> Result { + let env = self.get_env(); + + let size = env + .real_disk_size() + .map_err(crate::error::Error::GenericDbError)?; + let path = env.path().to_string_lossy().to_string(); + let entry_counts = self.count_entries()?; + + Ok(DbHealth { + path, + disk_size: size, + entry_counts, + healthy: self.is_healthy(), + }) + } +} diff --git a/crates/fff-core/src/dbs/frecency.rs b/crates/fff-core/src/dbs/frecency.rs new file mode 100644 index 0000000..7413396 --- /dev/null +++ b/crates/fff-core/src/dbs/frecency.rs @@ -0,0 +1,531 @@ +use super::db_healthcheck::DbHealthChecker; +use super::lmdb::{DbHealth, LmdbStore, is_map_full}; +use crate::error::{Error, Result}; +use crate::file_picker::FFFMode; +use crate::git::is_modified_status; +use heed::types::{Bytes, SerdeBincode}; +use heed::{Database, Env}; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::{collections::VecDeque, path::Path}; + +const DECAY_CONSTANT: f64 = 0.0693; // ln(2)/10 for 10-day half-life +const SECONDS_PER_DAY: f64 = 86400.0; +const MAX_HISTORY_DAYS: f64 = 30.0; // Only consider accesses within 30 days +const MAX_TIMESTAMPS_PER_FILE: usize = 128; + +// AI mode: faster decay since AI sessions are shorter and more intense +const AI_DECAY_CONSTANT: f64 = 0.231; // ln(2)/3 for 3-day half-life +const AI_MAX_HISTORY_DAYS: f64 = 7.0; // Only consider accesses within 7 days + +#[derive(Debug)] +pub struct FrecencyTracker { + env: Env, + db: Database>>, + health: DbHealth, +} + +const MODIFICATION_THRESHOLDS: [(i64, u64); 5] = [ + (16, 60 * 2), // 2 minutes + (8, 60 * 15), // 15 minutes + (4, 60 * 60), // 1 hour + (2, 60 * 60 * 24), // 1 day + (1, 60 * 60 * 24 * 7), // 1 week +]; + +// AI mode: compressed thresholds since AI edits happen in rapid bursts +const AI_MODIFICATION_THRESHOLDS: [(i64, u64); 5] = [ + (16, 30), // 30 seconds + (8, 60 * 5), // 5 minutes + (4, 60 * 15), // 15 minutes + (2, 60 * 60), // 1 hour + (1, 60 * 60 * 4), // 4 hours +]; + +impl DbHealthChecker for FrecencyTracker { + fn get_env(&self) -> &heed::Env { + &self.env + } + + fn is_healthy(&self) -> bool { + self.health.is_healthy() + } + + fn count_entries(&self) -> Result> { + let rtxn = self + .env + .read_txn() + .map_err(|source| Error::DbStartReadTxn { + db: Self::LABEL, + source, + })?; + let count = self.db.len(&rtxn).map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + + Ok(vec![("absolute_frecency_entries", count)]) + } +} + +impl LmdbStore for FrecencyTracker { + const LABEL: &'static str = "frecency"; + // 10 MiB hard ceiling. Owner's db after years of use is ~560 KiB, so this + // leaves ~18× headroom while capping runaway growth (see GH issue #437). + const MAP_SIZE: usize = 10 * 1024 * 1024; + const MAX_DBS: u32 = 0; + // Nuke the db when it exceeds 8 MiB on disk — leaves a small margin under + // MAP_SIZE so we don't hit MDB_MAP_FULL before the open-time erase fires. + const SIZE_CAP_BYTES: u64 = 12 * 1024 * 1024; + + fn env(&self) -> &Env { + &self.env + } + + fn health(&self) -> &DbHealth { + &self.health + } + + fn purge_stale_data(env: &Env) -> Result<()> { + let (deleted, pruned) = Self::purge_stale_entries(env)?; + if deleted > 0 || pruned > 0 { + tracing::info!(deleted, pruned, "Frecency GC purged entries"); + } + + Ok(()) + } +} + +impl FrecencyTracker { + /// Returns the on-disk path of the LMDB environment directory. + pub fn db_path(&self) -> &Path { + self.env.path() + } + + pub fn open(db_path: impl AsRef) -> Result { + let db_path = db_path.as_ref(); + let (env, health) = Self::open_env(db_path)?; + + let db = Self::open_database_safe(&env, None)?; + Ok(FrecencyTracker { db, env, health }) + } + + #[deprecated( + since = "0.7.0", + note = "LMDB unsafe no-lock mode is no longer supported; use `FrecencyTracker::open` instead. \ + The `_use_unsafe_no_lock` argument is ignored." + )] + pub fn new(db_path: impl AsRef, _use_unsafe_no_lock: bool) -> Result { + Self::open(db_path) + } + + /// Removes entries where all timestamps are older than MAX_HISTORY_DAYS, + /// and prunes stale timestamps from entries that still have recent ones. + /// Returns (deleted_count, pruned_count). + fn purge_stale_entries(env: &Env) -> Result<(usize, usize)> { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let cutoff_time = now.saturating_sub((MAX_HISTORY_DAYS * SECONDS_PER_DAY) as u64); + + let db: Database>> = Self::open_database_safe(env, None)?; + + let rtxn = env.read_txn().map_err(|source| Error::DbStartReadTxn { + db: Self::LABEL, + source, + })?; + let mut to_delete: Vec> = Vec::new(); + let mut to_update: Vec<(Vec, VecDeque)> = Vec::new(); + + let iter = db.iter(&rtxn).map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + for result in iter { + let (key, accesses) = result.map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + + // Timestamps chronologically ordered (oldest at front). + let fresh_start = accesses.iter().position(|&ts| ts >= cutoff_time); + match fresh_start { + None => to_delete.push(key.to_vec()), + Some(0) => {} + Some(start) => { + let pruned: VecDeque = accesses.iter().skip(start).copied().collect(); + to_update.push((key.to_vec(), pruned)); + } + } + } + drop(rtxn); + + if to_delete.is_empty() && to_update.is_empty() { + return Ok((0, 0)); + } + + let mut wtxn = env.write_txn().map_err(|source| Error::DbStartWriteTxn { + db: Self::LABEL, + source, + })?; + + for key in &to_delete { + db.delete(&mut wtxn, key).map_err(|source| Error::DbWrite { + db: Self::LABEL, + source, + })?; + } + + for (key, accesses) in &to_update { + db.put(&mut wtxn, key, accesses) + .map_err(|source| Error::DbWrite { + db: Self::LABEL, + source, + })?; + } + wtxn.commit().map_err(|source| Error::DbCommit { + db: Self::LABEL, + source, + })?; + + Ok((to_delete.len(), to_update.len())) + } + + fn get_accesses(&self, path: &Path) -> Result>> { + let key_hash = Self::path_to_hash_bytes(path)?; + + let rtxn = self + .env + .read_txn() + .map_err(|source| Error::DbStartReadTxn { + db: Self::LABEL, + source, + })?; + let result = self + .db + .get(&rtxn, &key_hash) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + rtxn.commit().map_err(|source| Error::DbCommit { + db: Self::LABEL, + source, + })?; + + Ok(result) + } + + fn get_now(&self) -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + } + + fn path_to_hash_bytes(path: &Path) -> Result<[u8; 32]> { + // On Windows, resolve to the canonical form (short-name/case/symlink) + // so the same file always hashes to one key regardless of how the + // caller spelled it. Falls back to the raw path when the file no + // longer exists (e.g. watcher delete events), so the op is never + // dropped. No-op on other platforms. + #[cfg(windows)] + let canonical: Option = crate::path_utils::canonicalize(path).ok(); + #[cfg(windows)] + let path: &Path = canonical.as_deref().unwrap_or(path); + + let Some(key) = path.to_str() else { + return Err(Error::InvalidPath(path.to_path_buf())); + }; + + Ok(*blake3::hash(key.as_bytes()).as_bytes()) + } + + /// Returns seconds since the most recent tracked access, or `None` if the + /// file has never been tracked. + pub fn seconds_since_last_access(&self, path: &Path) -> Result> { + let accesses = self.get_accesses(path)?; + let last = accesses.and_then(|a| a.back().copied()); + Ok(last.map(|ts| self.get_now().saturating_sub(ts))) + } + + /// Number of tracked access for file path + pub fn access_count(&self, path: &Path) -> Result { + Ok(self.get_accesses(path)?.map_or(0, |a| a.len())) + } + + pub fn track_access(&self, path: &Path) -> Result<()> { + let key_hash = Self::path_to_hash_bytes(path)?; + let mut accesses = self.get_accesses(path)?.unwrap_or_default(); + + let now = self.get_now(); + let cutoff_time = now.saturating_sub((MAX_HISTORY_DAYS * SECONDS_PER_DAY) as u64); + + // Drop stale timestamps from the front while also enforcing the + // per-file cap. Reserves one slot for the `push_back` below. + while let Some(&front_time) = accesses.front() { + if front_time < cutoff_time || accesses.len() >= MAX_TIMESTAMPS_PER_FILE { + accesses.pop_front(); + } else { + break; + } + } + + accesses.push_back(now); + tracing::debug!(?path, accesses = accesses.len(), "Tracking access"); + + let mut wtxn = self + .env + .write_txn() + .map_err(|source| Error::DbStartWriteTxn { + db: Self::LABEL, + source, + })?; + if let Err(e) = self.db.put(&mut wtxn, &key_hash, &accesses) { + if is_map_full(&e) { + self.health.mark_unhealthy("MDB_MAP_FULL on put"); + tracing::error!( + ?path, + "Frecency DB hit MDB_MAP_FULL; dropping write — db will be \ + erased on next open via LmdbStore::erase_if_oversized" + ); + return Ok(()); + } + return Err(Error::DbWrite { + db: Self::LABEL, + source: e, + }); + } + + wtxn.commit() + .inspect_err(|e| { + if is_map_full(e) { + self.health.mark_unhealthy("MDB_MAP_FULL on commit"); + tracing::error!( + ?path, + "Frecency DB hit MDB_MAP_FULL on commit; dropping write" + ); + } + }) + .map_err(|source| Error::DbCommit { + db: Self::LABEL, + source, + }) + } + + pub fn get_access_score(&self, file_path: &Path, mode: FFFMode) -> i64 { + let accesses = self + .get_accesses(file_path) + .ok() + .flatten() + .unwrap_or_default(); + + if accesses.is_empty() { + return 0; + } + + let decay_constant = if mode.is_ai() { + AI_DECAY_CONSTANT + } else { + DECAY_CONSTANT + }; + let max_history_days = if mode.is_ai() { + AI_MAX_HISTORY_DAYS + } else { + MAX_HISTORY_DAYS + }; + + let now = self.get_now(); + let mut total_frecency = 0.0; + + let cutoff_time = now.saturating_sub((max_history_days * SECONDS_PER_DAY) as u64); + + for &access_time in accesses.iter().rev() { + if access_time < cutoff_time { + break; // All remaining entries are older, stop processing + } + + let days_ago = (now.saturating_sub(access_time) as f64) / SECONDS_PER_DAY; + let decay_factor = (-decay_constant * days_ago).exp(); + total_frecency += decay_factor; + } + + let normalized_frecency = if total_frecency <= 10.0 { + total_frecency + } else { + 10.0 + (total_frecency - 10.0).sqrt() // Diminishing: >10 accesses grow slowly + }; + + normalized_frecency.round() as i64 + } + + /// Calculating modification score but only if the file is modified in the current git dir + pub fn get_modification_score( + &self, + modified_time: u64, + git_status: Option, + mode: FFFMode, + ) -> i64 { + let is_modified_git_status = git_status.is_some_and(is_modified_status); + if !is_modified_git_status { + return 0; + } + + let thresholds = if mode.is_ai() { + &AI_MODIFICATION_THRESHOLDS + } else { + &MODIFICATION_THRESHOLDS + }; + + let now = self.get_now(); + let duration_since = now.saturating_sub(modified_time); + + for i in 0..thresholds.len() { + let (current_points, current_threshold) = thresholds[i]; + + if duration_since <= current_threshold { + if i == 0 || duration_since == current_threshold { + return current_points; + } + + let (prev_points, prev_threshold) = thresholds[i - 1]; + + let time_range = current_threshold - prev_threshold; + let time_offset = duration_since - prev_threshold; + let points_diff = prev_points - current_points; + + let interpolated_score = + prev_points - (points_diff * time_offset as i64) / time_range as i64; + + return interpolated_score; + } + } + + 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::file_picker::FFFMode; + + // A path that doesn't exist on disk must still hash (canonicalize fails on + // Windows → falls back to the raw string), so watcher delete events and + // raced files never drop their frecency op. + #[test] + fn hashes_nonexistent_path_without_error() { + let missing = Path::new("/this/path/definitely/does/not/exist/frecency_test_xyz"); + assert!(FrecencyTracker::path_to_hash_bytes(missing).is_ok()); + } + + fn calculate_test_frecency_score(access_timestamps: &[u64], current_time: u64) -> i64 { + let mut total_frecency = 0.0; + + for &access_time in access_timestamps { + let days_ago = (current_time.saturating_sub(access_time) as f64) / SECONDS_PER_DAY; + let decay_factor = (-DECAY_CONSTANT * days_ago).exp(); + total_frecency += decay_factor; + } + + let normalized_frecency = if total_frecency <= 20.0 { + total_frecency + } else { + 20.0 + (total_frecency - 10.0).sqrt() + }; + + normalized_frecency.round() as i64 + } + + #[test] + fn test_frecency_calculation() { + let current_time = 1000000000; // Base timestamp + + let score = calculate_test_frecency_score(&[], current_time); + assert_eq!(score, 0); + + let accesses = [current_time]; // Accessed right now + let score = calculate_test_frecency_score(&accesses, current_time); + assert_eq!(score, 1); // 1.0 decay factor = 1 + + let ten_days_seconds = 10 * 86400; // 10 days in seconds + let accesses = [current_time - ten_days_seconds]; + let score = calculate_test_frecency_score(&accesses, current_time); + assert_eq!(score, 1); // ~0.5 decay factor rounds to 1 + + let accesses = [ + current_time, // Today + current_time - 86400, // 1 day ago + current_time - 172800, // 2 days ago + ]; + let score = calculate_test_frecency_score(&accesses, current_time); + assert!(score > 2 && score < 4, "Score: {}", score); // About 3 accesses with decay + + let thirty_days = 30 * 86400; + let accesses = [current_time - thirty_days]; // 30 days ago + let score = calculate_test_frecency_score(&accesses, current_time); + assert!( + score < 2, + "Old access should have minimal score, got: {}", + score + ); + + let recent_frequent = [current_time, current_time - 86400, current_time - 172800]; + let old_single = [current_time - ten_days_seconds]; + + let recent_score = calculate_test_frecency_score(&recent_frequent, current_time); + let old_score = calculate_test_frecency_score(&old_single, current_time); + + assert!( + recent_score > old_score, + "Recent frequent access ({}) should score higher than old single access ({})", + recent_score, + old_score + ); + } + + #[test] + fn test_modification_score_interpolation() { + let temp_dir = std::env::temp_dir().join("fff_test_interpolation"); + let _ = std::fs::remove_dir_all(&temp_dir); + let tracker = FrecencyTracker::open(temp_dir.to_str().unwrap()).unwrap(); + + let current_time = tracker.get_now(); + let git_status = Some(git2::Status::WT_MODIFIED); + + // At 5 minutes: should interpolate between 16 and 8 points + let five_minutes_ago = current_time - (5 * 60); + let score = tracker.get_modification_score(five_minutes_ago, git_status, FFFMode::Neovim); + + // Expected: 16 - (8 * 3 / 13) = 16 - 1 = 15 points + // (time_offset = 5-2 = 3, time_range = 15-2 = 13, points_diff = 16-8 = 8) + assert_eq!(score, 15, "5 minutes should interpolate to 15 points"); + + let two_minutes_ago = current_time - (2 * 60); + let score = tracker.get_modification_score(two_minutes_ago, git_status, FFFMode::Neovim); + assert_eq!(score, 16, "2 minutes should be exactly 16 points"); + + let fifteen_minutes_ago = current_time - (15 * 60); + let score = + tracker.get_modification_score(fifteen_minutes_ago, git_status, FFFMode::Neovim); + assert_eq!(score, 8, "15 minutes should be exactly 8 points"); + + // At 12 hours: should interpolate between 4 and 2 points + let twelve_hours_ago = current_time - (12 * 60 * 60); + let score = tracker.get_modification_score(twelve_hours_ago, git_status, FFFMode::Neovim); + // Expected: 4 - (2 * 11 / 23) = 4 - 0 = 4 points (integer division) + // (time_offset = 12-1 = 11 hours, time_range = 24-1 = 23 hours, points_diff = 4-2 = 2) + assert_eq!(score, 4, "12 hours should interpolate to 4 points"); + + // at 18 hours for more significant interpolation + let eighteen_hours_ago = current_time - (18 * 60 * 60); + let score = tracker.get_modification_score(eighteen_hours_ago, git_status, FFFMode::Neovim); + // Expected: 4 - (2 * 17 / 23) = 4 - 1 = 3 points + assert_eq!(score, 3, "18 hours should interpolate to 3 points"); + + let score = tracker.get_modification_score(five_minutes_ago, None, FFFMode::Neovim); + assert_eq!(score, 0, "No git status should return 0"); + + let _ = std::fs::remove_dir_all(&temp_dir); + } +} diff --git a/crates/fff-core/src/dbs/lmdb.rs b/crates/fff-core/src/dbs/lmdb.rs new file mode 100644 index 0000000..9eba707 --- /dev/null +++ b/crates/fff-core/src/dbs/lmdb.rs @@ -0,0 +1,259 @@ +use heed::{Database, Env, EnvOpenOptions}; +use std::fs; +use std::path::Path; +use std::sync::Arc; +use std::sync::RwLock; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::thread; +use std::time::Duration; + +use crate::error::{Error, Result}; + +pub(crate) fn is_map_full(err: &heed::Error) -> bool { + matches!(err, heed::Error::Mdb(heed::MdbError::MapFull)) +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DbHealthState { + Pending = 0, + Healthy = 1, + Degraded = 2, +} + +impl DbHealthState { + fn from_u8(v: u8) -> Self { + debug_assert!(v <= 2); + + match v { + 0 => Self::Pending, + 1 => Self::Healthy, + _ => Self::Degraded, + } + } +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct DbHealth(Arc); + +impl DbHealth { + pub(crate) fn new() -> Self { + Self(Arc::new(AtomicU8::new(DbHealthState::Pending as u8))) + } + + pub(crate) fn is_healthy(&self) -> bool { + // Pending counts as unhealthy: if the GC thread never flipped to + // Healthy, something's wrong (deadlocked clear_stale_readers, stuck + // writer mutex, etc.) and we want that surfaced to the user. + DbHealthState::from_u8(self.0.load(Ordering::Acquire)) == DbHealthState::Healthy + } + + pub(crate) fn mark_healthy(&self) { + let _ = self.0.compare_exchange( + DbHealthState::Pending as u8, + DbHealthState::Healthy as u8, + Ordering::AcqRel, + Ordering::Acquire, + ); + } + + pub(crate) fn mark_unhealthy(&self, reason: &'static str) { + let prev = self.0.swap(DbHealthState::Degraded as u8, Ordering::AcqRel); + if DbHealthState::from_u8(prev) != DbHealthState::Degraded { + tracing::error!(reason, "LMDB tracker marked unhealthy"); + } + } +} + +/// Spawns a background thread that is ensuring that the environment that was previously +/// open is safe, accessible and doesn't have a corrupted lock.md file. If it does this thread will +/// hang indefinitely but we will have the information that the database is in failure mode +pub(crate) fn spawn_lmdb_gc(shared: Arc>>) { + let thread_shared = shared.clone(); + let spawn_result = thread::Builder::new() + .name("fff-lmdb-gc".into()) + .spawn(move || { + // Holding a read guard blocks `destroy` / re-init's write + // guard until this thread finishes — natural serialization. + let guard = match thread_shared.read() { + Ok(g) => g, + Err(e) => { + tracing::debug!("gc: read lock poisoned: {e}"); + return; + } + }; + let Some(ref tracker) = *guard else { + return; // destroyed before we started + }; + let env = tracker.env(); + + if let Err(e) = T::purge_stale_data(env) { + tracing::debug!("purge_stale_data failed: {e}"); + } + + tracker.health().mark_healthy(); + }); + + if let Err(e) = spawn_result { + tracing::debug!(?e, "failed to spawn fff-lmdb-gc thread"); + // No thread = mark healthy now so healthcheck isn't stuck Pending. + if let Ok(guard) = shared.read() + && let Some(ref tracker) = *guard + { + tracker.health().mark_healthy(); + } + } +} + +// Concurrent `mdb_env_open` calls on the same path can race on macOS +// this is for some reason fixabtly by simple retry of the open +fn is_transient_env_open_error(err: &heed::Error) -> bool { + match err { + heed::Error::Io(io) => matches!( + io.kind(), + std::io::ErrorKind::InvalidInput | std::io::ErrorKind::NotFound + ), + _ => false, + } +} + +pub(crate) trait LmdbStore: Sized + Send + Sync + 'static { + /// Short label used to defferintiate different instances of this trait + const LABEL: &'static str; + /// LMDB map size in bytes. Must be a multiple of the OS page size. + const MAP_SIZE: usize; + /// Number of named sub-databases. `0` for single-db envs. + const MAX_DBS: u32; + /// Hard cap on `data.mdb` size. + const SIZE_CAP_BYTES: u64; + + /// Borrow the env in the read lock + fn env(&self) -> &Env; + /// Borrow the health flag from the tracker. + fn health(&self) -> &DbHealth; + + /// Override to purge stale rows, compact, etc. Default no-op. Runs on + /// the GC thread while a read lock is held against the shared handle, + /// so destroy / re-init naturally wait for it. + fn purge_stale_data(_env: &Env) -> Result<()> { + Ok(()) + } + + /// Open the LMDB env. Returns env + a `DbHealth` starting in Pending; + /// the GC thread spawned by `spawn_gc` flips it to Healthy. Write + /// paths flip it to Degraded on MDB_MAP_FULL. + #[tracing::instrument] + fn open_env(db_path: &Path) -> Result<(Env, DbHealth)> { + Self::erase_if_oversized(db_path); + fs::create_dir_all(db_path).map_err(Error::CreateDir)?; + let db = Self::LABEL; + + const MAX_ATTEMPTS: u32 = 8; + let mut attempt = 0u32; + let env = loop { + let result = unsafe { + let mut opts = EnvOpenOptions::new(); + opts.map_size(Self::MAP_SIZE); + if Self::MAX_DBS > 0 { + opts.max_dbs(Self::MAX_DBS); + } + opts.open(db_path) + }; + + match result { + Ok(env) => break env, + Err(e) if is_transient_env_open_error(&e) && attempt + 1 < MAX_ATTEMPTS => { + attempt += 1; + tracing::debug!( + path = %db_path.display(), + attempt, + error = ?e, + "transient LMDB env open error, retrying" + ); + + thread::sleep(Duration::from_millis(50)); + } + Err(e) => return Err(Error::EnvOpen { db, source: e }), + } + }; + + // Reclaim reader slots left behind by prior processes that died + // without cleanup. Must run before we start any read txns (which + // open_database_safe does) — otherwise we may hit MDB_READERS_FULL + // on a fresh env just because lock.mdb still has stale entries + // from a previous crash. + // + // This is the one LMDB maintenance call we run on the caller's + // thread. If the lock file is genuinely wedged this will block + // forever, but the alternative — never getting past init — is + // worse and the bg-thread trick doesn't solve it anyway. + match env.clear_stale_readers() { + Ok(cleared) if cleared > 0 => { + tracing::warn!(cleared, "reclaimed stale LMDB reader slots at open"); + } + Ok(_) => {} + Err(e) => tracing::debug!("clear_stale_readers at open failed: {e}"), + } + + Ok((env, DbHealth::new())) + } + + /// Open or create a database without blocking on the LMDB writer mutex + /// when the database already exists. + fn open_database_safe(env: &Env, name: Option<&str>) -> Result> + where + KC: 'static, + DC: 'static, + { + let db = Self::LABEL; + let rtxn = env + .read_txn() + .map_err(|source| Error::DbStartReadTxn { db, source })?; + let maybe_db: Option> = env + .open_database(&rtxn, name) + .map_err(|source| Error::DbOpen { db, source })?; + + // do not drop the DB here + rtxn.commit() + .map_err(|source| Error::DbCommit { db, source })?; + + match maybe_db { + Some(handle) => Ok(handle), + None => { + // First time: create the database (requires write lock). + // unfortunately this CAN be deadlocking and this is what we see happens + // if the other part of the code is segfaulting, so the only rule to prevent this + // write the good code mf, okay? + let mut wtxn = env + .write_txn() + .map_err(|source| Error::DbStartWriteTxn { db, source })?; + let handle = env + .create_database(&mut wtxn, name) + .map_err(|source| Error::DbCreate { db, source })?; + + wtxn.commit() + .map_err(|source| Error::DbCommit { db, source })?; + Ok(handle) + } + } + } + + fn erase_if_oversized(db_path: &Path) { + let data = db_path.join("data.mdb"); + let Ok(meta) = fs::metadata(&data) else { + return; + }; + if meta.len() <= Self::SIZE_CAP_BYTES { + return; + } + + tracing::error!( + path = %db_path.display(), + size = meta.len(), + cap = Self::SIZE_CAP_BYTES, + "LMDB db exceeds size cap, erasing" + ); + let _ = fs::remove_file(&data); + let _ = fs::remove_file(db_path.join("lock.mdb")); + } +} diff --git a/crates/fff-core/src/dbs/mod.rs b/crates/fff-core/src/dbs/mod.rs new file mode 100644 index 0000000..63073cc --- /dev/null +++ b/crates/fff-core/src/dbs/mod.rs @@ -0,0 +1,10 @@ +pub(crate) mod lmdb; + +pub mod db_healthcheck; +pub use db_healthcheck::{DbHealth, DbHealthChecker}; + +pub mod frecency; +pub use frecency::*; + +pub mod query_tracker; +pub use query_tracker::*; diff --git a/crates/fff-core/src/dbs/query_tracker.rs b/crates/fff-core/src/dbs/query_tracker.rs new file mode 100644 index 0000000..40d0117 --- /dev/null +++ b/crates/fff-core/src/dbs/query_tracker.rs @@ -0,0 +1,548 @@ +use super::db_healthcheck::DbHealthChecker; +use super::lmdb::{DbHealth, LmdbStore, is_map_full}; +use crate::error::Error; +use heed::types::{Bytes, SerdeBincode}; +use heed::{Database, Env}; +use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; + +const MAX_HISTORY_ENTRIES: usize = 128; + +/// Simplified QueryFileEntry without redundant fields +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct QueryMatchEntry { + pub file_path: PathBuf, // File that was actually opened + pub open_count: u32, // Number of times opened with this query + pub last_opened: u64, // Unix timestamp +} + +/// Entry for query history tracking +#[derive(Debug, Serialize, Deserialize, Clone)] +struct HistoryEntry { + query: String, + timestamp: u64, +} + +#[derive(Debug)] +pub struct QueryTracker { + env: Env, + // Database for (project_path, query) -> QueryMatchEntry mappings + query_file_db: Database>, + // Database for project_path -> VecDeque mappings (file picker) + query_history_db: Database>>, + // Database for project_path -> VecDeque mappings (grep) + grep_query_history_db: Database>>, + health: DbHealth, +} + +impl DbHealthChecker for QueryTracker { + fn get_env(&self) -> &Env { + &self.env + } + + fn is_healthy(&self) -> bool { + self.health.is_healthy() + } + + fn count_entries(&self) -> Result, Error> { + let rtxn = self + .env + .read_txn() + .map_err(|source| Error::DbStartReadTxn { + db: Self::LABEL, + source, + })?; + + let count_queries = self + .query_file_db + .len(&rtxn) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + let count_histories = self + .query_history_db + .len(&rtxn) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + let count_grep_histories = + self.grep_query_history_db + .len(&rtxn) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + + Ok(vec![ + ("query_file_entries", count_queries), + ("query_history_entries", count_histories), + ("grep_query_history_entries", count_grep_histories), + ]) + } +} + +impl LmdbStore for QueryTracker { + const LABEL: &'static str = "query"; + // 10 MiB hard ceiling. Same reasoning as FrecencyTracker (GH issue #437). + const MAP_SIZE: usize = 10 * 1024 * 1024; + const MAX_DBS: u32 = 16; + const SIZE_CAP_BYTES: u64 = 8 * 1024 * 1024; + + fn env(&self) -> &Env { + &self.env + } + + fn health(&self) -> &DbHealth { + &self.health + } +} + +impl QueryTracker { + /// Returns the on-disk path of the LMDB environment directory. + pub fn db_path(&self) -> &Path { + self.env.path() + } + + pub fn open(db_path: impl AsRef) -> Result { + let db_path = db_path.as_ref(); + let (env, health) = Self::open_env(db_path)?; + + let query_file_db = Self::open_database_safe(&env, Some("query_file_associations"))?; + let query_history_db = Self::open_database_safe(&env, Some("query_history"))?; + let grep_query_history_db = Self::open_database_safe(&env, Some("grep_query_history"))?; + + Ok(QueryTracker { + env, + query_file_db, + query_history_db, + grep_query_history_db, + health, + }) + } + + #[deprecated( + since = "0.7.0", + note = "LMDB unsafe no-lock mode is no longer supported; use `QueryTracker::open` instead. \ + The `_use_unsafe_no_lock` argument is ignored." + )] + pub fn new(db_path: impl AsRef, _use_unsafe_no_lock: bool) -> Result { + Self::open(db_path) + } + + fn get_now(&self) -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + } + + fn create_query_key(project_path: &Path, query: &str) -> Result<[u8; 32], Error> { + let project_str = project_path + .to_str() + .ok_or_else(|| Error::InvalidPath(project_path.to_path_buf()))?; + + let mut hasher = blake3::Hasher::default(); + hasher.update(project_str.as_bytes()); + hasher.update(b"::"); + hasher.update(query.as_bytes()); + + Ok(*hasher.finalize().as_bytes()) + } + + fn create_project_key(project_path: &Path) -> Result<[u8; 32], Error> { + let project_str = project_path + .to_str() + .ok_or_else(|| Error::InvalidPath(project_path.to_path_buf()))?; + + Ok(*blake3::hash(project_str.as_bytes()).as_bytes()) + } + + /// Append a query to a history database within an existing write transaction. + fn append_to_history( + db: &Database>>, + wtxn: &mut heed::RwTxn, + project_key: &[u8; 32], + query: &str, + now: u64, + ) -> Result<(), Error> { + let mut history = db + .get(wtxn, project_key) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })? + .unwrap_or_default(); + + history.push_back(HistoryEntry { + query: query.to_string(), + timestamp: now, + }); + while history.len() > MAX_HISTORY_ENTRIES { + history.pop_front(); + } + + db.put(wtxn, project_key, &history) + .map_err(|source| Error::DbWrite { + db: Self::LABEL, + source, + })?; + Ok(()) + } + + /// Read a query from a history database at a specific offset. + /// offset=0 returns most recent, offset=1 returns 2nd most recent, etc. + fn read_history_at_offset( + db: &Database>>, + env: &Env, + project_key: &[u8; 32], + offset: usize, + ) -> Result, Error> { + let rtxn = env.read_txn().map_err(|source| Error::DbStartReadTxn { + db: Self::LABEL, + source, + })?; + + let mut history = db + .get(&rtxn, project_key) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })? + .unwrap_or_default(); + + // history is FIFO, last element is most recent + if history.len() > offset { + let index = history.len() - 1 - offset; + let record = history.remove(index); + Ok(record.map(|r| r.query)) + } else { + Ok(None) + } + } + + pub fn track_query_completion( + &mut self, + query: &str, + project_path: &Path, + file_path: &Path, + ) -> Result<(), Error> { + let now = self.get_now(); + let file_path_buf = file_path.to_path_buf(); + + let query_key = Self::create_query_key(project_path, query)?; + let mut wtxn = self + .env + .write_txn() + .map_err(|source| Error::DbStartWriteTxn { + db: Self::LABEL, + source, + })?; + + let mut entry = self + .query_file_db + .get(&wtxn, &query_key) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })? + .unwrap_or_else(|| QueryMatchEntry { + file_path: file_path_buf.clone(), + open_count: 0, + last_opened: now, + }); + + if entry.file_path == file_path_buf { + tracing::debug!( + ?query, + ?file_path, + "Query completed for same file as last time" + ); + + // Same file - just increment count + entry.open_count += 1; + } else { + tracing::debug!( + ?query, + ?file_path, + "Query completed for different file than last time" + ); + + // Different file - replace and reset count to 1 + entry.file_path = file_path_buf; + entry.open_count = 1; + } + + entry.last_opened = now; + + if let Err(e) = self.query_file_db.put(&mut wtxn, &query_key, &entry) { + if is_map_full(&e) { + self.health.mark_unhealthy("MDB_MAP_FULL on put"); + tracing::error!( + ?query, + "Query tracker DB hit MDB_MAP_FULL; dropping write — db will \ + be erased on next open" + ); + return Ok(()); + } + return Err(Error::DbWrite { + db: Self::LABEL, + source: e, + }); + } + + // Update query history database + let project_key = Self::create_project_key(project_path)?; + if let Err(e) = + Self::append_to_history(&self.query_history_db, &mut wtxn, &project_key, query, now) + { + if let Error::DbWrite { + source: ref inner, .. + } = e + && is_map_full(inner) + { + self.health.mark_unhealthy("MDB_MAP_FULL on history append"); + tracing::error!(?query, "Query tracker DB map full while appending history"); + return Ok(()); + } + return Err(e); + } + + if let Err(e) = wtxn.commit() { + if is_map_full(&e) { + self.health.mark_unhealthy("MDB_MAP_FULL on commit"); + tracing::error!(?query, "Query tracker DB map full on commit"); + return Ok(()); + } + return Err(Error::DbCommit { + db: Self::LABEL, + source: e, + }); + } + + tracing::debug!(?query, ?file_path, "Tracked query completion"); + Ok(()) + } + + pub fn get_last_query_entry( + &self, + query: &str, + project_path: &Path, + min_combo_count: u32, + ) -> Result, Error> { + let query_key = Self::create_query_key(project_path, query)?; + let rtxn = self + .env + .read_txn() + .map_err(|source| Error::DbStartReadTxn { + db: Self::LABEL, + source, + })?; + + let last_match = self + .query_file_db + .get(&rtxn, &query_key) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })?; + + Ok(last_match.filter(|entry| entry.open_count >= min_combo_count)) + } + + pub fn get_last_query_path( + &self, + query: &str, + project_path: &Path, + file_path: &Path, + combo_boost: i32, + ) -> Result { + let query_key = Self::create_query_key(project_path, query)?; + tracing::debug!(?query_key, "HASH"); + let rtxn = self + .env + .read_txn() + .map_err(|source| Error::DbStartReadTxn { + db: Self::LABEL, + source, + })?; + + match self + .query_file_db + .get(&rtxn, &query_key) + .map_err(|source| Error::DbRead { + db: Self::LABEL, + source, + })? { + Some(entry) => { + // Check if the file path matches and return boost + if entry.file_path == file_path && entry.open_count >= 2 { + Ok(combo_boost) + } else { + Ok(0) + } + } + None => Ok(0), // Query not found + } + } + + /// Get query from file picker history at a specific offset. + /// offset=0 returns most recent query, offset=1 returns 2nd most recent, etc. + pub fn get_historical_query( + &self, + project_path: &Path, + offset: usize, + ) -> Result, Error> { + let project_key = Self::create_project_key(project_path)?; + Self::read_history_at_offset(&self.query_history_db, &self.env, &project_key, offset) + } + + /// Track a grep query in the grep-specific history. + /// Only records query history (no file association tracking needed for grep). + pub fn track_grep_query(&mut self, query: &str, project_path: &Path) -> Result<(), Error> { + let now = self.get_now(); + let project_key = Self::create_project_key(project_path)?; + let mut wtxn = self + .env + .write_txn() + .map_err(|source| Error::DbStartWriteTxn { + db: Self::LABEL, + source, + })?; + + if let Err(e) = Self::append_to_history( + &self.grep_query_history_db, + &mut wtxn, + &project_key, + query, + now, + ) { + if let Error::DbWrite { + source: ref inner, .. + } = e + && is_map_full(inner) + { + self.health + .mark_unhealthy("MDB_MAP_FULL on grep history append"); + tracing::error!(?query, "Grep query history DB map full; dropping write"); + return Ok(()); + } + return Err(e); + } + + if let Err(e) = wtxn.commit() { + if is_map_full(&e) { + self.health.mark_unhealthy("MDB_MAP_FULL on commit"); + tracing::error!(?query, "Grep query history DB map full on commit"); + return Ok(()); + } + return Err(Error::DbCommit { + db: Self::LABEL, + source: e, + }); + } + + tracing::debug!(?query, "Tracked grep query"); + Ok(()) + } + + /// Get grep query from history at a specific offset. + /// offset=0 returns most recent grep query, offset=1 returns 2nd most recent, etc. + pub fn get_historical_grep_query( + &self, + project_path: &Path, + offset: usize, + ) -> Result, Error> { + let project_key = Self::create_project_key(project_path)?; + Self::read_history_at_offset(&self.grep_query_history_db, &self.env, &project_key, offset) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + #[test] + fn test_query_tracking() { + let temp_dir = env::temp_dir().join("fff_test_query_tracking_new"); + let _ = std::fs::remove_dir_all(&temp_dir); + + let mut tracker = QueryTracker::open(temp_dir.to_str().unwrap()).unwrap(); + + let project_path = PathBuf::from("/test/project"); + let file_path = PathBuf::from("/test/project/src/main.rs"); + + // First completion + tracker + .track_query_completion("main", &project_path, &file_path) + .unwrap(); + let boost = tracker + .get_last_query_path("main", &project_path, &file_path, 10000) + .unwrap(); + assert_eq!(boost, 0, "First completion should not boost"); + + // Second completion - should boost now + tracker + .track_query_completion("main", &project_path, &file_path) + .unwrap(); + let boost = tracker + .get_last_query_path("main", &project_path, &file_path, 10000) + .unwrap(); + assert_eq!(boost, 10000, "Second completion should boost"); + + // Different file for same query - should reset count and no boost + let other_file = PathBuf::from("/test/project/src/lib.rs"); + tracker + .track_query_completion("main", &project_path, &other_file) + .unwrap(); + let boost = tracker + .get_last_query_path("main", &project_path, &other_file, 10000) + .unwrap(); + assert_eq!(boost, 0, "Different file should reset boost"); + + // Original file should no longer get boost (replaced by new file) + let boost = tracker + .get_last_query_path("main", &project_path, &file_path, 10000) + .unwrap(); + assert_eq!(boost, 0, "Original file should not boost after replacement"); + + let _ = std::fs::remove_dir_all(&temp_dir); + } + + #[test] + fn test_hashing_functions() { + let project_path = PathBuf::from("/test/project"); + + // Test project key hashing + let key1 = QueryTracker::create_project_key(&project_path).unwrap(); + let key2 = QueryTracker::create_project_key(&project_path).unwrap(); + assert_eq!(key1, key2, "Same project should hash to same key"); + + // Test query key hashing + let query_key1 = QueryTracker::create_query_key(&project_path, "test").unwrap(); + let query_key2 = QueryTracker::create_query_key(&project_path, "test").unwrap(); + assert_eq!( + query_key1, query_key2, + "Same project+query should hash to same key" + ); + + // Different queries should hash differently + let query_key3 = QueryTracker::create_query_key(&project_path, "different").unwrap(); + assert_ne!( + query_key1, query_key3, + "Different queries should hash to different keys" + ); + + // Different projects should hash differently + let other_project = PathBuf::from("/other/project"); + let query_key4 = QueryTracker::create_query_key(&other_project, "test").unwrap(); + assert_ne!( + query_key1, query_key4, + "Different projects should hash to different keys" + ); + } +} diff --git a/crates/fff-core/src/error.rs b/crates/fff-core/src/error.rs new file mode 100644 index 0000000..451af79 --- /dev/null +++ b/crates/fff-core/src/error.rs @@ -0,0 +1,99 @@ +use std::path::StripPrefixError; + +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error("Thread panicked")] + ThreadPanic, + #[error("Invalid path {0}")] + InvalidPath(std::path::PathBuf), + #[error( + "Can not run certain FFF features in a file system root or home directories. Consider smaller per-project directories." + )] + FilesystemRoot(std::path::PathBuf), + #[error("File picker not initialized")] + FilePickerMissing, + #[error("Failed to acquire lock for frecency")] + AcquireFrecencyLock, + #[error("Failed to acquire lock for items by provider")] + AcquireItemLock, + #[error("Failed to acquire lock for path cache")] + AcquirePathCacheLock, + #[error("Failed to create directory: {0}")] + CreateDir(#[from] std::io::Error), + #[error("Failed to remove database directory {path}: {source}")] + RemoveDbDir { + path: std::path::PathBuf, + source: std::io::Error, + }, + #[error("Something is wrong with the local db instance: {0}")] + GenericDbError(#[from] heed::Error), + #[error("Failed to open {db} database env: {source}")] + EnvOpen { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to create {db} database: {source}")] + DbCreate { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to open {db} database: {source}")] + DbOpen { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to clear stale readers for {db} database: {source}")] + DbClearStaleReaders { + db: &'static str, + #[source] + source: heed::Error, + }, + + #[error("Failed to start read transaction for {db} database: {source}")] + DbStartReadTxn { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to start write transaction for {db} database: {source}")] + DbStartWriteTxn { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to read from {db} database: {source}")] + DbRead { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to write to {db} database: {source}")] + DbWrite { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to commit write transaction to {db} database: {source}")] + DbCommit { + db: &'static str, + #[source] + source: heed::Error, + }, + #[error("Failed to start file system watcher: {0}")] + FileSystemWatch(#[from] notify::Error), + + #[error("Expected a path to be child of another path: {0}")] + StripPrefixError(#[from] StripPrefixError), + + #[error("libgit2 error occurred: {0}")] + Git(#[from] git2::Error), + + #[error("Filesystem walk failed: {0}")] + WalkFailed(String), +} + +pub type Result = std::result::Result; diff --git a/crates/fff-core/src/file_picker.rs b/crates/fff-core/src/file_picker.rs new file mode 100644 index 0000000..0d51745 --- /dev/null +++ b/crates/fff-core/src/file_picker.rs @@ -0,0 +1,2252 @@ +//! Core file picker: filesystem indexing, background watching, and fuzzy search. +//! +//! [`FilePicker`] is the central component of fff-search. It: +//! +//! 1. **Indexes** a directory tree in a background thread, collecting every +//! non-ignored file into a path-sorted `Vec`. +//! 2. **Watches** the filesystem via the `notify` crate, applying +//! create/modify/delete events to the index in real time. +//! 3. **Owns files**: Provides a values for search and provides a good entry point for +//! fuzzy search and live grep +//! +//! # Lifecycle +//! +//! ```text +//! new_with_shared_state() +//! │ +//! ├─> background scan thread ──> populates SharedPicker +//! └─> file-system watcher ──> live updates SharedPicker +//! +//! search() <── borrows &self, delegates to fuzzy_search +//! grep() <── static, borrows &[FileItem] (live content search) +//! trigger_rescan() <── synchronous re-index +//! cancel() <── shuts down background work +//! ``` +//! +//! # Thread Safety +//! +//! `FilePicker` itself is **not** `Sync`! +//! all concurrent access goes through [`SharedPicker`](crate::SharedPicker) . +//! The background scanner and watcher acquire write locks only when mutating +//! the file index, so read-heavy search workloads rarely contend. + +use crate::FFFStringStorage; +use crate::background_watcher::BackgroundWatcher; +use crate::bigram_filter::{BigramFilter, BigramOverlay}; +use crate::constants::{MAX_OVERFLOW_FILES, PATH_BUF_SIZE}; +use crate::error::Error; +use crate::frecency::FrecencyTracker; +use crate::git::GitStatusCache; +use crate::grep::{GrepResult, GrepSearchOptions, grep_search, multi_grep_search}; +use crate::query_tracker::QueryTracker; +use crate::scan::{ScanConfig, ScanJob, ScanSignals}; +use crate::score::fuzzy_match_and_score_files; +use crate::shared::{SharedFilePicker, SharedFrecency}; +use crate::simd_path::ArenaPtr; +use crate::stable_vec::StableVec; +use crate::types::{ + ContentCacheBudget, DirItem, DirSearchResult, FileItem, MixedItemRef, MixedSearchResult, + PaginationArgs, Score, ScoringContext, SearchResult, +}; +use fff_query_parser::FFFQuery; +use git2::{Repository, Status}; +use rayon::prelude::*; +use std::fmt::Debug; +use std::ops::ControlFlow; +use std::path::{Path, PathBuf}; +use std::sync::{ + Arc, + atomic::{AtomicBool, AtomicUsize, Ordering}, +}; +use std::thread::JoinHandle; +use std::time::SystemTime; +use tracing::{Level, debug, error, info, warn}; + +use crate::parallelism::{BACKGROUND_THREAD_POOL, SEARCH_THREAD_POOL}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum FFFMode { + #[default] + Neovim, + Ai, +} + +impl FFFMode { + pub fn is_ai(self) -> bool { + self == FFFMode::Ai + } +} + +/// Configuration for a single fuzzy search invocation. +/// +/// Passed to [`FilePicker::search`] to control threading, pagination, +/// and scoring behavior. +#[derive(Debug, Clone, Copy, Default)] +pub struct FuzzySearchOptions<'a> { + pub max_threads: usize, + pub current_file: Option<&'a str>, + pub project_path: Option<&'a Path>, + pub combo_boost_score_multiplier: i32, + pub min_combo_count: u32, + pub pagination: PaginationArgs, +} + +#[derive(Debug, Clone)] +pub(crate) struct FileSync { + pub(crate) git_workdir: Option, + /// Base files laid out in two partitions, each internally sorted by + /// (parent_dir, filename): + /// `files[..indexable_count]` - indexable + /// `files[indexable_count..base_count]` - original-unindexable + /// `files[base_count..]`— overflow (created on demand) + files: StableVec, + indexable_count: usize, + base_count: usize, + /// Number of active present files that exists in the file system + pub(crate) live_count: usize, + /// Sorted directory table. `StableVec` so post-scan snapshots can keep + /// the allocation alive across a picker drop without copying, and so + /// concurrent readers observe a consistent view via the same shared + /// allocation. Dir frecency is updated through the per-entry atomic + /// (`DirItem::max_access_frecency`) without `&mut` aliasing. + dirs: StableVec, + /// Shared builder for overflow file paths. Each overflow file's ChunkedString + /// uses `arena_override` pointing into this builder's arena. + overflow_builder: Option, + bigram_index: Option>, + bigram_overlay: Option>>, + /// Chunk-level deduped path store. Arc so post-scan snapshots can hold + /// the arena alive while iterating file paths. + chunked_paths: Option>, + /// Ignore rules the walker assembled (zlob backend only). Shared with the + /// background watcher so filesystem events can be filtered without libgit2. + pub(crate) ignore_rules: Option>, +} + +impl FileSync { + fn new() -> Self { + Self { + files: StableVec::from_vec_with_reserve(Vec::new(), MAX_OVERFLOW_FILES), + indexable_count: 0, + base_count: 0, + live_count: 0, + dirs: StableVec::from_vec_with_reserve(Vec::new(), 0), + overflow_builder: None, + git_workdir: None, + bigram_index: None, + bigram_overlay: None, + chunked_paths: None, + ignore_rules: None, + } + } + + #[inline] + fn arena_base_ptr(&self) -> ArenaPtr { + self.chunked_paths + .as_ref() + .map(|s| s.as_arena_ptr()) + .unwrap_or(ArenaPtr::null()) + } + + #[inline] + fn arena_overflow_ptr(&self) -> ArenaPtr { + self.overflow_builder + .as_ref() + .map(|b| b.as_arena_ptr()) + .unwrap_or(ArenaPtr::null()) + } + + #[inline] + fn arena_for_file(&self, file: &FileItem) -> ArenaPtr { + if file.is_overflow() { + self.arena_overflow_ptr() + } else { + self.arena_base_ptr() + } + } + + #[inline] + fn files(&self) -> &[FileItem] { + &self.files + } + + #[inline] + fn overflow_files(&self) -> &[FileItem] { + &self.files[self.base_count..] + } + + #[inline] + fn get_file_mut(&mut self, index: usize) -> Option<(ArenaPtr, &mut FileItem)> { + Some(( + if index < self.base_count { + self.arena_base_ptr() + } else { + self.arena_overflow_ptr() + }, + self.files.get_mut(index)?, + )) + } + + #[inline] + fn find_file_index(&self, path: &Path, base_path: &Path) -> Option { + let arena = self.arena_base_ptr(); + + // Strip base_path prefix to get the relative path. On Windows this + // can fail for 8.3 short names or a different casing; fall back to + // canonicalize-then-strip so watcher events still land on the right + // `FileItem`. + let rel_path_owned: String = match path.strip_prefix(base_path) { + Ok(r) => r.to_string_lossy().into_owned(), + Err(_) => { + #[cfg(windows)] + { + canonical_relative_path(path, base_path)? + } + #[cfg(not(windows))] + { + return None; + } + } + }; + // The dir table and stored file paths are '/'-canonical; fold the + // native relative path so the byte-wise comparisons below match. + let rel_path_owned = crate::path_utils::to_canonical_slashes(&rel_path_owned).into_owned(); + let rel_path: &str = &rel_path_owned; + + // Split into directory (with trailing '/') and filename. + let parent_end = rel_path + .rfind(std::path::is_separator) + .map(|i| i + 1) + .unwrap_or(0); + let dir_rel = &rel_path[..parent_end]; + let filename = &rel_path[parent_end..]; + + // Binary search dirs to find the parent directory index. + // Dir items store the relative path including trailing '/' (e.g. "src/components/"). + let mut dir_buf = [0u8; crate::simd_path::PATH_BUF_SIZE]; + let dir_idx = self + .dirs + .binary_search_by(|d| d.read_relative_path(arena, &mut dir_buf).cmp(dir_rel)) + .ok(); + + // Binary search base files by (parent_dir, filename). Base files live in + // two internally-sorted partitions — indexable first, then unindexable — + // so we try each half in turn. Two O(log n) searches with short-circuit. + if let Some(dir_idx) = dir_idx { + let dir_idx = dir_idx as u32; + let cmp_key = |f: &FileItem| { + f.parent_dir_index.cmp(&dir_idx).then_with(|| { + let fname = f.file_name(arena); + fname.as_str().cmp(filename) + }) + }; + + if self.indexable_count > 0 + && let Ok(pos) = self.files[..self.indexable_count].binary_search_by(cmp_key) + { + return Some(pos); + } + + if self.indexable_count < self.base_count + && let Ok(rel_pos) = + self.files[self.indexable_count..self.base_count].binary_search_by(cmp_key) + { + return Some(self.indexable_count + rel_pos); + } + } + + // Overflow region: linear scan by full relative path. + if self.base_count < self.files.len() { + let overflow_arena = self.arena_overflow_ptr(); + if let Some(pos) = self.files[self.base_count..] + .iter() + .position(|f| f.relative_path_eq(overflow_arena, rel_path)) + { + return Some(self.base_count + pos); + } + } + + None + } + + // TODO remove this function and make a better way to remove all files + // from the directory without looping over the whole sync data list + /// Tombstones every file in the arena that matches certain predicate + fn tombstone_files_with_arena(&mut self, mut predicate: F) -> usize + where + F: FnMut(&FileItem, ArenaPtr) -> bool, + { + let base_arena = self.arena_base_ptr(); + let overflow_arena = self.arena_overflow_ptr(); + let base_count = self.base_count; + + let mut tombstoned = 0usize; + for (idx, file) in self.files.iter_mut().enumerate() { + if file.is_deleted() { + continue; + } + let arena = if idx < base_count { + base_arena + } else { + overflow_arena + }; + if predicate(file, arena) { + file.set_deleted(true); + tombstoned += 1; + } + } + self.live_count -= tombstoned; + tombstoned + } +} + +impl FileItem { + pub fn new(path: PathBuf, base_path: &Path, git_status: Option) -> (Self, String) { + let metadata = std::fs::metadata(&path).ok(); + Self::new_with_metadata(path, base_path, git_status, metadata.as_ref()) + } + + /// Create a FileItem using pre-fetched metadata to avoid a redundant stat syscall. + /// Returns `(FileItem, relative_path)`. The FileItem's `path` field is + /// empty; callers must populate it via `set_path` or `build_chunked_path_store_and_assign`. + fn new_with_metadata( + path: PathBuf, + base_path: &Path, + git_status: Option, + metadata: Option<&std::fs::Metadata>, + ) -> (Self, String) { + let path_buf = pathdiff::diff_paths(&path, base_path).unwrap_or_else(|| path.clone()); + // The index is '/'-canonical on every platform; fold native separators. + let relative_path = + crate::path_utils::to_canonical_slashes(&path_buf.to_string_lossy()).into_owned(); + + let (size, modified) = match metadata { + Some(metadata) => { + let size = metadata.len(); + let modified = metadata + .modified() + .ok() + .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map_or(0, |d| d.as_secs()); + + (size, modified) + } + None => (0, 0), + }; + + let is_binary = is_known_binary_extension(&path); + + let filename_start = relative_path + .rfind(std::path::is_separator) + .map(|i| i + 1) + .unwrap_or(0) as u16; + + let item = Self::new_raw(filename_start, size, modified, git_status, is_binary); + (item, relative_path) + } + + /// Create a FileItem with an empty ChunkedString from a path on disk. + /// + /// Returns `(file_item, relative_path_string)`. The relative path must be + /// kept alongside the FileItem until `build_chunked_path_store_and_assign` + /// populates each item's `path` field from the shared arena. + pub fn new_from_walk( + path: &Path, + base_path: &Path, + git_status: Option, + metadata: Option<&std::fs::Metadata>, + ) -> (Self, String) { + let (size, modified) = match metadata { + Some(metadata) => { + let size = metadata.len(); + let modified = metadata + .modified() + .ok() + .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map_or(0, |d| d.as_secs()); + (size, modified) + } + None => (0, 0), + }; + + Self::new_from_walk_parts(path, base_path, git_status, size, modified) + } + + /// Like [`Self::new_from_walk`] but takes already-extracted size and + /// modification time (Unix seconds) instead of a `std::fs::Metadata`. + /// Used by the zlob walker backend, which fetches metadata in bulk. + pub fn new_from_walk_parts( + path: &Path, + base_path: &Path, + git_status: Option, + size: u64, + modified: u64, + ) -> (Self, String) { + let is_binary = is_known_binary_extension(path); + + let rel = pathdiff::diff_paths(path, base_path).unwrap_or_else(|| path.to_path_buf()); + // The index is '/'-canonical on every platform; fold native separators. + let rel_str = crate::path_utils::to_canonical_slashes(&rel.to_string_lossy()).into_owned(); + let fname_offset = rel_str + .rfind(std::path::is_separator) + .map(|i| i + 1) + .unwrap_or(0) as u16; + + let item = Self::new_raw(fname_offset, size, modified, git_status, is_binary); + (item, rel_str) + } + + /// Zlob-walker fast path: skip the `pathdiff::diff_paths` PathBuf alloc by + /// taking the already-relative slice and the basename-offset that zlob's + /// scanner computed during traversal. ~80–120 ms saved on a chromium scan + /// (500k entries × one fewer alloc + no component walk). + /// + /// `relative_path` is root-relative bytes; `basename_offset` is the byte + /// offset where the basename begins (e.g. zlob's `entry.path_bytes().len() + /// - entry.file_name().as_os_str().as_encoded_bytes().len()` minus the + /// `relative_offset`). + pub fn new_from_walk_bytes( + path: &Path, + relative_path: &[u8], + basename_offset: u16, + git_status: Option, + size: u64, + modified: u64, + ) -> (Self, String) { + let is_binary = is_known_binary_extension(path); + // SAFETY-ish: paths on macOS/Linux are bytes; lossy conversion mirrors + // the existing `to_string_lossy()` behavior on non-UTF8 names. + let rel_str = String::from_utf8_lossy(relative_path).into_owned(); + let item = Self::new_raw(basename_offset, size, modified, git_status, is_binary); + (item, rel_str) + } + + pub(crate) fn update_frecency_scores( + &mut self, + tracker: &FrecencyTracker, + arena: ArenaPtr, + base_path: &Path, + mode: FFFMode, + ) -> Result<(), Error> { + let mut abs_buf = [0u8; crate::simd_path::PATH_BUF_SIZE]; + let abs = self.write_absolute_path(arena, base_path, &mut abs_buf); + self.access_frecency_score = tracker.get_access_score(abs, mode) as i16; + self.modification_frecency_score = + tracker.get_modification_score(self.modified, self.git_status, mode) as i16; + + Ok(()) + } +} + +/// Options for creating a [`FilePicker`]. +pub struct FilePickerOptions { + pub base_path: String, + /// Pre-populate mmap caches for top-frecency files after the initial scan. + pub enable_mmap_cache: bool, + /// Build content index after the initial scan for faster content-aware filtering. + pub enable_content_indexing: bool, + /// Mode of the picker impact the way file watcher events are handled and the scoring logic + pub mode: FFFMode, + /// Explicit cache budget. When `None`, the budget is auto-computed from + /// the repo size after the initial scan completes. + pub cache_budget: Option, + /// When `false`, `new_with_shared_state` skips the background file watcher. + pub watch: bool, + /// Follow symbolic links during file indexing. + pub follow_symlinks: bool, + /// Allow indexing the filesystem root (`/`). Off by default — these dirs + /// generate enormous fs-event traffic and are rarely the intended target. + pub enable_fs_root_scanning: bool, + /// Allow indexing the user's home directory. Off by default for the same + /// reason as `enable_fs_root_scanning`. + pub enable_home_dir_scanning: bool, +} + +impl Default for FilePickerOptions { + fn default() -> Self { + Self { + base_path: ".".into(), + enable_mmap_cache: false, + enable_content_indexing: false, + mode: FFFMode::default(), + cache_budget: None, + watch: true, + follow_symlinks: false, + enable_fs_root_scanning: false, + enable_home_dir_scanning: false, + } + } +} + +pub struct FilePicker { + pub mode: FFFMode, + pub base_path: PathBuf, + sync_data: FileSync, + pub(crate) signals: ScanSignals, + pub(crate) background_watcher: Option, + /// Single serialized writer for all git-status updates (scan, watcher, + /// FFI). Owned by the picker so it exists before the first scan; its + /// consumer thread is spawned lazily once a git workdir is discovered. + pub(crate) git_status_worker: Arc, + cache_budget: Arc, + has_explicit_cache_budget: bool, + scanned_files_count: Arc, + enable_mmap_cache: bool, + enable_content_indexing: bool, + watch: bool, + follow_symlinks: bool, + enable_fs_root_scanning: bool, + enable_home_dir_scanning: bool, + trace_span: tracing::Span, + trace_id: String, +} + +impl std::fmt::Debug for FilePicker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FilePicker") + .field("base_path", &self.base_path) + .field("sync_data", &self.sync_data) + .field( + "is_scanning", + &self.signals.scanning.load(Ordering::Relaxed), + ) + .field( + "scanned_files_count", + &self.scanned_files_count.load(Ordering::Relaxed), + ) + .finish_non_exhaustive() + } +} + +impl FFFStringStorage for &FilePicker { + #[inline] + fn arena_for(&self, file: &FileItem) -> crate::simd_path::ArenaPtr { + self.sync_data.arena_for_file(file) + } + + #[inline] + fn base_arena(&self) -> crate::simd_path::ArenaPtr { + self.sync_data.arena_base_ptr() + } + + #[inline] + fn overflow_arena(&self) -> crate::simd_path::ArenaPtr { + self.sync_data.arena_overflow_ptr() + } +} + +impl FilePicker { + pub fn base_path(&self) -> &Path { + &self.base_path + } + + /// Ignore rules the walker assembled during the last scan (zlob backend + /// only). The background watcher uses these to filter events without + /// libgit2. `None` when the backend doesn't surface rules or no ignore + /// files were present. + pub(crate) fn ignore_rules(&self) -> Option> { + self.sync_data.ignore_rules.clone() + } + + pub fn has_mmap_cache(&self) -> bool { + self.enable_mmap_cache + } + + pub fn has_content_indexing(&self) -> bool { + self.enable_content_indexing + } + + pub fn has_watcher(&self) -> bool { + self.watch + } + + pub fn follows_symlinks(&self) -> bool { + self.follow_symlinks + } + + pub fn fs_root_scanning_enabled(&self) -> bool { + self.enable_fs_root_scanning + } + + pub fn home_dir_scanning_enabled(&self) -> bool { + self.enable_home_dir_scanning + } + + pub fn trace_id(&self) -> &str { + &self.trace_id + } + + pub fn trace_span(&self) -> tracing::Span { + self.trace_span.clone() + } + + pub fn mode(&self) -> FFFMode { + self.mode + } + + pub fn cache_budget(&self) -> &ContentCacheBudget { + &self.cache_budget + } + + pub fn bigram_index(&self) -> Option<&BigramFilter> { + self.sync_data.bigram_index.as_deref() + } + + pub fn bigram_overlay(&self) -> Option<&parking_lot::RwLock> { + self.sync_data.bigram_overlay.as_deref() + } + + pub fn get_file_mut(&mut self, index: usize) -> Option<(ArenaPtr, &mut FileItem)> { + self.sync_data.get_file_mut(index) + } + + /// Absolute path to the repository root if the indexed tree lives + /// inside a git working directory. `None` for non-git bases. + pub fn git_root(&self) -> Option<&Path> { + self.sync_data.git_workdir.as_deref() + } + + pub fn has_explicit_cache_budget(&self) -> bool { + self.has_explicit_cache_budget + } + + pub fn set_cache_budget(&mut self, budget: ContentCacheBudget) { + self.cache_budget = Arc::new(budget); + } + + /// Get all indexed files sorted by path. + /// Note: Files are stored sorted by PATH for efficient insert/remove. + /// For frecency-sorted results, use search() which sorts matched results. + pub fn get_files(&self) -> &[FileItem] { + self.sync_data.files() + } + + /// Count of live (non-tombstoned) files. O(1). + #[inline] + pub fn live_file_count(&self) -> usize { + self.sync_data.live_count + } + + pub fn get_overflow_files(&self) -> &[FileItem] { + self.sync_data.overflow_files() + } + + /// Get the directory table (sorted by path). + pub fn get_dirs(&self) -> &[DirItem] { + &self.sync_data.dirs + } + + /// Actual heap bytes used: (chunked_path_store, 0, 0). + /// The second element is 0 because leaked overflow stores aren't tracked. + pub fn arena_bytes(&self) -> (usize, usize, usize) { + let chunked = self + .sync_data + .chunked_paths + .as_ref() + .map_or(0, |s| s.heap_bytes()); + + (chunked, 0, 0) + } + + #[tracing::instrument(level = "debug", skip_all)] + pub(crate) fn for_each_dir(&self, mut f: impl FnMut(&Path) -> ControlFlow<()>) { + let dir_table = &self.sync_data.dirs; + let base = self.base_path.as_path(); + + if !dir_table.is_empty() { + let arena = self.arena_base_ptr(); + let mut path_buf = PathBuf::with_capacity(crate::simd_path::PATH_BUF_SIZE); + let mut prev_relative_path = String::new(); + + let mut scratch_buf = [0u8; crate::simd_path::PATH_BUF_SIZE]; + for dir_item in dir_table.iter() { + let full_relative_path = dir_item.read_relative_path(arena, &mut scratch_buf); + let relative_path = full_relative_path.trim_end_matches(std::path::is_separator); + + if relative_path.is_empty() { + // Files directly under base_path + prev_relative_path.clear(); + continue; + } + + let mut i = common_dir_prefix_len(&prev_relative_path, relative_path); + // If we stopped on a separator, skip it — we want to start + // emitting at the first unseen segment, not re-emit the + // already-emitted prefix path. + if i < relative_path.len() + && std::path::is_separator(relative_path.as_bytes()[i] as char) + { + i += 1; + } + + // Walk the suffix of `relative_path` one segment at a time, emitting + // each previously unseen ancestor up to and including `relative_path`. + while i < relative_path.len() { + let next_sep = relative_path[i..] + .find(std::path::is_separator) + .map(|off| i + off) + .unwrap_or(relative_path.len()); + let ancestor_rel = &relative_path[..next_sep]; + + path_buf.clear(); + path_buf.push(base); + path_buf.push(ancestor_rel); + + // we can't really emit iterator here unfortunately + if matches!(f(path_buf.as_path()), ControlFlow::Break(())) { + return; + } + + i = next_sep + 1; + } + + prev_relative_path.clear(); + prev_relative_path.push_str(relative_path); + } + return; + } + + // fallback that should never be happening, but it is possible to get the file + // path from the absolute path using components api as well: + let files = self.sync_data.files(); + let arena = self.arena_base_ptr(); + let mut current = self.base_path.clone(); + let mut path_buf = [0u8; PATH_BUF_SIZE]; + + for file in files { + let abs = file.write_absolute_path(arena, base, &mut path_buf); + let Some(parent) = abs.parent() else { + continue; + }; + if parent == current.as_path() { + continue; + } + + while current.as_path() != base && !parent.starts_with(¤t) { + current.pop(); + } + + let Ok(remainder) = parent.strip_prefix(¤t) else { + continue; + }; + for component in remainder.components() { + current.push(component); + if matches!(f(current.as_path()), ControlFlow::Break(())) { + return; + } + } + } + } + + /// Create a new FilePicker from options. + /// Always prefer new_with_shared_state for the consumer application, use this only if you know + /// what you are doing. This won't spawn the backgraound watcher and won't walk the file tree. + pub fn new(options: FilePickerOptions) -> Result { + let path = PathBuf::from(&options.base_path); + if !path.exists() { + error!("Base path does not exist: {}", options.base_path); + return Err(Error::InvalidPath(path)); + } + if path.parent().is_none() && !options.enable_fs_root_scanning { + error!("Refusing to index filesystem root: {}", path.display()); + return Err(Error::FilesystemRoot(path)); + } + if !options.enable_home_dir_scanning + && Some(path.as_os_str()) == dirs::home_dir().as_ref().map(|p| p.as_os_str()) + { + error!("Refusing to index home directory: {}", path.display()); + return Err(Error::FilesystemRoot(path)); + } + + // Windows-only: canonicalize with so the base path does NOT + // have the `\\?\` UNC prefix that `std::fs::canonicalize` adds. + // libgit2's `repo.workdir()` + #[cfg(windows)] + let path = crate::path_utils::canonicalize(&path).unwrap_or(path); + + let has_explicit_budget = options.cache_budget.is_some(); + let initial_budget = options.cache_budget.unwrap_or_default(); + + let trace_id = crate::log::generate_trace_id(); + let trace_span = crate::log::trace_span(&trace_id, "picker"); + + Ok(FilePicker { + background_watcher: None, + git_status_worker: crate::git_status_worker::GitStatusWorker::new(), + base_path: path, + cache_budget: Arc::new(initial_budget), + has_explicit_cache_budget: has_explicit_budget, + signals: crate::scan::ScanSignals::default(), + mode: options.mode, + scanned_files_count: Arc::new(AtomicUsize::new(0)), + sync_data: FileSync::new(), + enable_mmap_cache: options.enable_mmap_cache, + enable_content_indexing: options.enable_content_indexing, + watch: options.watch, + follow_symlinks: options.follow_symlinks, + enable_fs_root_scanning: options.enable_fs_root_scanning, + enable_home_dir_scanning: options.enable_home_dir_scanning, + trace_span, + trace_id, + }) + } + + /// Create a picker, place it into the shared handle, and spawn background + /// indexing + file-system watcgenerate_trace_id the default entry point. + pub fn new_with_shared_state( + shared_picker: SharedFilePicker, + shared_frecency: SharedFrecency, + options: FilePickerOptions, + ) -> Result<(), Error> { + let picker = Self::new(options)?; + + info!( + "Spawning background threads: base_path={}, warmup={}, content_indexing={}, mode={:?}", + picker.base_path.display(), + picker.enable_mmap_cache, + picker.enable_content_indexing, + picker.mode, + ); + + let warmup = picker.enable_mmap_cache; + let content_indexing = picker.enable_content_indexing; + let watch = picker.watch; + let mode = picker.mode; + let follow_symlinks = picker.follow_symlinks; + let enable_fs_root_scanning = picker.enable_fs_root_scanning; + let enable_home_dir_scanning = picker.enable_home_dir_scanning; + + let signals = picker.scan_signals(); + let scanned_files_counter = picker.scanned_files_counter(); + let path = picker.base_path.clone(); + let trace_span = picker.trace_span.clone(); + + // Pre-arm `scanning` BEFORE publishing the new picker. `ScanJob::spawn` + // also sets it, but that runs after this function returns; consumers + // (e.g. lua `wait_for_initial_scan` after `restart_index_in_path`) + // that grab the signal Arc between publish and spawn would otherwise + // observe scanning=false and skip the wait, racing the walker. The + // race is wide on Windows CI where notify is slow. + signals + .scanning + .store(true, std::sync::atomic::Ordering::Release); + + { + let mut guard = shared_picker.write()?; + *guard = Some(picker); + // dropping old picker flips its `cancelled` flag → bg threads exit cleanly + } + + ScanJob::new_initial( + shared_picker, + shared_frecency, + path, + mode, + signals, + scanned_files_counter, + trace_span, + ScanConfig { + warmup, + content_indexing, + watch, + auto_cache_budget: true, + install_watcher: true, + follow_symlinks, + enable_fs_root_scanning, + enable_home_dir_scanning, + }, + ) + .spawn(); + + Ok(()) + } + + /// Synchronous filesystem scan — populates `self` with indexed files. + /// + /// Use this when you need direct access to the picker without shared state: + /// ```ignore + /// let mut picker = FilePicker::new(options)?; + /// picker.collect_files()?; + /// // picker.get_files() is now populated + /// ``` + pub fn collect_files(&mut self) -> Result<(), Error> { + self.signals.scanning.store(true, Ordering::Relaxed); + self.scanned_files_count.store(0, Ordering::Relaxed); + + let git_workdir = FileSync::discover_git_workdir(&self.base_path); + let git_handle = git_workdir.clone().map(FileSync::spawn_git_status); + + let empty_frecency = SharedFrecency::default(); + let sync = FileSync::walk_filesystem( + &self.base_path, + git_workdir, + &self.scanned_files_count, + &empty_frecency, + self.mode, + self.follow_symlinks, + )?; + + self.sync_data = sync; + + if !self.has_explicit_cache_budget { + let file_count = self.sync_data.files().len(); + self.cache_budget = Arc::new(ContentCacheBudget::new_for_repo(file_count)); + } else { + self.cache_budget.reset(); + } + + if let Some(handle) = git_handle + && let Ok(Some(git_cache)) = handle.join() + { + let mut path_buf = [0u8; crate::simd_path::PATH_BUF_SIZE]; + + let arena = self.arena_base_ptr(); + for file in self.sync_data.files.iter_mut() { + file.git_status = git_cache.lookup_status(file.write_absolute_path( + arena, + &self.base_path, + &mut path_buf, + )); + } + } + + self.signals.scanning.store(false, Ordering::Relaxed); + Ok(()) + } + + /// Perform fuzzy search on files with a pre-parsed query. + /// + /// The query should be parsed using [`crate::FFFQuery`] before calling + /// this function. If a [`crate::QueryTracker`] is provided, the search will + /// automatically look up the last selected file for this query and boost it + #[tracing::instrument(skip_all, name = "Fuzzy file search", fields(query = query.raw_query))] + pub fn fuzzy_search<'q>( + &self, + query: &'q FFFQuery<'q>, + query_tracker: Option<&QueryTracker>, + options: FuzzySearchOptions<'q>, + ) -> SearchResult<'_> { + let files = self.get_files(); + let max_threads = if options.max_threads == 0 { + std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(4) + } else { + options.max_threads + }; + + debug!( + raw_query = ?query.raw_query, + pagination = ?options.pagination, + ?max_threads, + current_file = ?options.current_file, + "Fuzzy search", + ); + + let total_files = self.live_file_count(); + let location = query.location; + + // Get effective query for max_typos calculation (without location suffix) + let effective_query = match &query.fuzzy_query { + fff_query_parser::FuzzyQuery::Text(t) => *t, + fff_query_parser::FuzzyQuery::Parts(parts) if !parts.is_empty() => parts[0], + _ => query.raw_query.trim(), + }; + + // small queries with a large number of results can match absolutely everything + let max_typos = (effective_query.len() as u16 / 4).clamp(2, 6); + // Look up the last file selected for this query (combo-boost scoring) + let last_same_query_entry = + query_tracker + .zip(options.project_path) + .and_then(|(tracker, project_path)| { + tracker + .get_last_query_entry( + query.raw_query, + project_path, + options.min_combo_count, + ) + .ok() + .flatten() + }); + + let context = ScoringContext { + query, + max_typos, + max_threads, + project_path: options.project_path, + current_file: options.current_file, + last_same_query_match: last_same_query_entry, + combo_boost_score_multiplier: options.combo_boost_score_multiplier, + min_combo_count: options.min_combo_count, + pagination: options.pagination, + }; + + let time = std::time::Instant::now(); + + let base_arena = self.sync_data.arena_base_ptr(); + let overflow_arena = self.sync_data.arena_overflow_ptr(); + + let (items, scores, total_matched) = fuzzy_match_and_score_files( + files, + &context, + self.sync_data.base_count, + base_arena, + overflow_arena, + ); + + info!( + ?query, + completed_in = ?time.elapsed(), + total_matched, + returned_count = items.len(), + pagination = ?options.pagination, + "Fuzzy search completed", + ); + + SearchResult { + items, + scores, + total_matched, + total_files, + location, + } + } + + /// Perform fuzzy search on indexed directories. + /// + /// Returns directories ranked by fuzzy match quality + frecency. + pub fn fuzzy_search_directories<'q>( + &self, + query: &'q FFFQuery<'q>, + options: FuzzySearchOptions<'q>, + ) -> DirSearchResult<'_> { + let dirs = self.get_dirs(); + let max_threads = if options.max_threads == 0 { + std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(4) + } else { + options.max_threads + }; + + let total_dirs = dirs.len(); + + let effective_query = match &query.fuzzy_query { + fff_query_parser::FuzzyQuery::Text(t) => *t, + fff_query_parser::FuzzyQuery::Parts(parts) if !parts.is_empty() => parts[0], + _ => query.raw_query.trim(), + }; + + let max_typos = (effective_query.len() as u16 / 4).clamp(2, 6); + + let context = ScoringContext { + query, + max_typos, + max_threads, + project_path: options.project_path, + current_file: options.current_file, + last_same_query_match: None, + combo_boost_score_multiplier: 0, + min_combo_count: 0, + pagination: options.pagination, + }; + + let arena = self.sync_data.arena_base_ptr(); + let time = std::time::Instant::now(); + + let (items, scores, total_matched) = + crate::score::fuzzy_match_and_score_dirs(dirs, &context, arena); + + info!( + ?query, + completed_in = ?time.elapsed(), + total_matched, + returned_count = items.len(), + "Directory search completed", + ); + + DirSearchResult { + items, + scores, + total_matched, + total_dirs, + } + } + + /// Perform a mixed fuzzy search across both files and directories. + /// + /// Returns a single flat list where files and directories are interleaved + /// by total score in descending order. + /// + /// If the raw query ends with a path separator (`/`), only directories + /// are searched — files are skipped entirely. The caller should parse the + /// query with `DirSearchConfig` so that trailing `/` is kept as fuzzy + /// text instead of becoming a `PathSegment` constraint. + pub fn fuzzy_search_mixed<'q>( + &self, + query: &'q FFFQuery<'q>, + query_tracker: Option<&QueryTracker>, + options: FuzzySearchOptions<'q>, + ) -> MixedSearchResult<'_> { + let location = query.location; + let page_offset = options.pagination.offset; + let page_limit = if options.pagination.limit > 0 { + options.pagination.limit + } else { + 100 + }; + + let dirs_only = + query.raw_query.ends_with(std::path::MAIN_SEPARATOR) || query.raw_query.ends_with('/'); + + // Run file search and dir search with no pagination (we merge then paginate). + let internal_limit = page_offset.saturating_add(page_limit).saturating_mul(2); + + let dir_options = FuzzySearchOptions { + pagination: PaginationArgs { + offset: 0, + limit: internal_limit, + }, + ..options + }; + let dir_results = self.fuzzy_search_directories(query, dir_options); + + if dirs_only { + let total_matched = dir_results.total_matched; + let total_dirs = dir_results.total_dirs; + + let mut merged: Vec<(MixedItemRef<'_>, Score)> = + Vec::with_capacity(dir_results.items.len()); + for (dir, score) in dir_results.items.into_iter().zip(dir_results.scores) { + merged.push((MixedItemRef::Dir(dir), score)); + } + + if page_offset >= merged.len() { + return MixedSearchResult { + items: vec![], + scores: vec![], + total_matched, + total_files: self.live_file_count(), + total_dirs, + location, + }; + } + + let end = (page_offset + page_limit).min(merged.len()); + let page = merged.drain(page_offset..end); + let (items, scores): (Vec<_>, Vec<_>) = page.unzip(); + + return MixedSearchResult { + items, + scores, + total_matched, + total_files: self.live_file_count(), + total_dirs, + location, + }; + } + + let file_options = FuzzySearchOptions { + pagination: PaginationArgs { + offset: 0, + limit: internal_limit, + }, + ..options + }; + let file_results = self.fuzzy_search(query, query_tracker, file_options); + + // Merge by score descending. + let total_matched = file_results.total_matched + dir_results.total_matched; + let total_files = file_results.total_files; + let total_dirs = dir_results.total_dirs; + + let mut merged: Vec<(MixedItemRef<'_>, Score)> = + Vec::with_capacity(file_results.items.len() + dir_results.items.len()); + + for (file, score) in file_results.items.into_iter().zip(file_results.scores) { + merged.push((MixedItemRef::File(file), score)); + } + for (dir, score) in dir_results.items.into_iter().zip(dir_results.scores) { + merged.push((MixedItemRef::Dir(dir), score)); + } + + // Sort merged results by total score descending. + merged.sort_unstable_by_key(|b| std::cmp::Reverse(b.1.total)); + + // Paginate. + if page_offset >= merged.len() { + return MixedSearchResult { + items: vec![], + scores: vec![], + total_matched, + total_files, + total_dirs, + location, + }; + } + + let end = (page_offset + page_limit).min(merged.len()); + let page = merged.drain(page_offset..end); + let (items, scores): (Vec<_>, Vec<_>) = page.unzip(); + + MixedSearchResult { + items, + scores, + total_matched, + total_files, + total_dirs, + location, + } + } + + /// Glob search: filter indexed files by a single glob pattern, rank by + /// frecency, and paginate. Bypasses the regular query parser entirely — + /// useful when callers already have a literal glob (`*.rs`, `**/*.test.ts`) + /// and want neither fuzzy matching nor multi-token constraint parsing. + /// + /// Pipeline: `apply_constraints(Glob) → score_filtered_by_frecency → sort_and_paginate`. + /// Same ranking semantics as `fuzzy_search` when the fuzzy query is empty. + pub fn glob<'p>( + &'p self, + pattern: &'p str, + options: FuzzySearchOptions<'p>, + ) -> SearchResult<'p> { + let query = FFFQuery { + raw_query: pattern, + constraints: vec![fff_query_parser::Constraint::Glob(pattern)], + fuzzy_query: fff_query_parser::FuzzyQuery::Empty, + location: None, + }; + + // `fuzzy_search` short-circuits to `score_filtered_by_frecency` when + // `fuzzy_query` is `Empty`, then runs the same `sort_and_paginate` + // path. Reusing it keeps the ranking guarantees identical without + // exposing the private scoring helpers. + self.fuzzy_search(&query, None, options) + } + + /// Perform a live grep search across indexed files. + /// + /// If `options.abort_signal` is set it overrides the picker's internal + /// cancellation flag, giving the caller full control over when to stop. + pub fn grep(&self, query: &FFFQuery<'_>, options: &GrepSearchOptions) -> GrepResult<'_> { + let overlay_guard = self.sync_data.bigram_overlay.as_ref().map(|o| o.read()); + let arena = self.arena_base_ptr(); + let overflow_arena = self.sync_data.arena_overflow_ptr(); + let cancel = options + .abort_signal + .as_deref() + .unwrap_or(&self.signals.cancelled); + + SEARCH_THREAD_POOL.install(|| { + grep_search( + self.get_files(), + query, + options, + self.cache_budget(), + self.sync_data.bigram_index.as_deref(), + overlay_guard.as_deref(), + cancel, + &self.base_path, + arena, + overflow_arena, + ) + }) + } + + /// Multi-pattern grep search across indexed files. + pub fn multi_grep( + &self, + patterns: &[&str], + constraints: &[fff_query_parser::Constraint<'_>], + options: &GrepSearchOptions, + ) -> GrepResult<'_> { + let overlay_guard = self.sync_data.bigram_overlay.as_ref().map(|o| o.read()); + let arena = self.arena_base_ptr(); + let overflow_arena = self.sync_data.arena_overflow_ptr(); + let cancel = options + .abort_signal + .as_deref() + .unwrap_or(&self.signals.cancelled); + + SEARCH_THREAD_POOL.install(|| { + multi_grep_search( + self.get_files(), + patterns, + constraints, + options, + self.cache_budget(), + self.sync_data.bigram_index.as_deref(), + overlay_guard.as_deref(), + cancel, + &self.base_path, + arena, + overflow_arena, + ) + }) + } + + // Returns an ongoing or finisshed scan progress + pub fn get_scan_progress(&self) -> ScanProgress { + let scanned_count = self.scanned_files_count.load(Ordering::Relaxed); + let is_scanning = self.signals.scanning.load(Ordering::Relaxed); + + ScanProgress { + scanned_files_count: scanned_count, + is_scanning, + is_watcher_ready: self.signals.watcher_ready.load(Ordering::Relaxed), + is_warmup_complete: !self.enable_content_indexing + || self.sync_data.bigram_index.is_some(), + } + } + + pub(crate) fn set_bigram_index(&mut self, index: BigramFilter) { + self.sync_data.bigram_index = Some(Arc::new(index)); + // once the index is reset automatically reset the overaly + self.sync_data.bigram_overlay = Some(Arc::new(parking_lot::RwLock::new( + BigramOverlay::new(self.sync_data.indexable_count), + ))); + } + + pub(crate) fn scan_signals(&self) -> crate::scan::ScanSignals { + self.signals.clone() + } + + pub(crate) fn scanned_files_counter(&self) -> Arc { + Arc::clone(&self.scanned_files_count) + } + + /// Capture raw pointers to the picker's internal arrays for off-lock use. + /// + /// Sets `post_scan_indexing_active` and returns a snapshot that clears it + /// on drop. This is the ONLY approved way to escape the lock for + /// long-running parallel work (git status, warmup, bigram). + /// + /// Returns `None` if `post_scan_indexing_active` is already set — this + /// means another post-scan is in flight and we must not create a second + /// set of dangling pointers. + /// + /// # Safety + /// 1. `walk_filesystem` reserved `MAX_OVERFLOW_FILES` capacity on the + /// files Vec at creation — watcher pushes cannot reallocate it. + /// 2. `post_scan_indexing_active` is set — prevents `commit_new_sync` + /// from replacing the Vec (ScanJob::new checks this flag). + /// 3. Only `[..base_count]` is accessed — base files use the immutable + /// base arena. Overflow files use a different arena. + pub(crate) unsafe fn post_scan_snapshot(&self) -> Option { + if self + .signals + .post_scan_indexing_active + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_err() + { + tracing::error!( + "Can not acquire post scan unsafe snapshot, someone already acquired it" + ); + return None; + } + + Some(PostScanUnsafeSnapshot { + files: self.sync_data.files.clone(), + arena: self.sync_data.chunked_paths.as_ref().map(Arc::clone), + base_count: self.sync_data.base_count, + indexable_count: self.sync_data.indexable_count, + base_path: self.base_path.clone(), + post_scan_flag: Arc::clone(&self.signals.post_scan_indexing_active), + _budget: Arc::clone(&self.cache_budget), + }) + } + + pub(crate) fn commit_new_sync(&mut self, sync: FileSync) { + self.sync_data = sync; + self.cache_budget.reset(); + } + + #[inline] + pub(crate) fn arena_base_ptr(&self) -> ArenaPtr { + self.sync_data.arena_base_ptr() + } + + /// Update git statuses for files, using the provided shared frecency tracker. + pub(crate) fn update_git_statuses( + &mut self, + status_cache: GitStatusCache, + shared_frecency: &SharedFrecency, + ) -> Result<(), Error> { + debug!( + statuses_count = status_cache.statuses_len(), + "Updating git status", + ); + + let mode = self.mode; + let bp = self.base_path.clone(); + let frecency = shared_frecency.read()?; + + status_cache + .into_iter() + .try_for_each(|(path, status)| -> Result<(), Error> { + if let Some((arena, file)) = self.get_mut_file_by_path(&path) { + file.git_status = Some(status); + if let Some(ref f) = *frecency { + file.update_frecency_scores(f, arena, &bp, mode)?; + } + // Update parent dir frecency inline. `DirItem` has an + // interior-mutable atomic score, so `&self` access is + // enough — no write aliasing against Arc clones. + let score = file.access_frecency_score as i32; + let dir_idx = file.parent_dir_index as usize; + if let Some(dir) = self.sync_data.dirs.get(dir_idx) { + dir.update_frecency_if_larger(score); + } + } else { + // Expected on sparse checkouts: git reports a status for + // a path that isn't materialized on disk and therefore + // isn't in the file index. Don't spam the log (#404). + debug!(?path, "Git status for path not in index, skipping"); + } + Ok(()) + })?; + + Ok(()) + } + + pub fn update_single_file_frecency( + &mut self, + file_path: impl AsRef, + frecency_tracker: &FrecencyTracker, + ) -> Result<(), Error> { + let path = file_path.as_ref(); + + let Some(index) = self.sync_data.find_file_index(path, &self.base_path) else { + return Ok(()); + }; + + if let Some((arena, file)) = self.sync_data.get_file_mut(index) { + file.update_frecency_scores(frecency_tracker, arena, &self.base_path, self.mode)?; + + // Update parent dir frecency inline (atomic, &self access). + let score = file.access_frecency_score as i32; + let dir_idx = file.parent_dir_index as usize; + if let Some(dir) = self.sync_data.dirs.get(dir_idx) { + dir.update_frecency_if_larger(score); + } + } + + Ok(()) + } + + pub fn get_file_by_path(&self, path: impl AsRef) -> Option<&FileItem> { + self.sync_data + .find_file_index(path.as_ref(), &self.base_path) + .and_then(|index| self.sync_data.files().get(index)) + } + + pub fn get_mut_file_by_path( + &mut self, + path: impl AsRef, + ) -> Option<(ArenaPtr, &mut FileItem)> { + let path = path.as_ref(); + let index = self.sync_data.find_file_index(path, &self.base_path); + index.and_then(|i| self.sync_data.get_file_mut(i)) + } + + /// Handle the event of certain file being modified or adds a neww file if it is not added + /// If this function returns `None` it means that picker is in the invalid state, or the capacity + /// of index is exhausted and a new rescan needs to be triggered. + #[tracing::instrument(skip(self),level = Level::DEBUG)] + pub fn handle_create_or_modify(&mut self, path: impl AsRef + Debug) -> Option<&FileItem> { + let path = path.as_ref(); + + if let Some(idx) = self.sync_data.find_file_index(path, &self.base_path) { + let slot = if idx < self.sync_data.base_count { + FileSlot::Base(idx) + } else { + FileSlot::Overflow(idx) + }; + + return self.handle_file_modify(path, slot); + } + + self.add_new_file(path) + } + + #[tracing::instrument(skip_all, fields(path = ?path), level = Level::DEBUG)] + fn handle_file_modify(&mut self, path: &Path, slot: FileSlot) -> Option<&FileItem> { + let overlay = self.sync_data.bigram_overlay.as_ref().map(Arc::clone); + let pos = slot.index(); + + // this is the only way to actually know if the file is on disk, we CAN NOT + // rely on the watcher to proive the latest state of the file, do the actual check + let metadata = match std::fs::metadata(path) { + Ok(m) => { + self.untombstone_file(pos); + + m + } + Err(_) => { + self.tombstone_file(pos); + return None; + } + }; + + let (_arena, file) = self.sync_data.get_file_mut(pos)?; + + let size = metadata.len(); + let modified_time = metadata + .modified() + .ok() + .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|d| d.as_secs()); + + file.update_metadata(&self.cache_budget, modified_time, Some(size)); + + // Re-classify binary status from current content (chunked, fixed + // buffer). Already-binary files are left alone. + if !file.is_binary() { + let mut chunk = [0u8; crate::types::BINARY_CLASSIFICATION_CHUNK_SIZE]; + file.detect_binary_per_byte(path, &mut chunk); + } + + // Indexable base-region files feed fresh content to the bigram overlay. + if matches!(slot, FileSlot::Base(_)) + && let Some(ref overlay) = overlay + { + let in_indexable = { + let guard = overlay.read(); + pos < guard.base_file_count() + }; + + if in_indexable && let Ok(content) = std::fs::read(path) { + overlay.write().modify_file(pos, &content); + } + } + + self.sync_data.files().get(pos) + } + + /// Adds a new file to picker, if the file can not be added returns `None` + /// which indicates that it's time to trigger a new sync + #[tracing::instrument(skip(self))] + pub fn add_new_file(&mut self, path: &Path) -> Option<&FileItem> { + // On Windows `pathdiff::diff_paths` is byte-wise, so a short-name + // input never shares a prefix with the canonicalized base_path and + // the resulting relative path becomes absolute. Canonicalize first. + #[cfg(windows)] + let canonical_buf: Option = if path.starts_with(&self.base_path) { + None + } else if let Ok(c) = crate::path_utils::canonicalize(path) { + Some(c) + } else { + tracing::error!(path = ?path.display(), "Failed to canonicalize file path to add"); + return None; + }; + + #[cfg(windows)] + let path_for_index: &Path = canonical_buf.as_deref().unwrap_or(path); + #[cfg(not(windows))] + let path_for_index: &Path = path; + + let (mut file_item, rel_path) = + FileItem::new(path_for_index.to_path_buf(), &self.base_path, None); + + // we have to perform manual classification for every new file this will be + // batched during the scan, this is the path when the file is ad-hoc added to the sync + file_item.detect_binary_per_byte( + path_for_index, + // inline chunk buf + &mut [0u8; crate::types::BINARY_CLASSIFICATION_CHUNK_SIZE], + ); + + let builder = self.sync_data.overflow_builder.get_or_insert_with(|| { + // we know that overflow would never create more files during the file + crate::simd_path::ChunkedPathStoreBuilder::new(MAX_OVERFLOW_FILES) + }); + + file_item.set_path(builder.add_file_immediate(&rel_path, file_item.path.filename_offset)); + file_item.set_overflow(true); + + if !self.sync_data.files.push(file_item) { + return None; + } + + self.sync_data.live_count += 1; + self.sync_data.files.last() + } + + fn tombstone_file(&mut self, index: usize) { + let file = &mut self.sync_data.files[index]; + if file.is_deleted() { + return; + } + + file.set_deleted(true); + file.invalidate_mmap(&self.cache_budget); + file.git_status = None; + + // Only base-region files participate in the bigram overlay + if index < self.sync_data.base_count + && let Some(ref overlay) = self.sync_data.bigram_overlay + { + overlay.write().delete_file(index); + } + + self.sync_data.live_count -= 1; + } + + fn untombstone_file(&mut self, index: usize) { + let file = &mut self.sync_data.files[index]; + if !file.is_deleted() { + return; + } + file.set_deleted(false); + + self.sync_data.live_count += 1; + } + + /// Marks file as deleted, make sure that if you call this yourself these changes can be reverted + /// by the internal mechanics if the file actually exists on the disk, use only if you know that + /// the file going to be disapperaed or if you do not have the watcher installed + pub fn remove_file_by_path(&mut self, path: impl AsRef) -> bool { + let path = path.as_ref(); + if let Some(index) = self.sync_data.find_file_index(path, &self.base_path) { + self.tombstone_file(index); + true + } else { + false + } + } + + // TODO make this O(n) + pub fn remove_all_files_in_dir(&mut self, dir: impl AsRef) -> usize { + let dir_path = dir.as_ref(); + let relative_dir = self + .to_relative_path(dir_path) + .map(|c| c.into_owned()) + .unwrap_or_default(); + + let dir_prefix = if relative_dir.is_empty() { + String::new() + } else { + // Stored relative paths are '/'-canonical on every platform. + format!("{relative_dir}/") + }; + + self.sync_data.tombstone_files_with_arena(|file, arena| { + file.relative_path_starts_with(arena, &dir_prefix) + }) + } + + /// Use this to prevent any substantial background threads from acquiring the locks + pub fn cancel(&self) { + self.signals.cancelled.store(true, Ordering::Release); + } + + /// Stop the background filesystem watcher. Non-blocking. + pub fn stop_background_monitor(&mut self) { + if let Some(mut watcher) = self.background_watcher.take() { + watcher.stop(); + } + } + + /// Quick way to check if scan is going without acquiring a lock for [Self::get_scan_progress] + pub fn is_scan_active(&self) -> bool { + self.signals.scanning.load(Ordering::Relaxed) + } + + pub fn is_post_scan_active(&self) -> bool { + self.signals + .post_scan_indexing_active + .load(Ordering::Acquire) + } + + /// Return a clone of the watcher-ready flag so callers can poll it without + /// holding a lock on the picker. + pub fn watcher_signal(&self) -> Arc { + Arc::clone(&self.signals.watcher_ready) + } + + /// Convert an absolute path to a relative path string (relative to base_path). + /// Returns None if the path doesn't start with base_path. + /// + /// On Windows the picker canonicalizes its base via `dunce`, so caller + /// paths that still carry 8.3 short names or a different casing would + /// fail a naive prefix check. Fall back to canonicalizing (or, when the + /// file was just deleted, canonicalizing its parent) before stripping. + fn to_relative_path<'a>(&self, path: &'a Path) -> Option> { + if let Ok(stripped) = path.strip_prefix(&self.base_path) + && let Some(s) = stripped.to_str() + { + // Callers compare against '/'-canonical stored paths. + return Some(crate::path_utils::to_canonical_slashes(s)); + } + + #[cfg(windows)] + { + let rel = canonical_relative_path(path, &self.base_path)?; + return Some(std::borrow::Cow::Owned(rel)); + } + + #[cfg(not(windows))] + None + } +} + +/// Resolve a possibly-short-name Windows path to the picker's canonical base. +/// Used by the Windows-only fallbacks in `to_relative_path` and +/// `find_file_index` so events still match tombstoned entries. +#[cfg(windows)] +fn canonical_relative_path(path: &Path, base: &Path) -> Option { + if let Ok(canonical) = crate::path_utils::canonicalize(path) + && let Ok(stripped) = canonical.strip_prefix(base) + && let Some(s) = stripped.to_str() + { + return Some(crate::path_utils::to_canonical_slashes(s).into_owned()); + } + + // Deleted files can't be canonicalized — canonicalize the parent and + // re-attach the filename. + let parent = path.parent()?; + let file_name = path.file_name()?; + let canonical_parent = crate::path_utils::canonicalize(parent).ok()?; + let stripped_parent = canonical_parent.strip_prefix(base).ok()?; + let mut rel = stripped_parent.to_path_buf(); + rel.push(file_name); + rel.to_str() + .map(|s| crate::path_utils::to_canonical_slashes(s).into_owned()) +} + +impl Drop for FilePicker { + fn drop(&mut self) { + // Cancel any in-flight ScanJob bound to this picker's signals so + // it cannot mutate the replacement picker after a swap. + self.signals.cancelled.store(true, Ordering::Release); + // Wake the git-status consumer so it exits; never joined (it takes + // the picker write lock, a blocking join here could deadlock). + self.git_status_worker.signal_shutdown(); + } +} + +#[derive(Debug, Clone, Copy)] +enum FileSlot { + Base(usize), + Overflow(usize), +} + +impl FileSlot { + fn index(self) -> usize { + match self { + FileSlot::Base(i) | FileSlot::Overflow(i) => i, + } + } +} + +/// Snapshot of FilePicker state for off-lock post-scan work. +/// +/// Each data field is an Arc-shared clone of the picker's backing +/// allocation, so dropping the `FilePicker` (e.g. via +/// `SharedFilePicker::write().take()`) cannot free memory this +/// snapshot is still reading — UAF is impossible by construction. +/// +/// Implements `Drop` to clear `post_scan_indexing_active`. Since only +/// one snapshot can exist at a time (enforced by the flag check in +/// `post_scan_snapshot`) and it is always created/dropped within +/// `ScanJob::run`, `scan_job_running == false` implies no live snapshot. +pub(crate) struct PostScanUnsafeSnapshot { + pub files: StableVec, + pub arena: Option>, + // TODO figure this out + pub _budget: Arc, + pub base_count: usize, + pub indexable_count: usize, + pub base_path: PathBuf, + post_scan_flag: Arc, +} + +impl Drop for PostScanUnsafeSnapshot { + fn drop(&mut self) { + self.post_scan_flag.store(false, Ordering::Release); + } +} + +// SAFETY: every data field is Arc-shared and outlives the snapshot +// via its own refcount. The mutable cast in `apply_git_status_and_frecency` +// is consumed on the scan thread under the single-writer discipline. +unsafe impl Send for PostScanUnsafeSnapshot {} +unsafe impl Sync for PostScanUnsafeSnapshot {} + +/// A point-in-time snapshot of the file-scanning progress. +/// +/// Returned by [`FilePicker::get_scan_progress`]. Useful for displaying +/// a progress indicator while the initial scan is running. +#[derive(Debug, Clone)] +pub struct ScanProgress { + pub scanned_files_count: usize, + pub is_scanning: bool, + pub is_watcher_ready: bool, + pub is_warmup_complete: bool, +} + +impl FileSync { + pub(crate) fn discover_git_workdir(base_path: &Path) -> Option { + let git_workdir = Repository::discover(base_path) + .ok() + .and_then(|repo| repo.workdir().map(Path::to_path_buf)) + .map(crate::path_utils::normalize); + + match &git_workdir { + Some(workdir) => debug!("Git repository found at: {}", workdir.display()), + None => warn!("No git repository found for path: {}", base_path.display()), + } + + git_workdir + } + + pub(crate) fn spawn_git_status(git_workdir: PathBuf) -> JoinHandle> { + std::thread::spawn(move || { + GitStatusCache::read_git_status( + Some(git_workdir.as_path()), + &mut crate::git::initial_scan_status_options(), + ) + }) + } + + /// Returns files immediately (searchable) and a handle to the in-progress + /// git status computation. This avoids blocking on `git status` which can + /// take 10+ seconds on very large repos (e.g. chromium). + #[tracing::instrument(skip_all, name = "walk_filesystem", level = Level::INFO)] + pub(crate) fn walk_filesystem( + base_path: &Path, + git_workdir: Option, + synced_files_count: &Arc, + shared_frecency: &SharedFrecency, + mode: FFFMode, + follow_symlinks: bool, + ) -> Result { + let scan_start = std::time::Instant::now(); + info!("SCAN: Starting filesystem walk and git status (async)"); + + // Walk files (the fast part, typically 2-3s even on huge repos). + let is_git_repo = git_workdir.is_some(); + let bg_threads = BACKGROUND_THREAD_POOL.current_num_threads(); + + let mut walk_output = crate::walk::walk_collect_files( + base_path, + is_git_repo, + follow_symlinks, + bg_threads, + synced_files_count, + )?; + let ignore_rules = walk_output.ignore_rules.take().map(Arc::new); + let mut pairs = walk_output.pairs; + + // Sort by (dir_part, filename). This groups files by their directory + // into contiguous runs so the linear dir-extraction pass below can + // dedupe by comparing only against the previous dir. + BACKGROUND_THREAD_POOL.install(|| { + pairs.par_sort_unstable_by(|(a, path_a), (b, path_b)| { + // SAFETY: `filename_offset` is always at a character boundary + let (a_dir, a_file) = path_a.split_at(a.path.filename_offset as usize); + let (b_dir, b_file) = path_b.split_at(b.path.filename_offset as usize); + a_dir.cmp(b_dir).then_with(|| a_file.cmp(b_file)) + }); + }); + + let mut builder = crate::simd_path::ChunkedPathStoreBuilder::new(pairs.len()); + let dirs = populates_dirs_files_chunked_storage(&mut pairs, &mut builder); + + let mut files: Vec = pairs.into_iter().map(|(file, _)| file).collect(); + let chunked_paths = builder.finish(); + let arena = chunked_paths.as_arena_ptr(); + + // Apply frecency scores (access-based only — git status not yet available). + // DirItem.max_access_frecency is AtomicI32, so parallel threads write directly. + let frecency = shared_frecency + .read() + .map_err(|_| Error::AcquireFrecencyLock)?; + + if let Some(frecency) = frecency.as_ref() { + let dirs_ref = &dirs; + BACKGROUND_THREAD_POOL.install(|| { + files.par_iter_mut().for_each(|file| { + let _ = file.update_frecency_scores(frecency, arena, base_path, mode); + let score = file.access_frecency_score as i32; + if score > 0 { + let dir_idx = file.parent_dir_index as usize; + if let Some(dir) = dirs_ref.get(dir_idx) { + dir.update_frecency_if_larger(score); + } + } + }); + }); + } + drop(frecency); + + // un-indexable files that are binary or not fitting the size cap has to beplaced in the end + let is_indexable = |f: &FileItem| { + !f.is_binary() + && f.size > 0 + && f.size <= crate::constants::MAX_INDEXABLE_FILE_SIZE as u64 + }; + + BACKGROUND_THREAD_POOL.install(|| { + files.par_sort_unstable_by(|a, b| { + (!is_indexable(a)) + .cmp(&!is_indexable(b)) + // this just makes it faster in terms of allocation - we store the dir indexes + .then_with(|| a.parent_dir_index.cmp(&b.parent_dir_index)) + .then_with(|| a.file_name(arena).cmp(&b.file_name(arena))) + }); + }); + let indexable_count = files.partition_point(is_indexable); + + // Ask the allocator to return freed pages to the OS. + hint_allocator_collect(); + + let file_item_size = std::mem::size_of::(); + let files_vec_bytes = files.len() * file_item_size; + let dir_table_bytes = dirs.len() * std::mem::size_of::() + + dirs + .iter() + .map(|d| d.relative_path(arena).len()) + .sum::(); + + let total_time = scan_start.elapsed(); + info!( + "SCAN: Walk completed in {:?} ({} files, {} dirs, \ + chunked_store={:.2}MB, files_vec={:.2}MB, dirs={:.2}MB, FileItem={}B)", + total_time, + files.len(), + dirs.len(), + chunked_paths.heap_bytes() as f64 / 1_048_576.0, + files_vec_bytes as f64 / 1_048_576.0, + dir_table_bytes as f64 / 1_048_576.0, + file_item_size, + ); + + let base_count = files.len(); + + Ok(FileSync { + files: StableVec::from_vec_with_reserve(files, MAX_OVERFLOW_FILES), + indexable_count, + base_count, + live_count: base_count, + dirs: StableVec::from_vec_with_reserve(dirs, 0), + overflow_builder: None, + git_workdir, + bigram_index: None, + bigram_overlay: None, + chunked_paths: Some(Arc::new(chunked_paths)), + ignore_rules, + }) + } +} + +/// Pre-populate mmap caches for cold tail files so the first grep search +/// doesn't pay the mmap creation + page fault cost. +#[allow(dead_code)] +#[tracing::instrument(skip(files), name = "warmup_mmaps", level = Level::DEBUG)] +pub(crate) fn warmup_mmaps( + files: &[FileItem], + budget: &ContentCacheBudget, + base_path: &Path, + arena: ArenaPtr, +) { + // for most of the use cases mmaps limit would be significantly smaller than arepo + for file in files.iter() { + if file.is_likely_hot() + || file.is_binary() + || file.size == 0 + || file.size > budget.max_file_size + { + continue; + } + + let _ = file.get_cached_content(arena, base_path, budget); + + if budget.is_exhausted() { + break; + } + } +} + +/// This does both thing (yes sorry all the OOP morons) +/// in one go: populates files chunked storage and creates new directories +fn populates_dirs_files_chunked_storage<'a>( + pairs: &'a mut [(FileItem, String)], + chunk_storage: &mut crate::simd_path::ChunkedPathStoreBuilder, +) -> Vec { + let mut dirs: Vec = Vec::new(); + + let mut prev_dir: &'a str = ""; + let mut prev_dir_valid = false; + let mut current_dir_idx: u32 = 0; + + for (file, rel) in pairs.iter_mut() { + let rel: &'a str = rel; + let dir_part: &'a str = &rel[..file.path.filename_offset as usize]; + + if !prev_dir_valid || prev_dir != dir_part { + let dir_string = chunk_storage.add_dir_immediate(dir_part); + + // Compute last-segment offset: for "src/components/" -> 4 (points to "components/") + let last_seg = if dir_part.is_empty() { + 0 + } else { + let trimmed = dir_part.trim_end_matches(std::path::is_separator); + trimmed + .rfind(std::path::is_separator) + .map(|i| i + 1) + .unwrap_or(0) as u16 + }; + + dirs.push(DirItem::new(dir_string, last_seg)); + current_dir_idx = (dirs.len() - 1) as u32; + + prev_dir = dir_part; + prev_dir_valid = true; + } + + file.path = chunk_storage.add_file_immediate(rel, file.path.filename_offset); + file.parent_dir_index = current_dir_idx; + } + + dirs +} + +/// Fast extension-based binary detection. Avoids opening files during scan. +/// Covers the vast majority of binary files in typical repositories. +#[inline] +#[doc(hidden)] +pub fn is_known_binary_extension(path: &Path) -> bool { + let Some(ext) = path.extension().and_then(|e| e.to_str()) else { + return false; + }; + is_binary_extension_str(ext) +} + +/// Like [`is_known_binary_extension`] but takes a basename string directly, +/// avoiding `Path::extension()` overhead. Mirrors `Path::extension()` +/// semantics: dotfiles with no other dots → no extension. Used by the zlob +/// walker, which already has the basename slice from traversal. +#[cfg(feature = "zlob")] +#[inline] +pub(crate) fn is_known_binary_extension_basename(name: &str) -> bool { + match name.rfind('.') { + Some(pos) if pos > 0 && pos < name.len() - 1 => is_binary_extension_str(&name[pos + 1..]), + _ => false, + } +} + +#[inline] +fn is_binary_extension_str(ext: &str) -> bool { + matches!( + ext, + // Images + "png" | "jpg" | "jpeg" | "gif" | "bmp" | "ico" | "webp" | "tiff" | "tif" | "avif" | + "heic" | "heif" | "jxl" | "jp2" | "j2k" | "psd" | "icns" | "cur" | "cr2" | + "nef" | "dng" | "tga" | + // GPU / VFX texture formats + "rgbe" | "hdr" | "exr" | "dds" | "ktx" | "ktx2" | "pvr" | "astc" | + // Adobe Illustrator (PDF wrapper) / Apple webarchive / MIME HTML archive + "ai" | "webarchive" | "mhtml" | + // Video/Audio + "mp4" | "avi" | "mov" | "wmv" | "mkv" | "mp3" | "wav" | "flac" | "ogg" | "m4a" | + "aac" | "webm" | "flv" | "mpg" | "mpeg" | "wma" | "opus" | "pcm" | "reapeaks" | + // Compressed/Archives + "zip" | "tar" | "gz" | "bz2" | "xz" | "7z" | "rar" | "zst" | "lz4" | "lzma" | + "cab" | "cpio" | "jsonlz4" | + // Packages/Installers + "deb" | "rpm" | "apk" | "dmg" | "msi" | "iso" | "nupkg" | "whl" | "egg" | + "appimage" | "flatpak" | "crx" | "pak" | + // Executables/Libraries + "exe" | "dll" | "so" | "dylib" | "o" | "a" | "lib" | "bin" | "elf" | + // Documents (binary office formats) + "pdf" | "doc" | "docx" | "xls" | "xlsx" | "ppt" | "pptx" | + // Databases + "db" | "sqlite" | "sqlite3" | "mdb" | + // SQLite / LevelDB auxiliary files + "sqlite-wal" | "sqlite-shm" | "sqlite3-wal" | "sqlite3-shm" | + "db-wal" | "db-shm" | "ldb" | + // Fonts + "ttf" | "otf" | "woff" | "woff2" | "eot" | + // Compiled/Runtime + "class" | "pyc" | "pyo" | "wasm" | "dex" | "jar" | "war" | + // OCaml / Swift / Objective-C build artefacts + "cmi" | "cmt" | "cmti" | "cmx" | "nib" | + "swiftdeps" | "swiftdeps~" | "swiftdoc" | "swiftmodule" | "swiftsourceinfo" | + // ML/Data Science + "npy" | "npz" | "h5" | "hdf5" | "pt" | "onnx" | + "safetensors" | "tfrecord" | "tflite" | "gguf" | "ggml" | "joblib" | + // 3D/Game assets + "glb" | "blend" | "blp" | + // Gzipped-XML / binary maps + "dia" | "bcmap" | + // Protobuf wire format + "pb" | + // Data/serialized + "parquet" | "arrow" | + // IDE/OS metadata + "suo" + ) +} + +/// Length of the longest shared directory prefix of two relative dir +/// paths (without a trailing separator), measured as the number of bytes +/// up to and including the last shared separator — plus the full shorter +/// path when it is itself a directory prefix of the longer one. +/// +/// Examples: +/// `"src/components"` vs `"src/routes"` → 4 (`"src/"` emitted once) +/// `"lib/deep/nested"` vs `"lib/deep"` → 8 (`"lib/deep"` is a prefix) +/// `"lib/deep"` vs `"lib/deeper"` → 4 (only `"lib/"` is shared) +/// `"lib"` vs `"src"` → 0 +/// +/// Used by [`FilePicker::for_each_watch_dir`] to avoid re-emitting +/// ancestors that were already yielded for the previous (sorted) sibling. +fn common_dir_prefix_len(a: &str, b: &str) -> usize { + let max = a.len().min(b.len()); + let a_bytes = a.as_bytes(); + let b_bytes = b.as_bytes(); + let mut last_sep = 0; + let mut i = 0; + while i < max && a_bytes[i] == b_bytes[i] { + if std::path::is_separator(a_bytes[i] as char) { + last_sep = i + 1; + } + i += 1; + } + // If one string is a prefix of the other and the next byte in the + // longer one is a separator, the full shorter path is a shared dir. + if i == max && i > 0 { + let longer = if a.len() > b.len() { a_bytes } else { b_bytes }; + if i < longer.len() && std::path::is_separator(longer[i] as char) { + return i; + } + } + last_sep +} + +/// Ask the global allocator to return freed pages to the OS. +/// Enabled via the `mimalloc-collect` feature (set by fff-nvim). +/// No-op when the feature is off (tests, system allocator). +pub(crate) fn hint_allocator_collect() { + #[cfg(feature = "mimalloc-collect")] + { + // Collect BACKGROUND_THREAD_POOL workers — that's where the bigram + // builder allocated memory. `rayon::broadcast` would target the global + // pool, which is the wrong set of threads. + BACKGROUND_THREAD_POOL.broadcast(|_| unsafe { libmimalloc_sys::mi_collect(true) }); + + // Main thread too. + unsafe { libmimalloc_sys::mi_collect(true) }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The watcher must watch every ancestor directory up to `base_path`, + /// not just the immediate parents of indexed files. Intermediate dirs + /// that contain only subdirectories (no direct files) are NOT in + /// `sync_data.dirs` — yet they must still appear in `extract_watch_dirs` + /// so Create events on new subdirectories below them fire. + /// + /// Correctness regression guard for any refactor that replaces the + /// ancestor walk with a direct `sync_data.dirs` iteration. + #[test] + fn extract_watch_dirs_includes_pure_ancestor_dirs() { + let dir = tempfile::tempdir().unwrap(); + // On Windows the picker canonicalizes base_path with dunce; match that + // here so the stored dir paths line up with assertions built from + // `base.join(..)` (which otherwise would carry an 8.3 short name). + let base_buf = crate::path_utils::canonicalize(dir.path()).unwrap(); + let base = base_buf.as_path(); + + // Tree: + // base/src/components/button.txt (src/components has a file) + // base/src/routes/home.txt (src/routes has a file) + // base/lib/deep/nested/util.txt (lib and lib/deep have no files) + // + // `sync_data.dirs` will only contain: + // src/components/ + // src/routes/ + // lib/deep/nested/ + // + // But the watcher also needs: + // src/ (pure ancestor — no direct files) + // lib/ (pure ancestor) + // lib/deep/ (pure ancestor) + // otherwise new siblings like `src/NewDir/x.txt` are missed. + for rel in [ + "src/components/button.txt", + "src/routes/home.txt", + "lib/deep/nested/util.txt", + ] { + let path = base.join(rel); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(&path, b"x").unwrap(); + } + + let mut picker = FilePicker::new(FilePickerOptions { + base_path: base.to_str().unwrap().into(), + watch: false, + ..Default::default() + }) + .unwrap(); + picker.collect_files().unwrap(); + + let mut watch_dirs: Vec = Vec::new(); + picker.for_each_dir(|p| { + watch_dirs.push(p.to_path_buf()); + std::ops::ControlFlow::Continue(()) + }); + let watch_set: std::collections::HashSet = watch_dirs.iter().cloned().collect(); + + // Immediate parents (in sync_data.dirs) must be present. + for rel in ["src/components", "src/routes", "lib/deep/nested"] { + assert!( + watch_set.contains(&base.join(rel)), + "expected immediate parent {rel} in watch dirs, got {watch_set:?}", + ); + } + + // Pure-ancestor dirs (NOT in sync_data.dirs) must also be present. + for rel in ["src", "lib", "lib/deep"] { + assert!( + watch_set.contains(&base.join(rel)), + "expected pure-ancestor {rel} in watch dirs, got {watch_set:?}", + ); + } + + // No duplicates — streaming dedup must not emit the same dir twice. + assert_eq!( + watch_dirs.len(), + watch_set.len(), + "duplicate watch dir emitted: {watch_dirs:?}", + ); + + // Base path itself is NOT walked into the result — the walker stops + // at `current == base`. The outer `debouncer.watch(base_path, ...)` + // call in create_debouncer covers it separately. + assert!( + !watch_set.contains(base), + "base path must not be in watch dirs (covered by the top-level watch call)", + ); + } + + #[test] + fn common_dir_prefix_len_cases() { + assert_eq!(common_dir_prefix_len("", ""), 0); + assert_eq!(common_dir_prefix_len("", "src"), 0); + assert_eq!(common_dir_prefix_len("lib", "src"), 0); + assert_eq!(common_dir_prefix_len("src/components", "src/routes"), 4); + assert_eq!(common_dir_prefix_len("lib/deep/nested", "lib/deep"), 8); + assert_eq!(common_dir_prefix_len("lib/deep", "lib/deep/nested"), 8); + assert_eq!(common_dir_prefix_len("lib/deep", "lib/deeper"), 4); + assert_eq!(common_dir_prefix_len("src", "src"), 0); + // "src" is emitted-as-dir; "src/x" extends it — full "src" is shared. + assert_eq!(common_dir_prefix_len("src", "src/x"), 3); + } +} diff --git a/crates/fff-core/src/git.rs b/crates/fff-core/src/git.rs new file mode 100644 index 0000000..85bce5e --- /dev/null +++ b/crates/fff-core/src/git.rs @@ -0,0 +1,253 @@ +use crate::error::Result; +use ahash::AHashMap; +use git2::{Repository, Status, StatusOptions}; +use std::{ + fmt::Debug, + path::{Path, PathBuf}, +}; + +pub(crate) fn default_status_options() -> StatusOptions { + let mut opts = StatusOptions::new(); + opts.include_untracked(true) + .recurse_untracked_dirs(true) + .include_unmodified(true) + .exclude_submodules(true); + opts +} + +/// Status options for the initial scan / rescan. +/// +/// Skips `include_unmodified` because every `FileItem` starts with +/// `git_status: None` (== clean), so a missing cache entry already means +/// "clean" — no need to ask libgit2 to enumerate every tracked path. +/// Saves seconds on huge dirty trees (e.g. chromium with 400k+ entries). +pub(crate) fn initial_scan_status_options() -> StatusOptions { + let mut opts = StatusOptions::new(); + opts.include_untracked(true) + .recurse_untracked_dirs(true) + .exclude_submodules(true); + opts +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct GitStatusCache(AHashMap); + +impl IntoIterator for GitStatusCache { + type Item = (PathBuf, Status); + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl GitStatusCache { + pub fn statuses_len(&self) -> usize { + self.0.len() + } + + #[inline] + pub fn lookup_status(&self, full_path: &Path) -> Option { + self.0.get(full_path).copied() + } + + #[tracing::instrument(skip(repo, status_options))] + fn read_status_impl(repo: &Repository, status_options: &mut StatusOptions) -> Result { + let statuses = repo.statuses(Some(status_options))?; + let Some(repo_path) = repo.workdir() else { + return Ok(Self(AHashMap::new())); // repo is bare + }; + + let repo_path = crate::path_utils::normalize(repo_path.to_path_buf()); + + let mut entries = AHashMap::with_capacity(statuses.len()); + for entry in &statuses { + if let Some(entry_path) = entry.path() { + // libgit2 returns entry paths with forward slashes on every platform + // fff stores native paths - meaning we have forward slash issue on windows + let full_path = crate::path_utils::normalize(repo_path.join(entry_path)); + entries.insert(full_path, entry.status()); + } + } + + Ok(Self(entries)) + } + + pub fn read_git_status( + git_workdir: Option<&Path>, + status_options: &mut StatusOptions, + ) -> Option { + let git_workdir = git_workdir.as_ref()?; + let repository = Repository::open(git_workdir).ok()?; + + let status = Self::read_status_impl(&repository, status_options); + + match status { + Ok(status) => Some(status), + Err(e) => { + tracing::error!(?e, "Failed to read git status"); + + None + } + } + } + + #[tracing::instrument(skip(repo), fields(paths_count = paths.len()), level = tracing::Level::DEBUG)] + pub fn git_status_for_paths + Debug>( + repo: &Repository, + paths: &[TPath], + ) -> Result { + if paths.is_empty() { + return Ok(Self(AHashMap::new())); + } + + let Some(workdir) = repo.workdir() else { + return Ok(Self(AHashMap::new())); + }; + let workdir = crate::path_utils::normalize(workdir.to_path_buf()); + + // git pathspec is pretty slow and requires to walk the whole directory + // so for a single file which is the most general use case we query directly the file + if paths.len() == 1 { + let full_path = paths[0].as_ref(); + let relative_path = full_path.strip_prefix(&workdir)?; + let status = repo.status_file(relative_path)?; + + let mut map = AHashMap::with_capacity(1); + map.insert(full_path.to_path_buf(), status); + return Ok(Self(map)); + } + + let mut status_options = default_status_options(); + for path in paths { + status_options.pathspec(path.as_ref().strip_prefix(&workdir)?); + } + + let git_status_cache = Self::read_status_impl(repo, &mut status_options)?; + Ok(git_status_cache) + } +} + +#[inline] +pub fn is_modified_status(status: Status) -> bool { + status.intersects( + Status::WT_MODIFIED + | Status::INDEX_MODIFIED + | Status::WT_NEW + | Status::INDEX_NEW + | Status::WT_RENAMED, + ) +} + +pub fn format_git_status_opt(status: Option) -> Option<&'static str> { + match status { + None => Some("clean"), + Some(status) => { + if status.contains(Status::WT_NEW) { + Some("untracked") + } else if status.contains(Status::WT_MODIFIED) { + Some("modified") + } else if status.contains(Status::WT_DELETED) { + Some("deleted") + } else if status.contains(Status::WT_RENAMED) { + Some("renamed") + } else if status.contains(Status::INDEX_NEW) { + Some("staged_new") + } else if status.contains(Status::INDEX_MODIFIED) { + Some("staged_modified") + } else if status.contains(Status::INDEX_DELETED) { + Some("staged_deleted") + } else if status.contains(Status::IGNORED) { + Some("ignored") + } else if status.contains(Status::CURRENT) || status.is_empty() { + Some("clean") + } else { + None + } + } + } +} + +pub fn format_git_status(status: Option) -> &'static str { + format_git_status_opt(status).unwrap_or("unknown") +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::process::Command; + use tempfile::TempDir; + + fn git(dir: &Path, args: &[&str]) { + let out = Command::new("git") + .args(args) + .current_dir(dir) + .env("GIT_AUTHOR_NAME", "t") + .env("GIT_AUTHOR_EMAIL", "t@t") + .env("GIT_COMMITTER_NAME", "t") + .env("GIT_COMMITTER_EMAIL", "t@t") + .output() + .unwrap(); + assert!(out.status.success(), "git {args:?} failed"); + } + + /// Regression: on case-insensitive filesystems libgit2 returns + /// statuses in a case-insensitive order. Our previous sorted-`Vec` + + /// `binary_search_by(Path::cmp)` lookup silently missed entries + /// because `Path::cmp` is byte-wise. + /// + /// This test uses deliberately mixed-case filenames so the two + /// orderings disagree, then checks every lookup succeeds. + #[test] + fn lookup_is_case_exact_regardless_of_libgit2_sort_order() { + let tmp = TempDir::new().unwrap(); + // `std::fs::canonicalize` on Windows adds a `\\?\` UNC prefix that + // libgit2's workdir string lacks. Use dunce so both sides match. + let base = crate::path_utils::canonicalize(tmp.path()).unwrap(); + + // Mixed-case names that sort differently under byte-wise vs + // case-insensitive comparators. + let names = [ + "README.md", + "a_lower.rs", + "Z_upper.rs", + "mixed_Case.txt", + "nested/Inner_File.rs", + ]; + for n in &names { + let p = base.join(n); + fs::create_dir_all(p.parent().unwrap()).unwrap(); + fs::write(&p, format!("// {n}\n")).unwrap(); + } + + git(&base, &["init", "-b", "main"]); + git(&base, &["add", "-A"]); + git(&base, &["commit", "-m", "seed", "--no-gpg-sign"]); + + // Modify every file so they all end up in the status output as + // WT_MODIFIED — guarantees a non-trivial map we have to look up. + for n in &names { + let p = base.join(n); + fs::write(&p, format!("// {n}\n// edit\n")).unwrap(); + } + + let repo = Repository::open(&base).unwrap(); + let paths: Vec = names.iter().map(|n| base.join(n)).collect(); + let cache = GitStatusCache::git_status_for_paths(&repo, &paths).unwrap(); + + for (n, abs) in names.iter().zip(paths.iter()) { + let status = cache.lookup_status(abs); + assert!( + status.is_some(), + "lookup for {n} returned None; cache holds {} entries", + cache.statuses_len(), + ); + assert!( + status.unwrap().contains(Status::WT_MODIFIED), + "expected WT_MODIFIED for {n}, got {:?}", + status + ); + } + } +} diff --git a/crates/fff-core/src/git_status_worker.rs b/crates/fff-core/src/git_status_worker.rs new file mode 100644 index 0000000..b6e7402 --- /dev/null +++ b/crates/fff-core/src/git_status_worker.rs @@ -0,0 +1,120 @@ +use crate::shared::{SharedFrecency, WeakFilePicker}; +use ahash::AHashSet; +use parking_lot::{Condvar, Mutex}; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +// we don't really need a queue here +#[derive(Default)] +struct Pending { + paths: AHashSet, + full_rescan: bool, + shutdown: bool, +} + +impl Pending { + fn has_work(&self) -> bool { + self.full_rescan || !self.paths.is_empty() + } +} + +/// Condvar based queue that is used for batch processing events +pub(crate) struct GitStatusWorker { + state: Mutex, + cv: Condvar, + consumer_spawned: AtomicBool, +} + +impl GitStatusWorker { + pub(crate) fn new() -> Arc { + Arc::new(Self { + state: Mutex::new(Pending::default()), + cv: Condvar::new(), + consumer_spawned: AtomicBool::new(false), + }) + } + + pub(crate) fn spawn_once( + self: &Arc, + weak_picker: WeakFilePicker, + frecency: SharedFrecency, + ) { + if self + .consumer_spawned + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + Self::spawn_consumer(Arc::clone(self), weak_picker, frecency); + } + } + + pub(crate) fn enqueue_paths(&self, paths: I) + where + I: IntoIterator, + { + let mut guard = self.state.lock(); + guard.paths.extend(paths); + drop(guard); + self.cv.notify_one(); + } + + pub(crate) fn request_full_rescan(&self) { + let mut guard = self.state.lock(); + guard.full_rescan = true; + drop(guard); + self.cv.notify_one(); + } + + pub(crate) fn signal_shutdown(&self) { + let mut guard = self.state.lock(); + guard.shutdown = true; + drop(guard); + self.cv.notify_one(); + } + + fn wait_and_take(&self) -> Option { + let mut guard = self.state.lock(); + while !guard.shutdown && !guard.has_work() { + self.cv.wait(&mut guard); + } + if guard.shutdown { + return None; + } + Some(std::mem::take(&mut *guard)) + } + + // the problem: git status update can take a lot of time especially on big repositories + // and there is unpredictable wait time on the lock file if huge commit is going so we have to + // spawn a separate thread to guartee that notify handler is unlocked even if git update takes a + // lot of time on every event burst (pretty cheap as this thread is going to sleep 99.9% of time) + fn spawn_consumer( + mailbox: Arc, + weak_picker: WeakFilePicker, + frecency: SharedFrecency, + ) { + let _ = std::thread::Builder::new() + .name("fff-git-status".into()) + .spawn(move || { + while let Some(work) = mailbox.wait_and_take() { + let Some(picker) = weak_picker.upgrade() else { + break; + }; + + if work.full_rescan { + if let Err(e) = picker.refresh_git_status(&frecency) { + tracing::error!("git-status worker: full rescan failed: {e:?}"); + } + } else if !work.paths.is_empty() { + let paths: Vec = work.paths.into_iter().collect(); + if let Err(e) = picker.update_git_status_for_paths(&paths, &frecency) { + tracing::error!("git-status worker: path update failed: {e:?}"); + } + } + } + + tracing::info!("git-status worker stopped"); + }) + .inspect_err(|err| tracing::error!(?err, "Failed to spawn git status worker")); + } +} diff --git a/crates/fff-core/src/grep/classify.rs b/crates/fff-core/src/grep/classify.rs new file mode 100644 index 0000000..8619a03 --- /dev/null +++ b/crates/fff-core/src/grep/classify.rs @@ -0,0 +1,131 @@ +//! Definition and import line classification (vibe coded POC) +//! +//! Byte-level heuristics that tag a matched line as a code definition +//! (`struct`, `fn`, `class`, …) or an import/use statement. Used to +//! rank/annotate grep results for AI/MCP consumers. Gated behind the +//! `definitions` feature since only such consumers need it. + +/// Detect if a line looks like a code definition (struct, fn, class, etc.) +pub fn is_definition_line(line: &str) -> bool { + let s = line.trim_start().as_bytes(); + let s = skip_modifiers(s); + is_definition_keyword(s) +} + +/// Modifier keywords that can precede a definition keyword. +/// Each must be followed by whitespace to be consumed. +const MODIFIERS: &[&[u8]] = &[ + b"pub", + b"export", + b"default", + b"async", + b"abstract", + b"unsafe", + b"static", + b"protected", + b"private", + b"public", +]; + +/// Definition keywords to detect. +const DEF_KEYWORDS: &[&[u8]] = &[ + b"struct", + b"fn", + b"enum", + b"trait", + b"impl", + b"class", + b"interface", + b"function", + b"def", + b"func", + b"type", + b"module", + b"object", +]; + +/// Skip zero or more modifier keywords (including `pub(crate)` style visibility). +fn skip_modifiers(mut s: &[u8]) -> &[u8] { + loop { + // Handle `pub(...)` — e.g. `pub(crate)`, `pub(super)` + if s.starts_with(b"pub(") + && let Some(end) = s.iter().position(|&b| b == b')') + { + s = skip_ws(&s[end + 1..]); + continue; + } + let mut matched = false; + for &kw in MODIFIERS { + if s.starts_with(kw) { + let rest = &s[kw.len()..]; + if rest.first().is_some_and(|b| b.is_ascii_whitespace()) { + s = skip_ws(rest); + matched = true; + break; + } + } + } + if !matched { + return s; + } + } +} + +/// Check if `s` starts with a definition keyword followed by a word boundary. +fn is_definition_keyword(s: &[u8]) -> bool { + for &kw in DEF_KEYWORDS { + if s.starts_with(kw) { + let after = s.get(kw.len()); + // Word boundary: end of input, or next byte is not alphanumeric/underscore + if after.is_none_or(|b| !b.is_ascii_alphanumeric() && *b != b'_') { + return true; + } + } + } + false +} + +/// Skip ASCII whitespace. +#[inline] +fn skip_ws(s: &[u8]) -> &[u8] { + let n = s + .iter() + .position(|b| !b.is_ascii_whitespace()) + .unwrap_or(s.len()); + &s[n..] +} + +/// Detect import/use lines — lower value than definitions or usages. +/// +/// Checks if the line (after leading whitespace) starts with a common +/// import statement prefix. Pure byte-level checks, no regex. +pub fn is_import_line(line: &str) -> bool { + let s = line.trim_start().as_bytes(); + s.starts_with(b"import ") + || s.starts_with(b"import\t") + || (s.starts_with(b"from ") && s.get(5).is_some_and(|&b| b == b'\'' || b == b'"')) + || s.starts_with(b"use ") + || s.starts_with(b"use\t") + || starts_with_require(s) + || starts_with_include(s) +} + +/// Match `require(` or `require (`. +#[inline] +fn starts_with_require(s: &[u8]) -> bool { + if !s.starts_with(b"require") { + return false; + } + let rest = &s[b"require".len()..]; + rest.first() == Some(&b'(') || (rest.first() == Some(&b' ') && rest.get(1) == Some(&b'(')) +} + +/// Match `# include ` (with optional spaces after `#`). +#[inline] +fn starts_with_include(s: &[u8]) -> bool { + if s.first() != Some(&b'#') { + return false; + } + let rest = skip_ws(&s[1..]); + rest.starts_with(b"include ") || rest.starts_with(b"include\t") +} diff --git a/crates/fff-core/src/grep/fuzzy_grep.rs b/crates/fff-core/src/grep/fuzzy_grep.rs new file mode 100644 index 0000000..75e1f8f --- /dev/null +++ b/crates/fff-core/src/grep/fuzzy_grep.rs @@ -0,0 +1,358 @@ +use crate::simd_path::ArenaPtr; +use crate::types::{ContentCacheBudget, FileItem, MmapSlot}; +use fff_grep::lines::LineStep; +use rayon::prelude::*; +use std::path::Path; +use std::sync::atomic::{AtomicBool, Ordering}; + +use super::grep::{ + GrepMatch, GrepSearchOptions, char_indices_to_byte_offsets, classify_definition, + truncate_display_bytes, +}; +use super::utils::{GrepResult, strip_line_terminators}; + +#[allow(clippy::too_many_arguments)] +pub(super) fn fuzzy_grep_search<'a>( + grep_text: &str, + files_to_search: &[&'a FileItem], + options: &GrepSearchOptions, + total_files: usize, + filtered_file_count: usize, + case_insensitive: bool, + budget: &ContentCacheBudget, + abort_signal: &AtomicBool, + base_path: &Path, + arena: ArenaPtr, + overflow_arena: ArenaPtr, +) -> GrepResult<'a> { + // max_typos controls how many *needle* characters can be unmatched. + // A transposition (e.g. "shcema" -> "schema") costs ~1 typo with + // default gap penalties. We scale max_typos by needle length: + // 1-2 chars -> 0 typos (exact subsequence only) + // 3-5 chars -> 1 typo + // 6+ chars -> 2 typos + // Cap at 2: higher values (3+) let the SIMD prefilter pass lines + // missing key characters entirely (e.g. query "flvencodeX" matching + // lines without 'l' or 'v'). Quality comes from the post-match filters. + let max_typos = (grep_text.len() / 3).min(2); + let scoring = neo_frizbee::Scoring { + // Use default gap penalties. Higher values (e.g. 20) cause + // smith-waterman to prefer *dropping needle chars* over paying + // gap costs, which inflates the typo count and breaks + // transposition matching ("shcema" -> "schema" becomes 3 typos instead of 1) + exact_match_bonus: 100, + // gap_open_penalty: 4, + // gap_extend_penalty: 2, + prefix_bonus: 0, + capitalization_bonus: if case_insensitive { 0 } else { 4 }, + ..neo_frizbee::Scoring::default() + }; + + let matcher = neo_frizbee::Matcher::new( + grep_text, + &neo_frizbee::Config { + // Use the real max_typos so frizbee's SIMD prefilter actually rejects non-matching lines (~2 SIMD instructions per line vs full SW scoring). + max_typos: Some(max_typos as u16), + sort: false, + scoring, + ..Default::default() + }, + ); + + // Minimum score threshold: 50% of a perfect contiguous match. + // With default scoring (match_score=12, matching_case_bonus=4 = 16/char), + // a transposition costs ~5 from a gap, keeping the score well above 50% + let perfect_score = (grep_text.len() as u16) * 16; + let min_score = (perfect_score * 50) / 100; + + // Target identifiers are often longer than the query due to delimiters + // (e.g. query "flvencodepicture" -> "ff_flv_encode_picture_header" from ffmpeg) + // Allow 3x needle length to accommodate underscore/dot-separated names + let max_match_span = grep_text.len() * 3; + let needle_len = grep_text.len(); + + // Each delimiter (_, .) in the target creates a gap. A typical C/Rust + // identifier like "ff_flv_encode_picture_header" has 4-5 underscores. + // Scale generously so delimiter gaps don't reject valid matches. + let max_gaps = (needle_len / 3).max(2); + + // If a file doesn't contain enough distinct needle characters just skip it + let needle_bytes = grep_text.as_bytes(); + let mut unique_needle_chars: Vec = Vec::new(); + for &b in needle_bytes { + let lo = b.to_ascii_lowercase(); + let hi = b.to_ascii_uppercase(); + if !unique_needle_chars.contains(&lo) { + unique_needle_chars.push(lo); + } + if lo != hi && !unique_needle_chars.contains(&hi) { + unique_needle_chars.push(hi); + } + } + + // How many distinct needle chars must appear in the file. + // With max_typos allowed, we need at least (unique_count - max_typos) + let unique_count = { + let mut seen = [false; 256]; + for &b in needle_bytes { + seen[b.to_ascii_lowercase() as usize] = true; + } + seen.iter().filter(|&&v| v).count() + }; + let min_chars_required = unique_count.saturating_sub(max_typos); + + let time_budget = if options.time_budget_ms > 0 { + Some(std::time::Duration::from_millis(options.time_budget_ms)) + } else { + None + }; + let search_start = std::time::Instant::now(); + let budget_exceeded = AtomicBool::new(false); + let max_matches_per_file = options.max_matches_per_file; + + // for fuzzy match we need a bit smarter chunking as the amount of work we have to perform is + // exponentially larger than the original grep (and the nature of work is heavier), so in short we have to + // understand if the approximate index prefilter got us a lot of candidates or not + // + // if we have a few candidates -> likely we have a lot of matches, so verify the check faster + // if we have a lot of candidates -> rely on a larger chunk pipelining more parallel lines at once + let page_limit = options.page_limit; + let base_chunk = rayon::current_num_threads() * 4; + let prefilter_strong = total_files > 0 && files_to_search.len() * 2 < total_files; + let max_chunk = if prefilter_strong { + base_chunk + } else { + (base_chunk * 256).max(8 * 1024) + }; + + let growth = if prefilter_strong { 1 } else { 2 }; + let mut chunk_size = base_chunk; + let mut chunk_start = 0; + let mut running_matches = 0usize; + let mut per_file_results: Vec<(usize, &'a FileItem, Vec)> = Vec::new(); + + while chunk_start < files_to_search.len() { + let chunk_end = (chunk_start + chunk_size).min(files_to_search.len()); + let chunk = &files_to_search[chunk_start..chunk_end]; + let chunk_offset = chunk_start; + chunk_start = chunk_end; + chunk_size = (chunk_size * growth).min(max_chunk); + + // Parallel phase with `map_init`: each rayon worker thread clones the + // matcher once and gets a reusable read buffer + mmap slot. Buffer holds + // small files, slot holds fresh mmap for cache-miss files ≥ FRESH_MMAP_THRESHOLD. + let chunk_results: Vec<(usize, &'a FileItem, Vec)> = chunk + .par_iter() + .enumerate() + .map_init( + || { + ( + matcher.clone(), + Vec::with_capacity(64 * 1024), + MmapSlot::default(), + ) + }, + |(matcher, buf, mmap_slot), (local_idx, file)| { + if abort_signal.load(Ordering::Relaxed) { + budget_exceeded.store(true, Ordering::Relaxed); + return None; + } + + if let Some(budget) = time_budget + && search_start.elapsed() > budget + { + budget_exceeded.store(true, Ordering::Relaxed); + return None; + } + + let file_arena = if file.is_overflow() { + overflow_arena + } else { + arena + }; + + let file_bytes = + file.get_content_for_search(buf, mmap_slot, file_arena, base_path, budget)?; + + if min_chars_required > 0 { + let mut chars_found = 0usize; + for &ch in &unique_needle_chars { + if memchr::memchr(ch, file_bytes).is_some() { + chars_found += 1; + if chars_found >= min_chars_required { + break; + } + } + } + if chars_found < min_chars_required { + return None; + } + } + + // Validate the whole file as UTF-8 once upfront. Source code + // files are virtually always valid UTF-8; this single check + // replaces per-line from_utf8 calls (~8% of fuzzy grep time) + let file_is_utf8 = std::str::from_utf8(file_bytes).is_ok(); + + let mut stepper = LineStep::new(b'\n', 0, file_bytes.len()); + let estimated_lines = (file_bytes.len() / 40).max(64); + let mut file_lines: Vec<&str> = Vec::with_capacity(estimated_lines); + let mut line_meta: Vec<(u64, u64)> = Vec::with_capacity(estimated_lines); + + let mut line_number: u64 = 1; + while let Some(line_match) = stepper.next_match(file_bytes) { + let byte_offset = line_match.start() as u64; + let trimmed = strip_line_terminators(&file_bytes[line_match]); + + if !trimmed.is_empty() { + // we know for sure that the file is UTF-8 at this point + let line_str = if file_is_utf8 { + unsafe { std::str::from_utf8_unchecked(trimmed) } + } else if let Ok(s) = std::str::from_utf8(trimmed) { + s + } else { + line_number += 1; + continue; + }; + file_lines.push(line_str); + line_meta.push((line_number, byte_offset)); + } + + line_number += 1; + } + + if file_lines.is_empty() { + return None; + } + + // Single-pass: score + indices in one Smith-Waterman run per line (not parallel) + let matches_with_indices = matcher.match_list_indices(&file_lines); + let mut file_matches: Vec = Vec::new(); + + for mut match_indices in matches_with_indices { + if match_indices.score < min_score { + continue; + } + + let idx = match_indices.index as usize; + let raw_line = file_lines[idx]; + + let truncated = truncate_display_bytes(raw_line.as_bytes()); + let display_line = if truncated.len() < raw_line.len() { + // SAFETY: truncate_display_bytes preserves UTF-8 char boundaries + &raw_line[..truncated.len()] + } else { + raw_line + }; + + // If the line was truncated, re-compute indices on the shorter string. + if display_line.len() < raw_line.len() { + let Some(re_indices) = matcher + .match_list_indices(&[display_line]) + .into_iter() + .next() + else { + continue; + }; + match_indices = re_indices; + } + + match_indices.indices.sort_unstable(); + + // Minimum matched chars: at least (needle_len - max_typos) + // characters must appear. This is consistent with the typo + // budget: each typo can drop one needle char from the alignment. + let min_matched = needle_len.saturating_sub(max_typos).max(1); + if match_indices.indices.len() < min_matched { + continue; + } + + let indices = &match_indices.indices; + + if let (Some(&first), Some(&last)) = (indices.first(), indices.last()) { + // reject widely scattered matches + let span = last - first + 1; + if span > max_match_span { + continue; + } + + // Density check: matched chars / span must be dense enough. + // Relaxed for perfect subsequence matches (all needle chars + // present), slightly relaxed for typo matches to handle + // delimiter-heavy targets + // (e.g. "ff_flv_encode_picture_header" has span inflated by underscores w/ density ~68%) + let density = (indices.len() * 100) / span; + let min_density = if indices.len() >= needle_len { + 45 // Perfect subsequence relaxed (delimiters inflate span) + } else { + 65 // Has typos filter out a long string + }; + if density < min_density { + continue; + } + + // Gap count check: count discontinuities in the indices + let gap_count = indices.windows(2).filter(|w| w[1] != w[0] + 1).count(); + if gap_count > max_gaps { + continue; + } + } + + let (ln, bo) = line_meta[idx]; + let match_byte_offsets = + char_indices_to_byte_offsets(display_line, &match_indices.indices); + let col = match_byte_offsets + .first() + .map(|r| r.0 as usize) + .unwrap_or(0); + + file_matches.push(GrepMatch { + file_index: 0, + line_number: ln, + col, + byte_offset: bo, + is_definition: classify_definition( + options.classify_definitions, + display_line, + ), + line_content: display_line.to_string(), + match_byte_offsets, + fuzzy_score: Some(match_indices.score), + context_before: Vec::new(), + context_after: Vec::new(), + }); + + if max_matches_per_file != 0 && file_matches.len() >= max_matches_per_file { + break; + } + } + + if file_matches.is_empty() { + return None; + } + + Some((chunk_offset + local_idx, *file, file_matches)) + }, + ) + .flatten() + .collect(); + + for result in chunk_results { + running_matches += result.2.len(); + per_file_results.push(result); + } + + if running_matches >= page_limit || budget_exceeded.load(Ordering::Relaxed) { + break; + } + } + + GrepResult::collect( + per_file_results, + files_to_search.len(), + options, + total_files, + filtered_file_count, + budget_exceeded.load(Ordering::Relaxed), + ) +} diff --git a/crates/fff-core/src/grep/grep.rs b/crates/fff-core/src/grep/grep.rs new file mode 100644 index 0000000..27f72bd --- /dev/null +++ b/crates/fff-core/src/grep/grep.rs @@ -0,0 +1,1548 @@ +use crate::{ + bigram_filter::{BigramFilter, BigramOverlay, extract_bigrams}, + bigram_query::{fuzzy_to_bigram_query, regex_to_bigram_query}, + constraints::{ConstraintPlan, ConstraintsBuffers}, + simd_string_utils::memmem, + sort_buffer::sort_with_buffer, + types::{ContentCacheBudget, FileItem, FileSliceExt, MmapSlot}, +}; +use aho_corasick::AhoCorasick; +use fff_grep::{ + Searcher, SearcherBuilder, Sink, SinkMatch, + matcher::{Match, Matcher, NoError}, +}; +use fff_query_parser::{Constraint, FFFQuery, GrepConfig, QueryParser}; +use rayon::prelude::*; +use smallvec::SmallVec; +use std::path::Path; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use tracing::Level; + +use super::utils::{GrepResult, strip_line_terminators}; + +#[cfg(feature = "definitions")] +#[inline] +pub(super) fn classify_definition(enabled: bool, line: &str) -> bool { + enabled && super::classify::is_definition_line(line) +} + +#[cfg(not(feature = "definitions"))] +#[inline] +pub(super) fn classify_definition(_enabled: bool, _line: &str) -> bool { + false +} + +/// Check if `text` contains `\n` that is NOT preceded by another `\`. +/// +/// `\n` -> true (user wants multiline search) +/// `\\n` -> false (escaped backslash followed by literal `n`, e.g. `\\nvim-data`) +#[inline] +pub(super) fn has_unescaped_newline_escape(text: &str) -> bool { + let bytes = text.as_bytes(); + let mut i = 0; + while i < bytes.len().saturating_sub(1) { + if bytes[i] == b'\\' { + if bytes[i + 1] == b'n' { + // Count consecutive backslashes ending at position i + let mut backslash_count = 1; + while backslash_count <= i && bytes[i - backslash_count] == b'\\' { + backslash_count += 1; + } + // Odd number of backslashes before 'n' -> real \n escape + if backslash_count % 2 == 1 { + return true; + } + } + // Skip past the escaped character + i += 2; + } else { + i += 1; + } + } + false +} + +/// Replace only unescaped `\n` sequences with real newlines. +/// +/// `\n` -> newline character +/// `\\n` -> preserved as-is (literal backslash + `n`) +pub(super) fn replace_unescaped_newline_escapes(text: &str) -> String { + let bytes = text.as_bytes(); + let mut result = Vec::with_capacity(bytes.len()); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'\\' && i + 1 < bytes.len() { + if bytes[i + 1] == b'n' { + let mut backslash_count = 1; + while backslash_count <= i && bytes[i - backslash_count] == b'\\' { + backslash_count += 1; + } + if backslash_count % 2 == 1 { + result.push(b'\n'); + i += 2; + continue; + } + } + result.push(bytes[i]); + i += 1; + } else { + result.push(bytes[i]); + i += 1; + } + } + String::from_utf8(result).unwrap_or_else(|_| text.to_string()) +} + +/// Controls how the grep pattern is interpreted. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum GrepMode { + /// Literal plain text match: default path that doesn't require any regex machinery + #[default] + PlainText, + /// Regex mode: uses the same exact matching engine as ripgrep + Regex, + /// Smart fuzzy mode, allows user to make either a couple of single char typos or long gaps + /// e.g. shcema -> shcema, or UserController -> UserAuthController + /// + /// Significatnly slower than plain text, especially on unindexed FilePicker + Fuzzy, +} + +/// A single content match within a file +#[derive(Debug, Clone)] +pub struct GrepMatch { + /// Index into the deduplicated `files` vec of the GrepResult. + pub file_index: usize, + /// 1-based line number. + pub line_number: u64, + /// 0-based byte column of first match start within the line. + pub col: usize, + /// Absolute byte offset of the matched line from the start of the file. + /// Can be used by the preview to seek directly without scanning from the top. + pub byte_offset: u64, + /// The matched line text, truncated to `MAX_LINE_DISPLAY_LEN`. + pub line_content: String, + /// Byte offsets `(start, end)` within `line_content` for each match. + /// Stack-allocated for the common case of ≤4 spans per line. + pub match_byte_offsets: SmallVec<[(u32, u32); 4]>, + /// Fuzzy match score from neo_frizbee (only set in Fuzzy grep mode). + pub fuzzy_score: Option, + /// Whether the matched line looks like a definition (struct, fn, class, etc.). + /// Computed at match time so output formatters don't need to re-scan. + pub is_definition: bool, + /// Lines before the match (for context display). Empty when context is 0. + pub context_before: Vec, + /// Lines after the match (for context display). Empty when context is 0. + pub context_after: Vec, +} + +impl GrepMatch { + /// Strip leading whitespace from `line_content` and all context lines, + /// adjusting `col` and `match_byte_offsets` so highlights remain correct. + pub fn trim_leading_whitespace(&mut self) { + let strip_len = self.line_content.len() - self.line_content.trim_start().len(); + if strip_len > 0 { + self.line_content.drain(..strip_len); + let off = strip_len as u32; + self.col = self.col.saturating_sub(strip_len); + for range in &mut self.match_byte_offsets { + range.0 = range.0.saturating_sub(off); + range.1 = range.1.saturating_sub(off); + } + } + for line in &mut self.context_before { + let n = line.len() - line.trim_start().len(); + if n > 0 { + line.drain(..n); + } + } + for line in &mut self.context_after { + let n = line.len() - line.trim_start().len(); + if n > 0 { + line.drain(..n); + } + } + } +} + +pub use crate::constants::MAX_FFFILE_SIZE; + +/// Options for grep search. +#[derive(Debug, Clone)] +pub struct GrepSearchOptions { + pub max_file_size: u64, + pub max_matches_per_file: usize, + pub smart_case: bool, + /// File-based pagination offset: index into the sorted/filtered file list + /// to start searching from. Pass 0 for the first page, then use + /// `GrepResult::next_file_offset` for subsequent pages. + pub file_offset: usize, + /// Maximum number of matches to collect before stopping. + pub page_limit: usize, + /// How to interpret the search pattern. Defaults to `PlainText`. + pub mode: GrepMode, + /// Maximum time in milliseconds to spend searching before returning partial + /// results. Prevents UI freezes on pathological queries. 0 = no limit. + pub time_budget_ms: u64, + /// Number of context lines to include before each match. 0 = disabled. + pub before_context: usize, + /// Number of context lines to include after each match. 0 = disabled. + pub after_context: usize, + /// Whether to classify each match as a definition line. Adds ~2% overhead + /// on large repos; disable for interactive grep where it is not needed. + pub classify_definitions: bool, + /// Strip leading whitespace from matched lines and context lines, adjusting + /// highlight byte offsets accordingly. Useful for AI/MCP consumers and UIs + /// that don't need indentation. Default: false. + pub trim_whitespace: bool, + /// External abort signal. When provided, overrides the picker's internal + /// cancellation flag. Set to `true` to stop the search early and return + /// partial results. Omit (or use `..Default::default()`) to let the + /// picker manage cancellation. + pub abort_signal: Option>, +} + +impl Default for GrepSearchOptions { + fn default() -> Self { + Self { + max_file_size: MAX_FFFILE_SIZE, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 50, + mode: GrepMode::default(), + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } + } +} + +#[derive(Clone, Copy)] +struct GrepContext<'a, 'b> { + total_files: usize, + filtered_file_count: usize, + budget: &'a ContentCacheBudget, + base_path: &'a Path, + arena: crate::simd_path::ArenaPtr, + overflow_arena: crate::simd_path::ArenaPtr, + prefilter: Option<&'a memchr::memmem::Finder<'b>>, + prefilter_case_insensitive: bool, + abort_signal: &'a AtomicBool, +} + +impl GrepContext<'_, '_> { + #[inline] + fn arena_for_file(&self, file: &FileItem) -> crate::simd_path::ArenaPtr { + if file.is_overflow() { + self.overflow_arena + } else { + self.arena + } + } +} + +struct RegexMatcher<'r> { + regex: &'r regex::bytes::Regex, + is_multiline: bool, +} + +impl Matcher for RegexMatcher<'_> { + type Error = NoError; + + #[inline] + fn find_at(&self, haystack: &[u8], at: usize) -> Result, NoError> { + Ok(self + .regex + .find_at(haystack, at) + .map(|m| Match::new(m.start(), m.end()))) + } + + #[inline] + fn line_terminator(&self) -> Option { + if self.is_multiline { + None + } else { + Some(fff_grep::LineTerminator::byte(b'\n')) + } + } +} + +/// A `grep_matcher::Matcher` backed by `memchr::memmem` for literal search. +/// +/// This is used in `PlainText` mode and is significantly faster than regex +/// for literal patterns: memchr uses SIMD (AVX2/NEON) two-way substring +/// search internally, avoiding the overhead of regex compilation and DFA +/// state transitions. +/// +/// Always reports `\n` as line terminator so the searcher uses the fast +/// candidate-line path (plain text can never span lines unless `\n` is +/// literally in the needle, which we handle separately). +struct PlainTextMatcher<'a> { + /// Case-folded needle bytes for case-insensitive matching. + /// When case-sensitive, this is the original pattern bytes. + needle: &'a [u8], + case_insensitive: bool, +} + +impl Matcher for PlainTextMatcher<'_> { + type Error = NoError; + + #[inline] + fn find_at(&self, haystack: &[u8], at: usize) -> Result, NoError> { + let hay = &haystack[at..]; + + let found = if self.case_insensitive { + memmem::find(hay, self.needle) + } else { + memchr::memmem::find(hay, self.needle) + }; + + Ok(found.map(|pos| Match::new(at + pos, at + pos + self.needle.len()))) + } + + #[inline] + fn line_terminator(&self) -> Option { + Some(fff_grep::LineTerminator::byte(b'\n')) + } +} + +/// Maximum bytes of a matched line to keep for display. Prevents minified +/// JS or huge single-line files from blowing up memory. +const MAX_LINE_DISPLAY_LEN: usize = 512; + +struct SinkState { + file_index: usize, + matches: Vec, + max_matches: usize, + before_context: usize, + after_context: usize, + classify_definitions: bool, +} + +impl SinkState { + #[inline] + fn prepare_line<'a>(line_bytes: &'a [u8], mat: &SinkMatch<'_>) -> (&'a [u8], u32, u64, u64) { + let line_number = mat.line_number().unwrap_or(0); + let byte_offset = mat.absolute_byte_offset(); + + // Trim trailing newline/CR directly on bytes to avoid UTF-8 conversion. + let trimmed_bytes = strip_line_terminators(line_bytes); + + // Truncate for display (floor to a char boundary). + let display_bytes = truncate_display_bytes(trimmed_bytes); + + let display_len = display_bytes.len() as u32; + (display_bytes, display_len, line_number, byte_offset) + } + + #[inline] + #[allow(clippy::too_many_arguments)] + fn push_match( + &mut self, + line_number: u64, + col: usize, + byte_offset: u64, + line_content: String, + match_byte_offsets: SmallVec<[(u32, u32); 4]>, + context_before: Vec, + context_after: Vec, + ) { + let is_definition = classify_definition(self.classify_definitions, &line_content); + self.matches.push(GrepMatch { + file_index: self.file_index, + line_number, + col, + byte_offset, + line_content, + match_byte_offsets, + fuzzy_score: None, + is_definition, + context_before, + context_after, + }); + } + + /// Extract context lines from the full buffer around a matched region. + fn extract_context(&self, mat: &SinkMatch<'_>) -> (Vec, Vec) { + if self.before_context == 0 && self.after_context == 0 { + return (Vec::new(), Vec::new()); + } + + let buffer = mat.buffer(); + let range = mat.bytes_range_in_buffer(); + + let mut before = Vec::new(); + if self.before_context > 0 && range.start > 0 { + // Walk backward from the start of the match line to find preceding lines + let mut pos = range.start; + let mut lines_found = 0; + while lines_found < self.before_context && pos > 0 { + // Skip the newline just before our current position + pos -= 1; + // Find the previous newline + let line_start = match memchr::memrchr(b'\n', &buffer[..pos]) { + Some(nl) => nl + 1, + None => 0, + }; + let line = &buffer[line_start..pos]; + // Trim trailing \r + let line = if line.last() == Some(&b'\r') { + &line[..line.len() - 1] + } else { + line + }; + let truncated = truncate_display_bytes(line); + before.push(String::from_utf8_lossy(truncated).into_owned()); + pos = line_start; + lines_found += 1; + } + before.reverse(); + } + + let mut after = Vec::new(); + if self.after_context > 0 && range.end < buffer.len() { + let mut pos = range.end; + let mut lines_found = 0; + while lines_found < self.after_context && pos < buffer.len() { + // Find the next newline + let line_end = match memchr::memchr(b'\n', &buffer[pos..]) { + Some(nl) => pos + nl, + None => buffer.len(), + }; + let line = &buffer[pos..line_end]; + // Trim trailing \r + let line = if line.last() == Some(&b'\r') { + &line[..line.len() - 1] + } else { + line + }; + let truncated = truncate_display_bytes(line); + after.push(String::from_utf8_lossy(truncated).into_owned()); + pos = if line_end < buffer.len() { + line_end + 1 // skip past \n + } else { + buffer.len() + }; + lines_found += 1; + } + } + + (before, after) + } +} + +/// Truncate a byte slice for display, respecting UTF-8 char boundaries. +#[inline] +pub(super) fn truncate_display_bytes(bytes: &[u8]) -> &[u8] { + if bytes.len() <= MAX_LINE_DISPLAY_LEN { + bytes + } else { + let mut end = MAX_LINE_DISPLAY_LEN; + while end > 0 && !is_utf8_char_boundary(bytes[end]) { + end -= 1; + } + &bytes[..end] + } +} + +/// Sink for `PlainText` mode. +/// +/// Highlights are extracted with `memchr::memmem::Finder` (case-sensitive) +/// or the SIMD `simd_string_utils::memmem` search (case-insensitive). No regex engine is +/// involved at any point. +struct PlainTextSink<'r> { + state: SinkState, + finder: &'r memchr::memmem::Finder<'r>, + pattern_len: u32, + case_insensitive: bool, +} + +impl Sink for PlainTextSink<'_> { + type Error = std::io::Error; + + fn matched(&mut self, _searcher: &Searcher, mat: &SinkMatch<'_>) -> Result { + if self.state.max_matches != 0 && self.state.matches.len() >= self.state.max_matches { + return Ok(false); + } + + let line_bytes = mat.bytes(); + let (display_bytes, display_len, line_number, byte_offset) = + SinkState::prepare_line(line_bytes, mat); + + let line_content = String::from_utf8_lossy(display_bytes).into_owned(); + let mut match_byte_offsets: SmallVec<[(u32, u32); 4]> = SmallVec::new(); + let mut col = 0usize; + let mut first = true; + + if self.case_insensitive { + // The finder was built over the lowered pattern, so its needle is + // exactly the `needle_lower` expected by `memmem::find`. + let needle_lower = self.finder.needle(); + let mut start_pos = 0usize; + while let Some(pos) = memmem::find(&display_bytes[start_pos..], needle_lower) { + let abs_start = (start_pos + pos) as u32; + let abs_end = (abs_start + self.pattern_len).min(display_len); + if first { + col = abs_start as usize; + first = false; + } + match_byte_offsets.push((abs_start, abs_end)); + start_pos += pos + 1; + } + } else { + let mut start_pos = 0usize; + while let Some(pos) = self.finder.find(&display_bytes[start_pos..]) { + let abs_start = (start_pos + pos) as u32; + let abs_end = (abs_start + self.pattern_len).min(display_len); + if first { + col = abs_start as usize; + first = false; + } + match_byte_offsets.push((abs_start, abs_end)); + start_pos += pos + 1; + } + } + + let (context_before, context_after) = self.state.extract_context(mat); + self.state.push_match( + line_number, + col, + byte_offset, + line_content, + match_byte_offsets, + context_before, + context_after, + ); + Ok(true) + } + + fn finish(&mut self, _: &Searcher, _: &fff_grep::SinkFinish) -> Result<(), Self::Error> { + Ok(()) + } +} + +/// Sink for `Regex` mode. +/// +/// Uses the compiled regex to extract precise variable-length highlight spans +/// from each matched line. No `memmem` finder is involved. +struct RegexSink<'r> { + state: SinkState, + re: &'r regex::bytes::Regex, +} + +impl Sink for RegexSink<'_> { + type Error = std::io::Error; + + fn matched( + &mut self, + _searcher: &Searcher, + sink_match: &SinkMatch<'_>, + ) -> Result { + if self.state.max_matches != 0 && self.state.matches.len() >= self.state.max_matches { + return Ok(false); + } + + let line_bytes = sink_match.bytes(); + let (display_bytes, display_len, line_number, byte_offset) = + SinkState::prepare_line(line_bytes, sink_match); + + let line_content = String::from_utf8_lossy(display_bytes).into_owned(); + let mut match_byte_offsets: SmallVec<[(u32, u32); 4]> = SmallVec::new(); + let mut col = 0usize; + let mut first = true; + + for m in self.re.find_iter(display_bytes) { + let abs_start = m.start() as u32; + let abs_end = (m.end() as u32).min(display_len); + if first { + col = abs_start as usize; + first = false; + } + match_byte_offsets.push((abs_start, abs_end)); + } + + let (context_before, context_after) = self.state.extract_context(sink_match); + self.state.push_match( + line_number, + col, + byte_offset, + line_content, + match_byte_offsets, + context_before, + context_after, + ); + Ok(true) + } + + fn finish(&mut self, _: &Searcher, _: &fff_grep::SinkFinish) -> Result<(), Self::Error> { + Ok(()) + } +} + +/// A `grep_matcher::Matcher` backed by Aho-Corasick for multi-pattern search. +/// +/// Finds the first occurrence of any pattern starting at the given offset. +/// Always reports `\n` as the line terminator for the fast candidate-line path. +struct AhoCorasickMatcher<'a> { + ac: &'a AhoCorasick, +} + +impl Matcher for AhoCorasickMatcher<'_> { + type Error = NoError; + + #[inline] + fn find_at(&self, haystack: &[u8], at: usize) -> std::result::Result, NoError> { + let hay = &haystack[at..]; + let found: Option = self.ac.find(hay); + Ok(found.map(|m| Match::new(at + m.start(), at + m.end()))) + } + + #[inline] + fn line_terminator(&self) -> Option { + Some(fff_grep::LineTerminator::byte(b'\n')) + } +} + +/// Sink for Aho-Corasick multi-pattern mode. +/// +/// Collects all pattern match positions on each matched line for highlighting. +struct AhoCorasickSink<'a> { + state: SinkState, + ac: &'a AhoCorasick, +} + +impl Sink for AhoCorasickSink<'_> { + type Error = std::io::Error; + + fn matched(&mut self, _searcher: &Searcher, mat: &SinkMatch<'_>) -> Result { + if self.state.max_matches != 0 && self.state.matches.len() >= self.state.max_matches { + return Ok(false); + } + + let line_bytes = mat.bytes(); + let (display_bytes, display_len, line_number, byte_offset) = + SinkState::prepare_line(line_bytes, mat); + + let line_content = String::from_utf8_lossy(display_bytes).into_owned(); + let mut match_byte_offsets: SmallVec<[(u32, u32); 4]> = SmallVec::new(); + let mut col = 0usize; + let mut first = true; + + for m in self.ac.find_iter(display_bytes as &[u8]) { + let abs_start = m.start() as u32; + let abs_end = (m.end() as u32).min(display_len); + if first { + col = abs_start as usize; + first = false; + } + match_byte_offsets.push((abs_start, abs_end)); + } + + let (context_before, context_after) = self.state.extract_context(mat); + self.state.push_match( + line_number, + col, + byte_offset, + line_content, + match_byte_offsets, + context_before, + context_after, + ); + Ok(true) + } + + fn finish(&mut self, _: &Searcher, _: &fff_grep::SinkFinish) -> Result<(), Self::Error> { + Ok(()) + } +} + +/// Multi-pattern OR search using Aho-Corasick. +/// +/// Builds a single automaton from all patterns and searches each file in one +/// pass. This is significantly faster than regex alternation for literal text +/// searches because Aho-Corasick uses SIMD-accelerated multi-needle matching. +/// +/// Returns the same `GrepResult` type as `grep_search`. +#[allow(clippy::too_many_arguments)] +pub(crate) fn multi_grep_search<'a>( + files: &'a [FileItem], + patterns: &[&str], + constraints: &[fff_query_parser::Constraint<'_>], + options: &GrepSearchOptions, + budget: &ContentCacheBudget, + bigram_index: Option<&BigramFilter>, + bigram_overlay: Option<&BigramOverlay>, + abort_signal: &AtomicBool, + base_path: &Path, + arena: crate::simd_path::ArenaPtr, + overflow_arena: crate::simd_path::ArenaPtr, +) -> GrepResult<'a> { + let total_files = files.live_count(); + + if patterns.is_empty() || patterns.iter().all(|p| p.is_empty()) { + return GrepResult::empty(total_files, total_files); + } + + // Bigram prefiltering: OR the candidate bitsets for each pattern. + // A file is a candidate if it matches ANY of the patterns' bigrams. + let bigram_candidates = if let Some(idx) = bigram_index + && idx.is_ready() + { + let mut combined: Option> = None; + for pattern in patterns { + if let Some(candidates) = idx.query(pattern.as_bytes()) { + combined = Some(match combined { + None => candidates, + Some(mut acc) => { + // OR: file is candidate if it matches any pattern + acc.iter_mut() + .zip(candidates.iter()) + .for_each(|(a, b)| *a |= *b); + acc + } + }); + } + } + + if let Some(ref mut candidates) = combined + && let Some(overlay) = bigram_overlay + { + for pattern in patterns { + let pattern_bigrams = extract_bigrams(pattern.as_bytes()); + for file_idx in overlay.query_modified(&pattern_bigrams) { + let word = file_idx / 64; + if word < candidates.len() { + candidates[word] |= 1u64 << (file_idx % 64); + } + } + } + } + + combined + } else { + None + }; + + let base_file_count = match bigram_overlay { + Some(bigram_overlay) => bigram_overlay.base_file_count(), + None => files.len(), + }; + + let (mut files_to_search, mut filtered_file_count) = prefilter_files( + files, + constraints, + bigram_candidates.as_deref(), + base_file_count, + options, + arena, + overflow_arena, + ); + + // If constraints yielded 0 files and we had FilePath constraints, + // retry without them (the path token was likely part of the search text). + if files_to_search.is_empty() + && let Some(stripped) = strip_file_path_constraint_if_present(constraints) + { + let (retry_files, retry_count) = prefilter_files( + files, + &stripped, + bigram_candidates.as_deref(), + base_file_count, + options, + arena, + overflow_arena, + ); + files_to_search = retry_files; + filtered_file_count = retry_count; + } + + if files_to_search.is_empty() { + return GrepResult::empty(total_files, filtered_file_count); + } + + // Smart case: case-insensitive when all patterns are lowercase + let case_insensitive = if options.smart_case { + !patterns.iter().any(|p| p.chars().any(|c| c.is_uppercase())) + } else { + false + }; + + let ac = aho_corasick::AhoCorasickBuilder::new() + .ascii_case_insensitive(case_insensitive) + .build(patterns) + .expect("Aho-Corasick build should not fail for literal patterns"); + + let searcher = { + let mut b = SearcherBuilder::new(); + b.line_number(true); + b + } + .build(); + + let ac_matcher = AhoCorasickMatcher { ac: &ac }; + perform_grep( + &files_to_search, + options, + &GrepContext { + total_files, + filtered_file_count, + budget, + base_path, + arena, + overflow_arena, + prefilter: None, // no memmem prefilter for multi-pattern search + prefilter_case_insensitive: false, + abort_signal, + }, + |file_bytes: &[u8], max_matches: usize| { + let state = SinkState { + file_index: 0, + matches: Vec::with_capacity(4), + max_matches, + before_context: options.before_context, + after_context: options.after_context, + classify_definitions: options.classify_definitions, + }; + + let mut sink = AhoCorasickSink { state, ac: &ac }; + + if let Err(e) = searcher.search_slice(&ac_matcher, file_bytes, &mut sink) { + tracing::error!(error = %e, "Grep (aho-corasick multi) search failed"); + } + + sink.state.matches + }, + ) +} + +// copied from the rust u8 private method +#[inline] +const fn is_utf8_char_boundary(b: u8) -> bool { + (b as i8) >= -0x40 +} + +fn build_regex(pattern: &str, smart_case: bool) -> Result { + if pattern.is_empty() { + return Err("empty pattern".to_string()); + } + + let regex_pattern = if pattern.contains("\\n") { + pattern.replace("\\n", "\n") + } else { + pattern.to_string() + }; + + let case_insensitive = if smart_case { + !pattern.chars().any(|c| c.is_uppercase()) + } else { + false + }; + + regex::bytes::RegexBuilder::new(®ex_pattern) + .case_insensitive(case_insensitive) + .multi_line(true) + .unicode(false) + .build() + .map_err(|e| e.to_string()) +} + +/// Convert character-position indices from neo_frizbee into byte-offset +/// pairs (start, end) suitable for `match_byte_offsets`. +/// +/// frizbee returns character positions (0-based index into the char +/// iterator). We need byte ranges because the UI renderer and Lua layer +/// use byte offsets for extmark highlights. +/// +/// Each matched character becomes its own (byte_start, byte_end) pair. +/// Adjacent characters are merged into a single contiguous range. +pub(super) fn char_indices_to_byte_offsets( + line: &str, + char_indices: &[usize], +) -> SmallVec<[(u32, u32); 4]> { + if char_indices.is_empty() { + return SmallVec::new(); + } + + // Build a map: char_index -> (byte_start, byte_end) for all chars. + // Iterating all chars is O(n) in the line length which is bounded by MAX_LINE_DISPLAY_LEN (512). + let char_byte_ranges: Vec<(usize, usize)> = line + .char_indices() + .map(|(byte_pos, ch)| (byte_pos, byte_pos + ch.len_utf8())) + .collect(); + + // Convert char indices to byte ranges, merging adjacent ranges + let mut result: SmallVec<[(u32, u32); 4]> = SmallVec::with_capacity(char_indices.len()); + + for &ci in char_indices { + if ci >= char_byte_ranges.len() { + continue; // out of bounds (shouldn't happen with valid data) + } + let (start, end) = char_byte_ranges[ci]; + // Merge with previous range if adjacent + if let Some(last) = result.last_mut() + && last.1 == start as u32 + { + last.1 = end as u32; + continue; + } + result.push((start as u32, end as u32)); + } + + result +} + +#[tracing::instrument( + skip_all, + level = Level::DEBUG, + fields(prefiltered_count = files_to_search.len()) +)] +fn perform_grep<'a, F>( + files_to_search: &[&'a FileItem], + options: &GrepSearchOptions, + ctx: &GrepContext<'_, '_>, + search_file: F, +) -> GrepResult<'a> +where + F: Fn(&[u8], usize) -> Vec + Sync, +{ + let time_budget = if options.time_budget_ms > 0 { + Some(std::time::Duration::from_millis(options.time_budget_ms)) + } else { + None + }; + + let search_start = std::time::Instant::now(); + let page_limit = options.page_limit; + let budget_exceeded = AtomicBool::new(false); + + let mut result_files: Vec<&'a FileItem> = Vec::new(); + let mut all_matches: Vec = Vec::new(); + let mut files_consumed: usize = 0; + let mut page_filled = false; + + // Each chunk is a rayon barrier. A flat small chunk over 500k files = ~7800 + // barriers; x2 growth makes it logarithmic. But a too-aggressive growth + // over-scans: when a page fills mid-chunk, the whole submitted chunk still + // runs. + // + // So only grow when the prefilter is weak (large candidate set); + // when bigram cut the set in half, keep fixed small chunks for cheap page-fill termination. + let base_chunk = rayon::current_num_threads() * 4; + let prefilter_strong = ctx.total_files > 0 && files_to_search.len() * 2 < ctx.total_files; + let max_chunk = if prefilter_strong { + base_chunk + } else { + (base_chunk * 256).max(8 * 1024) + }; + let growth = if prefilter_strong { 1 } else { 2 }; + let mut chunk_size = base_chunk; + let mut chunk_start = 0; + + while chunk_start < files_to_search.len() { + let chunk_end = (chunk_start + chunk_size).min(files_to_search.len()); + let chunk = &files_to_search[chunk_start..chunk_end]; + chunk_start = chunk_end; + chunk_size = (chunk_size * growth).min(max_chunk); + let chunk_offset = files_consumed; + + let chunk_results: Vec<(usize, &'a FileItem, Vec)> = chunk + .par_iter() + .enumerate() + .map_init( + // tested it out a few times, this is just fine for rayon worker in this specific + // case it doesn't reallocate this many times and it is actually faster than using + // scoped threads with a predefined local scratch buffers because of spawn cost + || (Vec::with_capacity(64 * 1024), MmapSlot::default()), + |(buf, mmap_slot), (local_idx, file)| { + // perform all the atomic machinery on every 8th + if local_idx % 8 == 0 { + let mut need_abort = ctx.abort_signal.load(Ordering::Relaxed); + if !need_abort + && let Some(budget) = time_budget + && all_matches.len() > 1 + && search_start.elapsed() > budget + { + need_abort = true; + } + + if need_abort { + budget_exceeded.store(true, Ordering::Relaxed); + return None; + } + } + + let content = file.get_content_for_search( + buf, + mmap_slot, + ctx.arena_for_file(file), + ctx.base_path, + ctx.budget, + )?; + + // Fast whole-file memmem check before entering the + // grep-searcher machinery. Skips Vec alloc, Searcher + // setup, and line-splitting for files that can't match. + if let Some(pf) = ctx.prefilter { + let found = if ctx.prefilter_case_insensitive { + memmem::find(content, pf.needle()).is_some() + } else { + pf.find(content).is_some() + }; + if !found { + return None; + } + } + + let file_matches = search_file(content, options.max_matches_per_file); + + if file_matches.is_empty() { + return None; + } + + Some((chunk_offset + local_idx, *file, file_matches)) + }, + ) + .flatten() + .collect(); + + // Every file in the chunk was visited by rayon (matched or not). + files_consumed = chunk_offset + chunk.len(); + + // Flatten this chunk's results into the accumulator. + for (batch_idx, file, file_matches) in chunk_results { + let file_result_idx = result_files.len(); + result_files.push(file); + + for mut m in file_matches { + m.file_index = file_result_idx; + if options.trim_whitespace { + m.trim_leading_whitespace(); + } + all_matches.push(m); + } + + if all_matches.len() >= page_limit { + // Tighten files_consumed to the file that tipped us over so + // the next page resumes right after it. + files_consumed = batch_idx + 1; + page_filled = true; + break; + } + } + + if page_filled || budget_exceeded.load(Ordering::Relaxed) { + break; + } + } + + // If no file had any match, we searched the entire slice. + if result_files.is_empty() { + files_consumed = files_to_search.len(); + } + + let has_more = budget_exceeded.load(Ordering::Relaxed) + || (page_filled && files_consumed < files_to_search.len()); + + let next_file_offset = if has_more { + options.file_offset + files_consumed + } else { + 0 + }; + + GrepResult { + matches: all_matches, + files_with_matches: result_files.len(), + files: result_files, + total_files_searched: files_consumed, + total_files: ctx.total_files, + filtered_file_count: ctx.filtered_file_count, + next_file_offset, + regex_fallback_error: None, + } +} + +/// Single pass prefilter that doesn't involve file reading +/// allocates only amount of memory required for storing references of the FileItems have to be +/// opened for grepping unaviodably, in the worst case allocates N * memory if no prefilter needed +fn prefilter_files<'a>( + files: &'a [FileItem], + constraints: &[fff_query_parser::Constraint<'_>], + bigram_candidates: Option<&[u64]>, + base_count: usize, + options: &GrepSearchOptions, + arena: crate::simd_path::ArenaPtr, + overflow_arena: crate::simd_path::ArenaPtr, +) -> (Vec<&'a FileItem>, usize) { + let max_file_size = options.max_file_size; + let plan = if constraints.is_empty() { + None + } else { + Some(ConstraintPlan::build( + constraints, + files, + arena, + overflow_arena, + )) + }; + + let mut scratch = ConstraintsBuffers::new(); + + #[inline(always)] + fn basic_prefilter(file: &FileItem, max: u64) -> bool { + !file.is_deleted() && !file.is_binary() && file.size > 0 && file.size <= max + } + + // squeeze as much prefilters into a single loop as possible + let mut prefiltered: Vec<&FileItem> = match bigram_candidates { + Some(candidates) => { + let boundary = base_count.min(files.len()); + let (indexed, tail) = files.split_at(boundary); + + let cap = BigramFilter::count_candidates(candidates) + tail.len(); + let mut out: Vec<&FileItem> = Vec::with_capacity(cap); + + let full_words = boundary / 64; + let last_word_bits = boundary % 64; + + // we need this because we already had a regression of the wrong bit + // has been set for the very last word based on the overlay, it's pretty cheap + macro_rules! evaluate_bigram_match_word { + ($word:expr, $base:expr) => {{ + let mut bits: u64 = $word; + while bits != 0 { + let bit = bits.trailing_zeros() as usize; + let file_idx = $base + bit; + bits &= bits - 1; + + let f = unsafe { indexed.get_unchecked(file_idx) }; + if !basic_prefilter(f, max_file_size) { + continue; + } + if let Some(plan) = plan.as_ref() + && !plan.matches(f, file_idx, arena, overflow_arena, &mut scratch) + { + continue; + } + out.push(f); + } + }}; + } + + // Full words: every set bit guaranteed `< boundary`. + for (word_idx, &word) in candidates.iter().take(full_words).enumerate() { + if word != 0 { + evaluate_bigram_match_word!(word, word_idx * 64); + } + } + + // Last partial word: mask bits past `boundary` once at word load. + if last_word_bits != 0 { + // this will get only (mod 64) bits from the last word guaratee that it's 0 padded + let last_mask: u64 = (1u64 << last_word_bits) - 1; + let word = candidates[full_words] & last_mask; + if word != 0 { + evaluate_bigram_match_word!(word, full_words * 64); + } + } + + // Sequential processing for non-bigrammable files: they are always in the end + for (offset, f) in tail.iter().enumerate() { + if !basic_prefilter(f, max_file_size) { + continue; + } + if let Some(ref p) = plan + && !p.matches(f, boundary + offset, arena, overflow_arena, &mut scratch) + { + continue; + } + out.push(f); + } + + out + } + // this will be executed if there is no bigram, in the worst case it will allocate + // whole array of files but probability in the real repo of NO preflter working is so + // low that we just ignore that, usually there would be at least a few files excluded + None => { + let mut out: Vec<&FileItem> = Vec::new(); + for (idx, f) in files.iter().enumerate() { + if !basic_prefilter(f, max_file_size) { + continue; + } + if let Some(ref p) = plan + && !p.matches(f, idx, arena, overflow_arena, &mut scratch) + { + continue; + } + out.push(f); + } + out + } + }; + + let total_count = prefiltered.len(); + + sort_with_buffer(&mut prefiltered, |a, b| { + b.total_frecency_score() + .cmp(&a.total_frecency_score()) + .then(b.modified.cmp(&a.modified)) + }); + + if options.file_offset > 0 && options.file_offset < total_count { + let paginated = prefiltered.split_off(options.file_offset); + (paginated, total_count) + } else if options.file_offset >= total_count { + (Vec::new(), total_count) + } else { + (prefiltered, total_count) + } +} + +/// Perform a grep search across all indexed files. +/// +/// When `query` is empty, returns git-modified/untracked files sorted by +/// frecency for the "welcome state" UI. +#[tracing::instrument(skip_all, fields(file_count = files.len()))] +#[allow(clippy::too_many_arguments)] +pub(crate) fn grep_search<'a>( + files: &'a [FileItem], + query: &FFFQuery<'_>, + options: &GrepSearchOptions, + budget: &ContentCacheBudget, + bigram_index: Option<&BigramFilter>, + bigram_overlay: Option<&BigramOverlay>, + abort_signal: &AtomicBool, + base_path: &Path, + arena: crate::simd_path::ArenaPtr, + overflow_arena: crate::simd_path::ArenaPtr, +) -> GrepResult<'a> { + let total_files = files.live_count(); + + // Extract the grep text and file constraints from the parsed query. + // For grep, the search pattern is the original query with constraint tokens + // removed. All non-constraint text tokens are collected and joined with + // spaces to form the grep pattern: + // "name = *.rs someth" -> grep "name = someth" with constraint Extension("rs") + let constraints_from_query = &query.constraints[..]; + + let grep_text = if !matches!(query.fuzzy_query, fff_query_parser::FuzzyQuery::Empty) { + query.grep_text() + } else { + // if constraint-only or empty query we use raw_query for backslash-escape handling + let t = query.raw_query.trim(); + if t.starts_with('\\') && t.len() > 1 { + let suffix = &t[1..]; + let parser = QueryParser::new(GrepConfig); + if !parser.parse(suffix).constraints.is_empty() { + suffix.to_string() + } else { + t.to_string() + } + } else { + t.to_string() + } + }; + + if grep_text.is_empty() { + return GrepResult::empty(total_files, total_files); + } + + let case_insensitive = if options.smart_case { + !grep_text.chars().any(|c| c.is_uppercase()) + } else { + false + }; + + let mut regex_fallback_error: Option = None; + let regex = match options.mode { + GrepMode::PlainText => None, + GrepMode::Fuzzy => { + // Bigram prefilter: pick 5 evenly-spaced probe bigrams, require + // (5 - max_typos) of them to appear. Widely-spaced probes are + // far more selective than sliding windows of adjacent bigrams. + let bigram_candidates = if let Some(idx) = bigram_index + && idx.is_ready() + { + let bq = fuzzy_to_bigram_query(&grep_text, 7); + if !bq.is_any() + && let Some(mut candidates) = bq.evaluate(idx) + { + if let Some(overlay) = bigram_overlay { + for (r, t) in candidates.iter_mut().zip(overlay.tombstones().iter()) { + *r &= !t; + } + // Fuzzy: conservatively add all modified files + for file_idx in overlay.modified_indices() { + let word = file_idx / 64; + if word < candidates.len() { + candidates[word] |= 1u64 << (file_idx % 64); + } + } + } + Some(candidates) + } else { + None + } + } else { + None + }; + + let base_count = match bigram_overlay { + Some(bigram_overlay) => bigram_overlay.base_file_count(), + None => files.len(), + }; + + let (mut files_to_search, mut filtered_file_count) = prefilter_files( + files, + constraints_from_query, + bigram_candidates.as_deref(), + base_count, + options, + arena, + overflow_arena, + ); + + if files_to_search.is_empty() + && let Some(stripped) = + strip_file_path_constraint_if_present(constraints_from_query) + { + let (retry_files, retry_count) = prefilter_files( + files, + &stripped, + bigram_candidates.as_deref(), + base_count, + options, + arena, + overflow_arena, + ); + + files_to_search = retry_files; + filtered_file_count = retry_count; + } + + if files_to_search.is_empty() { + return GrepResult::empty(total_files, filtered_file_count); + } + + return super::fuzzy_grep::fuzzy_grep_search( + &grep_text, + &files_to_search, + options, + total_files, + filtered_file_count, + case_insensitive, + budget, + abort_signal, + base_path, + arena, + overflow_arena, + ); + } + GrepMode::Regex => build_regex(&grep_text, options.smart_case) + .inspect_err(|err| { + tracing::warn!("Regex compilation failed for {}. Error {}", grep_text, err); + + regex_fallback_error = Some(err.to_string()); + }) + .ok(), + }; + + let is_multiline = has_unescaped_newline_escape(&grep_text); + + let effective_pattern = if is_multiline { + replace_unescaped_newline_escapes(&grep_text) + } else { + grep_text.to_string() + }; + + let finder_pattern: Vec = if case_insensitive { + effective_pattern.as_bytes().to_ascii_lowercase() + } else { + effective_pattern.as_bytes().to_vec() + }; + let finder = memchr::memmem::Finder::new(&finder_pattern); + let pattern_len = finder_pattern.len() as u32; + + // Bigram prefiltering: query the inverted index + merge overlay. + // For PlainText mode: extract bigrams directly from the literal pattern. + // For Regex mode: decompose the regex HIR into an AND/OR bigram query tree + // and evaluate it against the inverted index (supports alternation, optional + // groups, character classes, and sparse-1 bigrams across single-byte wildcards). + let bigram_candidates = if let Some(idx) = bigram_index + && idx.is_ready() + { + let raw_candidates = if regex.is_none() { + // PlainText or regex-fallback-to-plain: literal bigram query + idx.query(effective_pattern.as_bytes()) + } else { + // Regex mode: decompose pattern into bigram query tree + let bq = regex_to_bigram_query(&effective_pattern); + if !bq.is_any() { bq.evaluate(idx) } else { None } + }; + + if let Some(mut candidates) = raw_candidates { + if let Some(overlay) = bigram_overlay { + // Clear tombstoned (deleted) files from candidates + for (r, t) in candidates.iter_mut().zip(overlay.tombstones().iter()) { + *r &= !t; + } + + if regex.is_none() { + let pattern_bigrams = extract_bigrams(effective_pattern.as_bytes()); + for file_idx in overlay.query_modified(&pattern_bigrams) { + let word = file_idx / 64; + if word < candidates.len() { + candidates[word] |= 1u64 << (file_idx % 64); + } + } + } else { + for file_idx in overlay.modified_indices() { + let word = file_idx / 64; + if word < candidates.len() { + candidates[word] |= 1u64 << (file_idx % 64); + } + } + } + } + Some(candidates) + } else { + None + } + } else { + None + }; + + // Bigram bitset only covers `files[..bigram_boundary]`, new files aka overflow + // (max 1024 always scanned) + let bigram_boundary = bigram_overlay + .map(|o| o.base_file_count()) + .unwrap_or(files.len()); + + let (mut files_to_search, mut filtered_file_count) = prefilter_files( + files, + constraints_from_query, + bigram_candidates.as_deref(), + bigram_boundary, + options, + arena, + overflow_arena, + ); + + if files_to_search.is_empty() + && let Some(stripped) = strip_file_path_constraint_if_present(constraints_from_query) + { + let (retry_files, retry_count) = prefilter_files( + files, + &stripped, + bigram_candidates.as_deref(), + bigram_boundary, + options, + arena, + overflow_arena, + ); + files_to_search = retry_files; + filtered_file_count = retry_count; + } + + if files_to_search.is_empty() { + return GrepResult::empty(total_files, filtered_file_count); + } + + // `PlainTextMatcher` is used by the grep-searcher engine for line detection. + // `PlainTextSink` / `RegexSink` handle highlight extraction independently via ripgrep create + let plain_matcher = PlainTextMatcher { + needle: &finder_pattern, + case_insensitive, + }; + + let searcher = { + let mut b = SearcherBuilder::new(); + b.line_number(true).multi_line(is_multiline); + b + } + .build(); + + let should_prefilter = regex.is_none(); + let mut result = perform_grep( + &files_to_search, + options, + &GrepContext { + total_files, + filtered_file_count, + budget, + base_path, + arena, + overflow_arena, + prefilter: should_prefilter.then_some(&finder), + prefilter_case_insensitive: case_insensitive, + abort_signal, + }, + |file_bytes: &[u8], max_matches: usize| { + let state = SinkState { + file_index: 0, + matches: Vec::with_capacity(4), + max_matches, + before_context: options.before_context, + after_context: options.after_context, + classify_definitions: options.classify_definitions, + }; + + match regex { + Some(ref re) => { + let regex_matcher = RegexMatcher { + regex: re, + is_multiline, + }; + let mut sink = RegexSink { state, re }; + if let Err(e) = searcher.search_slice(®ex_matcher, file_bytes, &mut sink) { + tracing::error!(error = %e, "Grep (regex) search failed"); + } + sink.state.matches + } + None => { + let mut sink = PlainTextSink { + state, + finder: &finder, + pattern_len, + case_insensitive, + }; + if let Err(e) = searcher.search_slice(&plain_matcher, file_bytes, &mut sink) { + tracing::error!(error = %e, "Grep (plain text) search failed"); + } + sink.state.matches + } + } + }, + ); + result.regex_fallback_error = regex_fallback_error; + result +} + +pub fn parse_grep_query(query: &str) -> FFFQuery<'_> { + let parser = QueryParser::new(GrepConfig); + parser.parse(query) +} + +fn strip_file_path_constraint_if_present<'a>( + constraints: &[Constraint<'a>], +) -> Option> { + if !constraints + .iter() + .any(|c| matches!(c, Constraint::FilePath(_))) + { + return None; + } + + let filtered: fff_query_parser::ConstraintVec<'a> = constraints + .iter() + .filter(|c| !matches!(c, Constraint::FilePath(_))) + .cloned() + .collect(); + + Some(filtered) +} diff --git a/crates/fff-core/src/grep/grep_tests.rs b/crates/fff-core/src/grep/grep_tests.rs new file mode 100644 index 0000000..d342902 --- /dev/null +++ b/crates/fff-core/src/grep/grep_tests.rs @@ -0,0 +1,372 @@ +use super::grep::*; + +use crate::bigram_filter::BigramIndexBuilder; +use crate::file_picker::{FilePicker, FilePickerOptions}; +use std::io::Write; +use std::sync::atomic::AtomicBool; + +#[test] +fn test_unescaped_newline_detection() { + // Single \n → multiline + assert!(has_unescaped_newline_escape("foo\\nbar")); + // \\n → escaped backslash + literal n, NOT multiline + // (this is what the user types when grepping Rust source with `\\nvim`) + assert!(!has_unescaped_newline_escape("foo\\\\nvim-data")); + // Real-world: source file has literal \\AppData\\Local\\nvim-data + // (double backslash in the file, so user types double backslash) + assert!(!has_unescaped_newline_escape( + r#"format!("{}\\AppData\\Local\\nvim-data","# + )); + // No \n at all + assert!(!has_unescaped_newline_escape("hello world")); + // \\\\n → even number of backslashes before n → NOT multiline + assert!(!has_unescaped_newline_escape("foo\\\\\\\\nbar")); + // \\\n → 3 backslashes: first two pair up, third + n = \n → multiline + assert!(has_unescaped_newline_escape("foo\\\\\\nbar")); +} + +#[test] +fn test_replace_unescaped_newline() { + // \n → real newline + assert_eq!(replace_unescaped_newline_escapes("foo\\nbar"), "foo\nbar"); + // \\n → preserved as-is + assert_eq!( + replace_unescaped_newline_escapes("foo\\\\nvim"), + "foo\\\\nvim" + ); +} + +#[test] +fn test_fuzzy_typo_scoring() { + // Mirror the config from fuzzy_grep_search + let needle = "schema"; + let max_typos = (needle.len() / 3).min(2); // 2 + let config = neo_frizbee::Config { + max_typos: Some(max_typos as u16), + sort: false, + scoring: neo_frizbee::Scoring { + exact_match_bonus: 100, + ..neo_frizbee::Scoring::default() + }, + ..Default::default() + }; + let min_matched = needle.len().saturating_sub(1).max(1); // 5 + let max_match_span = needle.len() + 4; // 10 + + // Helper: check if a match would pass our post-filters + let passes = |n: &str, h: &str| -> bool { + let Some(mut mi) = neo_frizbee::match_list_indices(n, &[h], &config) + .into_iter() + .next() + else { + return false; + }; + // upstream returns indices in reverse order, sort ascending + mi.indices.sort_unstable(); + if mi.indices.len() < min_matched { + return false; + } + if let (Some(&first), Some(&last)) = (mi.indices.first(), mi.indices.last()) { + let span = last - first + 1; + if span > max_match_span { + return false; + } + let density = (mi.indices.len() * 100) / span; + if density < 70 { + return false; + } + } + true + }; + + // Exact match: must pass + assert!(passes("schema", "schema")); + // Exact in longer line: must pass + assert!(passes("schema", " schema: String,")); + // In identifier: must pass + assert!(passes("schema", "pub fn validate_schema() {}")); + // Transposition: must pass + assert!(passes("shcema", "schema")); + // Partial "ema" only line: must NOT pass + assert!(!passes("schema", "it has ema in it")); + // Completely unrelated: must NOT pass + assert!(!passes("schema", "hello world foo bar")); +} + +#[test] +fn test_multi_grep_search() { + use crate::file_picker::{FilePicker, FilePickerOptions}; + use std::io::Write; + + let dir = tempfile::tempdir().unwrap(); + + // File 1: has "GrepMode" and "GrepMatch" + { + let mut f = std::fs::File::create(dir.path().join("grep.rs")).unwrap(); + writeln!(f, "pub enum GrepMode {{").unwrap(); + writeln!(f, " PlainText,").unwrap(); + writeln!(f, " Regex,").unwrap(); + writeln!(f, "}}").unwrap(); + writeln!(f, "pub struct GrepMatch {{").unwrap(); + writeln!(f, " pub line_number: u64,").unwrap(); + writeln!(f, "}}").unwrap(); + } + + // File 2: has "PlainTextMatcher" only + { + let mut f = std::fs::File::create(dir.path().join("matcher.rs")).unwrap(); + writeln!(f, "struct PlainTextMatcher {{").unwrap(); + writeln!(f, " needle: Vec,").unwrap(); + writeln!(f, "}}").unwrap(); + } + + // File 3: no matches + { + let mut f = std::fs::File::create(dir.path().join("other.rs")).unwrap(); + writeln!(f, "fn main() {{").unwrap(); + writeln!(f, " println!(\"hello\");").unwrap(); + writeln!(f, "}}").unwrap(); + } + + let mut picker = FilePicker::new(FilePickerOptions { + base_path: dir.path().to_str().unwrap().into(), + watch: false, + ..Default::default() + }) + .unwrap(); + picker.collect_files().unwrap(); + + let files = picker.get_files(); + let arena = picker.arena_base_ptr(); + + let options = super::GrepSearchOptions { + max_file_size: MAX_FFFILE_SIZE, + max_matches_per_file: 0, + smart_case: true, + file_offset: 0, + page_limit: 100, + mode: super::GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + }; + let no_cancel = AtomicBool::new(false); + + // Test with 3 patterns + let result = super::multi_grep_search( + files, + &["GrepMode", "GrepMatch", "PlainTextMatcher"], + &[], + &options, + picker.cache_budget(), + None, + None, + &no_cancel, + dir.path(), + arena, + arena, + ); + + assert!( + result.matches.len() >= 3, + "Expected at least 3 matches, got {}", + result.matches.len() + ); + + let has_grep_mode = result + .matches + .iter() + .any(|m| m.line_content.contains("GrepMode")); + let has_grep_match = result + .matches + .iter() + .any(|m| m.line_content.contains("GrepMatch")); + let has_plain_text_matcher = result + .matches + .iter() + .any(|m| m.line_content.contains("PlainTextMatcher")); + + assert!(has_grep_mode, "Should find GrepMode"); + assert!(has_grep_match, "Should find GrepMatch"); + assert!(has_plain_text_matcher, "Should find PlainTextMatcher"); + + assert_eq!(result.files.len(), 2, "Should match exactly 2 files"); + + // Test with single pattern + let result2 = super::multi_grep_search( + files, + &["PlainTextMatcher"], + &[], + &options, + picker.cache_budget(), + None, + None, + &no_cancel, + dir.path(), + arena, + arena, + ); + assert_eq!( + result2.matches.len(), + 1, + "Single pattern should find 1 match" + ); + + // Test with empty patterns + let result3 = super::multi_grep_search( + files, + &[], + &[], + &options, + picker.cache_budget(), + None, + None, + &no_cancel, + dir.path(), + arena, + arena, + ); + assert_eq!( + result3.matches.len(), + 0, + "Empty patterns should find nothing" + ); +} + +/// Regression test for issue #407: Live grep returns duplicate results +/// when the bigram candidate bitset has trailing bits set beyond +/// `base_file_count`. The bitset is rounded up to a multiple of 64 bits +/// so any trailing bit that happens to be set (e.g. from overlay data) +/// would previously map to an overflow file index, which was then also +/// unconditionally appended by the overflow loop, producing duplicates. +#[test] +fn test_grep_no_duplicates_with_overflow_trailing_bits() { + let dir = tempfile::tempdir().unwrap(); + // Match the picker's internal dunce-canonicalize so paths passed to + // on_create_or_modify resolve back to the same base_path on Windows. + let base = crate::path_utils::canonicalize(dir.path()).unwrap(); + + // Five base files: only three contain the pattern "unicorn". + // We need some files WITHOUT the pattern so the bigrams for + // "unicorn" aren't treated as ubiquitous (≥90% of files) and + // dropped from the index during compress(). + let base_contents: &[(&str, &str)] = &[ + ("a.txt", "hello unicorn world"), + ("b.txt", "another unicorn line"), + ("c.txt", "one more unicorn here"), + ("d.txt", "nothing special in here"), + ("e.txt", "just some random content"), + ]; + for (name, content) in base_contents { + let mut f = std::fs::File::create(base.join(name)).unwrap(); + writeln!(f, "{}", content).unwrap(); + } + + let mut picker = FilePicker::new(FilePickerOptions { + base_path: base.to_str().unwrap().into(), + watch: false, + ..Default::default() + }) + .unwrap(); + picker.collect_files().unwrap(); + assert_eq!(picker.get_files().len(), 5); + + // Manually build a bigram index over the 5 base files. + let base_count = 5usize; + let consec_builder = BigramIndexBuilder::new(base_count); + let skip_builder = BigramIndexBuilder::new(base_count); + for (i, (_, content)) in base_contents.iter().enumerate() { + consec_builder.add_file_content(&skip_builder, i, content.as_bytes()); + } + let mut index = consec_builder.compress(Some(0)); + index.set_skip_index(skip_builder.compress(Some(0))); + picker.set_bigram_index(index); + + // Add three overflow files (new after the bigram index was built), + // all containing "unicorn". + for name in ["f.txt", "g.txt", "h.txt"] { + let path = base.join(name); + let mut f = std::fs::File::create(&path).unwrap(); + writeln!(f, "overflow unicorn entry").unwrap(); + drop(f); + picker.handle_create_or_modify(&path); + } + assert_eq!(picker.get_files().len(), 8); + + // Inject a trailing bit into the overlay at a file index that + // corresponds to an overflow file (i.e. >= base_file_count=5 but + // < bitset_word_size=64). Without the fix, the bigram-candidate + // merge would set this bit in the bitset, and the bitset loop would + // push files[6] while the overflow loop also appends files[5..] + // which includes files[6], producing a duplicate. + let overflow_rel = "g.txt"; // middle overflow file + let overflow_abs = picker + .get_files() + .iter() + .position(|f| f.relative_path(&picker) == overflow_rel) + .expect("overflow file should be present"); + assert!(overflow_abs >= base_count); + assert!( + overflow_abs < 64, + "index must fit in the single bitset word" + ); + + if let Some(overlay) = picker.bigram_overlay() { + overlay + .write() + .modify_file(overflow_abs, b"overflow unicorn entry"); + } + + // Run a grep for "unicorn": six files match + // (a, b, c in base + f, g, h in overflow). + let query = super::parse_grep_query("unicorn"); + let options = super::GrepSearchOptions { + max_file_size: MAX_FFFILE_SIZE, + max_matches_per_file: 0, + smart_case: true, + file_offset: 0, + page_limit: 100, + mode: super::GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: Some(std::sync::Arc::new(AtomicBool::new(false))), + }; + let result = picker.grep(&query, &options); + + // Collect the matched relative paths via the returned files list. + let mut paths: Vec = result + .files + .iter() + .map(|f| f.relative_path(&picker)) + .collect(); + paths.sort(); + + // Every file (base + overflow) should match exactly once. + let mut dedup = paths.clone(); + dedup.dedup(); + assert_eq!( + dedup, paths, + "grep must not return duplicate results (issue #407): {:?}", + paths + ); + assert_eq!( + paths, + vec!["a.txt", "b.txt", "c.txt", "f.txt", "g.txt", "h.txt"], + ); + + // And the match count must equal the number of files (one line per + // file). A duplicate entry in files_to_search would double-count + // matches for the duplicated file. + assert_eq!( + result.matches.len(), + 6, + "expected exactly one match per file, got {}", + result.matches.len() + ); +} diff --git a/crates/fff-core/src/grep/mod.rs b/crates/fff-core/src/grep/mod.rs new file mode 100644 index 0000000..eefe52b --- /dev/null +++ b/crates/fff-core/src/grep/mod.rs @@ -0,0 +1,16 @@ +mod fuzzy_grep; + +mod utils; +pub use utils::*; // contains some of the generally available functions and types + +#[allow(clippy::module_inception)] +mod grep; +pub use grep::*; + +#[cfg(feature = "definitions")] +mod classify; +#[cfg(feature = "definitions")] +pub use classify::*; + +#[cfg(test)] +mod grep_tests; diff --git a/crates/fff-core/src/grep/utils.rs b/crates/fff-core/src/grep/utils.rs new file mode 100644 index 0000000..37eb2b2 --- /dev/null +++ b/crates/fff-core/src/grep/utils.rs @@ -0,0 +1,122 @@ +use super::grep::{GrepMatch, GrepSearchOptions}; +use crate::types::FileItem; + +#[inline] +pub(crate) fn strip_line_terminators(bytes: &[u8]) -> &[u8] { + let mut len = bytes.len(); + while len > 0 && matches!(bytes[len - 1], b'\n' | b'\r') { + len -= 1; + } + &bytes[..len] +} + +/// Result of a grep search with a list of matches, list of matched files, and metadata. +#[derive(Debug, Clone, Default)] +pub struct GrepResult<'a> { + pub matches: Vec, + /// Deduplicated file references for the returned matches. + pub files: Vec<&'a FileItem>, + /// Number of files actually searched in this call. + pub total_files_searched: usize, + /// Total number of indexed files (before filtering). + pub total_files: usize, + /// Total number of searchable files (after filtering out binary, too-large, etc.). + pub filtered_file_count: usize, + /// Number of files that contained at least one match. + pub files_with_matches: usize, + /// The file offset to pass for the next page. `0` if there are no more files. + /// Callers should store this and pass it as `file_offset` in the next call. + pub next_file_offset: usize, + /// When regex mode fails to compile the pattern, the search falls back to + /// literal matching and this field contains the compilation error message. + /// The UI can display this to inform the user their regex was invalid. + pub regex_fallback_error: Option, +} + +impl<'a> GrepResult<'a> { + /// Empty result carrying only the file counts (empty query / prefilter miss) + pub(crate) fn empty(total_files: usize, filtered_file_count: usize) -> Self { + Self { + total_files, + filtered_file_count, + ..Default::default() + } + } + + pub(crate) fn collect( + per_file_results: Vec<(usize, &'a FileItem, Vec)>, + files_to_search_len: usize, + options: &GrepSearchOptions, + total_files: usize, + filtered_file_count: usize, + budget_exceeded: bool, + ) -> Self { + let page_limit = options.page_limit; + + // Each match stores a `file_index` pointing into `result_files` so that + // consumers (FFI JSON, Lua) can look up file metadata without duplicating + // it across every match from the same file + let mut result_files: Vec<&'a FileItem> = Vec::new(); + let mut all_matches: Vec = Vec::new(); + // files_consumed tracks how far into files_to_search we have advanced, + // counting every file whose results were emitted (with or without matches). + // We use the batch_idx of the last consumed file + 1, which is correct + // because per_file_results only contains files that had matches, and + // files between them that had no matches were still searched and can be + // safely skipped on the next page + let mut files_consumed: usize = 0; + + for (batch_idx, file, file_matches) in per_file_results { + // batch_idx is the 0-based position in files_to_search. + // Advance files_consumed to include this file and all no-match files before it. + files_consumed = batch_idx + 1; + + let file_result_idx = result_files.len(); + result_files.push(file); + + for mut m in file_matches { + m.file_index = file_result_idx; + if options.trim_whitespace { + m.trim_leading_whitespace(); + } + all_matches.push(m); + } + + // page_limit is a soft cap: we always finish the current file before + // stopping, so no matches are dropped. A page may return up to + // page_limit + max_matches_per_file - 1 matches in the worst case + if all_matches.len() >= page_limit { + break; + } + } + + // If no file had any match, we searched the entire slice. + if result_files.is_empty() { + files_consumed = files_to_search_len; + } + + let has_more = budget_exceeded + || (all_matches.len() >= page_limit && files_consumed < files_to_search_len); + + let next_file_offset = if has_more { + options.file_offset + files_consumed + } else { + 0 + }; + + Self { + matches: all_matches, + files_with_matches: result_files.len(), + files: result_files, + total_files_searched: files_consumed, + total_files, + filtered_file_count, + next_file_offset, + regex_fallback_error: None, + } + } +} + +pub fn has_regex_metacharacters(text: &str) -> bool { + regex::escape(text) != text +} diff --git a/crates/fff-core/src/ignore.rs b/crates/fff-core/src/ignore.rs new file mode 100644 index 0000000..ac29f52 --- /dev/null +++ b/crates/fff-core/src/ignore.rs @@ -0,0 +1,68 @@ +use std::path::Path; + +/// Directories excluded when walking a non-git root. Entries are `cfg`-gated +/// so a single iteration covers standard + platform-specific overrides. +pub(crate) const IGNORED_DIRS: &[&str] = &[ + "node_modules", + "__pycache__", + "venv", + ".venv", + // Rust (glob-only patterns for non_git_repo_overrides; is_non_code_directory + // matches the "target" component separately). + "target/debug", + "target/release", + "target/rust-analyzer", + "target/criterion", + #[cfg(target_os = "macos")] + "Library/Application Support", + #[cfg(target_os = "macos")] + "Library/Caches", + // App-group sandbox storage — used by iMessage, Photos, Notes, Calendar, + // Electron apps, etc. for SQLite-WAL, LevelDB, protobuf files. These are + // almost entirely extension-less binary files (~80k on a typical $HOME) + // that never need to appear in a fuzzy or grep search. + #[cfg(target_os = "macos")] + "Library/Group Containers", + #[cfg(target_os = "macos")] + "Library/Containers", + #[cfg(target_os = "windows")] + "bin/Debug", + #[cfg(target_os = "windows")] + "bin/Release", + #[cfg(target_os = "windows")] + "Program Files", + #[cfg(target_os = "windows")] + "Program Files (x86)", + #[cfg(target_os = "windows")] + "AppData/Local", + #[cfg(target_os = "windows")] + "AppData/Roaming", +]; + +#[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] +pub(crate) fn non_git_repo_overrides(base_path: &Path) -> Option { + use ignore::overrides::OverrideBuilder; + + let mut builder = OverrideBuilder::new(base_path); + for dir in IGNORED_DIRS { + let pattern = format!("!**/{dir}/"); + if let Err(e) = builder.add(&pattern) { + tracing::warn!("failed to add ignore pattern {pattern}: {e}"); + } + } + + builder.build().ok() +} + +pub(crate) fn is_non_code_directory(path: &Path) -> bool { + let path_str = path.as_os_str().to_str().unwrap_or(""); + IGNORED_DIRS.iter().any(|&dir| { + #[cfg(target_os = "windows")] + let dir = dir.replace('/', std::path::MAIN_SEPARATOR_STR); + #[cfg(target_os = "windows")] + return path_str.contains(dir.as_str()); + + #[cfg(not(target_os = "windows"))] + path_str.contains(dir) + }) +} diff --git a/crates/fff-core/src/lib.rs b/crates/fff-core/src/lib.rs new file mode 100644 index 0000000..434fcd8 --- /dev/null +++ b/crates/fff-core/src/lib.rs @@ -0,0 +1,161 @@ +//! # FFF Search — High-performance file finder core +//! +//! This crate provides the core search engine for [FFF (Fast File Finder)](https://github.com/dmtrKovalenko/fff.nvim). +//! It includes filesystem indexing with real-time watching, fuzzy matching powered +//! by [frizbee](https://docs.rs/neo_frizbee), frecency scoring backed by LMDB, +//! and multi-mode grep search. +//! +//! > [!Important performance information] +//! > For the most optimized fff build use `zlob` feature. It requires zig v0.16.0 to be installed on the machine. +//! +//! ## Architecture +//! +//! - [`file_picker::FilePicker`] — Main entry point. Indexes a directory tree in a +//! background thread, maintains a sorted file list, watches the filesystem for +//! changes, and performs fuzzy search with frecency-weighted scoring. +//! - [`frecency::FrecencyTracker`] — LMDB-backed database that tracks file access +//! and modification patterns for intelligent result ranking. +//! - [`query_tracker::QueryTracker`] — Tracks search query history and provides +//! "combo-boost" scoring for repeatedly matched files. +//! - [`grep`] — Live grep search supporting regex, plain-text, and fuzzy modes +//! with optional constraint filtering. +//! - [`git`] — Git status caching and repository detection. +//! +//! ## Shared State +//! +//! [`SharedFilePicker`], [`SharedFrecency`], and [`SharedQueryTracker`] are +//! newtype wrappers around `Arc>>` for thread-safe shared +//! access. They provide `read()` / `write()` methods with built-in error +//! conversion and convenience helpers like `wait_for_scan()`. +//! +//! ## Quick Start +//! +//! ``` +//! use fff_search::file_picker::FilePicker; +//! use fff_search::frecency::FrecencyTracker; +//! use fff_search::query_tracker::QueryTracker; +//! use fff_search::{ +//! FFFMode, FilePickerOptions, FuzzySearchOptions, PaginationArgs, QueryParser, +//! SharedFrecency, SharedFilePicker, SharedQueryTracker, +//! }; +//! +//! let shared_picker = SharedFilePicker::default(); +//! let shared_frecency = SharedFrecency::default(); +//! let shared_query_tracker = SharedQueryTracker::default(); +//! +//! let tmp = std::env::temp_dir().join("fff-doctest"); +//! std::fs::create_dir_all(&tmp).unwrap(); +//! +//! // 1. Optionally initialize frecency and query tracker databases +//! let frecency = FrecencyTracker::open(tmp.join("frecency"))?; +//! shared_frecency.init(frecency)?; +//! +//! let query_tracker = QueryTracker::open(tmp.join("queries"))?; +//! shared_query_tracker.init(query_tracker)?; +//! +//! // 2. Init the file picker (spawns background scan + watcher) +//! FilePicker::new_with_shared_state( +//! shared_picker.clone(), +//! shared_frecency.clone(), +//! FilePickerOptions { +//! base_path: ".".into(), +//! mode: FFFMode::Ai, +//! ..Default::default() +//! }, +//! )?; +//! +//! // 3. Wait for scan +//! shared_picker.wait_for_scan(std::time::Duration::from_secs(10)); +//! +//! // 4. Search: lock the picker and query tracker +//! let picker_guard = shared_picker.read()?; +//! let picker = picker_guard.as_ref().unwrap(); +//! let qt_guard = shared_query_tracker.read()?; +//! +//! // 5. Parse the query and perform fuzzy search +//! let parser = QueryParser::default(); +//! let query = parser.parse("lib.rs"); +//! +//! let results = picker.fuzzy_search( +//! &query, +//! qt_guard.as_ref(), +//! FuzzySearchOptions { +//! max_threads: 0, +//! current_file: None, +//! pagination: PaginationArgs { offset: 0, limit: 50 }, +//! ..Default::default() +//! }, +//! ); +//! +//! assert!(results.total_matched > 0); +//! assert!(results.items.first().unwrap().relative_path(picker).ends_with("lib.rs")); +//! +//! let _ = std::fs::remove_dir_all(&tmp); +//! # Ok::<(), Box>(()) +//! ``` + +#[cfg(not(any(feature = "ripgrep", feature = "zlob")))] +compile_error!( + "fff-search requires either the `ripgrep` (default) or `zlob` feature. \ + Enable one, e.g. `--features ripgrep` or `--features zlob`." +); + +/// Primary entry points with thread-safe [`SharedFilePicker`](shared::FilePicker) instance +pub mod shared; +pub use shared::*; + +/// Core file picker single thread: filesystem indexing, background watching, and fuzzy search. +/// See [`FilePicker`](file_picker::FilePicker) for the main entry point. +pub mod file_picker; +pub use file_picker::*; + +/// Database-backed persistence: frecency, query history, LMDB plumbing. +pub mod dbs; +pub use dbs::*; + +/// Git status caching and repository detection utilities. +pub mod git; + +/// Live grep search with regex, plain-text, and fuzzy matching modes. +pub mod grep; +pub use grep::*; + +/// Tracing/logging initialization and panic hook setup. +pub mod log; + +/// Various path utils might be handy for you to work with fff paths +pub mod path_utils; + +/// Core data types shared across the crate. +pub mod types; +pub use types::*; + +pub mod constants; + +// ================================== +// these are public only for benchmarks, no backward compatibility guaranteed +#[doc(hidden)] +pub mod bigram_filter; +#[doc(hidden)] +pub mod simd_string_utils; +// ================================== + +mod background_watcher; +mod constraints; +mod error; +mod git_status_worker; +mod ignore; +mod scan; +mod score; +mod sort_buffer; + +pub(crate) mod bigram_query; +pub(crate) mod parallelism; +pub(crate) mod simd_path; +pub(crate) mod stable_vec; +pub(crate) mod walk; + +// fff error +pub use error::{Error, Result}; + +pub use fff_query_parser::*; diff --git a/crates/fff-core/src/log.rs b/crates/fff-core/src/log.rs new file mode 100644 index 0000000..1e58648 --- /dev/null +++ b/crates/fff-core/src/log.rs @@ -0,0 +1,281 @@ +use std::io; +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; +use tracing_appender::non_blocking; +use tracing_subscriber::fmt::format::FmtSpan; +use tracing_subscriber::{EnvFilter, fmt, prelude::*}; + +// Set once on first init_tracing; doubles as the init-once gate. +static LOG_FILE_PATH: OnceLock = OnceLock::new(); +static CRASH_HOOKS: OnceLock<()> = OnceLock::new(); + +fn write_crash_report(header: &str, body: &str) { + let msg = format!( + "\n=== CRASH (this might NOT BE fff related) {} ===\n{}\n=== CRASH END {} ===\n", + header, body, header + ); + let _ = std::io::Write::write_all(&mut std::io::stderr(), msg.as_bytes()); + if let Some(path) = LOG_FILE_PATH.get() { + let _ = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + .and_then(|mut f| std::io::Write::write_all(&mut f, msg.as_bytes())); + } +} + +// SIGSEGV handler writes a banner to a pre-opened fd (open(2) inside a signal +// handler is unsafe due to path-resolution allocs). Unix only. +#[cfg(unix)] +mod sigsegv { + use std::os::fd::IntoRawFd; + use std::path::Path; + use std::sync::atomic::{AtomicI32, Ordering}; + + static LOG_FD: AtomicI32 = AtomicI32::new(-1); + + // Must `create(true)` — this runs before init_tracing opens/creates the + // writer file, so an append-only open on a non-existent path silently + // fails, LOG_FD stays -1, and the SIGSEGV banner never reaches the log. + pub fn set_log_fd(path: &Path) { + if let Ok(file) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + { + let prev = LOG_FD.swap(file.into_raw_fd(), Ordering::Relaxed); + if prev >= 0 { + unsafe { libc::close(prev) }; + } + } + } + + // Body must be async-signal-safe: write(2), atomic load, signal(2). Nothing else. + fn handler(_info: &libc::siginfo_t) { + const BANNER: &[u8] = b"\n=== CRASH SIGSEGV (fff) ===\n\ + fff.nvim's rust extension hit a segfault and is about to die.\n\ + Please file the bug at https://github.com/dmtrKovalenko/fff/issues with this banner attached.\n\ + === CRASH END SIGSEGV ===\n"; + unsafe { + libc::write(2, BANNER.as_ptr().cast(), BANNER.len()); + let log_fd = LOG_FD.load(Ordering::Relaxed); + if log_fd >= 0 { + libc::write(log_fd, BANNER.as_ptr().cast(), BANNER.len()); + } + // Reset to default so handler return → kernel kills us instead of + // re-running the faulting instruction in an infinite loop. + libc::signal(libc::SIGSEGV, libc::SIG_DFL); + } + } + + pub fn install() { + // signal-hook-registry chains to LuaJIT's prior handler automatically. + unsafe { + let _ = signal_hook_registry::register_unchecked(libc::SIGSEGV, handler); + } + } +} + +#[cfg(not(unix))] +mod sigsegv { + use std::path::Path; + pub fn set_log_fd(_path: &Path) {} + pub fn install() {} +} + +pub fn install_panic_hook() { + CRASH_HOOKS.get_or_init(install_crash_hooks); +} + +fn install_crash_hooks() { + let default_panic = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |panic_info| { + let message = if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic_info.payload().downcast_ref::() { + s.clone() + } else { + "Unknown panic payload".to_string() + }; + + let location = panic_info + .location() + .map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column())) + .unwrap_or_else(|| "unknown location".to_string()); + + tracing::error!( + panic.message = %message, + panic.location = %location, + "PANIC occurred in FFF" + ); + + write_crash_report( + "RUST PANIC", + &format!("Message: {}\nLocation: {}", message, location), + ); + default_panic(panic_info); + })); + + sigsegv::install(); +} + +/// Parse a log level string into a `tracing::Level`. +pub fn parse_log_level(level: Option<&str>) -> tracing::Level { + match level.as_ref().map(|s| s.trim().to_lowercase()).as_deref() { + Some("trace") => tracing::Level::TRACE, + Some("debug") => tracing::Level::DEBUG, + Some("info") => tracing::Level::INFO, + Some("warn") => tracing::Level::WARN, + Some("error") => tracing::Level::ERROR, + _ => tracing::Level::INFO, + } +} + +/// Default retention: how many prior nvim sessions' log files to keep. +const DEFAULT_RETAIN_RUNS: usize = 20; + +pub fn generate_trace_id() -> String { + use std::sync::atomic::{AtomicU64, Ordering}; + static TRACE_COUNTER: AtomicU64 = AtomicU64::new(0); + + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos() as u64) + .unwrap_or(0); + + let pid = std::process::id() as u64; + let counter = TRACE_COUNTER.fetch_add(1, Ordering::Relaxed); + + // very simple hash functions helps to distinguish trace ids visually + let id = nanos ^ (pid.wrapping_mul(0x9E37_79B9_7F4A_7C15)) ^ (counter << 32); + format!("{:016x}", id) +} + +pub fn trace_span(trace_id: &str, label: &'static str) -> tracing::Span { + tracing::info_span!("fff.trace", trace_id = trace_id, label = label) +} + +fn unix_secs() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0) +} + +fn session_path_from_hint(hint: &Path) -> PathBuf { + let stem = hint.file_stem().and_then(|s| s.to_str()).unwrap_or("fff"); + let ext = hint.extension().and_then(|e| e.to_str()).unwrap_or("log"); + let parent = hint.parent().unwrap_or_else(|| Path::new(".")); + parent.join(format!( + "{stem}+{ts}+{pid}.{ext}", + ts = unix_secs(), + pid = std::process::id(), + )) +} + +fn rotate_logs(dir: &Path, stem: &str, ext: &str, retain_runs: usize) { + let Ok(entries) = std::fs::read_dir(dir) else { + return; + }; + let prefix = format!("{stem}+"); + let suffix = format!(".{ext}"); + + let mut files: Vec<(std::time::SystemTime, PathBuf)> = entries + .filter_map(|res| { + let entry = res.ok()?; + let name = entry.file_name(); + let name = name.to_str()?; + if !name.starts_with(&prefix) || !name.ends_with(&suffix) { + return None; + } + + let mtime = entry.metadata().ok()?.modified().ok()?; + Some((mtime, entry.path())) + }) + .collect(); + + if files.len() <= retain_runs { + return; + } + // Newest first, then drop everything past retain_runs. + files.sort_by_key(|(mtime, _)| std::cmp::Reverse(*mtime)); + for (_, path) in files.into_iter().skip(retain_runs) { + let _ = std::fs::remove_file(path); + } +} + +/// `log_file_path` is a path-shape hint. Each call writes a unique sibling +/// `++.` so concurrent processes never collide. +/// Returns the absolute path of the session file. +pub fn init_tracing( + log_file_path: &str, + log_level: Option<&str>, + retain_runs: Option, +) -> Result { + let hint = Path::new(log_file_path); + let session_dir = hint + .parent() + .unwrap_or_else(|| Path::new(".")) + .to_path_buf(); + std::fs::create_dir_all(&session_dir)?; + + let session_path = session_path_from_hint(hint); + + // First init wins; repeat callers no-op and return the original path. + if LOG_FILE_PATH.set(session_path.clone()).is_err() { + return Ok(LOG_FILE_PATH + .get() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default()); + } + + sigsegv::set_log_fd(&session_path); + install_panic_hook(); + + let stem = hint.file_stem().and_then(|s| s.to_str()).unwrap_or("fff"); + let ext = hint.extension().and_then(|e| e.to_str()).unwrap_or("log"); + rotate_logs( + &session_dir, + stem, + ext, + retain_runs.unwrap_or(DEFAULT_RETAIN_RUNS), + ); + + let writer_file = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&session_path)?; + + // we intinionally leark the guard we don't ever want to stop logging + let (non_blocking_appender, guard) = non_blocking(writer_file); + Box::leak(Box::new(guard)); + + let subscriber = tracing_subscriber::registry() + .with( + fmt::layer() + .with_writer(non_blocking_appender) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(true) + .with_ansi(false) + .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE), + ) + .with( + EnvFilter::builder() + .with_default_directive(parse_log_level(log_level).into()) + .from_env_lossy(), + ); + + if let Err(e) = tracing::subscriber::set_global_default(subscriber) { + eprintln!("Failed to set tracing subscriber: {}", e); + } else { + tracing::info!( + "FFF tracing initialized: {} (pid={}, retain_runs={})", + session_path.display(), + std::process::id(), + retain_runs.unwrap_or(DEFAULT_RETAIN_RUNS), + ); + } + + Ok(session_path.to_string_lossy().into_owned()) +} diff --git a/crates/fff-core/src/parallelism.rs b/crates/fff-core/src/parallelism.rs new file mode 100644 index 0000000..7f6c3bb --- /dev/null +++ b/crates/fff-core/src/parallelism.rs @@ -0,0 +1,84 @@ +//! Dedicated rayon pools. The global pool spans every logical core, which +//! oversubscribes asymmetric chips (Apple P+E): E-cores are ~2× slower and +//! `open()` contends on a per-VFS lock past P-core count, so a larger pool is +//! slower on file-heavy work. + +use std::sync::LazyLock; + +/// Dedicated thread pool for background work (scan, warmup, bigram build). +pub static BACKGROUND_THREAD_POOL: LazyLock = LazyLock::new(|| { + let total = std::thread::available_parallelism() + .map(|p| p.get()) + .unwrap_or(4); + + // Background work is mostly syscall-bound; halving parallelism leaves + // cores for search/UI at negligible throughput cost. + let bg_threads = (total / 2).max(2); + rayon::ThreadPoolBuilder::new() + .num_threads(bg_threads) + .thread_name(|i| format!("fff-bg-{i}")) + .start_handler(|_| { + // QoS pin keeps workers on P-cores; the kernel otherwise drifts + // them to ~2× slower E-cores. + #[cfg(target_os = "macos")] + unsafe { + let _ = libc::pthread_set_qos_class_self_np( + libc::qos_class_t::QOS_CLASS_USER_INITIATED, + 0, + ); + } + }) + .build() + .expect("failed to create background rayon pool") +}); + +/// Physical performance-core count via sysctl, falling back to logical cores. +/// On a 12P+4E M4 Max, grep runs 16t=6.2s vs 13t=4.9s — fewer threads win. +#[cfg(target_os = "macos")] +fn performance_core_count() -> usize { + let mut count: libc::c_int = 0; + let mut size = std::mem::size_of::(); + let name = c"hw.perflevel0.physicalcpu"; + let ok = unsafe { + libc::sysctlbyname( + name.as_ptr(), + &mut count as *mut _ as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) + }; + if ok == 0 && count > 0 { + count as usize + } else { + std::thread::available_parallelism() + .map(|p| p.get()) + .unwrap_or(4) + } +} + +/// Pool for grep content search: P-core sized and QoS-pinned on macOS, full +/// parallelism elsewhere. Avoids E-core drag and VFS-lock contention. +pub static SEARCH_THREAD_POOL: LazyLock = LazyLock::new(|| { + #[cfg(target_os = "macos")] + let threads = performance_core_count(); + #[cfg(not(target_os = "macos"))] + let threads = std::thread::available_parallelism() + .map(|p| p.get()) + .unwrap_or(4); + + rayon::ThreadPoolBuilder::new() + .num_threads(threads) + .thread_name(|i| format!("fff-search-{i}")) + .start_handler(|_| { + #[cfg(target_os = "macos")] + unsafe { + let _ = libc::pthread_set_qos_class_self_np( + libc::qos_class_t::QOS_CLASS_USER_INITIATED, + 0, + ); + } + }) + .build() + .expect("failed to create search rayon pool") +}); diff --git a/crates/fff-core/src/path_utils.rs b/crates/fff-core/src/path_utils.rs new file mode 100644 index 0000000..a63d655 --- /dev/null +++ b/crates/fff-core/src/path_utils.rs @@ -0,0 +1,211 @@ +use std::path::{Path, PathBuf}; + +#[cfg(windows)] +pub fn canonicalize(path: impl AsRef) -> std::io::Result { + dunce::canonicalize(path) +} + +#[cfg(not(windows))] +pub fn canonicalize(path: impl AsRef) -> std::io::Result { + std::fs::canonicalize(path) +} + +/// The index stores relative paths with `/` on every platform. These helpers +/// convert between that canonical form and the OS-native separator, and are +/// no-ops on non-Windows where `/` is already native. + +/// Fold a relative path to the canonical `/` form (no-op off Windows). +#[cfg(windows)] +pub fn to_canonical_slashes(rel: &str) -> std::borrow::Cow<'_, str> { + if rel.contains('\\') { + std::borrow::Cow::Owned(rel.replace('\\', "/")) + } else { + std::borrow::Cow::Borrowed(rel) + } +} + +#[cfg(not(windows))] +#[inline] +pub fn to_canonical_slashes(rel: &str) -> std::borrow::Cow<'_, str> { + std::borrow::Cow::Borrowed(rel) +} + +/// Rewrite canonical `/` bytes to the OS-native separator in place (no-op off +/// Windows). Used at OS/state boundaries (absolute-path reconstruction). +#[cfg(windows)] +#[inline] +pub fn nativize_slashes_in_place(bytes: &mut [u8]) { + for b in bytes { + if *b == b'/' { + *b = b'\\'; + } + } +} + +#[cfg(not(windows))] +#[inline] +pub fn nativize_slashes_in_place(_bytes: &mut [u8]) {} + +/// Git requires a normalized forward-slashed paths on windows +#[cfg(windows)] +pub fn normalize(path: PathBuf) -> PathBuf { + let as_str = path.to_string_lossy(); + let with_backslashes: String = as_str.replace('/', "\\"); + let buf = PathBuf::from(with_backslashes); + dunce::canonicalize(&buf).unwrap_or(buf) +} + +#[cfg(not(windows))] +pub fn normalize(path: PathBuf) -> PathBuf { + path +} + +#[cfg(windows)] +pub fn expand_tilde(path: &str) -> PathBuf { + return PathBuf::from(path); +} + +#[cfg(not(windows))] +pub fn expand_tilde(path: &str) -> PathBuf { + if let Some(stripped) = path.strip_prefix("~/") + && let Some(home_dir) = dirs::home_dir() + { + return home_dir.join(stripped); + } + + PathBuf::from(path) +} + +/// Calculate distance penalty based on directory proximity. +/// Returns a negative penalty score based on how far the candidate is from the current file. +/// +/// `candidate_dir` is the directory portion of the candidate path (e.g. `"src/components/"`). +/// It may have a trailing `/` which is stripped internally. +/// +/// Zero-allocation: walks both directory part iterators in lockstep. +pub fn calculate_distance_penalty(current_file: Option<&str>, candidate_dir: &str) -> i32 { + let Some(current_path) = current_file else { + return 0; + }; + + let current_dir = Path::new(current_path).parent().unwrap_or(Path::new("")); + let candidate = Path::new(candidate_dir); + + if current_dir == candidate { + return 0; + } + + let mut current_parts = current_dir.components(); + let mut candidate_parts = candidate.components(); + + let mut common_len = 0usize; + let mut current_total = 0usize; + + loop { + match (current_parts.next(), candidate_parts.next()) { + (Some(a), Some(b)) => { + current_total += 1; + if a == b { + common_len += 1; + } else { + current_total += current_parts.count(); + break; + } + } + (Some(_), None) => { + current_total += 1 + current_parts.count(); + break; + } + (None, _) => { + break; + } + } + } + + let depth_from_common = current_total - common_len; + if depth_from_common == 0 { + return 0; + } + + (-(depth_from_common as i32)).max(-20) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(not(target_family = "windows"))] + fn test_calculate_distance_penalty() { + // candidate_dir is now just the directory portion (with or without trailing /) + assert_eq!(calculate_distance_penalty(None, "examples/user/test/"), 0); + // Same directory + assert_eq!( + calculate_distance_penalty(Some("examples/user/test/main.rs"), "examples/user/test/"), + 0 + ); + // + // One level apart + assert_eq!( + calculate_distance_penalty( + Some("examples/user/test/subdir/file.rs"), + "examples/user/test/" + ), + -1 + ); + // + // Different subdirectories (same parent) + assert_eq!( + calculate_distance_penalty( + Some("examples/user/test/dir1/file.rs"), + "examples/user/test/dir2/" + ), + -1 + ); + + assert_eq!( + calculate_distance_penalty( + Some("examples/audio-announce/src/lib/audio-announce.rs"), + "examples/audio-announce/src/" + ), + -1 + ); + + assert_eq!( + calculate_distance_penalty( + Some("examples/audio-announce/src/audio-announce.rs"), + "examples/pixel/src/" + ), + -2 + ); + + // Root level files (empty dir) + assert_eq!(calculate_distance_penalty(Some("main.rs"), ""), 0); + } + + #[test] + #[cfg(target_family = "windows")] + fn distance_penalty_works_on_windows() { + assert_eq!( + calculate_distance_penalty(None, "examples\\user\\test\\"), + 0 + ); + // Same directory + assert_eq!( + calculate_distance_penalty( + Some("examples\\user\\test\\main.rs"), + "examples\\user\\test\\" + ), + 0 + ); + // + // One level apart + assert_eq!( + calculate_distance_penalty( + Some("examples\\user\\test\\subdir\\file.rs"), + "examples\\user\\test\\" + ), + -1 + ); + } +} diff --git a/crates/fff-core/src/scan.rs b/crates/fff-core/src/scan.rs new file mode 100644 index 0000000..3fe6fa3 --- /dev/null +++ b/crates/fff-core/src/scan.rs @@ -0,0 +1,404 @@ +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +use tracing::{error, info}; + +use crate::FileSync; +use crate::background_watcher::BackgroundWatcher; +use crate::bigram_filter::{build_bigram_index, sniff_binary_for_non_indexable}; +use crate::error::Error; +use crate::file_picker::FFFMode; +use crate::parallelism::BACKGROUND_THREAD_POOL; +use crate::shared::{SharedFilePicker, SharedFrecency}; +use crate::types::ContentCacheBudget; + +#[derive(Clone, Default)] +pub(crate) struct ScanSignals { + /// Set to `true` while any scan phase is running + pub(crate) scanning: Arc, + /// Set to `true` once the filesystem watcher has been installed + pub(crate) watcher_ready: Arc, + /// Indicates that that owning picker was requested to shut down + pub(crate) cancelled: Arc, + /// Used to resolve conflicts if multiple rescans were triggered in a queue + pub(crate) rescan_pending: Arc, + /// Set by `post_scan_snapshot`, cleared by `PostScanSnapshot::drop`. + /// DO NOT set or clear this manually — it is managed exclusively by the + /// PostScanSnapshot lifecycle. + pub(crate) post_scan_indexing_active: Arc, +} + +/// Which optional phases a scan should run. +#[derive(Clone, Copy, Default, Debug)] +pub(crate) struct ScanConfig { + pub(crate) warmup: bool, + pub(crate) content_indexing: bool, + pub(crate) watch: bool, + pub(crate) auto_cache_budget: bool, + pub(crate) install_watcher: bool, + pub(crate) follow_symlinks: bool, + pub(crate) enable_fs_root_scanning: bool, + pub(crate) enable_home_dir_scanning: bool, +} + +/// A fully-configured scan job ready to run on a background thread. +/// +/// Build with [`ScanJob::from_picker`] (reads all state from the +/// current `FilePicker`) or [`ScanJob::initial`] (for the bootstrap +/// scan, before the picker is published to `SharedPicker`). +pub(crate) struct ScanJob { + shared_picker: SharedFilePicker, + shared_frecency: SharedFrecency, + base_path: PathBuf, + mode: FFFMode, + signals: ScanSignals, + config: ScanConfig, + /// Walker-maintained counter backing `get_scan_progress` on the UI + /// side. Reset to 0 at scan start, incremented per-file by the + /// walker. Shared `Arc` so the UI polls the same atomic. + scanned_files_counter: Arc, + trace_span: tracing::Span, +} + +impl ScanJob { + pub fn new_rescan( + shared_picker: &SharedFilePicker, + shared_frecency: &SharedFrecency, + ) -> Result, Error> { + let guard = shared_picker.read()?; + let picker = guard.as_ref().ok_or(Error::FilePickerMissing)?; + + if picker.is_scan_active() + || picker + .signals + .post_scan_indexing_active + .load(Ordering::Acquire) + { + return Ok(None); + } + + let mode = picker.mode(); + let signals = picker.scan_signals(); + let scanned_files_counter = picker.scanned_files_counter(); + let base_path = picker.base_path().to_path_buf(); + let trace_span = picker.trace_span(); + + let new_scan_config = ScanConfig { + warmup: picker.has_mmap_cache(), + content_indexing: picker.has_content_indexing(), + watch: picker.has_watcher(), + auto_cache_budget: !picker.has_explicit_cache_budget(), + install_watcher: false, // the watcher is independent of rescan, it is not restarting EVER + follow_symlinks: picker.follows_symlinks(), + enable_fs_root_scanning: picker.fs_root_scanning_enabled(), + enable_home_dir_scanning: picker.home_dir_scanning_enabled(), + }; + + drop(guard); // just a sanity check + + Ok(Some(Self { + mode, + signals, + base_path, + scanned_files_counter, + config: new_scan_config, + shared_picker: shared_picker.clone(), + shared_frecency: shared_frecency.clone(), + trace_span, + })) + } + + #[allow(clippy::too_many_arguments)] + pub fn new_initial( + shared_picker: SharedFilePicker, + shared_frecency: SharedFrecency, + base_path: PathBuf, + mode: FFFMode, + signals: ScanSignals, + scanned_files_counter: Arc, + trace_span: tracing::Span, + config: ScanConfig, + ) -> Self { + Self { + shared_picker, + shared_frecency, + base_path, + mode, + signals, + scanned_files_counter, + config, + trace_span, + } + } + + /// Run the job on `BACKGROUND_THREAD_POOL`. Returns immediately. + /// + /// Routed through the pool — and not a fresh `std::thread::spawn` — so the + /// orchestrator inherits rayon's QoS pin (USER_INITIATED). Without that + /// pin, an interactive nvim's USER_INTERACTIVE main thread spawns a child + /// at lower QoS, the walker's Zig worker pool inherits the demotion, and + /// the kernel drifts those workers onto E-cores. On chromium that turns a + /// ~800 ms walk into ~3 s. + pub fn spawn(self) { + self.signals.scanning.store(true, Ordering::Release); + let span = self.trace_span.clone(); + BACKGROUND_THREAD_POOL.spawn(move || { + let _g = span.enter(); + self.run(); + }); + } + + fn run(self) { + let Self { + shared_picker, + shared_frecency, + base_path, + mode, + signals, + scanned_files_counter, + config, + trace_span: _, + } = self; + + let _scanning = ScanningGuard::new(&signals, config.install_watcher); + scanned_files_counter.store(0, Ordering::Relaxed); + + // 1. Walk the file system and collect the list of files + let git_workdir = FileSync::discover_git_workdir(&base_path); + let sync = match FileSync::walk_filesystem( + &base_path, + git_workdir.clone(), + &scanned_files_counter, + &shared_frecency, + mode, + config.follow_symlinks, + ) { + Ok(sync) => sync, + Err(e) => { + error!(?e, "scan walk failed"); + return; + } + }; + + // 2. Populate the file list + let git_status_worker; + if let Ok(mut guard) = shared_picker.write() + && let Some(picker) = guard.as_mut() + { + if signals.cancelled.load(Ordering::Acquire) { + info!("scan cancelled between walk and commit, discarding"); + return; + } + + let live_count = sync.live_count; + picker.commit_new_sync(sync); + git_status_worker = Arc::clone(&picker.git_status_worker); + + if config.auto_cache_budget && !picker.has_explicit_cache_budget() { + picker.set_cache_budget(ContentCacheBudget::new_for_repo(live_count)); + } + } else { + error!("failed to install scan results into picker"); + return; + } + + // Spawn the git status worker once. BUG PINNNING. If the user initiated git in the folder + // which is a real use case we need to have a way to start the git worker background thread dynamically + if git_workdir.is_some() && !signals.cancelled.load(Ordering::Acquire) { + git_status_worker.spawn_once(shared_picker.weaken(), shared_frecency.clone()); + git_status_worker.request_full_rescan(); // this runs anyway + } + + // BUG pinning: take the snapshot *before* the storing the scan=true, otherwise there is a tiny + // race window when there scanned is set to true, but `post_scan_indexing_active` flag is `false` + let snapshot = if !signals.cancelled.load(Ordering::Acquire) { + shared_picker.read().ok().and_then(|guard| { + guard + .as_ref() + .and_then(|picker| unsafe { picker.post_scan_snapshot() }) + }) + } else { + None + }; + + signals.scanning.store(false, Ordering::Relaxed); // file are searchable + + // in case we do a rescan, we have to resubscribe a watcher to the new set of directories + // all the already watched directories are not going to be resubscribed (this is internally deduped) + if !config.install_watcher && !signals.cancelled.load(Ordering::Acquire) { + rescubscribe_watcher_post_scan(&shared_picker); + } + + // 3. Runs post scna in parallel with git status collection + if !signals.cancelled.load(Ordering::Acquire) + && let Some(snap) = snapshot.as_ref() + { + Self::run_post_scan(&shared_picker, &signals, &config, snap); + } + + drop(snapshot); // SNAPSHOT SHOULD NOT BE USED AFTER THIS POINT + + // 5. Install filesystem watcher (initial scan only). + if config.install_watcher && config.watch && !signals.cancelled.load(Ordering::Acquire) { + let shared_picker: &SharedFilePicker = &shared_picker; + let shared_frecency: &SharedFrecency = &shared_frecency; + let base_path: &std::path::Path = &base_path; + + match BackgroundWatcher::new( + base_path.to_path_buf(), + git_workdir, + shared_picker.clone(), + shared_frecency.clone(), + mode, + config.enable_fs_root_scanning, + config.enable_home_dir_scanning, + git_status_worker, + tracing::Span::current(), + ) { + Ok(watcher) => { + if let Ok(mut guard) = shared_picker.write() + && let Some(picker) = guard.as_mut() + { + picker.background_watcher = Some(watcher); + } + } + Err(e) => error!(?e, "failed to initialize background watcher"), + }; + } + + // 6. Drain any rescan that arrived while we were busy. + // if user initiated a new rescan we had no way to cancel current post scan, so do it again + if !signals.cancelled.load(Ordering::Acquire) + && signals.rescan_pending.swap(false, Ordering::AcqRel) + { + match Self::new_rescan(&shared_picker, &shared_frecency) { + Ok(Some(follow_up)) => { + info!("Rescheduling deferred rescan after current scan finished"); + follow_up.spawn(); + } + Ok(None) => { + // this should be practically impossible because we do not have any + // queue, but if somehow a new rescan was triggered JUST IN THIS MOMENT + // just ignore it because the ongoing one is fresh enough + tracing::warn!("Post scan was re-triggered, ignoring"); + } + Err(e) => { + error!(?e, "Failed to reschedule deferred rescan"); + } + } + } + } + + /// THIS IS VERY VERY IMPORTANT THAT ANYTHING INSIDE THIS FUNCTION TO NOT READ ANYTHING CLEARABLE OUTSIDE + /// this is a very silly off lock implementation that actually matters, and that's why it is crafted + /// to never read anything from the picker, it can only WRITE information using single instructions + /// + /// Things that are safe and immutable - file list, indexes of files, paths, and signals. + #[tracing::instrument(skip_all, fields(warmup = ?config.warmup, indexing = ?config.content_indexing))] + fn run_post_scan( + shared_picker: &SharedFilePicker, + signals: &ScanSignals, + config: &ScanConfig, + unsafe_snapshot: &crate::file_picker::PostScanUnsafeSnapshot, + ) { + let Some(arena) = unsafe_snapshot + .arena // we are never touching overlays so this arena is always correct + .as_ref() + .map(|s| s.as_arena_ptr()) + else { + tracing::error!("Failed to run post scan: arena is invalid"); + return; + }; + + let files: &[crate::types::FileItem] = &unsafe_snapshot.files[..unsafe_snapshot.base_count]; + if signals.cancelled.load(Ordering::Acquire) { + return; + } + + if config.content_indexing { + let indexable_count = unsafe_snapshot.indexable_count.min(files.len()); + let (indexable_files, non_indexable_files) = files.split_at(indexable_count); + let index = build_bigram_index(indexable_files, &unsafe_snapshot.base_path, arena); + + if let Ok(mut guard) = shared_picker.write() + && let Some(picker) = guard.as_mut() + { + picker.set_bigram_index(index); + } + + // Bigram only sniffs files <= MAX_INDEXABLE_FILE_SIZE; large + // unknown-extension binaries slip past it and would otherwise be + // grep-able as text. Cheap header sniff catches those. + if !signals.cancelled.load(Ordering::Acquire) { + sniff_binary_for_non_indexable( + non_indexable_files, + &unsafe_snapshot.base_path, + arena, + &signals.cancelled, + ); + } + } else { + // this potentially a long running as we are not parallelizing it but it's okay + sniff_binary_for_non_indexable( + files, + &unsafe_snapshot.base_path, + arena, + &signals.cancelled, + ); + } + + // TODO Skipped as potentially unsafe - figure this out later + // if config.warmup && !signals.cancelled.load(Ordering::Acquire) { + // warmup_mmaps(files, budget, &unsafe_snapshot.base_path, arena); + // } + } +} + +/// RAII helper that flips the `scanning` signal on construction and +/// resets it on drop (so early-returns can't leave it stuck on `true`). +/// Also drives the `watcher_ready` signal on the initial-scan path. +struct ScanningGuard<'a> { + signals: &'a ScanSignals, + release_watcher_ready_on_drop: bool, +} + +impl<'a> ScanningGuard<'a> { + fn new(signals: &'a ScanSignals, release_watcher_ready_on_drop: bool) -> Self { + signals.scanning.store(true, Ordering::Relaxed); + Self { + signals, + release_watcher_ready_on_drop, + } + } +} + +impl Drop for ScanningGuard<'_> { + fn drop(&mut self) { + self.signals.scanning.store(false, Ordering::Relaxed); + if self.release_watcher_ready_on_drop { + self.signals.watcher_ready.store(true, Ordering::Release); + } + } +} + +/// If the scan encounters new directories created we have to add them to the watch list +/// this is fine because the watcher does deduplicate the entries and doesn't add a lot of +/// garbage notify watchers / fs events streams +#[tracing::instrument(skip_all)] +fn rescubscribe_watcher_post_scan(shared_picker: &SharedFilePicker) { + let Ok(guard) = shared_picker.read() else { + return; + }; + let Some(picker) = guard.as_ref() else { + return; + }; + let Some(watcher) = picker.background_watcher.as_ref() else { + return; + }; + + picker.for_each_dir(|dir: &std::path::Path| { + watcher.request_watch_dir(dir.to_path_buf()); + std::ops::ControlFlow::Continue(()) + }); +} diff --git a/crates/fff-core/src/score.rs b/crates/fff-core/src/score.rs new file mode 100644 index 0000000..f757f48 --- /dev/null +++ b/crates/fff-core/src/score.rs @@ -0,0 +1,1604 @@ +use crate::{ + constraints::apply_constraints, + git::is_modified_status, + path_utils::calculate_distance_penalty, + simd_path::{ArenaPtr, MAX_PATH_CHUNKS}, + sort_buffer::{sort_by_key_with_buffer, sort_with_buffer}, + types::{DirItem, FileItem, Score, ScoringContext}, +}; +use fff_query_parser::FuzzyQuery; +use neo_frizbee::Scoring; +use rayon::prelude::*; +use std::{borrow::Cow, path::MAIN_SEPARATOR}; + +enum FileItems<'a> { + All(&'a [FileItem]), + Filtered(Vec<&'a FileItem>), +} + +impl<'a> FileItems<'a> { + #[inline] + fn index(&self, index: usize) -> &'a FileItem { + match self { + FileItems::All(s) => &s[index], + FileItems::Filtered(v) => v[index], + } + } +} + +/// Resolve a FileItem's chunked path into frizbee's pointer buffer. +/// Returns `Some((chunk_count, byte_len))` or `None` for deleted files. +#[inline] +fn resolve_file_chunks( + file: &FileItem, + arena: ArenaPtr, + buf: &mut [*const u8; MAX_PATH_CHUNKS], +) -> Option<(usize, u16)> { + if file.is_deleted() { + return None; + } + let ptrs = file.path.resolve_ptrs(arena, buf); + Some((ptrs.len(), file.path.byte_len)) +} + +#[inline] +fn match_fuzzy_parts( + fuzzy_parts: &[&str], + working_files: &FileItems<'_>, + options: &neo_frizbee::Config, + max_threads: usize, + arena: ArenaPtr, +) -> Vec { + let valid_parts: Vec<&str> = fuzzy_parts + .iter() + .copied() + .filter(|p| p.len() >= 2) + .collect(); + + if valid_parts.is_empty() { + tracing::debug!("match_fuzzy_parts: no valid parts after filtering, returning empty"); + return vec![]; + } + + let resolve = |file: &FileItem, + buf: &mut [*const u8; MAX_PATH_CHUNKS]| + -> Option<(usize, u16)> { resolve_file_chunks(file, arena, buf) }; + + // because we reassemble the vec of reference we have to use a different type + // to narrow down the [&FileItem] which would be resolved by frizbee as && + let resolve_ref = |file: &&FileItem, + buf: &mut [*const u8; MAX_PATH_CHUNKS]| + -> Option<(usize, u16)> { resolve_file_chunks(file, arena, buf) }; + + let first_part_matches = match working_files { + FileItems::All(files) => neo_frizbee::match_list_parallel_resolved( + valid_parts[0], + files, + &resolve, + options, + max_threads, + ), + FileItems::Filtered(files) => neo_frizbee::match_list_parallel_resolved( + valid_parts[0], + files.as_slice(), + &resolve_ref, + options, + max_threads, + ), + }; + + if valid_parts.len() == 1 { + return first_part_matches; + } + + let total_parts = valid_parts.len() as u32; + let mut matches = first_part_matches; + for part in valid_parts[1..].iter() { + let mut part_options = *options; + part_options.max_typos = options.max_typos.map(|t| t.min(part.len() as u16)); + + // Collect the subset of files that survived the previous round. + let subset: Vec<&FileItem> = matches + .iter() + .map(|m| working_files.index(m.index as usize)) + .collect(); + + let sub_matches = neo_frizbee::match_list_parallel_resolved( + part, + subset.as_slice(), + &resolve_ref, + &part_options, + max_threads, + ); + + if sub_matches.is_empty() { + return vec![]; // break early! + } + + // Map sub_matches back to original indices, average scores across all parts. + matches = sub_matches + .into_iter() + .map(|sm| { + let prev = &matches[sm.index as usize]; + let sum = (prev.score as u32).saturating_add(sm.score as u32); + let avg = sum / total_parts; + + neo_frizbee::Match { + index: prev.index, + score: avg.min(u16::MAX as u32) as u16, + end_col: prev.end_col, // keep first part's position for filename bonus + exact: prev.exact && sm.exact, + } + }) + .collect(); + } + + matches +} + +/// Match + score across base and overflow files, each with their own arena. +#[tracing::instrument(skip_all, level = tracing::Level::DEBUG)] +pub(crate) fn fuzzy_match_and_score_files<'a>( + files: &'a [FileItem], + context: &ScoringContext, + base_count: usize, + base_arena: ArenaPtr, + overflow_arena: ArenaPtr, +) -> (Vec<&'a FileItem>, Vec, usize) { + // Process overflow files first: newly added files (created after the + // initial scan) live in the overflow arena and are more likely to be + // relevant to the current search. + // + // putting them first in the list makes sorting more efficient and gives + // them tiebreaker advantage in case sorting is the same + let results = if files.len() > base_count { + let mut results = match_and_score_in_arena(&files[base_count..], context, overflow_arena); + + results.extend(match_and_score_in_arena( + &files[..base_count], + context, + base_arena, + )); + + results + } else { + match_and_score_in_arena(files, context, base_arena) + }; + + sort_and_paginate(results, context) +} + +/// Resolve a DirItem's chunked path into frizbee's pointer buffer. +#[inline] +fn resolve_dir_chunks( + dir: &DirItem, + arena: ArenaPtr, + buf: &mut [*const u8; MAX_PATH_CHUNKS], +) -> Option<(usize, u16)> { + let ptrs = dir.path.resolve_ptrs(arena, buf); + Some((ptrs.len(), dir.path.byte_len)) +} + +/// Run SIMD fuzzy match across directory items, narrowing the candidate set +/// through each query part — same pipeline as file matching. +fn match_fuzzy_parts_dirs( + fuzzy_parts: &[&str], + working_dirs: &[&DirItem], + options: &neo_frizbee::Config, + max_threads: usize, + arena: ArenaPtr, +) -> Vec { + let valid_parts: Vec<&str> = fuzzy_parts + .iter() + .copied() + .filter(|p| p.len() >= 2) + .collect(); + + if valid_parts.is_empty() { + return vec![]; + } + + let resolve_chunks_for_frizbee = + |dir: &&DirItem, buf: &mut [*const u8; MAX_PATH_CHUNKS]| -> Option<(usize, u16)> { + resolve_dir_chunks(dir, arena, buf) + }; + + let first_part_matches = neo_frizbee::match_list_parallel_resolved( + valid_parts[0], + working_dirs, + &resolve_chunks_for_frizbee, + options, + max_threads, + ); + + if valid_parts.len() == 1 { + return first_part_matches; + } + + let mut matches = first_part_matches; + let total_parts = valid_parts.len() as u32; + for part in valid_parts[1..].iter() { + let mut part_options = *options; + part_options.max_typos = options.max_typos.map(|t| t.min(part.len() as u16)); + + // Collect the subset of dirs that survived the previous round. + let subset: Vec<&DirItem> = matches + .iter() + .map(|m| working_dirs[m.index as usize]) + .collect(); + + let sub_matches = neo_frizbee::match_list_parallel_resolved( + part, + subset.as_slice(), + &resolve_chunks_for_frizbee, + &part_options, + max_threads, + ); + + if sub_matches.is_empty() { + return vec![]; + } + + // Map sub_matches back to original indices, average scores across all parts. + matches = sub_matches + .into_iter() + .map(|sm| { + let prev = &matches[sm.index as usize]; + let sum = (prev.score as u32).saturating_add(sm.score as u32); + let avg = sum / total_parts; + + neo_frizbee::Match { + index: prev.index, + score: avg.min(u16::MAX as u32) as u16, + end_col: prev.end_col, // keep first part's position for dirname bonus + exact: prev.exact && sm.exact, + } + }) + .collect(); + } + + matches +} + +/// Match + score directories against a fuzzy query. +/// Scoring: base_score, frecency_boost, distance_penalty, dirname_bonus. +#[tracing::instrument(skip_all, level = tracing::Level::DEBUG)] +pub(crate) fn fuzzy_match_and_score_dirs<'a>( + dirs: &'a [DirItem], + context: &ScoringContext, + arena: ArenaPtr, +) -> (Vec<&'a DirItem>, Vec, usize) { + if dirs.is_empty() { + return (vec![], vec![], 0); + } + + let parsed_query = context.query; + let working_dirs: Vec<&DirItem> = if parsed_query.constraints.is_empty() { + dirs.iter().collect() + } else { + match apply_constraints(dirs, &parsed_query.constraints, arena, arena) { + Some(filtered) if !filtered.is_empty() => filtered, + Some(_) => return (vec![], vec![], 0), + None => dirs.iter().collect(), + } + }; + + let fuzzy_parts: &[&str] = match &parsed_query.fuzzy_query { + FuzzyQuery::Text(t) if t.len() >= 2 => std::slice::from_ref(t), + FuzzyQuery::Parts(parts) if !parts.is_empty() => parts.as_slice(), + _ => { + return score_dirs_by_frecency(&working_dirs, context); + } + }; + + let valid_parts: Vec<&str> = fuzzy_parts + .iter() + .copied() + .filter(|p| p.len() >= 2) + .collect(); + + if valid_parts.is_empty() { + return score_dirs_by_frecency(&working_dirs, context); + } + + let has_uppercase = valid_parts + .iter() + .any(|p| p.chars().any(|c| c.is_uppercase())); + + let options = neo_frizbee::Config { + max_typos: Some(context.max_typos), + sort: false, + scoring: Scoring { + capitalization_bonus: if has_uppercase { 8 } else { 0 }, + matching_case_bonus: if has_uppercase { 4 } else { 0 }, + ..Default::default() + }, + ..Default::default() + }; + + let path_matches = match_fuzzy_parts_dirs( + fuzzy_parts, + &working_dirs, + &options, + context.max_threads, + arena, + ); + + let main_needle = valid_parts[0].as_bytes(); + let main_needle_len = main_needle.len() as u16; + + let mut dir_buf = String::with_capacity(64); + let mut dirname_buf = String::with_capacity(32); + + let results: Vec<(&DirItem, Score)> = path_matches + .into_iter() + .map(|path_match| { + let dir = working_dirs[path_match.index as usize]; + let base_score = path_match.score as i32; + let frecency_boost = base_score.saturating_mul(dir.max_access_frecency()) / 100; + + // Distance penalty from current file's directory. + let distance_penalty = if context.current_file.is_some() { + dir.path.write_to_string(arena, &mut dir_buf); + calculate_distance_penalty(context.current_file, &dir_buf) + } else { + 0 + }; + + // Dirname bonus: if the match is in the last path segment. + let last_seg_offset = dir.last_segment_offset(); + let match_start_approx = path_match.end_col.saturating_sub(main_needle_len - 1); + let is_dirname_match = match_start_approx >= last_seg_offset; + + dir.write_dir_name(arena, &mut dirname_buf); + let dirname_len = dirname_buf.len(); + let is_exact_dirname = is_dirname_match + && main_needle_len as usize == dirname_len + && main_needle.eq_ignore_ascii_case(dirname_buf.as_bytes()); + + let filename_bonus = if is_exact_dirname { + base_score / 5 * 2 // 40% bonus for exact dirname match + } else if is_dirname_match { + base_score / 6 // ~16% bonus for fuzzy dirname match + } else { + 0 + }; + + let total = base_score + .saturating_add(frecency_boost) + .saturating_add(distance_penalty) + .saturating_add(filename_bonus); + + let score = Score { + total, + base_score, + filename_bonus, + special_filename_bonus: 0, + frecency_boost, + git_status_boost: 0, + distance_penalty, + current_file_penalty: 0, + combo_match_boost: 0, + path_alignment_bonus: 0, + exact_match: is_exact_dirname || path_match.exact, + match_type: if is_exact_dirname { + "exact_dirname" + } else if is_dirname_match { + "fuzzy_dirname" + } else if path_match.exact { + "exact_path" + } else { + "fuzzy_path" + }, + }; + + (dir, score) + }) + .collect(); + + sort_and_paginate_dirs(results, context) +} + +fn score_dirs_by_frecency<'a>( + dirs: &[&'a DirItem], + context: &ScoringContext, +) -> (Vec<&'a DirItem>, Vec, usize) { + let results: Vec<(&DirItem, Score)> = dirs + .iter() + .map(|&dir| { + let score = Score { + total: dir.max_access_frecency(), + frecency_boost: dir.max_access_frecency(), + match_type: "frecency", + ..Default::default() + }; + + (dir, score) + }) + .collect(); + + sort_and_paginate_dirs(results, context) +} + +/// Sort dir results by total score (descending) and apply pagination. +fn sort_and_paginate_dirs<'a>( + mut results: Vec<(&'a DirItem, Score)>, + context: &ScoringContext, +) -> (Vec<&'a DirItem>, Vec, usize) { + let total_matched = results.len(); + if total_matched == 0 { + return (vec![], vec![], 0); + } + + let offset = context.pagination.offset; + let limit = if context.pagination.limit > 0 { + context.pagination.limit + } else { + total_matched + }; + + if offset >= total_matched { + return (vec![], vec![], total_matched); + } + + let items_needed = offset.saturating_add(limit).min(total_matched); + let use_partial_sort = items_needed < total_matched / 2 && total_matched > 100; + + if use_partial_sort { + results.select_nth_unstable_by(items_needed - 1, |a, b| b.1.total.cmp(&a.1.total)); + results.truncate(items_needed); + } + + sort_with_buffer(&mut results, |a, b| b.1.total.cmp(&a.1.total)); + + if results.len() > limit { + let page_end = std::cmp::min(offset + limit, results.len()); + let page_size = page_end - offset; + results.drain(0..offset); + results.truncate(page_size); + } + + let (items, scores): (Vec<&DirItem>, Vec) = results.into_iter().unzip(); + (items, scores, total_matched) +} + +fn match_and_score_in_arena<'a>( + files: &'a [FileItem], + context: &ScoringContext, + arena: ArenaPtr, +) -> Vec<(&'a FileItem, Score)> { + if files.is_empty() { + return vec![]; + } + + let parsed = context.query; + let working_files: FileItems<'a> = if parsed.constraints.is_empty() { + FileItems::All(files) + } else { + match apply_constraints(files, &parsed.constraints, arena, arena) { + Some(filtered) if !filtered.is_empty() => FileItems::Filtered(filtered), + Some(_) => { + return vec![]; + } + None => FileItems::All(files), + } + }; + + let fuzzy_parts: &[&str] = match &parsed.fuzzy_query { + FuzzyQuery::Text(t) if t.len() >= 2 => std::slice::from_ref(t), + FuzzyQuery::Parts(parts) if !parts.is_empty() => parts.as_slice(), + _ => { + return score_filtered_by_frecency(&working_files, context, arena); + } + }; + + debug_assert!(!fuzzy_parts.is_empty()); + let has_uppercase = fuzzy_parts + .iter() + .any(|p| p.chars().any(|c| c.is_uppercase())); + // Users type `/` regardless of platform. Checking the OS separator alone + // would miss forward-slash queries on Windows. + let query_contains_path_separator = fuzzy_parts + .iter() + .any(|p| p.contains('/') || p.contains(MAIN_SEPARATOR)); + + let options = neo_frizbee::Config { + max_typos: Some(context.max_typos), + sort: false, + scoring: Scoring { + capitalization_bonus: if has_uppercase { 8 } else { 0 }, + matching_case_bonus: if has_uppercase { 4 } else { 0 }, + ..Default::default() + }, + ..Default::default() + }; + + let path_matches = match_fuzzy_parts( + fuzzy_parts, + &working_files, + &options, + context.max_threads, + arena, + ); + + let main_needle = fuzzy_parts[0].as_bytes(); // safe + let main_needle_len = main_needle.len() as u16; + + let mut fallback_indices: Vec = Vec::new(); + let filename_fallback_matches = if query_contains_path_separator || path_matches.len() > 15_000 + { + vec![] + } else { + let mut fallback_filenames: Vec> = Vec::new(); + + for (i, path_match) in path_matches.iter().enumerate() { + let file = working_files.index(path_match.index as usize); + let filename_start = file.filename_offset_in_relative_path() as u16; + let match_start_approx = path_match.end_col.saturating_sub(main_needle_len - 1); + + if match_start_approx < filename_start { + fallback_indices.push(i as u32); + fallback_filenames.push(file.path.filename_cow(arena)); + } + } + + if fallback_filenames.is_empty() { + vec![] + } else { + let mut matches = neo_frizbee::match_list_parallel( + fuzzy_parts[0], + &fallback_filenames, + &options, + if path_matches.len() > 4096 { + context.max_threads.div_ceil(2048) + } else { + 1 + }, + ); + + sort_by_key_with_buffer(&mut matches, |m| fallback_indices[m.index as usize]); + matches + } + }; + + let mut next_filename_match_cursor = 0; + let mut dir_buf = String::with_capacity(64); + let mut fname_buf = String::with_capacity(32); + let mut path_buf = [0u8; crate::simd_path::PATH_BUF_SIZE]; + + let results: Vec<_> = path_matches + .into_iter() + .enumerate() + .map(|(match_idx, path_match)| { + let file_idx = path_match.index as usize; + let file = working_files.index(file_idx); + + let base_score = path_match.score as i32; + let frecency_boost = base_score.saturating_mul(file.total_frecency_score()) / 100; + + let git_status_boost = if file.git_status.is_some_and(is_modified_status) { + base_score * 15 / 100 + } else { + 0 + }; + + if context.current_file.is_some() || context.last_same_query_match.is_some() { + file.write_dir_str(arena, &mut dir_buf); + } + + let distance_penalty = if context.current_file.is_some() { + calculate_distance_penalty(context.current_file, &dir_buf) + } else { + 0 + }; + + let filename_start = file.filename_offset_in_relative_path() as u16; + let match_start_approx = path_match.end_col.saturating_sub(main_needle_len - 1); + + let end_col_filename_match = match_start_approx >= filename_start; + let simd_filename_match = if !end_col_filename_match { + filename_fallback_matches + .get(next_filename_match_cursor) + .and_then(|m| { + if fallback_indices[m.index as usize] == match_idx as u32 { + next_filename_match_cursor += 1; + Some(m) + } else { + None + } + }) + } else { + None + }; + + let is_filename_match = end_col_filename_match || simd_filename_match.is_some(); + let fname_len = file.path.byte_len as usize - file.path.filename_offset as usize; + + let is_exact_filename = simd_filename_match.is_some_and(|m| m.exact) + || (end_col_filename_match && main_needle_len as usize == fname_len && { + file.write_file_name_from_arena(arena, &mut fname_buf); + main_needle.eq_ignore_ascii_case(fname_buf.as_bytes()) + }); + + let mut has_special_filename_bonus = false; + let filename_bonus = if is_exact_filename { + base_score / 5 * 2 // 40% bonus for exact filename match + } else if is_filename_match { + // 16% bonus for fuzzy filename match that landed in the filename region. + // For fallback matches (where the path match landed in a directory segment), + // scale the bonus by the quality of the filename match — a contiguous match + // like "rename" in "rename.ts" gets the full bonus, while a scattered + // subsequence like r-e-n-a-m-e in "generateSessionName.ts" gets much less. + let max_bonus = (base_score / 6).min(30); + if let Some(fm) = simd_filename_match { + let max_possible = main_needle_len as i32 * 16; + let quality = (fm.score as i32).min(max_possible); + max_bonus * quality / max_possible + } else { + max_bonus + } + } else if !is_filename_match && (5..=11).contains(&fname_len) { + file.write_file_name_from_arena(arena, &mut fname_buf); + // 5% bonus for special file but not as much as file name to avoid situations + // when you have /user_service/server.rs and /user_service/server/mod.rs + if is_special_entry_point_file(&fname_buf) { + has_special_filename_bonus = true; + base_score * 5 / 100 + } else { + 0 + } + } else { + 0 + }; + + let current_file_penalty = + calculate_current_file_penalty(file, base_score / 4, context, arena); + let combo_match_boost = { + let last_same_query_match = context.last_same_query_match.as_ref().filter(|m| { + let file_path_str = m.file_path.to_string_lossy(); + let total_len = file.path.byte_len as usize; + if file_path_str.len() < total_len { + return false; + } + // Reuse dir_buf (already has capacity) for the full path + file.write_relative_path_from_arena(arena, &mut dir_buf); + file_path_str.ends_with(dir_buf.as_str()) + }); + + match last_same_query_match { + Some(_) if context.min_combo_count == 0 => 1000, + Some(combo_match) if combo_match.open_count >= context.min_combo_count => { + combo_match.open_count as i32 * context.combo_boost_score_multiplier + } + Some(combo_match) => combo_match.open_count as i32 * 5, + _ => 0, + } + }; + + // Path alignment bonus: when the query looks like a file path, + // reward candidates whose path closely matches the typed query. + // Uses suffix overlap — bytes matching from the end. A full prefix + // match is just the 100% coverage case, so no separate branch needed. + let path_alignment_bonus = if query_contains_path_separator { + let rel_path = file.path.read_to_buf(arena, &mut path_buf); + let path_bytes = rel_path.as_bytes(); + let common_suffix = main_needle + .iter() + .rev() + .zip(path_bytes.iter().rev()) + .take_while(|(n, p): &(&u8, &u8)| n.eq_ignore_ascii_case(p)) + .count(); + + let needle_len = main_needle.len(); + if common_suffix > 10 && needle_len > 0 { + let coverage = common_suffix * 100 / needle_len; + if coverage >= 30 { + base_score * coverage as i32 / 100 + } else { + 0 + } + } else { + 0 + } + } else { + 0 + }; + + let total = base_score + .saturating_add(frecency_boost) + .saturating_add(git_status_boost) + .saturating_add(distance_penalty) + .saturating_add(filename_bonus) + .saturating_add(current_file_penalty) + .saturating_add(combo_match_boost) + .saturating_add(path_alignment_bonus); + + let score = Score { + total, + base_score, + current_file_penalty, + filename_bonus, + special_filename_bonus: if has_special_filename_bonus { + filename_bonus + } else { + 0 + }, + frecency_boost, + git_status_boost, + distance_penalty, + combo_match_boost, + path_alignment_bonus, + exact_match: is_exact_filename || path_match.exact, + match_type: if is_exact_filename { + "exact_filename" + } else if is_filename_match { + "fuzzy_filename" + } else if path_match.exact { + "exact_path" + } else { + "fuzzy_path" + }, + }; + + (file, score) + }) + .collect(); + + results +} + +fn is_special_entry_point_file(filename: &str) -> bool { + matches!( + filename, + "mod.rs" + | "lib.rs" + | "main.rs" + | "index.js" + | "index.jsx" + | "index.ts" + | "index.tsx" + | "index.mjs" + | "index.cjs" + | "index.vue" + | "__init__.py" + | "__main__.py" + | "main.go" + | "main.c" + | "index.php" + | "main.rb" + | "index.rb" + ) +} + +fn score_filtered_by_frecency<'a>( + files: &FileItems<'a>, + context: &ScoringContext, + arena: ArenaPtr, +) -> Vec<(&'a FileItem, Score)> { + let score_file = |file: &'a FileItem| { + let total_frecency_score = file.access_frecency_score as i32 + + (file.modification_frecency_score as i32).saturating_mul(4); + + // Give modified/dirty files a boost even in frecency-only mode + let git_status_boost = if file.git_status.is_some_and(is_modified_status) { + total_frecency_score * 15 / 100 + } else { + 0 + }; + + let current_file_penalty = + calculate_current_file_penalty(file, total_frecency_score, context, arena); + let total = total_frecency_score + .saturating_add(git_status_boost) + .saturating_add(current_file_penalty); + + let score = Score { + total, + base_score: 0, + filename_bonus: 0, + distance_penalty: 0, + special_filename_bonus: 0, + combo_match_boost: 0, + path_alignment_bonus: 0, + current_file_penalty, + frecency_boost: total_frecency_score, + git_status_boost, + exact_match: false, + match_type: "frecency", + }; + + (file, score) + }; + + match files { + FileItems::All(s) => s + .par_iter() + .filter_map(|f| { + let live = !f.is_deleted(); + live.then_some(score_file(f)) + }) + .collect(), + FileItems::Filtered(v) => v + .iter() + .filter_map(|f| { + let live = !f.is_deleted(); + live.then_some(score_file(f)) + }) + .collect(), + } +} + +#[inline] +fn calculate_current_file_penalty( + file: &FileItem, + base_score: i32, + context: &ScoringContext, + arena: ArenaPtr, +) -> i32 { + let mut penalty = 0i32; + + if let Some(current) = context.current_file + && file.relative_path_eq(arena, current) + { + penalty -= base_score; + } + + penalty +} + +/// Sorts elements by total score (descending) and returns the requested page. +/// Always returns results in descending order (best scores first). +/// The UI layer handles rendering order based on prompt position. +#[tracing::instrument(skip_all, level = tracing::Level::DEBUG)] +fn sort_and_paginate<'a>( + mut results: Vec<(&'a FileItem, Score)>, + context: &ScoringContext, +) -> (Vec<&'a FileItem>, Vec, usize) { + let total_matched = results.len(); + + if total_matched == 0 { + return (vec![], vec![], 0); + } + + let offset = context.pagination.offset; + let limit = if context.pagination.limit > 0 { + context.pagination.limit + } else { + total_matched + }; + + // Check if offset is out of bounds + if offset >= total_matched { + tracing::warn!( + offset = offset, + total_matched = total_matched, + "Pagination: offset >= total_matched, returning empty" + ); + + return (vec![], vec![], total_matched); + } + + let items_needed = offset.saturating_add(limit).min(total_matched); + // Use partial sort if we need less than half the results and dataset is large + let use_partial_sort = items_needed < total_matched / 2 && total_matched > 100; + // Always sort in descending order (best scores first) + if use_partial_sort { + // Partition at position (items_needed - 1) with descending comparator + // This puts the highest N needed items at the front + results.select_nth_unstable_by(items_needed - 1, |a, b| { + b.1.total + .cmp(&a.1.total) + .then_with(|| b.0.modified.cmp(&a.0.modified)) + }); + results.truncate(items_needed); + } + + // select nth does not sort the results, we have to sort accordingly anyway + sort_with_buffer(&mut results, |a, b| { + b.1.total + .cmp(&a.1.total) + .then_with(|| b.0.modified.cmp(&a.0.modified)) + }); + + // in the best scenario truncation happened in the select_nth step + if results.len() > limit { + let page_end = std::cmp::min(offset + limit, results.len()); + let page_size = page_end - offset; + + results.drain(0..offset); + results.truncate(page_size); + } + + let (items, scores): (Vec<&FileItem>, Vec) = results.into_iter().unzip(); + (items, scores, total_matched) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::PaginationArgs; + use fff_query_parser::QueryParser; + + fn make_test_files(specs: &[(&str, i32, u64)]) -> (Vec<(FileItem, Score)>, ArenaPtr) { + let path_strings: Vec = specs.iter().map(|(p, _, _)| p.to_string()).collect(); + let items: Vec = specs + .iter() + .map(|(p, _, _)| { + let fname = p.rfind(std::path::is_separator).map(|i| i + 1).unwrap_or(0) as u16; + FileItem::new_raw(fname, 0, 0, None, false) + }) + .collect(); + let (store, strings) = + crate::simd_path::build_chunked_path_store_from_strings(&path_strings, &items); + let arena = store.as_arena_ptr(); + let result: Vec<(FileItem, Score)> = specs + .iter() + .enumerate() + .map(|(i, &(_, score, modified))| { + let filename_start = path_strings[i] + .rfind(std::path::is_separator) + .map(|j| j + 1) + .unwrap_or(0) as u16; + let mut file = FileItem::new_raw(filename_start, 0, modified, None, false); + file.set_path(strings[i].clone()); + let score_obj = Score { + total: score, + base_score: score, + filename_bonus: 0, + distance_penalty: 0, + special_filename_bonus: 0, + current_file_penalty: 0, + frecency_boost: 0, + git_status_boost: 0, + exact_match: false, + match_type: "test", + combo_match_boost: 0, + path_alignment_bonus: 0, + }; + (file, score_obj) + }) + .collect(); + std::mem::forget(store); + (result, arena) + } + + #[test] + fn test_partial_sort_descending() { + // Create test data with known scores + let (test_data, arena) = make_test_files(&[ + ("file1.rs", 100, 1000), + ("file2.rs", 200, 2000), + ("file3.rs", 50, 3000), + ("file4.rs", 300, 4000), + ("file5.rs", 150, 5000), + ("file6.rs", 250, 6000), + ("file7.rs", 80, 7000), + ("file8.rs", 180, 8000), + ("file9.rs", 120, 9000), + ("file10.rs", 90, 10000), + ]); + + // Convert to references like the actual function uses + let results: Vec<(&FileItem, Score)> = test_data + .iter() + .map(|(file, score)| (file, score.clone())) + .collect(); + + let query_str = "test"; + let parser = QueryParser::default(); + let query = parser.parse(query_str); + let context = ScoringContext { + query: &query, + max_threads: 1, + max_typos: 2, + current_file: None, + last_same_query_match: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + + pagination: PaginationArgs { + offset: 0, + limit: 0, + }, + }; + + // Test with full sort - returns all results sorted descending + let (items, scores, total) = sort_and_paginate(results.clone(), &context); + + // Should return all 10 items sorted by score descending + assert_eq!(total, 10); + assert_eq!(scores.len(), 10); + assert_eq!(scores[0].total, 300, "First should be highest score"); + assert_eq!(scores[1].total, 250, "Second should be second highest"); + assert_eq!(scores[2].total, 200, "Third should be third highest"); + + // Verify the files match + assert_eq!(items[0].relative_path(arena), "file4.rs"); + assert_eq!(items[1].relative_path(arena), "file6.rs"); + assert_eq!(items[2].relative_path(arena), "file2.rs"); + } + + #[test] + fn test_partial_sort_with_same_scores() { + // Test tiebreaker with modified time + let (test_data, _arena) = make_test_files(&[ + ("file1.rs", 100, 5000), // Same score, older + ("file2.rs", 100, 8000), // Same score, newer + ("file3.rs", 100, 3000), // Same score, oldest + ("file4.rs", 200, 1000), + ("file5.rs", 200, 9000), // Higher score, newest + ]); + + let results: Vec<(&FileItem, Score)> = test_data + .iter() + .map(|(file, score)| (file, score.clone())) + .collect(); + + let query_str = "test"; + let parser = QueryParser::default(); + let query = parser.parse(query_str); + let context = ScoringContext { + query: &query, + max_threads: 1, + max_typos: 2, + current_file: None, + last_same_query_match: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + + pagination: PaginationArgs { + offset: 0, + limit: 0, + }, + }; + + let (items, scores, _) = sort_and_paginate(results, &context); + + // Should return all 5 items sorted: 200(9000), 200(1000), 100(8000), 100(5000), 100(3000) + assert_eq!(scores.len(), 5); + assert_eq!(scores[0].total, 200); + assert_eq!(items[0].modified, 9000, "First 200 should be newest"); + assert_eq!(scores[1].total, 200); + assert_eq!(items[1].modified, 1000, "Second 200 should be older"); + assert_eq!(scores[2].total, 100); + assert_eq!(items[2].modified, 8000, "First 100 should be newest"); + assert_eq!(scores[3].total, 100); + assert_eq!(items[3].modified, 5000); + assert_eq!(scores[4].total, 100); + assert_eq!(items[4].modified, 3000, "Last 100 should be oldest"); + } + + #[test] + fn test_no_partial_sort_for_small_results() { + // When results.len() <= threshold, should use regular sort + let (test_data, arena) = make_test_files(&[ + ("file1.rs", 100, 1000), + ("file2.rs", 200, 2000), + ("file3.rs", 50, 3000), + ]); + + let results: Vec<(&FileItem, Score)> = test_data + .iter() + .map(|(file, score)| (file, score.clone())) + .collect(); + + let query_str = "test"; + let parser = QueryParser::default(); + let query = parser.parse(query_str); + let context = ScoringContext { + query: &query, + max_threads: 1, + max_typos: 2, + current_file: None, + last_same_query_match: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + + pagination: PaginationArgs { + offset: 0, + limit: 0, + }, + }; + + // Returns all results sorted descending + let (items, scores, _) = sort_and_paginate(results, &context); + + assert_eq!(scores.len(), 3); + assert_eq!(scores[0].total, 200); + assert_eq!(scores[1].total, 100); + assert_eq!(scores[2].total, 50); + assert_eq!(items[0].relative_path(arena), "file2.rs"); + assert_eq!(items[1].relative_path(arena), "file1.rs"); + assert_eq!(items[2].relative_path(arena), "file3.rs"); + } +} + +#[cfg(test)] +mod filename_bonus_tests { + use super::*; + use crate::types::PaginationArgs; + use fff_query_parser::QueryParser; + fn make_files(paths: &[&str]) -> (Vec, ArenaPtr) { + let path_strings: Vec = paths.iter().map(|p| p.to_string()).collect(); + let items: Vec = paths + .iter() + .map(|p| { + let fname = p.rfind(std::path::is_separator).map(|i| i + 1).unwrap_or(0) as u16; + FileItem::new_raw(fname, 0, 0, None, false) + }) + .collect(); + let (store, strings) = + crate::simd_path::build_chunked_path_store_from_strings(&path_strings, &items); + let arena = store.as_arena_ptr(); + let mut result: Vec = items; + for (i, file) in result.iter_mut().enumerate() { + file.set_path(strings[i].clone()); + } + std::mem::forget(store); + (result, arena) + } + + fn make_files_with_frecency(specs: &[(&str, i16)]) -> (Vec, ArenaPtr) { + let path_strings: Vec = specs.iter().map(|(p, _)| p.to_string()).collect(); + let items: Vec = specs + .iter() + .map(|(p, _)| { + let fname = p.rfind(std::path::is_separator).map(|i| i + 1).unwrap_or(0) as u16; + FileItem::new_raw(fname, 0, 0, None, false) + }) + .collect(); + let (store, strings) = + crate::simd_path::build_chunked_path_store_from_strings(&path_strings, &items); + let arena = store.as_arena_ptr(); + let mut result: Vec = items; + for (i, file) in result.iter_mut().enumerate() { + file.set_path(strings[i].clone()); + file.access_frecency_score = specs[i].1; + } + std::mem::forget(store); + (result, arena) + } + + /// Run `fuzzy_match_and_score_files` with production-like max_typos scaling. + fn search(files: &[FileItem], query: &str, arena: ArenaPtr) -> Vec<(String, Score)> { + let parser = QueryParser::default(); + let parsed = parser.parse(query); + + let effective_query = match &parsed.fuzzy_query { + FuzzyQuery::Text(t) => *t, + FuzzyQuery::Parts(parts) if !parts.is_empty() => parts[0], + _ => query, + }; + let max_typos = (effective_query.len() as u16 / 4).clamp(2, 6); + + let ctx = ScoringContext { + query: &parsed, + max_threads: 1, + max_typos, + current_file: None, + last_same_query_match: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }; + let (items, scores, _) = + fuzzy_match_and_score_files(files, &ctx, files.len(), arena, arena); + items + .iter() + .zip(scores.iter()) + .map(|(f, s)| (f.relative_path(arena).to_string(), s.clone())) + .collect() + } + + #[test] + fn test_filename_match_ranks_above_path_only_match() { + let (files, arena) = make_files(&["src/username/handler.rs", "src/username/username.rs"]); + + let results = search(&files, "usrnmea", arena); + + assert!( + results.len() >= 2, + "both files should match, got {}", + results.len() + ); + assert_eq!( + results[0].0, "src/username/username.rs", + "filename match should rank first" + ); + assert!( + results[0].1.filename_bonus > 0, + "username.rs should have filename bonus" + ); + assert_eq!( + results[1].1.filename_bonus, 0, + "handler.rs should have no filename bonus" + ); + } + + #[test] + fn test_exact_filename_beats_fuzzy_filename() { + let (files, arena) = make_files(&["src/user_name_handler.rs", "src/username.rs"]); + + let results = search(&files, "username.rs", arena); + + assert!(results.len() >= 2); + assert_eq!( + results[0].0, "src/username.rs", + "exact filename should rank first" + ); + assert_eq!(results[0].1.match_type, "exact_filename"); + assert!(results[0].1.filename_bonus > results[1].1.filename_bonus); + } + + #[test] + fn test_same_length_filename_no_false_exact() { + let (files, arena) = make_files(&["src/item_sync/file.rs", "src/models/item.rs"]); + + let results = search(&files, "item.rs", arena); + + assert!(results.len() >= 2); + assert_eq!(results[0].0, "src/models/item.rs"); + assert_eq!(results[0].1.match_type, "exact_filename"); + assert_ne!( + results[1].1.match_type, "exact_filename", + "file.rs should not get exact_filename" + ); + } + + #[test] + fn test_path_separator_disables_filename_bonus() { + let (files, arena) = make_files(&["src/controllers/user.rs"]); + + let results = search(&files, "src/user", arena); + + assert!(!results.is_empty()); + assert_eq!( + results[0].1.filename_bonus, 0, + "path-like query should not get filename bonus" + ); + } + + /// Regression: full-path query should rank the near-exact path match first. + /// https://x.com/mbarneyjr/status/2043474268390817861 + #[test] + fn test_full_path_query_prefers_closer_filename_match() { + let (files, arena) = make_files_with_frecency(&[ + ( + "test-utils/completion/condition-key/yaml_partial-svc-colon.yml", + 0, + ), + ( + "test-utils/test-cases/completion/condition-key/yaml_partial-svc.yml", + 0, + ), + ( + "test-utils/action-value/yaml_inline_partial-svc-colon.yml", + 0, + ), + ( + "test-utils/completion/action-value/yaml_array_partial-svc-colon.yml", + 0, + ), + ( + "test-utils/completion/action-value/yaml_array_partial-svc.yml", + 0, + ), + ( + "test-utils/completion/condition-key/yaml_global-tag-keys.yml", + 0, + ), + ( + "test-utils/test-cases/completion/condition-key/yaml_partial.yml", + 10, + ), + ]); + + let results = search( + &files, + "t-utils/test-cases/completion/condition-key/yaml_partial-svc.yml", + arena, + ); + + assert!(!results.is_empty(), "query should match at least one file"); + + assert_eq!( + results[0].0, "test-utils/test-cases/completion/condition-key/yaml_partial-svc.yml", + "near-exact full-path match should rank first, but got: {} \ + (total={}, base={}, frecency={})", + results[0].0, results[0].1.total, results[0].1.base_score, results[0].1.frecency_boost, + ); + } + + /// Regression: PR #652 / field panic in pi-fff v0.9.6. + /// A path >512 bytes (but within PATH_MAX) overflows the fixed + /// `[*const u8; 32]` chunk-pointer buffer during scoring and panics with + /// "index out of bounds: the len is 32 but the index is 32". + #[test] + fn test_path_longer_than_512_bytes_does_not_panic_and_matches() { + let mut long_path = String::new(); + while long_path.len() < 600 { + long_path.push_str("deeply_nested_directory_segment/"); + } + long_path.push_str("needle_file.rs"); + assert!(long_path.len() > 512 && long_path.len() < crate::simd_path::PATH_BUF_SIZE); + + let (files, arena) = make_files(&[long_path.as_str(), "src/other.rs"]); + + // Panics here on unfixed code: frizbee resolves chunk ptrs per file. + let results = search(&files, "needle", arena); + + assert!( + results.iter().any(|(p, _)| p == &long_path), + "filename at the tail of a >512-byte path must still match, got: {:?}", + results.iter().map(|(p, _)| p).collect::>() + ); + } + + #[test] + fn test_single_path_matching() { + let path = "core_workflow_service/kafka_event_consumer/src/ai_part_extraction_request/ai_part_extraction_request_handler.rs"; + + let options = neo_frizbee::Config { + max_typos: Some(2), + sort: false, + ..Default::default() + }; + + let matches = neo_frizbee::match_list("aipart", &[path], &options); + assert!(!matches.is_empty(), "'aipart' should match the path"); + + let matches = neo_frizbee::match_list("core", &[path], &options); + assert!(!matches.is_empty(), "'core' should match the path"); + + let co_options = neo_frizbee::Config { + max_typos: Some(2), + ..options + }; + let matches = neo_frizbee::match_list("co", &[path], &co_options); + assert!(!matches.is_empty(), "'co' should match the path"); + } + + #[test] + fn test_lowercase_path_matching() { + let path = "core_workflow_service/kafka_event_consumer/src/ai_part_extraction_request/ai_part_extraction_request_handler.rs".to_lowercase(); + + let options = neo_frizbee::Config { + max_typos: Some(2), + sort: false, + ..Default::default() + }; + + let matches = neo_frizbee::match_list("co", &[path.as_str()], &options); + assert!(!matches.is_empty(), "'co' should match the lowercase path"); + + let matches = neo_frizbee::match_list("core", &[path.as_str()], &options); + assert!( + !matches.is_empty(), + "'core' should match the lowercase path" + ); + } +} + +#[cfg(test)] +mod typo_resistance_tests { + use super::*; + use crate::types::PaginationArgs; + use fff_query_parser::QueryParser; + + fn make_files(paths: &[&str]) -> (Vec, ArenaPtr) { + let path_strings: Vec = paths.iter().map(|p| p.to_string()).collect(); + let items: Vec = paths + .iter() + .map(|p| { + let fname = p.rfind(std::path::is_separator).map(|i| i + 1).unwrap_or(0) as u16; + FileItem::new_raw(fname, 0, 0, None, false) + }) + .collect(); + let (store, strings) = + crate::simd_path::build_chunked_path_store_from_strings(&path_strings, &items); + let arena = store.as_arena_ptr(); + let mut result: Vec = items; + for (i, file) in result.iter_mut().enumerate() { + file.set_path(strings[i].clone()); + } + std::mem::forget(store); + (result, arena) + } + + fn search_with_typos( + files: &[FileItem], + query: &str, + arena: ArenaPtr, + max_typos: u16, + ) -> Vec { + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let ctx = ScoringContext { + query: &parsed, + max_threads: 1, + max_typos, + current_file: None, + last_same_query_match: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 100, + }, + }; + let (items, _, _) = fuzzy_match_and_score_files(files, &ctx, files.len(), arena, arena); + items.iter().map(|f| f.relative_path(arena)).collect() + } + + #[test] + fn test_typo_resistant_long_query() { + let (files, arena) = make_files(&[ + "src/pricing/bid_comparison_supplier_part_cost_modifiers.rs", + "src/pricing/bid_evaluation_handler.rs", + "src/pricing/supplier_contract_terms.rs", + "src/models/purchase_order.rs", + "src/models/inventory_item.rs", + "src/controllers/user_controller.rs", + "src/controllers/admin_controller.rs", + "src/services/notification_service.rs", + "src/services/email_dispatcher.rs", + "src/utils/string_helpers.rs", + "src/utils/date_formatter.rs", + "src/db/migration_runner.rs", + "src/db/connection_pool.rs", + "src/config/app_settings.rs", + "src/config/feature_flags.rs", + ]); + + // Exact substring — must always match + let results = search_with_typos(&files, "bid_comparison", arena, 6); + assert!( + results + .iter() + .any(|p| p.contains("bid_comparison_supplier_part_cost_modifiers")), + "exact substring 'bid_comparison' should match, got: {results:?}" + ); + + // Concatenated query with typos — the real-world scenario. + // "bidcomparsionsupplierpartcostmodfiers" is a smushed version of + // "bid_comparison_supplier_part_cost_modifiers" with 2 typos + // (comparison → comparison, modifiers → modifiers). + let results = search_with_typos(&files, "bidcomparsionsupplierpartcostmodfiers", arena, 6); + assert!( + results + .iter() + .any(|p| p.contains("bid_comparison_supplier_part_cost_modifiers")), + "typo query 'bidcomparsionsupplierpartcostmodfiers' should match, got: {results:?}" + ); + + // Shorter typo query + let results = search_with_typos(&files, "bidcomp", arena, 4); + assert!( + results.iter().any(|p| p.contains("bid_comparison")), + "'bidcomp' should match bid_comparison file, got: {results:?}" + ); + + // Query with missing underscores + let results = search_with_typos(&files, "supplierpartcost", arena, 6); + assert!( + results + .iter() + .any(|p| p.contains("bid_comparison_supplier_part_cost_modifiers")), + "'supplierpartcost' should match, got: {results:?}" + ); + } +} + +#[cfg(test)] +mod constraint_only_query_tests { + use super::*; + use crate::types::PaginationArgs; + use fff_query_parser::QueryParser; + + #[test] + fn constraint_only_git_status_query_returns_filtered_files() { + let paths = ["modified.rs", "clean.rs"]; + let path_strings: Vec = paths.iter().map(|p| p.to_string()).collect(); + let items: Vec = paths + .iter() + .map(|p| { + let fname = p.rfind(std::path::is_separator).map(|i| i + 1).unwrap_or(0) as u16; + FileItem::new_raw(fname, 0, 0, None, false) + }) + .collect(); + let (store, strings) = + crate::simd_path::build_chunked_path_store_from_strings(&path_strings, &items); + let arena = store.as_arena_ptr(); + let mut files: Vec = items; + for (i, file) in files.iter_mut().enumerate() { + file.set_path(strings[i].clone()); + } + files[0].git_status = Some(git2::Status::WT_MODIFIED); + files[1].git_status = Some(git2::Status::CURRENT); + std::mem::forget(store); + + let parser = QueryParser::default(); + let parsed = parser.parse("git:modified"); + + let ctx = ScoringContext { + query: &parsed, + max_threads: 1, + max_typos: 2, + current_file: None, + last_same_query_match: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 50, + }, + }; + + let (items, _scores, total_matched) = + fuzzy_match_and_score_files(&files, &ctx, files.len(), arena, arena); + + assert_eq!( + total_matched, 1, + "git:modified constraint-only query should match exactly 1 modified file" + ); + assert_eq!(items.len(), 1); + assert_eq!(items[0].relative_path(arena), "modified.rs"); + } + + #[test] + fn constraint_only_status_modified_alias_returns_filtered_files() { + let paths = ["a.rs", "b.rs"]; + let path_strings: Vec = paths.iter().map(|p| p.to_string()).collect(); + let items: Vec = paths + .iter() + .map(|p| { + let fname = p.rfind(std::path::is_separator).map(|i| i + 1).unwrap_or(0) as u16; + FileItem::new_raw(fname, 0, 0, None, false) + }) + .collect(); + let (store, strings) = + crate::simd_path::build_chunked_path_store_from_strings(&path_strings, &items); + let arena = store.as_arena_ptr(); + let mut files: Vec = items; + for (i, file) in files.iter_mut().enumerate() { + file.set_path(strings[i].clone()); + } + files[0].git_status = Some(git2::Status::WT_MODIFIED); + files[1].git_status = Some(git2::Status::CURRENT); + std::mem::forget(store); + + let parser = QueryParser::default(); + let parsed = parser.parse("status:modified"); + + let ctx = ScoringContext { + query: &parsed, + max_threads: 1, + max_typos: 2, + current_file: None, + last_same_query_match: None, + project_path: None, + combo_boost_score_multiplier: 100, + min_combo_count: 3, + pagination: PaginationArgs { + offset: 0, + limit: 50, + }, + }; + + let (items, _scores, total_matched) = + fuzzy_match_and_score_files(&files, &ctx, files.len(), arena, arena); + + assert_eq!(total_matched, 1); + assert_eq!(items[0].relative_path(arena), "a.rs"); + } +} diff --git a/crates/fff-core/src/shared.rs b/crates/fff-core/src/shared.rs new file mode 100644 index 0000000..10d6e65 --- /dev/null +++ b/crates/fff-core/src/shared.rs @@ -0,0 +1,391 @@ +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak}; +use std::time::{Duration, Instant}; + +use crate::dbs::lmdb::{LmdbStore, spawn_lmdb_gc}; +use crate::error::Error; +use crate::file_picker::FilePicker; +use crate::frecency::FrecencyTracker; +use crate::git::GitStatusCache; +use crate::query_tracker::QueryTracker; +use crate::scan::ScanJob; +use git2::Repository; + +/// Poll `.git/index.lock` until it disappears (git write completed), giving up +/// after [`GIT_LOCK_MAX_WAIT`]. Used by [`SharedPicker::refresh_git_status`] +/// to avoid reading a half-updated index when the watcher fires mid-`git add`. +/// +/// The wait is bounded and cheap: the lock file is typically cleared within +/// a few milliseconds of the git command exiting. +fn wait_for_git_index_lock_release(git_root: &Path) { + const GIT_LOCK_POLL: Duration = Duration::from_millis(10); + const GIT_LOCK_MAX_WAIT: Duration = Duration::from_millis(500); + + let lock = git_root.join(".git").join("index.lock"); + // Fast path: no lock present. + if !lock.exists() { + return; + } + let deadline = Instant::now() + GIT_LOCK_MAX_WAIT; + while lock.exists() && Instant::now() < deadline { + std::thread::sleep(GIT_LOCK_POLL); + } + if lock.exists() { + tracing::warn!( + "Proceeding with git status refresh despite lingering \ + .git/index.lock at {} — will retry once it clears", + lock.display() + ); + } +} + +/// Poll `done` every 10ms until it returns `true`, or until `timeout` elapses. +/// Returns `true` if the condition was met, `false` on timeout. +fn poll_until(timeout: Duration, mut done: impl FnMut() -> bool) -> bool { + let start = Instant::now(); + while !done() { + if start.elapsed() >= timeout { + return false; + } + std::thread::sleep(Duration::from_millis(10)); + } + true +} + +/// Thread-safe shared handle to the [`FilePicker`] instance. +/// This accumulates only asynchronous non-blocking operations against the +/// file picker: creating, triggering various rescans and so on. +/// +/// For blocking access use internal picker via `.read()` or `.write()` +/// +/// ```ignore +/// let shared_picker = SharedFilePicker::default(); +/// +/// if let Some(picker) = shared_picker.read()?.as_ref() { +/// let files = picker.fuzzy_search(&query, options); +/// println!("Found {} files", files.len()); +/// } else { +/// println!("Picker not initialized"); +/// } +/// ``` +#[derive(Clone, Default)] +pub struct SharedFilePicker(pub(crate) Arc); + +pub struct SharedPickerInner { + picker: parking_lot::RwLock>, +} + +impl Default for SharedPickerInner { + fn default() -> Self { + Self { + picker: parking_lot::RwLock::new(None), + } + } +} + +/// Non-owning handle to a [`SharedPicker`]. +#[derive(Clone)] +pub(crate) struct WeakFilePicker(Weak); + +impl WeakFilePicker { + /// Try to promote the weak handle back to a strong [`SharedPicker`]. + /// + /// Returns `None` once every strong `SharedPicker` clone has been + /// dropped. Callers should treat that as "the picker is being + /// torn down" and exit their current iteration cleanly. + pub(crate) fn upgrade(&self) -> Option { + self.0.upgrade().map(SharedFilePicker) + } +} + +impl std::fmt::Debug for SharedFilePicker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SharedPicker").field(&"..").finish() + } +} + +impl SharedFilePicker { + pub fn read(&self) -> Result>, Error> { + Ok(self.0.picker.read()) + } + + pub fn write(&self) -> Result>, Error> { + Ok(self.0.picker.write()) + } + + /// Signal the background scan to cancel. Non-blocking: post-scan + /// threads check this flag and bail out at their next cancellation point. + pub fn cancel(&self) { + if let Ok(guard) = self.read() + && let Some(picker) = guard.as_ref() + { + picker.cancel(); + } + } + + /// Produce a non-owning handle to the same inner picker. + /// Use it if you don't need to block internal threads from dropping while owning this ref + pub(crate) fn weaken(&self) -> WeakFilePicker { + WeakFilePicker(Arc::downgrade(&self.0)) + } + + /// Return `true` if this is an instance of the picker that requires a complicated post-scan + /// indexing/cache warmup job. The indexing is not crazy but it takes time. + pub fn need_complex_rebuild(&self) -> bool { + let guard = self.0.picker.read(); + guard + .as_ref() + .is_some_and(|p| p.has_mmap_cache() || p.has_content_indexing()) + } + + /// Block until the background filesystem scan finishes. + /// Returns `true` if scan completed, `false` on timeout. + pub fn wait_for_scan(&self, timeout: Duration) -> bool { + let signal = { + let guard = self.0.picker.read(); + match &*guard { + Some(picker) => Arc::clone(&picker.signals.scanning), + None => return true, + } + }; + + poll_until(timeout, || { + !signal.load(std::sync::atomic::Ordering::Acquire) + }) + } + + /// Block until the background file watcher is ready. + /// Returns `true` if watcher ready, `false` on timeout. + pub fn wait_for_watcher(&self, timeout: Duration) -> bool { + let watch_ready_signal = { + let guard = self.0.picker.read(); + match &*guard { + Some(picker) => Arc::clone(&picker.signals.watcher_ready), + None => return true, + } + }; + + poll_until(timeout, || { + watch_ready_signal.load(std::sync::atomic::Ordering::Acquire) + }) + } + + /// Blocks until both the filesystem walk and post-scan indexing are done. + /// Returns true once scanning=false AND post_scan_indexing_active=false. + pub fn wait_for_indexing_complete(&self, timeout: Duration) -> bool { + let (scanning, post_scan_active) = { + let guard = self.0.picker.read(); + match &*guard { + Some(picker) => ( + Arc::clone(&picker.signals.scanning), + Arc::clone(&picker.signals.post_scan_indexing_active), + ), + None => return true, + } + }; + + poll_until(timeout, || { + !scanning.load(std::sync::atomic::Ordering::Acquire) + && !post_scan_active.load(std::sync::atomic::Ordering::Acquire) + }) + } + + /// Trigger a full filesystem rescan without blocking the caller. + /// Performs a safe async rescan. Guarantees only single active rescan per picker. + /// If many rescans requested the last one guaranteed to be finished. + pub fn trigger_full_rescan_async(&self, shared_frecency: &SharedFrecency) -> Result<(), Error> { + match ScanJob::new_rescan(self, shared_frecency)? { + Some(job) => { + job.spawn(); + } + None => { + // we can not abort the ongoing sync, but if the events + if let Ok(guard) = self.read() + && let Some(picker) = guard.as_ref() + { + picker + .scan_signals() + .rescan_pending + .store(true, std::sync::atomic::Ordering::Release); + tracing::info!( + "Full rescan requested while another scan is active — \ + deferred via rescan_pending flag" + ); + } + } + } + Ok(()) + } + + /// Refresh git statuses for all indexed files + #[tracing::instrument(level = "info", skip_all)] + pub fn refresh_git_status(&self, shared_frecency: &SharedFrecency) -> Result { + use tracing::debug; + + let git_status = { + let git_root = { + let guard = self.read()?; + let Some(ref picker) = *guard else { + return Err(Error::FilePickerMissing); + }; + picker.git_root().map(|p| p.to_path_buf()) + }; + + debug!(?git_root, "Refreshing git status for picker"); + + if let Some(ref root) = git_root { + wait_for_git_index_lock_release(root); + } + + GitStatusCache::read_git_status( + git_root.as_deref(), + &mut crate::git::default_status_options(), + ) + }; + + let mut guard = self.write()?; + let picker = guard.as_mut().ok_or(Error::FilePickerMissing)?; + + let statuses_count = if let Some(git_status) = git_status { + let count = git_status.statuses_len(); + picker.update_git_statuses(git_status, shared_frecency)?; + count + } else { + 0 + }; + + Ok(statuses_count) + } + + /// Recompute and apply git status for a specific set of paths. + pub fn update_git_status_for_paths( + &self, + paths: &[PathBuf], + shared_frecency: &SharedFrecency, + ) -> Result<(), Error> { + if paths.is_empty() { + return Ok(()); + } + + let git_root = { + let guard = self.read()?; + let Some(ref picker) = *guard else { + return Err(Error::FilePickerMissing); + }; + picker.git_root().map(|p| p.to_path_buf()) + }; + let Some(git_root) = git_root else { + return Ok(()); + }; + + wait_for_git_index_lock_release(&git_root); + + let repo = Repository::open(&git_root)?; + let status = GitStatusCache::git_status_for_paths(&repo, paths)?; + + let mut guard = self.write()?; + let picker = guard.as_mut().ok_or(Error::FilePickerMissing)?; + picker.update_git_statuses(status, shared_frecency) + } +} + +/// Thread-safe shared handle to an LMDB-backed store. A disabled (`noop`) +/// instance silently ignores writes. See the [`SharedFrecency`] and +/// [`SharedQueryTracker`] aliases. +/// +/// `LmdbStore` is intentionally crate-private, so the store type is sealed: +/// only `FrecencyTracker` / `QueryTracker` can ever instantiate this. +#[allow(private_bounds)] +pub struct SharedDb { + inner: Arc>>, + enabled: bool, +} + +// Hand-written to avoid a spurious `T: Clone` bound — `Arc` is always `Clone`. +impl Clone for SharedDb { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + enabled: self.enabled, + } + } +} + +impl Default for SharedDb { + fn default() -> Self { + Self { + inner: Arc::new(RwLock::new(None)), + enabled: true, + } + } +} + +impl std::fmt::Debug for SharedDb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SharedDb").field(&T::LABEL).finish() + } +} + +#[allow(private_bounds)] +impl SharedDb { + /// Creates a disabled instance that silently ignores all writes. + pub fn noop() -> Self { + Self { + inner: Arc::new(RwLock::new(None)), + enabled: false, + } + } + + pub fn read(&self) -> Result>, Error> { + self.inner.read().map_err(|_| Error::AcquireFrecencyLock) + } + + pub fn write(&self) -> Result>, Error> { + self.inner.write().map_err(|_| Error::AcquireFrecencyLock) + } + + /// Initialize the store + spawn GC in the background. No-op when disabled. + pub fn init(&self, tracker: T) -> Result<(), Error> { + if !self.enabled { + return Ok(()); + } + + { + let mut guard = self.write()?; + *guard = Some(tracker); + } + + // GC holds a read guard on this lock, so destroy / re-init wait won't race + spawn_lmdb_gc(self.inner.clone()); + Ok(()) + } + + /// Drop the in-memory tracker and delete the on-disk database directory. + /// + /// Acquires the write lock, ensuring all readers (including any active mmap + /// access) are finished before the LMDB environment is closed and the files + /// are removed. + /// + /// Returns `Ok(Some(path))` with the deleted path, or `Ok(None)` if no tracker was initialized. + pub fn destroy(&self) -> Result, Error> { + let mut guard = self.write()?; + let Some(tracker) = guard.take() else { + return Ok(None); + }; + let db_path = tracker.env().path().to_path_buf(); + // Drop closes the LMDB env and unmaps the files + drop(tracker); + drop(guard); + std::fs::remove_dir_all(&db_path).map_err(|source| Error::RemoveDbDir { + path: db_path.clone(), + source, + })?; + Ok(Some(db_path)) + } +} + +/// Thread-safe shared handle to the [`FrecencyTracker`] instance. +pub type SharedFrecency = SharedDb; + +/// Thread-safe shared handle to the [`QueryTracker`] instance. +pub type SharedQueryTracker = SharedDb; diff --git a/crates/fff-core/src/simd_path.rs b/crates/fff-core/src/simd_path.rs new file mode 100644 index 0000000..881f109 --- /dev/null +++ b/crates/fff-core/src/simd_path.rs @@ -0,0 +1,612 @@ +use ahash::AHashMap; +use smallvec::SmallVec; +use std::borrow::Cow; + +/// SIMD chunk size in bytes (matches NEON/SSE2 register width). +/// This must stay in sync with neo_frizbee's internal chunk size. +pub(crate) const SIMD_CHUNK_BYTES: usize = 16; + +/// 4 chunks = 64 bytes inline, covers ~85% of paths without heap fallback. +const INLINE_CHUNKS: usize = 4; + +pub(crate) type ChunkIndices = SmallVec<[u32; INLINE_CHUNKS]>; + +#[derive(Clone, Copy)] +pub struct ArenaPtr(pub(crate) *const u8); + +// SAFETY: The arena is a read-only immutable part of file sync +unsafe impl Send for ArenaPtr {} +unsafe impl Sync for ArenaPtr {} + +impl ArenaPtr { + #[inline] + pub fn new(ptr: *const u8) -> Self { + Self(ptr) + } + + #[inline] + pub fn null() -> Self { + Self(std::ptr::null()) + } + + #[inline] + pub fn as_ptr(self) -> *const u8 { + self.0 + } +} + +impl std::fmt::Debug for ArenaPtr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "--arena-raw-pointer-0x({:?})", self.0) + } +} + +#[repr(C, align(16))] +#[derive(Clone, Copy)] +pub(crate) struct SimdChunk(pub(crate) [u8; SIMD_CHUNK_BYTES]); + +impl Default for SimdChunk { + #[inline] + fn default() -> Self { + Self([0u8; SIMD_CHUNK_BYTES]) + } +} + +impl std::fmt::Debug for SimdChunk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Show the actual bytes, trimming trailing zeros for readability + let end = self.0.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1); + write!(f, "SimdChunk({:?})", &self.0[..end]) + } +} + +pub use crate::constants::PATH_BUF_SIZE; + +/// Chunk pointer capacity needed for the longest path the platform allows. +pub(crate) const MAX_PATH_CHUNKS: usize = PATH_BUF_SIZE.div_ceil(SIMD_CHUNK_BYTES); + +/// Indices into a shared `SimdChunk` arena representing a file path. +/// +/// All read methods require an explicit `arena_base` pointer from the owning +/// `ChunkedPathStore`. The struct itself contains no raw pointers to the arena +#[derive(Clone)] +pub(crate) struct ChunkedString { + indices: ChunkIndices, + pub byte_len: u16, + /// Byte offset where the filename begins. 0 for root-level files. + pub filename_offset: u16, +} + +impl ChunkedString { + pub fn empty() -> Self { + Self { + indices: SmallVec::new(), + byte_len: 0, + filename_offset: 0, + } + } + + #[inline] + pub fn new(indices: ChunkIndices, byte_len: u16, filename_offset: u16) -> Self { + Self { + indices, + byte_len, + filename_offset, + } + } + + #[cfg(test)] + pub fn chunk_count(&self) -> usize { + self.indices.len() + } + + #[inline] + pub fn resolve_ptrs<'a>(&self, arena: ArenaPtr, buf: &'a mut [*const u8]) -> &'a [*const u8] { + let count = self.indices.len().min(buf.len()); + let base = arena.as_ptr(); + for (i, &idx) in self.indices[..count].iter().enumerate() { + buf[i] = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) }; + } + &buf[..count] + } + + #[inline] + fn write_slice_to_vec( + indices: &[u32], + base: *const u8, + offset_in_chunk: usize, + len: usize, + vec: &mut Vec, + ) { + let mut written = 0usize; + for (i, &idx) in indices.iter().enumerate() { + let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) }; + let chunk_bytes = unsafe { core::slice::from_raw_parts(src, SIMD_CHUNK_BYTES) }; + let start = if i == 0 { offset_in_chunk } else { 0 }; + let end = SIMD_CHUNK_BYTES.min(start + (len - written)); + vec.extend_from_slice(&chunk_bytes[start..end]); + written += end - start; + } + } + + /// Return the filename portion as a `Cow`. + /// + /// When the filename starts at a chunk boundary and fits in one chunk we + /// borrow directly from the arena (zero-copy). Otherwise we allocate. + /// Filenames are almost always <=16 bytes so the fast path dominates. + #[inline] + pub fn filename_cow<'a>(&self, arena: ArenaPtr) -> Cow<'a, str> { + let fname_offset = self.filename_offset as usize; + let fname_len = self.byte_len as usize - fname_offset; + if fname_len == 0 { + return Cow::Borrowed(""); + } + + let base = arena.as_ptr(); + let start_chunk = fname_offset / SIMD_CHUNK_BYTES; + let offset_in_chunk = fname_offset % SIMD_CHUNK_BYTES; + + if offset_in_chunk == 0 && fname_len <= SIMD_CHUNK_BYTES { + let ptr = unsafe { base.add(self.indices[start_chunk] as usize * SIMD_CHUNK_BYTES) }; + let slice = unsafe { core::slice::from_raw_parts(ptr, fname_len) }; + return Cow::Borrowed(unsafe { core::str::from_utf8_unchecked(slice) }); + } + + let mut out = String::with_capacity(fname_len); + let needed_chunks = chunks_needed(offset_in_chunk + fname_len); + Self::write_slice_to_vec( + &self.indices[start_chunk..start_chunk + needed_chunks], + base, + offset_in_chunk, + fname_len, + unsafe { out.as_mut_vec() }, + ); + Cow::Owned(out) + } + + /// Truncates at `buf.len()` if exceeded -- use `[u8; PATH_BUF_SIZE]` to avoid. + #[inline] + pub fn read_to_buf<'a>(&self, arena: ArenaPtr, buf: &'a mut [u8]) -> &'a str { + let total = (self.byte_len as usize).min(buf.len()); + let usable_chunks = total.div_ceil(SIMD_CHUNK_BYTES); + let chunks_to_copy = usable_chunks.min(self.indices.len()); + let base = arena.as_ptr(); + + for (i, &idx) in self.indices[..chunks_to_copy].iter().enumerate() { + let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) }; + let dst_offset = i * SIMD_CHUNK_BYTES; + let take = SIMD_CHUNK_BYTES.min(total - dst_offset); + + unsafe { + core::ptr::copy_nonoverlapping(src, buf.as_mut_ptr().add(dst_offset), take); + } + } + + unsafe { core::str::from_utf8_unchecked(&buf[..total]) } + } + + #[inline] + pub fn write_dir_to(&self, arena: ArenaPtr, out: &mut String) { + out.clear(); + + let dir_len = self.filename_offset as usize; + out.reserve(dir_len); + let dir_chunks = chunks_needed(dir_len).min(self.indices.len()); + let base = arena.as_ptr(); + let vec = unsafe { out.as_mut_vec() }; + for (i, &idx) in self.indices[..dir_chunks].iter().enumerate() { + let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) }; + let take = SIMD_CHUNK_BYTES.min(dir_len - i * SIMD_CHUNK_BYTES); + vec.extend_from_slice(unsafe { core::slice::from_raw_parts(src, take) }); + } + } + + #[inline] + pub fn write_filename_to(&self, arena: ArenaPtr, out: &mut String) { + out.clear(); + + let fname_offset = self.filename_offset as usize; + let fname_len = self.byte_len as usize - fname_offset; + out.reserve(fname_len); + let start_chunk = fname_offset / SIMD_CHUNK_BYTES; + let offset_in_chunk = fname_offset % SIMD_CHUNK_BYTES; + let needed_chunks = chunks_needed(offset_in_chunk + fname_len); + Self::write_slice_to_vec( + &self.indices[start_chunk..start_chunk + needed_chunks], + arena.as_ptr(), + offset_in_chunk, + fname_len, + unsafe { out.as_mut_vec() }, + ); + } + + #[inline] + pub fn write_to_string(&self, arena: ArenaPtr, out: &mut String) { + out.clear(); + + let total = self.byte_len as usize; + if total == 0 { + return; + } + out.reserve(total); + let base = arena.as_ptr(); + let vec = unsafe { out.as_mut_vec() }; + for (i, &idx) in self.indices.iter().enumerate() { + let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) }; + let take = SIMD_CHUNK_BYTES.min(total - i * SIMD_CHUNK_BYTES); + vec.extend_from_slice(unsafe { core::slice::from_raw_parts(src, take) }); + } + } +} + +impl std::fmt::Debug for ChunkedString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ChunkedString") + .field("indices", &self.indices.as_slice()) + .field("chunks", &self.indices.len()) + .field("byte_len", &self.byte_len) + .field("filename_offset", &self.filename_offset) + .finish() + } +} + +#[inline] +const fn chunks_needed(byte_len: usize) -> usize { + if byte_len == 0 { + 0 + } else { + byte_len.div_ceil(SIMD_CHUNK_BYTES) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct ChunkedPathStore { + arena: Vec, +} + +// SAFETY: arena is immutable after construction. Pointers derived from it are +// only read during scoring (no mutation, no reallocation). +unsafe impl Send for ChunkedPathStore {} +unsafe impl Sync for ChunkedPathStore {} + +impl ChunkedPathStore { + pub fn heap_bytes(&self) -> usize { + self.arena.len() * SIMD_CHUNK_BYTES + } + + #[cfg(test)] + fn unique_chunks(&self) -> usize { + self.arena.len() + } + + #[inline] + pub fn as_arena_ptr(&self) -> ArenaPtr { + ArenaPtr::new(self.arena.as_ptr() as *const u8) + } +} + +/// At runtime the builder should be split out from the store after `finish()`. +#[derive(Clone, Debug)] +pub(crate) struct ChunkedPathStoreBuilder { + arena: Vec, + chunk_dedup: AHashMap<[u8; SIMD_CHUNK_BYTES], u32>, +} + +impl ChunkedPathStoreBuilder { + pub fn new(estimated_files: usize) -> Self { + let est_chunks = estimated_files * INLINE_CHUNKS; // we know that most of repos will fit + // most paths into 64 = 16 * INLINE_CHUNKS + Self { + arena: Vec::with_capacity(est_chunks), + chunk_dedup: AHashMap::with_capacity(est_chunks), + } + } + + pub fn finish(self) -> ChunkedPathStore { + ChunkedPathStore { arena: self.arena } + } + + pub fn as_arena_ptr(&self) -> ArenaPtr { + ArenaPtr::new(self.arena.as_ptr() as *const u8) + } + + /// Like [`add_file_immediate`] but for directory paths where the entire + /// string is the "directory" portion (filename_offset == byte_len). + pub fn add_dir_immediate(&mut self, dir_rel_path: &str) -> ChunkedString { + self.add_file_immediate(dir_rel_path, dir_rel_path.len() as u16) + } + + pub fn add_file_immediate(&mut self, rel_path: &str, filename_offset: u16) -> ChunkedString { + let path_bytes = rel_path.as_bytes(); + let byte_len = rel_path.len(); + let mut indices = ChunkIndices::with_capacity(chunks_needed(byte_len)); + + for chunk in path_bytes.chunks(SIMD_CHUNK_BYTES) { + let mut chunk_bytes = [0u8; SIMD_CHUNK_BYTES]; + chunk_bytes[..chunk.len()].copy_from_slice(chunk); + + let arena_idx = match self.chunk_dedup.get(&chunk_bytes) { + Some(&idx) => idx, + None => { + let idx = self.arena.len() as u32; + self.arena.push(SimdChunk(chunk_bytes)); + self.chunk_dedup.insert(chunk_bytes, idx); + idx + } + }; + + indices.push(arena_idx); + } + + ChunkedString::new(indices, byte_len as u16, filename_offset) + } +} + +#[cfg(test)] +pub(crate) fn build_chunked_path_store_from_strings( + rel_paths: &[String], + files: &[crate::types::FileItem], +) -> (ChunkedPathStore, Vec) { + assert_eq!(rel_paths.len(), files.len()); + let mut builder = ChunkedPathStoreBuilder::new(rel_paths.len()); + let strings: Vec = rel_paths + .iter() + .zip(files.iter()) + .map(|(rel_path, file)| builder.add_file_immediate(rel_path, file.path.filename_offset)) + .collect(); + (builder.finish(), strings) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_file_item(path: &str) -> crate::types::FileItem { + let filename_start = path + .rfind(std::path::is_separator) + .map(|i| i + 1) + .unwrap_or(0) as u16; + crate::types::FileItem::new_raw(filename_start, 0, 0, None, false) + } + + fn build_test_store( + paths: &[&str], + ) -> ( + ChunkedPathStore, + Vec, + Vec, + ) { + let mut files: Vec = + paths.iter().map(|p| make_file_item(p)).collect(); + let path_strings: Vec = paths.iter().map(|p| p.to_string()).collect(); + let (store, strings) = build_chunked_path_store_from_strings(&path_strings, &files); + for (i, file) in files.iter_mut().enumerate() { + file.set_path(strings[i].clone()); + } + (store, strings, files) + } + + #[test] + fn test_chunked_store_empty() { + let (store, strings, _files) = build_test_store(&[]); + assert_eq!(strings.len(), 0); + assert_eq!(store.unique_chunks(), 0); + } + + #[test] + fn test_chunked_store_basic() { + let (store, strings, _files) = + build_test_store(&["src/lib.rs", "src/main.rs", "Cargo.toml"]); + let arena = store.as_arena_ptr(); + + assert_eq!(strings.len(), 3); + assert!(store.unique_chunks() >= 2); + + let mut buf = [0u8; 512]; + assert_eq!( + strings[0].read_to_buf(arena, &mut buf).len(), + "src/lib.rs".len() + ); + assert_eq!( + strings[2].read_to_buf(arena, &mut buf).len(), + "Cargo.toml".len() + ); + } + + #[test] + fn test_chunked_string_full_path() { + let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + let mut buf = [0u8; 512]; + assert_eq!(cs.read_to_buf(arena, &mut buf), "src/components/Button.tsx"); + assert_eq!(cs.byte_len, 25); + assert_eq!(cs.filename_offset, 15); + } + + #[test] + fn test_chunked_string_dir_and_filename() { + let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + let mut s = String::new(); + cs.write_dir_to(arena, &mut s); + assert_eq!(s, "src/components/"); + cs.write_filename_to(arena, &mut s); + assert_eq!(s, "Button.tsx"); + } + + #[test] + fn test_chunked_string_root_file() { + let (store, strings, _files) = build_test_store(&["Cargo.toml"]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + let mut s = String::new(); + cs.write_dir_to(arena, &mut s); + assert_eq!(s, ""); + cs.write_filename_to(arena, &mut s); + assert_eq!(s, "Cargo.toml"); + let mut buf = [0u8; 512]; + assert_eq!(cs.read_to_buf(arena, &mut buf), "Cargo.toml"); + } + + #[test] + fn test_chunked_string_resolve_ptrs() { + let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + let mut ptrs = [std::ptr::null::(); MAX_PATH_CHUNKS]; + let resolved = cs.resolve_ptrs(arena, &mut ptrs); + assert_eq!(resolved.len(), 2); // 25 bytes = 2 chunks + + // Verify we can read back the bytes + let mut reconstructed = Vec::new(); + for (i, &ptr) in resolved.iter().enumerate() { + let chunk = unsafe { std::slice::from_raw_parts(ptr, SIMD_CHUNK_BYTES) }; + let start = i * SIMD_CHUNK_BYTES; + let take = SIMD_CHUNK_BYTES.min(25 - start); + reconstructed.extend_from_slice(&chunk[..take]); + } + assert_eq!( + std::str::from_utf8(&reconstructed).unwrap(), + "src/components/Button.tsx" + ); + } + + #[test] + fn test_resolve_ptrs_path_exceeding_512_bytes() { + // Regression: a fixed 32-ptr buffer covered only 512 bytes while + // PATH_BUF_SIZE (libc::PATH_MAX) allows longer paths, panicking with + // "index out of bounds: the len is 32 but the index is 32" + let mut path = String::new(); + while path.len() < 600 { + path.push_str("deeply_nested_directory_segment/"); + } + path.push_str("needle_file.rs"); + assert!(path.len() > 512 && path.len() < PATH_BUF_SIZE); + + let (store, strings, _files) = build_test_store(&[path.as_str()]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + assert!(cs.chunk_count() > 32, "path must span more than 32 chunks"); + + let mut ptrs = [std::ptr::null::(); MAX_PATH_CHUNKS]; + let resolved = cs.resolve_ptrs(arena, &mut ptrs); + + // Truncation is not acceptable either: it silently drops the tail of + // the path (including the filename here) from fuzzy matching. + assert_eq!( + resolved.len(), + cs.chunk_count(), + "resolve_ptrs must resolve every chunk of a PATH_MAX-legal path" + ); + + let total = cs.byte_len as usize; + let mut reconstructed = Vec::with_capacity(total); + for (i, &ptr) in resolved.iter().enumerate() { + let take = SIMD_CHUNK_BYTES.min(total - i * SIMD_CHUNK_BYTES); + reconstructed.extend_from_slice(unsafe { std::slice::from_raw_parts(ptr, take) }); + } + assert_eq!(std::str::from_utf8(&reconstructed).unwrap(), path); + } + + #[test] + fn test_filename_cow_mid_chunk() { + let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + assert_eq!(cs.filename_offset, 15); + assert_eq!(cs.byte_len, 25); + + let fname = cs.filename_cow(arena); + assert_eq!(&*fname, "Button.tsx"); + } + + #[test] + fn test_filename_cow_chunk_aligned() { + let path = "0123456789abcdef/file.txt"; + let (store, strings, _files) = build_test_store(&[path]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + assert_eq!(cs.filename_offset, 17); + let fname = cs.filename_cow(arena); + assert_eq!(&*fname, "file.txt"); + } + + #[test] + fn test_filename_cow_root_file() { + let (store, strings, _files) = build_test_store(&["Cargo.toml"]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + assert_eq!(cs.filename_offset, 0); + let fname = cs.filename_cow(arena); + assert_eq!(&*fname, "Cargo.toml"); + } + + #[test] + fn test_chunked_string_long_path() { + let path = "very/deeply/nested/directory/structure/with/many/levels/file.txt"; + let (store, strings, _files) = build_test_store(&[path]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + + let mut buf = [0u8; 512]; + assert_eq!(cs.read_to_buf(arena, &mut buf), path); + assert!( + cs.chunk_count() <= 6, + "should fit inline in ChunkIndices (INLINE_CHUNKS={})", + INLINE_CHUNKS + ); + } + + #[test] + fn test_chunked_string_clone() { + let (store, strings, _files) = build_test_store(&["src/main.rs"]); + let arena = store.as_arena_ptr(); + let cs = &strings[0]; + let cs2 = cs.clone(); + + let mut buf1 = [0u8; 512]; + let mut buf2 = [0u8; 512]; + assert_eq!( + cs.read_to_buf(arena, &mut buf1), + cs2.read_to_buf(arena, &mut buf2) + ); + } + + #[test] + fn test_chunked_string_full_path_roundtrip() { + let paths = [ + "src/components/Button.tsx", + "src/components/ui/DatePicker.tsx", + "very/deeply/nested/directory/structure/file.txt", + "Cargo.toml", + "a.rs", + ]; + let (store, strings, _files) = build_test_store(&paths); + let arena = store.as_arena_ptr(); + + for (i, expected) in paths.iter().enumerate() { + let mut buf = [0u8; 512]; + let got = strings[i].read_to_buf(arena, &mut buf); + assert_eq!(got, *expected, "full path roundtrip failed for file {i}"); + + let mut ds = String::new(); + let mut fs = String::new(); + strings[i].write_dir_to(arena, &mut ds); + strings[i].write_filename_to(arena, &mut fs); + assert_eq!( + format!("{ds}{fs}"), + *expected, + "dir+fname mismatch for file {i}" + ); + } + } +} diff --git a/crates/fff-core/src/simd_string_utils/case.rs b/crates/fff-core/src/simd_string_utils/case.rs new file mode 100644 index 0000000..fc806f4 --- /dev/null +++ b/crates/fff-core/src/simd_string_utils/case.rs @@ -0,0 +1,168 @@ +#[inline] +pub fn ascii_swap_case(b: u8) -> u8 { + b ^ 0x20 +} + +#[inline] +fn eq_lowered_scalar(h: *const u8, needle_lower: &[u8]) -> bool { + for (i, &n) in needle_lower.iter().enumerate() { + if unsafe { *h.add(i) }.to_ascii_lowercase() != n { + return false; + } + } + true +} + +/// AVX2 only has a **signed** byte compare (`cmpgt`), but we need an +/// **unsigned** range check (`'A' <= byte <= 'Z'`). XOR-ing every byte with +/// `0x80` maps the unsigned range `[0, 255]` into the signed range +/// `[-128, 127]` preserving order, so signed `cmpgt` becomes correct +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn eq_lowered_avx2(h: *const u8, needle_lower: &[u8]) -> bool { + use core::arch::x86_64::*; + + let len = needle_lower.len(); + let mut i = 0usize; + + let flip = _mm256_set1_epi8(0x80u8 as i8); + let a_minus_1 = _mm256_set1_epi8((b'A' - 1) as i8 ^ 0x80u8 as i8); + let z_plus_1 = _mm256_set1_epi8((b'Z' + 1) as i8 ^ 0x80u8 as i8); + let bit20 = _mm256_set1_epi8(0x20u8 as i8); + + while i + 32 <= len { + let hv = unsafe { _mm256_loadu_si256(h.add(i) as *const __m256i) }; + let nv = unsafe { _mm256_loadu_si256(needle_lower.as_ptr().add(i) as *const __m256i) }; + + // Signed-domain range check selects uppercase lanes, OR bit 5 folds them. + let x = _mm256_xor_si256(hv, flip); + let ge_a = _mm256_cmpgt_epi8(x, a_minus_1); + let le_z = _mm256_cmpgt_epi8(z_plus_1, x); + let upper = _mm256_and_si256(ge_a, le_z); + let folded = _mm256_or_si256(hv, _mm256_and_si256(upper, bit20)); + + let eq = _mm256_cmpeq_epi8(folded, nv); + if _mm256_movemask_epi8(eq) != -1i32 { + return false; + } + + i += 32; + } + + while i < len { + if unsafe { *h.add(i) }.to_ascii_lowercase() != needle_lower[i] { + return false; + } + i += 1; + } + true +} + +/// Unsigned range checks (`vcge`/`vcle`) detect uppercase ASCII, bit 5 folds +/// to lowercase, then equality is checked via udot: xors the folded haystack +/// with the pre-lowered needle and dot-product the difference with itself +/// any non-zero byte produces a non-zero u32 lane. udot is emitted via inline +/// asm because `vdotq_u32` is still behind an unstable feature gate. +#[cfg(target_arch = "aarch64")] +#[target_feature(enable = "neon,dotprod")] +unsafe fn eq_lowered_neon_dotprod(h: *const u8, needle_lower: &[u8]) -> bool { + use core::arch::aarch64::*; + + let len = needle_lower.len(); + let mut i = 0usize; + + let a_val = vdupq_n_u8(b'A'); + let z_val = vdupq_n_u8(b'Z'); + let bit20 = vdupq_n_u8(0x20); + + while i + 16 <= len { + let hv = unsafe { vld1q_u8(h.add(i)) }; + let nv = unsafe { vld1q_u8(needle_lower.as_ptr().add(i)) }; + + let upper = vandq_u8(vcgeq_u8(hv, a_val), vcleq_u8(hv, z_val)); + let folded = vorrq_u8(hv, vandq_u8(upper, bit20)); + let xored = veorq_u8(folded, nv); + + let dots: uint32x4_t; + let zero = vdupq_n_u32(0); + unsafe { + core::arch::asm!( + "udot {d:v}.4s, {a:v}.16b, {b:v}.16b", + d = inlateout(vreg) zero => dots, + a = in(vreg) xored, + b = in(vreg) xored, + ); + } + + if vmaxvq_u32(dots) != 0 { + return false; + } + + i += 16; + } + + while i < len { + if unsafe { *h.add(i) }.to_ascii_lowercase() != needle_lower[i] { + return false; + } + i += 1; + } + true +} + +/// Case-insensitive equality of `needle_lower` against the haystack bytes +/// starting at `h`. `needle_lower` must be pre-lowercased (ASCII). +/// +/// # Safety +/// `h` must be valid for reads of `needle_lower.len()` bytes. +#[inline] +pub(crate) unsafe fn eq_lowered_case(haystack: *const u8, needle_lower: &[u8]) -> bool { + #[cfg(target_arch = "x86_64")] + { + if needle_lower.len() >= 32 && std::is_x86_feature_detected!("avx2") { + return unsafe { eq_lowered_avx2(haystack, needle_lower) }; + } + } + #[cfg(target_arch = "aarch64")] + { + if needle_lower.len() >= 16 && std::arch::is_aarch64_feature_detected!("dotprod") { + return unsafe { eq_lowered_neon_dotprod(haystack, needle_lower) }; + } + } + + eq_lowered_scalar(haystack, needle_lower) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn eq_lowered(haystack: &[u8], needle_lower: &[u8]) -> bool { + assert!(haystack.len() >= needle_lower.len()); + unsafe { eq_lowered_case(haystack.as_ptr(), needle_lower) } + } + + #[test] + fn swap_case_toggles_letters() { + assert_eq!(ascii_swap_case(b'n'), b'N'); + assert_eq!(ascii_swap_case(b'N'), b'n'); + assert_eq!(ascii_swap_case(b'z'), b'Z'); + } + + #[test] + fn eq_matches_std_semantics() { + assert!(eq_lowered(b"Hello", b"hello")); + assert!(eq_lowered(b"HELLO WORLD", b"hello")); + assert!(!eq_lowered(b"Hellp", b"hello")); + // Non-letters must not fold: '[' (0x5B) vs '{' (0x7B) differ only in bit 5. + assert!(!eq_lowered(b"A[", b"a{")); + assert!(eq_lowered(b"A{", b"a{")); + // Long inputs exercise the SIMD kernels. + let hay = b"INT STRUCT MUTEX *LOCK(STRUCT MUTEX *LOCK) { RETURN 0; }"; + let needle: Vec = hay.iter().map(|b| b.to_ascii_lowercase()).collect(); + assert!(eq_lowered(hay, &needle)); + let mut bad = needle.clone(); + *bad.last_mut().unwrap() = b'!'; + assert!(!eq_lowered(hay, &bad)); + } +} diff --git a/crates/fff-core/src/simd_string_utils/memmem.rs b/crates/fff-core/src/simd_string_utils/memmem.rs new file mode 100644 index 0000000..a5bda60 --- /dev/null +++ b/crates/fff-core/src/simd_string_utils/memmem.rs @@ -0,0 +1,494 @@ +use super::case::{ascii_swap_case, eq_lowered_case}; +use smallvec::SmallVec; + +// Byte frequency table stolen from memchr +const BYTE_FREQUENCIES: [u8; 256] = [ + 55, 52, 51, 50, 49, 48, 47, 46, 45, 103, 242, 66, 67, 229, 44, 43, // 0x00 + 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 56, 32, 31, 30, 29, 28, // 0x10 + 255, 148, 164, 149, 136, 160, 155, 173, 221, 222, 134, 122, 232, 202, 215, 224, // 0x20 + 208, 220, 204, 187, 183, 179, 177, 168, 178, 200, 226, 195, 154, 184, 174, 126, // 0x30 + 120, 191, 157, 194, 170, 189, 162, 161, 150, 193, 142, 137, 171, 176, 185, + 167, // 0x40 A-O + 186, 112, 175, 192, 188, 156, 140, 143, 123, 133, 128, 147, 138, 146, 114, + 223, // 0x50 P-_ + 151, 249, 216, 238, 236, 253, 227, 218, 230, 247, 135, 180, 241, 233, 246, + 244, // 0x60 a-o + 231, 139, 245, 243, 251, 235, 201, 196, 240, 214, 152, 182, 205, 181, 127, + 27, // 0x70 p-DEL + 212, 211, 210, 213, 228, 197, 169, 159, 131, 172, 105, 80, 98, 96, 97, 81, // 0x80 + 207, 145, 116, 115, 144, 130, 153, 121, 107, 132, 109, 110, 124, 111, 82, 108, // 0x90 + 118, 141, 113, 129, 119, 125, 165, 117, 92, 106, 83, 72, 99, 93, 65, 79, // 0xa0 + 166, 237, 163, 199, 190, 225, 209, 203, 198, 217, 219, 206, 234, 248, 158, 239, // 0xb0 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0xc0 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0xd0 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0xe0 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0xf0 +]; + +#[inline] +fn rank(lower: u8) -> u8 { + if lower.is_ascii_lowercase() { + BYTE_FREQUENCIES[lower as usize].max(BYTE_FREQUENCIES[ascii_swap_case(lower) as usize]) + } else { + BYTE_FREQUENCIES[lower as usize] + } +} + +/// Pick two needle positions with the rarest bytes (case-insensitive) +fn select_rare_pair(needle_lower: &[u8]) -> (usize, usize) { + debug_assert!(needle_lower.len() >= 2); + + let mut best1 = (u8::MAX, 0usize); // (rank, position) + let mut best2 = (u8::MAX, 1usize); + + for (i, &b) in needle_lower.iter().enumerate() { + let r = rank(b); + if r < best1.0 { + best2 = best1; + best1 = (r, i); + } else if r < best2.0 && i != best1.1 { + best2 = (r, i); + } + } + + let i1 = best1.1.min(best2.1); + let i2 = best1.1.max(best2.1); + (i1, i2) +} + +/// Extract a 16-bit bitmask from a NEON comparison result (each byte 0x00 or 0xFF) +/// Bit *i* of the result corresponds to byte *i* of the input vector +#[cfg(target_arch = "aarch64")] +#[target_feature(enable = "neon")] +#[inline] +unsafe fn neon_movemask(v: core::arch::aarch64::uint8x16_t) -> u16 { + use core::arch::aarch64::*; + + // AND each byte with its bit-position mask, then horizontally sum each half. + static BITS: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128]; + let bit_mask = unsafe { vld1q_u8(BITS.as_ptr()) }; + let masked = vandq_u8(v, bit_mask); + let lo = vaddv_u8(vget_low_u8(masked)); + let hi = vaddv_u8(vget_high_u8(masked)); + (lo as u16) | ((hi as u16) << 8) +} + +/// AVX2 packed-pair kernel: scan 32 haystack positions per iteration, +/// checking two rare bytes (case-insensitive) simultaneously +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn find_packed_pair_avx2( + haystack: &[u8], + needle_lower: &[u8], + i1: usize, + i2: usize, +) -> Option { + use core::arch::x86_64::*; + + let n = needle_lower.len(); + let hlen = haystack.len(); + let ptr = haystack.as_ptr(); + let last_start = hlen - n; // last valid match-start position + + let b1 = needle_lower[i1]; + let b1_alt = if b1.is_ascii_lowercase() { + ascii_swap_case(b1) + } else { + b1 + }; + let b2 = needle_lower[i2]; + let b2_alt = if b2.is_ascii_lowercase() { + ascii_swap_case(b2) + } else { + b2 + }; + + let v1_lo = _mm256_set1_epi8(b1 as i8); + let v1_hi = _mm256_set1_epi8(b1_alt as i8); + let v2_lo = _mm256_set1_epi8(b2 as i8); + let v2_hi = _mm256_set1_epi8(b2_alt as i8); + + // Loads come from ptr+offset+i1 and ptr+offset+i2, so we need offset + max(i1,i2) + 31 < hlen. + let max_idx = i1.max(i2); + let max_offset = hlen.saturating_sub(max_idx + 32); + let mut offset = 0usize; + + while offset <= max_offset { + let chunk1 = unsafe { _mm256_loadu_si256(ptr.add(offset + i1) as *const __m256i) }; + let chunk2 = unsafe { _mm256_loadu_si256(ptr.add(offset + i2) as *const __m256i) }; + + // Case-insensitive match: OR both case variants, then AND the two positions. + let eq1 = _mm256_or_si256( + _mm256_cmpeq_epi8(chunk1, v1_lo), + _mm256_cmpeq_epi8(chunk1, v1_hi), + ); + let eq2 = _mm256_or_si256( + _mm256_cmpeq_epi8(chunk2, v2_lo), + _mm256_cmpeq_epi8(chunk2, v2_hi), + ); + + let mut mask = _mm256_movemask_epi8(_mm256_and_si256(eq1, eq2)) as u32; + + // Candidates are visited in increasing position order, so the first + // verified candidate is the leftmost match + while mask != 0 { + let bit = mask.trailing_zeros() as usize; + let candidate = offset + bit; + if candidate > last_start { + return None; + } + if unsafe { eq_lowered_case(ptr.add(candidate), needle_lower) } { + return Some(candidate); + } + mask &= mask - 1; + } + + offset += 32; + } + + // handle remaining characters + if offset <= last_start { + let rare_pos = if rank(needle_lower[i1]) <= rank(needle_lower[i2]) { + i1 + } else { + i2 + }; + let rare_byte = needle_lower[rare_pos]; + let tail_start = offset + rare_pos; + let tail_end = last_start + rare_pos + 1; + if tail_start < tail_end { + let tail_space = &haystack[tail_start..tail_end]; + if rare_byte.is_ascii_lowercase() { + for pos in memchr::memchr2_iter(rare_byte, ascii_swap_case(rare_byte), tail_space) { + let candidate = offset + pos; + if unsafe { eq_lowered_case(ptr.add(candidate), needle_lower) } { + return Some(candidate); + } + } + } else { + for pos in memchr::memchr_iter(rare_byte, tail_space) { + let candidate = offset + pos; + if unsafe { eq_lowered_case(ptr.add(candidate), needle_lower) } { + return Some(candidate); + } + } + } + } + } + + None +} + +#[cfg(target_arch = "aarch64")] +#[target_feature(enable = "neon")] +unsafe fn find_packed_pair_neon( + haystack: &[u8], + needle_lower: &[u8], + i1: usize, + i2: usize, +) -> Option { + use core::arch::aarch64::*; + + let n = needle_lower.len(); + let hlen = haystack.len(); + let ptr = haystack.as_ptr(); + let last_start = hlen - n; + + let b1 = needle_lower[i1]; + let b1_alt = if b1.is_ascii_lowercase() { + ascii_swap_case(b1) + } else { + b1 + }; + let b2 = needle_lower[i2]; + let b2_alt = if b2.is_ascii_lowercase() { + ascii_swap_case(b2) + } else { + b2 + }; + + let v1_lo = vdupq_n_u8(b1); + let v1_hi = vdupq_n_u8(b1_alt); + let v2_lo = vdupq_n_u8(b2); + let v2_hi = vdupq_n_u8(b2_alt); + + let max_idx = i1.max(i2); + let max_offset = hlen.saturating_sub(max_idx + 16); + let mut offset = 0usize; + + while offset <= max_offset { + let chunk1 = unsafe { vld1q_u8(ptr.add(offset + i1)) }; + let chunk2 = unsafe { vld1q_u8(ptr.add(offset + i2)) }; + + // Case-insensitive match: OR both case variants, then AND the two positions. + let eq1 = vorrq_u8(vceqq_u8(chunk1, v1_lo), vceqq_u8(chunk1, v1_hi)); + let eq2 = vorrq_u8(vceqq_u8(chunk2, v2_lo), vceqq_u8(chunk2, v2_hi)); + + let mut mask = unsafe { neon_movemask(vandq_u8(eq1, eq2)) }; + + while mask != 0 { + let bit = mask.trailing_zeros() as usize; + let candidate = offset + bit; + if candidate > last_start { + return None; + } + if unsafe { eq_lowered_case(ptr.add(candidate), needle_lower) } { + return Some(candidate); + } + mask &= mask - 1; + } + + offset += 16; + } + + // Tail: remaining positions that couldn't fill a full vector. + if offset <= last_start { + let rare_pos = if rank(needle_lower[i1]) <= rank(needle_lower[i2]) { + i1 + } else { + i2 + }; + let rare_byte = needle_lower[rare_pos]; + let tail_start = offset + rare_pos; + let tail_end = last_start + rare_pos + 1; + if tail_start < tail_end { + let tail_space = &haystack[tail_start..tail_end]; + if rare_byte.is_ascii_lowercase() { + for pos in memchr::memchr2_iter(rare_byte, ascii_swap_case(rare_byte), tail_space) { + let candidate = offset + pos; + if unsafe { eq_lowered_case(ptr.add(candidate), needle_lower) } { + return Some(candidate); + } + } + } else { + for pos in memchr::memchr_iter(rare_byte, tail_space) { + let candidate = offset + pos; + if unsafe { eq_lowered_case(ptr.add(candidate), needle_lower) } { + return Some(candidate); + } + } + } + } + } + + None +} + +fn find_first_byte_with_memchr(haystack: &[u8], needle_lower: &[u8]) -> Option { + let n = needle_lower.len(); + debug_assert!(n >= 1 && n <= haystack.len()); + + let search_space = &haystack[..=haystack.len() - n]; + let first = needle_lower[0]; + + if first.is_ascii_lowercase() { + let alt = ascii_swap_case(first); + for pos in memchr::memchr2_iter(first, alt, search_space) { + if unsafe { eq_lowered_case(haystack.as_ptr().add(pos), needle_lower) } { + return Some(pos); + } + } + } else { + for pos in memchr::memchr_iter(first, search_space) { + if unsafe { eq_lowered_case(haystack.as_ptr().add(pos), needle_lower) } { + return Some(pos); + } + } + } + None +} + +/// ASCII case-insensitive substring search returning the leftmost match +/// position. `needle_lower` must be pre-lowercased (ASCII). +// pub because it is used in out of the crate benchmarks +#[doc(hidden)] // it's pub only for benches +pub fn find(haystack: &[u8], needle_lower: &[u8]) -> Option { + let n = needle_lower.len(); + if n == 0 { + return Some(0); + } + if n > haystack.len() { + return None; + } + + if n == 1 { + let first = needle_lower[0]; + return if first.is_ascii_lowercase() { + memchr::memchr2(first, ascii_swap_case(first), haystack) + } else { + memchr::memchr(first, haystack) + }; + } + + #[cfg_attr( + not(any(target_arch = "x86_64", target_arch = "aarch64")), + allow(unused_variables) + )] + let (i1, i2) = select_rare_pair(needle_lower); + + #[cfg(target_arch = "x86_64")] + { + if std::is_x86_feature_detected!("avx2") { + // Need enough haystack for at least one vector load. + let max_idx = i1.max(i2); + if haystack.len() >= max_idx + 32 { + return unsafe { find_packed_pair_avx2(haystack, needle_lower, i1, i2) }; + } + } + } + + #[cfg(target_arch = "aarch64")] + { + // Packed-pair wins when the first byte is common (memchr2 drowns in + // false positives), but a rare first byte (z, q, x, ...) makes + // memchr2's raw throughput dominate. Threshold 200 on the frequency + // table splits common letters (s=243, e=253) from rare ones (z=152). + let first_byte_rank = rank(needle_lower[0]); + let max_idx = i1.max(i2); + if first_byte_rank >= 200 && haystack.len() >= max_idx + 16 { + return unsafe { find_packed_pair_neon(haystack, needle_lower, i1, i2) }; + } + } + + // fallbacks to memchr based implementation cause we still have it and it supports more SIMD backends + // TODO convert all the supported backend by memchr and get rid of the fallback + find_first_byte_with_memchr(haystack, needle_lower) +} + +/// A case insensitive find that works better with smaller strings, doesn't unwrap a complicated +/// AVX backend we use for grep because only cpu flags check takes usually more time than find itself +pub fn find_case_insensitive_short(haystack: &[u8], needle: &[u8]) -> Option { + debug_assert!(haystack.len() < 1024); + let mut needle_lower: SmallVec<[u8; 64]> = SmallVec::from_slice(needle); + needle_lower.make_ascii_lowercase(); + + find(haystack, &needle_lower) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn reference_find(haystack: &[u8], needle_lower: &[u8]) -> Option { + if needle_lower.is_empty() { + return Some(0); + } + if needle_lower.len() > haystack.len() { + return None; + } + haystack + .windows(needle_lower.len()) + .position(|w| w.eq_ignore_ascii_case(needle_lower)) + } + + #[test] + fn basic_case_insensitive() { + assert_eq!(find(b"Hello World", b"hello"), Some(0)); + assert_eq!(find(b"Hello World", b"world"), Some(6)); + assert_eq!(find(b"NOMORE bugs", b"nomore"), Some(0)); + assert_eq!(find(b"Hello World", b"xyz"), None); + assert!(find(b"Hello World", b"o w").is_some()); + } + + #[test] + fn edge_cases() { + assert_eq!(find(b"ab", b"ab"), Some(0)); + assert_eq!(find(b"AB", b"ab"), Some(0)); + assert_eq!(find(b"a", b"ab"), None); + assert_eq!(find(b"anything", b""), Some(0)); + assert_eq!(find(b"", b"x"), None); + assert_eq!(find(b"xxA", b"a"), Some(2)); + assert_eq!(find(b"xx:", b":"), Some(2)); + } + + #[test] + fn returns_leftmost_match() { + assert_eq!(find(b"foo FOO foo", b"foo"), Some(0)); + let mut big = vec![b'.'; 300]; + big[100..103].copy_from_slice(b"FoO"); + big[200..203].copy_from_slice(b"foo"); + assert_eq!(find(&big, b"foo"), Some(100)); + } + + #[test] + fn non_letter_bytes_do_not_case_fold() { + // '[' (0x5B) and '{' (0x7B) differ only in bit 5 but are not letters. + // A fold implemented as a bare `| 0x20` would falsely match these. + assert_eq!(find(b"A[", b"a{"), None); + assert_eq!(find(b"x@y", b"x`y"), None); + assert_eq!(find(b"a]b", b"a}b"), None); + assert_eq!(find(b"A{", b"a{"), Some(0)); + } + + #[test] + fn matches_reference_on_random_inputs() { + // Deterministic xorshift PRNG — no external deps. + let mut state = 0x9E3779B97F4A7C15u64; + let mut next = move || { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + state + }; + + // Alphabet with letters, both-case pairs, and 0x20-differing symbols. + let alphabet = b"aAbBzZ [{@`]}^~_0.\n"; + for _ in 0..2000 { + let hlen = (next() % 200) as usize; + let nlen = (next() % 8) as usize; + let haystack: Vec = (0..hlen) + .map(|_| alphabet[(next() % alphabet.len() as u64) as usize]) + .collect(); + let needle: Vec = (0..nlen) + .map(|_| alphabet[(next() % alphabet.len() as u64) as usize].to_ascii_lowercase()) + .collect(); + + assert_eq!( + find(&haystack, &needle), + reference_find(&haystack, &needle), + "mismatch for haystack={:?} needle={:?}", + haystack, + needle, + ); + } + } + + #[test] + fn long_haystack_simd_paths() { + let haystack = + b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaTHIS_IS_A_LONG_NEEDLE_TESTbbbbbbbbbbbbbbbbbb"; + assert_eq!(find(haystack, b"this_is_a_long_needle_test"), Some(32)); + assert_eq!(find(haystack, b"this_is_a_long_needle_testz"), None); + + // Needle >= 16 bytes exercises SIMD verify. + let haystack2 = b"int STRUCT MUTEX *LOCK(struct mutex *lock) { return 0; }"; + assert_eq!(find(haystack2, b"struct mutex *lock"), Some(4)); + + let upper_hay = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + assert_eq!(find(upper_hay, b"qrstuvwxyz0123456789a"), Some(16)); + assert_eq!(find(upper_hay, b"qrstuvwxyz01234567899"), None); + + // Needle at very end / very start. + let end_hay = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxfind_me"; + assert_eq!(find(end_hay, b"find_me"), Some(end_hay.len() - 7)); + assert_eq!(find(end_hay, b"xx"), Some(0)); + + // 1KB haystack with needle near the end. + let mut big = vec![b'z'; 1024]; + big[1000..1010].copy_from_slice(b"hElLo_WoRl"); + assert_eq!(find(&big, b"hello_wo"), Some(1000)); + assert_eq!(find(&big, b"hello_world"), None); + } + + #[test] + fn rare_pair_selection() { + let (i1, i2) = select_rare_pair(b"nomore"); + let ranks: Vec = b"nomore".iter().map(|&b| rank(b)).collect(); + let (r1, r2) = (ranks[i1], ranks[i2]); + for (i, &r) in ranks.iter().enumerate() { + if i != i1 && i != i2 { + assert!(r1 <= r || r2 <= r, "pair ({i1},{i2}) not optimal"); + } + } + } +} diff --git a/crates/fff-core/src/simd_string_utils/mod.rs b/crates/fff-core/src/simd_string_utils/mod.rs new file mode 100644 index 0000000..bb66b43 --- /dev/null +++ b/crates/fff-core/src/simd_string_utils/mod.rs @@ -0,0 +1,5 @@ +//! SIMD-accelerated string utilities: case flipping/folding and +//! case-insensitive substring search. + +pub mod case; +pub mod memmem; diff --git a/crates/fff-core/src/sort_buffer.rs b/crates/fff-core/src/sort_buffer.rs new file mode 100644 index 0000000..940169b --- /dev/null +++ b/crates/fff-core/src/sort_buffer.rs @@ -0,0 +1,131 @@ +use parking_lot::Mutex; +use std::mem::MaybeUninit; + +// this originally happen to be in TLS but there is a limit of TLS +// + the storage itself is not free, so now we rely on the fact that most calls +// are sequential in practice and allocate ONLY when we have a parallel access +static SORT_BUFFER: Mutex> = Mutex::new(Vec::new()); + +fn ensure_capacity(buf: &mut Vec, required: usize) { + if buf.capacity() < required { + let len = buf.len(); + buf.reserve(required - len); + } +} + +struct SharedSortBuf { + guard: parking_lot::MutexGuard<'static, Vec>, +} + +impl SharedSortBuf { + fn as_slice_mut(&mut self, len: usize) -> &mut [MaybeUninit] { + let align = std::mem::align_of::>(); + let size = std::mem::size_of::>(); + let required = len.saturating_mul(size).saturating_add(align); + ensure_capacity(&mut self.guard, required); + + // SAFETY: the Vec is only 1-byte aligned, so we over-allocate by + // `align` bytes and shift the pointer to satisfy T's alignment. + // Callers never read uninitialised data through the returned slice. + unsafe { + let ptr = self.guard.as_mut_ptr(); + let offset = ptr.align_offset(align); + debug_assert!(offset != usize::MAX && offset + len * size <= self.guard.capacity()); + std::slice::from_raw_parts_mut(ptr.add(offset) as *mut MaybeUninit, len) + } + } +} + +fn try_lock_shared_buf() -> Option { + SORT_BUFFER.try_lock().map(|guard| SharedSortBuf { guard }) +} + +pub fn sort_with_buffer(slice: &mut [T], compare: F) +where + F: FnMut(&T, &T) -> std::cmp::Ordering, +{ + match try_lock_shared_buf() { + Some(mut buf) => { + let typed = buf.as_slice_mut::(slice.len()); + glidesort::sort_with_buffer_by(slice, typed, compare); + } + None => glidesort::sort_by(slice, compare), + } +} + +pub fn sort_by_key_with_buffer(slice: &mut [T], key_fn: F) +where + K: Ord, + F: FnMut(&T) -> K, +{ + match try_lock_shared_buf() { + Some(mut buf) => { + let typed = buf.as_slice_mut::(slice.len()); + glidesort::sort_with_buffer_by_key(slice, typed, key_fn); + } + None => glidesort::sort_by_key(slice, key_fn), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sort_with_buffer() { + let mut data = vec![5, 2, 8, 1, 9]; + sort_with_buffer(&mut data, |a, b| a.cmp(b)); + assert_eq!(data, vec![1, 2, 5, 8, 9]); + } + + #[test] + fn test_sort_by_key_with_buffer() { + let mut data = vec![(1, 50), (2, 20), (3, 80), (4, 10), (5, 90)]; + sort_by_key_with_buffer(&mut data, |a| a.1); + assert_eq!(data, vec![(4, 10), (2, 20), (1, 50), (3, 80), (5, 90)]); + } + + #[test] + fn test_reverse_sort() { + let mut data = vec![1, 2, 3, 4, 5]; + sort_with_buffer(&mut data, |a, b| b.cmp(a)); + assert_eq!(data, vec![5, 4, 3, 2, 1]); + } + + #[test] + fn test_empty_slice() { + let mut data: Vec = vec![]; + sort_with_buffer(&mut data, |a, b| a.cmp(b)); + assert_eq!(data, Vec::::new()); + } + + #[test] + fn test_single_element() { + let mut data = vec![42]; + sort_with_buffer(&mut data, |a, b| a.cmp(b)); + assert_eq!(data, vec![42]); + } + + #[test] + fn test_with_duplicates() { + let mut data = vec![3, 1, 4, 1, 5, 9, 2, 6, 5]; + sort_with_buffer(&mut data, |a, b| a.cmp(b)); + assert_eq!(data, vec![1, 1, 2, 3, 4, 5, 5, 6, 9]); + } + + #[test] + fn test_descending_order() { + let mut data = vec![3, 1, 4, 1, 5, 9, 2, 6, 5]; + sort_with_buffer(&mut data, |a, b| b.cmp(a)); + assert_eq!(data, vec![9, 6, 5, 5, 4, 3, 2, 1, 1]); + } + + #[test] + fn test_simple_descending() { + let mut data = vec![100, 300, 200]; + sort_with_buffer(&mut data, |a, b| b.cmp(a)); + assert_eq!(data[0], 300); + assert_eq!(data[1], 200); + assert_eq!(data[2], 100); + } +} diff --git a/crates/fff-core/src/stable_vec.rs b/crates/fff-core/src/stable_vec.rs new file mode 100644 index 0000000..950607a --- /dev/null +++ b/crates/fff-core/src/stable_vec.rs @@ -0,0 +1,177 @@ +use std::alloc::{self, Layout}; +use std::ptr::NonNull; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +/// Vector that guarantees no re-alloc happening at runtime +pub(crate) struct StableVec { + inner: Arc>, +} + +struct StableBuf { + ptr: NonNull, + cap: usize, + /// Atomic because: + /// 1. `push(&self)` must mutate this through a shared `&StableBuf`, + /// which requires interior mutability. + /// 2. Arc clones (e.g. post-scan snapshots) read `len` outside the + /// picker lock, concurrent with an appending writer. Acquire/Release + /// on len is what makes "observed len ⇒ element bytes initialized" + /// actually hold. + /// + /// Arc wrapping only shares ownership of the buffer; it does NOT + /// synchronize access to fields inside the shared buffer. + len: AtomicUsize, +} + +// SAFETY: StableBuf is a thread-safe container when T is send + sync +// There is another application level constraint: mutations are safe +// when they are atomic updates, not read + update. +unsafe impl Send for StableBuf {} +unsafe impl Sync for StableBuf {} + +impl Drop for StableBuf { + fn drop(&mut self) { + let len = *self.len.get_mut(); + unsafe { + std::ptr::drop_in_place(std::ptr::slice_from_raw_parts_mut(self.ptr.as_ptr(), len)); + if self.cap > 0 { + let layout = Layout::array::(self.cap).expect("layout"); + alloc::dealloc(self.ptr.as_ptr().cast(), layout); + } + } + } +} + +impl StableVec { + pub fn from_vec_with_reserve(mut vec: Vec, extra: usize) -> Self { + vec.reserve(extra); + let cap = vec.capacity(); + let len = vec.len(); + + let inner = if cap == 0 { + StableBuf { + ptr: NonNull::dangling(), + cap: 0, + len: AtomicUsize::new(0), + } + } else { + // Take ownership of the Vec's buffer without running element + // drops; we hand them off to the StableBuf. + let mut vec = std::mem::ManuallyDrop::new(vec); + let ptr = NonNull::new(vec.as_mut_ptr()).expect("non-null"); + StableBuf { + ptr, + cap, + len: AtomicUsize::new(len), + } + }; + + Self { + inner: Arc::new(inner), + } + } + + /// Append. Returns `false` if capacity is exhausted (item dropped). + /// + /// Safe to call via `&self` as long as the caller holds the outer + /// picker write lock (single-writer invariant). + #[inline] + pub fn push(&self, item: T) -> bool { + let cap = self.inner.cap; + let len = self.inner.len.load(Ordering::Acquire); + if len >= cap { + debug_assert!( + false, + "StableVec: push would exceed capacity ({len} at capacity {cap})" + ); + tracing::error!( + len, + capacity = cap, + "StableVec: capacity exhausted — dropping item to prevent reallocation" + ); + return false; + } + unsafe { + std::ptr::write(self.inner.ptr.as_ptr().add(len), item); + } + self.inner.len.store(len + 1, Ordering::Release); + true + } + + // this method is specifically private because you probably need to use + // live_count if you are trying to access this method + #[inline] + pub fn len(&self) -> usize { + self.inner.len.load(Ordering::Acquire) + } + + /// Mutable element access for in-place field updates. Never shifts. + /// + /// LATENT UB: produces `&mut T` aliasing Arc-shared memory; the + /// `&mut self` on StableVec does NOT imply unique access to the + /// `StableBuf` when sibling Arc clones exist. Safe in practice + /// because callers hold the picker write lock and writes target + /// disjoint fields, but strictly forbidden by the aliasing model. + #[inline] + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + let len = self.inner.len.load(Ordering::Acquire); + if index >= len { + return None; + } + unsafe { Some(&mut *self.inner.ptr.as_ptr().add(index)) } + } + + #[inline] + pub fn last(&self) -> Option<&T> { + let len = self.len(); + if len == 0 { + None + } else { + unsafe { Some(&*self.inner.ptr.as_ptr().add(len - 1)) } + } + } + + /// Iterate mutably for in-place field updates. Never shifts storage. + /// Same latent-UB caveat as [`get_mut`]: `&mut T` into Arc-shared memory. + #[inline] + pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> { + let len = self.inner.len.load(Ordering::Acquire); + unsafe { std::slice::from_raw_parts_mut(self.inner.ptr.as_ptr(), len).iter_mut() } + } +} + +impl Clone for StableVec { + #[inline] + fn clone(&self) -> Self { + Self { + inner: Arc::clone(&self.inner), + } + } +} + +impl std::fmt::Debug for StableVec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("StableVec").field(&self.len()).finish() + } +} + +impl std::ops::Deref for StableVec { + type Target = [T]; + #[inline] + fn deref(&self) -> &[T] { + let len = self.len(); + unsafe { std::slice::from_raw_parts(self.inner.ptr.as_ptr(), len) } + } +} + +impl std::ops::DerefMut for StableVec { + /// LATENT UB: `&mut [T]` aliases Arc-shared memory. Kept for + /// Index/IndexMut ergonomics at call sites that write disjoint + /// fields under the picker write lock. See module-level doc. + #[inline] + fn deref_mut(&mut self) -> &mut [T] { + let len = self.inner.len.load(Ordering::Acquire); + unsafe { std::slice::from_raw_parts_mut(self.inner.ptr.as_ptr(), len) } + } +} diff --git a/crates/fff-core/src/types.rs b/crates/fff-core/src/types.rs new file mode 100644 index 0000000..932fb10 --- /dev/null +++ b/crates/fff-core/src/types.rs @@ -0,0 +1,971 @@ +use std::io::Read; +use std::path::{Path, PathBuf}; +#[cfg(not(target_os = "windows"))] +use std::sync::OnceLock; +use std::sync::atomic::{AtomicI32, AtomicU8, AtomicU64, AtomicUsize, Ordering}; + +#[cfg(not(target_os = "windows"))] +use crate::constants::{FRESH_MMAP_THRESHOLD, MMAP_THRESHOLD}; +use crate::constants::{MAX_CACHED_CONTENT_BYTES, MAX_FFFILE_SIZE, PATH_BUF_SIZE}; +use crate::constraints::Constrainable; +use crate::query_tracker::QueryMatchEntry; +use crate::simd_path::ArenaPtr; +use fff_query_parser::{FFFQuery, FuzzyQuery, Location}; + +/// Different sources of the string storage used by FFF +/// implements as a deduplicated 16-bytes alined heap +/// can be stored in RAM or on disk +pub trait FFFStringStorage { + /// Resolve the arena for a [`FileItem`] (handles base vs overflow split). + fn arena_for(&self, file: &FileItem) -> ArenaPtr; + + /// The base arena (scan-time paths). + fn base_arena(&self) -> ArenaPtr; + /// The overflow arena (paths added after the last full scan). + fn overflow_arena(&self) -> ArenaPtr; +} + +impl FFFStringStorage for ArenaPtr { + #[inline] + fn arena_for(&self, _file: &FileItem) -> ArenaPtr { + *self + } + + #[inline] + fn base_arena(&self) -> ArenaPtr { + *self + } + + #[inline] + fn overflow_arena(&self) -> ArenaPtr { + *self + } +} + +pub trait FileSliceExt { + fn live_count(&self) -> usize; +} + +impl FileSliceExt for [FileItem] { + #[inline] + fn live_count(&self) -> usize { + self.iter().filter(|f| !f.is_deleted()).count() + } +} + +pub struct FileItemFlags; + +impl FileItemFlags { + pub const BINARY: u8 = 1 << 0; + /// Tombstone — file was deleted but index slot is preserved so + /// bigram indices for other files stay valid. + pub const DELETED: u8 = 1 << 1; + /// File was added after the last full reindex; its indices point + /// into the overflow builder arena, not the base arena. + pub const OVERFLOW: u8 = 1 << 2; +} + +pub struct DirFlags; + +impl DirFlags { + pub const OVERFLOW: u8 = 1 << 0; +} + +/// A directory in the file index. Shares chunk arena with file paths. +#[derive(Debug)] +pub struct DirItem { + flags: u8, + pub(crate) path: crate::simd_path::ChunkedString, + /// Byte offset where the last path segment begins (e.g. for `src/components/` + /// this is 4, pointing to `components/`). Used for dirname-bonus scoring. + last_segment_offset: u16, + /// Maximum `access_frecency_score` among direct child files. + /// Atomic so parallel frecency updates can write directly without juggling. + max_access_frecency: AtomicI32, +} + +impl Clone for DirItem { + fn clone(&self) -> Self { + Self { + flags: self.flags, + path: self.path.clone(), + last_segment_offset: self.last_segment_offset, + max_access_frecency: AtomicI32::new(self.max_access_frecency()), + } + } +} + +impl DirItem { + #[inline(always)] + pub fn is_overflow(&self) -> bool { + self.flags & DirFlags::OVERFLOW != 0 + } + + pub(crate) fn new(path: crate::simd_path::ChunkedString, last_segment_offset: u16) -> Self { + Self { + path, + flags: 0, + last_segment_offset, + max_access_frecency: AtomicI32::new(0), + } + } + + /// Byte offset of the last path segment within the directory path. + #[inline] + pub fn last_segment_offset(&self) -> u16 { + self.last_segment_offset + } + + /// Current max access frecency score. + #[inline] + pub fn max_access_frecency(&self) -> i32 { + self.max_access_frecency.load(Ordering::Relaxed) + } + + /// Atomically update the directory's frecency score if the given score is larger. + /// Safe to call from parallel threads. + #[inline] + pub fn update_frecency_if_larger(&self, score: i32) { + self.max_access_frecency.fetch_max(score, Ordering::Relaxed); + } + + /// Reset frecency to zero (used before full recomputation). + #[inline] + pub fn reset_frecency(&self) { + self.max_access_frecency.store(0, Ordering::Relaxed); + } + + pub(crate) fn read_relative_path<'a>(&self, arena: ArenaPtr, buf: &'a mut [u8]) -> &'a str { + self.path.read_to_buf(arena, buf) + } + + /// Relative dir path as owned String (cold path). + pub fn relative_path(&self, arena: impl FFFStringStorage) -> String { + let mut out = String::new(); + let ptr = if self.is_overflow() { + arena.overflow_arena() + } else { + arena.base_arena() + }; + + self.path.write_to_string(ptr, &mut out); + out + } + + /// Write the last segment (dirname) of this directory path to `out`. + pub fn write_dir_name(&self, arena: ArenaPtr, out: &mut String) { + out.clear(); + let total = self.path.byte_len as usize; + let offset = self.last_segment_offset as usize; + if offset >= total { + return; + } + // Read the full path, then slice from last_segment_offset + let mut buf = [0u8; PATH_BUF_SIZE]; + let full = self.path.read_to_buf(arena, &mut buf); + out.push_str(&full[offset..]); + } + + /// The dirname (last segment) as an owned String. Cold path. + pub fn dir_name(&self, arena: impl FFFStringStorage) -> String { + let mut out = String::new(); + let ptr = if self.is_overflow() { + arena.overflow_arena() + } else { + arena.base_arena() + }; + self.write_dir_name(ptr, &mut out); + out + } + + /// A path = base_path + "/" + relative. Cold path, allocates. + pub fn absolute_path(&self, arena: impl FFFStringStorage, base_path: &Path) -> PathBuf { + let rel = self.relative_path(arena); + if rel.is_empty() { + base_path.to_path_buf() + } else { + base_path.join(&rel) + } + } +} + +impl Constrainable for DirItem { + #[inline] + fn write_file_name(&self, arena: ArenaPtr, out: &mut String) { + // For dirs, the "file name" equivalent is the last path segment + self.write_dir_name(arena, out); + } + + #[inline] + fn write_relative_path(&self, arena: ArenaPtr, out: &mut String) { + self.path.write_to_string(arena, out); + } + + #[inline] + fn git_status(&self) -> Option { + None + } + + #[inline] + fn is_overflow(&self) -> bool { + DirItem::is_overflow(self) + } +} + +#[derive(Debug)] +pub struct FileItem { + pub size: u64, + pub modified: u64, + pub access_frecency_score: i16, + pub modification_frecency_score: i16, + pub git_status: Option, + pub(crate) path: crate::simd_path::ChunkedString, + pub(crate) parent_dir_index: u32, + flags: AtomicU8, + /// Lazy mmap cache. Only populated by the actual file read, controlled by the budget. + #[cfg(not(target_os = "windows"))] + content: OnceLock, +} + +impl Clone for FileItem { + fn clone(&self) -> Self { + Self { + path: self.path.clone(), + parent_dir_index: self.parent_dir_index, + size: self.size, + modified: self.modified, + access_frecency_score: self.access_frecency_score, + modification_frecency_score: self.modification_frecency_score, + git_status: self.git_status, + flags: AtomicU8::new(self.flags.load(Ordering::Relaxed)), + // on clone we have to reset the content lock + #[cfg(not(target_os = "windows"))] + content: OnceLock::new(), + } + } +} + +/// Single-block read used by the binary classifier. Most binaries reveal a +/// NUL byte within the first filesystem block, so 16 KB lets one read settle +/// the classification for typical files while keeping the scratch buffer +/// small enough to live on the stack. +pub const BINARY_CLASSIFICATION_CHUNK_SIZE: usize = 16 * 1024; + +/// A file is treated as binary if any NUL byte appears in the scanned prefix. +#[inline] +pub(crate) fn detect_binary_content(content: &[u8]) -> bool { + memchr::memchr(0, content).is_some() +} + +impl FileItem { + pub fn new_raw( + filename_start: u16, + size: u64, + modified: u64, + git_status: Option, + is_binary: bool, + ) -> Self { + let mut flags = 0u8; + if is_binary { + flags |= FileItemFlags::BINARY; + } + + let mut path = crate::simd_path::ChunkedString::empty(); + path.filename_offset = filename_start; + + Self { + path, + parent_dir_index: u32::MAX, + size, + modified, + access_frecency_score: 0, + modification_frecency_score: 0, + git_status, + flags: AtomicU8::new(flags), + #[cfg(not(target_os = "windows"))] + content: OnceLock::new(), + } + } + + /// Returns an absolute path of the file + pub fn absolute_path(&self, arena: impl FFFStringStorage, base_path: &Path) -> PathBuf { + let mut buf = [0u8; PATH_BUF_SIZE]; + let rel = self.path.read_to_buf(arena.arena_for(self), &mut buf); + base_path.join(rel) + } + + pub(crate) fn set_path(&mut self, path: crate::simd_path::ChunkedString) { + self.path = path; + } + + pub fn dir_str(&self, arena: impl FFFStringStorage) -> String { + let mut s = String::with_capacity(64); + self.path.write_dir_to(arena.arena_for(self), &mut s); + s + } + + pub(crate) fn write_dir_str(&self, arena: ArenaPtr, out: &mut String) { + self.path.write_dir_to(arena, out); + } + + pub fn file_name(&self, arena: impl FFFStringStorage) -> String { + let mut s = String::with_capacity(32); + self.path.write_filename_to(arena.arena_for(self), &mut s); + s + } + + pub(crate) fn write_file_name_from_arena(&self, arena: ArenaPtr, out: &mut String) { + self.path.write_filename_to(arena, out); + } + + pub fn relative_path(&self, arena: impl FFFStringStorage) -> String { + let mut s = String::with_capacity(64); + self.path.write_to_string(arena.arena_for(self), &mut s); + s + } + + pub(crate) fn write_relative_path_from_arena(&self, arena: ArenaPtr, out: &mut String) { + self.path.write_to_string(arena, out); + } + + pub fn relative_path_len(&self) -> usize { + self.path.byte_len as usize + } + + pub fn filename_offset_in_relative_path(&self) -> usize { + self.path.filename_offset as usize + } + + pub(crate) fn relative_path_eq(&self, arena: ArenaPtr, other: &str) -> bool { + if other.len() != self.path.byte_len as usize { + return false; + } + let mut buf = [0u8; 512]; + let mine = self.path.read_to_buf(arena, &mut buf); + mine == other + } + + pub(crate) fn relative_path_starts_with(&self, arena: ArenaPtr, prefix: &str) -> bool { + let mut buf = [0u8; PATH_BUF_SIZE]; + let path = self.path.read_to_buf(arena, &mut buf); + path.starts_with(prefix) + } + + /// Write `base_path + '/' + relative_path` into `buf` and return it + /// as `&Path`. Takes a fixed-size array so the buffer can live on + /// the stack (no heap allocation, no bounds checks in the hot loop). + pub(crate) fn write_absolute_path<'a>( + &self, + arena: ArenaPtr, + base_path: &Path, + buf: &'a mut [u8; PATH_BUF_SIZE], + ) -> &'a Path { + let base = base_path.as_os_str().as_encoded_bytes(); + let base_len = base.len(); + buf[..base_len].copy_from_slice(base); + let sep_len = if base_len > 0 && base[base_len - 1] != std::path::MAIN_SEPARATOR as u8 { + buf[base_len] = std::path::MAIN_SEPARATOR as u8; + 1 + } else { + 0 + }; + + let base_end_idx = base_len + sep_len; + let relative_portion_str = self.path.read_to_buf(arena, &mut buf[base_end_idx..]); + let rel_len = relative_portion_str.len(); + let total = base_end_idx + rel_len; + // Stored relative paths are '/'-canonical; rewrite to the OS-native + // separator so the result matches git-cache keys, the frecency DB, and + // Win32 file APIs. No-op off Windows. + crate::path_utils::nativize_slashes_in_place(&mut buf[base_end_idx..total]); + Path::new(unsafe { std::str::from_utf8_unchecked(&buf[..total]) }) + } + + /// Write the relative path into `buf` and NUL-terminate, returning + /// a `&CStr`. Fixed-size array so the buffer is stack-allocatable. + /// + /// Paired with a parent-directory fd this eliminates the per-file + /// absolute-path memcpy: `openat(dir_fd, cstr.as_ptr(), O_RDONLY)` + /// resolves the name relative to `dir_fd`. Unix-only. + #[cfg(unix)] + pub(crate) fn write_relative_cstr<'a>( + &self, + arena: ArenaPtr, + buf: &'a mut [u8; PATH_BUF_SIZE], + ) -> &'a std::ffi::CStr { + // Reserve the last byte for the NUL terminator. + let rel = self.path.read_to_buf(arena, &mut buf[..PATH_BUF_SIZE - 1]); + let n = rel.len(); + buf[n] = 0; + // SAFETY: `buf[..=n]` ends with the NUL we just wrote and + // filesystem paths never contain interior NULs. + unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(&buf[..=n]) } + } + + #[inline] + pub fn total_frecency_score(&self) -> i32 { + self.access_frecency_score as i32 + self.modification_frecency_score as i32 + } + + #[allow(dead_code)] + #[inline] + pub(crate) fn is_likely_hot(&self) -> bool { + self.access_frecency_score > 0 || self.git_status.is_some() + } + + /// Reads a fixed bytes count from the file optimized for quick speed of opening + #[inline] + pub(crate) fn read_trimmed_into_buf( + &self, + base_fd: i32, + base_path: &Path, + arena: ArenaPtr, + path_buf: &mut [u8; PATH_BUF_SIZE], + buf: &mut [u8], + ) -> usize { + #[cfg(unix)] + { + self.read_into_buf_unix(base_fd, base_path, arena, path_buf, buf) + } + #[cfg(not(unix))] + { + let _ = base_fd; + self.read_into_buf_std(base_path, arena, path_buf, buf) + } + } + + #[cfg(unix)] + fn read_into_buf_unix( + &self, + base_fd: libc::c_int, + base_path: &Path, + arena: ArenaPtr, + path_buf: &mut [u8; PATH_BUF_SIZE], + buf: &mut [u8], + ) -> usize { + let fd = if base_fd >= 0 { + let relative_path = self.write_relative_cstr(arena, path_buf); + // SAFETY: `relative_path` is NUL-terminated, `base_fd` is a + // valid directory descriptor owned by the caller. + unsafe { libc::openat(base_fd, relative_path.as_ptr(), libc::O_RDONLY) } + } else { + use std::os::unix::io::IntoRawFd; + let abs = self.write_absolute_path(arena, base_path, path_buf); + match std::fs::File::open(abs) { + Ok(f) => f.into_raw_fd(), + Err(e) => { + tracing::error!(?e, "Failed to fopen file"); + return 0; + } + } + }; + if fd < 0 { + return 0; + } + + let mut filled = 0usize; + while filled < buf.len() { + // SAFETY: `fd` is an owned descriptor, `buf[filled..]` is a + // valid writable slice for `buf.len() - filled` bytes. + let n = unsafe { + libc::read( + fd, + buf[filled..].as_mut_ptr() as *mut libc::c_void, + (buf.len() - filled) as libc::size_t, + ) + }; + if n <= 0 { + break; + } + filled += n as usize; + } + + // SAFETY: matching close for the owned descriptor. + unsafe { libc::close(fd) }; + filled + } + + #[cfg(not(unix))] + fn read_into_buf_std( + &self, + base_path: &Path, + arena: ArenaPtr, + path_buf: &mut [u8; PATH_BUF_SIZE], + buf: &mut [u8], + ) -> usize { + let abs = self.write_absolute_path(arena, base_path, path_buf); + let Ok(mut f) = std::fs::File::open(abs) else { + return 0; + }; + let mut filled = 0usize; + while filled < buf.len() { + match f.read(&mut buf[filled..]) { + Ok(0) => break, + Ok(n) => filled += n, + Err(_) => return 0, + } + } + filled + } + + #[inline] + pub fn is_binary(&self) -> bool { + self.flags.load(Ordering::Relaxed) & FileItemFlags::BINARY != 0 + } + + #[inline] + pub fn set_binary(&self, val: bool) { + if val { + self.flags + .fetch_or(FileItemFlags::BINARY, Ordering::Relaxed); + } else { + self.flags + .fetch_and(!FileItemFlags::BINARY, Ordering::Relaxed); + } + } + + /// Chunked classifier of the binary content of the file chunk by chunk + /// accepts path which to reuse the allocated buffer for absolute path read + pub(crate) fn detect_binary_per_byte(&self, path: &Path, chunk: &mut [u8]) { + if self.size == 0 { + return; + } + + let Ok(mut file) = std::fs::OpenOptions::new() + .write(false) + .read(true) + .open(path) + else { + tracing::error!(path = ?path.display(), "Failed to open indexed file"); + return; + }; + + loop { + match file.read(chunk) { + Ok(0) => break, + Err(e) => { + tracing::error!(?e, "Failed to read file chunk"); + break; + } + Ok(n) => { + if detect_binary_content(&chunk[..n]) { + self.set_binary(true); + } + } + } + } + } + + #[inline] + pub fn is_deleted(&self) -> bool { + self.flags.load(Ordering::Relaxed) & FileItemFlags::DELETED != 0 + } + + #[inline] + #[doc(hidden)] + /// Don't use it, use FilePicker::delete_file + pub fn set_deleted(&self, val: bool) { + if val { + self.flags + .fetch_or(FileItemFlags::DELETED, Ordering::Relaxed); + } else { + self.flags + .fetch_and(!FileItemFlags::DELETED, Ordering::Relaxed); + } + } + + #[inline] + pub fn is_overflow(&self) -> bool { + self.flags.load(Ordering::Relaxed) & FileItemFlags::OVERFLOW != 0 + } + + #[inline] + pub fn set_overflow(&self, val: bool) { + if val { + self.flags + .fetch_or(FileItemFlags::OVERFLOW, Ordering::Relaxed); + } else { + self.flags + .fetch_and(!FileItemFlags::OVERFLOW, Ordering::Relaxed); + } + } +} + +impl FileItem { + /// Invalidate the cached mmap content, has to be called every time the file is updated. + /// + /// Call this when the background watcher detects that the file has been modified. + /// On Unix, a file that is truncated while mapped can cause SIGBUS. On Windows, + /// the stale buffer simply won't reflect the new contents. In both cases, + /// invalidating ensures a fresh read on the next access. + #[cfg(not(target_os = "windows"))] + pub fn invalidate_mmap(&mut self, budget: &ContentCacheBudget) { + if self.content.get().is_some() { + budget.cached_count.fetch_sub(1, Ordering::Relaxed); + budget.cached_bytes.fetch_sub(self.size, Ordering::Relaxed); + } + + self.content = OnceLock::new(); + } + + #[cfg(target_os = "windows")] + pub fn invalidate_mmap(&mut self, _: &ContentCacheBudget) {} + + pub fn update_metadata( + &mut self, + budget: &ContentCacheBudget, + modified_secs: Option, + new_size: Option, + ) { + if let Some(modified) = modified_secs + && self.modified < modified + { + self.modified = modified; + } + + self.invalidate_mmap(budget); + + if let Some(size) = new_size { + self.size = size; + } + } + + /// Get the cached file contents or lazily load and cache them. + /// + /// Returns `None` if the file is too large, empty, can't be opened, **or + /// the cache budget is exhausted**. Callers that need content regardless + /// of the budget should use [`get_content_for_search`]. + /// + /// After the first call, this is lock-free (just an atomic load + pointer deref). + /// + /// On Windows we never back this cache — `memmap2` would require a full + /// `std::fs::read` heap copy and the OS page cache already absorbs repeat + /// reopens. Returning `None` keeps callers on the scratch-read slow path + /// and avoids duplicating every indexed file on the heap. + #[cfg(target_os = "windows")] + pub(crate) fn get_cached_content( + &self, + _arena: ArenaPtr, + _base_path: &Path, + _budget: &ContentCacheBudget, + ) -> Option<&[u8]> { + None + } + + /// Returns a reference to a cached mmap of the file's contents. + /// + /// SAFETY-CRITICAL: callers must hold the picker read lock for as long as the returned slice is in use. + #[cfg(not(target_os = "windows"))] + pub(crate) fn get_cached_content( + &self, + arena: ArenaPtr, + base_path: &Path, + budget: &ContentCacheBudget, + ) -> Option<&[u8]> { + if let Some(content) = self.content.get() { + return Some(content); + } + + if self.size < MMAP_THRESHOLD || self.size > budget.max_file_size { + return None; + } + + // Check cache budget before creating a new persistent cache entry. + let count = budget.cached_count.load(Ordering::Relaxed); + let bytes = budget.cached_bytes.load(Ordering::Relaxed); + let max_files = budget.max_files; + let max_bytes = budget.max_bytes; + if count >= max_files || bytes + self.size > max_bytes { + return None; + } + + let path = self.absolute_path(arena, base_path); + let file = std::fs::File::open(&path).ok()?; + // SAFETY: the mmap is backed by the kernel page cache and reflects + // file updates; the only risk is SIGBUS on a concurrent truncate, + // which the watcher mitigates by invalidating on modification. + let mmap = unsafe { memmap2::Mmap::map(&file) }.ok()?; + let result = self.content.get_or_init(|| mmap); + + budget.cached_count.fetch_add(1, Ordering::Relaxed); + budget.cached_bytes.fetch_add(self.size, Ordering::Relaxed); + + Some(result) + } + + /// Get file content for searching — **always returns content** for eligible + /// files, even when the persistent cache budget is exhausted. + /// + /// The caller provides a reusable `path_buf` (pre-filled with `base_path/`) + /// and its `base_len` to avoid allocations when constructing the absolute path. + #[inline] + pub(crate) fn get_content_for_search<'a>( + &'a self, + buf: &'a mut Vec, + #[cfg_attr(target_os = "windows", allow(unused_variables))] mmap_slot: &'a mut MmapSlot, + arena: ArenaPtr, + base_path: &Path, + budget: &ContentCacheBudget, + ) -> Option<&'a [u8]> { + #[cfg(not(target_os = "windows"))] + { + // Fast path: persistent cache hit (zero-copy). Safe here because + // grep callers hold the picker read lock for the lifetime of the + // returned slice — see [`Self::get_cached_content`] safety note. + if let Some(cached) = self.get_cached_content(arena, base_path, budget) { + return Some(cached); + } + } + + let max_file_size = budget.max_file_size; + if self.is_binary() || self.size == 0 || self.size > max_file_size { + return None; + } + + let abs = self.absolute_path(arena, base_path); + + #[cfg(not(target_os = "windows"))] + if self.size >= FRESH_MMAP_THRESHOLD { + let file = std::fs::File::open(&abs).ok()?; + let mmap = unsafe { memmap2::Mmap::map(&file) }.ok()?; + let stored = mmap_slot.insert(mmap); + return Some(&stored[..]); + } else { + let _ = (mmap_slot, arena); + } + + let len = self.size as usize; + buf.resize(len, 0); + + let mut file = std::fs::File::open(&abs).ok()?; + file.read_exact(buf).ok()?; + Some(buf.as_slice()) + } +} + +/// Per-thread scratch slot owning a transient mmap returned from +/// [`FileItem::get_content_for_search`]. `Option` on Unix, +/// unit on Windows where mmap is unused. +#[cfg(not(target_os = "windows"))] +pub type MmapSlot = Option; +#[cfg(target_os = "windows")] +pub type MmapSlot = (); + +impl Constrainable for FileItem { + #[inline] + fn write_file_name(&self, arena: ArenaPtr, out: &mut String) { + self.path.write_filename_to(arena, out); + } + + #[inline] + fn write_relative_path(&self, arena: ArenaPtr, out: &mut String) { + self.path.write_to_string(arena, out); + } + + #[inline] + fn git_status(&self) -> Option { + self.git_status + } + + #[inline] + fn is_overflow(&self) -> bool { + FileItem::is_overflow(self) + } +} + +#[derive(Debug, Clone, Default)] +pub struct Score { + pub total: i32, + pub base_score: i32, + pub filename_bonus: i32, + pub special_filename_bonus: i32, + pub frecency_boost: i32, + pub git_status_boost: i32, + pub distance_penalty: i32, + pub current_file_penalty: i32, + pub combo_match_boost: i32, + pub path_alignment_bonus: i32, + pub exact_match: bool, + pub match_type: &'static str, +} + +#[derive(Debug, Clone, Copy)] +pub struct PaginationArgs { + pub offset: usize, + pub limit: usize, +} + +impl Default for PaginationArgs { + fn default() -> Self { + Self { + offset: 0, + limit: 100, + } + } +} + +#[derive(Debug, Clone)] +pub struct ScoringContext<'a> { + pub query: &'a FFFQuery<'a>, + pub project_path: Option<&'a Path>, + pub current_file: Option<&'a str>, + pub max_typos: u16, + pub max_threads: usize, + pub last_same_query_match: Option, + pub combo_boost_score_multiplier: i32, + pub min_combo_count: u32, + pub pagination: PaginationArgs, +} + +impl ScoringContext<'_> { + pub fn effective_query(&self) -> &str { + match &self.query.fuzzy_query { + FuzzyQuery::Text(t) => t, + FuzzyQuery::Parts(parts) if !parts.is_empty() => parts[0], + _ => self.query.raw_query.trim(), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct SearchResult<'a> { + pub items: Vec<&'a FileItem>, + pub scores: Vec, + pub total_matched: usize, + pub total_files: usize, + pub location: Option, +} + +/// Search result for directory-only fuzzy search. +#[derive(Debug, Clone, Default)] +pub struct DirSearchResult<'a> { + pub items: Vec<&'a DirItem>, + pub scores: Vec, + pub total_matched: usize, + pub total_dirs: usize, +} + +/// A single item in a mixed (files + directories) search result. +#[derive(Debug, Clone)] +pub enum MixedItemRef<'a> { + File(&'a FileItem), + Dir(&'a DirItem), +} + +/// Search result for mixed (files + directories) fuzzy search. +/// Items are interleaved by total score in descending order. +#[derive(Debug, Clone, Default)] +pub struct MixedSearchResult<'a> { + pub items: Vec>, + pub scores: Vec, + pub total_matched: usize, + pub total_files: usize, + pub total_dirs: usize, + pub location: Option, +} + +impl Default for MixedItemRef<'_> { + fn default() -> Self { + // Should never be used, exists only for Default derive on MixedSearchResult + unreachable!("MixedItemRef::default should not be called") + } +} + +#[derive(Debug)] +pub struct ContentCacheBudget { + pub max_files: usize, + pub max_bytes: u64, + pub max_file_size: u64, + pub cached_count: AtomicUsize, + pub cached_bytes: AtomicU64, +} + +impl ContentCacheBudget { + pub fn unlimited() -> Self { + Self { + max_files: usize::MAX, + max_bytes: u64::MAX, + max_file_size: MAX_FFFILE_SIZE, + cached_count: AtomicUsize::new(0), + cached_bytes: AtomicU64::new(0), + } + } + + pub fn zero() -> Self { + Self { + max_files: 0, + max_bytes: 0, + max_file_size: 0, + cached_count: AtomicUsize::new(0), + cached_bytes: AtomicU64::new(0), + } + } + + // Byte budget + pub fn is_exhausted(&self) -> bool { + self.cached_count.load(Ordering::Relaxed) >= self.max_files + || self.cached_bytes.load(Ordering::Relaxed) >= self.max_bytes + } + + pub fn new_for_repo(file_count: usize) -> Self { + let max_files = if file_count > 50_000 { + 5_000 + } else if file_count > 10_000 { + 10_000 + } else { + 30_000 // effectively unlimited for small repos + }; + + let max_bytes = if file_count > 50_000 { + 128 * 1024 * 1024 // 128 MB + } else if file_count > 10_000 { + 256 * 1024 * 1024 // 256 MB + } else { + MAX_CACHED_CONTENT_BYTES // 512 MB + }; + + Self { + max_files, + max_bytes, + max_file_size: MAX_FFFILE_SIZE, + cached_count: AtomicUsize::new(0), + cached_bytes: AtomicU64::new(0), + } + } + + /// Build a budget from caller-supplied overrides. + /// + /// Each argument is a cap; `0` means "use the library default for that + /// cap" (inherits from [`Self::default`], which is `new_for_repo(30_000)`). + /// Returns `None` when every cap is `0`, signalling to the picker that it + /// should auto-size the budget from the final scanned file count rather + /// than applying an explicit override. + pub fn from_overrides(max_files: usize, max_bytes: u64, max_file_size: u64) -> Option { + if max_files == 0 && max_bytes == 0 && max_file_size == 0 { + return None; + } + + let mut budget = Self::default(); + if max_files > 0 { + budget.max_files = max_files; + } + if max_bytes > 0 { + budget.max_bytes = max_bytes; + } + if max_file_size > 0 { + budget.max_file_size = max_file_size; + } + Some(budget) + } + + pub fn reset(&self) { + self.cached_count.store(0, Ordering::Relaxed); + self.cached_bytes.store(0, Ordering::Relaxed); + } +} + +impl Default for ContentCacheBudget { + fn default() -> Self { + Self::new_for_repo(30_000) + } +} diff --git a/crates/fff-core/src/walk/mod.rs b/crates/fff-core/src/walk/mod.rs new file mode 100644 index 0000000..a533f0b --- /dev/null +++ b/crates/fff-core/src/walk/mod.rs @@ -0,0 +1,146 @@ +//! Filesystem traversal backend. Selects one implementation at compile time: +//! - `zlob`: zlob's native parallel walker (requires the Zig toolchain). +//! - `ripgrep`: the `ignore` crate (ripgrep's walker), used by default. +//! +//! Both expose [`walk_collect_files`] with identical semantics so the rest of +//! the crate stays backend-agnostic. + +use crate::types::FileItem; +use std::path::Path; + +#[cfg(feature = "zlob")] +mod zlob; +#[cfg(feature = "zlob")] +pub(crate) use zlob::walk_collect_files; + +#[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] +mod ripgrep; +#[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] +pub(crate) use ripgrep::walk_collect_files; + +pub(crate) struct WalkOutput { + pub(crate) pairs: Vec<(FileItem, String)>, + pub(crate) ignore_rules: Option, +} + +pub(crate) struct WalkIgnoreRules { + #[cfg(feature = "zlob")] + inner: ::zlob::walk::WalkerOutcomeRules, + #[cfg(not(feature = "zlob"))] + _never: std::convert::Infallible, +} + +// SAFETY: the underlying storage is immutable, heap-owned, and thread-safe to +// read from concurrently (mirrors zlob's `IgnoreRules: Send + Sync`). +unsafe impl Send for WalkIgnoreRules {} +unsafe impl Sync for WalkIgnoreRules {} + +impl std::fmt::Debug for WalkIgnoreRules { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("WalkIgnoreRules") + } +} + +// In ripgrep builds `WalkIgnoreRules` is never constructed (the `_never` +// field is uninhabited), so its methods are legitimately dead there. +#[cfg_attr(not(feature = "zlob"), allow(dead_code))] +impl WalkIgnoreRules { + /// Returns `true` if the provided path is ignored by the collected rule set + /// + /// `relative_path` has to be relative to the walker's provided base path + pub(crate) fn is_ignored(&self, relative_path: &Path) -> bool { + #[cfg(feature = "zlob")] + { + self.inner + .rules() + .is_some_and(|rules| rules.is_ignored(relative_path)) + } + #[cfg(not(feature = "zlob"))] + { + let _ = relative_path; + match self._never {} + } + } + + // The old `is_ignored_untrusted` variant was folded away when zlob's + // ignore matcher moved to full ancestor enumeration — trailing-slash + // sniffing on the input is now sufficient for external queries. +} + +#[cfg(test)] +mod tests { + use super::walk_collect_files; + use std::fs; + use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; + + // Backend-agnostic parity check: both the zlob and ripgrep walkers must + // respect .gitignore, skip hidden files in a git repo, and surface the + // expected file set with a correct synced count. + #[test] + fn collects_files_respecting_gitignore() { + let dir = tempfile::tempdir().unwrap(); + let root = dir.path(); + fs::create_dir(root.join(".git")).unwrap(); + fs::create_dir(root.join("src")).unwrap(); + fs::create_dir(root.join("target")).unwrap(); + fs::write(root.join(".gitignore"), "target/\n*.log\n").unwrap(); + fs::write(root.join("Cargo.toml"), "x").unwrap(); + fs::write(root.join("debug.log"), "").unwrap(); + fs::write(root.join("src/main.rs"), "fn main() {}").unwrap(); + fs::write(root.join("target/out.bin"), "bin").unwrap(); + + let counter = Arc::new(AtomicUsize::new(0)); + let out = walk_collect_files(root, true, false, 1, &counter).unwrap(); + + let mut names: Vec = out.pairs.into_iter().map(|(_, rel)| rel).collect(); + names.sort(); + + assert!(names.contains(&"Cargo.toml".to_string())); + assert!(names.iter().any(|n| n.ends_with("main.rs"))); + // target/ and *.log are gitignored; .git/ is skipped. + assert!(!names.iter().any(|n| n.contains("target"))); + assert!(!names.iter().any(|n| n.ends_with(".log"))); + assert!(!names.iter().any(|n| n.contains(".git/"))); + assert_eq!(counter.load(Ordering::Relaxed), names.len()); + } + + // Non-git roots prune known non-code directories (node_modules). + #[test] + fn prunes_non_code_dirs_for_non_git_root() { + let dir = tempfile::tempdir().unwrap(); + let root = dir.path(); + fs::create_dir(root.join("node_modules")).unwrap(); + fs::write(root.join("node_modules/lib.js"), "x").unwrap(); + fs::write(root.join("index.js"), "x").unwrap(); + + let counter = Arc::new(AtomicUsize::new(0)); + let out = walk_collect_files(root, false, false, 1, &counter).unwrap(); + let names: Vec = out.pairs.into_iter().map(|(_, rel)| rel).collect(); + + assert!(names.iter().any(|n| n.ends_with("index.js"))); + assert!(!names.iter().any(|n| n.contains("node_modules"))); + } + + // Only the zlob backend surfaces reusable ignore rules; they must match + // the same tree the walk respected. + #[cfg(feature = "zlob")] + #[test] + fn surfaces_reusable_ignore_rules() { + use std::path::Path; + + let dir = tempfile::tempdir().unwrap(); + let root = dir.path(); + fs::create_dir(root.join(".git")).unwrap(); + fs::write(root.join(".gitignore"), "target/\n*.log\n").unwrap(); + fs::write(root.join("Cargo.toml"), "x").unwrap(); + + let counter = Arc::new(AtomicUsize::new(0)); + let out = walk_collect_files(root, true, false, 1, &counter).unwrap(); + + let rules = out.ignore_rules.expect("zlob surfaces ignore rules"); + assert!(rules.is_ignored(Path::new("target/"))); + assert!(rules.is_ignored(Path::new("debug.log"))); + assert!(!rules.is_ignored(Path::new("Cargo.toml"))); + } +} diff --git a/crates/fff-core/src/walk/ripgrep.rs b/crates/fff-core/src/walk/ripgrep.rs new file mode 100644 index 0000000..249de2d --- /dev/null +++ b/crates/fff-core/src/walk/ripgrep.rs @@ -0,0 +1,72 @@ +use crate::background_watcher::is_git_file; +use crate::ignore::non_git_repo_overrides; +use crate::types::FileItem; +use crate::walk::WalkOutput; +use ignore::WalkBuilder; +use std::path::Path; +use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, +}; + +#[tracing::instrument(skip_all, name = "ripgrep walker", level = "info")] +pub(crate) fn walk_collect_files( + base_path: &Path, + is_git_repo: bool, + follow_symlinks: bool, + threads: usize, + synced_files_count: &Arc, +) -> crate::Result { + let mut walk_builder = WalkBuilder::new(base_path); + walk_builder + // this is a very important guard for the user opening ~/ or other root non-git dir + .hidden(!is_git_repo) + .git_ignore(true) + .git_exclude(true) + .git_global(true) + .ignore(true) + .follow_links(follow_symlinks) + .threads(threads); + + if !is_git_repo && let Some(overrides) = non_git_repo_overrides(base_path) { + walk_builder.overrides(overrides); + } + + let walker = walk_builder.build_parallel(); + + let pairs = parking_lot::Mutex::new(Vec::<(FileItem, String)>::new()); + walker.run(|| { + let pairs = &pairs; + let counter = Arc::clone(synced_files_count); + let base_path = base_path.to_path_buf(); + + Box::new(move |result| { + let Ok(entry) = result else { + return ignore::WalkState::Continue; + }; + + if entry.file_type().is_some_and(|ft| ft.is_file()) { + let path = entry.path(); + + // Ignore walkers sometimes surface files inside `.git/` + // when the base is itself a git repo — skip them. + if is_git_file(path) { + return ignore::WalkState::Continue; + } + + let metadata = entry.metadata().ok(); + let (file_item, rel_path) = + FileItem::new_from_walk(path, &base_path, None, metadata.as_ref()); + + pairs.lock().push((file_item, rel_path)); + counter.fetch_add(1, Ordering::Relaxed); + } + ignore::WalkState::Continue + }) + }); + + Ok(WalkOutput { + pairs: pairs.into_inner(), + ignore_rules: None, + }) +} diff --git a/crates/fff-core/src/walk/zlob.rs b/crates/fff-core/src/walk/zlob.rs new file mode 100644 index 0000000..0978ad1 --- /dev/null +++ b/crates/fff-core/src/walk/zlob.rs @@ -0,0 +1,111 @@ +//! Filesystem traversal backed by zlob's native parallel walker. +//! Active when the `zlob` feature is enabled (requires the Zig toolchain). + +use crate::file_picker::is_known_binary_extension_basename; +use crate::ignore::IGNORED_DIRS; +use crate::types::FileItem; +use crate::walk::{WalkIgnoreRules, WalkOutput}; +use std::path::Path; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use zlob::walk::{WalkBuilder, WalkFlags, WalkMetadata, WalkState}; + +const PROGRESS_STEP: usize = 13; + +#[tracing::instrument(skip_all, name = "zlob walker", level = "info")] +pub(crate) fn walk_collect_files( + base_path: &Path, + is_git_repo: bool, + follow_symlinks: bool, + threads: usize, + synced_files_count: &Arc, +) -> crate::Result { + // gitignore on; skip hidden on non-git roots (so `~/` doesn't recurse into + // ~/.cache, ~/.config, etc.); optionally follow symlinks. + let mut flags = WalkFlags::GITIGNORE; + if !is_git_repo { + flags |= WalkFlags::SKIP_HIDDEN; + } + if follow_symlinks { + flags |= WalkFlags::FOLLOW_SYMLINKS; + } + + let mut builder = WalkBuilder::new(base_path) + .map_err(|e| crate::Error::WalkFailed(format!("WalkBuilder::new: {e:?}")))?; + builder + .options(flags) + .threads(threads) + // Bulk-fetch the only metadata FileItem needs; zlob never stats more. + .metadata(WalkMetadata::SIZE | WalkMetadata::MTIME); + + if !is_git_repo + && !IGNORED_DIRS.is_empty() + && let Err(e) = builder.extra_ignore(IGNORED_DIRS) + { + // Interior NUL in one of the extra_ignore patterns would fail + // here — treat as if no extras were supplied rather than + // aborting the whole walk. + tracing::warn!(?e, "zlob extra_ignore rejected; walking without it"); + } + + let pairs = parking_lot::Mutex::new(Vec::<(FileItem, String)>::new()); + + let outcome = match builder.run(|entry| { + if !entry.is_file() { + return WalkState::Continue; + } + let rel_bytes = entry.relative_path_bytes(); + + // `basename()` returns `&str` for files only. + let basename = entry.basename().unwrap_or(""); + let is_binary = is_known_binary_extension_basename(basename); + + let size = entry.size().unwrap_or(0); + // zlob reports mtime in ns since the Unix epoch; FileItem wants secs. + let modified = entry + .modified_ns() + .map(|ns| (ns / 1_000_000_000).max(0) as u64) + .unwrap_or(0); + + let basename_offset = entry.basename_offset_in_relative(); + // zlob emits '/'-separated relative paths, which is fff's canonical + // internal form on every platform — store them verbatim. + let rel_str = String::from_utf8_lossy(rel_bytes).into_owned(); + let item = FileItem::new_raw(basename_offset, size, modified, None, is_binary); + + let mut guard = pairs.lock(); + guard.push((item, rel_str)); + let n = guard.len(); + drop(guard); + + if n % PROGRESS_STEP == 0 { + synced_files_count.store(n, Ordering::Relaxed); + } + + WalkState::Continue + }) { + Ok(outcome) => outcome, + Err(e) => { + // Preserve whatever we collected before the failure so the caller + // can still surface a partial index instead of nothing. + tracing::error!(?e, "zlob walk failed"); + return Err(crate::Error::WalkFailed(format!("{e:?}"))); + } + }; + + let pairs = pairs.into_inner(); + // Always report the exact final total regardless of the last step. + synced_files_count.store(pairs.len(), Ordering::Relaxed); + + // Retain the ignore rules only when the walk actually gathered some + // (git roots with .gitignore/.ignore). Otherwise callers fall back. + let ignore_rules = outcome + .rules() + .is_some() + .then(|| WalkIgnoreRules { inner: outcome }); + + Ok(WalkOutput { + pairs, + ignore_rules, + }) +} diff --git a/crates/fff-core/tests/bigram_overlay_coherence_test.rs b/crates/fff-core/tests/bigram_overlay_coherence_test.rs new file mode 100644 index 0000000..6cee64e --- /dev/null +++ b/crates/fff-core/tests/bigram_overlay_coherence_test.rs @@ -0,0 +1,1656 @@ +//! Stress tests for the bigram overlay layer. +//! +//! These tests create a real git repository with many files, build the bigram +//! index, then perform loops of creates / edits / deletes and verify that grep +//! always returns correct results through the overlay. Finally, a git commit + +//! rescan cycle verifies the index is rebuilt cleanly. +//! +//! # Known bugs exposed by these tests +//! +//! 1. **Overflow files invisible to grep** — `grep_search` only iterates +//! base-file bits from the bigram candidate bitset. Overflow files +//! (index >= base_count) are never included. `BigramOverlay::query_added` +//! exists but is dead code — never called from the grep path. +//! See: grep.rs lines ~1787-1855. + +mod overflow_frecency_segfault; + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Duration; +use tempfile::TempDir; + +use fff_search::file_picker::{FFFMode, FilePicker, FuzzySearchOptions}; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{ + FilePickerOptions, PaginationArgs, QueryParser, SharedFilePicker, SharedFrecency, +}; + +/// Stress test: 200 base files, 3 rounds of edits + deletes. New files +/// are tracked but NOT verified via grep (see Group 3 for that bug). +#[test] +fn bigram_overlay_coherence_stress_base_edits_and_deletes() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let initial_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + // Sanity: all 200 tokens findable. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + for (_, token) in &initial_files { + assert!( + grep_count(picker, token) >= 1, + "Initial token {token} should be findable" + ); + } + } + + let mut live_tokens: Vec<(String, String)> = initial_files.clone(); + let mut dead_tokens: Vec = Vec::new(); + + // Sleep so mtime advances past the scan snapshot. + std::thread::sleep(Duration::from_millis(1100)); + + for round in 0..3 { + // -- DELETE: remove first 5 live base files -- + let delete_count = 5.min(live_tokens.len()); + let to_delete: Vec<(String, String)> = live_tokens.drain(..delete_count).collect(); + for (name, token) in &to_delete { + let path = base.join(name); + fs::remove_file(&path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker.remove_file_by_path(&path), + "round {round}: remove({name}) should succeed" + ); + } + dead_tokens.push(token.clone()); + } + + // -- EDIT: modify next 5 live base files -- + let edit_count = 5.min(live_tokens.len()); + for i in 0..edit_count { + let (ref name, ref mut old_token) = live_tokens[i]; + let new_token = format!("EDITED_R{round}_{i:04}"); + write_file_with_token(base, name, &new_token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker.handle_create_or_modify(base.join(name)).is_some(), + "round {round}: modify({name}) should succeed" + ); + } + dead_tokens.push(old_token.clone()); + *old_token = new_token; + } + + // -- VERIFY: all live base tokens findable, all dead tokens gone -- + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + for (name, token) in &live_tokens { + let count = grep_count(picker, token); + assert!( + count >= 1, + "round {round}: live token {token} ({name}) should be findable, got {count}" + ); + } + + for token in &dead_tokens { + assert_eq!( + grep_count(picker, token), + 0, + "round {round}: dead token {token} should NOT be findable" + ); + } + } + } + + stop_picker(&shared_picker); +} + +/// Simulate a long editing session: 10 rounds of edits to 20 base files. +/// Only the latest content should be searchable. +#[test] +fn bigram_overlay_coherence_long_session_incremental_edits() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + let file_count = 20; + let edits_per_file = 10; + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + let mut latest_tokens: Vec = repo_files[..file_count] + .iter() + .map(|(_, token)| token.clone()) + .collect(); + + for edit_round in 0..edits_per_file { + for file_idx in 0..file_count { + let (ref name, _) = repo_files[file_idx]; + let new_token = format!("LONG_EDIT_F{file_idx:04}_R{edit_round:04}"); + write_file_with_token(base, name, &new_token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(name)); + } + latest_tokens[file_idx] = new_token; + } + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + for (file_idx, token) in latest_tokens.iter().enumerate() { + let (ref name, _) = repo_files[file_idx]; + assert!( + grep_count(picker, token) >= 1, + "{name}: latest token {token} should be findable" + ); + } + + // Spot-check old tokens are gone. + for file_idx in [0, 5, 10, 15, 19] { + if file_idx >= file_count { + continue; + } + let (ref name, ref seed_token) = repo_files[file_idx]; + assert_eq!( + grep_count(picker, seed_token), + 0, + "{name}: seed token should not be findable" + ); + let mid = format!("LONG_EDIT_F{file_idx:04}_R0003"); + assert_eq!( + grep_count(picker, &mid), + 0, + "{name}: intermediate token should not be findable" + ); + } + } + + stop_picker(&shared_picker); +} + +/// Resurrect tombstoned base files: delete then re-create with new content. +#[test] +fn bigram_overlay_coherence_resurrect_tombstoned_file() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + let (ref target_name, ref target_seed_token) = repo_files[3]; + let target_path = base.join(target_name); + + // Delete. + fs::remove_file(&target_path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&target_path); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert_eq!(grep_count(picker, target_seed_token), 0); + } + + // Re-create with new content. + write_file_with_token(base, target_name, "RESURRECTED_TOKEN"); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!(picker.handle_create_or_modify(&target_path).is_some()); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert!( + grep_count(picker, "RESURRECTED_TOKEN") >= 1, + "resurrected token should be findable" + ); + assert_eq!( + grep_count(picker, target_seed_token), + 0, + "old token should not be findable after resurrect" + ); + } + + stop_picker(&shared_picker); +} + +/// Prove the overlay is doing real work for base file modifications. +/// Uses files with diverse content so the bigram index is discriminating. +#[test] +fn bigram_overlay_coherence_proves_contribution_for_modified_base() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + // Pick the 2nd file from the realistic repo to modify. + let (ref target_name, _) = repo_files[1]; + let target_path = base.join(target_name); + + // Replace with content containing a unique token that shares + // NO bigrams with the original thematic content. + let unique = "XYZZY_PLUGH_WIZARDRY"; + fs::write( + &target_path, + format!("fn beta() {{ println!(\"{unique}\"); }}\n"), + ) + .unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(&target_path); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + let with_overlay = grep_count(picker, unique); + assert_eq!(with_overlay, 1, "overlay should find the new token"); + } + + stop_picker(&shared_picker); +} + +/// Rapidly create-then-delete the same base file path 10 times. +#[test] +fn bigram_overlay_coherence_rapid_create_delete_same_base_path() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + + // volatile.rs is added ON TOP of the realistic repo as a base file. + fs::write(base.join("volatile.rs"), "initial volatile content\n").unwrap(); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + // Sanity: a token from the realistic repo is findable. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let (_, ref token) = repo_files[0]; + assert!( + grep_count(picker, token) >= 1, + "sanity: realistic repo token should be findable" + ); + } + + std::thread::sleep(Duration::from_millis(1100)); + + let volatile_path = base.join("volatile.rs"); + + for cycle in 0..10 { + let token = format!("VOLATILE_CYCLE_{cycle:03}"); + + // Overwrite. + write_file_with_token(base, "volatile.rs", &token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(&volatile_path); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert!( + grep_count(picker, &token) >= 1, + "cycle {cycle}: {token} should be findable" + ); + if cycle > 0 { + let prev = format!("VOLATILE_CYCLE_{:03}", cycle - 1); + assert_eq!( + grep_count(picker, &prev), + 0, + "cycle {cycle}: previous token should be gone" + ); + } + } + + // Delete (tombstone). + fs::remove_file(&volatile_path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&volatile_path); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert_eq!( + grep_count(picker, &token), + 0, + "cycle {cycle}: {token} should be gone after delete" + ); + } + + // Re-create for next cycle. + write_file_with_token(base, "volatile.rs", &format!("placeholder_{cycle}")); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(&volatile_path); + } + } + + stop_picker(&shared_picker); +} + +/// New overflow files should be searchable via grep. +/// +/// To trigger the bug, we need an effective bigram index (diverse file +/// content so prefiltering is actually active). With identical-template +/// files, ALL bigrams are either ubiquitous (in every file) or unique +/// (in only one file), so the index retains no columns and prefiltering +/// is always bypassed — masking the bug. +/// +/// The realistic repo seeds 200 files with 4 thematic groups so bigram +/// columns have moderate cardinality (retained by the index), making +/// the prefilter discriminating. +#[test] +fn bigram_overlay_coherence_overflow_files_searchable_via_grep() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + // Verify bigram index was built and is effective. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert!(picker.bigram_index().is_some()); + assert!(picker.bigram_overlay().is_some()); + + // Sanity: a query with content from one file finds exactly that file. + assert!( + grep_count(picker, "REPO_TOKEN_0000") >= 1, + "sanity: known content should be findable" + ); + } + + std::thread::sleep(Duration::from_millis(1100)); + + // Add a new overflow file whose content has bigrams shared with + // existing files (e.g., "SELECT" shares bigrams with group A files). + let new_path = base.join("overflow_new.rs"); + fs::write( + &new_path, + "pub fn recalculate_velocity_delta() {\n \ + let v = SELECT_transaction();\n \ + println!(\"delta: {}\", v);\n}\n", + ) + .unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!(picker.handle_create_or_modify(&new_path).is_some()); + } + + // Overflow file is tracked. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert_eq!(picker.get_overflow_files().len(), 1); + } + + // BUG: grep_search's bigram candidate bitset only covers base files + // (indices 0..base_count). Overflow files at index >= base_count are + // never set as candidates. BigramOverlay::query_added() exists but + // is dead code — never called from the grep path. + // + // Search for content unique to the overflow file. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert!( + grep_count(picker, "recalculate_velocity_delta") >= 1, + "BUG: overflow file content should be findable via grep but \ + bigram prefiltering skips overflow files" + ); + } + + stop_picker(&shared_picker); +} + +/// Mixed tombstones and overflow: delete base files AND add new ones. +/// Tombstone assertions pass; overflow grep assertions expose the bug. +#[test] +fn bigram_overlay_coherence_mixed_tombstones_and_overflow() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + // Delete first 15 base files. + let deleted_tokens: Vec = repo_files[..15] + .iter() + .map(|(_, token)| token.clone()) + .collect(); + for (name, _) in &repo_files[..15] { + let path = base.join(name); + fs::remove_file(&path).unwrap(); + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&path); + } + + // Add 15 new overflow files. + let mut new_tokens = Vec::new(); + for i in 0..15 { + let name = format!("replacement_{i:04}.rs"); + let token = format!("REPLACEMENT_{i:04}"); + write_file_with_token(base, &name, &token); + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(&name)); + new_tokens.push(token); + } + + { + // assert + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + // deleted tokens are gon + for token in &deleted_tokens { + assert_eq!( + grep_count(picker, token), + 0, + "deleted token {token} should not be findable" + ); + } + + // Surviving base files still findable. + for (name, token) in &repo_files[15..] { + assert!( + grep_count(picker, token) == 1, + "surviving {name} token should be findable" + ); + } + + // New tokens needs to be findable + for token in &new_tokens { + assert!( + grep_count(picker, token) == 1, + "BUG: overflow token {token} should be findable" + ); + } + } + + stop_picker(&shared_picker); +} + +#[test] +fn bigram_overlay_coherence_full_stress_loop_with_overflow() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let initial_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + let mut live_base: Vec<(String, String)> = initial_files; + let mut live_overflow: Vec<(String, String)> = Vec::new(); + let mut dead_tokens: Vec = Vec::new(); + + std::thread::sleep(Duration::from_millis(1100)); + + for round in 0..3 { + // -- DELETE 5 base files -- + let to_delete: Vec<_> = live_base.drain(..5).collect(); + for (name, token) in &to_delete { + let path = base.join(name); + fs::remove_file(&path).unwrap(); + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!(picker.remove_file_by_path(&path)); + dead_tokens.push(token.clone()); + } + + // -- EDIT 5 base files -- + for i in 0..5.min(live_base.len()) { + let (ref name, ref mut old_token) = live_base[i]; + let new_token = format!("EDITED_R{round}_{i:04}"); + write_file_with_token(base, name, &new_token); + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(name)); + dead_tokens.push(old_token.clone()); + *old_token = new_token; + } + + // -- CREATE 5 overflow files -- + for i in 0..5 { + let name = format!("new_r{round}_{i:04}.rs"); + let token = format!("NEWFILE_R{round}_{i:04}"); + write_file_with_token(base, &name, &token); + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(&name)); + live_overflow.push((name, token)); + } + + // -- VERIFY base files -- + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + for (name, token) in &live_base { + assert!( + grep_count(picker, token) >= 1, + "round {round}: base token {token} ({name}) should be findable" + ); + } + + for token in &dead_tokens { + assert_eq!( + grep_count(picker, token), + 0, + "round {round}: dead token {token} should NOT be findable" + ); + } + + // BUG: overflow files not findable via grep. + for (name, token) in &live_overflow { + assert!( + grep_count(picker, token) >= 1, + "round {round}: BUG overflow token {token} ({name}) not findable via grep" + ); + } + } + } + + stop_picker(&shared_picker); +} + +/// Overflow files can be edited and deleted through the picker. +/// Verifies the overlay tracks overflow changes even though grep may not +/// search them (that's the separate bug in Group 2). +#[test] +fn bigram_overlay_coherence_overflow_file_edit_and_delete() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + // Add overflow files and track their paths. + let mut overflow_files: Vec<(PathBuf, String)> = Vec::new(); + for i in 0..10 { + let name = format!("overflow_{i}.rs"); + let token = format!("OVERFLOW_ORIG_{i}"); + let path = base.join(&name); + write_file_with_token(base, &name, &token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(&path); + } + overflow_files.push((path, token)); + } + + // Verify overflow files are tracked in the picker. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert_eq!( + picker.get_overflow_files().len(), + 10, + "should have 10 overflow files" + ); + } + + // Edit all overflow files. + let mut edited_tokens = Vec::new(); + for (i, (path, _)) in overflow_files.iter().enumerate() { + let name = path.file_name().unwrap().to_str().unwrap(); + let new_token = format!("OVERFLOW_EDITED_{i}"); + write_file_with_token(base, name, &new_token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(path); + } + edited_tokens.push(new_token); + } + + // Delete first 5 overflow files. + for i in 0..5 { + let (ref path, _) = overflow_files[i]; + fs::remove_file(path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(path); + } + } + + // Verify 5 overflow remain live (tombstones still occupy slots by design — + // StableVec never shifts, so get_overflow_files().len() stays at 10). + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let live = picker + .get_overflow_files() + .iter() + .filter(|f| !f.is_deleted()) + .count(); + assert_eq!( + live, 5, + "should have 5 live overflow files after deleting 5" + ); + } + + stop_picker(&shared_picker); +} + +// =================================================================== +// Group 4: Rescan after git commit. Tests that `trigger_rescan` picks +// up committed changes and that the file list is refreshed. +// =================================================================== + +/// After editing base files, committing, and rescanning, the new content +/// should be searchable. +/// +/// NOTE: `trigger_rescan` replaces `sync_data` which drops the bigram +/// index (it lives inside `FileSync`). After rescan, grep falls back to +/// full search (no bigram prefiltering) which is correct but slower. +#[test] +fn bigram_overlay_coherence_rescan_after_git_commit() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + let base_count = repo_files.len(); // 200 + git_init_and_commit(base); + + let (shared_picker, shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + // Edit 10 base files and add 5 new files. + let mut edited_tokens = Vec::new(); + for i in 0..10 { + let (ref name, _) = repo_files[i]; + let token = format!("PRE_COMMIT_EDIT_{i:04}"); + write_file_with_token(base, name, &token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(name)); + } + edited_tokens.push(token); + } + + let mut new_tokens = Vec::new(); + for i in 0..5 { + let name = format!("committed_new_{i:04}.rs"); + let token = format!("COMMITTED_NEW_{i:04}"); + write_file_with_token(base, &name, &token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(&name)); + } + new_tokens.push(token); + } + + // Phase 2: Commit and rescan. + git_add_and_commit(base, "batch edit"); + shared_picker + .trigger_full_rescan_async(&shared_frecency) + .expect("rescan should succeed"); + + // After trigger_rescan, sync_data is replaced (and bigram_index dropped + // with it). Wait for the synchronous scan to finish. + assert!( + shared_picker.wait_for_scan(Duration::from_secs(15)), + "Timed out waiting for scan to complete" + ); + + // Verify the file list is refreshed: all base_count + 5 files should + // be present as base files (not overflow, since they're committed). + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert_eq!( + picker.get_files().len(), + base_count + 5, + "post-rescan: should have {} files ({} original + 5 new)", + base_count + 5, + base_count + ); + // After rescan, new files are in the base (no overflow). + assert_eq!( + picker.get_overflow_files().len(), + 0, + "post-rescan: should have 0 overflow files" + ); + } + + // Post-rescan grep verification. + // After rescan + bigram rebuild, edited tokens should be in the base + // index and findable without the overlay. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + for token in &edited_tokens { + let with = grep_count(picker, token); + assert!( + with >= 1, + "post-rescan: edited token {token} should be findable" + ); + } + + for token in &new_tokens { + let with = grep_count(picker, token); + assert!( + with >= 1, + "post-rescan: new token {token} should be findable" + ); + } + } + + stop_picker(&shared_picker); +} + +// =================================================================== +// Group 5: Full lifecycle -- seed, overlay edits, commit, rescan, more +// overlay edits. Tests the complete workflow. +// =================================================================== + +/// seed -> index -> overlay edits -> commit -> rescan -> more overlay edits +#[test] +fn bigram_overlay_coherence_full_lifecycle_seed_edit_commit_rescan_edit() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + // -- Phase 1: Pre-commit overlay edits (base files only) -- + let mut phase1_tokens = Vec::new(); + for i in 0..10 { + let (ref name, _) = repo_files[i]; + let token = format!("PHASE1_EDIT_{i:04}"); + write_file_with_token(base, name, &token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(name)); + } + phase1_tokens.push(token); + } + + // Delete some files (pick indices near the end). + let mut phase1_dead = Vec::new(); + for i in 195..200 { + let (ref name, ref token) = repo_files[i]; + let path = base.join(name); + fs::remove_file(&path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&path); + } + phase1_dead.push(token.clone()); + } + + // Verify phase 1 (base file operations only). + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + for token in &phase1_tokens { + assert!( + grep_count(picker, token) >= 1, + "phase1: {token} should be findable" + ); + } + for token in &phase1_dead { + assert_eq!( + grep_count(picker, token), + 0, + "phase1: {token} should be gone" + ); + } + } + + // -- Phase 2: Commit and rescan -- + git_add_and_commit(base, "phase 1 changes"); + shared_picker + .trigger_full_rescan_async(&shared_frecency) + .expect("rescan should succeed"); + + assert!( + shared_picker.wait_for_scan(Duration::from_secs(15)), + "Timed out waiting for scan to complete" + ); + + // Phase1 tokens should still be findable (now in base index). + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + for token in &phase1_tokens { + assert!( + grep_count(picker, token) >= 1, + "post-rescan: {token} should still be findable" + ); + } + for token in &phase1_dead { + assert_eq!( + grep_count(picker, token), + 0, + "post-rescan: {token} should not be findable" + ); + } + } + + std::thread::sleep(Duration::from_millis(1100)); + + // -- Phase 3: More overlay edits after rescan -- + let mut phase3_tokens = Vec::new(); + for i in 0..5 { + let (ref name, _) = repo_files[i]; + let token = format!("PHASE3_EDIT_{i:04}"); + write_file_with_token(base, name, &token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(name)); + } + phase3_tokens.push(token); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + for token in &phase3_tokens { + assert!( + grep_count(picker, token) >= 1, + "phase3: {token} should be findable" + ); + } + + // Phase1 tokens for files 0-4 were overwritten by phase3. + for i in 0..5 { + let old = format!("PHASE1_EDIT_{i:04}"); + assert_eq!( + grep_count(picker, &old), + 0, + "phase3: overwritten {old} should not be findable" + ); + } + + // Phase1 tokens for files 5-9 should still be there (in base). + for i in 5..10 { + let token = format!("PHASE1_EDIT_{i:04}"); + assert!( + grep_count(picker, &token) >= 1, + "phase3: {token} should still be findable" + ); + } + } + + stop_picker(&shared_picker); +} + +// =================================================================== +// Group 6: Nested directory operations. +// =================================================================== + +/// Files in nested directories should be editable via overlay. +#[test] +fn bigram_overlay_coherence_nested_directory_edits() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + // Verify all initial tokens findable. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + for (name, token) in &repo_files { + assert!( + grep_count(picker, token) >= 1, + "initial token {token} ({name}) should be findable" + ); + } + } + + std::thread::sleep(Duration::from_millis(1100)); + + // Edit one file from each subdir group (the realistic repo rotates + // through 8 subdirs, so indices 0,1,2,...,7 cover all dirs). + let mut edited = Vec::new(); + for i in 0..8 { + let (ref name, _) = repo_files[i]; + let token = format!("NESTED_EDIT_{i:02}"); + write_file_with_token(base, name, &token); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(name)); + } + edited.push(token); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + for token in &edited { + assert!( + grep_count(picker, token) >= 1, + "nested edit token {token} should be findable" + ); + } + } + + stop_picker(&shared_picker); +} + +// =================================================================== +// Group 7: Fuzzy file search. Verifies that fuzzy filename matching +// works for base files, edited files, overflow files, and after rescan. +// =================================================================== + +/// Helper: run a fuzzy search and return matched file paths. +fn fuzzy_search_paths(picker: &FilePicker, query: &str) -> Vec { + let parser = QueryParser::default(); + let parsed = parser.parse(query); + let result = picker.fuzzy_search( + &parsed, + None, + FuzzySearchOptions { + max_threads: 1, + pagination: PaginationArgs { + offset: 0, + limit: 200, + }, + ..Default::default() + }, + ); + result + .items + .iter() + .map(|f| f.relative_path(picker)) + .collect() +} + +/// Fuzzy search finds base files, overflow files, and respects deletions. +#[test] +fn bigram_overlay_coherence_fuzzy_search_base_overflow_and_deleted() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + seed_realistic_repo(base); + + // Create files with distinctive names for fuzzy matching ON TOP of + // the realistic repo. + fs::write(base.join("controller_auth.rs"), "auth controller\n").unwrap(); + fs::write(base.join("controller_user.rs"), "user controller\n").unwrap(); + fs::write(base.join("model_invoice.rs"), "invoice model\n").unwrap(); + fs::write(base.join("service_payment.rs"), "payment service\n").unwrap(); + fs::write(base.join("helper_crypto.rs"), "crypto helper\n").unwrap(); + + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + // Fuzzy search for "controller" -- should find both controller files. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let results = fuzzy_search_paths(picker, "controller"); + assert!( + results.len() >= 2, + "fuzzy 'controller' should match at least 2 files, got {results:?}" + ); + assert!( + results.iter().any(|p| p.contains("controller_auth")), + "should find controller_auth.rs" + ); + assert!( + results.iter().any(|p| p.contains("controller_user")), + "should find controller_user.rs" + ); + } + + std::thread::sleep(Duration::from_millis(1100)); + + // Delete controller_user.rs (tombstone). + let user_path = base.join("controller_user.rs"); + fs::remove_file(&user_path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&user_path); + } + + // Fuzzy search should no longer return the deleted file. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let results = fuzzy_search_paths(picker, "controller"); + assert!( + results.iter().any(|p| p.contains("controller_auth")), + "should still find controller_auth.rs" + ); + assert!( + !results.iter().any(|p| p.contains("controller_user")), + "deleted controller_user.rs should not appear in fuzzy results" + ); + } + + // Add a new overflow file. + fs::write(base.join("controller_admin.rs"), "admin controller\n").unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join("controller_admin.rs")); + } + + // Fuzzy search should find the new overflow file. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let results = fuzzy_search_paths(picker, "controller"); + assert!( + results.iter().any(|p| p.contains("controller_admin")), + "overflow file controller_admin.rs should appear in fuzzy results, got {results:?}" + ); + assert!( + results.iter().any(|p| p.contains("controller_auth")), + "base file controller_auth.rs should still be in fuzzy results" + ); + } + + stop_picker(&shared_picker); +} + +/// Fuzzy search works after rescan: base files + committed new files all +/// appear, and the deleted files are gone. +#[test] +fn bigram_overlay_coherence_fuzzy_search_after_rescan() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + seed_realistic_repo(base); + + // Add distinctive router/middleware files ON TOP of the realistic repo. + fs::write(base.join("router_api.rs"), "api router\n").unwrap(); + fs::write(base.join("router_web.rs"), "web router\n").unwrap(); + fs::write(base.join("middleware_cors.rs"), "cors middleware\n").unwrap(); + + git_init_and_commit(base); + + let (shared_picker, shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + // Add a new file and delete an existing one. + fs::write(base.join("router_grpc.rs"), "grpc router\n").unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join("router_grpc.rs")); + } + + let web_path = base.join("router_web.rs"); + fs::remove_file(&web_path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&web_path); + } + + // Commit and rescan. + git_add_and_commit(base, "add grpc, remove web"); + + shared_picker + .trigger_full_rescan_async(&shared_frecency) + .expect("rescan should succeed"); + assert!( + shared_picker.wait_for_scan(Duration::from_secs(15)), + "Timed out waiting for scan to complete" + ); + + // After rescan, fuzzy search should reflect the committed state. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let results = fuzzy_search_paths(picker, "router"); + assert!( + results.iter().any(|p| p.contains("router_api")), + "router_api.rs should be in results" + ); + assert!( + results.iter().any(|p| p.contains("router_grpc")), + "committed router_grpc.rs should be in results, got {results:?}" + ); + assert!( + !results.iter().any(|p| p.contains("router_web")), + "deleted router_web.rs should not be in results" + ); + } + + stop_picker(&shared_picker); +} + +/// Combined: fuzzy search + grep on the same overlay state. Exercises +/// both search paths after a mix of edits, creates, and deletes. +#[test] +fn bigram_overlay_coherence_fuzzy_and_grep_combined() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + let repo_files = seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + std::thread::sleep(Duration::from_millis(1100)); + + // Edit a base file. + let (ref edit_name, ref _edit_old_token) = repo_files[5]; + write_file_with_token(base, edit_name, "COMBINED_EDIT_TOKEN"); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join(edit_name)); + } + + // Add an overflow file with a distinctive name. + fs::write( + base.join("unique_overflow_widget.rs"), + "OVERFLOW_WIDGET_CONTENT\n", + ) + .unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.handle_create_or_modify(base.join("unique_overflow_widget.rs")); + } + + // Delete a base file. + let (ref del_name, ref del_token) = repo_files[10]; + let del_path = base.join(del_name); + fs::remove_file(&del_path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + picker.remove_file_by_path(&del_path); + } + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + + // Grep: edited content findable. + assert!( + grep_count(picker, "COMBINED_EDIT_TOKEN") >= 1, + "grep should find edited base file" + ); + // Grep: overflow content findable. + assert!( + grep_count(picker, "OVERFLOW_WIDGET_CONTENT") >= 1, + "grep should find overflow file content" + ); + // Grep: deleted file's content gone. + assert_eq!( + grep_count(picker, del_token), + 0, + "grep should not find deleted file content" + ); + + // Fuzzy: overflow file findable by name. + let fuzzy = fuzzy_search_paths(picker, "overflow_widget"); + assert!( + fuzzy.iter().any(|p| p.contains("unique_overflow_widget")), + "fuzzy should find overflow file by name, got {fuzzy:?}" + ); + + // Fuzzy: deleted file not in results. + let fuzzy_del = fuzzy_search_paths(picker, del_name); + let del_file = Path::new(del_name).file_name().unwrap().to_str().unwrap(); + assert!( + !fuzzy_del.iter().any(|p| p.contains(del_file)), + "fuzzy should not find deleted file" + ); + + // Fuzzy: edited file still findable by name. + let fuzzy_edit = fuzzy_search_paths(picker, edit_name); + let edit_file = Path::new(edit_name).file_name().unwrap().to_str().unwrap(); + assert!( + fuzzy_edit.iter().any(|p| p.contains(edit_file)), + "fuzzy should find edited file by name" + ); + } + + stop_picker(&shared_picker); +} + +fn grep_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 500, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +fn grep_count(picker: &FilePicker, query: &str) -> usize { + let parsed = parse_grep_query(query); + picker.grep(&parsed, &grep_opts()).matches.len() +} + +/// Wait for scanning to finish (no bigram requirement). +/// Use after `trigger_rescan` which replaces sync_data but does not +/// rebuild the bigram index. +fn wait_for_bigram(shared_picker: &SharedFilePicker) { + let deadline = std::time::Instant::now() + Duration::from_secs(10); + loop { + std::thread::sleep(Duration::from_millis(50)); + let ready = shared_picker + .read() + .ok() + .map(|guard| { + guard + .as_ref() + .map_or(false, |p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } +} + +fn stop_picker(shared_picker: &SharedFilePicker) { + if let Ok(mut guard) = shared_picker.write() { + if let Some(ref mut picker) = *guard { + picker.stop_background_monitor(); + } + } +} + +fn git_run(dir: &Path, args: &[&str]) { + let out = Command::new("git") + .args(args) + .current_dir(dir) + .env("GIT_AUTHOR_NAME", "test") + .env("GIT_AUTHOR_EMAIL", "test@test.com") + .env("GIT_COMMITTER_NAME", "test") + .env("GIT_COMMITTER_EMAIL", "test@test.com") + .output() + .unwrap_or_else(|e| panic!("git {:?} failed: {}", args, e)); + assert!( + out.status.success(), + "git {:?} failed: {}", + args, + String::from_utf8_lossy(&out.stderr) + ); +} + +fn git_init_and_commit(dir: &Path) { + git_run(dir, &["init"]); + git_run(dir, &["add", "-A"]); + git_run(dir, &["commit", "-m", "initial"]); +} + +fn git_add_and_commit(dir: &Path, msg: &str) { + git_run(dir, &["add", "-A"]); + git_run(dir, &["commit", "-m", msg]); +} + +fn make_picker(base: &Path) -> (SharedFilePicker, SharedFrecency) { + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, // we drive events manually + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + (shared_picker, shared_frecency) +} + +/// Generate a file with a unique token embedded. +fn write_file_with_token(dir: &Path, name: &str, token: &str) { + let content = format!( + "// auto-generated file: {name}\nconst TOKEN: &str = \"{token}\";\nfn main() {{}}\n" + ); + fs::write(dir.join(name), content).unwrap(); +} + +/// Create a realistic 200-file repository with subdirectories and diverse +/// content. Files are split into 4 thematic groups (50 each) so that +/// group-specific bigrams appear in ~25% of files, keeping the bigram +/// index discriminating (columns retained for 3-90% of files). +/// +/// Uses only leaf directories (no files in parent dirs that also have +/// child dirs) so that the directory sort order is consistent with the +/// binary search in `find_file_index`. +/// +/// Returns `Vec<(relative_path, token)>` for all 200 files. +fn seed_realistic_repo(dir: &Path) -> Vec<(String, String)> { + let subdirs = [ + "src/core", + "src/models", + "src/net", + "src/utils", + "tests/integration", + "tests/unit", + "lib/helpers", + "lib/internal", + ]; + for subdir in &subdirs { + fs::create_dir_all(dir.join(subdir)).unwrap(); + } + + let groups: &[(&str, &[&str])] = &[ + ( + "database", + &[ + "SELECT", + "INSERT", + "transaction", + "rollback", + "schema", + "migration", + "postgresql", + "foreign_key", + "primary_key", + "tablespace", + "index_column", + "constraint", + "aggregate", + "partition", + "replication", + ], + ), + ( + "network", + &[ + "socket", + "listen", + "handshake", + "bandwidth", + "latency", + "throughput", + "packet", + "firewall", + "proxy", + "gateway", + "protocol", + "ethernet", + "timeout", + "connection", + "certificate", + ], + ), + ( + "graphics", + &[ + "vertex", + "shader", + "fragment", + "rasterize", + "polygon", + "texture", + "framebuffer", + "quaternion", + "viewport", + "projection", + "tessellation", + "antialiasing", + "voxel", + "billboard", + "raytracing", + ], + ), + ( + "audio", + &[ + "waveform", + "amplitude", + "frequency", + "oscillator", + "synthesizer", + "reverb", + "envelope", + "spectrogram", + "samplerate", + "bitdepth", + "compressor", + "equalizer", + "decibel", + "harmonics", + "chorus", + ], + ), + ]; + + let mut files = Vec::with_capacity(200); + + for (group_idx, (domain, words)) in groups.iter().enumerate() { + for file_in_group in 0..50 { + let idx = group_idx * 50 + file_in_group; + let subdir = subdirs[idx % subdirs.len()]; + let relative_path = format!("{subdir}/repo_{idx:04}.rs"); + let token = format!("REPO_TOKEN_{idx:04}"); + + let w1 = words[file_in_group % words.len()]; + let w2 = words[(file_in_group + 3) % words.len()]; + let w3 = words[(file_in_group + 7) % words.len()]; + let w4 = words[(file_in_group + 11) % words.len()]; + + let content = format!( + "// {domain} module {idx}\n\ + pub fn {w1}_{idx}() -> Result<(), Error> {{\n\ + let ctx = {w2}_context();\n\ + {w3}_process(&ctx)?;\n\ + {w4}_finalize(&ctx)\n\ + }}\n\ + const MARKER: &str = \"{token}\";\n" + ); + fs::write(dir.join(&relative_path), content).unwrap(); + files.push((relative_path, token)); + } + } + + files +} + +fn fuzzy_grep_opts() -> GrepSearchOptions { + GrepSearchOptions { + mode: GrepMode::Fuzzy, + ..grep_opts() + } +} + +fn fuzzy_grep_count(picker: &FilePicker, query: &str) -> usize { + let parsed = parse_grep_query(query); + picker.grep(&parsed, &fuzzy_grep_opts()).matches.len() +} + +/// Overflow files must be searchable via fuzzy grep. +/// +/// The fuzzy grep path uses bigram prefiltering which only covers base +/// files. Overflow files (index >= base_count) must be appended to the +/// search set unconditionally, same as the plain-text grep path does. +/// +/// The realistic repo seeds 200 files split into 4 thematic groups so +/// that group-specific bigrams land in ~25% of files -- right in the +/// retention sweet spot. +#[test] +fn bigram_overlay_coherence_fuzzy_grep_finds_overflow_files() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + seed_realistic_repo(base); + git_init_and_commit(base); + + let (shared_picker, _shared_frecency) = make_picker(base); + wait_for_bigram(&shared_picker); + + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert!( + picker.bigram_index().is_some(), + "bigram index must be built" + ); + let idx = picker.bigram_index().unwrap(); + assert!( + idx.columns_used() > 10, + "index should retain enough columns to be discriminating, got {}", + idx.columns_used() + ); + } + + std::thread::sleep(Duration::from_millis(1100)); + + // Add overflow file using "graphics" vocabulary -- its bigrams ("vertex", + // "shader", etc.) appear in ~25% of base files, so the prefilter will + // produce a candidate set that covers group C files but NOT the overflow + // file (its index is beyond the bitset). + let new_path = base.join("overflow_graphics.rs"); + fs::write( + &new_path, + "pub fn vertex_shader_overflow_marker() {\n \ + let mesh = tessellation_pipeline();\n \ + rasterize_fragment(&mesh);\n\ + println!(\"rendered\");\n}\n", + ) + .unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!(picker.handle_create_or_modify(&new_path).is_some()); + assert_eq!(picker.get_overflow_files().len(), 1); + } + + // Plain text grep finds the overflow file (known to work). + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert!( + grep_count(picker, "vertex_shader_overflow_marker") >= 1, + "plain text grep should find overflow file content" + ); + } + + // Fuzzy grep: query uses "vertex_shader" -- bigrams "ve","er","rt","te","ex", + // "sh","ha","ad","de","er" overlap with group C base files. The prefilter + // will produce Some(candidates) covering those base files but the overflow + // file's index is beyond the bitset -> silently dropped. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert!( + fuzzy_grep_count(picker, "vertex_shader") >= 1, + "BUG: fuzzy grep should find overflow file content but bigram \ + prefiltering drops overflow files whose index exceeds the \ + candidate bitset length" + ); + } + + stop_picker(&shared_picker); +} diff --git a/crates/fff-core/tests/bigram_overlay_integration.rs b/crates/fff-core/tests/bigram_overlay_integration.rs new file mode 100644 index 0000000..c663acf --- /dev/null +++ b/crates/fff-core/tests/bigram_overlay_integration.rs @@ -0,0 +1,410 @@ +//! Integration test: verify that modifying a file after the bigram index is built +//! still makes the new content findable via grep (through the overlay layer). + +use std::fs; +use std::time::Duration; +use tempfile::TempDir; + +use fff_search::file_picker::{FFFMode, FilePicker}; +use fff_search::grep::{GrepMode, GrepSearchOptions, parse_grep_query}; +use fff_search::{FilePickerOptions, SharedFilePicker, SharedFrecency}; + +/// Create a temp directory with some initial files, run the full picker lifecycle, +/// then modify a file and verify grep finds the new content. +#[test] +fn modified_file_findable_via_overlay() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + // Create initial files with known content. + fs::write(base.join("alpha.txt"), "hello world\nfoo bar\n").unwrap(); + fs::write( + base.join("beta.txt"), + "some other content\nnothing special\n", + ) + .unwrap(); + fs::write(base.join("gamma.txt"), "yet another file\nmore lines\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, // we drive events manually + ..Default::default() + }, + ) + .expect("Failed to create FilePicker"); + + // Wait for scan + bigram build to complete. + let deadline = std::time::Instant::now() + Duration::from_secs(30); + loop { + std::thread::sleep(Duration::from_millis(50)); + + let ready = shared_picker + .read() + .ok() + .map(|guard| { + guard + .as_ref() + .map_or(false, |p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for scan + bigram build" + ); + } + + // Sanity check: the 3 files are indexed. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + assert_eq!(picker.get_files().len(), 3, "Expected 3 files after scan"); + assert!( + picker.bigram_index().is_some(), + "Bigram index should be built" + ); + assert!( + picker.bigram_overlay().is_some(), + "Overlay should be initialized" + ); + } + + // "UNIQUE_NEEDLE" should NOT exist in any file yet. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let parsed = parse_grep_query("UNIQUE_NEEDLE"); + let opts = grep_opts(); + let result = picker.grep(&parsed, &opts); + assert_eq!( + result.matches.len(), + 0, + "UNIQUE_NEEDLE should not exist before modification" + ); + } + + // Sleep so the filesystem mtime (seconds granularity) advances past the + // value recorded during scan. Without this, on_create_or_modify skips + // mmap invalidation and grep reads stale cached content. + std::thread::sleep(Duration::from_millis(1100)); + + // Write new content containing the needle. + let modified_path = base.join("beta.txt"); + fs::write( + &modified_path, + "some other content\nUNIQUE_NEEDLE is here\nnothing special\n", + ) + .unwrap(); + + // Simulate watcher event: call on_create_or_modify. + // This updates the overlay's bigrams and invalidates the mmap cache. + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + let result = picker.handle_create_or_modify(&modified_path); + assert!( + result.is_some(), + "on_create_or_modify should return the file" + ); + } + + // The bigram index was built BEFORE the modification, so without the + // overlay, beta.txt would be filtered out (its old bigrams don't contain + // "UNIQUE_NEEDLE"). The overlay should fix that. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let parsed = parse_grep_query("UNIQUE_NEEDLE"); + let opts = grep_opts(); + let result = picker.grep(&parsed, &opts); + assert!( + !result.matches.is_empty(), + "UNIQUE_NEEDLE should be findable after modification (overlay adds the candidate back)" + ); + // May find 1 or 2 matches depending on mmap cache state — the important + // thing is that the modified content IS found. + assert!( + result + .matches + .iter() + .any(|m| m.line_content.contains("UNIQUE_NEEDLE")), + "At least one match should contain UNIQUE_NEEDLE" + ); + } + + // Cleanup: stop background watcher. + if let Ok(mut guard) = shared_picker.write() { + if let Some(ref mut picker) = *guard { + picker.stop_background_monitor(); + } + } +} + +/// Verify that deleting a file makes its content un-findable via grep. +#[test] +fn deleted_file_excluded_via_overlay() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + fs::write(base.join("keep.txt"), "keep this content\n").unwrap(); + fs::write(base.join("remove.txt"), "DELETEME_TOKEN is here\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, // we drive events manually + ..Default::default() + }, + ) + .unwrap(); + + wait_for_bigram(&shared_picker); + + // Sanity: DELETEME_TOKEN is findable. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let result = grep_for(picker, "DELETEME_TOKEN"); + assert_eq!( + result.matches.len(), + 1, + "Token should be found before delete" + ); + } + + // Delete the file on disk and via picker. + let remove_path = base.join("remove.txt"); + fs::remove_file(&remove_path).unwrap(); + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!( + picker.remove_file_by_path(&remove_path), + "remove should succeed" + ); + } + + // Token should no longer be found (tombstone in overlay clears the candidate). + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let result = grep_for(picker, "DELETEME_TOKEN"); + assert_eq!( + result.matches.len(), + 0, + "DELETEME_TOKEN should not be found after deletion (tombstone in overlay)" + ); + } + + if let Ok(mut guard) = shared_picker.write() { + if let Some(ref mut picker) = *guard { + picker.stop_background_monitor(); + } + } +} + +/// Verify that a newly added file (in overflow) is findable via grep. +#[test] +fn new_file_findable_after_add() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + fs::write(base.join("existing.txt"), "original content\n").unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, // we drive events manually + ..Default::default() + }, + ) + .unwrap(); + + wait_for_bigram(&shared_picker); + + // Create a new file on disk after the index was built. + let new_path = base.join("newcomer.txt"); + fs::write(&new_path, "BRAND_NEW_TOKEN lives here\n").unwrap(); + + // Simulate watcher detecting the new file. + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + let result = picker.handle_create_or_modify(&new_path); + assert!( + result.is_some(), + "on_create_or_modify should return the new file" + ); + } + + // The new file is in overflow, not in the base files slice. + // grep_search currently only searches base files, so we need to verify + // the overflow file is accessible. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let overflow = picker.get_overflow_files(); + assert_eq!(overflow.len(), 1, "Should have 1 overflow file"); + assert!( + overflow[0].relative_path(picker).ends_with("newcomer.txt"), + "Overflow file should be newcomer.txt" + ); + } + + if let Ok(mut guard) = shared_picker.write() { + if let Some(ref mut picker) = *guard { + picker.stop_background_monitor(); + } + } +} + +/// Verify that a file modified after index build is findable via regex grep +/// through the overlay. This catches a regression where `extract_bigrams` on +/// the raw regex string (e.g. "NEEDLE.*HERE") produces bogus bigrams containing +/// `.` and `*`, causing `query_modified` to miss the file. +#[test] +fn modified_file_findable_via_regex_overlay() { + let tmp = TempDir::new().unwrap(); + let base = tmp.path(); + + fs::write(base.join("alpha.txt"), "hello world\nfoo bar\n").unwrap(); + fs::write( + base.join("beta.txt"), + "some other content\nnothing special\n", + ) + .unwrap(); + + let shared_picker = SharedFilePicker::default(); + let shared_frecency = SharedFrecency::default(); + + FilePicker::new_with_shared_state( + shared_picker.clone(), + shared_frecency.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, // we drive events manually + ..Default::default() + }, + ) + .unwrap(); + + wait_for_bigram(&shared_picker); + + // Advance mtime past the scan timestamp so the cache is invalidated. + std::thread::sleep(Duration::from_millis(1100)); + + // Write content that matches the regex "NEEDLE.*HERE" into beta.txt. + let modified_path = base.join("beta.txt"); + fs::write( + &modified_path, + "some other content\nNEEDLE is right HERE\nnothing special\n", + ) + .unwrap(); + + { + let mut guard = shared_picker.write().unwrap(); + let picker = guard.as_mut().unwrap(); + assert!(picker.handle_create_or_modify(&modified_path).is_some()); + } + + // Regex grep should find the modified file through the overlay. + { + let guard = shared_picker.read().unwrap(); + let picker = guard.as_ref().unwrap(); + let parsed = parse_grep_query("NEEDLE.*HERE"); + let opts = GrepSearchOptions { + mode: GrepMode::Regex, + ..grep_opts() + }; + let result = picker.grep(&parsed, &opts); + assert!( + !result.matches.is_empty(), + "Regex grep should find NEEDLE.*HERE in modified file via overlay" + ); + assert!(result.matches[0].line_content.contains("NEEDLE")); + } + + if let Ok(mut guard) = shared_picker.write() { + if let Some(ref mut picker) = *guard { + picker.stop_background_monitor(); + } + } +} + +// ── Helpers ───────────────────────────────────────────────────────────── + +fn grep_opts() -> GrepSearchOptions { + GrepSearchOptions { + max_file_size: 10 * 1024 * 1024, + max_matches_per_file: 200, + smart_case: true, + file_offset: 0, + page_limit: 200, + mode: GrepMode::PlainText, + time_budget_ms: 0, + before_context: 0, + after_context: 0, + classify_definitions: false, + trim_whitespace: false, + abort_signal: None, + } +} + +fn grep_for<'a>(picker: &'a FilePicker, query: &str) -> fff_search::grep::GrepResult<'a> { + let parsed = parse_grep_query(query); + picker.grep(&parsed, &grep_opts()) +} + +fn wait_for_bigram(shared_picker: &SharedFilePicker) { + let deadline = std::time::Instant::now() + Duration::from_secs(30); + loop { + std::thread::sleep(Duration::from_millis(50)); + let ready = shared_picker + .read() + .ok() + .map(|guard| { + guard + .as_ref() + .map_or(false, |p| !p.is_scan_active() && p.bigram_index().is_some()) + }) + .unwrap_or(false); + if ready { + break; + } + assert!( + std::time::Instant::now() < deadline, + "Timed out waiting for bigram build" + ); + } +} diff --git a/crates/fff-core/tests/drop_during_post_scan.rs b/crates/fff-core/tests/drop_during_post_scan.rs new file mode 100644 index 0000000..f69090f --- /dev/null +++ b/crates/fff-core/tests/drop_during_post_scan.rs @@ -0,0 +1,161 @@ +// Regression pinning: dropping a picker during poset scan off-lock time +use std::fs; +use std::path::Path; +use std::process::Command; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::Duration; +use tempfile::TempDir; + +use fff_search::file_picker::{FFFMode, FilePicker, FuzzySearchOptions}; +use fff_search::{FilePickerOptions, QueryParser, SharedFilePicker, SharedFrecency}; + +fn seed_files(dir: &Path, count: usize) { + for i in 0..count { + let subdir = dir.join(format!("dir_{}", i / 20)); + fs::create_dir_all(&subdir).unwrap(); + fs::write( + subdir.join(format!("file_{i}.rs")), + format!("pub fn func_{i}() {{ /* token_{i} */ }}\n"), + ) + .unwrap(); + } +} + +fn git_init(dir: &Path) { + let run = |args: &[&str]| { + Command::new("git") + .args(args) + .current_dir(dir) + .env("GIT_AUTHOR_NAME", "test") + .env("GIT_AUTHOR_EMAIL", "t@t") + .env("GIT_COMMITTER_NAME", "test") + .env("GIT_COMMITTER_EMAIL", "t@t") + .output() + .unwrap(); + }; + run(&["init"]); + run(&["add", "-A"]); + run(&["commit", "-m", "init"]); +} + +fn make_picker(base: &Path) -> (SharedFilePicker, SharedFrecency) { + let sp = SharedFilePicker::default(); + let sf = SharedFrecency::default(); + FilePicker::new_with_shared_state( + sp.clone(), + sf.clone(), + FilePickerOptions { + base_path: base.to_string_lossy().to_string(), + enable_mmap_cache: true, + enable_content_indexing: true, + mode: FFFMode::Neovim, + watch: false, + ..Default::default() + }, + ) + .expect("init"); + (sp, sf) +} + +/// Drop picker immediately after scan starts — scan thread will find +/// the picker gone and exit cleanly. +#[test] +fn drop_picker_during_walk_no_segfault() { + let tmp = TempDir::new().unwrap(); + seed_files(tmp.path(), 500); + git_init(tmp.path()); + + let (sp, _sf) = make_picker(tmp.path()); + // Don't wait — drop immediately while walk is likely in progress + drop(sp); + + // If we get here without SIGSEGV, the test passes. + std::thread::sleep(Duration::from_millis(200)); +} + +/// Drop picker while post-scan indexing is running. The snapshot holds +/// Arc clones that keep the buffers alive. +#[test] +fn drop_picker_during_post_scan_no_segfault() { + let tmp = TempDir::new().unwrap(); + seed_files(tmp.path(), 500); + git_init(tmp.path()); + + let (sp, _sf) = make_picker(tmp.path()); + + // Wait for walk to finish (files are searchable) but post-scan is + // still running (bigram not yet built). + sp.wait_for_scan(Duration::from_secs(10)); + + // At this point post_scan_indexing_active is likely true. + // Drop the picker — this releases the picker's Arc clones, but the + // post-scan snapshot's clones keep the buffers alive. + if let Ok(mut guard) = sp.write() { + guard.take(); // drop the FilePicker + } + + // Give post-scan threads time to run against the "dead" picker. + // They must not segfault. + std::thread::sleep(Duration::from_secs(2)); +} + +/// Drop picker from a second thread while the first thread is doing +/// fuzzy searches. Verifies no segfault from interleaved access. +#[test] +fn drop_picker_concurrent_with_search_no_segfault() { + let tmp = TempDir::new().unwrap(); + seed_files(tmp.path(), 500); + git_init(tmp.path()); + + let (sp, _sf) = make_picker(tmp.path()); + sp.wait_for_scan(Duration::from_secs(10)); + + let sp_clone = sp.clone(); + let running = Arc::new(AtomicBool::new(true)); + let running_clone = running.clone(); + + // Searcher thread: continuously queries while the picker lives + let searcher = std::thread::spawn(move || { + let parser = QueryParser::default(); + while running_clone.load(Ordering::Relaxed) { + if let Ok(guard) = sp_clone.read() { + if let Some(picker) = guard.as_ref() { + let query = parser.parse("func"); + let _ = picker.fuzzy_search(&query, None, FuzzySearchOptions::default()); + } + } + std::thread::sleep(Duration::from_millis(1)); + } + }); + + // Let searches run for a bit, then drop + std::thread::sleep(Duration::from_millis(100)); + if let Ok(mut guard) = sp.write() { + guard.take(); + } + std::thread::sleep(Duration::from_millis(100)); + + running.store(false, Ordering::Relaxed); + searcher.join().unwrap(); +} + +/// Repeated init + wait + clean-drop cycle. This is the pattern that +/// SIGSEGV'd on the pre-refactor code in the benchmark. +#[test] +fn repeated_init_and_drop_no_segfault() { + let tmp = TempDir::new().unwrap(); + seed_files(tmp.path(), 200); + git_init(tmp.path()); + + for _ in 0..5 { + let (sp, _sf) = make_picker(tmp.path()); + sp.wait_for_scan(Duration::from_secs(10)); + sp.wait_for_indexing_complete(Duration::from_secs(30)); + if let Ok(mut guard) = sp.write() + && let Some(mut picker) = guard.take() + { + picker.stop_background_monitor(); + } + } +} diff --git a/crates/fff-core/tests/fixtures/binaries/codex_view b/crates/fff-core/tests/fixtures/binaries/codex_view new file mode 100644 index 0000000000000000000000000000000000000000..1e5a6cc96c41f0b92fd6594ec09179b0667ca096 GIT binary patch literal 4557704 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�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�L$(!od;Q}+6&X*e-`k1&3z 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