Commit Diff


commit - 6a0c46aa50e96249f1a1c05ddf19f9c9a6a72e9c
commit + 7d471d22b241d238c547cce1302fda33d48d9672
blob - bbff52568021b91084c34eafb4f02da858d4523c
blob + 0c306714cffb3c68edb14132f779544946d0e6f7
--- README.md
+++ README.md
@@ -2,7 +2,8 @@ tmdr - Tiny Markdown Reader
 ==================================
 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.
 
 ![tmdr screenshot](tmdr.png)
 
blob - eba3fba58f9ac8c4515984a5efa0c53647ce1349
blob + bb405810110ca95d1db93d6e1e4f77493b4de79b
--- example.md
+++ example.md
@@ -48,6 +48,14 @@ main(void)
 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
@@ -20,14 +20,18 @@ use std::{
     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);
@@ -149,10 +153,75 @@ fn render_block(w: &mut impl Write, node: &Node, cols:
                 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 {