use std::cell::RefCell;
use std::rc::Rc;
use crate::undo::Undoer;
use crate::widgets::text_field_core::{next_char_boundary, prev_char_boundary};
use super::super::commands::{apply_command, range_common_style, style_at, CommonStyle, RichCommand};
use super::super::model::{
extract_range, insert_text, merge_block_with_prev, remove_range, splice_fragment, split_block,
Block, DocPos, DocRange, InlineStyle, ListKind, RichDoc,
};
#[derive(Clone, PartialEq)]
pub(crate) struct EditSnapshot {
doc: RichDoc,
caret: DocPos,
anchor: DocPos,
}
pub struct RichEditCore {
doc: RichDoc,
caret: DocPos,
anchor: DocPos,
pending_style: Option<InlineStyle>,
default_font_size: f64,
undoer: Undoer<EditSnapshot>,
undo_suspended: bool,
preview_snapshot: Option<EditSnapshot>,
preview_dirty: bool,
doc_rev: u64,
rev: u64,
}
impl RichEditCore {
pub fn new(doc: RichDoc, default_font_size: f64) -> Self {
Self {
doc,
caret: DocPos::default(),
anchor: DocPos::default(),
pending_style: None,
default_font_size,
undoer: Undoer::default(),
undo_suspended: false,
preview_snapshot: None,
preview_dirty: false,
doc_rev: 0,
rev: 0,
}
}
pub fn load(&mut self, doc: RichDoc) {
self.doc = doc;
self.caret = DocPos::default();
self.anchor = DocPos::default();
self.pending_style = None;
self.undoer = Undoer::default();
self.preview_snapshot = None;
self.preview_dirty = false;
self.undo_suspended = false;
self.bump_doc();
}
pub fn doc(&self) -> &RichDoc {
&self.doc
}
pub fn plain_text(&self) -> String {
self.doc.plain_text()
}
pub fn caret(&self) -> DocPos {
self.caret
}
pub fn anchor(&self) -> DocPos {
self.anchor
}
pub fn default_font_size(&self) -> f64 {
self.default_font_size
}
pub fn set_default_font_size(&mut self, size: f64) {
self.default_font_size = size;
self.bump_doc();
}
pub fn doc_rev(&self) -> u64 {
self.doc_rev
}
pub fn rev(&self) -> u64 {
self.rev
}
pub fn pending_style(&self) -> Option<&InlineStyle> {
self.pending_style.as_ref()
}
pub fn selection(&self) -> DocRange {
DocRange::new(self.anchor, self.caret)
}
fn bump_rev(&mut self) {
self.rev = self.rev.wrapping_add(1);
}
fn bump_doc(&mut self) {
self.doc_rev = self.doc_rev.wrapping_add(1);
self.bump_rev();
}
pub fn clamp_pos(&self, pos: DocPos) -> DocPos {
let block = pos.block.min(self.doc.blocks.len().saturating_sub(1));
let Some(b) = self.doc.blocks.get(block) else {
return DocPos::new(0, 0);
};
let text = b.text();
let mut byte = pos.byte.min(text.len());
while byte > 0 && !text.is_char_boundary(byte) {
byte -= 1;
}
DocPos::new(block, byte)
}
pub fn set_caret(&mut self, pos: DocPos, extend: bool) {
let pos = self.clamp_pos(pos);
self.caret = pos;
if !extend {
self.anchor = pos;
}
self.pending_style = None;
self.bump_rev();
}
pub fn select_all(&mut self) {
self.anchor = DocPos::new(0, 0);
self.caret = self.doc.end_pos();
self.pending_style = None;
self.bump_rev();
}
pub fn set_selection(&mut self, anchor: DocPos, caret: DocPos) {
self.anchor = self.clamp_pos(anchor);
self.caret = self.clamp_pos(caret);
self.pending_style = None;
self.bump_rev();
}
pub fn style_for_insert(&self) -> InlineStyle {
self.pending_style
.clone()
.unwrap_or_else(|| style_at(&self.doc, self.caret))
}
pub fn common_style_of_selection(&self) -> CommonStyle {
let sel = self.selection();
if sel.is_empty() {
if let Some(p) = &self.pending_style {
let mut cs = CommonStyle::of_style(p);
cs.merge_blocks(&self.doc, sel);
return cs;
}
}
range_common_style(&self.doc, sel)
}
pub fn selected_plain_text(&self) -> String {
let sel = self.selection();
if sel.is_empty() {
return String::new();
}
let (a, b) = sel.ordered();
let mut out = String::new();
for bi in a.block..=b.block {
let Some(block) = self.doc.blocks.get(bi) else {
continue;
};
let text = block.text();
let lo = if bi == a.block { a.byte } else { 0 };
let hi = if bi == b.block { b.byte.min(text.len()) } else { text.len() };
if bi > a.block {
out.push('\n');
}
if lo <= hi {
out.push_str(&text[lo..hi]);
}
}
out
}
pub fn selected_fragment(&self) -> Vec<Block> {
extract_range(&self.doc, self.selection())
}
fn take_selection(&mut self) -> DocPos {
let sel = self.selection();
if sel.is_empty() {
self.caret
} else {
let pos = remove_range(&mut self.doc, sel);
self.caret = pos;
self.anchor = pos;
pos
}
}
pub fn insert(&mut self, text: &str) {
if text.is_empty() {
return;
}
let style = self.style_for_insert();
self.take_selection();
let mut first = true;
for segment in text.split('\n') {
if !first {
self.caret = split_block(&mut self.doc, self.caret);
}
if !segment.is_empty() {
insert_text(&mut self.doc, self.caret, segment, style.clone());
self.caret = DocPos::new(self.caret.block, self.caret.byte + segment.len());
}
first = false;
}
self.anchor = self.caret;
self.pending_style = None;
self.bump_doc();
}
pub fn insert_fragment(&mut self, fragment: &[Block]) {
if fragment.is_empty() {
return;
}
self.take_selection();
let end = splice_fragment(&mut self.doc, self.caret, fragment);
self.caret = end;
self.anchor = end;
self.pending_style = None;
self.bump_doc();
}
pub fn split(&mut self) {
self.take_selection();
if let Some(block) = self.doc.blocks.get_mut(self.caret.block) {
if block.list != ListKind::None && block.text_len() == 0 {
if block.indent > 0 {
block.indent -= 1;
} else {
block.list = ListKind::None;
}
self.anchor = self.caret;
self.pending_style = None;
self.bump_doc();
return;
}
}
self.caret = split_block(&mut self.doc, self.caret);
self.anchor = self.caret;
self.pending_style = None;
self.bump_doc();
}
pub fn backspace(&mut self) {
if !self.selection().is_empty() {
self.take_selection();
self.pending_style = None;
self.bump_doc();
return;
}
if self.caret.byte > 0 {
let text = self.doc.blocks[self.caret.block].text();
let prev = prev_char_boundary(&text, self.caret.byte);
let pos = remove_range(
&mut self.doc,
DocRange::new(DocPos::new(self.caret.block, prev), self.caret),
);
self.caret = pos;
self.anchor = pos;
} else if self.caret.block > 0 {
let pos = merge_block_with_prev(&mut self.doc, self.caret.block);
self.caret = pos;
self.anchor = pos;
} else {
return;
}
self.pending_style = None;
self.bump_doc();
}
pub fn delete_forward(&mut self) {
if !self.selection().is_empty() {
self.take_selection();
self.pending_style = None;
self.bump_doc();
return;
}
let block_len = self.doc.blocks[self.caret.block].text_len();
if self.caret.byte < block_len {
let text = self.doc.blocks[self.caret.block].text();
let next = next_char_boundary(&text, self.caret.byte);
remove_range(
&mut self.doc,
DocRange::new(self.caret, DocPos::new(self.caret.block, next)),
);
} else if self.caret.block + 1 < self.doc.blocks.len() {
merge_block_with_prev(&mut self.doc, self.caret.block + 1);
} else {
return;
}
self.pending_style = None;
self.bump_doc();
}
pub fn exec(&mut self, cmd: &RichCommand) {
let sel = self.selection();
if sel.is_empty() && is_inline(cmd) {
let mut base = self.style_for_insert();
apply_inline_to_style(&mut base, cmd);
self.pending_style = Some(base);
self.bump_rev();
} else {
apply_command(&mut self.doc, sel, cmd);
self.pending_style = None;
if self.preview_snapshot.is_some() {
self.preview_dirty = true;
}
self.bump_doc();
}
}
fn snapshot(&self) -> EditSnapshot {
EditSnapshot {
doc: self.doc.clone(),
caret: self.caret,
anchor: self.anchor,
}
}
fn apply_snapshot(&mut self, snap: EditSnapshot) {
self.doc = snap.doc;
self.caret = snap.caret;
self.anchor = snap.anchor;
self.pending_style = None;
self.bump_doc();
}
pub fn feed_undo(&mut self, time: f64) -> bool {
if self.undo_suspended {
return false;
}
let snap = self.snapshot();
self.undoer.feed_state(time, &snap);
self.undoer.is_in_flux()
}
pub fn begin_preview(&mut self) {
if self.preview_snapshot.is_none() {
self.preview_snapshot = Some(self.snapshot());
self.preview_dirty = false;
}
self.undo_suspended = true;
}
pub fn commit_preview(&mut self) {
self.preview_snapshot = None;
self.preview_dirty = false;
self.undo_suspended = false;
}
pub fn cancel_preview(&mut self) {
if let Some(snap) = self.preview_snapshot.take() {
self.apply_snapshot(snap);
}
self.preview_dirty = false;
self.undo_suspended = false;
}
pub fn is_previewing(&self) -> bool {
self.preview_snapshot.is_some()
}
pub fn is_preview_dirty(&self) -> bool {
self.preview_dirty
}
pub fn can_undo(&self) -> bool {
self.undoer.has_undo(&self.snapshot())
}
pub fn can_redo(&self) -> bool {
self.undoer.has_redo(&self.snapshot())
}
pub fn undo(&mut self) -> bool {
let cur = self.snapshot();
if let Some(prev) = self.undoer.undo(&cur).cloned() {
self.apply_snapshot(prev);
true
} else {
false
}
}
pub fn redo(&mut self) -> bool {
let cur = self.snapshot();
if let Some(next) = self.undoer.redo(&cur).cloned() {
self.apply_snapshot(next);
true
} else {
false
}
}
}
fn is_inline(cmd: &RichCommand) -> bool {
matches!(
cmd,
RichCommand::ToggleBold
| RichCommand::ToggleItalic
| RichCommand::ToggleUnderline
| RichCommand::ToggleStrikethrough
| RichCommand::SetFontFamily(_)
| RichCommand::SetFontSize(_)
| RichCommand::SetTextColor(_)
| RichCommand::SetHighlight(_)
)
}
fn apply_inline_to_style(style: &mut InlineStyle, cmd: &RichCommand) {
match cmd {
RichCommand::ToggleBold => style.bold = !style.bold,
RichCommand::ToggleItalic => style.italic = !style.italic,
RichCommand::ToggleUnderline => style.underline = !style.underline,
RichCommand::ToggleStrikethrough => style.strikethrough = !style.strikethrough,
RichCommand::SetFontFamily(f) => style.font_family = Some(f.clone()),
RichCommand::SetFontSize(s) => style.font_size = Some(*s),
RichCommand::SetTextColor(c) => style.text_color = Some(*c),
RichCommand::SetHighlight(c) => style.highlight = *c,
_ => {}
}
}
#[derive(Clone)]
pub struct RichEditHandle {
core: Rc<RefCell<RichEditCore>>,
}
impl RichEditHandle {
pub(crate) fn new(core: Rc<RefCell<RichEditCore>>) -> Self {
Self { core }
}
pub fn exec(&self, cmd: &RichCommand) {
self.core.borrow_mut().exec(cmd);
crate::animation::request_draw();
}
pub fn common_style_of_selection(&self) -> CommonStyle {
self.core.borrow().common_style_of_selection()
}
pub fn select_all(&self) {
self.core.borrow_mut().select_all();
crate::animation::request_draw();
}
pub fn set_caret(&self, pos: DocPos) {
self.core.borrow_mut().set_caret(pos, false);
crate::animation::request_draw();
}
pub fn set_selection(&self, range: DocRange) {
self.core
.borrow_mut()
.set_selection(range.start, range.end);
crate::animation::request_draw();
}
pub fn selection(&self) -> DocRange {
self.core.borrow().selection()
}
pub fn plain_text(&self) -> String {
self.core.borrow().plain_text()
}
pub fn load(&self, doc: RichDoc) {
self.core.borrow_mut().load(doc);
crate::animation::request_draw();
}
pub fn undo(&self) {
if self.core.borrow_mut().undo() {
crate::animation::request_draw();
}
}
pub fn redo(&self) {
if self.core.borrow_mut().redo() {
crate::animation::request_draw();
}
}
pub fn can_undo(&self) -> bool {
self.core.borrow().can_undo()
}
pub fn can_redo(&self) -> bool {
self.core.borrow().can_redo()
}
pub fn begin_preview(&self) {
self.core.borrow_mut().begin_preview();
}
pub fn commit_preview(&self) {
self.core.borrow_mut().commit_preview();
crate::animation::request_draw();
}
pub fn cancel_preview(&self) {
self.core.borrow_mut().cancel_preview();
crate::animation::request_draw();
}
pub fn is_previewing(&self) -> bool {
self.core.borrow().is_previewing()
}
pub fn is_preview_dirty(&self) -> bool {
self.core.borrow().is_preview_dirty()
}
}