use std::{borrow::Cow, ops::Range};
use epaint::{
Galley,
text::{
ByteIndex, ByteRangeExt as _, CharIndex, CharRange, CharRangeExt as _, cursor::CCursor,
},
};
const TAB_SIZE: usize = 4;
use crate::{
text::CCursorRange,
text_selection::text_cursor_state::{
byte_index_from_char_index, ccursor_next_word, ccursor_previous_word,
char_index_from_byte_index, find_line_start, slice_char_range,
},
};
pub trait TextBuffer {
fn is_mutable(&self) -> bool;
fn as_str(&self) -> &str;
fn insert_text(&mut self, text: &str, char_index: CharIndex) -> usize;
fn delete_char_range(&mut self, char_range: Range<CharIndex>);
fn char_range(&self, char_range: Range<CharIndex>) -> &str {
slice_char_range(self.as_str(), char_range)
}
fn byte_index_from_char_index(&self, char_index: CharIndex) -> ByteIndex {
byte_index_from_char_index(self.as_str(), char_index)
}
fn char_index_from_byte_index(&self, byte_index: ByteIndex) -> CharIndex {
char_index_from_byte_index(self.as_str(), byte_index)
}
fn clear(&mut self) {
self.delete_char_range(CharRange::full(self.as_str()));
}
fn replace_with(&mut self, text: &str) {
self.clear();
self.insert_text(text, CharIndex(0));
}
fn take(&mut self) -> String {
let s = self.as_str().to_owned();
self.clear();
s
}
fn insert_text_at(&mut self, ccursor: &mut CCursor, text_to_insert: &str, char_limit: usize) {
if char_limit < usize::MAX {
let mut new_string = text_to_insert;
let cutoff = char_limit.saturating_sub(self.as_str().chars().count());
new_string = match new_string.char_indices().nth(cutoff) {
None => new_string,
Some((idx, _)) => &new_string[..idx],
};
ccursor.index += self.insert_text(new_string, ccursor.index);
} else {
ccursor.index += self.insert_text(text_to_insert, ccursor.index);
}
}
fn decrease_indentation(&mut self, ccursor: &mut CCursor) {
let line_start = find_line_start(self.as_str(), *ccursor);
let remove_len = if self.as_str().chars().nth(line_start.index.0) == Some('\t') {
Some(1)
} else if self
.as_str()
.chars()
.skip(line_start.index.0)
.take(TAB_SIZE)
.all(|c| c == ' ')
{
Some(TAB_SIZE)
} else {
None
};
if let Some(len) = remove_len {
self.delete_char_range(line_start.index..(line_start.index + len));
if *ccursor != line_start {
*ccursor -= len;
}
}
}
fn delete_selected(&mut self, cursor_range: &CCursorRange) -> CCursor {
let [min, max] = cursor_range.sorted_cursors();
self.delete_selected_ccursor_range([min, max])
}
fn delete_selected_ccursor_range(&mut self, [min, max]: [CCursor; 2]) -> CCursor {
self.delete_char_range(min.index..max.index);
CCursor {
index: min.index,
prefer_next_row: true,
}
}
fn delete_previous_char(&mut self, ccursor: CCursor) -> CCursor {
if CharIndex::ZERO < ccursor.index {
let max_ccursor = ccursor;
let min_ccursor = max_ccursor - 1;
self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
} else {
ccursor
}
}
fn delete_next_char(&mut self, ccursor: CCursor) -> CCursor {
self.delete_selected_ccursor_range([ccursor, ccursor + 1])
}
fn delete_previous_word(&mut self, max_ccursor: CCursor) -> CCursor {
let min_ccursor = ccursor_previous_word(self.as_str(), max_ccursor);
self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
}
fn delete_next_word(&mut self, min_ccursor: CCursor) -> CCursor {
let max_ccursor = ccursor_next_word(self.as_str(), min_ccursor);
self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
}
fn delete_surrounding_chars(
&mut self,
mut cursor_range: CCursorRange,
before_chars: usize,
after_chars: usize,
) -> CCursorRange {
let [min, max] = cursor_range.sorted_cursors();
if after_chars > 0 {
self.delete_selected_ccursor_range([max, max + after_chars]);
}
if before_chars > 0 {
self.delete_selected_ccursor_range([min - before_chars, min]);
cursor_range.primary -= before_chars;
cursor_range.secondary -= before_chars;
}
cursor_range
}
fn delete_paragraph_before_cursor(
&mut self,
galley: &Galley,
cursor_range: &CCursorRange,
) -> CCursor {
let [min, max] = cursor_range.sorted_cursors();
let min = galley.cursor_begin_of_paragraph(&min);
if min == max {
self.delete_previous_char(min)
} else {
self.delete_selected(&CCursorRange::two(min, max))
}
}
fn delete_paragraph_after_cursor(
&mut self,
galley: &Galley,
cursor_range: &CCursorRange,
) -> CCursor {
let [min, max] = cursor_range.sorted_cursors();
let max = galley.cursor_end_of_paragraph(&max);
if min == max {
self.delete_next_char(min)
} else {
self.delete_selected(&CCursorRange::two(min, max))
}
}
fn type_id(&self) -> std::any::TypeId;
}
impl TextBuffer for String {
fn is_mutable(&self) -> bool {
true
}
fn as_str(&self) -> &str {
self.as_ref()
}
fn insert_text(&mut self, text: &str, char_index: CharIndex) -> usize {
let byte_idx = byte_index_from_char_index(self.as_str(), char_index);
self.insert_str(byte_idx.into(), text);
text.chars().count()
}
fn delete_char_range(&mut self, char_range: Range<CharIndex>) {
assert!(
char_range.start <= char_range.end,
"start must be <= end, but got {char_range:?}"
);
let byte_start = byte_index_from_char_index(self.as_str(), char_range.start);
let byte_end = byte_index_from_char_index(self.as_str(), char_range.end);
self.drain((byte_start..byte_end).as_usize());
}
fn clear(&mut self) {
self.clear();
}
fn replace_with(&mut self, text: &str) {
text.clone_into(self);
}
fn take(&mut self) -> String {
std::mem::take(self)
}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self>()
}
}
impl TextBuffer for Cow<'_, str> {
fn is_mutable(&self) -> bool {
true
}
fn as_str(&self) -> &str {
self.as_ref()
}
fn insert_text(&mut self, text: &str, char_index: CharIndex) -> usize {
<String as TextBuffer>::insert_text(self.to_mut(), text, char_index)
}
fn delete_char_range(&mut self, char_range: Range<CharIndex>) {
<String as TextBuffer>::delete_char_range(self.to_mut(), char_range);
}
fn clear(&mut self) {
<String as TextBuffer>::clear(self.to_mut());
}
fn replace_with(&mut self, text: &str) {
*self = Cow::Owned(text.to_owned());
}
fn take(&mut self) -> String {
std::mem::take(self).into_owned()
}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Cow<'_, str>>()
}
}
impl TextBuffer for &str {
fn is_mutable(&self) -> bool {
false
}
fn as_str(&self) -> &str {
self
}
fn insert_text(&mut self, _text: &str, _ch_idx: CharIndex) -> usize {
0
}
fn delete_char_range(&mut self, _ch_range: Range<CharIndex>) {}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<&str>()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn txt_n_sel(input: &str) -> (String, CCursorRange) {
assert!(
input.matches('[').count() == 1 && input.matches(']').count() == 1,
"`input` must contain exactly one `[` and one `]` to indicate the selection (cursor range)"
);
let mut primary_index = input.chars().position(|c| c == ']').unwrap();
let mut secondary_index = input.chars().position(|c| c == '[').unwrap();
let text = input.replace(['[', ']'], "");
if primary_index > secondary_index {
primary_index -= 1;
} else {
secondary_index -= 1;
}
let cursor_range = CCursorRange {
primary: CCursor::new(primary_index),
secondary: CCursor::new(secondary_index),
h_pos: None,
};
(text, cursor_range)
}
#[test]
fn test_txt_n_sel() {
assert_eq!(
txt_n_sel("<<L[]R>>"),
("<<LR>>".to_owned(), CCursorRange::one(CCursor::new(3)))
);
assert_eq!(
txt_n_sel("<<L[_]R>>"),
(
"<<L_R>>".to_owned(),
CCursorRange::two(CCursor::new(3), CCursor::new(4))
)
);
assert_eq!(
txt_n_sel("<<左[_]右>>"),
(
"<<左_右>>".to_owned(),
CCursorRange::two(CCursor::new(3), CCursor::new(4))
)
);
assert_eq!(
txt_n_sel("<<L]_[R>>"),
(
"<<L_R>>".to_owned(),
CCursorRange::two(CCursor::new(4), CCursor::new(3))
)
);
}
#[test]
fn test_delete_surrounding_chars() {
fn test_case(
(mut input_text, input_cursor_range): (String, CCursorRange),
before_chars: usize,
after_chars: usize,
(expected_text, expected_cursor_range): (String, CCursorRange),
) {
let new_cursor_range =
input_text.delete_surrounding_chars(input_cursor_range, before_chars, after_chars);
assert_eq!(input_text, expected_text);
assert_eq!(new_cursor_range, expected_cursor_range);
}
test_case(txt_n_sel("<<L[]R>>"), 1, 1, txt_n_sel("<<[]>>"));
test_case(txt_n_sel("<<L[_]R>>"), 1, 0, txt_n_sel("<<[_]R>>"));
test_case(txt_n_sel("<<L[_]R>>"), 0, 1, txt_n_sel("<<L[_]>>"));
test_case(txt_n_sel("<<L[_]R>>"), 1, 1, txt_n_sel("<<[_]>>"));
test_case(txt_n_sel("<<L[__]R>>"), 1, 1, txt_n_sel("<<[__]>>"));
test_case(txt_n_sel("<<LL[_]RR>>"), 2, 2, txt_n_sel("<<[_]>>"));
test_case(txt_n_sel("<<L]_[R>>"), 1, 0, txt_n_sel("<<]_[R>>"));
test_case(txt_n_sel("<<L]_[R>>"), 0, 1, txt_n_sel("<<L]_[>>"));
test_case(txt_n_sel("<<L]_[R>>"), 1, 1, txt_n_sel("<<]_[>>"));
test_case(txt_n_sel("<<˻[]˼>>"), 1, 1, txt_n_sel("<<[]>>"));
test_case(txt_n_sel("<<˻[_]˼>>"), 1, 0, txt_n_sel("<<[_]˼>>"));
test_case(txt_n_sel("<<˻[_]˼>>"), 0, 1, txt_n_sel("<<˻[_]>>"));
test_case(txt_n_sel("<<˻[_]˼>>"), 1, 1, txt_n_sel("<<[_]>>"));
test_case(txt_n_sel("<<˻[__]˼>>"), 1, 1, txt_n_sel("<<[__]>>"));
test_case(txt_n_sel("<<˻˻[_]˼˼>>"), 2, 2, txt_n_sel("<<[_]>>"));
test_case(txt_n_sel("<<˻]_[˼>>"), 1, 0, txt_n_sel("<<]_[˼>>"));
test_case(txt_n_sel("<<˻]_[˼>>"), 0, 1, txt_n_sel("<<˻]_[>>"));
test_case(txt_n_sel("<<˻]_[˼>>"), 1, 1, txt_n_sel("<<]_[>>"));
test_case(txt_n_sel("<<左[]右>>"), 1, 1, txt_n_sel("<<[]>>"));
test_case(txt_n_sel("<<左[_]右>>"), 1, 0, txt_n_sel("<<[_]右>>"));
test_case(txt_n_sel("<<左[_]右>>"), 0, 1, txt_n_sel("<<左[_]>>"));
test_case(txt_n_sel("<<左[_]右>>"), 1, 1, txt_n_sel("<<[_]>>"));
test_case(txt_n_sel("<<左[__]右>>"), 1, 1, txt_n_sel("<<[__]>>"));
test_case(txt_n_sel("<<左左[_]右右>>"), 2, 2, txt_n_sel("<<[_]>>"));
test_case(txt_n_sel("<<左]_[右>>"), 1, 0, txt_n_sel("<<]_[右>>"));
test_case(txt_n_sel("<<左]_[右>>"), 0, 1, txt_n_sel("<<左]_[>>"));
test_case(txt_n_sel("<<左]_[右>>"), 1, 1, txt_n_sel("<<]_[>>"));
test_case(txt_n_sel("<<L˻左[_]R˼右>>"), 3, 3, txt_n_sel("<<[_]>>"));
}
}