Skip to content

Commit

Permalink
feat: watch file code snippet files (#372)
Browse files Browse the repository at this point in the history
This makes it so that code snippets that use `file` and point to an
external file are watched for modifications.
  • Loading branch information
mfontanini authored Sep 21, 2024
2 parents 0056105 + 74205db commit 43bc5ff
Show file tree
Hide file tree
Showing 26 changed files with 202 additions and 108 deletions.
2 changes: 1 addition & 1 deletion src/custom.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
GraphicsMode,
input::user::KeyBinding,
media::{emulator::TerminalEmulator, kitty::KittyMode},
processing::code::SnippetLanguage,
GraphicsMode,
};
use clap::ValueEnum;
use schemars::JsonSchema;
Expand Down
2 changes: 1 addition & 1 deletion src/demo.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
ImageRegistry, MarkdownParser, PresentationBuilderOptions, PresentationTheme, Resources, Themes, ThirdPartyRender,
execute::SnippetExecutor,
input::{
source::Command,
Expand All @@ -8,7 +9,6 @@ use crate::{
presentation::Presentation,
processing::builder::{BuildError, PresentationBuilder},
render::{draw::TerminalDrawer, terminal::TerminalWrite},
ImageRegistry, MarkdownParser, PresentationBuilderOptions, PresentationTheme, Resources, Themes, ThirdPartyRender,
};
use std::{io, rc::Rc};

Expand Down
6 changes: 3 additions & 3 deletions src/export.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
MarkdownParser, PresentationTheme, Resources,
custom::KeyBindingsConfig,
execute::SnippetExecutor,
markdown::parse::ParseError,
Expand All @@ -11,10 +12,9 @@ use crate::{
render::properties::WindowSize,
third_party::ThirdPartyRender,
tools::{ExecutionError, ThirdPartyTools},
MarkdownParser, PresentationTheme, Resources,
};
use base64::{engine::general_purpose::STANDARD, Engine};
use image::{codecs::png::PngEncoder, DynamicImage, ImageEncoder, ImageError};
use base64::{Engine, engine::general_purpose::STANDARD};
use image::{DynamicImage, ImageEncoder, ImageError, codecs::png::PngEncoder};
use semver::Version;
use serde::Serialize;
use std::{
Expand Down
35 changes: 0 additions & 35 deletions src/input/fs.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/input/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pub(crate) mod fs;
pub(crate) mod source;
pub(crate) mod user;
24 changes: 8 additions & 16 deletions src/input/source.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,32 @@
use super::{
fs::PresentationFileWatcher,
user::{CommandKeyBindings, KeyBindingsValidationError, UserInput},
};
use super::user::{CommandKeyBindings, KeyBindingsValidationError, UserInput};
use crate::custom::KeyBindingsConfig;
use serde::Deserialize;
use std::{io, path::PathBuf, time::Duration};
use std::{io, time::Duration};
use strum::EnumDiscriminants;

/// The source of commands.
///
/// This expects user commands as well as watches over the presentation file to reload if it that
/// happens.
pub struct CommandSource {
watcher: PresentationFileWatcher,
user_input: UserInput,
}

impl CommandSource {
/// Create a new command source over the given presentation path.
pub fn new<P: Into<PathBuf>>(
presentation_path: P,
config: KeyBindingsConfig,
) -> Result<Self, KeyBindingsValidationError> {
let watcher = PresentationFileWatcher::new(presentation_path);
pub fn new(config: KeyBindingsConfig) -> Result<Self, KeyBindingsValidationError> {
let bindings = CommandKeyBindings::try_from(config)?;
Ok(Self { watcher, user_input: UserInput::new(bindings) })
Ok(Self { user_input: UserInput::new(bindings) })
}

/// Try to get the next command.
///
/// This attempts to get a command and returns `Ok(None)` on timeout.
pub(crate) fn try_next_command(&mut self) -> io::Result<Option<Command>> {
if let Some(command) = self.user_input.poll_next_command(Duration::from_millis(250))? {
return Ok(Some(command));
};
if self.watcher.has_modifications()? { Ok(Some(Command::Reload)) } else { Ok(None) }
match self.user_input.poll_next_command(Duration::from_millis(250))? {
Some(command) => Ok(Some(command)),
None => Ok(None),
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/input/user.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::source::{Command, CommandDiscriminants};
use crate::custom::KeyBindingsConfig;
use crossterm::event::{poll, read, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, poll, read};
use schemars::JsonSchema;
use serde_with::DeserializeFromStr;
use std::{fmt, io, iter, mem, str::FromStr, time::Duration};
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::{error::ErrorKind, CommandFactory, Parser};
use clap::{CommandFactory, Parser, error::ErrorKind};
use comrak::Arena;
use directories::ProjectDirs;
use presenterm::{
Expand Down Expand Up @@ -270,7 +270,7 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
println!("{}", serde_json::to_string_pretty(&meta)?);
}
} else {
let commands = CommandSource::new(&path, config.bindings.clone())?;
let commands = CommandSource::new(config.bindings.clone())?;
options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. });

let options = PresenterOptions {
Expand Down
3 changes: 2 additions & 1 deletion src/markdown/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ use crate::{
style::TextStyle,
};
use comrak::{
Arena, ComrakOptions,
arena_tree::Node,
format_commonmark,
nodes::{
Ast, AstNode, ListDelimType, ListType, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeList, NodeValue,
Sourcepos,
},
parse_document, Arena, ComrakOptions,
parse_document,
};
use std::{
cell::RefCell,
Expand Down
4 changes: 2 additions & 2 deletions src/media/ascii.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties};
use crossterm::{
QueueableCommand,
cursor::{MoveRight, MoveToColumn},
style::{Color, Stylize},
QueueableCommand,
};
use image::{imageops::FilterType, DynamicImage, GenericImageView, Pixel, Rgba};
use image::{DynamicImage, GenericImageView, Pixel, Rgba, imageops::FilterType};
use itertools::Itertools;
use std::{fs, ops::Deref};

Expand Down
2 changes: 1 addition & 1 deletion src/media/emulator.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::kitty::local_mode_supported;
use crate::{media::kitty::KittyMode, GraphicsMode};
use crate::{GraphicsMode, media::kitty::KittyMode};
use std::env;
use strum::IntoEnumIterator;

Expand Down
4 changes: 2 additions & 2 deletions src/media/iterm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties};
use base64::{engine::general_purpose::STANDARD, Engine};
use image::{codecs::png::PngEncoder, GenericImageView, ImageEncoder};
use base64::{Engine, engine::general_purpose::STANDARD};
use image::{GenericImageView, ImageEncoder, codecs::png::PngEncoder};
use std::{env, fs, path::Path};

pub(crate) struct ItermResource {
Expand Down
8 changes: 4 additions & 4 deletions src/media/kitty.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties};
use crate::style::Color;
use base64::{engine::general_purpose::STANDARD, Engine};
use base64::{Engine, engine::general_purpose::STANDARD};
use console::{Key, Term};
use crossterm::{cursor::MoveToColumn, style::SetForegroundColor, QueueableCommand};
use image::{codecs::gif::GifDecoder, AnimationDecoder, Delay, DynamicImage, EncodableLayout, ImageReader, RgbaImage};
use crossterm::{QueueableCommand, cursor::MoveToColumn, style::SetForegroundColor};
use image::{AnimationDecoder, Delay, DynamicImage, EncodableLayout, ImageReader, RgbaImage, codecs::gif::GifDecoder};
use rand::Rng;
use std::{
fmt,
Expand All @@ -12,7 +12,7 @@ use std::{
path::{Path, PathBuf},
sync::atomic::{AtomicU32, Ordering},
};
use tempfile::{tempdir, NamedTempFile, TempDir};
use tempfile::{NamedTempFile, TempDir, tempdir};

const IMAGE_PLACEHOLDER: &str = "\u{10EEEE}";
const DIACRITICS: &[u32] = &[
Expand Down
2 changes: 1 addition & 1 deletion src/media/sixel.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::printer::{
CreatePrinterError, PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties,
};
use image::{imageops::FilterType, DynamicImage, GenericImageView};
use image::{DynamicImage, GenericImageView, imageops::FilterType};
use sixel_rs::{
encoder::{Encoder, QuickFrameBuilder},
optflags::EncodePolicy,
Expand Down
22 changes: 17 additions & 5 deletions src/presenter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ impl<'a> Presenter<'a> {

/// Run a presentation.
pub fn present(mut self, path: &Path) -> Result<(), PresentationError> {
if matches!(self.options.mode, PresentMode::Development) {
self.resources.watch_presentation_file(path.to_path_buf());
}
self.state = PresenterState::Presenting(Presentation::from(vec![]));
self.try_reload(path, true);

Expand All @@ -104,11 +107,18 @@ impl<'a> Presenter<'a> {
if self.poll_async_renders()? {
self.render(&mut drawer)?;
}
let Some(command) = self.commands.try_next_command()? else {
if self.check_async_error() {
break;
}
continue;

let command = match self.commands.try_next_command()? {
Some(command) => command,
_ => match self.resources.resources_modified() {
true => Command::Reload,
false => {
if self.check_async_error() {
break;
}
continue;
}
},
};
match self.apply_command(command) {
CommandSideEffect::Exit => return Ok(()),
Expand Down Expand Up @@ -262,6 +272,7 @@ impl<'a> Presenter<'a> {
return;
}
self.slides_with_pending_async_renders.clear();
self.resources.clear_watches();
match self.load_presentation(path) {
Ok(mut presentation) => {
let current = self.state.presentation();
Expand All @@ -280,6 +291,7 @@ impl<'a> Presenter<'a> {
// file.
PresentMode::Export => presentation.trigger_all_async_renders(),
};

self.state = self.validate_overflows(presentation);
}
Err(e) => {
Expand Down
17 changes: 9 additions & 8 deletions src/processing/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1549,10 +1549,11 @@ mod test {
#[test]
fn iterate_list_starting_from_other() {
let list = ListIterator::new(
vec![
ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered },
ListItem { depth: 0, contents: "1".into(), item_type: ListItemType::Unordered },
],
vec![ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, ListItem {
depth: 0,
contents: "1".into(),
item_type: ListItemType::Unordered,
}],
3,
);
let expected_indexes = [3, 4];
Expand Down Expand Up @@ -1639,10 +1640,10 @@ mod test {

#[test]
fn implicit_slide_ends_with_front_matter() {
let elements = vec![
MarkdownElement::FrontMatter("theme:\n name: light".into()),
MarkdownElement::SetexHeading { text: "hi".into() },
];
let elements =
vec![MarkdownElement::FrontMatter("theme:\n name: light".into()), MarkdownElement::SetexHeading {
text: "hi".into(),
}];
let options = PresentationBuilderOptions { implicit_slide_ends: true, ..Default::default() };
let slides = build_presentation_with_options(elements, options).into_slides();
assert_eq!(slides.len(), 1);
Expand Down
15 changes: 9 additions & 6 deletions src/processing/code.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::padding::NumberPadder;
use crate::{
PresentationTheme,
markdown::{
elements::{Percent, PercentParseError},
text::{WeightedText, WeightedTextBlock},
Expand All @@ -11,7 +12,6 @@ use crate::{
},
style::{Color, TextStyle},
theme::{Alignment, CodeBlockStyle},
PresentationTheme,
};
use serde::Deserialize;
use serde_with::DeserializeFromStr;
Expand Down Expand Up @@ -622,8 +622,8 @@ pub(crate) struct ExternalFile {
#[cfg(test)]
mod test {
use super::*;
use rstest::rstest;
use Highlight::*;
use rstest::rstest;

fn parse_language(input: &str) -> SnippetLanguage {
let (language, _) = CodeBlockParser::parse_block_info(input).expect("parse failed");
Expand Down Expand Up @@ -725,10 +725,13 @@ mod test {
#[test]
fn highlight_line_range() {
let attributes = parse_attributes("bash { 1, 2-4,6 , all , 10 - 12 }");
assert_eq!(
attributes.highlight_groups,
&[HighlightGroup::new(vec![Single(1), Range(2..5), Single(6), All, Range(10..13)])]
);
assert_eq!(attributes.highlight_groups, &[HighlightGroup::new(vec![
Single(1),
Range(2..5),
Single(6),
All,
Range(10..13)
])]);
}

#[test]
Expand Down
5 changes: 2 additions & 3 deletions src/processing/execution.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crossterm::{
cursor,
ExecutableCommand, cursor,
terminal::{self, disable_raw_mode, enable_raw_mode},
ExecutableCommand,
};

use super::separator::{RenderSeparator, SeparatorWidth};
use crate::{
PresentationTheme,
ansi::AnsiSplitter,
execute::{ExecutionHandle, ExecutionState, ProcessStatus, SnippetExecutor},
markdown::{
Expand All @@ -17,7 +17,6 @@ use crate::{
render::{properties::WindowSize, terminal::should_hide_cursor},
style::{Colors, TextStyle},
theme::{Alignment, ExecutionStatusBlockStyle, Margin},
PresentationTheme,
};
use std::{
cell::RefCell,
Expand Down
11 changes: 4 additions & 7 deletions src/processing/footer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,10 @@ impl AsRenderOperations for FooterGenerator {
let columns_ratio = (total_columns as f64 * progress_ratio).ceil();
let bar = character.repeat(columns_ratio as usize);
let bar = Text::new(bar, TextStyle::default().colors(*colors));
vec![
RenderOperation::JumpToBottomRow { index: 0 },
RenderOperation::RenderText {
line: vec![bar].into(),
alignment: Alignment::Left { margin: Margin::Fixed(0) },
},
]
vec![RenderOperation::JumpToBottomRow { index: 0 }, RenderOperation::RenderText {
line: vec![bar].into(),
alignment: Alignment::Left { margin: Margin::Fixed(0) },
}]
}
FooterStyle::Empty => vec![],
}
Expand Down
Loading

0 comments on commit 43bc5ff

Please sign in to comment.