Terminal Markdown previewer — GUI-like experience.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: refacto modal to popup

RivoLink d3ba177d 0019a8e5

+37 -30
+2 -2
ARCHITECTURE.md
··· 25 25 - `src/render/` 26 26 - `mod.rs` — TUI layout orchestration with `ratatui` 27 27 - `content.rs` — main content panel and status bar rendering 28 - - `modal.rs` — modal rendering for help, file picker, theme picker, editor picker, picker loading/failed states 28 + - `popup.rs` — popup rendering for help, file picker, theme picker, editor picker, picker loading/failed states 29 29 - `status.rs` — status bar construction (brand, filename, search, watch, shortcuts, percentage) 30 30 - `toc.rs` — TOC sidebar rendering 31 31 ··· 82 82 ## Application modes 83 83 84 84 - **Initial mode** (`!app.has_content()`): no file loaded, picker is the main view. Quit shortcuts exit the app. 85 - - **Preview mode** (`app.has_content()`): file loaded via argument, stdin, or picker selection. Quit shortcuts in pickers close the modal and return to the preview. 85 + - **Preview mode** (`app.has_content()`): file loaded via argument, stdin, or picker selection. Quit shortcuts in pickers close the popup and return to the preview. 86 86 87 87 ## Picker lifecycle 88 88
+1 -1
README.md
··· 154 154 - ✅ Fuzzy Markdown picker when launched without a file, or anytime with `Ctrl+P` 155 155 - ✅ Classic directory browser picker with `leaf --picker`, or anytime with `Shift+P` 156 156 - ✅ Theme picker with runtime preview 157 - - ✅ Help modal with in-app shortcuts 157 + - ✅ Help popup with in-app shortcuts 158 158 159 159 ## Typical AI Workflow 160 160
+10
src/app/mod.rs
··· 472 472 self.path_popup_open 473 473 } 474 474 475 + pub(crate) fn is_popup_open(&self) -> bool { 476 + self.help_open 477 + || self.path_popup_open 478 + || self.file_picker.open 479 + || self.theme_picker.open 480 + || self.editor_picker.open 481 + || self.is_picker_loading() 482 + || self.is_picker_load_failed() 483 + } 484 + 475 485 pub(crate) fn clear_reload_flash(&mut self) { 476 486 self.reload_flash = None; 477 487 }
+8 -8
src/render/mod.rs
··· 1 1 mod content; 2 - mod modal; 2 + mod popup; 3 3 mod status; 4 4 mod toc; 5 5 ··· 10 10 }; 11 11 12 12 #[cfg(test)] 13 - pub(crate) use modal::wrap_path_lines; 13 + pub(crate) use popup::wrap_path_lines; 14 14 pub(crate) use status::build_status_bar; 15 15 pub(crate) use toc::{build_toc_line_with_index, toc_header_line}; 16 16 ··· 44 44 content::render_status_bar(f, app, root[1], viewport_height); 45 45 46 46 if app.is_help_open() { 47 - modal::render_help_popup(f); 47 + popup::render_help_popup(f, app); 48 48 } else if app.is_picker_loading() || app.is_picker_load_failed() { 49 - modal::render_picker_loading(f, app); 49 + popup::render_picker_loading_popup(f, app); 50 50 } else if app.is_file_picker_open() { 51 - modal::render_file_picker(f, app); 51 + popup::render_file_popup(f, app); 52 52 } else if app.is_theme_picker_open() { 53 - modal::render_theme_picker(f, app); 53 + popup::render_theme_popup(f, app); 54 54 } else if app.is_editor_picker_open() { 55 - modal::render_editor_picker(f, app); 55 + popup::render_editor_popup(f, app); 56 56 } else if app.is_path_popup_open() { 57 - modal::render_path_popup(f, app); 57 + popup::render_path_popup(f, app); 58 58 } 59 59 } 60 60
+15 -15
src/render/modal.rs src/render/popup.rs
··· 38 38 39 39 const PICKER_FAILED_FOOTER_PREVIEW: &[&str] = &["esc close", "enter close", "ctrl+c close"]; 40 40 41 - fn picker_footer(has_content: bool, is_fuzzy: bool, is_failed: bool) -> &'static [&'static str] { 41 + fn popup_footer(has_content: bool, is_fuzzy: bool, is_failed: bool) -> &'static [&'static str] { 42 42 if has_content { 43 43 if is_failed { 44 44 PICKER_FAILED_FOOTER_PREVIEW ··· 56 56 } 57 57 } 58 58 59 - fn modal_footer_line(segments: &[&'static str], bg: Color) -> Line<'static> { 59 + fn popup_footer_line(segments: &[&'static str], bg: Color) -> Line<'static> { 60 60 let theme = app_theme(); 61 61 let shortcut_style = Style::default().fg(theme.ui.status_shortcut_fg).bg(bg); 62 62 let separator_style = Style::default().fg(theme.ui.status_separator).bg(bg); ··· 70 70 Line::from(spans) 71 71 } 72 72 73 - pub(super) fn render_help_popup(f: &mut Frame) { 73 + pub(super) fn render_help_popup(f: &mut Frame, _app: &App) { 74 74 let theme = app_theme(); 75 75 let area = centered_rect(54, 22, f.area()); 76 76 let section_style = Style::default() ··· 164 164 Span::styled("toggle toc", text_style), 165 165 ]), 166 166 Line::from(""), 167 - modal_footer_line(&["esc close", "? close"], theme.ui.toc_bg), 167 + popup_footer_line(&["esc close", "? close"], theme.ui.toc_bg), 168 168 ]; 169 169 170 170 f.render_widget(Clear, area); ··· 181 181 ); 182 182 } 183 183 184 - pub(super) fn render_theme_picker(f: &mut Frame, app: &App) { 184 + pub(super) fn render_theme_popup(f: &mut Frame, app: &App) { 185 185 let theme = app_theme(); 186 186 let area = centered_rect(43, 10, f.area()); 187 187 let active = app.theme_picker_reference_preset(); ··· 241 241 ])); 242 242 } 243 243 lines.push(Line::from("")); 244 - lines.push(modal_footer_line( 244 + lines.push(popup_footer_line( 245 245 &["↑/↓ preview", "enter keep", "esc restore"], 246 246 theme.ui.toc_bg, 247 247 )); ··· 260 260 ); 261 261 } 262 262 263 - pub(super) fn render_file_picker(f: &mut Frame, app: &App) { 263 + pub(super) fn render_file_popup(f: &mut Frame, app: &App) { 264 264 let theme = app_theme(); 265 265 let area = centered_rect(78, 20, f.area()); 266 266 let title_style = Style::default() ··· 406 406 lines.push(Line::from("")); 407 407 } 408 408 409 - lines.push(modal_footer_line( 410 - picker_footer(app.has_content(), app.is_fuzzy_file_picker(), false), 409 + lines.push(popup_footer_line( 410 + popup_footer(app.has_content(), app.is_fuzzy_file_picker(), false), 411 411 theme.ui.toc_bg, 412 412 )); 413 413 ··· 425 425 ); 426 426 } 427 427 428 - pub(super) fn render_picker_loading(f: &mut Frame, app: &App) { 428 + pub(super) fn render_picker_loading_popup(f: &mut Frame, app: &App) { 429 429 let theme = app_theme(); 430 430 let area = centered_rect(78, 20, f.area()); 431 431 let title_style = Style::default() ··· 481 481 } 482 482 483 483 lines.push(Line::from("")); 484 - lines.push(modal_footer_line( 485 - picker_footer(app.has_content(), is_fuzzy, is_failed), 484 + lines.push(popup_footer_line( 485 + popup_footer(app.has_content(), is_fuzzy, is_failed), 486 486 theme.ui.toc_bg, 487 487 )); 488 488 ··· 587 587 spans 588 588 } 589 589 590 - pub(super) fn render_editor_picker(f: &mut Frame, app: &App) { 590 + pub(super) fn render_editor_popup(f: &mut Frame, app: &App) { 591 591 let theme = app_theme(); 592 592 let entries = app.editor_picker_entries(); 593 593 let selected = app.editor_picker_index(); ··· 677 677 } 678 678 679 679 lines.push(Line::from("")); 680 - lines.push(modal_footer_line( 680 + lines.push(popup_footer_line( 681 681 &["↑/↓ move", "enter confirm", "esc cancel"], 682 682 theme.ui.toc_bg, 683 683 )); ··· 768 768 lines.extend(rel_lines); 769 769 lines.extend(abs_lines); 770 770 lines.push(Line::from("")); 771 - lines.push(modal_footer_line( 771 + lines.push(popup_footer_line( 772 772 &["enter close", "esc close"], 773 773 theme.ui.toc_bg, 774 774 ));
+1 -4
src/runtime.rs
··· 427 427 Event::Mouse(mouse) => { 428 428 let prev_pos = app.mouse_position; 429 429 app.mouse_position = (mouse.column, mouse.row); 430 - let state_changed = if app.is_file_picker_open() 431 - || app.is_theme_picker_open() 432 - || app.is_path_popup_open() 433 - { 430 + let state_changed = if app.is_popup_open() { 434 431 if matches!(mouse.kind, MouseEventKind::Up(..)) { 435 432 app.scrollbar_dragging = false; 436 433 }