use core::cell::RefCell;
use crate::compat::{Box, Rc, String};
use crate::undo::{UndoStack, ValueCommand};
use crate::widget::text_utils::{floor_char_boundary, next_char_boundary, previous_char_boundary};
pub(crate) struct LineEditor {
text: String,
cursor: usize,
anchor: Option<usize>,
undo_stack: UndoStack,
history_target: Rc<RefCell<String>>,
restoring: bool,
}
impl LineEditor {
pub(crate) fn new() -> Self {
Self {
text: String::new(),
cursor: 0,
anchor: None,
undo_stack: UndoStack::new(),
history_target: Rc::new(RefCell::new(String::new())),
restoring: false,
}
}
pub(crate) fn text(&self) -> &str {
&self.text
}
pub(crate) fn cursor(&self) -> usize {
self.cursor
}
pub(crate) fn selection(&self) -> Option<(usize, usize)> {
let anchor = self.anchor?;
let (start, end) =
if anchor <= self.cursor { (anchor, self.cursor) } else { (self.cursor, anchor) };
(start != end).then_some((start, end))
}
pub(crate) fn set_text(&mut self, text: String, record: bool) -> bool {
if self.text == text {
return false;
}
let before = core::mem::replace(&mut self.text, text);
self.cursor = self.text.len();
self.anchor = None;
self.push_history(before, record);
true
}
pub(crate) fn insert(&mut self, text: &str) -> bool {
if text.is_empty() {
return false;
}
let (start, end) = self.selection().unwrap_or((self.cursor, self.cursor));
let before = self.text.clone();
self.text.replace_range(start..end, text);
self.cursor = start + text.len();
self.anchor = None;
self.push_history(before, true);
true
}
pub(crate) fn backspace(&mut self) -> bool {
let (start, end) = self
.selection()
.unwrap_or((previous_char_boundary(&self.text, self.cursor), self.cursor));
self.delete_range(start, end)
}
pub(crate) fn delete_forward(&mut self) -> bool {
let (start, end) =
self.selection().unwrap_or((self.cursor, next_char_boundary(&self.text, self.cursor)));
self.delete_range(start, end)
}
fn delete_range(&mut self, start: usize, end: usize) -> bool {
if start == end {
return false;
}
let before = self.text.clone();
self.text.replace_range(start..end, "");
self.cursor = start;
self.anchor = None;
self.push_history(before, true);
true
}
pub(crate) fn move_caret(&mut self, delta: i32, extend: bool) -> bool {
let mut target = self.cursor;
if delta < 0 {
for _ in 0..delta.unsigned_abs() {
target = previous_char_boundary(&self.text, target);
}
} else {
for _ in 0..delta.unsigned_abs() {
target = next_char_boundary(&self.text, target);
}
}
self.set_caret(target, extend)
}
pub(crate) fn set_caret(&mut self, position: usize, extend: bool) -> bool {
let target = floor_char_boundary(&self.text, position.min(self.text.len()));
let next_anchor = if extend { Some(self.anchor.unwrap_or(self.cursor)) } else { None };
if self.cursor == target && self.anchor == next_anchor {
return false;
}
self.cursor = target;
self.anchor = next_anchor;
true
}
pub(crate) fn select_all(&mut self) -> bool {
if self.text.is_empty() {
return false;
}
let changed = self.selection() != Some((0, self.text.len()));
self.anchor = Some(0);
self.cursor = self.text.len();
changed
}
pub(crate) fn undo(&mut self) -> bool {
if self.undo_stack.undo().is_err() {
return false;
}
self.restore_history();
true
}
pub(crate) fn redo(&mut self) -> bool {
if self.undo_stack.redo().is_err() {
return false;
}
self.restore_history();
true
}
pub(crate) fn can_undo(&self) -> bool {
self.undo_stack.can_undo()
}
pub(crate) fn can_redo(&self) -> bool {
self.undo_stack.can_redo()
}
fn restore_history(&mut self) {
self.restoring = true;
self.text = self.history_target.borrow().clone();
self.restoring = false;
self.cursor = self.text.len();
self.anchor = None;
}
fn push_history(&mut self, before: String, record: bool) {
if !record || self.restoring {
return;
}
let after = self.text.clone();
*self.history_target.borrow_mut() = after.clone();
self.undo_stack.push(Box::new(ValueCommand::new(
self.history_target.clone(),
before,
after,
"Edit text",
"combo_box_text",
)));
}
}
impl Default for LineEditor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::ToString;
#[test]
fn typing_records_one_undo_step_per_edit() {
let mut editor = LineEditor::new();
assert!(editor.insert("ab"));
assert!(editor.insert("c"));
assert_eq!(editor.text(), "abc");
assert_eq!(editor.cursor(), 3);
assert!(editor.undo());
assert_eq!(editor.text(), "ab");
assert!(editor.redo());
assert_eq!(editor.text(), "abc");
}
#[test]
fn a_selection_is_replaced_by_the_next_insert() {
let mut editor = LineEditor::new();
editor.set_text("hello".to_string(), false);
assert!(editor.set_caret(0, false));
assert!(editor.set_caret(5, true));
assert_eq!(editor.selection(), Some((0, 5)));
assert!(editor.insert("bye"));
assert_eq!(editor.text(), "bye");
assert_eq!(editor.selection(), None);
}
#[test]
fn a_programmatic_write_is_not_undoable() {
let mut editor = LineEditor::new();
editor.set_text("typed".to_string(), true);
editor.set_text("assigned".to_string(), false);
assert_eq!(editor.text(), "assigned");
assert!(editor.undo());
assert_eq!(editor.text(), "");
assert!(!editor.can_undo());
}
#[test]
fn backspace_and_delete_stop_at_the_ends() {
let mut editor = LineEditor::new();
assert!(!editor.backspace());
assert!(!editor.delete_forward());
editor.set_text("ab".to_string(), false);
assert!(editor.backspace());
assert_eq!(editor.text(), "a");
assert!(!editor.delete_forward());
assert!(editor.set_caret(0, false));
assert!(editor.delete_forward());
assert_eq!(editor.text(), "");
}
}