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}