Skip to content

Commit

Permalink
Add a way to reload a file with slides using keypress when presenting
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrmurach committed Jul 1, 2023
1 parent 405810a commit bf29fd7
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 172 deletions.
13 changes: 7 additions & 6 deletions lib/slideck/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ class CLI
desc "Present Markdown-powered slide decks in the terminal"

desc "Controls:",
" First ^",
" Go to 1..n+g",
" Last $",
" Next n, l, Right, Spacebar",
" Prev p, h, Left, Backspace",
" Quit q, Esc"
" First ^",
" Go to 1..n+g",
" Last $",
" Next n, l, Right, Spacebar",
" Prev p, h, Left, Backspace",
" Reload r, Ctrl+l",
" Quit q, Esc"

example "Start presentation",
"$ #{program} slides.md"
Expand Down
38 changes: 23 additions & 15 deletions lib/slideck/presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ module Slideck
class Presenter
# Create a Presenter
#
# @param [Array<Hash>] slides
# the slides to present
# @param [TTY::Reader] reader
# the keyboard input reader
# @param [Slideck::Renderer] renderer
Expand All @@ -17,35 +15,31 @@ class Presenter
# the tracker for slides
# @param [IO] output
# the output stream for the slides
# @param [Proc] reloader
# the metadata and slides reloader
#
# @api public
def initialize(slides, reader, renderer, tracker, output)
@slides = slides
def initialize(reader, renderer, tracker, output, &reloader)
@reader = reader
@renderer = renderer
@tracker = tracker
@output = output
@reloader = reloader
@stop = false
@buffer = []
end

# Reload presentation
#
# @example
# renderer.reload(metadata, slides)
#
# @param [Slideck::Metadata] metadata
# the slides metadata
# @param [Array<Hash>] slides
# the slides to present
# renderer.reload
#
# @return [Slideck::Presenter]
#
# @api public
def reload(metadata, slides)
@slides = slides
@renderer = @renderer.with_metadata(metadata)
@tracker = @tracker.resize(slides.size)
def reload
@metadata, @slides = *@reloader.()
@tracker = @tracker.resize(@slides.size)
self
end

Expand All @@ -58,6 +52,7 @@ def reload(metadata, slides)
#
# @api public
def start
reload
@reader.subscribe(self)
hide_cursor

Expand Down Expand Up @@ -108,7 +103,10 @@ def clear_screen
# @api private
def render_slide
@output.print @renderer.render(
@slides[@tracker.current], @tracker.current + 1, @tracker.total)
@metadata,
@slides[@tracker.current],
@tracker.current + 1,
@tracker.total)
end

# Hide cursor
Expand Down Expand Up @@ -145,6 +143,7 @@ def keypress(event)
when "$" then go_to_last
when "g" then go_to_slide
when /\d/ then add_to_buffer(event.value)
when "r" then keyctrl_l
when "q" then keyctrl_x
end
end
Expand All @@ -171,6 +170,15 @@ def keyleft(*)
alias keybackspace keyleft
alias keypage_up keyleft

# Reload presentation
#
# @return [void]
#
# @api private
def keyctrl_l(*)
reload
end

# Exit presentation
#
# @return [void]
Expand Down
77 changes: 36 additions & 41 deletions lib/slideck/renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,47 +23,30 @@ class Renderer
# the ansi codes handler
# @param [TTY::Cursor] cursor
# the cursor navigation
# @param [Slideck::Metadata] metadata
# the configuration metadata
# @param [Integer] width
# the screen width
# @param [Integer] height
# the screen height
#
# @api public
def initialize(converter, ansi, cursor, metadata, width: nil, height: nil)
def initialize(converter, ansi, cursor, width: nil, height: nil)
@converter = converter
@ansi = ansi
@cursor = cursor
@metadata = metadata
@width = width
@height = height

freeze
end

# Create a Renderer with new metadata
#
# @example
# renderer.with_metadata(metadata)
#
# @param [Slideck::Metadata] metadata
# the configuration metadata
#
# @return [Slideck::Renderer]
#
# @api public
def with_metadata(metadata)
self.class.new(@converter, @ansi, @cursor, metadata,
width: @width, height: @height)
end

# Render a slide
#
# @example
# renderer.render("slide content", 1, 5)
# renderer.render(metadata, slide, 1, 5)
#
# @param [String, nil] slide
# @param [Slideck::Metadata] metadata
# the global metadata
# @param [Hash{Symbol => Hash, String}, nil] slide
# the current slide to render
# @param [Integer] current_num
# the current slide number
Expand All @@ -73,12 +56,13 @@ def with_metadata(metadata)
# @return [String]
#
# @api public
def render(slide, current_num, num_of_slides)
def render(metadata, slide, current_num, num_of_slides)
slide_metadata = slide && slide[:metadata]
[].tap do |out|
out << render_content(slide) if slide
out << render_footer(slide_metadata)
out << render_pager(slide_metadata, current_num, num_of_slides)
out << render_content(metadata, slide) if slide
out << render_footer(metadata, slide_metadata)
out << render_pager(metadata, slide_metadata,
current_num, num_of_slides)
end.join
end

Expand All @@ -98,42 +82,49 @@ def clear

# Render slide content
#
# @param [String] slide
# @param [Slideck::Metadata] metadata
# the global metadata
# @param [Hash{Symbol => Hash, String}] slide
# the slide to render
#
# @return [String]
#
# @api private
def render_content(slide)
def render_content(metadata, slide)
alignment, margin, symbols, theme =
*select_metadata(slide[:metadata], :align, :margin, :symbols, :theme)
*select_metadata(metadata, slide[:metadata], :align, :margin,
:symbols, :theme)
converted = convert_markdown(slide[:content], margin, symbols, theme)

render_section(converted.lines, alignment, margin)
end

# Render footer
#
# @param [Slideck::Metadata] metadata
# the global metadata
# @param [Slideck::Metadata] slide_metadata
# the slide metadata
#
# @return [String]
#
# @api private
def render_footer(slide_metadata)
footer_metadata = pick_metadata(slide_metadata, :footer)
def render_footer(metadata, slide_metadata)
footer_metadata = pick_metadata(metadata, slide_metadata, :footer)
return if (text = footer_metadata[:text]).empty?

alignment = footer_metadata[:align] || @metadata.footer[:align]
alignment = footer_metadata[:align] || metadata.footer[:align]
margin, symbols, theme =
*select_metadata(slide_metadata, :margin, :symbols, :theme)
*select_metadata(metadata, slide_metadata, :margin, :symbols, :theme)
converted = convert_markdown(text, margin, symbols, theme).chomp

render_section(converted.lines, alignment, margin)
end

# Render pager
#
# @param [Slideck::Metadata] metadata
# the global metadata
# @param [Slideck::Metadata] slide_metadata
# the slide metadata
# @param [Integer] current_num
Expand All @@ -144,13 +135,13 @@ def render_footer(slide_metadata)
# @return [String]
#
# @api private
def render_pager(slide_metadata, current_num, num_of_slides)
pager_metadata = pick_metadata(slide_metadata, :pager)
def render_pager(metadata, slide_metadata, current_num, num_of_slides)
pager_metadata = pick_metadata(metadata, slide_metadata, :pager)
return if (text = pager_metadata[:text]).empty?

alignment = pager_metadata[:align] || @metadata.pager[:align]
alignment = pager_metadata[:align] || metadata.pager[:align]
margin, symbols, theme =
*select_metadata(slide_metadata, :margin, :symbols, :theme)
*select_metadata(metadata, slide_metadata, :margin, :symbols, :theme)
formatted_text = format(text, page: current_num, total: num_of_slides)
converted = convert_markdown(formatted_text, margin, symbols, theme).chomp

Expand All @@ -159,6 +150,8 @@ def render_pager(slide_metadata, current_num, num_of_slides)

# Select configuration(s) by name(s) from metadata
#
# @param [Slideck::Metadata] metadata
# the global metadata
# @param [Slideck::Metadata] slide_metadata
# the slide metadata
# @param [Array<Symbol>] names
Expand All @@ -167,14 +160,16 @@ def render_pager(slide_metadata, current_num, num_of_slides)
# @return [Array<Object>]
#
# @api private
def select_metadata(slide_metadata, *names)
def select_metadata(metadata, slide_metadata, *names)
names.each_with_object([]) do |name, selected|
selected << pick_metadata(slide_metadata, name)
selected << pick_metadata(metadata, slide_metadata, name)
end
end

# Pick configuration by name from metadata
#
# @param [Slideck::Metadata] metadata
# the global metadata
# @param [Slideck::Metadata] slide_metadata
# the slide metadata
# @param [Symbol] name
Expand All @@ -183,9 +178,9 @@ def select_metadata(slide_metadata, *names)
# @return [Hash, Slideck::Alignment, Slideck::Margin, String, Symbol]
#
# @api private
def pick_metadata(slide_metadata, name)
def pick_metadata(metadata, slide_metadata, name)
slide_metadata_item = slide_metadata && slide_metadata.send(name)
slide_metadata_item || @metadata.send(name)
slide_metadata_item || metadata.send(name)
end

# Render section with aligned lines
Expand Down
26 changes: 10 additions & 16 deletions lib/slideck/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ def initialize(screen, input, output, env)
#
# @api public
def run(filename, color: nil, watch: nil)
presenter = build_presenter(*read_slides(filename), color)
presenter = build_presenter(color) { read_slides(filename) }

if watch
listener = build_listener(presenter, filename)
listener = build_listener(filename) { presenter.reload.render }
listener.start
end

Expand Down Expand Up @@ -155,43 +155,37 @@ def build_metadata(custom_metadata, metadata_defaults)

# Build presenter
#
# @param [Slideck::Metadata] metadata
# the configuration metadata
# @param [Array<Hash>] slides
# the slides to present
# @param [String, Symbol] color
# the color display out of always, auto or never
# @param [Proc] reloader
# the metadata and slides reloader
#
# @return [Slideck::Presenter]
#
# @api private
def build_presenter(metadata, slides, color)
def build_presenter(color, &reloader)
reader = TTY::Reader.new(input: @input, output: @output, env: @env,
interrupt: :exit)
converter = Converter.new(TTY::Markdown, color: color)
renderer = Renderer.new(converter, Strings::ANSI, TTY::Cursor, metadata,
renderer = Renderer.new(converter, Strings::ANSI, TTY::Cursor,
width: @screen.width, height: @screen.height)
tracker = Tracker.for(slides.size)
Presenter.new(slides, reader, renderer, tracker, @output)
tracker = Tracker.for(0)
Presenter.new(reader, renderer, tracker, @output, &reloader)
end

# Build a listener for changes in a filename
#
# @param [Slideck::Presenter] presenter
# the presenter for slides
# @param [String] filename
# the filename with slides
#
# @return [Listen::Listener]
#
# @api private
def build_listener(presenter, filename)
def build_listener(filename)
watched_dir = File.expand_path(File.dirname(filename))
watched_file = File.expand_path(filename)
Listen.to(watched_dir) do |changed_files, _, _|
if changed_files.include?(watched_file)
presenter.reload(*read_slides(filename)).render
end
yield if changed_files.include?(watched_file)
end
end
end # Runner
Expand Down
13 changes: 7 additions & 6 deletions spec/unit/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@
Present Markdown-powered slide decks in the terminal
Controls:
First ^
Go to 1..n+g
Last $
Next n, l, Right, Spacebar
Prev p, h, Left, Backspace
Quit q, Esc
First ^
Go to 1..n+g
Last $
Next n, l, Right, Spacebar
Prev p, h, Left, Backspace
Reload r, Ctrl+l
Quit q, Esc
Options:
--color WHEN When to color output (permitted: always, auto, never)
Expand Down
Loading

0 comments on commit bf29fd7

Please sign in to comment.