commit - 6a0c46aa50e96249f1a1c05ddf19f9c9a6a72e9c
commit + 7d471d22b241d238c547cce1302fda33d48d9672
blob - bbff52568021b91084c34eafb4f02da858d4523c
blob + 0c306714cffb3c68edb14132f779544946d0e6f7
--- README.md
+++ README.md
==================================
tmdr is a tiny markdown terminal reader written in Rust.
It renders headings, paragraphs, lists, code blocks, bold,
-italic, inline code, and links (OSC 8) directly in the terminal.
+italic, inline code, links (OSC 8), and GFM tables directly
+in the terminal.

blob - eba3fba58f9ac8c4515984a5efa0c53647ce1349
blob + bb405810110ca95d1db93d6e1e4f77493b4de79b
--- example.md
+++ example.md
Visit [OpenBSD](https://www.openbsd.org) for more info.
A [link with **bold**](https://example.com) inside a paragraph.
+## Tables
+
+| Left | Center | Right |
+|:-------|:------:|------:|
+| a | b | c |
+| longer | mid | 42 |
+| `code` | **bo** | *it* |
+
## Headers
### Level 3
blob - 33df2adde1a59722af69e1600471f021f9288f27
blob + 54d9c2a7d8c1734972a9481bac0d2a721d3c21db
--- tmdr.rs
+++ tmdr.rs
io::{self, Read, Write},
};
-use markdown::{ParseOptions, mdast::Node, to_mdast};
+use markdown::{
+ ParseOptions,
+ mdast::{AlignKind, Node, Table},
+ to_mdast,
+};
fn main() {
let args: Vec<String> = env::args().collect();
let (cols, file) = parse_args(&args);
let input = read_input(file);
- let tree = match to_mdast(&input, &ParseOptions::default()) {
+ let tree = match to_mdast(&input, &ParseOptions::gfm()) {
Ok(t) => t,
Err(e) => {
eprintln!("tmdr: {}", e);
render_block(w, child, cols.saturating_sub(2));
}
}
+ Node::Table(t) => render_table(w, t),
_ => {}
}
}
+fn render_table(w: &mut impl Write, table: &Table) {
+ let mut rows: Vec<Vec<String>> = Vec::new();
+ for child in &table.children {
+ if let Node::TableRow(r) = child {
+ let mut row = Vec::new();
+ for cell in &r.children {
+ if let Node::TableCell(c) = cell {
+ row.push(inline_text(&c.children));
+ }
+ }
+ rows.push(row);
+ }
+ }
+ if rows.is_empty() {
+ return;
+ }
+
+ let ncols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
+ let mut widths = vec![0usize; ncols];
+ for row in &rows {
+ for (i, cell) in row.iter().enumerate() {
+ widths[i] = widths[i].max(visible_len(cell));
+ }
+ }
+
+ for (ri, row) in rows.iter().enumerate() {
+ let _ = write!(w, "\u{2502}");
+ for (ci, width) in widths.iter().enumerate() {
+ let empty = String::new();
+ let cell = row.get(ci).unwrap_or(&empty);
+ let align = table.align.get(ci).copied().unwrap_or(AlignKind::None);
+ let vlen = visible_len(cell);
+ let extra = width.saturating_sub(vlen);
+ let (lp, rp) = match align {
+ AlignKind::Right => (extra, 0),
+ AlignKind::Center => (extra / 2, extra - extra / 2),
+ _ => (0, extra),
+ };
+ let _ = write!(
+ w,
+ " {}{}{} \u{2502}",
+ " ".repeat(lp),
+ cell,
+ " ".repeat(rp)
+ );
+ }
+ let _ = writeln!(w);
+
+ if ri == 0 {
+ let _ = write!(w, "\u{251c}");
+ for (ci, width) in widths.iter().enumerate() {
+ let _ = write!(w, "{}", "\u{2500}".repeat(width + 2));
+ let joint = if ci + 1 < widths.len() {
+ '\u{253c}'
+ } else {
+ '\u{2524}'
+ };
+ let _ = write!(w, "{}", joint);
+ }
+ let _ = writeln!(w);
+ }
+ }
+}
+
fn inline_text(nodes: &[Node]) -> String {
let mut out = String::new();
for node in nodes {