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