quarto_source_map/
utils.rs1use crate::types::{Location, Range};
4
5pub fn offset_to_location(source: &str, offset: usize) -> Option<Location> {
9 if offset > source.len() {
10 return None;
11 }
12
13 let mut row = 0;
14 let mut column = 0;
15 let mut current_offset = 0;
16 let mut safe_offset = offset;
20
21 for ch in source.chars() {
22 if current_offset >= offset {
23 break;
24 }
25
26 let char_len = ch.len_utf8();
27 if offset < current_offset + char_len {
28 safe_offset = current_offset;
33 break;
34 }
35
36 if ch == '\n' {
37 row += 1;
38 column = 0;
39 } else {
40 column += 1;
41 }
42
43 current_offset += char_len;
44 }
45
46 Some(Location {
47 offset: safe_offset,
48 row,
49 column,
50 })
51}
52
53pub fn line_col_to_offset(source: &str, line: usize, col: usize) -> Option<usize> {
57 let mut current_line = 0;
58 let mut current_col = 0;
59 let mut offset = 0;
60
61 for ch in source.chars() {
62 if current_line == line && current_col == col {
63 return Some(offset);
64 }
65
66 if ch == '\n' {
67 current_line += 1;
68 current_col = 0;
69 } else {
70 current_col += 1;
71 }
72
73 offset += ch.len_utf8();
74 }
75
76 if current_line == line && current_col == col {
78 return Some(offset);
79 }
80
81 None
82}
83
84pub fn range_from_offsets(start: usize, end: usize) -> Range {
90 Range {
91 start: Location {
92 offset: start,
93 row: 0,
94 column: 0,
95 },
96 end: Location {
97 offset: end,
98 row: 0,
99 column: 0,
100 },
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_offset_to_location_simple() {
110 let source = "hello\nworld";
111
112 let loc = offset_to_location(source, 0).unwrap();
114 assert_eq!(loc.offset, 0);
115 assert_eq!(loc.row, 0);
116 assert_eq!(loc.column, 0);
117
118 let loc = offset_to_location(source, 3).unwrap();
120 assert_eq!(loc.offset, 3);
121 assert_eq!(loc.row, 0);
122 assert_eq!(loc.column, 3);
123
124 let loc = offset_to_location(source, 6).unwrap();
126 assert_eq!(loc.offset, 6);
127 assert_eq!(loc.row, 1);
128 assert_eq!(loc.column, 0);
129
130 let loc = offset_to_location(source, 9).unwrap();
132 assert_eq!(loc.offset, 9);
133 assert_eq!(loc.row, 1);
134 assert_eq!(loc.column, 3);
135 }
136
137 #[test]
138 fn test_offset_to_location_out_of_bounds() {
139 let source = "hello";
140 assert!(offset_to_location(source, 100).is_none());
141 }
142
143 #[test]
144 fn test_offset_to_location_end() {
145 let source = "hello";
146 let loc = offset_to_location(source, 5).unwrap();
147 assert_eq!(loc.offset, 5);
148 assert_eq!(loc.row, 0);
149 assert_eq!(loc.column, 5);
150 }
151
152 #[test]
153 fn test_line_col_to_offset_simple() {
154 let source = "hello\nworld";
155
156 let offset = line_col_to_offset(source, 0, 0).unwrap();
158 assert_eq!(offset, 0);
159
160 let offset = line_col_to_offset(source, 0, 3).unwrap();
162 assert_eq!(offset, 3);
163
164 let offset = line_col_to_offset(source, 1, 0).unwrap();
166 assert_eq!(offset, 6);
167
168 let offset = line_col_to_offset(source, 1, 3).unwrap();
170 assert_eq!(offset, 9);
171 }
172
173 #[test]
174 fn test_line_col_to_offset_out_of_bounds() {
175 let source = "hello\nworld";
176 assert!(line_col_to_offset(source, 10, 0).is_none());
177 assert!(line_col_to_offset(source, 0, 100).is_none());
178 }
179
180 #[test]
181 fn test_line_col_to_offset_end() {
182 let source = "hello";
183 let offset = line_col_to_offset(source, 0, 5).unwrap();
184 assert_eq!(offset, 5);
185 }
186
187 #[test]
188 fn test_roundtrip() {
189 let source = "hello\nworld\ntest";
190
191 for test_offset in [0, 3, 6, 10, 16] {
193 let loc = offset_to_location(source, test_offset).unwrap();
194 let back_to_offset = line_col_to_offset(source, loc.row, loc.column).unwrap();
195 assert_eq!(test_offset, back_to_offset);
196 }
197 }
198
199 #[test]
200 fn test_range_from_offsets() {
201 let range = range_from_offsets(10, 20);
202 assert_eq!(range.start.offset, 10);
203 assert_eq!(range.end.offset, 20);
204 assert_eq!(range.start.row, 0);
205 assert_eq!(range.start.column, 0);
206 }
207
208 #[test]
209 fn test_offset_to_location_multiline() {
210 let source = "line1\nline2\nline3";
211
212 let loc = offset_to_location(source, 0).unwrap();
214 assert_eq!(loc.row, 0);
215 assert_eq!(loc.column, 0);
216
217 let loc = offset_to_location(source, 6).unwrap();
218 assert_eq!(loc.row, 1);
219 assert_eq!(loc.column, 0);
220
221 let loc = offset_to_location(source, 12).unwrap();
222 assert_eq!(loc.row, 2);
223 assert_eq!(loc.column, 0);
224 }
225
226 #[test]
227 fn test_offset_to_location_agrees_with_file_information_on_mid_char_offset() {
228 let source = "x = 'A✨B'";
237 let loc = offset_to_location(source, 7).unwrap();
238 assert_eq!(loc.offset, 6);
239 assert_eq!(loc.row, 0);
240 assert_eq!(loc.column, 6);
241 }
242}