Skip to content
Draft
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
68 changes: 68 additions & 0 deletions crates/pidgin-napi/schema/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@
"nullable": false
}
]
},
{
"name": "FuzzyMatchResult",
"doc": "Result of [`fuzzy_match`]; serialized to pi's `{ matches, score }`.",
"fields": [
{
"name": "matches",
"type": "boolean",
"nullable": false
},
{
"name": "score",
"type": "float64",
"nullable": false
}
]
}
],
"unions": [],
Expand Down Expand Up @@ -870,6 +886,58 @@
"name": "hasTrustRequiringProjectResources"
}
}
},
{
"name": "fuzzyMatch",
"doc": "`fuzzyMatch` (fuzzy.ts): fuzzy-match `query` against `text`, returning pi's\n`{ matches, score }` (lower score = better).",
"shape": "unary",
"infallible": true,
"readonly": true,
"params": [
{
"name": "query",
"type": "string"
},
{
"name": "text",
"type": "string"
}
],
"returns": {
"model": "FuzzyMatchResult"
},
"bindings": {
"node": {
"name": "fuzzyMatch"
}
}
},
{
"name": "fuzzyFilter",
"doc": "`fuzzyFilter` (fuzzy.ts): run pi's whole filter orchestration in Rust. Given\neach candidate's already-materialized text and the query, return the\nsurviving candidates' original indices ranked best-match-first. The shim\nmaps these indices back to items, so pi's `getText` callback stays in JS.",
"shape": "unary",
"infallible": true,
"readonly": true,
"params": [
{
"name": "texts",
"type": {
"list": "string"
}
},
{
"name": "query",
"type": "string"
}
],
"returns": {
"list": "uint32"
},
"bindings": {
"node": {
"name": "fuzzyFilter"
}
}
}
]
},
Expand Down
22 changes: 22 additions & 0 deletions crates/pidgin-napi/src/core_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
/// native `word-navigation.ts` shim. Cursors are UTF-16 string indices crossing
/// as `int32` (JS `number`) and widened to the engine's `usize` at the seam —
/// the JS-visible cursor values are identical to the pre-swap wrappers.
/// - the tui fuzzy ops (`fuzzyMatch`, `fuzzyFilter`) route into `pidgin_tui`'s
/// fuzzy layer, backing the native `fuzzy.ts` shim. `fuzzyMatch` returns
/// `FuzzyMatchResult { matches, score }` with the score crossing as `float64`
/// (JS `number`); `fuzzyFilter` returns the surviving indices as `uint32` (JS
/// `number`), widened from the engine's `usize` at the seam — the JS-visible
/// scores and indices are identical to the pre-swap wrappers.
pub struct PidginImpl;

impl crate::generated::PidginCore for PidginImpl {
Expand Down Expand Up @@ -262,6 +268,22 @@ impl crate::generated::PidginCore for PidginImpl {
&cwd, &home_dir,
)
}

fn fuzzy_match(query: String, text: String) -> crate::generated::FuzzyMatchResult {
let m = pidgin_tui::fuzzy_match(&query, &text);
crate::generated::FuzzyMatchResult {
matches: m.matches,
score: m.score,
}
}

fn fuzzy_filter(texts: Vec<String>, query: String) -> Vec<u32> {
let text_refs: Vec<&str> = texts.iter().map(String::as_str).collect();
pidgin_tui::fuzzy_filter_indices(&text_refs, &query)
.into_iter()
.map(|i| i as u32)
.collect()
}
}

// --- coding-agent session-cwd seam (core/session-cwd.ts) --------------------
Expand Down
26 changes: 26 additions & 0 deletions crates/pidgin-napi/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ pub struct SessionCwdIssueJs {
pub fallback_cwd: String,
}

/// Result of [`fuzzy_match`]; serialized to pi's `{ matches, score }`.
#[napi(object)]
#[derive(Clone)]
pub struct FuzzyMatchResult {
pub matches: bool,
pub score: f64,
}

/// The `Pidgin` contract — implement over the engine in `crate::core_impl`.
pub trait PidginCore: Sized + Send + Sync + 'static {
fn version() -> String;
Expand Down Expand Up @@ -100,6 +108,8 @@ pub trait PidginCore: Sized + Send + Sync + 'static {
fn is_newer_package_version(candidate_version: String, current_version: String) -> bool;
fn get_project_trust_parent_path(cwd: String) -> Option<String>;
fn has_trust_requiring_project_resources(cwd: String, home_dir: String) -> bool;
fn fuzzy_match(query: String, text: String) -> FuzzyMatchResult;
fn fuzzy_filter(texts: Vec<String>, query: String) -> Vec<u32>;
}

/// The `KeybindingsManagerCore` contract — implement over the engine in `crate::core_impl`.
Expand Down Expand Up @@ -424,6 +434,22 @@ pub fn has_trust_requiring_project_resources(cwd: String, home_dir: String) -> b
)
}

/// `fuzzyMatch` (fuzzy.ts): fuzzy-match `query` against `text`, returning pi's
/// `{ matches, score }` (lower score = better).
#[napi(js_name = "fuzzyMatch")]
pub fn fuzzy_match(query: String, text: String) -> FuzzyMatchResult {
<crate::core_impl::PidginImpl as PidginCore>::fuzzy_match(query, text)
}

/// `fuzzyFilter` (fuzzy.ts): run pi's whole filter orchestration in Rust. Given
/// each candidate's already-materialized text and the query, return the
/// surviving candidates' original indices ranked best-match-first. The shim
/// maps these indices back to items, so pi's `getText` callback stays in JS.
#[napi(js_name = "fuzzyFilter")]
pub fn fuzzy_filter(texts: Vec<String>, query: String) -> Vec<u32> {
<crate::core_impl::PidginImpl as PidginCore>::fuzzy_filter(texts, query)
}

/// The Rust-backed keybindings core, exposed to JavaScript as
/// `KeybindingsManagerCore`.
#[napi]
Expand Down
49 changes: 11 additions & 38 deletions crates/pidgin-napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,44 +807,17 @@ pub fn migrate_keybindings_config(raw_json: String) -> napi::Result<String> {

// --- tui fuzzy layer (packages/tui/src/fuzzy.ts) ---------------------------
//
// Thin wrappers over `pidgin_tui::fuzzy`, backing the hand-written native
// `fuzzy.ts` shim. `fuzzyMatch` crosses as a plain `{ matches, score }`
// object. `fuzzyFilter` crosses as `(texts, query) -> ranked indices`: the
// shim materializes each item's text via its `getText` callback in JS, calls
// this, and maps the returned indices back to items — so pi's `getText` stays
// on the JS side while the whole tokenize/AND-gate/score-sum/sort orchestration
// runs in Rust.

/// Result of [`fuzzy_match`]; serialized to pi's `{ matches, score }`.
#[napi(object)]
pub struct FuzzyMatchResult {
pub matches: bool,
pub score: f64,
}

/// `fuzzyMatch` (fuzzy.ts): fuzzy-match `query` against `text`, returning pi's
/// `{ matches, score }` (lower score = better).
#[napi(js_name = "fuzzyMatch")]
pub fn fuzzy_match(query: String, text: String) -> FuzzyMatchResult {
let m = pidgin_tui::fuzzy_match(&query, &text);
FuzzyMatchResult {
matches: m.matches,
score: m.score,
}
}

/// `fuzzyFilter` (fuzzy.ts): run pi's whole filter orchestration in Rust. Given
/// each candidate's already-materialized text and the query, return the
/// surviving candidates' original indices ranked best-match-first. The shim
/// maps these indices back to items, so pi's `getText` callback stays in JS.
#[napi(js_name = "fuzzyFilter")]
pub fn fuzzy_filter(texts: Vec<String>, query: String) -> Vec<u32> {
let text_refs: Vec<&str> = texts.iter().map(String::as_str).collect();
pidgin_tui::fuzzy_filter_indices(&text_refs, &query)
.into_iter()
.map(|i| i as u32)
.collect()
}
// The two fuzzy ops (`fuzzyMatch`, `fuzzyFilter`) now generate from the fluessig
// api schema through `crate::generated` + `crate::core_impl`, routing into
// `pidgin_tui`'s fuzzy layer. `fuzzyMatch` returns `FuzzyMatchResult`, whose
// `score` crosses as `float64` (JS `number`); `fuzzyFilter` returns the ranked
// surviving indices as `uint32` (JS `number`), widened from the engine's `usize`
// at the core seam. `fuzzyFilter`'s shim materializes each item's text via its
// `getText` callback in JS and maps the returned indices back to items, so pi's
// `getText` stays JS-side while the whole tokenize/AND-gate/score-sum/sort
// orchestration runs in Rust. The hand-written `#[napi]` exports that lived here
// were deleted; edit `schema/api.json` and rerun `regen.sh` instead of re-adding
// them.

// --- tui word-navigation layer (packages/tui/src/word-navigation.ts) --------
//
Expand Down