How to: Perform Advanced Search

Goal: Use Vfs::grep with GrepOptions to run ripgrep-style content searches against any backend.


GrepOptions reference

#![allow(unused)]
fn main() {
use synwire_core::vfs::grep_options::{GrepOptions, GrepOutputMode};

let opts = GrepOptions {
    path: None,                  // search root; None = backend's current working directory
    case_insensitive: false,
    invert: false,               // true = return non-matching lines
    line_numbers: false,         // include line numbers in results
    output_mode: GrepOutputMode::Content,
    before_context: 0,           // lines to include before each match
    after_context: 0,            // lines to include after each match
    context: None,               // symmetric context (overrides before/after when set)
    max_matches: None,           // stop after N total matches
    glob: None,                  // filename glob filter, e.g. "*.rs"
    file_type: None,             // ripgrep-style type: "rust", "python", "go", etc.
    multiline: false,
    fixed_string: false,         // treat pattern as literal, not regex
};
}

All fields have Default implementations. Start from GrepOptions::default() and override only what you need.


GrepMatch fields

#![allow(unused)]
fn main() {
pub struct GrepMatch {
    pub file: String,
    pub line_number: usize,    // 1-indexed when line_numbers: true; 0 otherwise
    pub column: usize,         // 0-indexed byte offset of match start
    pub line_content: String,  // the matching line
    pub before: Vec<String>,   // lines before the match (up to before_context)
    pub after: Vec<String>,    // lines after the match (up to after_context)
}
}

In Count mode, line_number holds the match count for the file and line_content is its string representation.


#![allow(unused)]
fn main() {
use synwire_core::vfs::grep_options::GrepOptions;
use synwire_core::vfs::protocol::Vfs;

let opts = GrepOptions {
    case_insensitive: true,
    line_numbers: true,
    ..Default::default()
};

let matches = backend.grep("todo", opts).await?;
for m in &matches {
    println!("{}:{} {}", m.file, m.line_number, m.line_content);
}
}

Context lines

#![allow(unused)]
fn main() {
let opts = GrepOptions {
    context: Some(2),     // 2 lines before AND after each match
    line_numbers: true,
    ..Default::default()
};

// Or use asymmetric context:
let opts = GrepOptions {
    before_context: 3,
    after_context: 1,
    ..Default::default()
};
}

When both context and before_context/after_context are set, context takes precedence.


File-type filter

Accepts ripgrep-style type names. Supported aliases include rust/rs, python/py, js/javascript, ts/typescript, go, json, yaml/yml, toml, md/markdown, sh/bash.

#![allow(unused)]
fn main() {
let opts = GrepOptions {
    file_type: Some("rust".to_string()),
    ..Default::default()
};

let matches = backend.grep("unwrap", opts).await?;
// Returns only matches in *.rs files.
}

Glob filter

Restricts results to files whose name matches the glob pattern. * matches any sequence of non-separator characters; ** matches anything.

#![allow(unused)]
fn main() {
let opts = GrepOptions {
    glob: Some("*.toml".to_string()),
    ..Default::default()
};

let matches = backend.grep("version", opts).await?;
}

Inverted match

Returns lines that do NOT match the pattern.

#![allow(unused)]
fn main() {
let opts = GrepOptions {
    invert: true,
    ..Default::default()
};

// Lines that do not contain "TODO".
let matches = backend.grep("TODO", opts).await?;
}

Files-with-matches mode

Returns one entry per file (with empty line_content) rather than one entry per matching line.

#![allow(unused)]
fn main() {
use synwire_core::vfs::grep_options::GrepOutputMode;

let opts = GrepOptions {
    output_mode: GrepOutputMode::FilesWithMatches,
    ..Default::default()
};

let matches = backend.grep("panic!", opts).await?;
let files: Vec<&str> = matches.iter().map(|m| m.file.as_str()).collect();
}

Count mode

Returns one entry per file with line_number set to the number of matches in that file.

#![allow(unused)]
fn main() {
let opts = GrepOptions {
    output_mode: GrepOutputMode::Count,
    ..Default::default()
};

let counts = backend.grep("error", opts).await?;
for c in &counts {
    println!("{}: {} occurrences", c.file, c.line_number);
}
}

Limiting results

#![allow(unused)]
fn main() {
let opts = GrepOptions {
    max_matches: Some(50),
    ..Default::default()
};
}

The search stops after max_matches total matches across all files. Useful for large codebases where you only need the first few hits.


Scoped search path

#![allow(unused)]
fn main() {
let opts = GrepOptions {
    path: Some("src/backends".to_string()),
    file_type: Some("rust".to_string()),
    ..Default::default()
};

let matches = backend.grep("VfsError", opts).await?;
}

path is resolved relative to the backend's current working directory.


See also