Skip to main content

jsslint_core/tex/
position.rs

1//! `pos_to_lineno_colno` port — mirrors `pylatexenc.latexwalker`'s
2//! method of the same name (empirically verified: 1-based line, 0-based
3//! column, both counted in codepoints, splitting on `\n`).
4//!
5//! `_helpers.py::_lineno_col` then adds 1 to the column to get the
6//! 1-based column `Violation` actually carries — that final `+1` step
7//! belongs to the rule layer (Phase 3), not here; this module only
8//! reproduces the walker-level primitive.
9
10/// Precomputed newline codepoint-offsets for O(log n) line lookup —
11/// same idea as `pylatexenc`'s internal line-boundary cache.
12pub struct LineIndex {
13    /// Offset of each `\n` character in the source.
14    newline_positions: Vec<usize>,
15    /// Added to every `lineno_colno` line result. Zero for a normal
16    /// `.tex`/`.rnw` file; non-zero for a `.Rmd` raw-LaTeX prose
17    /// fragment, whose `chars`/`nodes` positions stay fragment-local
18    /// (matching Python's `node.pos`, which `_OffsetWalker` never
19    /// touches) while reported line numbers must still be
20    /// source-accurate on the original `.Rmd`. Mirrors
21    /// `rmd_parser.py::_OffsetWalker.pos_to_lineno_colno`'s `line +
22    /// self._offset`, applied here instead of via a wrapper type.
23    line_offset: u32,
24}
25
26impl LineIndex {
27    pub fn new(chars: &[char]) -> Self {
28        Self::with_offset(chars, 0)
29    }
30
31    /// Like [`Self::new`], but every `lineno_colno` result has
32    /// `offset` added to its line. `pos` arguments stay relative to
33    /// `chars` as given (fragment-local), not to the offset target.
34    pub fn with_offset(chars: &[char], offset: u32) -> Self {
35        let newline_positions = chars
36            .iter()
37            .enumerate()
38            .filter(|(_, &c)| c == '\n')
39            .map(|(i, _)| i)
40            .collect();
41        Self {
42            newline_positions,
43            line_offset: offset,
44        }
45    }
46
47    /// Returns `(1-based line, 0-based column)`, both in codepoints.
48    pub fn lineno_colno(&self, pos: usize) -> (u32, u32) {
49        // Number of newlines strictly before `pos` = 0-based line index.
50        let line_idx = self.newline_positions.partition_point(|&nl| nl < pos);
51        let line = (line_idx + 1) as u32 + self.line_offset;
52        let line_start = if line_idx == 0 {
53            0
54        } else {
55            self.newline_positions[line_idx - 1] + 1
56        };
57        let col = (pos - line_start) as u32;
58        (line, col)
59    }
60}