Terminal Markdown previewer — GUI-like experience.
1
fork

Configure Feed

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

Merge pull request #21 from RivoLink/fix/minor-ux-bugs

fix: minor ux bugs

authored by

Rivo Link and committed by
GitHub
d7112b29 1439b891

+42 -19
+17 -8
README.md
··· 71 71 ## Usage 72 72 73 73 ```bash 74 - # Preview a file 74 + # Open a Markdown file 75 75 leaf TESTING.md 76 76 77 77 # Watch mode — reloads automatically on save 78 78 leaf --watch TESTING.md 79 79 leaf -w TESTING.md 80 80 81 + # Open the fuzzy Markdown picker 82 + leaf 83 + 84 + # Open the classic directory browser picker 85 + leaf --picker 86 + 87 + # Open the fuzzy Markdown picker, then watch the selected file 88 + leaf -w 89 + 90 + # Open the classic directory browser picker, then watch the selected file 91 + leaf -w --picker 92 + 81 93 # Open a dash-prefixed filename 82 94 leaf -- -notes.md 83 95 84 - # Pipe from stdin 96 + # Stream Markdown from another CLI tool 85 97 claude "explain Rust lifetimes" | leaf 98 + 99 + # Preview a local file through stdin 86 100 cat TESTING.md | leaf 87 - 88 - # Open the fuzzy Markdown picker in the current directory and subdirectories 89 - leaf 90 - 91 - # Open the classic directory browser picker 92 - leaf --picker 93 101 94 102 ``` 95 103 ··· 115 123 ## Features 116 124 117 125 - ✅ **Watch mode** `--watch` / `-w` — reloads every 250ms, with `⟳ reloaded` flash feedback 126 + - ✅ `leaf --watch` can start from the picker and begin watching after file selection 118 127 - ✅ Syntax highlighting with common language aliases like `py`, `cpp`, `json`, `toml`, `ps1`, and `dockerfile` 119 128 - ✅ Unicode box-drawing tables with left / center / right alignment 120 129 - ✅ TOC sidebar with active section tracking and two-level navigation
+3 -3
src/cli.rs
··· 15 15 } 16 16 17 17 pub(crate) fn usage_text() -> &'static str { 18 - "Usage: leaf [--watch] [--theme arctic|forest|ocean|solarized-dark] [file.md]\n leaf --picker\n leaf --update\n echo '# Hello' | leaf" 18 + "Usage: leaf [--watch] [--theme arctic|forest|ocean|solarized-dark] [file.md]\n leaf [--watch] --picker\n leaf --update\n echo '# Hello' | leaf" 19 19 } 20 20 21 21 pub(crate) fn version_text() -> &'static str { ··· 83 83 } 84 84 85 85 if options.picker { 86 - let has_non_picker_flags = options.watch || options.file_arg.is_some(); 86 + let has_non_picker_flags = options.file_arg.is_some(); 87 87 if has_non_picker_flags { 88 - anyhow::bail!("--picker cannot be combined with --watch or a file path"); 88 + anyhow::bail!("--picker cannot be combined with a file path"); 89 89 } 90 90 } 91 91
+2 -2
src/render.rs
··· 272 272 &["enter confirm", "esc cancel"] 273 273 } else if app.is_file_picker_open() { 274 274 if app.is_fuzzy_file_picker() { 275 - &["j/k move", "enter open", "backspace delete", "ctrl+c quit"] 275 + &["↑/↓ move", "enter open", "backspace delete", "ctrl+c quit"] 276 276 } else { 277 277 &["j/k move", "enter open", "backspace up", "ctrl+c quit"] 278 278 } ··· 590 590 lines.push(Line::from("")); 591 591 lines.push(Line::from(vec![Span::styled( 592 592 if app.is_fuzzy_file_picker() { 593 - "enter open • type filter • esc clear • ctrl+c quit" 593 + "↑/↓ move • enter open • type filter • esc clear • ctrl+c quit" 594 594 } else { 595 595 "enter open • backspace up • ctrl+c quit" 596 596 },
+12 -2
src/runtime.rs
··· 107 107 state_changed = app.activate_file_picker_selection(ss, themes); 108 108 } 109 109 KeyCode::Char('q') if app.is_browser_file_picker() => break, 110 - KeyCode::Char('j') | KeyCode::Down => app.move_file_picker_down(), 111 - KeyCode::Char('k') | KeyCode::Up => app.move_file_picker_up(), 110 + KeyCode::Char('j') | KeyCode::Down if app.is_browser_file_picker() => { 111 + app.move_file_picker_down() 112 + } 113 + KeyCode::Char('k') | KeyCode::Up if app.is_browser_file_picker() => { 114 + app.move_file_picker_up() 115 + } 116 + KeyCode::Down if app.is_fuzzy_file_picker() => { 117 + app.move_file_picker_down() 118 + } 119 + KeyCode::Up if app.is_fuzzy_file_picker() => { 120 + app.move_file_picker_up() 121 + } 112 122 KeyCode::Esc => { 113 123 if app.is_browser_file_picker() || app.file_picker_query().is_empty() { 114 124 state_changed = false;
+5 -3
src/tests.rs
··· 136 136 } 137 137 138 138 #[test] 139 - fn parse_cli_rejects_picker_with_watch() { 139 + fn parse_cli_accepts_picker_with_watch() { 140 140 let args = vec![ 141 141 "leaf".to_string(), 142 142 "--picker".to_string(), 143 143 "--watch".to_string(), 144 144 ]; 145 145 146 - let err = parse_cli(&args).unwrap_err(); 147 - assert!(err.to_string().contains("--picker cannot be combined")); 146 + let options = parse_cli(&args).unwrap(); 147 + assert!(options.picker); 148 + assert!(options.watch); 149 + assert_eq!(options.file_arg, None); 148 150 } 149 151 150 152 #[test]
+3 -1
src/update.rs
··· 27 27 } 28 28 29 29 pub(crate) fn run_update() -> Result<()> { 30 + println!("Updating leaf..."); 31 + 30 32 let current_version = env!("CARGO_PKG_VERSION"); 31 33 let asset_name = current_asset_name()?; 32 34 let release = fetch_latest_release()?; ··· 50 52 51 53 match replace_binary(&current_exe, &temp_path) { 52 54 Ok(()) => { 53 - println!("Updated leaf from {current_version} to {latest_version}"); 55 + println!("leaf updated from {current_version} to {latest_version}"); 54 56 Ok(()) 55 57 } 56 58 Err(err) => {