use crate::clipboard::Clipboard;
use crate::grapheme::{Glyph, GlyphIter, Grapheme};
use crate::range_map::{expand_range_by, ranges_intersect, shrink_range_by, RangeMap};
use crate::text_store::TextStore;
use crate::undo_buffer::{StyleChange, TextPositionChange, UndoBuffer, UndoEntry, UndoOp};
use crate::{upos_type, Cursor, TextError, TextPosition, TextRange};
use dyn_clone::clone_box;
use std::borrow::Cow;
use std::cmp::min;
use std::ops::Range;
#[derive(Debug)]
pub struct TextCore<Store> {
text: Store,
cursor: TextPosition,
anchor: TextPosition,
styles: Option<Box<RangeMap>>,
undo: Option<Box<dyn UndoBuffer>>,
clip: Option<Box<dyn Clipboard>>,
newline: String,
tabs: u16,
expand_tabs: bool,
glyph_ctrl: bool,
glyph_line_break: bool,
}
impl<Store: Clone> Clone for TextCore<Store> {
fn clone(&self) -> Self {
Self {
text: self.text.clone(),
cursor: self.cursor,
anchor: self.anchor,
styles: self.styles.clone(),
undo: self.undo.as_ref().map(|v| clone_box(v.as_ref())),
clip: self.clip.as_ref().map(|v| clone_box(v.as_ref())),
newline: self.newline.clone(),
tabs: self.tabs,
expand_tabs: self.expand_tabs,
glyph_ctrl: self.glyph_ctrl,
glyph_line_break: self.glyph_line_break,
}
}
}
impl<Store: TextStore + Default> TextCore<Store> {
pub fn new(undo: Option<Box<dyn UndoBuffer>>, clip: Option<Box<dyn Clipboard>>) -> Self {
Self {
text: Store::default(),
cursor: Default::default(),
anchor: Default::default(),
styles: Default::default(),
undo,
clip,
newline: "\n".to_string(),
tabs: 8,
expand_tabs: true,
glyph_ctrl: false,
glyph_line_break: true,
}
}
#[inline]
pub fn set_newline(&mut self, br: String) {
self.newline = br;
}
#[inline]
pub fn newline(&self) -> &str {
&self.newline
}
#[inline]
pub fn set_tab_width(&mut self, tabs: u16) {
self.tabs = tabs;
}
#[inline]
pub fn tab_width(&self) -> u16 {
self.tabs
}
#[inline]
pub fn set_expand_tabs(&mut self, expand: bool) {
self.expand_tabs = expand;
}
#[inline]
pub fn expand_tabs(&self) -> bool {
self.expand_tabs
}
#[inline]
pub fn set_glyph_ctrl(&mut self, show_ctrl: bool) {
self.glyph_ctrl = show_ctrl;
}
pub fn glyph_ctrl(&self) -> bool {
self.glyph_ctrl
}
#[inline]
pub fn set_glyph_line_break(&mut self, line_break: bool) {
self.glyph_line_break = line_break;
}
pub fn glyph_line_break(&self) -> bool {
self.glyph_line_break
}
}
impl<Store: TextStore + Default> TextCore<Store> {
pub fn set_clipboard(&mut self, clip: Option<Box<dyn Clipboard + 'static>>) {
self.clip = clip;
}
pub fn clipboard(&self) -> Option<&dyn Clipboard> {
match &self.clip {
None => None,
Some(v) => Some(v.as_ref()),
}
}
}
impl<Store: TextStore + Default> TextCore<Store> {
#[inline]
pub fn set_undo_buffer(&mut self, undo: Option<Box<dyn UndoBuffer>>) {
self.undo = undo;
}
#[inline]
pub fn set_undo_count(&mut self, n: u32) {
if let Some(undo) = self.undo.as_mut() {
undo.set_undo_count(n);
};
}
#[inline]
pub fn begin_undo_seq(&mut self) {
if let Some(undo) = self.undo.as_mut() {
undo.begin_seq();
};
}
#[inline]
pub fn end_undo_seq(&mut self) {
if let Some(undo) = self.undo.as_mut() {
undo.end_seq();
};
}
#[inline]
pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
match &self.undo {
None => None,
Some(v) => Some(v.as_ref()),
}
}
#[inline]
pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
match &mut self.undo {
None => None,
Some(v) => Some(v.as_mut()),
}
}
pub fn undo(&mut self) -> bool {
let Some(undo) = self.undo.as_mut() else {
return false;
};
undo.append(UndoOp::Undo);
self._undo()
}
fn _undo(&mut self) -> bool {
let Some(undo) = self.undo.as_mut() else {
return false;
};
let undo_op = undo.undo();
let changed = !undo_op.is_empty();
for op in undo_op {
match op {
UndoOp::InsertChar {
bytes,
cursor,
anchor,
..
}
| UndoOp::InsertStr {
bytes,
cursor,
anchor,
..
} => {
self.text.remove_b(bytes.clone()).expect("valid_bytes");
if let Some(sty) = &mut self.styles {
sty.remap(|r, _| Some(shrink_range_by(bytes.clone(), r)));
}
self.anchor = anchor.before;
self.cursor = cursor.before;
}
UndoOp::RemoveStr {
bytes,
cursor,
anchor,
txt,
styles,
}
| UndoOp::RemoveChar {
bytes,
cursor,
anchor,
txt,
styles,
} => {
self.text.insert_b(bytes.start, &txt).expect("valid_bytes");
if let Some(sty) = &mut self.styles {
for s in styles {
sty.remove(s.after.clone(), s.style);
}
for s in styles {
sty.add(s.before.clone(), s.style);
}
sty.remap(|r, _| {
if ranges_intersect(bytes.clone(), r.clone()) {
Some(r)
} else {
Some(expand_range_by(bytes.clone(), r))
}
});
}
self.anchor = anchor.before;
self.cursor = cursor.before;
}
UndoOp::Cursor { cursor, anchor } => {
self.anchor = anchor.before;
self.cursor = cursor.before;
}
UndoOp::SetStyles { styles_before, .. } => {
if let Some(sty) = &mut self.styles {
sty.set(styles_before.iter().cloned());
}
}
UndoOp::AddStyle { range, style } => {
if let Some(sty) = &mut self.styles {
sty.remove(range.clone(), *style);
}
}
UndoOp::RemoveStyle { range, style } => {
if let Some(sty) = &mut self.styles {
sty.add(range.clone(), *style);
}
}
UndoOp::SetText { .. } | UndoOp::Undo | UndoOp::Redo => {
unreachable!()
}
}
}
changed
}
pub fn redo(&mut self) -> bool {
let Some(undo) = self.undo.as_mut() else {
return false;
};
undo.append(UndoOp::Redo);
self._redo()
}
fn _redo(&mut self) -> bool {
let Some(undo) = self.undo.as_mut() else {
return false;
};
let redo_op = undo.redo();
let changed = !redo_op.is_empty();
for op in redo_op {
match op {
UndoOp::InsertChar {
bytes,
cursor,
anchor,
txt,
}
| UndoOp::InsertStr {
bytes,
cursor,
anchor,
txt,
} => {
self.text.insert_b(bytes.start, &txt).expect("valid_bytes");
if let Some(sty) = &mut self.styles {
sty.remap(|r, _| Some(expand_range_by(bytes.clone(), r)));
}
self.anchor = anchor.after;
self.cursor = cursor.after;
}
UndoOp::RemoveChar {
bytes,
cursor,
anchor,
styles,
..
}
| UndoOp::RemoveStr {
bytes,
cursor,
anchor,
styles,
..
} => {
self.text.remove_b(bytes.clone()).expect("valid_bytes");
if let Some(sty) = &mut self.styles {
sty.remap(|r, _| {
if ranges_intersect(bytes.clone(), r.clone()) {
Some(r)
} else {
Some(shrink_range_by(bytes.clone(), r))
}
});
for s in styles {
sty.remove(s.before.clone(), s.style);
}
for s in styles {
sty.add(s.after.clone(), s.style);
}
}
self.anchor = anchor.after;
self.cursor = cursor.after;
}
UndoOp::Cursor { cursor, anchor } => {
self.anchor = anchor.after;
self.cursor = cursor.after;
}
UndoOp::SetStyles { styles_after, .. } => {
if let Some(sty) = &mut self.styles {
sty.set(styles_after.iter().cloned());
}
}
UndoOp::AddStyle { range, style } => {
if let Some(sty) = &mut self.styles {
sty.add(range.clone(), *style);
}
}
UndoOp::RemoveStyle { range, style } => {
if let Some(sty) = &mut self.styles {
sty.remove(range.clone(), *style);
}
}
UndoOp::SetText { .. } | UndoOp::Undo | UndoOp::Redo => {
unreachable!()
}
}
}
changed
}
pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
if let Some(undo) = &mut self.undo {
undo.recent_replay_log()
} else {
Vec::default()
}
}
pub fn replay_log(&mut self, replay: &[UndoEntry]) {
for replay_entry in replay {
match &replay_entry.operation {
UndoOp::SetText { txt } => {
self.text.set_string(txt);
if let Some(sty) = &mut self.styles {
sty.clear();
}
if let Some(undo) = self.undo.as_mut() {
undo.clear();
};
}
UndoOp::InsertChar { bytes, txt, .. } | UndoOp::InsertStr { bytes, txt, .. } => {
self.text.insert_b(bytes.start, txt).expect("valid_range");
if let Some(sty) = &mut self.styles {
sty.remap(|r, _| Some(expand_range_by(bytes.clone(), r)));
}
}
UndoOp::RemoveChar { bytes, styles, .. }
| UndoOp::RemoveStr { bytes, styles, .. } => {
self.text.remove_b(bytes.clone()).expect("valid_range");
if let Some(sty) = &mut self.styles {
sty.remap(|r, _| {
if ranges_intersect(bytes.clone(), r.clone()) {
Some(r)
} else {
Some(shrink_range_by(bytes.clone(), r))
}
});
for s in styles {
sty.remove(s.before.clone(), s.style);
}
for s in styles {
sty.add(s.after.clone(), s.style);
}
}
}
UndoOp::Cursor { .. } => {
}
UndoOp::SetStyles { styles_after, .. } => {
self.init_styles();
if let Some(sty) = &mut self.styles {
sty.set(styles_after.iter().cloned());
}
}
UndoOp::AddStyle { range, style } => {
self.init_styles();
if let Some(sty) = &mut self.styles {
sty.add(range.clone(), *style);
}
}
UndoOp::RemoveStyle { range, style } => {
self.init_styles();
if let Some(sty) = &mut self.styles {
sty.remove(range.clone(), *style);
}
}
UndoOp::Undo => {
self._undo();
}
UndoOp::Redo => {
self._redo();
}
}
if let Some(undo) = self.undo.as_mut() {
undo.append_from_replay(replay_entry.clone());
};
}
}
}
impl<Store: TextStore + Default> TextCore<Store> {
fn init_styles(&mut self) {
if self.styles.is_none() {
self.styles = Some(Box::new(RangeMap::default()));
}
}
#[inline]
pub fn set_styles(&mut self, new_styles: Vec<(Range<usize>, usize)>) {
self.init_styles();
let Some(sty) = &mut self.styles else {
return;
};
if let Some(undo) = &mut self.undo {
if undo.undo_styles_enabled() || undo.has_replay_log() {
undo.append(UndoOp::SetStyles {
styles_before: sty.values().collect::<Vec<_>>(),
styles_after: new_styles.clone(),
});
}
}
sty.set(new_styles.iter().cloned());
}
#[inline]
pub fn add_style(&mut self, range: Range<usize>, style: usize) {
self.init_styles();
if let Some(sty) = &mut self.styles {
sty.add(range.clone(), style);
}
if let Some(undo) = &mut self.undo {
if undo.undo_styles_enabled() || undo.has_replay_log() {
undo.append(UndoOp::AddStyle { range, style });
}
}
}
#[inline]
pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
if let Some(sty) = &mut self.styles {
sty.remove(range.clone(), style);
}
if let Some(undo) = &mut self.undo {
if undo.undo_styles_enabled() || undo.has_replay_log() {
undo.append(UndoOp::RemoveStyle { range, style });
}
}
}
#[inline]
pub(crate) fn styles_at_page(&self, range: Range<usize>, pos: usize, buf: &mut Vec<usize>) {
if let Some(sty) = &self.styles {
sty.values_at_page(range, pos, buf);
}
}
pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
if let Some(sty) = &self.styles {
sty.values_in(range, buf);
}
}
#[inline]
pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
if let Some(sty) = &self.styles {
sty.values_at(byte_pos, buf);
}
}
#[inline]
pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
if let Some(sty) = &self.styles {
sty.value_match(byte_pos, style)
} else {
None
}
}
#[inline]
pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
if let Some(sty) = &self.styles {
Some(sty.values())
} else {
None
}
}
}
impl<Store: TextStore + Default> TextCore<Store> {
pub fn set_cursor(&mut self, mut cursor: TextPosition, extend_selection: bool) -> bool {
let old_cursor = self.cursor;
let old_anchor = self.anchor;
cursor.y = min(cursor.y, self.len_lines().saturating_sub(1));
cursor.x = min(cursor.x, self.line_width(cursor.y).expect("valid-line"));
self.cursor = cursor;
if !extend_selection {
self.anchor = cursor;
}
if let Some(undo) = self.undo.as_mut() {
undo.append(UndoOp::Cursor {
cursor: TextPositionChange {
before: old_cursor,
after: self.cursor,
},
anchor: TextPositionChange {
before: old_anchor,
after: self.anchor,
},
});
}
old_cursor != self.cursor || old_anchor != self.anchor
}
#[inline]
pub fn cursor(&self) -> TextPosition {
self.cursor
}
#[inline]
pub fn anchor(&self) -> TextPosition {
self.anchor
}
#[inline]
pub fn has_selection(&self) -> bool {
self.anchor != self.cursor
}
#[inline]
pub fn set_selection(&mut self, anchor: TextPosition, cursor: TextPosition) -> bool {
let old_selection = self.selection();
self.set_cursor(anchor, false);
self.set_cursor(cursor, true);
old_selection != self.selection()
}
#[inline]
pub fn select_all(&mut self) -> bool {
let old_selection = self.selection();
self.set_cursor(TextPosition::new(0, 0), false);
let last = self.len_lines().saturating_sub(1);
let last_width = self.line_width(last).expect("valid_line");
self.set_cursor(TextPosition::new(last_width, last), true);
old_selection != self.selection()
}
#[inline]
pub fn selection(&self) -> TextRange {
#[allow(clippy::comparison_chain)]
if self.cursor.y < self.anchor.y {
TextRange {
start: self.cursor,
end: self.anchor,
}
} else if self.cursor.y > self.anchor.y {
TextRange {
start: self.anchor,
end: self.cursor,
}
} else {
if self.cursor.x < self.anchor.x {
TextRange {
start: self.cursor,
end: self.anchor,
}
} else {
TextRange {
start: self.anchor,
end: self.cursor,
}
}
}
}
}
impl<Store: TextStore + Default> TextCore<Store> {
#[inline]
pub fn is_empty(&self) -> bool {
self.text.len_lines() == 1 && self.text.line_width(0).expect("line") == 0
}
#[inline]
pub fn byte_at(&self, pos: TextPosition) -> Result<Range<usize>, TextError> {
self.text.byte_range_at(pos)
}
#[inline]
pub fn bytes_at_range(&self, range: TextRange) -> Result<Range<usize>, TextError> {
self.text.byte_range(range)
}
#[inline]
pub fn byte_pos(&self, byte: usize) -> Result<TextPosition, TextError> {
self.text.byte_to_pos(byte)
}
#[inline]
pub fn byte_range(&self, bytes: Range<usize>) -> Result<TextRange, TextError> {
self.text.bytes_to_range(bytes)
}
#[inline]
pub fn str_slice(&self, range: TextRange) -> Result<Cow<'_, str>, TextError> {
self.text.str_slice(range)
}
#[inline]
pub fn str_slice_byte(&self, range: Range<usize>) -> Result<Cow<'_, str>, TextError> {
self.text.str_slice_byte(range)
}
#[inline]
pub fn glyphs(
&self,
rows: Range<upos_type>,
screen_offset: u16,
screen_width: u16,
) -> Result<impl Iterator<Item = Glyph<'_>>, TextError> {
let iter = self.graphemes(
TextRange::new((0, rows.start), (0, rows.end)),
TextPosition::new(0, rows.start),
)?;
let mut it = GlyphIter::new(TextPosition::new(0, rows.start), iter);
it.set_screen_offset(screen_offset);
it.set_screen_width(screen_width);
it.set_tabs(self.tabs);
it.set_show_ctrl(self.glyph_ctrl);
it.set_line_break(self.glyph_line_break);
Ok(it)
}
#[inline]
pub fn grapheme_at(&self, pos: TextPosition) -> Result<Option<Grapheme<'_>>, TextError> {
let mut it = self
.text
.graphemes(TextRange::new(pos, (pos.x + 1, pos.y)), pos)?;
Ok(it.next())
}
#[inline]
pub fn text_graphemes(
&self,
pos: TextPosition,
) -> Result<impl Iterator<Item = Grapheme<'_>> + Cursor, TextError> {
let rows = self.text.len_lines();
let cols = self.text.line_width(rows).expect("valid_row");
self.text
.graphemes(TextRange::new((0, 0), (cols, rows)), pos)
}
#[inline]
pub fn graphemes(
&self,
range: TextRange,
pos: TextPosition,
) -> Result<impl Iterator<Item = Grapheme<'_>> + Cursor, TextError> {
self.text.graphemes(range, pos)
}
#[inline]
pub fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
self.text.line_at(row)
}
#[inline]
pub fn lines_at(
&self,
row: upos_type,
) -> Result<impl Iterator<Item = Cow<'_, str>>, TextError> {
self.text.lines_at(row)
}
#[inline]
pub fn line_graphemes(
&self,
row: upos_type,
) -> Result<impl Iterator<Item = Grapheme<'_>> + Cursor, TextError> {
self.text.line_graphemes(row)
}
#[inline]
pub fn line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
self.text.line_width(row)
}
#[inline]
pub fn len_lines(&self) -> upos_type {
self.text.len_lines()
}
}
impl<Store: TextStore + Default> TextCore<Store> {
pub fn clear(&mut self) {
self.text.set_string("");
self.cursor = TextPosition::default();
self.anchor = TextPosition::default();
if let Some(sty) = &mut self.styles {
sty.clear();
}
if let Some(undo) = &mut self.undo {
undo.clear();
if undo.has_replay_log() {
undo.append(UndoOp::SetText {
txt: self.text.string(),
});
}
}
}
pub fn text(&self) -> &Store {
&self.text
}
pub fn set_text(&mut self, t: Store) -> bool {
self.text = t;
if let Some(sty) = &mut self.styles {
sty.clear();
}
self.cursor.y = min(self.cursor.y, self.len_lines().saturating_sub(1));
self.cursor.x = min(
self.cursor.x,
self.line_width(self.cursor.y).expect("valid_line"),
);
self.anchor.y = min(self.anchor.y, self.len_lines().saturating_sub(1));
self.anchor.x = min(
self.anchor.x,
self.line_width(self.anchor.y).expect("valid_line"),
);
if let Some(undo) = &mut self.undo {
undo.clear();
if undo.has_replay_log() {
undo.append(UndoOp::SetText {
txt: self.text.string(),
});
}
}
true
}
pub fn insert_quotes(&mut self, mut sel: TextRange, c: char) -> Result<bool, TextError> {
self.begin_undo_seq();
if sel.end.x > 0 {
let first = TextRange::new(sel.start, (sel.start.x + 1, sel.start.y));
let last = TextRange::new((sel.end.x - 1, sel.end.y), sel.end);
let c0 = self.str_slice(first).expect("valid_slice");
let c1 = self.str_slice(last).expect("valid_slice");
let remove_quote = if c == '\'' || c == '`' || c == '"' {
if c0 == "'" && c1 == "'" {
true
} else if c0 == "\"" && c1 == "\"" {
true
} else if c0 == "`" && c1 == "`" {
true
} else {
false
}
} else {
if c0 == "<" && c1 == ">" {
true
} else if c0 == "(" && c1 == ")" {
true
} else if c0 == "[" && c1 == "]" {
true
} else if c0 == "{" && c1 == "}" {
true
} else {
false
}
};
if remove_quote {
self.remove_char_range(last)?;
self.remove_char_range(first)?;
if sel.start.y == sel.end.y {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 2, sel.end.y));
} else {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 1, sel.end.y));
}
}
}
let cc = match c {
'\'' => '\'',
'`' => '`',
'"' => '"',
'<' => '>',
'(' => ')',
'[' => ']',
'{' => '}',
_ => unreachable!("invalid quotes"),
};
self.insert_char(sel.end, cc)?;
self.insert_char(sel.start, c)?;
if sel.start.y == sel.end.y {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 2, sel.end.y));
} else {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 1, sel.end.y));
}
self.set_selection(sel.start, sel.end);
self.end_undo_seq();
Ok(true)
}
pub fn insert_tab(&mut self, mut pos: TextPosition) -> Result<bool, TextError> {
if self.expand_tabs {
let n = self.tabs as upos_type - (pos.x % self.tabs as upos_type);
for _ in 0..n {
self.insert_char(pos, ' ')?;
pos.x += 1;
}
} else {
self.insert_char(pos, '\t')?;
}
Ok(true)
}
pub fn insert_newline(&mut self, mut pos: TextPosition) -> Result<bool, TextError> {
if self.text.is_multi_line() {
for c in self.newline.clone().chars() {
self.insert_char(pos, c)?;
pos.x += 1;
}
Ok(true)
} else {
Ok(false)
}
}
pub fn insert_char(&mut self, pos: TextPosition, c: char) -> Result<bool, TextError> {
let (inserted_range, inserted_bytes) = self.text.insert_char(pos, c)?;
let old_cursor = self.cursor;
let old_anchor = self.anchor;
if let Some(sty) = &mut self.styles {
sty.remap(|r, _| Some(expand_range_by((&inserted_bytes).clone(), r)));
}
self.cursor = inserted_range.expand_pos(self.cursor);
self.anchor = inserted_range.expand_pos(self.anchor);
if let Some(undo) = self.undo.as_mut() {
undo.append(UndoOp::InsertChar {
bytes: inserted_bytes.clone(),
cursor: TextPositionChange {
before: old_cursor,
after: self.cursor,
},
anchor: TextPositionChange {
before: old_anchor,
after: self.anchor,
},
txt: c.to_string(),
});
}
Ok(true)
}
pub fn insert_str(&mut self, pos: TextPosition, t: &str) -> Result<bool, TextError> {
let old_cursor = self.cursor;
let old_anchor = self.anchor;
let (inserted_range, inserted_bytes) = self.text.insert_str(pos, t)?;
if let Some(sty) = &mut self.styles {
sty.remap(|r, _| Some(expand_range_by((&inserted_bytes).clone(), r)));
}
self.anchor = inserted_range.expand_pos(self.anchor);
self.cursor = inserted_range.expand_pos(self.cursor);
if let Some(undo) = self.undo.as_mut() {
undo.append(UndoOp::InsertStr {
bytes: inserted_bytes.clone(),
cursor: TextPositionChange {
before: old_cursor,
after: self.cursor,
},
anchor: TextPositionChange {
before: old_anchor,
after: self.anchor,
},
txt: t.to_string(),
});
}
Ok(true)
}
pub fn remove_prev_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
let (sx, sy) = if pos.y == 0 && pos.x == 0 {
(0, 0)
} else if pos.y != 0 && pos.x == 0 {
let prev_line_width = self.line_width(pos.y - 1).expect("line_width");
(prev_line_width, pos.y - 1)
} else {
(pos.x - 1, pos.y)
};
let range = TextRange::new((sx, sy), (pos.x, pos.y));
self.remove_char_range(range)
}
pub fn remove_next_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
let c_line_width = self.line_width(pos.y)?;
let c_last_line = self.len_lines().saturating_sub(1);
let (ex, ey) = if pos.y == c_last_line && pos.x == c_line_width {
(pos.x, pos.y)
} else if pos.y != c_last_line && pos.x == c_line_width {
(0, pos.y + 1)
} else {
(pos.x + 1, pos.y)
};
let range = TextRange::new((pos.x, pos.y), (ex, ey));
self.remove_char_range(range)
}
pub fn remove_char_range(&mut self, range: TextRange) -> Result<bool, TextError> {
self._remove_range(range, true)
}
pub fn remove_str_range(&mut self, range: TextRange) -> Result<bool, TextError> {
self._remove_range(range, false)
}
fn _remove_range(&mut self, range: TextRange, char_range: bool) -> Result<bool, TextError> {
let old_cursor = self.cursor;
let old_anchor = self.anchor;
if range.is_empty() {
return Ok(false);
}
let (old_text, (_removed_range, removed_bytes)) = self.text.remove(range)?;
let mut changed_style = Vec::new();
if let Some(sty) = &mut self.styles {
sty.remap(|r, s| {
let new_range = shrink_range_by(removed_bytes.clone(), r.clone());
if ranges_intersect(r.clone(), removed_bytes.clone()) {
changed_style.push(StyleChange {
before: r.clone(),
after: new_range.clone(),
style: s,
});
if new_range.is_empty() {
None
} else {
Some(new_range)
}
} else {
Some(new_range)
}
});
}
self.anchor = range.shrink_pos(self.anchor);
self.cursor = range.shrink_pos(self.cursor);
if let Some(undo) = &mut self.undo {
if char_range {
undo.append(UndoOp::RemoveChar {
bytes: removed_bytes.clone(),
cursor: TextPositionChange {
before: old_cursor,
after: self.cursor,
},
anchor: TextPositionChange {
before: old_anchor,
after: self.anchor,
},
txt: old_text,
styles: changed_style,
});
} else {
undo.append(UndoOp::RemoveStr {
bytes: removed_bytes.clone(),
cursor: TextPositionChange {
before: old_cursor,
after: self.cursor,
},
anchor: TextPositionChange {
before: old_anchor,
after: self.anchor,
},
txt: old_text,
styles: changed_style,
});
}
}
Ok(true)
}
}
impl<Store: TextStore + Default> TextCore<Store> {
pub fn next_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
let pos = pos.into();
let mut cursor = self.text_graphemes(pos)?;
let mut last_pos = cursor.text_offset();
loop {
let Some(c) = cursor.next() else {
break;
};
last_pos = c.text_bytes().start;
if !c.is_whitespace() {
break;
}
}
Ok(self.byte_pos(last_pos).expect("valid_pos"))
}
pub fn next_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
let pos = pos.into();
let mut cursor = self.text_graphemes(pos)?;
let mut last_pos = cursor.text_offset();
let mut init = true;
loop {
let Some(c) = cursor.next() else {
break;
};
last_pos = c.text_bytes().start;
if init {
if !c.is_whitespace() {
init = false;
}
} else {
if c.is_whitespace() {
break;
}
}
}
Ok(self.byte_pos(last_pos).expect("valid_pos"))
}
pub fn prev_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
let pos = pos.into();
let mut cursor = self.text_graphemes(pos)?;
let mut last_pos = cursor.text_offset();
let mut init = true;
loop {
let Some(c) = cursor.prev() else {
break;
};
if init {
if !c.is_whitespace() {
init = false;
}
} else {
if c.is_whitespace() {
break;
}
}
last_pos = c.text_bytes().start;
}
Ok(self.byte_pos(last_pos).expect("valid_pos"))
}
pub fn prev_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
let pos = pos.into();
let mut cursor = self.text_graphemes(pos)?;
let mut last_pos = cursor.text_offset();
loop {
let Some(c) = cursor.prev() else {
break;
};
if !c.is_whitespace() {
break;
}
last_pos = c.text_bytes().start;
}
Ok(self.byte_pos(last_pos).expect("valid_pos"))
}
pub fn is_word_boundary(&self, pos: TextPosition) -> Result<bool, TextError> {
let pos = pos.into();
let mut cursor = self.text_graphemes(pos)?;
if let Some(c0) = cursor.prev() {
cursor.next();
if let Some(c1) = cursor.next() {
Ok(c0.is_whitespace() && !c1.is_whitespace()
|| !c0.is_whitespace() && c1.is_whitespace())
} else {
Ok(false)
}
} else {
Ok(false)
}
}
pub fn word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
let pos = pos.into();
let mut cursor = self.text_graphemes(pos)?;
let mut last_pos = cursor.text_offset();
loop {
let Some(c) = cursor.prev() else {
break;
};
if c.is_whitespace() {
break;
}
last_pos = c.text_bytes().start;
}
Ok(self.byte_pos(last_pos).expect("valid_pos"))
}
pub fn word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
let pos = pos.into();
let mut cursor = self.text_graphemes(pos)?;
let mut last_pos = cursor.text_offset();
loop {
let Some(c) = cursor.next() else {
break;
};
last_pos = c.text_bytes().start;
if c.is_whitespace() {
break;
}
}
Ok(self.byte_pos(last_pos).expect("valid_pos"))
}
}