use crate::completion::char_to_byte;
#[must_use]
pub fn insert(text: &str, cursor: usize, insertion: &str) -> (String, usize) {
let byte = char_to_byte(text, cursor);
let mut result = text.to_owned();
result.insert_str(byte, insertion);
(result, cursor + insertion.chars().count())
}
#[must_use]
pub fn newline_indent(text: &str, cursor: usize) -> (String, usize) {
let line_start = line_start_char(text, cursor);
let indent: String = text
.chars()
.skip(line_start)
.take_while(|c| *c == ' ' || *c == '\t')
.collect();
let mut insertion = String::with_capacity(indent.len() + 1);
insertion.push('\n');
insertion.push_str(&indent);
insert(text, cursor, &insertion)
}
#[must_use]
pub fn indent_line(text: &str, cursor: usize, width: usize) -> (String, usize) {
let start = line_start_char(text, cursor);
let (result, _) = insert(text, start, &" ".repeat(width));
(result, cursor + width)
}
#[must_use]
pub fn dedent_line(text: &str, cursor: usize, width: usize) -> (String, usize) {
let start = line_start_char(text, cursor);
let removable = text
.chars()
.skip(start)
.take(width)
.take_while(|c| *c == ' ')
.count();
if removable == 0 {
return (text.to_owned(), cursor);
}
let start_byte = char_to_byte(text, start);
let end_byte = char_to_byte(text, start + removable);
let mut result = text.to_owned();
result.replace_range(start_byte..end_byte, "");
let new_cursor = if cursor >= start + removable {
cursor - removable
} else {
start
};
(result, new_cursor)
}
#[must_use]
pub fn changed_range(before: &str, after: &str) -> (usize, usize) {
let before_len = before.chars().count();
let after_len = after.chars().count();
let prefix = before
.chars()
.zip(after.chars())
.take_while(|(old, new)| old == new)
.count();
let suffix = before
.chars()
.rev()
.zip(after.chars().rev())
.take_while(|(old, new)| old == new)
.count()
.min(before_len.min(after_len) - prefix);
(prefix, after_len - suffix)
}
#[must_use]
pub fn line_start_char(text: &str, cursor: usize) -> usize {
text.chars()
.take(cursor)
.enumerate()
.filter_map(|(index, character)| (character == '\n').then_some(index + 1))
.last()
.unwrap_or(0)
}
#[must_use]
pub fn line_end_char(text: &str, cursor: usize) -> usize {
text.chars()
.skip(cursor)
.position(|character| character == '\n')
.map_or_else(|| text.chars().count(), |offset| cursor + offset)
}