010eda7834
`enclosing_block_boundaries`, `block_range_at` and `summarize_code` each re-parsed the whole file on every call. The results are not cacheable — boundaries depend on the caller's visible ranges, which differ per call — but the `tree_sitter::Tree` is, so cache that instead and hand out `ts_tree_copy` clones. Keyed on (xxh64 of the source, source length, language). The hash is a bucket selector only: a hit re-verifies the stored source against the request byte-for-byte before returning the tree, so a collision costs a re-parse and can never yield a tree built from other content. Language is in the key because the same bytes parsed as TypeScript and as Python are different trees. Bounded at 12 slots and 4 MiB of retained source with LRU eviction; sources above 4 MiB are parsed but never retained. `Tree` is `Send` but not `Sync`, so entries sit behind a `Mutex` that is held only for a map probe, a byte compare and a refcount bump, never across a parse or walk. Error trees are cached like any other: `has_error()` is a property of the tree, so the callers' own checks reach an identical verdict from a cached tree, and repeated "does this parse" probes get the speedup too. Measured (M4 Max, bazel-built .node, median of 20, 1-40 visible): read.ts 81 KB 13.34 ms -> 8.86 ms on repeat; 1 MB synthetic 225.7 ms -> 138.3 ms. Parser::new + set_language measured at 0.30 us against a 3.91 ms parse, so no parser pooling.