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}