github.com/nogginly/termify.cr

A terminal rendering support shard for Crystal applications

0 stars
1 dependent
License: MPL-2.0

Nothing has been indexed for 0.5.1 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

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

Then run:

shards install

shard.yml

No shard.yml has been indexed for 0.5.1. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

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.