Skip to main content

diffler_core/syntax/
mod.rs

1//! Tree-sitter parsing shared by highlighting, scope context and the
2//! structural diff. We link grammars statically since musl-static binaries
3//! cannot `dlopen`. A parse failure or unknown language falls back to plain
4//! text.
5
6pub mod intraline;
7pub mod registry;
8pub mod scope;
9pub mod symbols;
10
11use std::ops::Range;
12
13use tree_sitter::Parser;
14
15pub use registry::{HIGHLIGHT_NAMES, LangEntry, LanguageRegistry};
16pub use scope::ScopeIndex;
17pub use symbols::Ident;
18
19/// We skip parsing larger files, which are usually generated or minified.
20pub(crate) const MAX_PARSE_BYTES: usize = 2_000_000;
21
22pub(crate) fn parse(entry: &LangEntry, src: &str) -> Option<tree_sitter::Tree> {
23    let mut parser = Parser::new();
24    parser.set_language(&entry.language).ok()?;
25    parser.parse(src, None)
26}
27
28/// `(start_byte, visible_end_byte)` per line, matching `str::lines()`: the
29/// visible end excludes the trailing `\n`/`\r\n`.
30pub(crate) fn line_bounds(content: &str) -> Vec<(usize, usize)> {
31    let bytes = content.as_bytes();
32    let mut out = Vec::new();
33    let mut start = 0;
34    for (i, &b) in bytes.iter().enumerate() {
35        if b == b'\n' {
36            let end = if i > start && bytes.get(i - 1) == Some(&b'\r') {
37                i - 1
38            } else {
39                i
40            };
41            out.push((start, end));
42            start = i + 1;
43        }
44    }
45    if start < bytes.len() {
46        let end = if bytes.last() == Some(&b'\r') {
47            bytes.len() - 1
48        } else {
49            bytes.len()
50        };
51        out.push((start, end));
52    }
53    out
54}
55
56/// Split a whole-file byte `range` across the lines it covers, calling `emit`
57/// with each line's index and the range clamped and rebased to that line's
58/// visible region. `starts` is the first column of `line_bounds`.
59pub(crate) fn split_range_by_line(
60    bounds: &[(usize, usize)],
61    starts: &[usize],
62    range: &Range<usize>,
63    mut emit: impl FnMut(usize, Range<usize>),
64) {
65    let mut li = match starts.binary_search(&range.start) {
66        Ok(i) => i,
67        Err(i) => i.saturating_sub(1),
68    };
69    while let Some(&(ls, le)) = bounds.get(li) {
70        if ls >= range.end {
71            break;
72        }
73        let s = range.start.max(ls);
74        let e = range.end.min(le);
75        if s < e {
76            emit(li, (s - ls)..(e - ls));
77        }
78        li += 1;
79    }
80}