1use std::collections::{HashMap, HashSet, VecDeque};
7use std::path::PathBuf;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::{Arc, mpsc};
10use std::time::Instant;
11
12use ratatui::style::Style;
13use ratatui::widgets::ListState;
14
15use crate::engine::{
16 cache,
17 db_ops::{DeltaType, UblxDbCategory},
18 viewer_async::ViewerAsyncState,
19};
20use crate::integrations::{ZahirFT, file_type_from_metadata_name};
21use crate::render::viewers::pdf_preview::PDFPrefetch;
22use crate::utils::{ClipboardCopyCommand, ToastSlot};
23
24use super::style;
25
26pub use crate::engine::db_ops::SnapshotTuiRow as TuiRow;
28
29pub const CATEGORY_DIRECTORY: &str = "Directory";
31
32#[derive(Debug, Default)]
34pub struct ContentMarqueeState {
35 pub offset: usize,
36 pub last_advance: Option<Instant>,
37 pub anchor: Option<(usize, String)>,
38}
39
40impl ContentMarqueeState {
41 pub fn reset(&mut self) {
42 self.offset = 0;
43 self.last_advance = None;
44 self.anchor = None;
45 }
46}
47
48#[derive(Default)]
50pub struct PanelState {
51 pub category_state: ListState,
52 pub content_state: ListState,
53 pub focus: PanelFocus,
54 pub preview_scroll: u16,
55 pub prev_preview_key: Option<(usize, Option<usize>)>,
56 pub highlight_style: Style,
57 pub content_sort: ContentSort,
58 pub sort_anchor_path: Option<String>,
60 pub right_pane_text_w: Option<u16>,
62 pub category_marquee: ContentMarqueeState,
64 pub content_marquee: ContentMarqueeState,
66 pub typed_column_tables: crate::config::ColumnStatsDisplay,
68}
69
70impl PanelState {
71 fn new() -> Self {
72 let mut p = Self {
73 highlight_style: style::list_highlight(),
74 ..Default::default()
75 };
76 p.category_state.select(Some(0));
77 p.content_state.select(Some(0));
78 p
79 }
80}
81
82#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
84pub enum SortDirection {
85 #[default]
86 Asc,
87 Desc,
88}
89
90impl SortDirection {
91 #[must_use]
92 pub fn next(self) -> Self {
93 match self {
94 Self::Asc => Self::Desc,
95 Self::Desc => Self::Asc,
96 }
97 }
98}
99
100#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
102pub enum SnapshotSortKey {
103 #[default]
104 Name,
105 Size,
106 Mod,
107}
108
109impl SnapshotSortKey {
110 #[must_use]
111 pub fn next(self) -> Self {
112 match self {
113 Self::Name => Self::Size,
114 Self::Size => Self::Mod,
115 Self::Mod => Self::Name,
116 }
117 }
118}
119
120#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
122pub struct ContentSort {
123 pub snapshot_key: SnapshotSortKey,
124 pub snapshot_dir: SortDirection,
125 pub delta_dir: SortDirection,
126}
127
128impl ContentSort {
129 #[must_use]
130 pub fn cycle_for_mode(self, main_mode: MainMode) -> Self {
131 match main_mode {
132 MainMode::Snapshot | MainMode::Duplicates => {
133 if self.snapshot_dir == SortDirection::Asc {
134 Self {
135 snapshot_dir: SortDirection::Desc,
136 ..self
137 }
138 } else {
139 Self {
140 snapshot_key: self.snapshot_key.next(),
141 snapshot_dir: SortDirection::Asc,
142 ..self
143 }
144 }
145 }
146 MainMode::Delta => Self {
147 delta_dir: self.delta_dir.next(),
148 ..self
149 },
150 MainMode::Lenses | MainMode::Settings => self,
151 }
152 }
153}
154
155#[derive(Default)]
157pub struct SearchState {
158 pub query: String,
159 pub active: bool,
160}
161
162#[derive(Default)]
164pub struct ViewerFindState {
165 pub query: String,
166 pub active: bool,
168 pub committed: bool,
170 pub ranges: Vec<(usize, usize)>,
171 pub current: usize,
172 pub last_sync_token: Option<u64>,
174 pub pending_scroll: bool,
176}
177
178#[derive(Default)]
180pub struct ThemeState {
181 pub selector_visible: bool,
182 pub selector_index: usize,
183 pub before_selector: Option<String>,
184 pub override_name: Option<String>,
185}
186
187#[derive(Default)]
189pub struct ToastState {
190 pub slots: Vec<ToastSlot>,
191 pub consumed_per_operation: HashMap<String, usize>,
192}
193
194#[derive(Default)]
196pub struct OpenMenuState {
197 pub visible: bool,
198 pub path: Option<String>,
199 pub can_terminal: bool,
200 pub selected_index: usize,
201}
202
203#[derive(Default)]
205pub struct LensMenuState {
206 pub visible: bool,
207 pub paths: Vec<String>,
209 pub exclude_lens_name: Option<String>,
211 pub selected_index: usize,
212 pub name_input: Option<String>,
213}
214
215#[derive(Default)]
217pub struct QAMenuState {
218 pub visible: bool,
219 pub selected_index: usize,
220 pub kind: Option<SpaceMenuKind>,
221}
222
223#[derive(Default)]
225pub struct EnhancePolicyMenuState {
226 pub visible: bool,
227 pub path: Option<String>,
228 pub selected_index: usize,
229}
230
231#[derive(Default)]
233pub struct LensConfirmState {
234 pub rename_input: Option<(String, String)>,
235 pub delete_visible: bool,
236 pub delete_lens_name: Option<String>,
237 pub delete_selected: usize,
238}
239
240#[derive(Default)]
242pub struct FileDeleteConfirmState {
243 pub visible: bool,
244 pub rel_path: Option<String>,
245 pub bulk_paths: Option<Vec<String>>,
247 pub selected_index: usize,
248}
249
250#[derive(Debug, Default)]
252pub struct MultiselectState {
253 pub active: bool,
254 pub selected: HashSet<String>,
255 pub bulk_menu_visible: bool,
256 pub bulk_menu_selected: usize,
257 pub bulk_menu_zahir_row: bool,
259}
260
261impl MultiselectState {
262 pub fn clear(&mut self) {
263 self.active = false;
264 self.selected.clear();
265 self.bulk_menu_visible = false;
266 self.bulk_menu_selected = 0;
267 self.bulk_menu_zahir_row = false;
268 }
269}
270
271#[derive(Clone, Debug, Default)]
273pub struct CtrlChordState {
274 pub pending: bool,
275 pub menu_visible: bool,
276 pub started: Option<std::time::Instant>,
277}
278
279impl CtrlChordState {
280 #[must_use]
281 pub fn is_active(&self) -> bool {
282 self.pending || self.menu_visible
283 }
284}
285
286#[derive(Default)]
288pub struct UblxSwitchPickerState {
289 pub visible: bool,
290 pub selected_index: usize,
291 pub roots: Vec<PathBuf>,
292}
293
294#[derive(Default)]
296pub struct ViewerChrome {
297 pub help_visible: bool,
298 pub help_tab: u8,
300 pub viewer_fullscreen: bool,
301 pub ctrl_chord: CtrlChordState,
302 pub ublx_switch: UblxSwitchPickerState,
303}
304
305#[derive(Debug, Clone)]
307pub struct StartupPromptState {
308 pub phase: StartupPromptPhase,
309}
310
311#[derive(Debug, Clone)]
312pub enum StartupPromptPhase {
313 RootChoice {
315 selected_index: usize,
316 roots: Vec<PathBuf>,
317 },
318 PreviousSettings { selected_index: usize },
321 Enhance { selected_index: usize },
323}
324
325#[derive(Default)]
327pub struct BackgroundSnapshot {
328 pub requested: bool,
329 pub poll_deadline: Option<std::time::Instant>,
330 pub done_received: bool,
331 pub defer_snapshot_after_current: bool,
333}
334
335#[derive(Default)]
337pub struct DuplicateLoadGate {
338 pub requested: bool,
339}
340
341#[derive(Default)]
343pub struct ZahirExportGate {
344 pub requested: bool,
345}
346
347#[derive(Default)]
349pub struct LensExportGate {
350 pub requested: bool,
351}
352
353#[derive(Clone, Copy, Debug)]
355pub struct SessionTickFlags {
356 pub first_tick: bool,
357 pub refresh_terminal_after_editor: bool,
358}
359
360impl Default for SessionTickFlags {
361 fn default() -> Self {
362 Self {
363 first_tick: true,
364 refresh_terminal_after_editor: false,
365 }
366 }
367}
368
369#[derive(Clone, Copy, Debug, Default)]
371pub struct SessionReloadFlags {
372 pub snapshot_rows: bool,
374 pub force_full_enhance_toast_shown: bool,
376 pub duplicate_groups: bool,
378}
379
380#[derive(Default)]
382pub struct SessionFlow {
383 pub tick: SessionTickFlags,
384 pub reload: SessionReloadFlags,
385 pub pending_switch_to: Option<PathBuf>,
387}
388
389pub struct PDF {
390 pub page: u32,
391 pub page_count: Option<u32>,
392 pub for_path: Option<PathBuf>,
393 pub page_count_rx: Option<mpsc::Receiver<Result<u32, String>>>,
394 pub prefetch_cancel: Arc<AtomicU64>,
395 pub prefetch_earliest: Option<Instant>,
396 pub prefetch_rx: Option<mpsc::Receiver<(String, Result<image::DynamicImage, String>)>>,
397}
398
399impl Default for PDF {
400 fn default() -> Self {
401 Self {
402 page: 1,
403 page_count: None,
404 for_path: None,
405 page_count_rx: None,
406 prefetch_cancel: Arc::new(AtomicU64::new(0)),
407 prefetch_earliest: None,
408 prefetch_rx: None,
409 }
410 }
411}
412
413#[derive(Default)]
415pub struct ViewerImageState {
416 pub protocol: Option<ratatui_image::protocol::StatefulProtocol>,
417 pub picker: Option<ratatui_image::picker::Picker>,
418 pub key: Option<String>,
420 pub decode_rx: Option<mpsc::Receiver<Result<image::DynamicImage, String>>>,
422 pub err: Option<String>,
423 pub image_lru: VecDeque<(String, ratatui_image::protocol::StatefulProtocol)>,
425 pub pdf: PDF,
427}
428
429impl ViewerImageState {
430 pub const LRU_EXTRA_SLOTS: usize = 4;
432 pub const LRU_CAP: usize = PDFPrefetch::MAX_EXTRA_PAGES as usize + Self::LRU_EXTRA_SLOTS;
433
434 pub fn push_lru(&mut self, path: String, proto: ratatui_image::protocol::StatefulProtocol) {
436 while self.image_lru.len() >= Self::LRU_CAP {
437 self.image_lru.pop_front();
438 }
439 self.image_lru.push_back((path, proto));
440 }
441
442 pub fn take_from_lru(
444 &mut self,
445 path: &str,
446 ) -> Option<ratatui_image::protocol::StatefulProtocol> {
447 let pos = self.image_lru.iter().position(|(k, _)| k == path)?;
448 self.image_lru.remove(pos).map(|(_, proto)| proto)
449 }
450
451 pub fn remove_lru_key(&mut self, key: &str) {
453 if let Some(pos) = self.image_lru.iter().position(|(k, _)| k == key) {
454 self.image_lru.remove(pos);
455 }
456 }
457
458 pub fn clear(&mut self) {
462 self.pdf.prefetch_cancel.fetch_add(1, Ordering::SeqCst);
463 self.pdf.prefetch_rx = None;
464 self.pdf.prefetch_earliest = None;
465 self.decode_rx = None;
466 self.pdf.page_count_rx = None;
467 self.err = None;
468 let k = self.key.take();
469 let p = self.protocol.take();
470 if let (Some(k), Some(p)) = (k, p) {
471 self.push_lru(k, p);
472 }
473 self.pdf.for_path = None;
474 self.pdf.page = 1;
475 self.pdf.page_count = None;
476 }
477}
478
479#[derive(Debug, Clone)]
481pub struct ViewerDiskContentCache {
482 pub rel_path: String,
483 pub category: String,
485 pub file_len: u64,
486 pub modified: Option<std::time::SystemTime>,
487 pub viewer_str: Option<String>,
488 pub embedded_cover_raster: Option<Vec<u8>>,
489 pub viewer_can_open: bool,
490}
491
492impl ViewerDiskContentCache {
493 #[must_use]
494 pub fn matches(&self, path: &str, category: &str, meta: &std::fs::Metadata) -> bool {
495 self.rel_path == path
496 && self.category == category
497 && self.file_len == meta.len()
498 && self.modified == meta.modified().ok()
499 }
500}
501
502#[derive(Default)]
503pub struct RightPaneAsync {
504 pub generation: u64,
505 pub last_spawn_path: String,
506 pub displayed: RightPaneContent,
507 pub rx: Option<tokio::sync::mpsc::UnboundedReceiver<RightPaneAsyncReady>>,
508}
509
510pub struct UblxState {
512 pub main_mode: MainMode,
513 pub right_pane_mode: RightPaneMode,
514 pub panels: PanelState,
515 pub search: SearchState,
516 pub viewer_find: ViewerFindState,
517 pub theme: ThemeState,
518 pub toasts: ToastState,
519 pub open_menu: OpenMenuState,
520 pub lens_menu: LensMenuState,
521 pub qa_menu: QAMenuState,
522 pub enhance_policy_menu: EnhancePolicyMenuState,
523 pub lens_confirm: LensConfirmState,
524 pub file_rename_input: Option<(String, String)>,
526 pub file_delete_confirm: FileDeleteConfirmState,
527 pub multiselect: MultiselectState,
528 pub chrome: ViewerChrome,
529 pub cached_tree: Option<(String, String)>,
530 pub viewer_disk_cache: Option<ViewerDiskContentCache>,
532 pub viewer_text_cache: Option<cache::ViewerTextCacheEntry>,
534 pub viewer_preview_source: Option<(String, cache::ViewerContentIdentity)>,
536 pub csv_table_text_lru:
538 cache::LruCache<cache::ViewerTableCacheKey, cache::ViewerTextCacheEntry>,
539 pub viewer_async: ViewerAsyncState,
541 pub viewer_image: ViewerImageState,
543 pub last_key_for_double: Option<char>,
544 pub snapshot_bg: BackgroundSnapshot,
545 pub duplicate_load: DuplicateLoadGate,
546 pub zahir_export_load: ZahirExportGate,
547 pub lens_export_load: LensExportGate,
548 pub duplicate_ignored_paths: HashSet<String>,
550 pub config_written_by_us_at: Option<std::time::Instant>,
551 pub session: SessionFlow,
552 pub clipboard_copy: Option<ClipboardCopyCommand>,
554 pub startup_prompt: Option<StartupPromptState>,
556 pub settings: SettingsPaneState,
557 pub right_pane_async: RightPaneAsync,
558}
559
560impl Default for UblxState {
561 fn default() -> Self {
562 Self::new()
563 }
564}
565
566impl UblxState {
567 #[must_use]
568 pub fn new() -> Self {
569 Self {
570 main_mode: MainMode::default(),
571 right_pane_mode: RightPaneMode::default(),
572 panels: PanelState::new(),
573 search: SearchState::default(),
574 viewer_find: ViewerFindState::default(),
575 theme: ThemeState::default(),
576 toasts: ToastState::default(),
577 open_menu: OpenMenuState::default(),
578 lens_menu: LensMenuState::default(),
579 qa_menu: QAMenuState::default(),
580 enhance_policy_menu: EnhancePolicyMenuState::default(),
581 lens_confirm: LensConfirmState::default(),
582 file_rename_input: None,
583 file_delete_confirm: FileDeleteConfirmState::default(),
584 multiselect: MultiselectState::default(),
585 chrome: ViewerChrome::default(),
586 cached_tree: None,
587 viewer_disk_cache: None,
588 viewer_text_cache: None,
589 viewer_preview_source: None,
590 csv_table_text_lru: cache::LruCache::default(),
591 viewer_async: ViewerAsyncState::default(),
592 viewer_image: ViewerImageState::default(),
593 last_key_for_double: None,
594 snapshot_bg: BackgroundSnapshot::default(),
595 duplicate_load: DuplicateLoadGate::default(),
596 zahir_export_load: ZahirExportGate::default(),
597 lens_export_load: LensExportGate::default(),
598 duplicate_ignored_paths: HashSet::new(),
599 config_written_by_us_at: None,
600 session: SessionFlow::default(),
601 clipboard_copy: ClipboardCopyCommand::detect(),
602 startup_prompt: None,
603 settings: SettingsPaneState::default(),
604 right_pane_async: RightPaneAsync::default(),
605 }
606 }
607
608 pub fn close_open_menu(&mut self) {
610 self.open_menu.visible = false;
611 self.open_menu.path = None;
612 self.open_menu.can_terminal = false;
613 }
614
615 pub fn open_open_menu(&mut self, path: String, can_open_in_terminal: bool) {
617 self.open_menu.visible = true;
618 self.open_menu.path = Some(path);
619 self.open_menu.can_terminal = can_open_in_terminal;
620 self.open_menu.selected_index = 0;
621 }
622
623 pub fn close_lens_menu(&mut self) {
625 self.lens_menu.visible = false;
626 self.lens_menu.paths.clear();
627 self.lens_menu.selected_index = 0;
628 }
629
630 pub fn close_qa_menu(&mut self) {
632 self.qa_menu.visible = false;
633 self.qa_menu.selected_index = 0;
634 self.qa_menu.kind = None;
635 }
636
637 pub fn close_enhance_policy_menu(&mut self) {
638 self.enhance_policy_menu.visible = false;
639 self.enhance_policy_menu.path = None;
640 self.enhance_policy_menu.selected_index = 0;
641 }
642
643 pub fn close_lens_delete_confirm(&mut self) {
645 self.lens_confirm.delete_visible = false;
646 self.lens_confirm.delete_lens_name = None;
647 self.lens_confirm.delete_selected = 0;
648 }
649
650 pub fn open_lens_menu(&mut self, paths: Vec<String>, exclude_current_lens: Option<String>) {
653 if paths.is_empty() {
654 return;
655 }
656 self.lens_menu.visible = true;
657 self.lens_menu.paths = paths;
658 self.lens_menu.exclude_lens_name = exclude_current_lens;
659 self.lens_menu.selected_index = 0;
660 }
661
662 pub fn open_qa_menu(&mut self, kind: SpaceMenuKind) {
664 self.qa_menu.visible = true;
665 self.qa_menu.selected_index = 0;
666 self.qa_menu.kind = Some(kind);
667 }
668
669 pub fn open_lens_delete_confirm(&mut self, lens_name: String) {
671 self.lens_confirm.delete_visible = true;
672 self.lens_confirm.delete_lens_name = Some(lens_name);
673 self.lens_confirm.delete_selected = 0;
674 }
675
676 pub fn open_file_rename_input(&mut self, rel_path: String) {
678 let base = std::path::Path::new(&rel_path)
679 .file_name()
680 .and_then(|s| s.to_str())
681 .unwrap_or("")
682 .to_string();
683 self.file_rename_input = Some((rel_path, base));
684 }
685
686 pub fn close_file_delete_confirm(&mut self) {
687 self.file_delete_confirm.visible = false;
688 self.file_delete_confirm.rel_path = None;
689 self.file_delete_confirm.bulk_paths = None;
690 self.file_delete_confirm.selected_index = 0;
691 }
692
693 pub fn open_file_delete_confirm(&mut self, rel_path: String) {
694 self.file_delete_confirm.visible = true;
695 self.file_delete_confirm.rel_path = Some(rel_path);
696 self.file_delete_confirm.bulk_paths = None;
697 self.file_delete_confirm.selected_index = 0;
698 }
699
700 pub fn open_file_delete_confirm_bulk(&mut self, paths: Vec<String>) {
701 self.file_delete_confirm.visible = true;
702 self.file_delete_confirm.rel_path = None;
703 self.file_delete_confirm.bulk_paths = Some(paths);
704 self.file_delete_confirm.selected_index = 0;
705 }
706}
707
708#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
710pub enum SettingsConfigScope {
711 #[default]
712 Global,
713 Local,
714}
715
716#[derive(Clone, Debug)]
718pub struct SettingsPaneState {
719 pub scope: SettingsConfigScope,
720 pub left_cursor: usize,
722 pub right_scroll: u16,
723 pub layout_unlocked: bool,
724 pub layout_left_buf: String,
725 pub layout_mid_buf: String,
726 pub layout_right_buf: String,
727 pub opacity_unlocked: bool,
728 pub opacity_buf: String,
729 pub editing_path: Option<std::path::PathBuf>,
731}
732
733impl Default for SettingsPaneState {
734 fn default() -> Self {
735 Self {
736 scope: SettingsConfigScope::Global,
737 left_cursor: 0,
738 right_scroll: 0,
739 layout_unlocked: false,
740 layout_left_buf: String::new(),
741 layout_mid_buf: String::new(),
742 layout_right_buf: String::new(),
743 opacity_unlocked: false,
744 opacity_buf: String::new(),
745 editing_path: None,
746 }
747 }
748}
749
750#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
752pub enum MainMode {
753 #[default]
754 Snapshot,
755 Delta,
756 Settings,
758 Duplicates,
759 Lenses,
760}
761
762impl MainMode {
763 #[must_use]
765 pub fn next(self, has_duplicates: bool, has_lenses: bool) -> MainMode {
766 match self {
767 MainMode::Snapshot => {
768 if has_lenses {
769 MainMode::Lenses
770 } else {
771 MainMode::Delta
772 }
773 }
774 MainMode::Lenses => MainMode::Delta,
775 MainMode::Delta => {
776 if has_duplicates {
777 MainMode::Duplicates
778 } else {
779 MainMode::Settings
780 }
781 }
782 MainMode::Duplicates => MainMode::Settings,
783 MainMode::Settings => MainMode::Snapshot,
784 }
785 }
786}
787
788#[derive(Clone, Copy, Default, PartialEq)]
790pub enum PanelFocus {
791 #[default]
792 Categories,
793 Contents,
794}
795
796#[derive(Clone, Debug)]
798pub enum SpaceMenuKind {
799 FileActions {
803 path: String,
804 can_open_in_terminal: bool,
805 show_enhance_directory_policy: bool,
807 show_enhance_zahir: bool,
809 show_copy_zahir_json: bool,
811 },
812 LensPanelActions { lens_name: String },
814 DuplicateMemberActions { path: String },
816}
817
818pub struct SectionedPreview {
820 pub templates: String,
821 pub metadata: Option<String>,
822 pub writing: Option<String>,
823}
824
825#[derive(Clone)]
827pub enum ViewContents {
828 SnapshotIndices(Vec<usize>),
830 DeltaRows(Vec<TuiRow>),
832}
833
834pub struct ViewData {
837 pub filtered_categories: Vec<String>,
838 pub contents: ViewContents,
839 pub category_list_len: usize,
840 pub content_len: usize,
841}
842
843impl ViewData {
844 #[must_use]
846 pub fn row_at<'a>(&'a self, i: usize, all_rows: Option<&'a [TuiRow]>) -> Option<&'a TuiRow> {
847 match &self.contents {
848 ViewContents::SnapshotIndices(indices) => indices
849 .get(i)
850 .and_then(|&pos| all_rows.and_then(|r| r.get(pos))),
851 ViewContents::DeltaRows(rows) => rows.get(i),
852 }
853 }
854
855 #[must_use]
857 pub fn iter_contents<'a>(
858 &'a self,
859 all_rows: Option<&'a [TuiRow]>,
860 ) -> Box<dyn Iterator<Item = &'a TuiRow> + 'a> {
861 match &self.contents {
862 ViewContents::SnapshotIndices(indices) => {
863 let iter = indices
864 .iter()
865 .filter_map(move |&pos| all_rows.and_then(|r| r.get(pos)));
866 Box::new(iter)
867 }
868 ViewContents::DeltaRows(rows) => Box::new(rows.iter()),
869 }
870 }
871}
872
873pub type DeltaRow = (i64, String);
875
876pub struct DeltaViewData {
878 pub overview_text: String,
879 pub added_rows: Vec<DeltaRow>,
880 pub mod_rows: Vec<DeltaRow>,
881 pub removed_rows: Vec<DeltaRow>,
882}
883
884impl DeltaViewData {
885 #[must_use]
887 pub fn rows_by_index(&self, idx: usize) -> &[DeltaRow] {
888 match DeltaType::from_index(idx) {
889 DeltaType::Added => &self.added_rows,
890 DeltaType::Mod => &self.mod_rows,
891 DeltaType::Removed => &self.removed_rows,
892 }
893 }
894}
895
896#[derive(Debug)]
898pub struct RightPaneAsyncReady {
899 pub generation: u64,
900 pub path: String,
901 pub content: RightPaneContent,
902 pub disk_cache: Option<ViewerDiskContentCache>,
903}
904
905#[derive(Clone, Debug, Default)]
906pub struct SnapshotEntryMeta {
907 pub path: Option<String>,
908 pub category: Option<String>,
909 pub size: Option<u64>,
910 pub mtime_ns: Option<i64>,
911 pub has_zahir_json: bool,
912}
913
914#[derive(Clone, Debug, Default)]
915pub struct RightPaneContentDerived {
916 pub abs_path: Option<PathBuf>,
917 pub can_open: bool,
918 pub offer_enhance_zahir: bool,
919 pub offer_enhance_directory_policy: bool,
920 pub embedded_cover_raster: Option<Vec<u8>>,
921}
922
923#[derive(Default, Clone, Debug)]
925pub struct RightPaneContent {
926 pub templates: String,
927 pub metadata: Option<String>,
928 pub writing: Option<String>,
929 pub viewer: Option<Arc<str>>,
931 pub viewer_directory_policy_line: Option<String>,
933 pub snap_meta: SnapshotEntryMeta,
934 pub derived: RightPaneContentDerived,
935}
936
937impl RightPaneContent {
938 #[must_use]
940 pub fn empty() -> Self {
941 Self::default()
942 }
943
944 #[must_use]
946 pub fn zahir_file_type(&self) -> Option<ZahirFT> {
947 file_type_from_metadata_name(self.snap_meta.category.as_deref().unwrap_or(""))
948 }
949
950 #[must_use]
952 pub fn ublx_db_category(&self) -> UblxDbCategory {
953 UblxDbCategory::from_snapshot_category(self.snap_meta.category.as_deref().unwrap_or(""))
954 }
955}
956
957#[derive(Clone, Copy, Default, PartialEq, Eq)]
958pub enum RightPaneMode {
959 #[default]
960 Viewer,
961 Templates,
962 Metadata,
963 Writing,
964}