1use crate::report::TextEdit;
2
3#[derive(Debug, thiserror::Error)]
4pub enum FixError {
5 #[error("invalid line/column")]
6 InvalidLineColumn,
7 #[error("invalid utf-8 boundaries for edit")]
8 InvalidUtf8Boundary,
9 #[error("edit out of bounds")]
10 OutOfBounds,
11 #[error("overlapping edits")]
12 Overlap,
13}
14
15pub fn line_col_to_byte_offset(text: &str, line_1: u32, col_1: u32) -> Result<usize, FixError> {
16 if line_1 == 0 || col_1 == 0 {
17 return Err(FixError::InvalidLineColumn);
18 }
19
20 let mut current_line: u32 = 1;
21 let mut idx: usize = 0;
22 while current_line < line_1 {
23 let Some(pos) = text[idx..].find('\n') else {
24 return Err(FixError::InvalidLineColumn);
25 };
26 idx += pos + 1;
27 current_line += 1;
28 }
29
30 let line_end = text[idx..].find('\n').map_or(text.len(), |p| idx + p);
31 let line_slice = &text[idx..line_end];
32
33 let target_col = (col_1 - 1) as usize;
34 let mut col: usize = 0;
35 let mut byte_in_line: usize = 0;
36 for (bidx, ch) in line_slice.char_indices() {
37 if col == target_col {
38 byte_in_line = bidx;
39 break;
40 }
41 col += 1;
42 byte_in_line = bidx + ch.len_utf8();
43 }
44 if col < target_col && target_col != col {
45 return Err(FixError::InvalidLineColumn);
46 }
47
48 Ok(idx + byte_in_line)
49}
50
51pub fn apply_text_edits(text: &str, edits: &[TextEdit]) -> Result<String, FixError> {
52 let mut out = text.to_string();
53 let mut ordered = edits.to_vec();
54 ordered.sort_by(|a, b| {
55 b.byte_start
56 .cmp(&a.byte_start)
57 .then(b.byte_end.cmp(&a.byte_end))
58 });
59
60 let mut last_start: Option<u32> = None;
61 for e in &ordered {
62 if e.byte_start > e.byte_end {
63 return Err(FixError::OutOfBounds);
64 }
65 if let Some(last) = last_start {
66 if e.byte_end > last {
67 return Err(FixError::Overlap);
68 }
69 }
70 last_start = Some(e.byte_start);
71
72 let start = e.byte_start as usize;
73 let end = e.byte_end as usize;
74 if end > out.len() {
75 return Err(FixError::OutOfBounds);
76 }
77 if !out.is_char_boundary(start) || !out.is_char_boundary(end) {
78 return Err(FixError::InvalidUtf8Boundary);
79 }
80 out.replace_range(start..end, &e.replacement);
81 }
82
83 Ok(out)
84}
85
86pub fn find_use_insertion_offset(text: &str) -> usize {
87 let mut offset: usize = 0;
88 for line in text.split_inclusive('\n') {
89 let trimmed = line.trim_start();
90 let is_inner_attr = trimmed.starts_with("#![");
91 let is_inner_doc = trimmed.starts_with("//!") || trimmed.starts_with("/*!");
92 if is_inner_attr || is_inner_doc {
93 offset += line.len();
94 continue;
95 }
96 break;
97 }
98 offset
99}