rust_analyzer_mcp/
position.rs1pub fn byte_offset(text: &str, line: u32, character: u32) -> usize {
14 let mut offset = 0;
15
16 for (number, line_text) in text.split_inclusive('\n').enumerate() {
17 if number as u32 == line {
18 return offset + column_offset(line_text, character);
19 }
20 offset += line_text.len();
21 }
22
23 text.len()
25}
26
27fn column_offset(line: &str, character: u32) -> usize {
29 let line = line.strip_suffix('\n').unwrap_or(line);
30 let line = line.strip_suffix('\r').unwrap_or(line);
31
32 let mut counted = 0;
33 for (offset, character_here) in line.char_indices() {
34 if counted >= character {
35 return offset;
36 }
37
38 let after = counted + character_here.len_utf16() as u32;
41 if after > character {
42 return offset;
43 }
44 counted = after;
45 }
46
47 line.len()
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 const TEXT: &str = "fn main() {\n let x = 1;\n}\n";
55
56 #[test]
57 fn a_position_is_a_line_and_a_column() {
58 assert_eq!(byte_offset(TEXT, 0, 0), 0);
59 assert_eq!(byte_offset(TEXT, 0, 3), 3);
60 assert_eq!(byte_offset(TEXT, 1, 4), "fn main() {\n ".len());
61 assert_eq!(byte_offset(TEXT, 2, 0), TEXT.len() - 2);
62 }
63
64 #[test]
65 fn columns_are_counted_in_utf16_code_units() {
66 let text = "let é = \"🦀\";";
68
69 assert_eq!(byte_offset(text, 0, 4), "let ".len());
70 assert_eq!(byte_offset(text, 0, 5), "let é".len());
71 assert_eq!(byte_offset(text, 0, 9), "let é = \"".len());
72 assert_eq!(byte_offset(text, 0, 11), "let é = \"🦀".len());
74 }
75
76 #[test]
77 fn a_column_inside_a_character_stops_before_it() {
78 let text = "🦀🦀";
80
81 assert_eq!(byte_offset(text, 0, 1), 0);
82 assert_eq!(byte_offset(text, 0, 2), "🦀".len());
83 assert_eq!(byte_offset(text, 0, 3), "🦀".len());
84 }
85
86 #[test]
87 fn a_position_past_the_end_is_the_end() {
88 assert_eq!(byte_offset(TEXT, 0, 999), "fn main() {".len());
89 assert_eq!(byte_offset(TEXT, 99, 0), TEXT.len());
90 assert_eq!(byte_offset("", 0, 0), 0);
91 }
92
93 #[test]
94 fn line_endings_are_not_part_of_the_line() {
95 let text = "one\r\ntwo\r\n";
96
97 assert_eq!(byte_offset(text, 0, 99), "one".len());
98 assert_eq!(byte_offset(text, 1, 0), "one\r\n".len());
99 assert_eq!(byte_offset(text, 1, 3), "one\r\ntwo".len());
100 }
101}