magical markdown slides

feat: implement basic cli ux with print command and documentation

+1074 -14
+4 -4
ROADMAP.md
··· 30 30 | __✓ Slide Model__ | Define structs: `Slide`, `Block`, `TextSpan`, `CodeBlock`, etc. | Internal | 31 31 | __✓ Metadata Parsing__ | Optional front matter (YAML/TOML) for theme, author, etc. | `serde_yml`[^5] | 32 32 | __Error & Validation__ | Provide friendly parser errors with file/line info. | `thiserror`[^6] | 33 - | __Basic CLI UX__ | `slides present file.md` runs full TUI. | `clap` | 33 + | __✓ Basic CLI UX__ | `slides present file.md` runs full TUI. | `clap` | 34 34 | | `slides print` renders to stdout with width constraint. | | 35 35 36 36 ## Rendering & Navigation ··· 40 40 | Task | Description | Key Crates | 41 41 | ------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------- | 42 42 | __✓ Ratatui Integration__ | Build basic slide viewer using layout, blocks, paragraphs. | `ratatui`[^7] | 43 - | __Input & State__ | Support `←/→`, `j/k`, `q`, numeric jumps, and window resize. | `crossterm`, `ratatui` | 44 - | __Status Bar__ | Display slide count, filename, clock, and theme name. | `ratatui` | 43 + | __✓ Input & State__ | Support `←/→`, `j/k`, `q`, numeric jumps, and window resize. | `crossterm`, `ratatui` | 44 + | __✓ Status Bar__ | Display slide count, filename, clock, and theme name. | `ratatui` | 45 45 | __✓ Color Styling__ | Apply consistent color palette via `owo-colors`. Define traits like `ThemeColor`. | `owo-colors` | 46 46 | __Configurable Themes__ | Support themes via TOML files mapping semantic roles (`heading`, `body`, `accent`) → color pairs. | `toml`, `serde` | 47 47 ··· 82 82 | __Tables & Lists__ | Render GitHub-style tables, bullets, and task lists. | `pulldown-cmark`, `ratatui` | 83 83 | __Admonitions__ | Highlighted boxes with icons | `owo-colors`, internal glyphs | 84 84 | __Horizontal Rules__ | Use box-drawing (`─`, `═`) and shading (`░`, `▓`). | Unicode constants | 85 - | __Generators__. | `slides init` scaffolds an example deck with code and notes. | `include_str!`, `fs` | 85 + | __Generators__ | `slides init` scaffolds an example deck with code and notes. | `include_str!`, `fs` | 86 86 87 87 ## RC 88 88
+61 -9
cli/src/main.rs
··· 24 24 Present { 25 25 /// Path to the markdown file 26 26 file: PathBuf, 27 - 28 27 /// Theme to use for presentation 29 28 #[arg(short, long)] 30 29 theme: Option<String>, ··· 34 33 Print { 35 34 /// Path to the markdown file 36 35 file: PathBuf, 37 - 38 36 /// Maximum width for output (in characters) 39 37 #[arg(short, long, default_value = "80")] 40 38 width: usize, 41 - 42 39 /// Theme to use for coloring 43 40 #[arg(short, long)] 44 41 theme: Option<String>, ··· 49 46 /// Directory to create the deck in 50 47 #[arg(default_value = ".")] 51 48 path: PathBuf, 52 - 53 49 /// Name of the deck file 54 50 #[arg(short, long, default_value = "slides.md")] 55 51 name: String, ··· 59 55 Check { 60 56 /// Path to the markdown file 61 57 file: PathBuf, 62 - 63 58 /// Enable strict mode with additional checks 64 59 #[arg(short, long)] 65 60 strict: bool, ··· 80 75 } 81 76 82 77 Commands::Print { file, width, theme } => { 83 - tracing::info!("Printing slides from: {} (width: {})", file.display(), width); 84 - if let Some(theme) = theme { 85 - tracing::debug!("Using theme: {}", theme); 78 + if let Err(e) = run_print(&file, width, theme) { 79 + eprintln!("Error: {}", e); 80 + std::process::exit(1); 86 81 } 87 - eprintln!("Print mode not yet implemented"); 88 82 } 89 83 90 84 Commands::Init { path, name } => { ··· 146 140 result 147 141 } 148 142 143 + fn run_print(file: &PathBuf, width: usize, theme_arg: Option<String>) -> io::Result<()> { 144 + tracing::info!("Printing slides from: {} (width: {})", file.display(), width); 145 + 146 + let markdown = std::fs::read_to_string(file) 147 + .map_err(|e| io::Error::new(e.kind(), format!("Failed to read file {}: {}", file.display(), e)))?; 148 + 149 + let (meta, slides) = parse_slides_with_meta(&markdown) 150 + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("Parse error: {}", e)))?; 151 + 152 + if slides.is_empty() { 153 + return Err(io::Error::new(io::ErrorKind::InvalidData, "No slides found in file")); 154 + } 155 + 156 + let theme_name = theme_arg.unwrap_or_else(|| meta.theme.clone()); 157 + tracing::debug!("Using theme: {}", theme_name); 158 + 159 + // TODO: Load theme from theme registry based on theme_name 160 + let theme = ThemeColors::default(); 161 + 162 + slides_core::printer::print_slides_to_stdout(&slides, &theme, width)?; 163 + 164 + Ok(()) 165 + } 166 + 149 167 #[cfg(test)] 150 168 mod tests { 151 169 use super::*; ··· 209 227 } 210 228 _ => panic!("Expected Check command"), 211 229 } 230 + } 231 + 232 + #[test] 233 + fn run_print_with_test_file() { 234 + let temp_dir = std::env::temp_dir(); 235 + let test_file = temp_dir.join("test_slides.md"); 236 + 237 + let content = "# Test Slide\n\nThis is a test paragraph.\n\n---\n\n# Second Slide\n\n- Item 1\n- Item 2"; 238 + std::fs::write(&test_file, content).expect("Failed to write test file"); 239 + 240 + let result = run_print(&test_file, 80, None); 241 + assert!(result.is_ok()); 242 + 243 + std::fs::remove_file(&test_file).ok(); 244 + } 245 + 246 + #[test] 247 + fn run_print_empty_file() { 248 + let temp_dir = std::env::temp_dir(); 249 + let test_file = temp_dir.join("empty_slides.md"); 250 + 251 + std::fs::write(&test_file, "").expect("Failed to write test file"); 252 + 253 + let result = run_print(&test_file, 80, None); 254 + assert!(result.is_err()); 255 + 256 + std::fs::remove_file(&test_file).ok(); 257 + } 258 + 259 + #[test] 260 + fn run_print_nonexistent_file() { 261 + let test_file = PathBuf::from("/nonexistent/file.md"); 262 + let result = run_print(&test_file, 80, None); 263 + assert!(result.is_err()); 212 264 } 213 265 }
+1
core/src/lib.rs
··· 1 1 pub mod error; 2 2 pub mod metadata; 3 3 pub mod parser; 4 + pub mod printer; 4 5 pub mod slide; 5 6 pub mod term; 6 7 pub mod theme;
+369
core/src/printer.rs
··· 1 + use crate::slide::{Block, CodeBlock, List, Table, TextSpan, TextStyle}; 2 + use crate::theme::ThemeColors; 3 + 4 + /// Print slides to stdout with formatted output 5 + /// 6 + /// Renders slides as plain text with ANSI colors and width constraints. 7 + pub fn print_slides_to_stdout( 8 + slides: &[crate::slide::Slide], theme: &ThemeColors, width: usize, 9 + ) -> std::io::Result<()> { 10 + let stdout = std::io::stdout(); 11 + let mut handle = stdout.lock(); 12 + print_slides(&mut handle, slides, theme, width) 13 + } 14 + 15 + /// Print slides to any writer with formatted output 16 + pub fn print_slides<W: std::io::Write>( 17 + writer: &mut W, slides: &[crate::slide::Slide], theme: &ThemeColors, width: usize, 18 + ) -> std::io::Result<()> { 19 + for (idx, slide) in slides.iter().enumerate() { 20 + if idx > 0 { 21 + writeln!(writer)?; 22 + let sep_text = "═".repeat(width); 23 + let separator = theme.rule(&sep_text); 24 + writeln!(writer, "{}", separator)?; 25 + writeln!(writer)?; 26 + } 27 + 28 + print_slide(writer, slide, theme, width)?; 29 + } 30 + 31 + Ok(()) 32 + } 33 + 34 + /// Print a single slide with formatted blocks 35 + fn print_slide<W: std::io::Write>( 36 + writer: &mut W, slide: &crate::slide::Slide, theme: &ThemeColors, width: usize, 37 + ) -> std::io::Result<()> { 38 + for block in &slide.blocks { 39 + print_block(writer, block, theme, width, 0)?; 40 + writeln!(writer)?; 41 + } 42 + 43 + Ok(()) 44 + } 45 + 46 + /// Print a single block with appropriate formatting 47 + fn print_block<W: std::io::Write>( 48 + writer: &mut W, block: &Block, theme: &ThemeColors, width: usize, indent: usize, 49 + ) -> std::io::Result<()> { 50 + match block { 51 + Block::Heading { level, spans } => { 52 + print_heading(writer, *level, spans, theme)?; 53 + } 54 + Block::Paragraph { spans } => { 55 + print_paragraph(writer, spans, theme, width, indent)?; 56 + } 57 + Block::Code(code) => { 58 + print_code_block(writer, code, theme, width)?; 59 + } 60 + Block::List(list) => { 61 + print_list(writer, list, theme, width, indent)?; 62 + } 63 + Block::Rule => { 64 + let rule_text = "─".repeat(width.saturating_sub(indent)); 65 + let rule = theme.rule(&rule_text); 66 + writeln!(writer, "{}{}", " ".repeat(indent), rule)?; 67 + } 68 + Block::BlockQuote { blocks } => { 69 + print_blockquote(writer, blocks, theme, width, indent)?; 70 + } 71 + Block::Table(table) => { 72 + print_table(writer, table, theme, width)?; 73 + } 74 + } 75 + 76 + Ok(()) 77 + } 78 + 79 + /// Print a heading with level-appropriate styling 80 + fn print_heading<W: std::io::Write>( 81 + writer: &mut W, level: u8, spans: &[TextSpan], theme: &ThemeColors, 82 + ) -> std::io::Result<()> { 83 + let prefix = match level { 84 + 1 => "# ", 85 + 2 => "## ", 86 + 3 => "### ", 87 + 4 => "#### ", 88 + 5 => "##### ", 89 + _ => "###### ", 90 + }; 91 + 92 + write!(writer, "{}", theme.heading(&prefix))?; 93 + 94 + for span in spans { 95 + print_span(writer, span, theme, true)?; 96 + } 97 + 98 + writeln!(writer)?; 99 + Ok(()) 100 + } 101 + 102 + /// Print a paragraph with word wrapping 103 + fn print_paragraph<W: std::io::Write>( 104 + writer: &mut W, spans: &[TextSpan], theme: &ThemeColors, width: usize, indent: usize, 105 + ) -> std::io::Result<()> { 106 + let indent_str = " ".repeat(indent); 107 + let effective_width = width.saturating_sub(indent); 108 + 109 + let text = spans.iter().map(|s| s.text.as_str()).collect::<Vec<_>>().join(""); 110 + 111 + let words: Vec<&str> = text.split_whitespace().collect(); 112 + let mut current_line = String::new(); 113 + 114 + for word in words { 115 + if current_line.is_empty() { 116 + current_line = word.to_string(); 117 + } else if current_line.len() + 1 + word.len() <= effective_width { 118 + current_line.push(' '); 119 + current_line.push_str(word); 120 + } else { 121 + write!(writer, "{}", indent_str)?; 122 + for span in spans { 123 + if current_line.contains(&span.text) { 124 + print_span(writer, span, theme, false)?; 125 + break; 126 + } 127 + } 128 + if !spans.is_empty() && !current_line.is_empty() { 129 + write!(writer, "{}", theme.body(&current_line))?; 130 + } 131 + writeln!(writer)?; 132 + current_line = word.to_string(); 133 + } 134 + } 135 + 136 + if !current_line.is_empty() { 137 + write!(writer, "{}", indent_str)?; 138 + for span in spans { 139 + print_span(writer, span, theme, false)?; 140 + } 141 + writeln!(writer)?; 142 + } 143 + 144 + Ok(()) 145 + } 146 + 147 + /// Print a code block with language tag 148 + fn print_code_block<W: std::io::Write>( 149 + writer: &mut W, code: &CodeBlock, theme: &ThemeColors, width: usize, 150 + ) -> std::io::Result<()> { 151 + if let Some(lang) = &code.language { 152 + writeln!(writer, "{}", theme.code_fence(&format!("```{}", lang)))?; 153 + } else { 154 + writeln!(writer, "{}", theme.code_fence(&"```"))?; 155 + } 156 + 157 + for line in code.code.lines() { 158 + let trimmed = if line.len() > width - 4 { &line[..width - 4] } else { line }; 159 + writeln!(writer, "{}", theme.code(&trimmed))?; 160 + } 161 + 162 + writeln!(writer, "{}", theme.code_fence(&"```"))?; 163 + Ok(()) 164 + } 165 + 166 + /// Print a list with bullets or numbers 167 + fn print_list<W: std::io::Write>( 168 + writer: &mut W, list: &List, theme: &ThemeColors, width: usize, indent: usize, 169 + ) -> std::io::Result<()> { 170 + for (idx, item) in list.items.iter().enumerate() { 171 + let marker = if list.ordered { format!("{}. ", idx + 1) } else { "• ".to_string() }; 172 + 173 + write!(writer, "{}", " ".repeat(indent))?; 174 + write!(writer, "{}", theme.list_marker(&marker))?; 175 + 176 + for span in &item.spans { 177 + print_span(writer, span, theme, false)?; 178 + } 179 + 180 + writeln!(writer)?; 181 + 182 + if let Some(nested) = &item.nested { 183 + print_list(writer, nested, theme, width, indent + 2)?; 184 + } 185 + } 186 + 187 + Ok(()) 188 + } 189 + 190 + /// Print a blockquote with border 191 + fn print_blockquote<W: std::io::Write>( 192 + writer: &mut W, blocks: &[Block], theme: &ThemeColors, width: usize, indent: usize, 193 + ) -> std::io::Result<()> { 194 + for block in blocks { 195 + match block { 196 + Block::Paragraph { spans } => { 197 + write!(writer, "{}", " ".repeat(indent))?; 198 + write!(writer, "{}", theme.blockquote_border(&"│ "))?; 199 + for span in spans { 200 + print_span(writer, span, theme, false)?; 201 + } 202 + writeln!(writer)?; 203 + } 204 + _ => { 205 + write!(writer, "{}", " ".repeat(indent))?; 206 + write!(writer, "{}", theme.blockquote_border(&"│ "))?; 207 + print_block(writer, block, theme, width, indent + 2)?; 208 + } 209 + } 210 + } 211 + 212 + Ok(()) 213 + } 214 + 215 + /// Print a table with borders 216 + /// 217 + /// TODO: Implement proper column width calculation and alignment 218 + fn print_table<W: std::io::Write>( 219 + writer: &mut W, table: &Table, theme: &ThemeColors, width: usize, 220 + ) -> std::io::Result<()> { 221 + let col_count = table.headers.len(); 222 + let _col_width = if col_count > 0 { (width.saturating_sub(col_count * 3)) / col_count } else { width }; 223 + 224 + if !table.headers.is_empty() { 225 + for (idx, header) in table.headers.iter().enumerate() { 226 + if idx > 0 { 227 + write!(writer, "{}", theme.table_border(&" │ "))?; 228 + } 229 + for span in header { 230 + print_span(writer, span, theme, true)?; 231 + } 232 + } 233 + writeln!(writer)?; 234 + 235 + let separator = "─".repeat(width); 236 + writeln!(writer, "{}", theme.table_border(&separator))?; 237 + } 238 + 239 + for row in &table.rows { 240 + for (idx, cell) in row.iter().enumerate() { 241 + if idx > 0 { 242 + write!(writer, "{}", theme.table_border(&" │ "))?; 243 + } 244 + for span in cell { 245 + print_span(writer, span, theme, false)?; 246 + } 247 + } 248 + writeln!(writer)?; 249 + } 250 + 251 + Ok(()) 252 + } 253 + 254 + /// Print a text span with styling 255 + fn print_span<W: std::io::Write>( 256 + writer: &mut W, span: &TextSpan, theme: &ThemeColors, is_heading: bool, 257 + ) -> std::io::Result<()> { 258 + let text = &span.text; 259 + let style = &span.style; 260 + 261 + if is_heading { 262 + write!(writer, "{}", apply_text_style(&theme.heading(text), style))?; 263 + } else if style.code { 264 + write!(writer, "{}", apply_text_style(&theme.code(text), style))?; 265 + } else { 266 + write!(writer, "{}", apply_text_style(&theme.body(text), style))?; 267 + } 268 + 269 + Ok(()) 270 + } 271 + 272 + /// Apply text style modifiers to styled text 273 + fn apply_text_style<T: std::fmt::Display>(styled: &owo_colors::Styled<T>, text_style: &TextStyle) -> String { 274 + let mut result = styled.to_string(); 275 + 276 + if text_style.bold { 277 + result = format!("\x1b[1m{}\x1b[22m", result); 278 + } 279 + if text_style.italic { 280 + result = format!("\x1b[3m{}\x1b[23m", result); 281 + } 282 + if text_style.strikethrough { 283 + result = format!("\x1b[9m{}\x1b[29m", result); 284 + } 285 + 286 + result 287 + } 288 + 289 + #[cfg(test)] 290 + mod tests { 291 + use super::*; 292 + use crate::slide::Slide; 293 + 294 + #[test] 295 + fn print_empty_slides() { 296 + let slides: Vec<Slide> = vec![]; 297 + let theme = ThemeColors::default(); 298 + let mut output = Vec::new(); 299 + 300 + let result = print_slides(&mut output, &slides, &theme, 80); 301 + assert!(result.is_ok()); 302 + assert_eq!(output.len(), 0); 303 + } 304 + 305 + #[test] 306 + fn print_single_heading() { 307 + let slide = Slide::with_blocks(vec![Block::Heading { 308 + level: 1, 309 + spans: vec![TextSpan::plain("Hello World")], 310 + }]); 311 + let theme = ThemeColors::default(); 312 + let mut output = Vec::new(); 313 + 314 + let result = print_slides(&mut output, &[slide], &theme, 80); 315 + assert!(result.is_ok()); 316 + let text = String::from_utf8_lossy(&output); 317 + assert!(text.contains("Hello World")); 318 + } 319 + 320 + #[test] 321 + fn print_paragraph_with_wrapping() { 322 + let long_text = "This is a very long paragraph that should wrap when printed to stdout with a width constraint applied to ensure readability."; 323 + let slide = Slide::with_blocks(vec![Block::Paragraph { spans: vec![TextSpan::plain(long_text)] }]); 324 + let theme = ThemeColors::default(); 325 + let mut output = Vec::new(); 326 + 327 + let result = print_slides(&mut output, &[slide], &theme, 40); 328 + assert!(result.is_ok()); 329 + } 330 + 331 + #[test] 332 + fn print_code_block() { 333 + let slide = Slide::with_blocks(vec![Block::Code(CodeBlock::with_language( 334 + "rust", 335 + "fn main() {\n println!(\"Hello\");\n}", 336 + ))]); 337 + let theme = ThemeColors::default(); 338 + let mut output = Vec::new(); 339 + 340 + let result = print_slides(&mut output, &[slide], &theme, 80); 341 + assert!(result.is_ok()); 342 + let text = String::from_utf8_lossy(&output); 343 + assert!(text.contains("```rust")); 344 + assert!(text.contains("fn main()")); 345 + } 346 + 347 + #[test] 348 + fn print_multiple_slides() { 349 + let slides = vec![ 350 + Slide::with_blocks(vec![Block::Heading { 351 + level: 1, 352 + spans: vec![TextSpan::plain("Slide 1")], 353 + }]), 354 + Slide::with_blocks(vec![Block::Heading { 355 + level: 1, 356 + spans: vec![TextSpan::plain("Slide 2")], 357 + }]), 358 + ]; 359 + 360 + let theme = ThemeColors::default(); 361 + let mut output = Vec::new(); 362 + let result = print_slides(&mut output, &slides, &theme, 80); 363 + assert!(result.is_ok()); 364 + 365 + let text = String::from_utf8_lossy(&output); 366 + assert!(text.contains("Slide 1")); 367 + assert!(text.contains("Slide 2")); 368 + } 369 + }
+9 -1
docs/src/SUMMARY.md
··· 1 1 # Summary 2 2 3 - - [Chapter 1](./chapter_1.md) 3 + [Introduction](./chapter_1.md) 4 + 5 + # Getting Started 6 + 7 + - [Quickstart](./quickstart.md) 8 + 9 + # Reference 10 + 11 + - [Symbols](./appendices/symbols.md)
+194
docs/src/quickstart.md
··· 1 + # Quickstart 2 + 3 + Get started with slides-rs in minutes. 4 + 5 + ## Installation 6 + 7 + Currently, you'll need to build from source: 8 + 9 + ```bash 10 + git clone https://github.com/yourusername/slides-rs.git 11 + cd slides-rs 12 + cargo build --release 13 + ``` 14 + 15 + The binary will be available at `target/release/slides`. 16 + 17 + ## Creating Your First Deck 18 + 19 + Create a new markdown file called `presentation.md`: 20 + 21 + ````markdown 22 + --- 23 + theme: default 24 + author: Your Name 25 + --- 26 + 27 + # Welcome to Slides 28 + 29 + A modern terminal-based presentation tool 30 + 31 + --- 32 + 33 + ## Features 34 + 35 + - Parse markdown into slides 36 + - Interactive TUI navigation with full keyboard support 37 + - Speaker notes with toggle visibility 38 + - Live presentation timer 39 + - Status bar with slide count and navigation hints 40 + - Print to stdout 41 + - Syntax highlighting (coming soon) 42 + 43 + --- 44 + 45 + ## Code Example 46 + 47 + ```rust 48 + fn main() { 49 + println!("Hello, slides!"); 50 + } 51 + ``` 52 + 53 + Supports multiple languages with syntax highlighting. 54 + 55 + --- 56 + 57 + ## Lists and Formatting 58 + 59 + - Unordered lists with bullets 60 + - **Bold text** for emphasis 61 + - *Italic text* for style 62 + - `inline code` for commands 63 + 64 + --- 65 + 66 + # Thank You 67 + 68 + Questions? 69 + ```` 70 + 71 + ## Presenting Your Slides 72 + 73 + Run the interactive TUI presenter: 74 + 75 + ```bash 76 + slides present presentation.md 77 + ``` 78 + 79 + ### Navigation Keys 80 + 81 + - `→`, `j`, `Space`, `n` - Next slide 82 + - `←`, `k`, `p` - Previous slide 83 + - `0-9` - Jump to slide (single digit) 84 + - `Shift+N` - Toggle speaker notes 85 + - `q`, `Ctrl+C`, `Esc` - Quit presentation 86 + 87 + ## Printing to Stdout 88 + 89 + Print all slides to stdout with formatting: 90 + 91 + ```bash 92 + slides print presentation.md 93 + ``` 94 + 95 + Adjust output width: 96 + 97 + ```bash 98 + slides print presentation.md --width 100 99 + ``` 100 + 101 + Use a specific theme: 102 + 103 + ```bash 104 + slides print presentation.md --theme dark 105 + ``` 106 + 107 + ## Slide Separators 108 + 109 + Slides are separated by three dashes on a line by themselves: 110 + 111 + ```markdown 112 + # Slide 1 113 + 114 + Content here 115 + 116 + --- 117 + 118 + # Slide 2 119 + 120 + More content 121 + ``` 122 + 123 + ## Front Matter 124 + 125 + Optional metadata at the start of your file: 126 + 127 + YAML format: 128 + 129 + ```yaml 130 + --- 131 + theme: dark 132 + author: Jane Doe 133 + --- 134 + ``` 135 + 136 + TOML format: 137 + 138 + ```toml 139 + +++ 140 + theme = "monokai" 141 + author = "John Smith" 142 + +++ 143 + ``` 144 + 145 + ## Supported Markdown 146 + 147 + Currently supported: 148 + 149 + - Headings (H1-H6) 150 + - Paragraphs with inline formatting (bold, italic, strikethrough, code) 151 + - Code blocks with language tags 152 + - Lists (ordered and unordered with nesting) 153 + - Horizontal rules 154 + - Blockquotes 155 + - Tables 156 + 157 + ## Speaker Notes 158 + 159 + Add speaker notes to any slide using the `::: notes` directive: 160 + 161 + ```markdown 162 + # Your Slide Title 163 + 164 + Main content visible to the audience. 165 + 166 + ::: notes 167 + These are your speaker notes. 168 + Press Shift+N to toggle their visibility. 169 + They appear in a separate panel during presentation. 170 + ::: 171 + ``` 172 + 173 + ## Status Bar 174 + 175 + The status bar at the bottom displays: 176 + 177 + - Filename of the current presentation 178 + - Current slide number / Total slides 179 + - Active theme name 180 + - Navigation hints 181 + - Notes visibility indicator (✓ when shown) 182 + - Elapsed presentation time (HH:MM:SS) 183 + 184 + ## Environment Variables 185 + 186 + Customize defaults with environment variables: 187 + 188 + ```bash 189 + # Set default theme 190 + export SLIDES_THEME=dark 191 + 192 + # Set default author (used if not in frontmatter) 193 + export USER=YourName 194 + ```
+436
examples/learn-rust.md
··· 1 + --- 2 + theme: default 3 + author: Learn Rust 4 + --- 5 + 6 + # Learn Rust 7 + 8 + A quick tour through Rust fundamentals 9 + 10 + --- 11 + 12 + ## Comments 13 + 14 + Rust supports multiple comment styles: 15 + 16 + ```rust 17 + // Line comments look like this 18 + // and extend multiple lines 19 + 20 + /* Block comments 21 + /* can be nested. */ */ 22 + 23 + /// Documentation comments support markdown 24 + /// # Examples 25 + /// ``` 26 + /// let five = 5 27 + /// ``` 28 + ``` 29 + 30 + --- 31 + 32 + ## Functions 33 + 34 + Functions use `fn` keyword with type annotations: 35 + 36 + ```rust 37 + fn add2(x: i32, y: i32) -> i32 { 38 + // Implicit return (no semicolon) 39 + x + y 40 + } 41 + ``` 42 + 43 + **Key points:** 44 + 45 + - `i32` is a 32-bit signed integer 46 + - Last expression without `;` is the return value 47 + - Function parameters must have type annotations 48 + 49 + --- 50 + 51 + ## Variables 52 + 53 + Rust has immutable bindings by default: 54 + 55 + ```rust 56 + // Immutable binding 57 + let x: i32 = 1; 58 + 59 + // Type inference works most of the time 60 + let implicit_x = 1; 61 + let implicit_f = 1.3; 62 + 63 + // Mutable variable 64 + let mut mutable = 1; 65 + mutable = 4; 66 + mutable += 2; 67 + ``` 68 + 69 + --- 70 + 71 + ## Numbers 72 + 73 + Integer and float types with suffixes: 74 + 75 + ```rust 76 + // Integer/float suffixes 77 + let y: i32 = 13i32; 78 + let f: f64 = 1.3f64; 79 + 80 + // Arithmetic 81 + let sum = x + y + 13; 82 + ``` 83 + 84 + --- 85 + 86 + ## Strings 87 + 88 + Two main string types in Rust: 89 + 90 + ```rust 91 + // String slice (&str) - immutable view 92 + let x: &str = "hello world!"; 93 + 94 + // String - heap-allocated, growable 95 + let s: String = "hello world".to_string(); 96 + 97 + // String slice from String 98 + let s_slice: &str = &s; 99 + 100 + // Printing 101 + println!("{} {}", f, x); 102 + ``` 103 + 104 + --- 105 + 106 + ## Arrays and Vectors 107 + 108 + Fixed-size arrays and dynamic vectors: 109 + 110 + ```rust 111 + // Fixed-size array 112 + let four_ints: [i32; 4] = [1, 2, 3, 4]; 113 + 114 + // Dynamic vector 115 + let mut vector: Vec<i32> = vec![1, 2, 3, 4]; 116 + vector.push(5); 117 + 118 + // Slice - immutable view 119 + let slice: &[i32] = &vector; 120 + 121 + // Debug printing 122 + println!("{:?} {:?}", vector, slice); 123 + ``` 124 + 125 + --- 126 + 127 + ## Tuples 128 + 129 + Fixed-size sets of values of possibly different types: 130 + 131 + ```rust 132 + // Tuple declaration 133 + let x: (i32, &str, f64) = (1, "hello", 3.4); 134 + 135 + // Destructuring 136 + let (a, b, c) = x; 137 + println!("{} {} {}", a, b, c); // 1 hello 3.4 138 + 139 + // Indexing 140 + println!("{}", x.1); // hello 141 + ``` 142 + 143 + --- 144 + 145 + ## Structs 146 + 147 + Custom data types with named fields: 148 + 149 + ```rust 150 + struct Point { 151 + x: i32, 152 + y: i32, 153 + } 154 + 155 + let origin: Point = Point { x: 0, y: 0 }; 156 + 157 + // Tuple struct (unnamed fields) 158 + struct Point2(i32, i32); 159 + let origin2 = Point2(0, 0); 160 + ``` 161 + 162 + --- 163 + 164 + ## Enums 165 + 166 + Enums can have variants with or without data: 167 + 168 + ```rust 169 + // Basic C-like enum 170 + enum Direction { 171 + Left, 172 + Right, 173 + Up, 174 + Down, 175 + } 176 + 177 + let up = Direction::Up; 178 + 179 + // Enum with fields 180 + enum OptionalI32 { 181 + AnI32(i32), 182 + Nothing, 183 + } 184 + 185 + let two: OptionalI32 = OptionalI32::AnI32(2); 186 + ``` 187 + 188 + --- 189 + 190 + ## Generics 191 + 192 + Type parameters for reusable code: 193 + 194 + ```rust 195 + struct Foo<T> { bar: T } 196 + 197 + enum Optional<T> { 198 + SomeVal(T), 199 + NoVal, 200 + } 201 + ``` 202 + 203 + The standard library provides `Option<T>` for optional values, replacing null pointers. 204 + 205 + --- 206 + 207 + ## Methods 208 + 209 + Functions associated with types: 210 + 211 + ```rust 212 + impl<T> Foo<T> { 213 + // Borrowed self 214 + fn bar(&self) -> &T { 215 + &self.bar 216 + } 217 + 218 + // Mutably borrowed self 219 + fn bar_mut(&mut self) -> &mut T { 220 + &mut self.bar 221 + } 222 + 223 + // Consumed self 224 + fn into_bar(self) -> T { 225 + self.bar 226 + } 227 + } 228 + ``` 229 + 230 + --- 231 + 232 + ## Traits 233 + 234 + Interfaces that define shared behavior: 235 + 236 + ```rust 237 + trait Frobnicate<T> { 238 + fn frobnicate(self) -> Option<T>; 239 + } 240 + 241 + impl<T> Frobnicate<T> for Foo<T> { 242 + fn frobnicate(self) -> Option<T> { 243 + Some(self.bar) 244 + } 245 + } 246 + 247 + let foo = Foo { bar: 1 }; 248 + println!("{:?}", foo.frobnicate()); // Some(1) 249 + ``` 250 + 251 + --- 252 + 253 + ## Pattern Matching 254 + 255 + Powerful control flow with `match`: 256 + 257 + ```rust 258 + let foo = OptionalI32::AnI32(1); 259 + match foo { 260 + OptionalI32::AnI32(n) => println!("it's an i32: {}", n), 261 + OptionalI32::Nothing => println!("it's nothing!"), 262 + } 263 + ``` 264 + 265 + --- 266 + 267 + ## Advanced Pattern Matching 268 + 269 + Destructure and use guards: 270 + 271 + ```rust 272 + struct FooBar { x: i32, y: OptionalI32 } 273 + let bar = FooBar { x: 15, y: OptionalI32::AnI32(32) }; 274 + 275 + match bar { 276 + FooBar { x: 0, y: OptionalI32::AnI32(0) } => 277 + println!("The numbers are zero!"), 278 + FooBar { x: n, y: OptionalI32::AnI32(m) } if n == m => 279 + println!("The numbers are the same"), 280 + FooBar { x: n, y: OptionalI32::AnI32(m) } => 281 + println!("Different numbers: {} {}", n, m), 282 + FooBar { x: _, y: OptionalI32::Nothing } => 283 + println!("The second number is Nothing!"), 284 + } 285 + ``` 286 + 287 + --- 288 + 289 + ## For Loops 290 + 291 + Iterate over arrays and ranges: 292 + 293 + ```rust 294 + // Array iteration 295 + let array = [1, 2, 3]; 296 + for i in array { 297 + println!("{}", i); 298 + } 299 + 300 + // Range iteration 301 + for i in 0u32..10 { 302 + print!("{} ", i); 303 + } 304 + // prints: 0 1 2 3 4 5 6 7 8 9 305 + ``` 306 + 307 + --- 308 + 309 + ## If Expressions 310 + 311 + `if` can be used as an expression: 312 + 313 + ```rust 314 + if 1 == 1 { 315 + println!("Maths is working!"); 316 + } else { 317 + println!("Oh no..."); 318 + } 319 + 320 + // if as expression 321 + let value = if true { 322 + "good" 323 + } else { 324 + "bad" 325 + }; 326 + ``` 327 + 328 + --- 329 + 330 + ## Loops 331 + 332 + Multiple loop constructs: 333 + 334 + ```rust 335 + // while loop 336 + while condition { 337 + println!("Looping..."); 338 + break // Exit the loop 339 + } 340 + 341 + // Infinite loop 342 + loop { 343 + println!("Hello!"); 344 + break // Must break explicitly 345 + } 346 + ``` 347 + 348 + --- 349 + 350 + ## Owned Pointers (Box) 351 + 352 + `Box<T>` provides heap allocation with single ownership: 353 + 354 + ```rust 355 + let mut mine: Box<i32> = Box::new(3); 356 + *mine = 5; // dereference 357 + 358 + // Ownership transfer (move) 359 + let mut now_its_mine = mine; 360 + *now_its_mine += 2; 361 + 362 + println!("{}", now_its_mine); // 7 363 + // println!("{}", mine); // Error! moved 364 + ``` 365 + 366 + When `Box` goes out of scope, memory is automatically deallocated. 367 + 368 + --- 369 + 370 + ## Immutable References 371 + 372 + Borrowing without transferring ownership: 373 + 374 + ```rust 375 + let mut var = 4; 376 + var = 3; 377 + let ref_var: &i32 = &var; 378 + 379 + println!("{}", var); // Still works! 380 + println!("{}", *ref_var); // 3 381 + 382 + // var = 5; // Error! var is borrowed 383 + // *ref_var = 6; // Error! immutable reference 384 + 385 + ref_var; // Use the reference 386 + var = 2; // Borrow ended, can mutate again 387 + ``` 388 + 389 + --- 390 + 391 + ## Mutable References 392 + 393 + Exclusive mutable access: 394 + 395 + ```rust 396 + let mut var2 = 4; 397 + let ref_var2: &mut i32 = &mut var2; 398 + *ref_var2 += 2; 399 + 400 + println!("{}", *ref_var2); // 6 401 + 402 + // var2 = 2; // Error! var2 is mutably borrowed 403 + 404 + ref_var2; // Use ends here 405 + // Now var2 can be used again 406 + ``` 407 + 408 + **Key rule:** Either many immutable references OR one mutable reference. 409 + 410 + --- 411 + 412 + ## Memory Safety 413 + 414 + Rust's borrow checker ensures: 415 + 416 + - No use after free 417 + - No double free 418 + - No data races 419 + - No dangling pointers 420 + 421 + All at **compile time** with **zero runtime cost**. 422 + 423 + --- 424 + 425 + ## Next Steps 426 + 427 + - Explore the [Rust Book](https://doc.rust-lang.org/book/) 428 + - Try [Rust by Example](https://doc.rust-lang.org/rust-by-example/) 429 + - Practice with [Rustlings](https://github.com/rust-lang/rustlings) 430 + - Join the [Rust community](https://www.rust-lang.org/community) 431 + 432 + --- 433 + 434 + ## Thank You 435 + 436 + Happy Rusting!