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}