1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
//! The text storage structures
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use super::*;
/// Error type for [`LineText`]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LineTextError {
pub text: String,
}
impl std::fmt::Display for LineTextError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Invalid line text given: {}", self.text)
}
}
impl std::error::Error for LineTextError {}
/// An immutable text data container for a single line of text
///
/// Upon creation verifies that the text is newline terminated and contains no
/// other newlines. Also uses reference counting to prevent data duplication
/// when cloning (as this will be done very often within `add-ed`'s logic).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LineText {
inner: Rc<String>,
}
impl LineText {
/// Create new LineText instance
///
/// Returns error if the text you are creating it from isn't a single valid
/// line (Exactly one newline should be in it, the last character).
pub fn new<T: Into<String>>(
text: T,
) -> Result<Self, LineTextError> {
let text: String = text.into();
// We can safely subtract 1 from len after verifying it contains a '\n'
if !text.ends_with('\n') || text[..text.len()-1].contains('\n') {
Err(LineTextError{text})
} else {
Ok(Self{ inner: Rc::new(text) })
}
}
}
impl std::ops::Deref for LineText {
type Target = String;
fn deref(&self) -> &Self::Target {
&(*self.inner)
}
}
impl TryFrom<&str> for LineText {
type Error = LineTextError;
fn try_from(t: &str) -> Result<Self, Self::Error> {
Self::new(t)
}
}
/// Text data and metadata for a single line of text
///
/// Note that the internal field accessed by `.tag()` and `.set_tag()` is shared
/// throughout the historical instances of the Line.
///
/// The main way to create, move around or clone Line instances is through
/// [`PubLine`]. For this purpose PubLine implements From<&Line> and Line
/// implements From<PubLine>. (Creating a PubLine from a Line is basically free,
/// creating a Line from a PubLine includes some allocations but still cheap.)
/// This is to ensure that the internal pointers that Line uses internally
/// (sharing objects between historical states) aren't pointing to somewhere
/// they shouldn't.
// We don't derive Clone, since it wouldn't be what library users expect.
#[derive(Debug, PartialEq, Eq)]
pub struct Line {
// Tracks if the line has been matched in a 'g' or similar command in a shared
// instance throughout the line's lifetime (to save on allocations)
// (A change to BitVec would be good, TODO.)
//
// To support nested invocations we have a vector, where index 0 is the
// outermost invocation and nested invocation have incrementing indices.
//
// Note that this has one main gotchas that must be handled where this is
// used:
// There mustn't be any old data on an index when an invocation uses it,
// not even outside the selection acted upon.
// (Handled by src/cmd/regex_commands.rs : mark_matching, which resizes
// Vec to the correct length and overwrites the soon to be relevant index
// across all lines. Since mark_matching is called for each nested
// invocation this should be run on every relevant index before it's used.)
//
// Also note that this will be empty on newly created lines, but get_matching
// handles this by defaulting to false and mark_matching explicitly resizes to
// the size it needs.
pub(crate) matched: Rc<RefCell<Vec<bool>>>,
// The tag set on the given line
//
// Rc<Cell> makes it so we can have the same tag throughout all snapshots of
// the same line, but also requires us to hide the variable (so library users
// can't clone the Rc and cause strange behaviour.
tag: Rc<Cell<char>>,
/// The text data for a given line
///
/// [`LineText`] ensures that the text data is valid for a single line and
/// implements reference counted cloning, allowing easy re-use of the same
/// data (through history, clipboard and even multiple identical lines
/// (depending on how they are created)).
pub text: LineText,
}
impl Line {
/// Create a new line with given text data
///
/// Returns LineTextError if the data is not newline terminated or contains
/// other newlines than the terminating one.
///
/// Sets the tag to `'\0'`, which is the data representation of not tagged.
pub (crate) fn new<T: Into<String>>(
text: T,
) -> Result<Self, LineTextError> {
Ok(Self{
matched: Rc::new(RefCell::new(Vec::new())),
tag: Rc::new(Cell::new('\0')),
text: LineText::new(text)?,
})
}
/// Get the current value of the tag field.
pub fn tag(&self) -> char {
self.tag.get()
}
/// Set the tag to given character
///
/// Note that this changes all historical states of this line.
pub fn set_tag(&self, new: char) {
self.tag.set(new)
}
}
// Our internal-only Clone implementation, to enable snapshotting without
// misleading library users that they can Clone Lines.
impl Snapshot for Line {
fn create_snapshot(&self) -> Self {
Line{
tag: self.tag.clone(),
matched: self.matched.clone(),
text: self.text.clone(),
}
}
}
impl From<&PubLine> for Line {
fn from(l: &PubLine) -> Self {
Self{
text: l.text.clone(),
tag: Rc::new(Cell::new(l.tag)),
matched: Rc::new(RefCell::new(Vec::new())),
}
}
}
impl TryFrom<&str> for Line {
type Error = LineTextError;
fn try_from(t: &str) -> Result<Self, Self::Error> {
Self::new(t)
}
}
/// A fully public version of the [`Line`] struct
///
/// Intended for API interaction, since it cannot represent the internal state
/// in Line which could cause trouble if invalid.
///
/// [`From`] is implemented both ways, to make it easy to convert into and from
/// [`Line`]. Some TryFrom implementations that may be useful also exist.
///
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PubLine {
/// The tag set on the line
///
/// See [`Line`] `.tag()` and `.set_tag()`, but note that we disconnect the
/// shared tag state through history by converting into this.
pub tag: char,
/// The text data for the line
///
/// See [`Line'].text.
pub text: LineText,
}
impl<'a> TryFrom<&'a str> for PubLine {
type Error = LineTextError;
fn try_from(t: &str) -> Result<Self, Self::Error> {
Ok(Self{tag: '\0', text: LineText::new(t)?})
}
}
impl<'a> TryFrom<&'a &'a str> for PubLine {
type Error = LineTextError;
fn try_from(t: &&str) -> Result<Self, Self::Error> {
Ok(Self{tag: '\0', text: LineText::new(*t)?})
}
}
impl<'a> TryFrom<(char, &'a str)> for PubLine {
type Error = LineTextError;
fn try_from(l: (char, &str)) -> Result<Self, Self::Error> {
(&l).try_into()
}
}
impl<'a> TryFrom<&'a (char, &'a str)> for PubLine {
type Error = LineTextError;
fn try_from(l: &(char, &str)) -> Result<Self, Self::Error> {
Ok(Self{tag: l.0, text: LineText::new(l.1)?})
}
}
impl From<&Line> for PubLine {
fn from(l: &Line) -> Self {
Self{tag: l.tag.get(), text: l.text.clone()}
}
}