1pub fn byte_column_to_char_column(line_content: &str, byte_column: usize) -> usize {
46 if byte_column <= 1 {
47 return 1;
48 }
49
50 let byte_offset = byte_column - 1;
52
53 let char_offset = byte_offset_to_char_offset(line_content, byte_offset);
55
56 char_offset + 1
58}
59
60pub fn get_line_content(content: &str, line_number: usize) -> Option<&str> {
62 if line_number == 0 {
63 return None;
64 }
65 content.lines().nth(line_number - 1)
66}
67
68pub fn byte_offset_to_char_offset(content: &str, byte_offset: usize) -> usize {
69 if byte_offset == 0 {
71 return 0;
72 }
73
74 if byte_offset >= content.len() {
75 return content.chars().count();
76 }
77
78 content
80 .char_indices()
81 .take_while(|(byte_idx, _)| *byte_idx < byte_offset)
82 .count()
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_byte_offset_to_char_offset_empty() {
91 assert_eq!(byte_offset_to_char_offset("", 0), 0);
92 assert_eq!(byte_offset_to_char_offset("", 1), 0);
93 }
94
95 #[test]
96 fn test_byte_offset_to_char_offset_ascii() {
97 let content = "Hello World";
99 assert_eq!(byte_offset_to_char_offset(content, 0), 0);
100 assert_eq!(byte_offset_to_char_offset(content, 5), 5);
101 assert_eq!(byte_offset_to_char_offset(content, 11), 11);
102 assert_eq!(byte_offset_to_char_offset(content, 100), 11);
104 }
105
106 #[test]
107 fn test_byte_offset_to_char_offset_norwegian() {
108 let content = "æ"; assert_eq!(content.len(), 2); assert_eq!(content.chars().count(), 1); assert_eq!(byte_offset_to_char_offset(content, 0), 0);
113 assert_eq!(byte_offset_to_char_offset(content, 2), 1); }
115
116 #[test]
117 fn test_byte_offset_to_char_offset_mixed() {
118 let content = "Hello æ world";
120 assert_eq!(content.len(), 14); assert_eq!(content.chars().count(), 13); assert_eq!(byte_offset_to_char_offset(content, 6), 6); assert_eq!(byte_offset_to_char_offset(content, 8), 7); assert_eq!(byte_offset_to_char_offset(content, 14), 13);
131 }
132
133 #[test]
134 fn test_byte_offset_to_char_offset_emoji() {
135 let content = "Hi 👋"; assert_eq!(content.len(), 7);
138 assert_eq!(content.chars().count(), 4);
139 assert_eq!(byte_offset_to_char_offset(content, 3), 3); assert_eq!(byte_offset_to_char_offset(content, 7), 4); }
142
143 #[test]
144 fn test_byte_offset_to_char_offset_norwegian_sentence() {
145 let content = "# Heading\n\nContent with Norwegian letter \"æ\".";
147 assert_eq!(content.len(), 46); assert_eq!(content.chars().count(), 45); assert_eq!(byte_offset_to_char_offset(content, 46), 45);
152 }
153
154 #[test]
155 fn test_byte_offset_to_char_offset_multiple_multibyte() {
156 let content = "café résumé"; assert_eq!(content.len(), 14);
159 assert_eq!(content.chars().count(), 11);
160
161 assert_eq!(byte_offset_to_char_offset(content, 0), 0);
162 assert_eq!(byte_offset_to_char_offset(content, 3), 3); assert_eq!(byte_offset_to_char_offset(content, 5), 4); assert_eq!(byte_offset_to_char_offset(content, 14), 11); }
166
167 #[test]
168 fn test_byte_column_to_char_column() {
169 let line = "Content with Norwegian letter \"æ\".";
171 assert_eq!(line.len(), 35);
174 assert_eq!(line.chars().count(), 34);
175
176 assert_eq!(byte_column_to_char_column(line, 1), 1);
178
179 assert_eq!(byte_column_to_char_column(line, 30), 30);
181
182 assert_eq!(byte_column_to_char_column(line, 32), 32);
184
185 assert_eq!(byte_column_to_char_column(line, 34), 33);
187
188 assert_eq!(byte_column_to_char_column(line, 36), 35);
190 }
191
192 #[test]
193 fn test_byte_column_to_char_column_edge_cases() {
194 assert_eq!(byte_column_to_char_column("", 1), 1);
196 assert_eq!(byte_column_to_char_column("", 0), 1);
197
198 let ascii = "Hello World";
200 assert_eq!(byte_column_to_char_column(ascii, 1), 1);
201 assert_eq!(byte_column_to_char_column(ascii, 6), 6);
202 assert_eq!(byte_column_to_char_column(ascii, 12), 12); let multi = "æøå"; assert_eq!(multi.len(), 6);
207 assert_eq!(multi.chars().count(), 3);
208 assert_eq!(byte_column_to_char_column(multi, 1), 1); assert_eq!(byte_column_to_char_column(multi, 3), 2); assert_eq!(byte_column_to_char_column(multi, 5), 3); assert_eq!(byte_column_to_char_column(multi, 7), 4); let emoji = "Hi 👋!"; assert_eq!(emoji.len(), 8);
216 assert_eq!(emoji.chars().count(), 5);
217 assert_eq!(byte_column_to_char_column(emoji, 4), 4); assert_eq!(byte_column_to_char_column(emoji, 8), 5); assert_eq!(byte_column_to_char_column(emoji, 9), 6); let only_multi = "日本語"; assert_eq!(only_multi.len(), 9);
224 assert_eq!(only_multi.chars().count(), 3);
225 assert_eq!(byte_column_to_char_column(only_multi, 1), 1);
226 assert_eq!(byte_column_to_char_column(only_multi, 4), 2);
227 assert_eq!(byte_column_to_char_column(only_multi, 7), 3);
228 assert_eq!(byte_column_to_char_column(only_multi, 10), 4);
229 }
230
231 #[test]
232 fn test_byte_column_to_char_column_bug_scenario() {
233 let line = "Content with Norwegian letter \"æ\".";
237
238 let byte_column_at_end = line.len() + 1; let expected_char_column = line.chars().count() + 1; assert_eq!(
245 byte_column_to_char_column(line, byte_column_at_end),
246 expected_char_column,
247 "End-of-line column should be converted from byte {byte_column_at_end} to char {expected_char_column}"
248 );
249
250 let line_from = 11_usize;
255 let from_position = line_from + (expected_char_column - 1);
256 assert_eq!(from_position, 45, "Fix position should be 45, not 46");
257 }
258
259 #[test]
260 fn test_get_line_content() {
261 let content = "# Heading\n\nContent with Norwegian letter \"æ\".";
262
263 assert_eq!(get_line_content(content, 1), Some("# Heading"));
264 assert_eq!(get_line_content(content, 2), Some(""));
265 assert_eq!(
266 get_line_content(content, 3),
267 Some("Content with Norwegian letter \"æ\".")
268 );
269 assert_eq!(get_line_content(content, 4), None);
270 assert_eq!(get_line_content(content, 0), None);
271 }
272
273 #[test]
274 fn test_get_line_content_edge_cases() {
275 assert_eq!(get_line_content("", 1), None);
277 assert_eq!(get_line_content("", 0), None);
278
279 assert_eq!(get_line_content("Hello", 1), Some("Hello"));
281 assert_eq!(get_line_content("Hello", 2), None);
282
283 let content = "\n\n\n";
285 assert_eq!(get_line_content(content, 1), Some(""));
286 assert_eq!(get_line_content(content, 2), Some(""));
287 assert_eq!(get_line_content(content, 3), Some(""));
288 assert_eq!(get_line_content(content, 4), None);
289
290 let content = "Line 1\næøå\n日本語\n👋🎉";
292 assert_eq!(get_line_content(content, 1), Some("Line 1"));
293 assert_eq!(get_line_content(content, 2), Some("æøå"));
294 assert_eq!(get_line_content(content, 3), Some("日本語"));
295 assert_eq!(get_line_content(content, 4), Some("👋🎉"));
296 assert_eq!(get_line_content(content, 5), None);
297 }
298}