mirror of
https://github.com/hanzoai/mcp.git
synced 2026-10-15 12:30:12 +00:00
hanzo-trigram is the one search engine behind both actions: tgrep-core
(the hanzoai fork, MIT, pinned by commit, feature docs) linked as a
library, so a search is a call, not an rg spawn. It is its own crate so
Dev can link it without this crate's browser and parser dependencies;
hanzo_mcp::trigram re-exports it.
Results are ranked (matching lines a file, or definitions before uses
for a symbol), then path, then line, so a page is an offset into one
fixed list and the cursor really pages: before, rg stopped at limit and
a cursor repeated the first page. .gitignore, hidden and binary files
are skipped as rg skips them. A glob that names a document type (*.pdf,
*.docx, *.zip) reads documents too, and a hit inside one carries the
archive members and its page, slide, paragraph or cell.
fs and code keep their names, schemas and answer shapes; entry and place
appear on a document hit only. search_symbol now reads definitions by
the engine's declaration patterns, not the first-keyword guess.
Linking tgrep-core brings libsqlite3-sys (its managed-index code), which
needs a newer cc than the tree-sitter grammars allow, so Cargo resolves
tree-sitter-{c,cpp,javascript,python,rust,typescript} to their previous
patch releases. 241 library tests and the code tool's pass on them.
Gating managed/ behind a feature in the fork removes both.
117 lines
4.5 KiB
Rust
117 lines
4.5 KiB
Rust
//! Documents built byte by byte: a PDF and a zip of stored members. Adapted from tgrep's own
|
|
//! test fixtures (MIT, https://github.com/hanzoai/tgrep, tgrep-cli/tests/fixture/mod.rs).
|
|
#![allow(dead_code)]
|
|
|
|
pub fn crc32(data: &[u8]) -> u32 {
|
|
let mut crc = !0u32;
|
|
for &byte in data {
|
|
crc ^= u32::from(byte);
|
|
for _ in 0..8 {
|
|
crc = if crc & 1 != 0 { (crc >> 1) ^ 0xEDB8_8320 } else { crc >> 1 };
|
|
}
|
|
}
|
|
!crc
|
|
}
|
|
|
|
/// A PDF whose page `i` shows the lines of `pages[i]` in Helvetica.
|
|
pub fn pdf(pages: &[&[&str]]) -> Vec<u8> {
|
|
let count = pages.len();
|
|
let font = 3 + 2 * count;
|
|
let mut objects: Vec<Vec<u8>> = vec![
|
|
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
|
format!(
|
|
"<< /Type /Pages /Kids [{}] /Count {count} >>",
|
|
(0..count).map(|i| format!("{} 0 R", 3 + 2 * i)).collect::<Vec<_>>().join(" ")
|
|
)
|
|
.into_bytes(),
|
|
];
|
|
for (i, lines) in pages.iter().enumerate() {
|
|
objects.push(
|
|
format!(
|
|
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents {} 0 R \
|
|
/Resources << /Font << /F1 {font} 0 R >> >> >>",
|
|
4 + 2 * i
|
|
)
|
|
.into_bytes(),
|
|
);
|
|
let mut stream = String::from("BT /F1 12 Tf 72 712 Td");
|
|
for (n, line) in lines.iter().enumerate() {
|
|
if n > 0 {
|
|
stream.push_str(" 0 -14 Td");
|
|
}
|
|
stream.push_str(&format!(" ({}) Tj", line.replace('(', "\\(").replace(')', "\\)")));
|
|
}
|
|
stream.push_str(" ET");
|
|
objects.push(format!("<< /Length {} >>\nstream\n{stream}\nendstream", stream.len()).into_bytes());
|
|
}
|
|
objects.push(b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".to_vec());
|
|
let mut out = b"%PDF-1.4\n".to_vec();
|
|
let mut offsets = Vec::new();
|
|
for (i, object) in objects.iter().enumerate() {
|
|
offsets.push(out.len());
|
|
out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
|
|
out.extend_from_slice(object);
|
|
out.extend_from_slice(b"\nendobj\n");
|
|
}
|
|
let xref = out.len();
|
|
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objects.len() + 1).as_bytes());
|
|
for offset in offsets {
|
|
out.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
|
|
}
|
|
out.extend_from_slice(
|
|
format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objects.len() + 1).as_bytes(),
|
|
);
|
|
out
|
|
}
|
|
|
|
/// A zip of stored members; a name ending in `/` is a directory.
|
|
pub fn zip(entries: &[(&str, &[u8])]) -> Vec<u8> {
|
|
const DATE: u16 = (40 << 9) | (1 << 5) | 1;
|
|
let mut out = Vec::new();
|
|
let mut central = Vec::new();
|
|
for (name, data) in entries {
|
|
let offset = out.len() as u32;
|
|
let crc = crc32(data);
|
|
let size = data.len() as u32;
|
|
let name_len = name.len() as u16;
|
|
out.extend_from_slice(&0x0403_4b50u32.to_le_bytes());
|
|
for field in [20u16, 0, 0, 0, DATE] {
|
|
out.extend_from_slice(&field.to_le_bytes());
|
|
}
|
|
for field in [crc, size, size] {
|
|
out.extend_from_slice(&field.to_le_bytes());
|
|
}
|
|
out.extend_from_slice(&name_len.to_le_bytes());
|
|
out.extend_from_slice(&0u16.to_le_bytes());
|
|
out.extend_from_slice(name.as_bytes());
|
|
out.extend_from_slice(data);
|
|
|
|
let external: u32 = if name.ends_with('/') { 0x10 } else { 0 };
|
|
central.extend_from_slice(&0x0201_4b50u32.to_le_bytes());
|
|
for field in [20u16, 20, 0, 0, 0, DATE] {
|
|
central.extend_from_slice(&field.to_le_bytes());
|
|
}
|
|
for field in [crc, size, size] {
|
|
central.extend_from_slice(&field.to_le_bytes());
|
|
}
|
|
for field in [name_len, 0, 0, 0, 0] {
|
|
central.extend_from_slice(&field.to_le_bytes());
|
|
}
|
|
central.extend_from_slice(&external.to_le_bytes());
|
|
central.extend_from_slice(&offset.to_le_bytes());
|
|
central.extend_from_slice(name.as_bytes());
|
|
}
|
|
let central_offset = out.len() as u32;
|
|
let central_size = central.len() as u32;
|
|
out.extend_from_slice(¢ral);
|
|
out.extend_from_slice(&0x0605_4b50u32.to_le_bytes());
|
|
let count = entries.len() as u16;
|
|
for field in [0u16, 0, count, count] {
|
|
out.extend_from_slice(&field.to_le_bytes());
|
|
}
|
|
out.extend_from_slice(¢ral_size.to_le_bytes());
|
|
out.extend_from_slice(¢ral_offset.to_le_bytes());
|
|
out.extend_from_slice(&0u16.to_le_bytes());
|
|
out
|
|
}
|