presenterm 0.16.1

A terminal slideshow presentation tool
use crate::{
    ImageRegistry, MarkdownParser, PresentationBuilderOptions, Resources, ThemeOptions, Themes, ThirdPartyRender,
    code::execute::SnippetExecutor,
    commands::{
        keyboard::{CommandKeyBindings, KeyboardListener},
        listener::Command,
    },
    markdown::elements::MarkdownElement,
    presentation::{
        Presentation,
        builder::{PresentationBuilder, error::BuildError},
    },
    render::TerminalDrawer,
    terminal::emulator::TerminalEmulator,
    theme::raw::PresentationTheme,
};
use std::{io, sync::Arc};

const PRESENTATION: &str = r#"
# Header 1
## Header 2
### Header 3
#### Header 4
##### Header 5
###### Header 6

```rust
fn greet(name: &str) -> String {
    format!("hi {name}")
}
````

* **bold text**
* _italics_
    * `some inline code`
    * ~strikethrough~

> a block quote

<!-- end_slide -->
<!-- end_slide -->
"#;

pub struct ThemesDemo {
    themes: Themes,
    input: KeyboardListener,
    drawer: TerminalDrawer,
}

impl ThemesDemo {
    pub fn new(themes: Themes, bindings: CommandKeyBindings) -> io::Result<Self> {
        let input = KeyboardListener::new(bindings);
        let drawer = TerminalDrawer::new(Default::default(), Default::default())?;
        Ok(Self { themes, input, drawer })
    }

    pub fn run(mut self) -> Result<(), Box<dyn std::error::Error>> {
        let arena = Default::default();
        let parser = MarkdownParser::new(&arena);
        let elements = parser.parse(PRESENTATION).expect("broken demo presentation");
        let mut presentations = Vec::new();
        for theme_name in self.themes.presentation.theme_names() {
            let theme = self.themes.presentation.load_by_name(&theme_name).expect("theme not found");
            let presentation = self.build(&elements, &theme_name, &theme)?;
            presentations.push(presentation);
        }
        let mut current = 0;
        loop {
            self.drawer.render_operations(presentations[current].current_slide().iter_visible_operations())?;

            let command = self.next_command()?;
            match command {
                DemoCommand::Next => current = (current + 1).min(presentations.len() - 1),
                DemoCommand::Previous => current = current.saturating_sub(1),
                DemoCommand::First => current = 0,
                DemoCommand::Last => current = presentations.len() - 1,
                DemoCommand::Exit => return Ok(()),
            };
        }
    }

    fn next_command(&mut self) -> io::Result<DemoCommand> {
        loop {
            let mut command = self.input.next_command()?;
            while command.is_none() {
                command = self.input.next_command()?;
            }
            match command.unwrap() {
                Command::Next => return Ok(DemoCommand::Next),
                Command::Previous => return Ok(DemoCommand::Previous),
                Command::FirstSlide => return Ok(DemoCommand::First),
                Command::LastSlide => return Ok(DemoCommand::Last),
                Command::Exit => return Ok(DemoCommand::Exit),
                _ => continue,
            }
        }
    }

    fn build(
        &self,
        base_elements: &[MarkdownElement],
        theme_name: &str,
        theme: &PresentationTheme,
    ) -> Result<Presentation, BuildError> {
        let image_registry = ImageRegistry::default();
        let resources = Resources::new("non_existent", "non_existent", image_registry.clone());
        let mut third_party = ThirdPartyRender::default();
        let options = PresentationBuilderOptions {
            theme_options: ThemeOptions { font_size_supported: TerminalEmulator::capabilities().font_size },
            ..Default::default()
        };
        let executer = Arc::new(SnippetExecutor::default());
        let bindings_config = Default::default();
        let arena = Default::default();
        let parser = MarkdownParser::new(&arena);
        let builder = PresentationBuilder::new(
            theme,
            resources,
            &mut third_party,
            executer,
            &self.themes,
            image_registry,
            bindings_config,
            &parser,
            options,
        )?;
        let mut elements = vec![MarkdownElement::SetexHeading { text: vec![format!("theme: {theme_name}").into()] }];
        elements.extend(base_elements.iter().cloned());
        builder.build_from_parsed(elements)
    }
}

enum DemoCommand {
    Next,
    Previous,
    First,
    Last,
    Exit,
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn demo_presentation() {
        let arena = Default::default();
        let parser = MarkdownParser::new(&arena);
        parser.parse(PRESENTATION).expect("broken demo presentation");
    }
}