A simple library to display static or dynamic Markdown snippets in your terminal application, with skin isolation.
Based on crossterm so works on most terminals (even on windows).
The goal isn't to display any markdown text with its various extensions (a terminal isn't really fit for that). The goal is rather to improve the display of texts in a terminal application when we want both the text and the skin to be easily configured.
Termimad also includes a few utilities helping efficient managing of events and user input in a multithread application.
Wrapping, table balancing, and scrolling are essential features of Termimad.
A text or a table can be displayed in an a priori unknown part of the screen, scrollable if desired, with a dynamically discovered width.
For example this markdown:
|:-:|:-:|-
|**feature**|**supported**|**details**|
|-:|:-:|-
| tables | yes | pipe based, with or without alignments
| italic, bold | yes | star based |
| inline code | yes | `with backquotes` (it works in tables too)
| code bloc | yes |with tabs or code fences
| syntax coloring | no |
| crossed text | ~~not yet~~ | wait... now it works `~~like this~~`
| horizontal rule | yes | Use 3 or more dashes (`---`)
| lists | yes|* unordered lists supported
| | |* ordered lists *not* supported
| quotes | yes |> What a wonderful time to be alive!
| links | no | (but your terminal already handles raw URLs)
|-
will give different results depending on the width:
[dependencies]
termimad = "0.10"
termimad::print_inline("**some** *nested **style*** and `some(code)`");
or
print!("{}", termimad::inline("**some** *nested **style*** and `some(code)`"));
Result:
Inline snippets are one line or less.
let mut skin = MadSkin::default();
skin.bold.set_fg(Yellow);
skin.print_inline("*Hey* **World!** Here's `some(code)`");
skin.paragraph.set_fgbg(Magenta, rgb(30, 30, 40));
skin.italic.add_attr(Underlined);
println!("\nand now {}\n", skin.inline("a little *too much* **style!** (and `some(code)` too)"));
Result:
Texts can be several lines. Tables and code blocks are automatically aligned, justified and consistently wrapped.
skin.print_text("# title\n* a list item\n* another item");
The code for this example is in examples/scrollable. To read the whole text just do
cargo run --example scrollable
In order to separate the rendering format from the content, you may want to have some constant markdown and fill some placeholders with dynamic items.
The format!
macro is not always a good solution for that because you may not be sure the content is free of characters which may mess the markdown.
A solution is to use one of the templating functions or macros.
A template is to markdown what a prepared statement is to SQL: interpreted once and preventing the content to be interpreted as parts of the structure.
Example:
mad_print_inline!(
&skin,
"**$0 formula:** *$1*", // the markdown template, interpreted once
"Disk", // fills $0
"2*π*r", // fills $1. Note that the stars don't mess the markdown
);
Main difference with using skin.print_inline(format!( ... ))
to build some markdown and parse it:
- the markdown parsing and template building are done only once (using
lazy_static
internally) - the given values aren't interpreted as markdown fragments and don't impact the style
- arguments can be omited, repeated, given in any order
- no support for fmt parameters or arguments other than
&str
(in the current version)
Inline templates are especially convenient combined with automated expansion or ellipsis, for filling a field in a terminal application.
You'll find more examples and advice in the inline-template example.
When you want to fill a multi-line area, for example the help page of your terminal application, you may use a text template.
A template defines placeholders as ${name}
which you may fill when using it.
For example
let text_template = TextTemplate::from(r#"
# ${app-name} v${app-version}
It is *very* ${adj}.
"#);
let mut expander = text_template.expander();
expander
.set("app-name", "MyApp")
.set("adj", "pretty")
.set("app-version", "42.5.3");
skin.print_expander(expander);
This would render like this:
The values you set with set
aren't parsed as markdown, so they may freely contain stars or backquotes.
A template is reusable and can be defined from a text content or any string.
By using sub-templates, you may handle repetitions. They're handy for lists or tables.
For example
let text_template = TextTemplate::from(r#"
|:-:|:-:|:-:|
|**name**|**path**|**description**|
|-:|:-:|:-|
${module-rows
|**${module-name}**|`${app-version}/${module-key}`|${module-description}|
}
|-|-|-|
"#);
let mut expander = text_template.expander();
expander
.set("app-version", "2");
expander.sub("module-rows")
.set("module-name", "lazy-regex")
.set("module-key", "lrex")
.set("module-description", "eases regexes");
expander.sub("module-rows")
.set("module-name", "termimad")
.set("module-key", "tmd")
.set_md("module-description", "do things on *terminal*");
skin.print_expander(expander);
to get
On this example, you can note that
sub("module-rows")
gets an expander for the sub template calledmodule-rows
set_md
can be used when you want to insert not just a raw uninterpreted string but some inline markdown.- you don't need to fill global placeholders again (here
${app-version}
).
If you want to insert a block of code, you may use set_lines
which applies the line style to all passed lines.
For example
let text_template = TextTemplate::from(r#"
## Example of a code block
${some-function}
"#);
let mut expander = text_template.expander();
expander.set_lines("some-function", r#"
fun test(a rational) {
irate(a)
}
"#);
skin.print_expander(expander);
to get
You'll find more text template functions in the documentation and in the example (run cargo run --example text-template
).
You may also be interested in OwningTemplateExpander
: an alternative expander owning the values which may be handy when you build them while iterating in sub templates.
- Start by reading the examples (in
/examples
): they cover almost the whole API, including templates, how to use an alternate screen or scroll the page, etc. - If you want to see how some file would look with Termimad, you may try the cli Clima.
- Be careful that some colors aren't displayable on all terminals. The default color set of your application should not include arbitrary RGB colors.
- If a feature is missing, or you don't know how to use some part, come and ping me on my chat during West European hours.
- The event / event-source part of Termimad is currently tailored for a short number of applications. If you use it or want to use it, please come and tell me so that your needs are taken into account!
-
broot is a file manager and uses termimad for its help screen and status information
-
whalespotter has been designed specifically to showcase Termimad components in a real application.
-
lfs is a linux utility displaying file system. Termimad templates are used to show the data in tables
-
Rhit is a nginx log analyzer. Termimad templates are used to show the data in tables
If you're the author of another application using Termimad, please tell me.