#![cfg(all(feature = "comms", any(unix, windows)))]
use super::cursor::Cursor;
use super::helpers::SEARCH_LIMIT_MAX;
use super::helpers_calls_scan::{CallRef, CallScanPage, calls_in_file_fjall, scan_calls_by_name};
use super::helpers_impls::{ImplScanPage, scan_impls_fjall};
use super::types::ReferenceHit;
use super::types_impls::ImplementationHit;
use crate::comms::index_read_proto::{
IndexReadQuery, IndexReadResult, MAX_CALLS_IN_FILES, WireCall, WireCallHit, WireCallScan, WireImplHit, WireImplScan,
};
use crate::index::IndexDb;
use crate::store::Store;
const NO_INDEX: &str = "the daemon holds this workspace without a fjall index, so the reference scan cannot run";
pub(crate) fn index_read_against(store: &Store, query: &IndexReadQuery) -> Result<IndexReadResult, String> {
let idx = store.index_db.as_ref().ok_or_else(|| NO_INDEX.to_string())?;
match query {
IndexReadQuery::CallScan { name, limit, cursor } => call_scan(idx, name, *limit, cursor.as_deref()),
IndexReadQuery::CallsInFiles { paths } => calls_in_files(idx, paths),
IndexReadQuery::GrepBloom { pattern, paths } => grep_skip(idx, &store.root, pattern, paths),
IndexReadQuery::ImplScan {
trait_name,
language,
limit,
cursor,
} => {
let page = scan_impls_fjall(
idx,
trait_name,
language.as_deref(),
clamp_limit(*limit),
cursor.as_deref(),
|rel, lang| store.lookup(rel).is_some_and(|entry| entry.language == lang),
|rel, start_byte| {
store
.lookup(rel)
.and_then(|entry| store.read_l1_by_hex(&entry.hash_hex).ok().flatten())
.map_or((0, 0), |l1| super::helpers_impls::impl_row_col(&l1, start_byte))
},
)
.map_err(|error| error.message.to_string())?;
Ok(IndexReadResult::ImplScan(impl_page_to_wire(page)))
}
}
}
pub(crate) fn call_scan(
idx: &IndexDb,
name: &str,
limit: u32,
cursor: Option<&[u8]>,
) -> Result<IndexReadResult, String> {
let page = scan_calls_by_name(idx, name, clamp_limit(limit), cursor).map_err(|error| error.message.to_string())?;
Ok(IndexReadResult::CallScan(call_page_to_wire(page)))
}
pub(crate) fn grep_skip(
idx: &IndexDb,
root: &std::path::Path,
pattern: &str,
paths: &[crate::path::RelPath],
) -> Result<IndexReadResult, String> {
use crate::comms::index_read_proto::MAX_GREP_BLOOM_PATHS;
if paths.len() > MAX_GREP_BLOOM_PATHS {
return Err(format!(
"grep bloom names {} files; at most {MAX_GREP_BLOOM_PATHS} per request",
paths.len()
));
}
let Some(needle) = crate::index::grep_bloom::Needle::compile(pattern) else {
return Ok(IndexReadResult::GrepSkip(vec![false; paths.len()]));
};
let refs: Vec<&crate::path::RelPath> = paths.iter().collect();
Ok(IndexReadResult::GrepSkip(idx.grep_skip_verdicts(root, &refs, &needle)))
}
fn calls_in_files(idx: &IndexDb, paths: &[crate::path::RelPath]) -> Result<IndexReadResult, String> {
if paths.len() > MAX_CALLS_IN_FILES {
return Err(format!(
"calls_in_files names {} files; at most {MAX_CALLS_IN_FILES} per request",
paths.len()
));
}
let per_file = paths
.iter()
.map(|path| {
calls_in_file_fjall(idx, path).map(|calls| {
calls
.into_iter()
.map(|c| WireCall {
callee: c.callee,
start_byte: c.start_byte,
line: c.line,
column: c.column,
})
.collect()
})
})
.collect::<Result<Vec<Vec<WireCall>>, _>>()
.map_err(|error| error.message.to_string())?;
Ok(IndexReadResult::CallsInFiles(per_file))
}
fn clamp_limit(limit: u32) -> usize {
(limit as usize).min(SEARCH_LIMIT_MAX as usize)
}
fn call_page_to_wire(page: CallScanPage) -> WireCallScan {
let has_more = page.next_cursor.is_some();
let hits = page
.hits
.into_iter()
.zip(page.hit_keys)
.zip(page.hit_starts)
.map(|((hit, key), start_byte)| WireCallHit {
key,
path: hit.path,
line: hit.line,
column: hit.column,
callee: hit.callee,
start_byte,
})
.collect();
WireCallScan {
total: page.total,
total_is_partial: page.total_is_partial,
has_more,
hits,
}
}
pub(super) fn call_page_from_wire(wire: WireCallScan) -> CallScanPage {
let mut hits = Vec::with_capacity(wire.hits.len());
let mut hit_keys = Vec::with_capacity(wire.hits.len());
let mut hit_starts = Vec::with_capacity(wire.hits.len());
for hit in wire.hits {
hits.push(ReferenceHit {
path: hit.path,
line: hit.line,
column: hit.column,
callee: hit.callee,
resolved: None,
});
hit_keys.push(hit.key);
hit_starts.push(hit.start_byte);
}
let next_cursor = if wire.has_more {
hit_keys.last().map(|key| Cursor::encode_fjall(key))
} else {
None
};
CallScanPage {
total: wire.total,
total_is_partial: wire.total_is_partial,
hits,
next_cursor,
hit_keys,
hit_starts,
}
}
pub(super) fn calls_from_wire(wire: Vec<WireCall>) -> Vec<CallRef> {
wire.into_iter()
.map(|c| CallRef {
callee: c.callee,
start_byte: c.start_byte,
line: c.line,
column: c.column,
})
.collect()
}
fn impl_page_to_wire(page: ImplScanPage) -> WireImplScan {
let hits = page
.hits
.into_iter()
.zip(page.hit_keys)
.map(|(hit, key)| WireImplHit {
key,
path: hit.path,
trait_name: hit.trait_name,
impl_type: hit.impl_type,
start_row: hit.start_row,
start_col: hit.start_col,
})
.collect();
WireImplScan {
total: page.total as u64,
total_is_partial: page.total_is_partial,
has_more: page.has_more,
hits,
}
}
pub(super) fn impl_page_from_wire(wire: WireImplScan) -> ImplScanPage {
let mut hits = Vec::with_capacity(wire.hits.len());
let mut hit_keys = Vec::with_capacity(wire.hits.len());
for hit in wire.hits {
hits.push(ImplementationHit {
path: hit.path,
trait_name: hit.trait_name,
impl_type: hit.impl_type,
start_row: hit.start_row,
start_col: hit.start_col,
});
hit_keys.push(hit.key);
}
ImplScanPage {
total: usize::try_from(wire.total).unwrap_or(usize::MAX),
total_is_partial: wire.total_is_partial,
hits,
hit_keys,
has_more: wire.has_more,
}
}