fff (Fast File Finder)

repository·main·Indexed 27 days ago

https://github.com/dmtrkovalenko/fff

A high-performance file search toolkit featuring typo-resistant fuzzy search, frecency-ranked access, and a lightweight in-memory index. It provides native git support via libgit, SIMD optimized plain and regex matching, and advanced constraints for prefiltering. The toolkit includes a raw C API (fff-c), bindings for Bun (@ff-labs/fff-bun) and Node.js (@ff-labs/fff-node), and a Lua-based picker UI coordinator for integration into editors and AI agents.

Tokens
30.8K
Snippets
50
Records
209
Agent score
94%

What's inside fff

  1. Overview of FFF (Fast File Finder)

    main

    FFF is a high-performance file search library designed for long-lived processes like IDE extensions or AI agents. Unlike CLI tools like ripgrep or fzf that fork a new process for every search, FFF keeps the index and file cache resident in memory, enabling sub-10ms queries on large repositories (e.g., 500k files).

    Key Features:

    • Frecency-ranked fuzzy matching: Ranks results based on access and modification frequency.
    • Typo-resistant matching: Uses Smith-Waterman fuzzy scoring for both paths and content.
    • Three Content Grep Modes: Plain literal (SIMD memmem), regex, and fuzzy (Smith-Waterman per line).
    • Git Awareness: Returns gitStatus (modified, staged, untracked, ignored) without shelling out to the git CLI.
    • Definition Classifier: Tags lines starting with keywords like struct, fn, class, def, and impl.
    • Background Indexing: Uses a file watcher to update the index incrementally.
  2. Overview of fff file search toolkit

    main
    fff is a high-performance file search toolkit designed for long-running applications such as file editors, AI agents, or file explorers. It is engineered to be faster than ripgrep and fzf, offering features like fuzzy file name search with typo resistance, frecency and query history ranking, and native git support via libgit.
  3. Understand the fff.picker_ui architecture

    main

    The fff.picker_ui module uses a coordinator pattern with shared state:

    • Coordinator Pattern: picker_ui.lua acts as the coordinator, initializing submodules via module.init(parent_module). This allows submodules to call back into the coordinator for cross-module coordination (e.g., navigation.lua calling P.render_list()).
    • Shared State: All submodules share a single source of truth via the picker_ui_state.state table. There is no message passing or event bus; modules read from and write to this shared table directly.
    • Module Types:
      • Managers: (e.g., search_manager, preview_manager) Manage state or lifecycle.
      • Behavioral: (e.g., navigation, renderer) Handle specific actions or logic.
      • Renderers: (e.g., file_renderer, grep_renderer) Handle rendering of specific item types.
      • Pure Data/Utils: (e.g., picker_ui_state, utils) Standalone modules without parent references or cross-module dependencies.
  4. Use FFF C library

    main

    The C library provides a stable ABI for binding from C/C++, Zig, Go, Python, etc.

    Build:

    # Builds only the C cdylib:
    make build-c-lib
    
    # Or with cargo (requires Zig for zlob feature):
    cargo build --release -p fff-c --features zlob

    Installation:

    # System-wide:
    sudo make install
    
    # User-local:
    make install PREFIX=$HOME/.local

    Minimal C Example:

    #include <fff.h>
    #include <stdio.h>
    
    int main(void) {
        FffResult *res = fff_create_instance(
            ".",        // base_path
            "",         // frecency_db_path
            "",         // 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;
    }

    Important Notes:

    • Functions returning FffResult* allocate with Rust's Box. Use fff_free_result to free them.
    • Use FffCreateOptions (versioned struct) for preferred instance creation to avoid ABI breaks.
    • Use fff_glob for literal glob searches that bypass the query parser.
    #include <fff.h>
    #include <stdio.h>
    
    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;
    }
  5. Use FFF Node & Bun SDK

    main

    The @ff-labs/fff-node package provides a TypeScript wrapper over the C library for Node.js and Bun. Every method returns a Result<T> object: { ok: true, value } | { ok: false, error }.

    Installation:

    npm install @ff-labs/fff-node
    # or
    bun add @ff-labs/fff-node

    Example Usage:

    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,
    });
    
    finder.value.destroy();
    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,
    });
    
    finder.value.destroy();
  6. Manually install @ff-labs/pi-fff for local development

    main

    To install the extension manually for development, clone the repository, install dependencies in the package directory, and add the entry point to your pi settings.json or run it directly with the -e flag.

    git clone https://github.com/dmtrKovalenko/fff.nvim.git
    cd fff.nvim/packages/pi-fff
    npm install

    Add to settings.json:

    {
      "extensions": ["/path/to/fff.nvim/packages/pi-fff/src/index.ts"]
    }

    Or test directly:

    pi -e /path/to/fff.nvim/packages/pi-fff/src/index.ts
  7. Build fff-node from source

    main

    If prebuilt binaries are unavailable for your platform, you can build the C library manually using Cargo. The resulting binary will be located in target/release/.

    # Clone the repository
    git clone https://github.com/dmtrKovalenko/fff.nvim
    cd fff.nvim
    
    # Build the C library
    cargo build --release -p fff-c
    
    # The binary will be at target/release/libfff_c.{so,dylib,dll}
  8. Install fff.nvim for Neovim

    main

    You can install fff.nvim using lazy.nvim or vim.pack.

    Using lazy.nvim:

    {
      'dmtrKovalenko/fff.nvim',
      build = function()
        require("fff.download").download_or_build_binary()
      end,
      opts = {
        debug = {
          enabled = true,
          show_scores = true,
        },
      },
      lazy = false,
      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',
        },
      },
    }

    Using vim.pack:

    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' })
    require("fff.download").download_or_build_binary()
  9. Build fff from source

    main

    If prebuilt binaries are unavailable for your platform, you can build the C library manually using Cargo. The resulting binary will be located in target/release/.

    # Clone the repository
    git clone https://github.com/dmtrKovalenko/fff.nvim
    cd fff.nvim
    
    # Build the C library
    cargo build --release -p fff-c
    
    # The binary will be at target/release/libfff_c.{so,dylib,dll}