use std::ops::Range;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct TextEditBuffer {
text: Vec<u8>,
selection: Range<usize>,
}
impl TextEditBuffer {
pub(crate) fn new(text: Vec<u8>, selection_start: usize, selection_end: usize) -> Self {
let start = selection_start.min(text.len());
let end = selection_end.min(text.len());
Self {
text,
selection: start.min(end)..start.max(end),
}
}
pub(crate) fn text(&self) -> &[u8] {
&self.text
}
pub(crate) fn selection(&self) -> Range<usize> {
self.selection.clone()
}
pub(crate) fn selected_text(&self) -> &[u8] {
&self.text[self.selection.clone()]
}
pub(crate) fn replace_selection(&mut self, inserted: &[u8]) {
let insertion_start = self.selection.start;
self.text
.splice(self.selection.clone(), inserted.iter().copied());
let insertion_end = insertion_start
.saturating_add(inserted.len())
.min(self.text.len());
self.selection = insertion_end..insertion_end;
}
pub(crate) fn delete_selection(&mut self) -> bool {
if self.selection.is_empty() {
return false;
}
self.replace_selection(&[]);
true
}
pub(crate) fn apply_key(&mut self, key: u8) {
match key {
0x08 | 0x7f => {
if self.selection.is_empty() && self.selection.start > 0 {
self.selection.start -= 1;
}
self.replace_selection(&[]);
}
0x1c => {
let caret = if self.selection.is_empty() {
self.selection.start.saturating_sub(1)
} else {
self.selection.start
};
self.selection = caret..caret;
}
0x1d => {
let caret = if self.selection.is_empty() {
self.selection.end.saturating_add(1).min(self.text.len())
} else {
self.selection.end
};
self.selection = caret..caret;
}
_ => self.replace_selection(&[key]),
}
}
}
pub(crate) fn aligned_line_left(
left: i16,
right: i16,
width: i16,
alignment: i16,
left_inset: i16,
) -> i16 {
match alignment {
1 => left.saturating_add(right.saturating_sub(left).saturating_sub(width) / 2),
-1 => right.saturating_sub(width),
_ => left.saturating_add(left_inset),
}
}
#[cfg(test)]
mod tests {
use super::{aligned_line_left, TextEditBuffer};
#[test]
fn selection_is_clamped_and_normalized_once() {
let buffer = TextEditBuffer::new(b"toolbox".to_vec(), 20, 3);
assert_eq!(buffer.selection(), 3..7);
assert_eq!(buffer.selected_text(), b"lbox");
}
#[test]
fn replace_and_delete_share_selection_semantics() {
let mut buffer = TextEditBuffer::new(b"one three".to_vec(), 4, 4);
buffer.replace_selection(b"two ");
assert_eq!(buffer.text(), b"one two three");
assert_eq!(buffer.selection(), 8..8);
let mut buffer = TextEditBuffer::new(buffer.text().to_vec(), 8, 4);
assert!(buffer.delete_selection());
assert_eq!(buffer.text(), b"one three");
assert_eq!(buffer.selection(), 4..4);
assert!(!buffer.delete_selection());
}
#[test]
fn key_editing_handles_backspace_and_caret_arrows() {
let mut buffer = TextEditBuffer::new(b"abc".to_vec(), 2, 2);
buffer.apply_key(0x08);
assert_eq!(buffer.text(), b"ac");
assert_eq!(buffer.selection(), 1..1);
buffer.apply_key(0x1c);
assert_eq!(buffer.selection(), 0..0);
buffer.apply_key(0x1d);
assert_eq!(buffer.selection(), 1..1);
assert_eq!(buffer.text(), b"ac");
}
#[test]
fn alignment_is_shared_for_every_guest_adapter() {
assert_eq!(aligned_line_left(20, 200, 80, 0, 1), 21);
assert_eq!(aligned_line_left(20, 200, 80, -2, 1), 21);
assert_eq!(aligned_line_left(20, 200, 80, 1, 1), 70);
assert_eq!(aligned_line_left(20, 200, 80, -1, 1), 120);
}
}