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