use std::ops::Range;
pub(crate) use crate::text::{strs::StrsBuf, tags::ToggleFn};
use crate::{
Ns,
buffer::{Change, History},
context::Handle,
data::Pass,
mode::{Selection, Selections},
text::{
tags::{FwdTags, InnerTags, RevTags},
utils::implPartialEq,
},
ui::{SpawnId, Widget},
};
pub use crate::{
text::{
builder::{AsBuilderPart, Builder, BuilderPart},
iter::{FwdIter, RevIter, TextPart, TextPlace},
search::{Matches, RegexHaystack, RegexPattern},
strs::{Lines, Strs},
tags::{
Conceal, FormTag, InlayId, Inlay, Mask, Overlay, RawTag, Spacer, Spawn, Tag, Tags,
Toggle,
},
utils::{Point, TextIndex, TextRange, TextRangeOrIndex, TwoPoints, utf8_char_width},
},
txt,
};
mod builder;
mod iter;
mod search;
mod shift_list;
mod strs;
mod tags;
mod utils;
pub struct Text(Box<InnerText>);
#[derive(Clone)]
struct InnerText {
buf: StrsBuf,
tags: InnerTags,
selections: Selections,
has_unsaved_changes: bool,
}
impl Text {
pub fn new() -> Self {
Self::from_parts(StrsBuf::new(String::new()), Selections::new_empty())
}
pub fn with_default_main_selection() -> Self {
Self::from_parts(
StrsBuf::new(String::new()),
Selections::new(Selection::default()),
)
}
pub(crate) fn from_parts(buf: StrsBuf, mut selections: Selections) -> Self {
let tags = InnerTags::new(buf.len());
let selections = if selections.iter().any(|(sel, _)| {
[Some(sel.caret()), sel.anchor()]
.into_iter()
.flatten()
.any(|point| point.char() >= buf.end_point().char())
}) {
Selections::new(Selection::default())
} else {
selections.correct_all(&buf);
selections
};
Self(Box::new(InnerText {
buf,
tags,
selections,
has_unsaved_changes: false,
}))
}
pub fn builder() -> Builder {
Builder::new()
}
pub fn is_empty(&self) -> bool {
let [s0, s1] = self.to_array();
(s0 == "\n" && s1.is_empty()) || (s0.is_empty() && s1 == "\n")
}
pub fn is_empty_empty(&self) -> bool {
self.0.buf.is_empty() && self.0.tags.is_empty()
}
pub fn parts(&mut self) -> TextParts<'_> {
TextParts {
strs: &self.0.buf,
tags: self.0.tags.tags(),
selections: &self.0.selections,
}
}
pub fn as_mut(&mut self) -> TextMut<'_> {
TextMut { text: self, history: None }
}
#[track_caller]
pub fn ghost_max_points_at(&self, b: usize) -> TwoPoints {
let point = self.point_at_byte(b);
if let Some(total_ghost) = self.0.tags.ghosts_total_at(point.byte()) {
TwoPoints::new(point, total_ghost)
} else {
TwoPoints::new_after_ghost(point)
}
}
pub fn end_points(&self) -> TwoPoints {
self.ghost_max_points_at(self.len())
}
#[track_caller]
pub fn points_after(&self, tp: TwoPoints) -> Option<TwoPoints> {
self.iter_fwd(tp)
.filter_map(|item| item.part.as_char().map(|_| item.points()))
.chain([self.end_points()])
.nth(1)
}
pub fn visual_line_start(&self, mut points: TwoPoints, skip: usize) -> TwoPoints {
let mut iter = self.iter_rev(points).peekable();
let mut total_seen = 0;
while let Some(peek) = iter.peek() {
match peek.part {
TextPart::Char('\n') => {
if total_seen == skip {
return points;
} else {
total_seen += 1;
}
}
TextPart::Char(_) => points = iter.next().unwrap().points(),
_ => _ = iter.next(),
}
}
points
}
pub fn get_ghost(&self, id: InlayId) -> Option<&Text> {
self.0.tags.get_ghost(id)
}
pub fn replace_range(&mut self, range: impl TextRange, edit: impl ToString) {
let range = range.to_range(self.len());
let (start, end) = (
self.point_at_byte(range.start),
self.point_at_byte(range.end),
);
let change = Change::new(edit, start..end, self);
self.0.buf.increment_version();
self.apply_change(0, change.as_ref());
}
fn apply_change(&mut self, guess_i: usize, change: Change<&str>) -> usize {
self.0.buf.apply_change(change);
self.0.tags.transform(
change.start().byte()..change.taken_end().byte(),
change.added_end().byte(),
);
self.0.has_unsaved_changes = true;
self.0.selections.apply_change(guess_i, change)
}
pub fn insert_text(&mut self, p: impl TextIndex, text: &Text) {
let b = p.to_byte_index().min(self.last_point().byte());
let cap = text.last_point().byte();
let added_str = text.0.buf[..cap].to_string();
let point = self.point_at_byte(b);
let change = Change::str_insert(&added_str, point);
self.apply_change(0, change);
self.0.tags.insert_tags(point, cap, &text.0.tags);
}
fn apply_and_process_changes<'a>(
&mut self,
changes: impl ExactSizeIterator<Item = Change<'a, &'a str>>,
) {
self.0.selections.clear();
let len = changes.len();
for (i, change) in changes.enumerate() {
self.apply_change(0, change);
let start = change.start().min(self.last_point());
let added_end = match change.added_str().chars().next_back() {
Some(last) => change.added_end().rev(last),
None => start,
};
let selection = Selection::new(added_end, (start != added_end).then_some(start));
self.0.selections.insert(i, selection, i == len - 1);
}
}
pub fn save_on(&mut self, mut writer: impl std::io::Write) -> std::io::Result<usize> {
self.0.has_unsaved_changes = false;
let [s0, s1] = self.0.buf.slices(..);
Ok(writer.write(s0)? + writer.write(s1)?)
}
pub fn has_unsaved_changes(&self) -> bool {
self.0.has_unsaved_changes
}
#[track_caller]
pub fn insert_tag<Idx>(&mut self, ns: Ns, idx: Idx, tag: impl Tag<Idx>) {
self.0.tags.insert_inner(ns, idx, tag, false)
}
pub fn insert_tag_after<Idx>(&mut self, ns: Ns, idx: Idx, tag: impl Tag<Idx>) {
self.0.tags.insert_inner(ns, idx, tag, true)
}
pub fn remove_tags(&mut self, ns: Ns, range: impl TextRangeOrIndex) {
let range = range.to_range(self.len() + 1);
self.0.tags.remove_from(ns, range)
}
pub fn remove_tags_excl(&mut self, ns: Ns, range: impl TextRangeOrIndex) {
let range = range.to_range(self.len() + 1);
self.0.tags.remove_from_excl(ns, range)
}
pub fn remove_tags_if(
&mut self,
ns: Ns,
from: impl TextRangeOrIndex,
filter: impl FnMut(usize, RawTag) -> bool,
) {
let range = from.to_range(self.len() + 1);
self.0.tags.remove_from_if(ns, range, filter)
}
pub fn clear_tags(&mut self) {
self.0.tags = InnerTags::new(self.0.buf.len());
}
#[track_caller]
pub fn iter_fwd(&self, at: TwoPoints) -> FwdIter<'_> {
FwdIter::new_at(self, at, false)
}
pub fn iter_rev(&self, at: TwoPoints) -> RevIter<'_> {
RevIter::new_at(self, at)
}
pub fn tags_fwd(&self, b: usize, lookaround: Option<usize>) -> FwdTags<'_> {
self.0.tags.fwd_at(b, lookaround)
}
pub fn tags_rev(&self, b: usize, lookaround: Option<usize>) -> RevTags<'_> {
self.0.tags.rev_at(b, lookaround)
}
pub fn raw_tags_fwd(&self, b: usize) -> impl Iterator<Item = (usize, RawTag)> {
self.0.tags.raw_fwd_at(b)
}
pub fn raw_tags_rev(&self, b: usize) -> impl Iterator<Item = (usize, RawTag)> {
self.0.tags.raw_rev_at(b)
}
pub fn selections(&self) -> &Selections {
&self.0.selections
}
pub fn selections_mut(&mut self) -> &mut Selections {
&mut self.0.selections
}
pub fn get_main_sel(&self) -> Option<&Selection> {
self.0.selections.get_main()
}
#[track_caller]
pub fn main_sel(&self) -> &Selection {
self.0.selections.main()
}
pub fn get_spawned_ids(&self) -> impl Iterator<Item = SpawnId> {
self.0.tags.get_spawned_ids()
}
pub fn to_string_no_last_nl(&self) -> String {
let mut string = self.to_string();
string.pop();
string
}
pub fn version(&self) -> TextVersion {
let (tags, meta_tags) = self.0.tags.versions();
TextVersion {
strs: self.0.buf.version(),
tags,
meta_tags,
}
}
pub(crate) fn prepare_for_reloading(&mut self) {
self.clear_tags();
}
pub(crate) fn take_reload_parts(&mut self) -> (StrsBuf, Selections) {
(
std::mem::take(&mut self.0.buf),
std::mem::replace(&mut self.0.selections, Selections::new_empty()),
)
}
pub(crate) fn toggles_surrounding(&self, point: Point) -> Vec<(Range<Point>, ToggleFn)> {
self.0
.tags
.toggles_surrounding(point.byte())
.map(|(range, toggle_fn)| {
(
self.point_at_byte(range.start)..self.point_at_byte(range.end),
toggle_fn,
)
})
.collect()
}
}
impl std::ops::Deref for Text {
type Target = Strs;
fn deref(&self) -> &Self::Target {
&self.0.buf
}
}
#[derive(Debug)]
pub struct TextMut<'t> {
text: &'t mut Text,
history: Option<&'t mut History>,
}
impl<'t> TextMut<'t> {
pub fn replace_range(&mut self, range: impl TextRange, edit: impl ToString) {
let range = range.to_range(self.len());
let (start, end) = (
self.point_at_byte(range.start),
self.point_at_byte(range.end),
);
let change = Change::new(edit, start..end, self);
self.text.0.buf.increment_version();
self.text.apply_change(0, change.as_ref());
self.history.as_mut().map(|h| h.apply_change(None, change));
}
pub(crate) fn apply_change(
&mut self,
guess_i: Option<usize>,
change: Change<'static, String>,
) -> (Option<usize>, usize) {
self.text.0.buf.increment_version();
let selections_taken = self
.text
.apply_change(guess_i.unwrap_or(0), change.as_ref());
let history = self.history.as_mut();
let insertion_i = history.map(|h| h.apply_change(guess_i, change));
(insertion_i, selections_taken)
}
pub fn parts(self) -> TextParts<'t> {
self.text.parts()
}
pub fn insert_tag<Idx>(&mut self, ns: Ns, idx: Idx, tag: impl Tag<Idx>) {
self.text.insert_tag(ns, idx, tag)
}
pub fn insert_tag_after<Idx>(&mut self, ns: Ns, idx: Idx, tag: impl Tag<Idx>) {
self.text.insert_tag_after(ns, idx, tag)
}
pub fn remove_tags(&mut self, ns: Ns, range: impl TextRangeOrIndex) {
let range = range.to_range(self.len() + 1);
self.text.remove_tags(ns, range)
}
pub fn remove_tags_excl(&mut self, ns: Ns, range: impl TextRangeOrIndex) {
let range = range.to_range(self.len() + 1);
self.text.remove_tags_excl(ns, range)
}
pub fn remove_tags_if(
&mut self,
ns: Ns,
from: impl TextRangeOrIndex,
filter: impl FnMut(usize, RawTag) -> bool,
) {
let range = from.to_range(self.len() + 1);
self.text.remove_tags_if(ns, range, filter)
}
pub fn clear_tags(&mut self) {
self.text.clear_tags();
}
pub(crate) fn update_bounds(&mut self) {
self.text.0.tags.update_bounds();
}
pub(crate) fn get_widget_spawns(
&mut self,
) -> Vec<(
SpawnId,
Box<dyn FnOnce(&mut Pass, usize, Handle<dyn Widget>) + Send>,
)> {
std::mem::take(&mut self.text.0.tags.spawn_fns.0)
}
pub fn undo(&mut self) {
if let Some(history) = &mut self.history
&& let Some((changes, saved_moment)) = history.move_backwards()
{
self.text.apply_and_process_changes(changes);
self.text.0.buf.increment_version();
self.text.0.has_unsaved_changes = !saved_moment;
}
}
pub fn redo(&mut self) {
if let Some(history) = &mut self.history
&& let Some((changes, saved_moment)) = history.move_forward()
{
self.text.apply_and_process_changes(changes);
self.text.0.buf.increment_version();
self.text.0.has_unsaved_changes = !saved_moment;
}
}
pub fn new_moment(&mut self) {
if let Some(h) = &mut self.history {
h.new_moment()
}
}
pub(crate) fn attach_history(&mut self, history: &'t mut History) {
self.history = Some(history);
}
pub fn selections_mut(self) -> &'t mut Selections {
&mut self.text.0.selections
}
}
impl<'t> std::ops::Deref for TextMut<'t> {
type Target = Text;
fn deref(&self) -> &Self::Target {
self.text
}
}
impl AsRef<Strs> for Text {
fn as_ref(&self) -> &Strs {
&self.0.buf
}
}
pub struct TextParts<'t> {
pub strs: &'t Strs,
pub tags: Tags<'t>,
pub selections: &'t Selections,
}
impl<'t> TextParts<'t> {
pub fn version(&self) -> TextVersion {
let (tags, meta_tags) = self.tags.versions();
TextVersion {
strs: self.strs.version(),
tags,
meta_tags,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TextVersion {
pub strs: u64,
pub tags: u64,
pub meta_tags: u64,
}
impl TextVersion {
pub fn has_changed_since(&self, other: Self) -> bool {
self.strs > other.strs || self.tags > other.tags || self.meta_tags > other.meta_tags
}
pub fn strs_have_changed_since(&self, other: Self) -> bool {
self.strs > other.strs
}
pub fn tags_have_changed_since(&self, other: Self) -> bool {
self.tags > other.tags
}
pub fn has_structurally_changed_since(&self, other: Self) -> bool {
self.strs > other.strs || self.meta_tags > other.meta_tags
}
}
impl Default for Text {
fn default() -> Self {
Self::new()
}
}
impl<T: ToString> From<T> for Text {
fn from(value: T) -> Self {
Self::from_parts(StrsBuf::new(value.to_string()), Selections::new_empty())
}
}
impl std::fmt::Debug for Text {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Text")
.field("buf", &self.0.buf)
.field("tags", &self.0.tags)
.finish_non_exhaustive()
}
}
impl Clone for Text {
fn clone(&self) -> Self {
let mut text = Self(self.0.clone());
if text.bytes().next_back().is_none_or(|b| b != b'\n') {
let end = text.end_point();
text.apply_change(0, Change::str_insert("\n", end));
}
text
}
}
impl Eq for Text {}
implPartialEq!(text: Text, other: Text,
text.0.buf == other.0.buf && text.0.tags == other.0.tags
);
implPartialEq!(text: Text, other: &str, text.0.buf == *other);
implPartialEq!(text: Text, other: String, text.0.buf == *other);
implPartialEq!(str: &str, text: Text, text.0.buf == **str);
implPartialEq!(str: String, text: Text, text.0.buf == **str);
impl Eq for TextMut<'_> {}
implPartialEq!(text_mut: TextMut<'_>, other: TextMut<'_>, text_mut.text == other.text);
implPartialEq!(text_mut: TextMut<'_>, other: Text, text_mut.text == other);
implPartialEq!(text_mut: TextMut<'_>, other: &str, text_mut.text.0.buf == *other);
implPartialEq!(text_mut: TextMut<'_>, other: String, text_mut.text.0.buf == *other);
implPartialEq!(text: Text, other: TextMut<'_>, *text == other.text);
implPartialEq!(str: &str, text_mut: TextMut<'_>, text_mut.text.0.buf == **str);
implPartialEq!(str: String, text_mut: TextMut<'_>, text_mut.text.0.buf == **str);