Skip to main content

substrate_data/forms/text/
line_col.rs

1//! [`LineCol`]: a 1-based line and column.
2
3use std::fmt;
4
5/// A 1-based line and column (in characters).
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct LineCol {
8    /// The line, from 1.
9    pub line: u32,
10    /// The column in characters, from 1.
11    pub col: u32,
12}
13
14impl LineCol {
15    /// Where byte `offset` of `text` is.
16    #[must_use]
17    pub fn of(text: &str, offset: usize) -> LineCol {
18        let offset = offset.min(text.len());
19        let before = &text.as_bytes()[..offset];
20        let lines = memchr::memchr_iter(b'\n', before).count() + 1;
21        let line_start = memchr::memrchr(b'\n', before).map_or(0, |i| i + 1);
22        let col = text
23            .get(line_start..offset)
24            .map_or(offset - line_start, |s| s.chars().count())
25            + 1;
26        LineCol {
27            line: u32::try_from(lines).unwrap_or(u32::MAX),
28            col: u32::try_from(col).unwrap_or(u32::MAX),
29        }
30    }
31}
32
33impl fmt::Display for LineCol {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(f, "{}:{}", self.line, self.col)
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::LineCol;
42
43    /// The scalar computation `LineCol::of` used before `memchr`.
44    fn scalar(text: &str, offset: usize) -> LineCol {
45        let offset = offset.min(text.len());
46        let before = &text.as_bytes()[..offset];
47        let lines = before.split(|&b| b == b'\n').count();
48        let line_start = before
49            .iter()
50            .rposition(|&b| b == b'\n')
51            .map_or(0, |i| i + 1);
52        let col = text
53            .get(line_start..offset)
54            .map_or(offset - line_start, |s| s.chars().count())
55            + 1;
56        LineCol {
57            line: u32::try_from(lines).unwrap_or(u32::MAX),
58            col: u32::try_from(col).unwrap_or(u32::MAX),
59        }
60    }
61
62    #[test]
63    fn matches_the_scalar_computation_at_every_offset() {
64        for text in [
65            "",
66            "one line",
67            "a\nb\n\nnaïve\n日本語 😀\nend",
68            "\n\n\n",
69            "tail\n",
70        ] {
71            for offset in 0..=text.len() + 2 {
72                assert_eq!(
73                    LineCol::of(text, offset),
74                    scalar(text, offset),
75                    "{text:?} {offset}"
76                );
77            }
78        }
79    }
80}