duat_core/buffer/mod.rs
1//! The primary [`Widget`] of Duat, used to display buffers.
2//!
3//! Most extensible features of Duat have the primary purpose of
4//! serving the [`Buffer`], such as multiple [`Cursor`]s, a
5//! `History` system, [`RawArea::PrintInfo`], etc.
6//!
7//! The [`Buffer`] also provides a list of printed lines through the
8//! [`Buffer::printed_lines`] method. This method is notably used by
9//! the [`LineNumbers`] widget, that shows the numbers of the
10//! currently printed lines.
11//!
12//! [`LineNumbers`]: https://docs.rs/duat/latest/duat/widgets/struct.LineNumbers.html
13//! [`Cursor`]: crate::mode::Cursor
14//! [`RawArea::PrintInfo`]: crate::ui::traits::RawArea::PrintInfo
15use std::{
16 fs,
17 path::{Path, PathBuf},
18 sync::Arc,
19};
20
21use parking_lot::{Mutex, MutexGuard};
22
23use self::parser::Parsers;
24pub use self::parser::{BufferTracker, Parser};
25use crate::{
26 context::{self, Cache, Handle},
27 data::Pass,
28 form::Painter,
29 hook::{self, BufferWritten},
30 mode::Selections,
31 opts::PrintOpts,
32 text::{BuilderPart, Bytes, Text, txt},
33 ui::{RwArea, Widget},
34};
35
36mod parser;
37
38/// The widget that is used to print and edit buffers
39pub struct Buffer {
40 path: PathKind,
41 text: Text,
42 printed_lines: Mutex<Vec<(usize, bool)>>,
43 parsers: Parsers,
44 sync_opts: Arc<Mutex<PrintOpts>>,
45 pub(crate) layout_order: usize,
46 /// The [`PrintOpts`] of this [`Buffer`]
47 ///
48 /// You can use this member to change the way this `Buffer` will
49 /// be printed specifically.
50 pub opts: PrintOpts,
51 prev_opts: PrintOpts,
52}
53
54impl Buffer {
55 /// Returns a new [`Buffer`], private for now
56 pub(crate) fn new(path: Option<PathBuf>, opts: PrintOpts) -> Self {
57 let (text, path) = match path {
58 Some(path) => {
59 let canon_path = path.canonicalize();
60 if let Ok(path) = &canon_path
61 && let Ok(buffer) = std::fs::read_to_string(path)
62 {
63 let selections = {
64 let selection = Cache::new().load(path).unwrap_or_default();
65 Selections::new(selection)
66 };
67 let text = Text::from_parts(Bytes::new(&buffer), selections, true);
68 (text, PathKind::SetExists(path.clone()))
69 } else if canon_path.is_err()
70 && let Ok(mut canon_path) = path.with_file_name(".").canonicalize()
71 {
72 canon_path.push(path.file_name().unwrap());
73 (Text::new_with_history(), PathKind::SetAbsent(canon_path))
74 } else {
75 (Text::new_with_history(), PathKind::new_unset())
76 }
77 }
78 None => (Text::new_with_history(), PathKind::new_unset()),
79 };
80
81 Self {
82 path,
83 text,
84 sync_opts: Arc::new(Mutex::new(opts)),
85 printed_lines: Mutex::new(Vec::new()),
86 parsers: Parsers::default(),
87 layout_order: 0,
88 opts,
89 prev_opts: opts,
90 }
91 }
92
93 ////////// Saving the Buffer
94
95 /// Writes the buffer to the current [`PathBuf`], if one was set
96 pub fn save(&mut self) -> Result<Option<usize>, Text> {
97 self.save_quit(false)
98 }
99
100 /// Saves and quits, resulting in no config reload
101 pub(crate) fn save_quit(&mut self, quit: bool) -> Result<Option<usize>, Text> {
102 if let PathKind::SetExists(path) | PathKind::SetAbsent(path) = &self.path {
103 let path = path.clone();
104 if self.text.has_unsaved_changes() {
105 let bytes = self
106 .text
107 .save_on(std::io::BufWriter::new(fs::File::create(&path)?))
108 .inspect(|_| self.path = PathKind::SetExists(path.clone()))?;
109
110 let path = path.to_string_lossy().to_string();
111 hook::queue(BufferWritten((path, bytes, quit)));
112
113 Ok(Some(bytes))
114 } else {
115 Ok(None)
116 }
117 } else {
118 Err(txt!("No buffer was set"))
119 }
120 }
121
122 /// Writes the buffer to the given [`Path`]
123 ///
124 /// [`Path`]: std::path::Path
125 pub fn save_to(&mut self, path: impl AsRef<std::path::Path>) -> std::io::Result<Option<usize>> {
126 self.save_quit_to(path, false)
127 }
128
129 /// Writes the buffer to the given [`Path`]
130 ///
131 /// [`Path`]: std::path::Path
132 pub(crate) fn save_quit_to(
133 &mut self,
134 path: impl AsRef<std::path::Path>,
135 quit: bool,
136 ) -> std::io::Result<Option<usize>> {
137 if self.text.has_unsaved_changes() {
138 let path = path.as_ref();
139 let res = self
140 .text
141 .save_on(std::io::BufWriter::new(fs::File::create(path)?))
142 .map(Some);
143
144 if let Ok(Some(bytes)) = res.as_ref() {
145 hook::queue(BufferWritten((
146 path.to_string_lossy().to_string(),
147 *bytes,
148 quit,
149 )));
150 }
151
152 res
153 } else {
154 Ok(None)
155 }
156 }
157
158 ////////// Path querying functions
159
160 /// The full path of the buffer.
161 ///
162 /// If there is no set path, returns `"*scratch buffer*#{id}"`.
163 pub fn path(&self) -> String {
164 self.path.path()
165 }
166
167 /// The full path of the buffer.
168 ///
169 /// Returns [`None`] if the path has not been set yet, i.e., if
170 /// the buffer is a scratch buffer.
171 pub fn path_set(&self) -> Option<String> {
172 self.path.path_set()
173 }
174
175 /// A [`Text`] from the full path of this [`PathKind`]
176 ///
177 /// # Formatting
178 ///
179 /// If the buffer's `path` was set:
180 ///
181 /// ```text
182 /// [buffer]{path}
183 /// ```
184 ///
185 /// If the buffer's `path` was not set:
186 ///
187 /// ```text
188 /// [buffer.new.scratch]*scratch buffer #{id}*
189 /// ```
190 pub fn path_txt(&self) -> Text {
191 self.path_kind().path_txt()
192 }
193
194 /// The buffer's name.
195 ///
196 /// If there is no set path, returns `"*scratch buffer #{id}*"`.
197 pub fn name(&self) -> String {
198 self.path.name()
199 }
200
201 /// The buffer's name.
202 ///
203 /// Returns [`None`] if the path has not been set yet, i.e., if
204 /// the buffer is a scratch buffer.
205 pub fn name_set(&self) -> Option<String> {
206 self.path.name_set()
207 }
208
209 /// A [`Text`] from the name of this [`PathKind`]
210 ///
211 /// The name of a [`Buffer`] widget is the same as the path, but
212 /// it strips away the current directory. If it can't, it will
213 /// try to strip away the home directory, replacing it with
214 /// `"~"`. If that also fails, it will just show the full
215 /// path.
216 ///
217 /// # Formatting
218 ///
219 /// If the buffer's `name` was set:
220 ///
221 /// ```text
222 /// [buffer]{name}
223 /// ```
224 ///
225 /// If the buffer's `name` was not set:
226 ///
227 /// ```text
228 /// [buffer.new.scratch]*scratch buffer #{id}*
229 /// ```
230 pub fn name_txt(&self) -> Text {
231 self.path.name_txt()
232 }
233
234 /// The type of [`PathBuf`]
235 ///
236 /// This represents the three possible states for a [`Buffer`]'s
237 /// [`PathBuf`], as it could either represent a real [`Buffer`],
238 /// not exist, or not have been defined yet.
239 pub fn path_kind(&self) -> PathKind {
240 self.path.clone()
241 }
242
243 /// Returns the currently printed set of lines.
244 ///
245 /// These are returned as a `usize`, showing the index of the line
246 /// in the buffer, and a `bool`, which is `true` when the line is
247 /// wrapped.
248 pub fn printed_lines(&self) -> MutexGuard<'_, Vec<(usize, bool)>> {
249 self.printed_lines.lock()
250 }
251
252 ////////// General querying functions
253
254 /// The [`Bytes`] of the [`Buffer`]'s [`Text`]
255 pub fn bytes(&self) -> &Bytes {
256 self.text.bytes()
257 }
258
259 /// The number of bytes in the buffer.
260 pub fn len_bytes(&self) -> usize {
261 self.text.len().byte()
262 }
263
264 /// The number of [`char`]s in the buffer.
265 pub fn len_chars(&self) -> usize {
266 self.text.len().char()
267 }
268
269 /// The number of lines in the buffer.
270 pub fn len_lines(&self) -> usize {
271 self.text.len().line()
272 }
273
274 /// The [`Selections`] that are used on the [`Text`]
275 pub fn selections(&self) -> &Selections {
276 self.text.selections()
277 }
278
279 /// A mutable reference to the [`Selections`]
280 pub fn selections_mut(&mut self) -> &mut Selections {
281 self.text.selections_mut()
282 }
283
284 /// Whether o not the [`Buffer`] exists or not
285 pub fn exists(&self) -> bool {
286 self.path_set()
287 .is_some_and(|p| std::fs::exists(PathBuf::from(&p)).is_ok_and(|e| e))
288 }
289
290 ////////// Parser functions
291
292 /// Adds a [`Parser`] to this `Buffer`
293 ///
294 /// The `Parser` will be able to keep track of every single
295 /// [`Change`] that takes place in the `Buffer`'s [`Text`], and
296 /// can act on the `Buffer` accordingly.
297 ///
298 /// This function will fail if a [`Parser`] of the same type was
299 /// already added to this [`Buffer`]
300 ///
301 /// [`Change`]: crate::text::Change
302 pub fn add_parser<P: Parser>(
303 &mut self,
304 f: impl FnOnce(BufferTracker) -> P,
305 ) -> Result<(), Text> {
306 self.parsers.add(self, f)
307 }
308
309 /// Reads from a specific [`Parser`], if it was [added]
310 ///
311 /// This function will block until the `Parser` is ready to be
312 /// read. This usually implies that the `Parser` is done
313 /// processing all the [`Change`]s up to this point. But it could
314 /// also be the case that the `Parser` doesn't really care about
315 /// `Change`s.
316 ///
317 /// While this function is being called, trying to read or write
318 /// to the same [`Parser`] will always return [`None`].
319 ///
320 /// If you want to read in a non blocking way, see
321 /// [`Buffer::try_read_parser`].
322 ///
323 /// [added]: Handle::add_parser
324 /// [`Change`]: crate::text::Change
325 pub fn read_parser<P: Parser, Ret>(&self, read: impl FnOnce(&P) -> Ret) -> Option<Ret> {
326 self.parsers.read_parser(read)
327 }
328
329 /// Tries tor read from a specific [`Parser`], if it was [added]
330 ///
331 /// Unlike [`Buffer::read_parser`], this function will only be
332 /// called when the `Parser` is ready to be read. This may not
333 /// be the case if, for example, it is still processing
334 /// [`Change`]s to the [`Text`]. In that case, the function will
335 /// not be called and [`try_read_parser`] will return [`None`].
336 ///
337 /// While this function is being called, trying to read or write
338 /// to the same [`Parser`] will always return [`None`].
339 ///
340 /// [added]: Handle::add_parser
341 /// [`Change`]: crate::text::Change
342 /// [`try_read_parser`]: Self::try_read_parser
343 pub fn try_read_parser<P: Parser, Ret>(&self, read: impl FnOnce(&P) -> Ret) -> Option<Ret> {
344 self.parsers.try_read_parser(read)
345 }
346
347 /// Writes to a specific [`Parser`], if it was [added]
348 ///
349 /// This function will block until the `Parser` is ready to be
350 /// written to. This usually implies that the `Parser` is done
351 /// processing all the [`Change`]s up to this point. But it could
352 /// also be the case that the `Parser` doesn't really care
353 /// about `Change`s.
354 ///
355 /// While this function is being called, trying to read or write
356 /// to the same `Parser` will always return `None`.
357 ///
358 /// If you want to write in a non blocking way, see
359 /// [`Buffer::try_write_parser`].
360 ///
361 /// [added]: Handle::add_parser
362 /// [`Change`]: crate::text::Change
363 pub fn write_parser<P: Parser, Ret>(&self, write: impl FnOnce(&mut P) -> Ret) -> Option<Ret> {
364 self.parsers.write_parser(write)
365 }
366
367 /// Tries tor read a specific [`Parser`], if it was [added]
368 ///
369 /// Unlike [`Buffer::write_parser`], this function will only be
370 /// called when the `Parser` is ready to be written to. This may
371 /// not be the case if, for example, it is still processing
372 /// [`Change`]s to the [`Text`]. In that case, the function will
373 /// not be called and [`try_write_parser`] will return [`None`].
374 ///
375 /// While this function is being called, trying to read or write
376 /// to the same `Parser` will always return `None`.
377 ///
378 /// [added]: Handle::add_parser
379 /// [`Change`]: crate::text::Change
380 /// [`try_write_parser`]: Self::try_write_parser
381 pub fn try_write_parser<P: Parser, Ret>(
382 &self,
383 write: impl FnOnce(&mut P) -> Ret,
384 ) -> Option<Ret> {
385 self.parsers.try_write_parser(write)
386 }
387
388 /// Prepare this `Buffer` for reloading
389 ///
390 /// This works by creating a new [`Buffer`], which will take
391 /// ownership of a stripped down version of this one's [`Text`]
392 pub(crate) fn prepare_for_reloading(&mut self) -> Self {
393 self.text.prepare_for_reloading();
394 Self {
395 path: self.path.clone(),
396 text: std::mem::take(&mut self.text),
397 printed_lines: Mutex::new(Vec::new()),
398 parsers: Parsers::default(),
399 sync_opts: Arc::default(),
400 layout_order: self.layout_order,
401 opts: PrintOpts::default(),
402 prev_opts: PrintOpts::default(),
403 }
404 }
405}
406
407impl Widget for Buffer {
408 fn update(pa: &mut Pass, handle: &Handle<Self>) {
409 let parsers = std::mem::take(&mut handle.write(pa).parsers);
410
411 let opts = handle.read(pa).opts;
412
413 let (buffer, area) = handle.write_with_area(pa);
414 if buffer.prev_opts != opts {
415 *buffer.sync_opts.lock() = opts;
416 buffer.prev_opts = opts;
417 }
418
419 if let Some(main) = buffer.text().selections().get_main() {
420 area.scroll_around_points(
421 buffer.text(),
422 main.caret().to_two_points_after(),
423 buffer.get_print_opts(),
424 );
425 }
426
427 let start = area.start_points(&buffer.text, opts);
428 let end = area.end_points(&buffer.text, opts);
429
430 parsers.update(pa, handle, start.real.byte()..end.real.byte());
431
432 let buffer = handle.write(pa);
433 buffer.parsers = parsers;
434
435 buffer.text.update_bounds();
436 }
437
438 fn needs_update(&self, _: &Pass) -> bool {
439 self.parsers.needs_update()
440 }
441
442 fn text(&self) -> &Text {
443 &self.text
444 }
445
446 fn text_mut(&mut self) -> &mut Text {
447 &mut self.text
448 }
449
450 fn get_print_opts(&self) -> PrintOpts {
451 self.opts
452 }
453
454 fn print(&self, pa: &Pass, painter: Painter, area: &RwArea) {
455 let opts = self.opts;
456 let start_points = area.start_points(pa, &self.text, opts);
457
458 let mut last_line = area
459 .rev_print_iter(pa, &self.text, start_points, opts)
460 .find_map(|(caret, item)| caret.wrap.then_some(item.line()));
461
462 let mut printed_lines = self.printed_lines.lock();
463 printed_lines.clear();
464
465 let mut has_wrapped = false;
466
467 area.print_with(pa, &self.text, opts, painter, move |caret, item| {
468 has_wrapped |= caret.wrap;
469 if has_wrapped && item.part.is_char() {
470 has_wrapped = false;
471 let line = item.line();
472 let wrapped = last_line.is_some_and(|ll| ll == line);
473 last_line = Some(line);
474 printed_lines.push((line, wrapped));
475 }
476 })
477 }
478}
479
480impl Handle {
481 /// Adds a [`Parser`] to react to [`Text`] [`Change`]s
482 ///
483 /// [`Change`]: crate::text::Change
484 pub fn add_parser<P: Parser>(
485 &self,
486 pa: &mut Pass,
487 f: impl FnOnce(BufferTracker) -> P,
488 ) -> Result<(), Text> {
489 let buffer = self.widget().read(pa);
490 buffer.parsers.add(buffer, f)
491 }
492}
493
494/// Represents the presence or absence of a path
495#[derive(Debug, Clone)]
496pub enum PathKind {
497 /// A [`PathBuf`] that has been defined and points to a real
498 /// buffer
499 SetExists(PathBuf),
500 /// A [`PathBuf`] that has been defined but isn't a real buffer
501 SetAbsent(PathBuf),
502 /// A [`PathBuf`] that has not been defined
503 ///
504 /// The number within represents a specific [`Buffer`], and when
505 /// printed to, for example, the [`StatusLine`], would show up as
506 /// `txt!("[buffer]*scratch buffer*#{id}")`
507 ///
508 /// [`StatusLine`]: https://docs.rs/duat/latest/duat/widgets/struct.StatusLine.html
509 NotSet(usize),
510}
511
512impl PathKind {
513 /// Returns a new unset [`PathBuf`]
514 pub(crate) fn new_unset() -> PathKind {
515 use std::sync::atomic::{AtomicUsize, Ordering};
516 static UNSET_COUNT: AtomicUsize = AtomicUsize::new(1);
517
518 PathKind::NotSet(UNSET_COUNT.fetch_add(1, Ordering::Relaxed))
519 }
520
521 /// Returns a [`PathBuf`] if `self` is [`SetExists`] or
522 /// [`SetAbsent`]
523 ///
524 /// [`SetExists`]: PathKind::SetExists
525 /// [`SetAbsent`]: PathKind::SetAbsent
526 pub fn as_path(&self) -> Option<PathBuf> {
527 match self {
528 PathKind::SetExists(path) | PathKind::SetAbsent(path) => Some(path.clone()),
529 PathKind::NotSet(_) => None,
530 }
531 }
532
533 /// The full path of the buffer.
534 ///
535 /// If there is no set path, returns `"*scratch buffer*#{id}"`.
536 pub fn path(&self) -> String {
537 match self {
538 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
539 path.to_string_lossy().to_string()
540 }
541 PathKind::NotSet(id) => {
542 format!("*scratch buffer*#{id}")
543 }
544 }
545 }
546
547 /// The full path of the buffer.
548 ///
549 /// Returns [`None`] if the path has not been set yet.
550 pub fn path_set(&self) -> Option<String> {
551 match self {
552 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
553 Some(path.to_string_lossy().to_string())
554 }
555 PathKind::NotSet(_) => None,
556 }
557 }
558
559 /// The buffer's name.
560 ///
561 /// If there is no set path, returns `"*scratch buffer #{id}*"`.
562 pub fn name(&self) -> String {
563 match self {
564 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
565 let cur_dir = context::current_dir();
566 if let Ok(path) = path.strip_prefix(cur_dir) {
567 path.to_string_lossy().to_string()
568 } else if let Some(home_dir) = dirs_next::home_dir()
569 && let Ok(path) = path.strip_prefix(home_dir)
570 {
571 Path::new("~").join(path).to_string_lossy().to_string()
572 } else {
573 path.to_string_lossy().to_string()
574 }
575 }
576 PathKind::NotSet(id) => format!("*scratch buffer #{id}*"),
577 }
578 }
579
580 /// The buffer's name.
581 ///
582 /// Returns [`None`] if the path has not been set yet.
583 pub fn name_set(&self) -> Option<String> {
584 match self {
585 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
586 let cur_dir = context::current_dir();
587 Some(if let Ok(path) = path.strip_prefix(cur_dir) {
588 path.to_string_lossy().to_string()
589 } else if let Some(home_dir) = dirs_next::home_dir()
590 && let Ok(path) = path.strip_prefix(home_dir)
591 {
592 Path::new("~").join(path).to_string_lossy().to_string()
593 } else {
594 path.to_string_lossy().to_string()
595 })
596 }
597 PathKind::NotSet(_) => None,
598 }
599 }
600
601 /// A [`Text`] from the full path of this [`PathKind`]
602 ///
603 /// # Formatting
604 ///
605 /// If the buffer's `path` was set:
606 ///
607 /// ```text
608 /// [buffer]{path}
609 /// ```
610 ///
611 /// If the buffer's `path` was not set:
612 ///
613 /// ```text
614 /// [buffer.new.scratch]*scratch buffer #{id}*
615 /// ```
616 pub fn path_txt(&self) -> Text {
617 match self {
618 PathKind::SetExists(path) | PathKind::SetAbsent(path) => txt!("[buffer]{path}"),
619 PathKind::NotSet(id) => txt!("[buffer.new.scratch]*scratch buffer #{id}*"),
620 }
621 }
622
623 /// A [`Text`] from the name of this `PathKind`
624 ///
625 /// The name of a [`Buffer`] widget is the same as the path, but
626 /// it strips away the current directory. If it can't, it will
627 /// try to strip away the home directory, replacing it with
628 /// `"~"`. If that also fails, it will just show the full
629 /// path.
630 ///
631 /// # Formatting
632 ///
633 /// If the buffer's `name` was set:
634 ///
635 /// ```text
636 /// [buffer]{name}
637 /// ```
638 ///
639 /// If the buffer's `name` was not set:
640 ///
641 /// ```text
642 /// [buffer.new.scratch]*scratch buffer #{id}*
643 /// ```
644 pub fn name_txt(&self) -> Text {
645 match self {
646 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
647 let cur_dir = context::current_dir();
648 if let Ok(path) = path.strip_prefix(cur_dir) {
649 txt!("[buffer]{path}")
650 } else if let Some(home_dir) = dirs_next::home_dir()
651 && let Ok(path) = path.strip_prefix(home_dir)
652 {
653 txt!("[buffer]{}", Path::new("~").join(path))
654 } else {
655 txt!("[buffer]{path}")
656 }
657 }
658 PathKind::NotSet(id) => txt!("[buffer.new.scratch]*scratch buffer #{id}*"),
659 }
660 }
661}
662
663impl<P: AsRef<Path>> From<P> for PathKind {
664 fn from(value: P) -> Self {
665 let path = value.as_ref();
666 if let Ok(true) = path.try_exists() {
667 PathKind::SetExists(path.into())
668 } else {
669 PathKind::SetAbsent(path.into())
670 }
671 }
672}
673
674impl PartialEq for PathKind {
675 fn eq(&self, other: &Self) -> bool {
676 match (self, other) {
677 (
678 Self::SetExists(l0) | Self::SetAbsent(l0),
679 Self::SetExists(r0) | Self::SetAbsent(r0),
680 ) => l0 == r0,
681 (Self::NotSet(l0), Self::NotSet(r0)) => l0 == r0,
682 _ => false,
683 }
684 }
685}
686
687impl Eq for PathKind {}
688
689impl From<PathKind> for BuilderPart {
690 fn from(value: PathKind) -> Self {
691 BuilderPart::Text(value.name_txt())
692 }
693}