1use crate::completion::char_to_byte;
4
5#[must_use]
8pub fn insert(text: &str, cursor: usize, insertion: &str) -> (String, usize) {
9 let byte = char_to_byte(text, cursor);
10 let mut result = text.to_owned();
11 result.insert_str(byte, insertion);
12 (result, cursor + insertion.chars().count())
13}
14
15#[must_use]
18pub fn newline_indent(text: &str, cursor: usize) -> (String, usize) {
19 let line_start = line_start_char(text, cursor);
20 let indent: String = text
21 .chars()
22 .skip(line_start)
23 .take_while(|c| *c == ' ' || *c == '\t')
24 .collect();
25 let mut insertion = String::with_capacity(indent.len() + 1);
26 insertion.push('\n');
27 insertion.push_str(&indent);
28 insert(text, cursor, &insertion)
29}
30
31#[must_use]
34pub fn indent_line(text: &str, cursor: usize, width: usize) -> (String, usize) {
35 let start = line_start_char(text, cursor);
36 let (result, _) = insert(text, start, &" ".repeat(width));
37 (result, cursor + width)
38}
39
40#[must_use]
43pub fn dedent_line(text: &str, cursor: usize, width: usize) -> (String, usize) {
44 let start = line_start_char(text, cursor);
45 let removable = text
46 .chars()
47 .skip(start)
48 .take(width)
49 .take_while(|c| *c == ' ')
50 .count();
51 if removable == 0 {
52 return (text.to_owned(), cursor);
53 }
54 let start_byte = char_to_byte(text, start);
55 let end_byte = char_to_byte(text, start + removable);
56 let mut result = text.to_owned();
57 result.replace_range(start_byte..end_byte, "");
58 let new_cursor = if cursor >= start + removable {
59 cursor - removable
60 } else {
61 start
62 };
63 (result, new_cursor)
64}
65
66#[must_use]
72pub fn changed_range(before: &str, after: &str) -> (usize, usize) {
73 let before_len = before.chars().count();
74 let after_len = after.chars().count();
75 let prefix = before
76 .chars()
77 .zip(after.chars())
78 .take_while(|(old, new)| old == new)
79 .count();
80 let suffix = before
81 .chars()
82 .rev()
83 .zip(after.chars().rev())
84 .take_while(|(old, new)| old == new)
85 .count()
86 .min(before_len.min(after_len) - prefix);
87 (prefix, after_len - suffix)
88}
89
90#[must_use]
92pub fn line_start_char(text: &str, cursor: usize) -> usize {
93 text.chars()
94 .take(cursor)
95 .enumerate()
96 .filter_map(|(index, character)| (character == '\n').then_some(index + 1))
97 .last()
98 .unwrap_or(0)
99}
100
101#[must_use]
103pub fn line_end_char(text: &str, cursor: usize) -> usize {
104 text.chars()
105 .skip(cursor)
106 .position(|character| character == '\n')
107 .map_or_else(|| text.chars().count(), |offset| cursor + offset)
108}