1use crate::{
2 history::History,
3 syntax::{Language, Syntax},
4};
5use indigo_kernel::edit::{self, Edit};
6use ropey::Rope;
7use std::ops::{Deref, Range};
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum Error {
12 #[error("Text is empty")]
13 Empty,
14
15 #[error("Text does not end with a newline")]
16 MissingTrailingNewline,
17}
18
19#[derive(Clone)]
20struct BidiEdit {
21 undo: Edit,
23 redo: Edit,
25}
26
27impl Extend<Self> for BidiEdit {
28 fn extend<T>(&mut self, opss: T)
29 where
30 T: IntoIterator<Item = Self>,
31 {
32 for ops in opss {
33 if self.redo.is_empty() {
34 *self = ops;
35 } else {
36 self.undo = ops.undo.compose(&self.undo).unwrap();
37 self.redo = self.redo.compose(&ops.redo).unwrap();
38 }
39 }
40 }
41}
42
43#[derive(Clone)]
44pub struct Text {
45 rope: Rope,
46 syntax: Option<(Syntax, usize)>,
47 history: History<BidiEdit, BidiEdit>,
48 log: Vec<Edit>,
49 pub readonly: bool,
50}
51
52impl Default for Text {
53 fn default() -> Self {
54 Self {
55 rope: Rope::from("\n"),
56 syntax: None,
57 history: History::default(),
58 log: Vec::new(),
59 readonly: false,
60 }
61 }
62}
63
64impl Text {
65 #[must_use]
66 pub fn new() -> Self {
67 Self::default()
68 }
69
70 #[must_use]
71 pub fn rope(&self) -> &Rope {
72 &self.rope
73 }
74
75 #[must_use]
76 pub fn language(&self) -> Option<Language> {
77 self.syntax_stale().map(|s| s.language())
78 }
79
80 #[must_use]
81 pub fn syntax(&mut self) -> Option<&Syntax> {
82 self.reparse();
83 self.syntax_stale()
84 }
85
86 #[must_use]
87 pub fn syntax_stale(&self) -> Option<&Syntax> {
88 self.syntax.as_ref().map(|(s, _)| s)
89 }
90
91 pub fn insert(&mut self, byte_offset: usize, text: &str) -> anyhow::Result<()> {
92 let mut ops = Edit::new();
93 ops.retain(byte_offset);
94 ops.insert(text);
95 ops.retain_rest(&self.rope)?;
96 self.apply(&ops)?;
97 Ok(())
98 }
99
100 pub fn delete(&mut self, range: Range<usize>) -> anyhow::Result<()> {
101 let mut ops = Edit::new();
102 ops.retain(range.start);
103 ops.delete(&self.rope.slice(range.start..range.end).to_string());
104 if range.end == self.rope.len()
105 && (range.start == 0 || self.rope.byte(range.start - 1) != b'\n')
106 {
107 ops.insert("\n");
108 }
109 ops.retain_rest(&self.rope)?;
110 self.apply(&ops)?;
111 Ok(())
112 }
113
114 pub fn apply(&mut self, ops: &Edit) -> anyhow::Result<()> {
115 anyhow::ensure!(!self.readonly, "Cannot modify readonly text");
116 let undo = ops.invert();
117 ops.apply(&mut self.rope)?;
118 self.history.push(BidiEdit {
119 redo: ops.clone(),
120 undo,
121 });
122 self.log.push(ops.clone());
123 self.assert_invariants().unwrap();
124 Ok(())
125 }
126
127 pub fn commit(&mut self) {
128 self.history.commit();
129 }
130
131 pub fn undo(&mut self) -> anyhow::Result<bool> {
132 anyhow::ensure!(!self.readonly, "Cannot modify readonly text");
133 if let Some(ops) = self.history.undo() {
134 ops.undo.apply(&mut self.rope)?;
135 self.log.push(ops.undo.clone());
136 self.assert_invariants().unwrap();
137 Ok(true)
138 } else {
139 Ok(false)
140 }
141 }
142
143 pub fn redo(&mut self) -> anyhow::Result<bool> {
144 anyhow::ensure!(!self.readonly, "Cannot modify readonly text");
145 if let Some(ops) = self.history.redo() {
146 ops.redo.apply(&mut self.rope)?;
147 self.log.push(ops.redo.clone());
148 self.assert_invariants().unwrap();
149 Ok(true)
150 } else {
151 Ok(false)
152 }
153 }
154
155 pub fn set_language(&mut self, language: Language) {
156 self.syntax = Some((Syntax::parse(language, &self.rope), self.version()));
157 }
158
159 pub fn reparse(&mut self) {
160 let version = self.version();
161 if let Some((syntax, parsed_at)) = &mut self.syntax
162 && *parsed_at < version
163 {
164 syntax.reparse(&self.rope);
165 *parsed_at = version;
166 }
167 }
168
169 #[must_use]
170 pub fn create_anchor(&self, byte_offset: usize) -> Anchor {
171 Anchor::new(self, byte_offset)
172 }
173
174 #[must_use]
175 pub fn resolve_anchor(&self, anchor: &Anchor) -> Option<usize> {
176 anchor.resolve(self)
177 }
178
179 #[must_use]
180 pub fn version(&self) -> usize {
181 self.log.len()
182 }
183
184 #[must_use]
185 pub fn ops_since(&self, version: usize) -> Option<&[Edit]> {
186 self.log.get(version..)
187 }
188
189 pub fn assert_invariants(&self) -> anyhow::Result<()> {
190 if self.rope.len() == 0 {
191 anyhow::bail!(Error::Empty);
192 }
193 if self.rope.byte(self.rope.len() - 1) != b'\n' {
194 anyhow::bail!(Error::MissingTrailingNewline);
195 }
196 Ok(())
197 }
198}
199
200impl Deref for Text {
201 type Target = Rope;
202
203 fn deref(&self) -> &Self::Target {
204 &self.rope
205 }
206}
207
208impl<'a> From<&'a str> for Text {
209 fn from(str: &'a str) -> Self {
210 Self::from(Rope::from(str))
211 }
212}
213
214impl From<Rope> for Text {
215 fn from(mut rope: Rope) -> Self {
216 if rope.len() == 0 || rope.byte(rope.len() - 1) != b'\n' {
217 rope.insert(rope.len(), "\n");
218 }
219 Self {
220 rope,
221 ..Self::default()
222 }
223 }
224}
225
226pub struct Anchor {
227 byte_offset: usize,
228 version: usize,
229}
230
231impl Anchor {
232 #[must_use]
233 pub fn new(text: &Text, byte_offset: usize) -> Self {
234 Self {
235 byte_offset,
236 version: text.version(),
237 }
238 }
239
240 #[must_use]
241 pub fn resolve(&self, text: &Text) -> Option<usize> {
242 let mut byte_offset = self.byte_offset;
243 for ops in text.ops_since(self.version)? {
244 byte_offset = ops.map_position(byte_offset, edit::Bias::Forward);
245 }
246 Some(byte_offset)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use std::cmp::{max, min};
254
255 fn has_trailing_newline(text: &Text) -> bool {
256 let rope = text.rope();
257 rope.len() > 0 && rope.byte(rope.len() - 1) == b'\n'
258 }
259
260 #[test]
261 fn constructors_normalize() {
262 assert_eq!(Text::new().to_string(), "\n");
263 assert_eq!(Text::default().to_string(), "\n");
264 assert_eq!(Text::from("").to_string(), "\n");
265 assert_eq!(Text::from("x").to_string(), "x\n");
266 assert_eq!(Text::from("x\n").to_string(), "x\n");
267 assert_eq!(Text::from(Rope::from("x")).to_string(), "x\n");
268 assert_eq!(Text::from("x\r").to_string(), "x\r\n");
270 }
271
272 #[test]
273 fn delete_to_end_keeps_trailing_newline() {
274 let mut text = Text::from("ab\n");
275 text.delete(1..3).unwrap();
276 assert_eq!(text.to_string(), "a\n");
277
278 let mut text = Text::from("ab\n");
280 text.delete(0..3).unwrap();
281 assert_eq!(text.to_string(), "\n");
282
283 let mut text = Text::from("a\nb\n");
285 text.delete(2..4).unwrap();
286 assert_eq!(text.to_string(), "a\n");
287 }
288
289 #[test]
290 fn undo_redo_keep_trailing_newline() {
291 let mut text = Text::from("ab\n");
292 text.delete(0..3).unwrap();
293 text.commit();
294 assert_eq!(text.to_string(), "\n");
295 assert!(text.undo().unwrap());
296 assert_eq!(text.to_string(), "ab\n");
297 assert!(has_trailing_newline(&text));
298 assert!(text.redo().unwrap());
299 assert_eq!(text.to_string(), "\n");
300 assert!(has_trailing_newline(&text));
301 }
302
303 #[hegel::test(test_cases = 1_000)]
304 fn fuzz(tc: hegel::TestCase) {
305 use crate::rope::RopeExt as _;
306 use hegel::generators as gs;
307 #[derive(Default)]
308 struct StateMachine {
309 text: Text,
310 }
311 #[hegel::state_machine]
312 #[expect(clippy::needless_pass_by_value)]
313 impl StateMachine {
314 #[rule]
315 fn insert(&mut self, tc: hegel::TestCase) {
316 let byte_offset = tc.draw(gs::integers::<usize>().max_value(self.text.len()));
320 let byte_offset = self
321 .text
322 .snap_to_grapheme_start(byte_offset)
323 .expect("Text is never empty");
324 let string = tc.draw(gs::text());
325 self.text.insert(byte_offset, &string).unwrap();
326 }
327 #[rule]
328 fn delete(&mut self, tc: hegel::TestCase) {
329 let a = tc.draw(gs::integers::<usize>().max_value(self.text.len()));
330 let b = tc.draw(gs::integers::<usize>().max_value(self.text.len()));
331 let start = self.text.floor_grapheme_boundary(min(a, b));
332 let end = self.text.floor_grapheme_boundary(max(a, b));
333 self.text.delete(start..end).unwrap();
334 }
335 #[rule]
336 fn commit(&mut self, _: hegel::TestCase) {
337 self.text.commit();
338 }
339 #[rule]
340 fn undo(&mut self, _: hegel::TestCase) {
341 self.text.undo().unwrap();
342 }
343 #[rule]
344 fn redo(&mut self, _: hegel::TestCase) {
345 self.text.redo().unwrap();
346 }
347 #[invariant]
348 fn invariants(&self, _: hegel::TestCase) {
349 self.text.assert_invariants().unwrap();
350 }
351 }
352 hegel::stateful::machine(StateMachine::default()).run(tc);
353 }
354}