substrate_data/forms/text/
line_col.rs1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct LineCol {
8 pub line: u32,
10 pub col: u32,
12}
13
14impl LineCol {
15 #[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 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}