magical markdown slides

chore: clippy

+65 -81
+13 -11
cli/src/main.rs
··· 1 1 /// TODO: Add --no-bg flag to present command to allow users to disable background color 2 2 use clap::{Parser, Subcommand}; 3 + use lantern_core::validator::{validate_slides, validate_theme_file}; 3 4 use lantern_core::{parser::parse_slides_with_meta, term::Terminal as SlideTerminal, theme::ThemeRegistry}; 4 5 use lantern_ui::App; 6 + use owo_colors::OwoColorize; 5 7 use ratatui::{Terminal, backend::CrosstermBackend}; 6 - use std::{io, path::PathBuf}; 8 + use std::{ 9 + io, 10 + path::{Path, PathBuf}, 11 + }; 7 12 use tracing::Level; 8 13 9 14 /// A modern terminal-based presentation tool ··· 74 79 .write(true) 75 80 .truncate(true) 76 81 .open(&log_path) 77 - .unwrap_or_else(|e| panic!("Failed to create log file at {}: {}", log_path, e)); 82 + .unwrap_or_else(|e| panic!("Failed to create log file at {log_path}: {e}")); 78 83 79 84 tracing_subscriber::fmt() 80 85 .with_max_level(cli.log_level) ··· 92 97 match cli.command { 93 98 Commands::Present { file, theme } => { 94 99 if let Err(e) = run_present(&file, theme) { 95 - eprintln!("Error: {}", e); 100 + eprintln!("Error: {e}"); 96 101 std::process::exit(1); 97 102 } 98 103 } 99 104 Commands::Print { file, width, theme } => { 100 105 if let Err(e) = run_print(&file, width, theme) { 101 - eprintln!("Error: {}", e); 106 + eprintln!("Error: {e}"); 102 107 std::process::exit(1); 103 108 } 104 109 } ··· 108 113 } 109 114 Commands::Check { file, strict, theme } => { 110 115 if let Err(e) = run_check(&file, strict, theme) { 111 - eprintln!("Error: {}", e); 116 + eprintln!("Error: {e}"); 112 117 std::process::exit(1); 113 118 } 114 119 } ··· 122 127 .map_err(|e| io::Error::new(e.kind(), format!("Failed to read file {}: {}", file.display(), e)))?; 123 128 124 129 let (meta, slides) = parse_slides_with_meta(&markdown) 125 - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("Parse error: {}", e)))?; 130 + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("Parse error: {e}")))?; 126 131 127 132 if slides.is_empty() { 128 133 return Err(io::Error::new(io::ErrorKind::InvalidData, "No slides found in file")); ··· 164 169 result 165 170 } 166 171 167 - fn run_check(file: &PathBuf, strict: bool, is_theme: bool) -> io::Result<()> { 168 - use lantern_core::validator::{validate_slides, validate_theme_file}; 169 - use owo_colors::OwoColorize; 170 - 172 + fn run_check(file: &Path, strict: bool, is_theme: bool) -> io::Result<()> { 171 173 if is_theme { 172 174 tracing::info!("Validating theme file: {}", file.display()); 173 175 let result = validate_theme_file(file); ··· 228 230 .map_err(|e| io::Error::new(e.kind(), format!("Failed to read file {}: {}", file.display(), e)))?; 229 231 230 232 let (meta, slides) = parse_slides_with_meta(&markdown) 231 - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("Parse error: {}", e)))?; 233 + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("Parse error: {e}")))?; 232 234 233 235 if slides.is_empty() { 234 236 return Err(io::Error::new(io::ErrorKind::InvalidData, "No slides found in file"));
+5 -7
core/src/metadata.rs
··· 41 41 match format { 42 42 FrontmatterFormat::Yaml => match serde_yml::from_str(header) { 43 43 Ok(meta) => Ok(meta), 44 - Err(e) => Err(SlideError::front_matter(format!("Failed to parse YAML: {}", e))), 44 + Err(e) => Err(SlideError::front_matter(format!("Failed to parse YAML: {e}"))), 45 45 }, 46 46 FrontmatterFormat::Toml => match toml::from_str(header) { 47 47 Ok(meta) => Ok(meta), 48 - Err(e) => Err(SlideError::front_matter(format!("Failed to parse TOML: {}", e))), 48 + Err(e) => Err(SlideError::front_matter(format!("Failed to parse TOML: {e}"))), 49 49 }, 50 50 } 51 51 } 52 52 53 53 /// Extract frontmatter block with the given delimiter and format 54 54 fn extract_frontmatter(rest: &str, delimiter: &str, format: FrontmatterFormat) -> Result<(Self, String)> { 55 - match rest.find(&format!("\n{}", delimiter)) { 55 + match rest.find(&format!("\n{delimiter}")) { 56 56 Some(end_pos) => Ok(( 57 57 Self::parse(&rest[..end_pos], format)?, 58 58 rest[end_pos + delimiter.len() + 1..].to_string(), 59 59 )), 60 60 None => Err(SlideError::front_matter(format!( 61 - "Unclosed {} frontmatter block (missing closing {})", 62 - format, delimiter 61 + "Unclosed {format} frontmatter block (missing closing {delimiter})" 63 62 ))), 64 63 } 65 64 } ··· 97 96 let day_of_year = epoch_days % 365; 98 97 let month = (day_of_year / 30) + 1; 99 98 let day = (day_of_year % 30) + 1; 100 - format!("{:04}-{:02}-{:02}", year, month, day) 99 + format!("{year:04}-{month:02}-{day:02}") 101 100 } 102 101 Err(_) => "Unknown".to_string(), 103 102 } ··· 125 124 FrontmatterFormat::Yaml => "YAML", 126 125 FrontmatterFormat::Toml => "TOML", 127 126 } 128 - .to_string() 129 127 ) 130 128 } 131 129 }
+6 -6
core/src/parser.rs
··· 161 161 }) = block_stack.last_mut() 162 162 { 163 163 if !current_row.is_empty() { 164 - *headers = current_row.drain(..).collect(); 164 + *headers = std::mem::take(current_row); 165 165 } 166 166 *in_header = false; 167 167 } ··· 174 174 }) = block_stack.last_mut() 175 175 { 176 176 if !current_row.is_empty() { 177 - rows.push(current_row.drain(..).collect()); 177 + rows.push(std::mem::take(current_row)); 178 178 } 179 179 } 180 180 } ··· 185 185 .. 186 186 }) = block_stack.last_mut() 187 187 { 188 - current_row.push(current_cell.drain(..).collect()); 188 + current_row.push(std::mem::take(current_cell)); 189 189 } 190 190 } 191 191 TagEnd::Item => { ··· 195 195 { 196 196 if !current_item.is_empty() { 197 197 items.push(ListItem { 198 - spans: current_item.drain(..).collect(), 198 + spans: std::mem::take(current_item), 199 199 nested: None, 200 200 }); 201 201 } ··· 543 543 544 544 match &slides[0].blocks[0] { 545 545 Block::Table(table) => { 546 - assert_eq!(table.rows[0][0][0].style.bold, true); 547 - assert_eq!(table.rows[0][1][0].style.code, true); 546 + assert!(table.rows[0][0][0].style.bold); 547 + assert!(table.rows[0][1][0].style.code); 548 548 } 549 549 _ => panic!("Expected table"), 550 550 }
+9 -9
core/src/printer.rs
··· 22 22 writeln!(writer)?; 23 23 let sep_text = "═".repeat(width); 24 24 let separator = theme.rule(&sep_text); 25 - writeln!(writer, "{}", separator)?; 25 + writeln!(writer, "{separator}")?; 26 26 writeln!(writer)?; 27 27 } 28 28 ··· 118 118 current_line.push(' '); 119 119 current_line.push_str(word); 120 120 } else { 121 - write!(writer, "{}", indent_str)?; 121 + write!(writer, "{indent_str}")?; 122 122 for span in spans { 123 123 if current_line.contains(&span.text) { 124 124 print_span(writer, span, theme, false)?; ··· 134 134 } 135 135 136 136 if !current_line.is_empty() { 137 - write!(writer, "{}", indent_str)?; 137 + write!(writer, "{indent_str}")?; 138 138 for span in spans { 139 139 print_span(writer, span, theme, false)?; 140 140 } ··· 149 149 writer: &mut W, code: &CodeBlock, theme: &ThemeColors, width: usize, 150 150 ) -> std::io::Result<()> { 151 151 if let Some(lang) = &code.language { 152 - writeln!(writer, "{}", theme.code_fence(&format!("```{}", lang)))?; 152 + writeln!(writer, "{}", theme.code_fence(&format!("```{lang}")))?; 153 153 } else { 154 154 writeln!(writer, "{}", theme.code_fence(&"```"))?; 155 155 } ··· 179 179 180 180 /// Print a list with bullets or numbers 181 181 fn print_list<W: std::io::Write>( 182 - writer: &mut W, list: &List, theme: &ThemeColors, width: usize, indent: usize, 182 + writer: &mut W, list: &List, theme: &ThemeColors, _width: usize, indent: usize, 183 183 ) -> std::io::Result<()> { 184 184 for (idx, item) in list.items.iter().enumerate() { 185 185 let marker = if list.ordered { format!("{}. ", idx + 1) } else { "• ".to_string() }; ··· 194 194 writeln!(writer)?; 195 195 196 196 if let Some(nested) = &item.nested { 197 - print_list(writer, nested, theme, width, indent + 2)?; 197 + print_list(writer, nested, theme, _width, indent + 2)?; 198 198 } 199 199 } 200 200 ··· 357 357 let mut result = styled.to_string(); 358 358 359 359 if text_style.bold { 360 - result = format!("\x1b[1m{}\x1b[22m", result); 360 + result = format!("\x1b[1m{result}\x1b[22m"); 361 361 } 362 362 if text_style.italic { 363 - result = format!("\x1b[3m{}\x1b[23m", result); 363 + result = format!("\x1b[3m{result}\x1b[23m"); 364 364 } 365 365 if text_style.strikethrough { 366 - result = format!("\x1b[9m{}\x1b[29m", result); 366 + result = format!("\x1b[9m{result}\x1b[29m"); 367 367 } 368 368 369 369 result
+1 -2
core/src/theme.rs
··· 660 660 let styled = theme.heading(&"Test"); 661 661 assert!( 662 662 styled.to_string().contains("Test"), 663 - "Theme '{}' failed to parse or apply styles", 664 - theme_name 663 + "Theme '{theme_name}' failed to parse or apply styles" 665 664 ); 666 665 } 667 666 }
+4 -3
core/src/validator.rs
··· 2 2 use crate::metadata::Meta; 3 3 use crate::parser::parse_slides_with_meta; 4 4 use crate::theme::{Base16Scheme, ThemeColors, ThemeRegistry}; 5 + 5 6 use std::path::Path; 6 7 7 8 /// Validation result containing errors and warnings ··· 55 56 let (meta, slides) = match parse_slides_with_meta(&markdown) { 56 57 Ok((m, s)) => (m, s), 57 58 Err(e) => { 58 - result.add_error(format!("Parse error: {}", e)); 59 + result.add_error(format!("Parse error: {e}")); 59 60 return result; 60 61 } 61 62 }; ··· 118 119 let scheme: Base16Scheme = match serde_yml::from_str(&yaml_content) { 119 120 Ok(s) => s, 120 121 Err(e) => { 121 - result.add_error(format!("Failed to parse YAML: {}", e)); 122 + result.add_error(format!("Failed to parse YAML: {e}")); 122 123 return result; 123 124 } 124 125 }; ··· 187 188 } 188 189 189 190 if !hex.chars().all(|c| c.is_ascii_hexdigit()) { 190 - result.add_error(format!("Color {} contains invalid hex characters", name)); 191 + result.add_error(format!("Color {name} contains invalid hex characters")); 191 192 } 192 193 } 193 194
+3 -7
ui/src/layout.rs
··· 3 3 /// Layout manager for slide presentation 4 4 /// 5 5 /// Calculates screen layout with main slide area, optional notes panel, status bar, and optional help line. 6 + #[derive(Default)] 6 7 pub struct SlideLayout { 7 8 show_notes: bool, 8 9 show_help: bool, ··· 80 81 } 81 82 } 82 83 83 - impl Default for SlideLayout { 84 - fn default() -> Self { 85 - Self { show_notes: false, show_help: false } 86 - } 87 - } 88 84 89 85 #[cfg(test)] 90 86 mod tests { ··· 148 144 let main_percentage = (main.width as f32 / area.width as f32) * 100.0; 149 145 let notes_percentage = (notes_area.width as f32 / area.width as f32) * 100.0; 150 146 151 - assert!(main_percentage >= 55.0 && main_percentage <= 65.0); 152 - assert!(notes_percentage >= 35.0 && notes_percentage <= 45.0); 147 + assert!((55.0..=65.0).contains(&main_percentage)); 148 + assert!((35.0..=45.0).contains(&notes_percentage)); 153 149 } 154 150 155 151 #[test]
+17 -29
ui/src/renderer.rs
··· 1 - use ratatui::{ 2 - style::{Modifier, Style}, 3 - text::{Line, Span, Text}, 4 - }; 5 1 use lantern_core::{ 6 2 highlighter, 7 3 slide::{Block, CodeBlock, List, Table, TextSpan, TextStyle}, 8 4 theme::ThemeColors, 9 5 }; 6 + use ratatui::{ 7 + style::{Modifier, Style}, 8 + text::{Line, Span, Text}, 9 + }; 10 10 11 11 /// Render a slide's blocks into ratatui Text 12 12 /// ··· 67 67 let fence_style = to_ratatui_style(&theme.code_fence, false); 68 68 69 69 if let Some(lang) = &code.language { 70 - lines.push(Line::from(Span::styled(format!("```{}", lang), fence_style))); 70 + lines.push(Line::from(Span::styled(format!("```{lang}"), fence_style))); 71 71 } else { 72 72 lines.push(Line::from(Span::styled("```".to_string(), fence_style))); 73 73 } ··· 123 123 let border_style = to_ratatui_style(&theme.blockquote_border, false); 124 124 125 125 for block in blocks { 126 - match block { 127 - Block::Paragraph { spans } => { 128 - let mut line_spans = vec![Span::styled("│ ".to_string(), border_style)]; 126 + if let Block::Paragraph { spans } = block { 127 + let mut line_spans = vec![Span::styled("│ ".to_string(), border_style)]; 129 128 130 - for span in spans { 131 - line_spans.push(create_span(span, theme, false)); 132 - } 133 - 134 - lines.push(Line::from(line_spans)); 129 + for span in spans { 130 + line_spans.push(create_span(span, theme, false)); 135 131 } 136 - _ => {} 132 + 133 + lines.push(Line::from(line_spans)); 137 134 } 138 135 } 139 136 } ··· 214 211 215 212 #[cfg(test)] 216 213 mod tests { 217 - use lantern_core::slide::ListItem; 218 - 219 214 use super::*; 220 215 216 + use lantern_core::slide::ListItem; 217 + use lantern_core::theme::Color; 218 + 221 219 #[test] 222 220 fn render_heading_basic() { 223 221 let blocks = vec![Block::Heading { level: 1, spans: vec![TextSpan::plain("Test Heading")] }]; ··· 275 273 276 274 #[test] 277 275 fn to_ratatui_style_converts_color() { 278 - use lantern_core::theme::Color; 279 - 280 276 let color = Color::new(255, 128, 64); 281 277 let style = to_ratatui_style(&color, false); 282 278 ··· 285 281 286 282 #[test] 287 283 fn to_ratatui_style_applies_bold() { 288 - use lantern_core::theme::Color; 289 - 290 284 let color = Color::new(100, 150, 200); 291 285 let style = to_ratatui_style(&color, true); 292 286 ··· 296 290 297 291 #[test] 298 292 fn to_ratatui_style_no_bold_when_false() { 299 - use lantern_core::theme::Color; 300 - 301 293 let color = Color::new(100, 150, 200); 302 294 let style = to_ratatui_style(&color, false); 303 - 304 295 assert!(!style.add_modifier.contains(Modifier::BOLD)); 305 296 } 306 297 ··· 308 299 fn render_heading_uses_theme_colors() { 309 300 let theme = ThemeColors::default(); 310 301 let blocks = vec![Block::Heading { level: 1, spans: vec![TextSpan::plain("Colored Heading")] }]; 311 - 312 302 let text = render_slide_content(&blocks, &theme); 313 303 assert!(!text.lines.is_empty()); 314 - assert!(text.lines.len() >= 1); 304 + assert!(!text.lines.is_empty()); 315 305 } 316 306 317 307 #[test] 318 308 fn apply_theme_style_respects_heading_bold() { 319 309 let theme = ThemeColors::default(); 320 310 let text_style = TextStyle::default(); 321 - 322 311 let style = apply_theme_style(&theme, &text_style, true); 323 312 assert!(style.add_modifier.contains(Modifier::BOLD)); 324 313 } ··· 326 315 #[test] 327 316 fn apply_theme_style_uses_code_color_for_code() { 328 317 let theme = ThemeColors::default(); 329 - let mut text_style = TextStyle::default(); 330 - text_style.code = true; 318 + let text_style = TextStyle { code: true, ..Default::default() }; 319 + let style = apply_theme_style(&theme, &text_style, false); 331 320 332 - let style = apply_theme_style(&theme, &text_style, false); 333 321 assert_eq!( 334 322 style.fg, 335 323 Some(ratatui::style::Color::Rgb(theme.code.r, theme.code.g, theme.code.b))
+7 -7
ui/src/viewer.rs
··· 152 152 self.slides.iter().any(|slide| slide.notes.is_some()) 153 153 } 154 154 155 - fn theme(&self) -> ThemeColors { 156 - self.stylesheet.theme.clone() 157 - } 158 - 159 155 /// Render the current slide to the frame 160 156 pub fn render(&self, frame: &mut Frame, area: Rect) { 161 157 if let Some(slide) = self.current_slide() { ··· 207 203 208 204 /// Render status bar with navigation info 209 205 pub fn render_status_bar(&self, frame: &mut Frame, area: Rect) { 210 - let filename_part = self.filename.as_ref().map(|f| format!("{} | ", f)).unwrap_or_default(); 206 + let filename_part = self.filename.as_ref().map(|f| format!("{f} | ")).unwrap_or_default(); 211 207 212 208 let elapsed = self 213 209 .start_time ··· 217 213 let hours = secs / 3600; 218 214 let minutes = (secs % 3600) / 60; 219 215 let seconds = secs % 60; 220 - format!(" | {:02}:{:02}:{:02}", hours, minutes, seconds) 216 + format!(" | {hours:02}:{minutes:02}:{seconds:02}") 221 217 }) 222 218 .unwrap_or_default(); 223 219 ··· 257 253 let text_len = help_text.chars().count(); 258 254 let padding = if text_len < width { " ".repeat(width - text_len) } else { String::new() }; 259 255 260 - let full_text = format!("{}{}", help_text, padding); 256 + let full_text = format!("{help_text}{padding}"); 261 257 262 258 let dimmed_style = Style::default().fg(Color::Rgb(100, 100, 100)).bg(Color::Rgb( 263 259 self.theme().ui_background.r, ··· 268 264 let help_line = Paragraph::new(Line::from(vec![Span::styled(full_text, dimmed_style)])); 269 265 270 266 frame.render_widget(help_line, area); 267 + } 268 + 269 + fn theme(&self) -> ThemeColors { 270 + self.stylesheet.theme 271 271 } 272 272 } 273 273