github.com/nogginly/termify.cr

A terminal rendering support shard for Crystal applications

0 stars
1 dependent
License: MPL-2.0

Installation

# Add this to your shard.yml
dependencies:
  termify:
    github: nogginly/termify.cr
    version: ~> 0.6.3

Then run:

shards install

shard.yml

Crystal
>= 1.19, < 2.0
License
MPL-2.0
Author
nogginly
Targets
  • hello from samples/hello.cr
  • md2term from samples/md2term.cr
  • scrollcat from samples/scrollcat.cr

Dependencies

Runtime Dependencies

Development Dependencies

  • ameba>= 1.7.0github: crystal-ameba/amebadev
  • spectator*gitlab: arctic-fox/spectatordev

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

Termify.cr

A Crystal shard for rendering Markdown to terminal, with an emphasis on streaming for the most part.

AI Usage

See DISCLOSURE for how I used AI for this project.

Installation

  1. Add the dependency to your shard.yml:

    dependencies:
      termify:
        github: nogginly/termify.cr
  2. Run shards install

Usage

require "termify"

Setup platform Terminal

term = Termify.terminal
# Enable VT console
term.setup_console
# Ensure we restore terminal console settings
at_exit { term.restore_console }

Terminal stands on its own. It needs nothing from the Markdown renderer, so a plain CLI tool can use it, and the ScrollRegion below, without rendering any Markdown at all.

Scrolling within part of the screen

ScrollRegion confines output to a fixed number of lines, so streaming text scrolls within them while the rest of the screen stays put. Height is clamped to 3..10.

region = Termify::ScrollRegion.new(term, 5)
region.start
# ... anything printed now scrolls within those 5 lines
region.stop

See scrollcat for a working example.

Rendering Markdown

Termify.render_markdown do |io|
  io.puts "# Hello"
  io.puts "_Hello_, **World!**"
end

Custom stylesheet

Here's an example of a custom stylesheet (from md2term sample):

STYLESHEET = Termify.markdown_stylesheet({
  "h1"         => {
    bold: true,
    line_prefix: "# ".colorize(:dark_gray).to_s,
    newline_after: true
  },
  "h2"         => {bold: true, line_prefix: "## ".colorize(:dark_gray).to_s, newline_after: true, newline_before: true},
  "h3"         => {bold: true, line_prefix: "### ".colorize(:dark_gray).to_s, newline_after: true, newline_before: true},
  "h4"         => {bold: true, fg: "white", line_prefix: "#### ".colorize(:dark_gray).to_s},
  "h5"         => {bold: true},
  "h6"         => {bold: true},
  "code_block" => {
    fg: :light_cyan, line_number_format: "%3d: ",
    highlight_theme: "catppuccin-macchiato",
    gutter_style: {dim: true},
  },
  "code_inline" => {fg: :red},
  "html_tag"    => {dim: true},
  "block_html"  => {dim: true},
  "list_item"   => {newline_after: true, newline_before: true},
  "block_quote" => {line_prefix: "│ ", newline_after: true, newline_before: true, bg: "Grey7"},
})

Termify.render_markdown(STDOUT, STYLESHEET) do |md_io|
  # send your Markdown to `md_io`
end

Progress while gathering

Most Markdown renders as it arrives. Two things cannot: a table needs every row before it can size its columns, and a code block needs its whole body before it can be syntax highlighted. When Markdown is streaming in slowly, output stops for as long as that takes, and a reader cannot tell if the app has locked up.

Pass an on_gather handler to hear about it so you can control what to show and when.

handler = ->(event : Termify::Markdown::GatherEvent) {
  case event.phase
  in .started?    then spinner.start(event.kind)
  in .progressed? then spinner.count = event.units # rows, or lines
  in .finished?   then spinner.stop
  end
}

Termify.render_markdown(STDOUT, STYLESHEET, on_gather: handler) do |md_io|
  # send your Markdown to `md_io`
end

Nothing is written between Started and Finished, so a spinner drawn on the current line can be cleared before the finished block lands on it. A Started is always followed by a Finished, including when a document ends mid-table, and a handler that raises will not stop the render.

See md2term for a working spinner, which reads slowly enough that you can watch it.

Credits

  • Tablo, for the table rendering
  • Tartrazine, for the code syntax highlighting

Development

See DEVELOPMENT

Contributions, by invitation!

With apologies, at this time contributions are by invitation only and limited to people I know and see often.

These are early days for Termify and I am busy with family and work.

At this time I want to work on this at a manageable pace.