use std::ops::Bound;
use rmcp::ErrorData as McpError;
use rmcp::model::CallToolResult;
use super::cursor::Cursor;
use super::helpers::{SEARCH_LIMIT_DEFAULT, SEARCH_LIMIT_MAX, elapsed_us, json_result};
use super::index_route::IndexRoute;
use super::types_impls::{FindImplementationsParams, FindImplementationsResponse, ImplementationHit};
use crate::path::RelPath;
pub(crate) struct ImplScanPage {
pub total: usize,
pub total_is_partial: bool,
pub hits: Vec<ImplementationHit>,
pub hit_keys: Vec<Vec<u8>>,
pub has_more: bool,
}
pub(super) async fn run_find_implementations(
route: &IndexRoute,
params: FindImplementationsParams,
cache: &super::MapCache,
notice: impl FnOnce() -> Option<super::types::LifecycleNotice>,
started: std::time::Instant,
) -> Result<CallToolResult, McpError> {
let limit = params.limit.unwrap_or(SEARCH_LIMIT_DEFAULT).min(SEARCH_LIMIT_MAX) as usize;
let cursor_bytes = params.cursor.as_ref().map(|c| c.decode_fjall()).transpose()?;
let page = route
.scan_impls(
cache,
¶ms.trait_name,
params.language.as_deref(),
limit,
cursor_bytes.as_deref(),
)
.await?;
let ImplScanPage {
total,
total_is_partial,
hits,
hit_keys,
has_more,
} = page;
let next_cursor = if has_more {
hit_keys.last().map(|k| Cursor::encode_fjall(k))
} else {
None
};
let budget = super::budget::apply_budget(hits, params.max_tokens);
let (hits, budgeted, next_cursor) = if budget.budgeted {
let kept = budget.items.len();
let cursor = hit_keys.get(kept - 1).map(|k| Cursor::encode_fjall(k));
(budget.items, true, cursor)
} else {
(budget.items, false, next_cursor)
};
json_result(&FindImplementationsResponse {
trait_name: params.trait_name,
total,
total_is_partial,
budgeted,
hits,
next_cursor,
notice: notice(),
elapsed_us: elapsed_us(started),
})
}
pub(crate) fn scan_impls_fjall(
idx: &crate::index::IndexDb,
trait_name: &str,
language: Option<&str>,
limit: usize,
cursor_after: Option<&[u8]>,
language_matches: impl Fn(&RelPath, &str) -> bool,
row_col: impl Fn(&RelPath, u32) -> (u32, u32),
) -> Result<ImplScanPage, McpError> {
let finder = memchr::memmem::Finder::new(trait_name.as_bytes());
let lower: Bound<Vec<u8>> = match cursor_after {
Some(k) => Bound::Excluded(k.to_vec()),
None => Bound::Unbounded,
};
let scan_cap = limit.saturating_mul(8).max(2_000);
let mut hits: Vec<ImplementationHit> = Vec::with_capacity(limit.min(64));
let mut hit_keys: Vec<Vec<u8>> = Vec::with_capacity(limit.min(64));
let mut total: usize = 0;
let mut total_is_partial = false;
let mut has_more = false;
let mut matched: usize = 0;
for guard in crate::index::name_dict::name_ordered_keys(
&idx.implementations_by_trait,
&idx.trait_names,
trait_name,
cursor_after,
lower,
) {
let (k, _) = guard
.into_inner()
.map_err(|e| McpError::internal_error(format!("impl index iter: {e}"), None))?;
let Some((trait_name, impl_type, rel, start_byte)) = crate::index::keys::parse_impl_by_trait(&k) else {
continue;
};
if finder.find(trait_name.as_bytes()).is_none() {
continue;
}
if let Some(lang_filter) = language
&& !language_matches(&rel, lang_filter)
{
continue;
}
total += 1;
matched += 1;
if hits.len() < limit {
let (start_row, start_col) = row_col(&rel, start_byte);
hits.push(ImplementationHit {
path: rel,
trait_name,
impl_type,
start_row,
start_col,
});
hit_keys.push(k.to_vec());
} else {
has_more = true;
}
if matched >= scan_cap {
total_is_partial = true;
break;
}
}
Ok(ImplScanPage {
total,
total_is_partial,
hits,
hit_keys,
has_more,
})
}
pub(crate) fn scan_impls_session(
idx: &crate::index::IndexDb,
cache: &super::MapCache,
trait_name: &str,
language: Option<&str>,
limit: usize,
cursor_after: Option<&[u8]>,
) -> Result<ImplScanPage, McpError> {
scan_impls_fjall(
idx,
trait_name,
language,
limit,
cursor_after,
|rel, lang| cache.language_of(rel) == Some(lang),
|rel, start_byte| resolve_impl_row_col(cache, rel, start_byte),
)
}
fn resolve_impl_row_col(cache: &super::MapCache, rel: &RelPath, start_byte: u32) -> (u32, u32) {
cache.get(rel).map_or((0, 0), |l1| impl_row_col(&l1, start_byte))
}
pub(crate) fn impl_row_col(l1: &crate::extract::FileMapL1, start_byte: u32) -> (u32, u32) {
l1.implementations
.iter()
.find(|i| i.start_byte == start_byte)
.map_or((0, 0), |imp| (imp.start_row + 1, imp.start_col))
}
pub(crate) fn scan_impls_in_ram(
index: &InRamImplIndex,
cache: &super::MapCache,
trait_name: &str,
language: Option<&str>,
limit: usize,
cursor_after: Option<&[u8]>,
) -> ImplScanPage {
let finder = memchr::memmem::Finder::new(trait_name.as_bytes());
let start = match cursor_after {
Some(c) => index.entries.partition_point(|e| e.key.as_slice() <= c),
None => 0,
};
let scan_cap = limit.saturating_mul(8).max(2_000);
let mut hits: Vec<ImplementationHit> = Vec::with_capacity(limit.min(64));
let mut hit_keys: Vec<Vec<u8>> = Vec::with_capacity(limit.min(64));
let mut total: usize = 0;
let mut total_is_partial = false;
let mut has_more = false;
let mut matched: usize = 0;
for entry in &index.entries[start..] {
if finder.find(entry.trait_name.as_bytes()).is_none() {
continue;
}
if let Some(lang) = language
&& cache.language_of(&entry.rel) != Some(lang)
{
continue;
}
total += 1;
matched += 1;
if hits.len() < limit {
hits.push(ImplementationHit {
path: entry.rel.clone(),
trait_name: entry.trait_name.clone(),
impl_type: entry.impl_type.clone(),
start_row: entry.start_row,
start_col: entry.start_col,
});
hit_keys.push(entry.key.clone());
} else {
has_more = true;
}
if matched >= scan_cap {
total_is_partial = true;
break;
}
}
ImplScanPage {
total,
total_is_partial,
hits,
hit_keys,
has_more,
}
}
pub(crate) struct InRamImplIndex {
entries: Vec<InRamImpl>,
capped: bool,
}
struct InRamImpl {
key: Vec<u8>,
trait_name: String,
impl_type: String,
rel: crate::path::RelPath,
start_row: u32,
start_col: u32,
}
impl InRamImplIndex {
pub(crate) fn build(
files: &super::l1_cache::FileIndexView,
l1: &super::l1_cache::L1Cache,
budget_bytes: u64,
) -> Self {
let mut entries: Vec<InRamImpl> = Vec::new();
let mut charged: u64 = 0;
let mut capped = false;
super::l1_cache::stream_while(files, l1, |rel, map| {
for imp in &map.implementations {
let Some(key) = crate::index::keys::impl_by_trait(&imp.trait_name, &imp.impl_type, rel, imp.start_byte)
else {
continue;
};
charged += (key.len() + imp.trait_name.len() + imp.impl_type.len() + rel.as_bytes().len()) as u64
+ std::mem::size_of::<InRamImpl>() as u64;
if budget_bytes != 0 && charged > budget_bytes {
capped = true;
return false;
}
entries.push(InRamImpl {
key,
trait_name: imp.trait_name.clone(),
impl_type: imp.impl_type.clone(),
rel: rel.clone(),
start_row: imp.start_row + 1,
start_col: imp.start_col,
});
}
true
});
entries.sort_unstable_by(|a, b| a.key.cmp(&b.key));
Self { entries, capped }
}
pub(crate) fn capped(&self) -> bool {
self.capped
}
}