speed_reader_core/
position.rs1#[derive(Debug, Clone, PartialEq)]
5pub struct SourcePosition {
6 pub byte_offset: usize,
7 pub line: usize,
8 pub column: usize,
9 pub context: String,
10}
11
12pub struct PositionTracker {
13 line_offsets: Vec<usize>,
14 text: String,
15}
16
17impl PositionTracker {
18 pub fn new(text: &str) -> Self {
19 let mut line_offsets = vec![0usize];
20 for (i, c) in text.char_indices() {
21 if c == '\n' {
22 line_offsets.push(i + 1);
23 }
24 }
25 Self {
26 line_offsets,
27 text: text.to_string(),
28 }
29 }
30
31 pub fn position_for_offset(&self, byte_offset: usize) -> SourcePosition {
33 let byte_offset = byte_offset.min(self.text.len());
34 let line = match self.line_offsets.binary_search(&byte_offset) {
36 Ok(line_idx) => line_idx + 1,
37 Err(line_idx) => line_idx, };
39 let line = line.max(1);
40 let line_start = self.line_offsets[line - 1];
41 let column = byte_offset - line_start;
42
43 let line_end = if line < self.line_offsets.len() {
45 self.line_offsets[line]
46 } else {
47 self.text.len()
48 };
49 let context = self.text[line_start..line_end]
50 .trim_end_matches('\n')
51 .trim_end_matches('\r')
52 .to_string();
53
54 SourcePosition {
55 byte_offset,
56 line,
57 column,
58 context,
59 }
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn test_single_line() {
69 let tracker = PositionTracker::new("Hello world");
70 let pos = tracker.position_for_offset(6);
71 assert_eq!(pos.line, 1);
72 assert_eq!(pos.column, 6);
73 assert_eq!(pos.context, "Hello world");
74 }
75
76 #[test]
77 fn test_multi_line() {
78 let tracker = PositionTracker::new("Line one\nLine two\nLine three");
79 let pos = tracker.position_for_offset(14); assert_eq!(pos.line, 2);
81 assert_eq!(pos.column, 5);
82 assert_eq!(pos.context, "Line two");
83 }
84
85 #[test]
86 fn test_first_line() {
87 let tracker = PositionTracker::new("Hello\nWorld");
88 let pos = tracker.position_for_offset(0);
89 assert_eq!(pos.line, 1);
90 assert_eq!(pos.column, 0);
91 }
92
93 #[test]
94 fn test_beyond_text() {
95 let tracker = PositionTracker::new("Hello");
96 let pos = tracker.position_for_offset(100);
97 assert_eq!(pos.byte_offset, 5); }
99}