Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/external-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:

- name: Build Rust binary (Windows)
if: matrix.target
run: cargo build --release --target ${{ matrix.target }} -p fff-nvim
run: cargo build --release --target ${{ matrix.target }} -p fff-nvim --features zlob

- name: Copy binary to target/release (Windows)
if: matrix.target
Expand All @@ -71,7 +71,7 @@ jobs:

- name: Build Rust binary
if: ${{ !matrix.target }}
run: cargo build --release -p fff-nvim
run: cargo build --release -p fff-nvim --features zlob

- name: Install Neovim
uses: rhysd/action-setup-vim@v1
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
- name: Build for Linux
if: contains(matrix.os, 'ubuntu') && !contains(matrix.target, 'android')
run: |
cargo zigbuild --release --target ${{ matrix.zigbuild_target || matrix.target }} -p fff-nvim
cargo zigbuild --release --target ${{ matrix.zigbuild_target || matrix.target }} -p fff-nvim --features zlob
mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.${{ matrix.ext }}"

- name: Build for Android (Termux)
Expand All @@ -95,13 +95,13 @@ jobs:
export AR_aarch64_linux_android="$NDK_BIN/llvm-ar"
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_BIN/aarch64-linux-android24-clang"

cargo build --release --target ${{ matrix.target }} -p fff-nvim
cargo build --release --target ${{ matrix.target }} -p fff-nvim --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 --release --target ${{ matrix.target }} -p fff-nvim
MACOSX_DEPLOYMENT_TARGET="13" cargo build --release --target ${{ matrix.target }} -p fff-nvim --features zlob
mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.${{ matrix.ext }}"

- name: Ad-hoc sign macOS binary
Expand All @@ -112,7 +112,7 @@ jobs:
if: contains(matrix.os, 'windows')
shell: bash
run: |
cargo build --release --target ${{ matrix.target }} -p fff-nvim
cargo build --release --target ${{ matrix.target }} -p fff-nvim --features zlob
mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.${{ matrix.ext }}"

- name: Upload artifacts
Expand Down Expand Up @@ -212,7 +212,7 @@ jobs:
- name: Build for Linux
if: contains(matrix.os, 'ubuntu') && !contains(matrix.target, 'android')
run: |
cargo zigbuild --release --target ${{ matrix.zigbuild_target || matrix.target }} -p fff-c
cargo zigbuild --release --target ${{ matrix.zigbuild_target || matrix.target }} -p fff-c --features zlob
mv "${{ matrix.artifact_name }}" "c-lib-${{ matrix.target }}.${{ matrix.ext }}"

- name: Build for Android (Termux)
Expand All @@ -225,13 +225,13 @@ jobs:
export AR_aarch64_linux_android="$NDK_BIN/llvm-ar"
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_BIN/aarch64-linux-android24-clang"

cargo build --release --target ${{ matrix.target }} -p fff-c
cargo build --release --target ${{ matrix.target }} -p fff-c --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 --release --target ${{ matrix.target }} -p fff-c
MACOSX_DEPLOYMENT_TARGET="13" cargo build --release --target ${{ matrix.target }} -p fff-c --features zlob
mv "${{ matrix.artifact_name }}" "c-lib-${{ matrix.target }}.${{ matrix.ext }}"

- name: Ad-hoc sign macOS binary
Expand All @@ -242,7 +242,7 @@ jobs:
if: contains(matrix.os, 'windows')
shell: bash
run: |
cargo build --release --target ${{ matrix.target }} -p fff-c
cargo build --release --target ${{ matrix.target }} -p fff-c --features zlob
mv "${{ matrix.artifact_name }}" "c-lib-${{ matrix.target }}.${{ matrix.ext }}"

- name: Prepare npm package
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ jobs:
components: rustfmt, clippy

- name: Run tests
run: cargo test --verbose --workspace --exclude fff-nvim
run: |
cargo test --verbose -p fff-core -p fff-query-parser -p fff-c --features zlob
cargo test --verbose -p grep-searcher

fmt:
name: cargo fmt
Expand Down Expand Up @@ -71,4 +73,6 @@ jobs:
components: clippy

- name: Run clippy
run: cargo clippy -- -D warnings
run: |
cargo clippy -p fff-core -p fff-query-parser -p fff-nvim -p fff-c --features zlob -- -D warnings
cargo clippy -p grep-searcher -- -D warnings
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ git2 = { version = "0.20.2", default-features = false, features = [
"vendored-libgit2",
] }
glidesort = "0.1"
globset = "0.4"
grep-matcher = "0.1.8"
grep-searcher = { path = "crates/fff-searcher" }
heed = "0.22.0"
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ PLENARY_DIR ?= ../plenary.nvim
.PHONY: build test test-rust test-lua test-bun test-setup prepare-bun

build:
cargo build --release
cargo build --release --features zlob

test-setup:
@if [ ! -d "$(PLENARY_DIR)" ]; then \
Expand All @@ -12,7 +12,7 @@ test-setup:
fi

test-rust:
cargo test --workspace
cargo test --workspace --features zlob

test-lua: test-setup build
nvim --headless -u tests/minimal_init.lua \
Expand Down
4 changes: 4 additions & 0 deletions crates/fff-c/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ license = "MIT"
[lib]
crate-type = ["cdylib"]

[features]
default = []
zlob = ["fff-core/zlob"]

[dependencies]
mimalloc.workspace = true
tracing.workspace = true
Expand Down
8 changes: 6 additions & 2 deletions crates/fff-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ crate-type = ["rlib", "staticlib", "cdylib"]
default = []
# Enable C FFI exports
ffi = []
# 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]
# Workspace dependencies
Expand All @@ -23,7 +26,7 @@ thiserror = { workspace = true }
tracing = { workspace = true }

# Local crates
fff-query-parser = { path = "../fff-query-parser" }
fff-query-parser = { path = "../fff-query-parser", default-features = false }

# External dependencies
bindet = { workspace = true }
Expand All @@ -32,6 +35,7 @@ chrono = { workspace = true }
dirs = { workspace = true }
git2 = { workspace = true }
glidesort = { workspace = true }
globset = { workspace = true }
grep-matcher = { workspace = true }
grep-searcher = { workspace = true }
memchr = "2"
Expand All @@ -49,7 +53,7 @@ serde = { version = "1.0", features = ["derive"] }
smartstring = { version = "1.0.1", features = ["serde"] }
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
zlob = { workspace = true }
zlob = { workspace = true, optional = true }
# Platform-specific: dunce for Windows to avoid \\?\ extended path prefix
[target.'cfg(windows)'.dependencies]
dunce = { workspace = true }
Expand Down
54 changes: 42 additions & 12 deletions crates/fff-core/build.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
fn main() {
// When the `zlob` feature is enabled (Zig-compiled C library):
// On Windows MSVC, explicitly link the C runtime libraries.
// This is needed because Zig-compiled static libraries (zlob) don't emit
// /DEFAULTLIB directives for the MSVC CRT. Without this, symbols like
// strcmp, memcpy, memchr etc. from vendored C libraries (libgit2, lmdb)
// are unresolved when linking the cdylib.
//
// We link both msvcrt (classic CRT) and ucrt (Universal CRT where memchr,
// strcmp etc. live on newer MSVC/ARM64 targets).
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");
// 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() {
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("CI").is_ok() {
// CI must always build with zlob for production-quality binaries.
if !zig_available() {
panic!(
"CI detected but Zig is not installed. \
Please install Zig and build with `--features zlob`."
);
}
panic!(
"CI detected but `zlob` feature is not enabled. \
Build with `--features zlob`."
);
} else {
// Hint: if Zig is available but the zlob feature wasn't enabled,
// let the developer know they can get faster glob matching.
if zig_available() {
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()
.map(|s| s.success())
.unwrap_or(false)
}
89 changes: 62 additions & 27 deletions crates/fff-core/src/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use ahash::AHashSet;
use fff_query_parser::{Constraint, GitStatusFilter};
use smallvec::SmallVec;
use zlob::{ZlobFlags, zlob_match_paths};

use crate::git::is_modified_status;

Expand Down Expand Up @@ -235,32 +234,8 @@ fn collect_glob_indices<'a>(
) {
match constraint {
Constraint::Glob(pattern) => {
if let Ok(Some(matches)) = zlob_match_paths(pattern, paths, ZlobFlags::RECOMMENDED) {
let matched_set: AHashSet<usize> =
matches.iter().map(|s| s.as_ptr() as usize).collect();

let indices: AHashSet<usize> = if paths.len() >= PAR_THRESHOLD {
use rayon::prelude::*;
paths
.par_iter()
.enumerate()
.filter(|(_, p)| matched_set.contains(&(p.as_ptr() as usize)))
.map(|(i, _)| i)
.collect::<Vec<_>>()
.into_iter()
.collect()
} else {
paths
.iter()
.enumerate()
.filter(|(_, p)| matched_set.contains(&(p.as_ptr() as usize)))
.map(|(i, _)| i)
.collect()
};
results.push((is_negated, indices));
} else {
results.push((is_negated, AHashSet::new()));
}
let indices = match_glob_pattern(pattern, paths);
results.push((is_negated, indices));
}
Constraint::Not(inner) => {
collect_glob_indices(inner, paths, results, !is_negated);
Expand All @@ -269,6 +244,66 @@ fn collect_glob_indices<'a>(
}
}

/// Match a glob pattern against a list of paths, returning the set of matching indices.
///
/// When the `zlob` feature is enabled, delegates to `zlob::zlob_match_paths` (Zig-compiled
/// C library, fastest). Otherwise falls back to `globset::Glob` (pure Rust).
#[cfg(feature = "zlob")]
fn match_glob_pattern(pattern: &str, paths: &[&str]) -> AHashSet<usize> {
let Ok(Some(matches)) = zlob::zlob_match_paths(pattern, paths, zlob::ZlobFlags::RECOMMENDED)
else {
return AHashSet::new();
};

let matched_set: AHashSet<usize> = matches.iter().map(|s| s.as_ptr() as usize).collect();

if paths.len() >= PAR_THRESHOLD {
use rayon::prelude::*;
paths
.par_iter()
.enumerate()
.filter(|(_, p)| matched_set.contains(&(p.as_ptr() as usize)))
.map(|(i, _)| i)
.collect::<Vec<_>>()
.into_iter()
.collect()
} else {
paths
.iter()
.enumerate()
.filter(|(_, p)| matched_set.contains(&(p.as_ptr() as usize)))
.map(|(i, _)| i)
.collect()
}
}

#[cfg(not(feature = "zlob"))]
fn match_glob_pattern(pattern: &str, paths: &[&str]) -> AHashSet<usize> {
let Ok(glob) = globset::Glob::new(pattern) else {
return AHashSet::new();
};
let matcher = glob.compile_matcher();

if paths.len() >= PAR_THRESHOLD {
use rayon::prelude::*;
paths
.par_iter()
.enumerate()
.filter(|(_, p)| matcher.is_match(p))
.map(|(i, _)| i)
.collect::<Vec<_>>()
.into_iter()
.collect()
} else {
paths
.iter()
.enumerate()
.filter(|(_, p)| matcher.is_match(p))
.map(|(i, _)| i)
.collect()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
5 changes: 4 additions & 1 deletion crates/fff-nvim/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ edition = "2024"
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

[features]
default = []
zlob = ["fff-core/zlob"]

[[bin]]
name = "test_watcher"
path = "src/bin/test_watcher.rs"
Expand Down Expand Up @@ -63,7 +67,6 @@ serde = { version = "1.0", features = ["derive"] }
smartstring = { version = "1.0.1", features = ["serde"] }
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
zlob = { workspace = true }

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
Expand Down
6 changes: 5 additions & 1 deletion crates/fff-query-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ edition = "2024"
[lib]
path = "src/lib.rs"

[features]
default = []
zlob = ["dep:zlob"]

[dependencies]
smallvec = { workspace = true }
zlob = { version = "1.2.8" }
zlob = { workspace = true, optional = true }

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
Expand Down
Loading