2026-10-14 00:01:03 +03:00
|
|
|
/* render.cpp — terminal markdown rendering with box-drawing tables. */
|
|
|
|
|
#include "bantam.hpp"
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <cctype>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <span>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- table helpers -------------------------- */
|
|
|
|
|
bool is_table_line(std::string_view line) {
|
|
|
|
|
std::string_view t = line;
|
|
|
|
|
while (!t.empty() && (t.front() == ' ' || t.front() == '\t')) t.remove_prefix(1);
|
|
|
|
|
if (t.find('|') == std::string_view::npos) return false;
|
|
|
|
|
return (t.front() == '|') || (t.back() == '|');
|
|
|
|
|
}
|
|
|
|
|
static std::string_view trim_spaces(std::string_view s) {
|
|
|
|
|
while (!s.empty() && (s.front() == ' ' || s.front() == '\t')) s.remove_prefix(1);
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
static bool is_table_sep(const std::vector<std::string> &cells) {
|
|
|
|
|
if (cells.empty()) return false;
|
|
|
|
|
for (auto &c : cells) {
|
|
|
|
|
std::string_view cv = trim_spaces(c);
|
|
|
|
|
bool ok = true;
|
|
|
|
|
for (char ch : cv) if (ch != '-' && ch != ':') { ok = false; break; }
|
|
|
|
|
if (!ok) return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
static std::vector<std::string> parse_table_cells(std::string_view line) {
|
|
|
|
|
std::string_view t = line;
|
|
|
|
|
while (!t.empty() && (t.front() == ' ' || t.front() == '\t')) t.remove_prefix(1);
|
|
|
|
|
std::string dup(t);
|
|
|
|
|
while (!dup.empty() && (dup.back() == '\n' || dup.back() == '\r')) dup.pop_back();
|
|
|
|
|
if (!dup.empty() && dup.front() == '|') dup.erase(0, 1);
|
|
|
|
|
if (!dup.empty() && dup.back() == '|') dup.pop_back();
|
|
|
|
|
size_t pipes = 0;
|
|
|
|
|
for (char c : dup) if (c == '|') pipes++;
|
|
|
|
|
std::vector<std::string> cells;
|
|
|
|
|
cells.reserve(pipes + 1);
|
|
|
|
|
std::string cur;
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
while (i <= dup.size()) {
|
|
|
|
|
if (i == dup.size() || dup[i] == '|') {
|
|
|
|
|
cells.push_back(std::string(trim_spaces(cur)));
|
|
|
|
|
cur.clear();
|
|
|
|
|
} else {
|
|
|
|
|
cur += dup[i];
|
|
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
return cells;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- inline --------------------------------- */
|
|
|
|
|
static bool is_word_byte(unsigned char c) {
|
|
|
|
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
|
|
|
|
|
(c >= 'A' && c <= 'Z') || c == '_';
|
|
|
|
|
}
|
2026-10-14 07:10:03 +00:00
|
|
|
template<class R>
|
2026-10-14 00:01:03 +03:00
|
|
|
static void wrap_pairs(std::string &dst, std::string_view src, std::string_view open,
|
2026-10-14 07:10:03 +00:00
|
|
|
std::string_view close, const R& codes,
|
2026-10-14 00:01:03 +03:00
|
|
|
bool boundary = false) {
|
|
|
|
|
size_t olen = open.size(), clen = close.size();
|
|
|
|
|
size_t p = 0;
|
|
|
|
|
while (p < src.size()) {
|
|
|
|
|
size_t o = src.find(open, p);
|
|
|
|
|
if (o == std::string_view::npos) { dst += src.substr(p); break; }
|
|
|
|
|
/* When word boundaries are enforced (the _ / __ rules) an opening
|
|
|
|
|
* delimiter glued to a preceding word character is not an opener. */
|
|
|
|
|
if (boundary && o > 0 && is_word_byte((unsigned char)src[o - 1])) {
|
|
|
|
|
dst += src.substr(p, o - p + olen);
|
|
|
|
|
p = o + olen;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
dst += src.substr(p, o - p);
|
|
|
|
|
size_t c = o + olen;
|
|
|
|
|
while (true) {
|
|
|
|
|
c = src.find(close, c);
|
|
|
|
|
if (c == std::string_view::npos) { dst += src.substr(o); break; }
|
|
|
|
|
/* A closing delimiter glued to a following word character is not a
|
|
|
|
|
* closer (e.g. the underscores in file_name_here); keep scanning. */
|
|
|
|
|
if (boundary && c + clen < src.size() &&
|
|
|
|
|
is_word_byte((unsigned char)src[c + clen])) {
|
|
|
|
|
c += clen;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (c == std::string_view::npos) break; /* substr(o) already appended */
|
|
|
|
|
std::string_view inner = src.substr(o + olen, c - (o + olen));
|
|
|
|
|
dst += cstr(inner, codes);
|
|
|
|
|
p = c + clen;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static void apply_links(std::string &dst, std::string_view src) {
|
|
|
|
|
size_t p = 0;
|
|
|
|
|
while (p < src.size()) {
|
|
|
|
|
size_t lb = src.find('[', p);
|
|
|
|
|
if (lb == std::string_view::npos) { dst += src.substr(p); break; }
|
|
|
|
|
size_t rb = src.find(']', lb);
|
|
|
|
|
if (rb == std::string_view::npos || rb + 1 >= src.size() || src[rb + 1] != '(') {
|
|
|
|
|
dst += src.substr(p, lb - p + 1); p = lb + 1; continue;
|
|
|
|
|
}
|
|
|
|
|
size_t rp = src.find(')', rb + 2);
|
|
|
|
|
if (rp == std::string_view::npos) { dst += src.substr(p); break; }
|
|
|
|
|
std::string_view text = src.substr(lb + 1, rb - (lb + 1));
|
|
|
|
|
std::string_view url = src.substr(rb + 2, rp - (rb + 2));
|
|
|
|
|
dst += src.substr(p, lb - p);
|
|
|
|
|
std::string wt = cstr(text, std::array{4, 36});
|
|
|
|
|
dst += wt;
|
|
|
|
|
std::string wu = cstr(url, std::array{2});
|
|
|
|
|
dst += "(" + wu + ")";
|
|
|
|
|
p = rp + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static std::string render_inline(std::string_view s) {
|
|
|
|
|
if (!g_col) return std::string(s);
|
|
|
|
|
if (s.empty()) return "";
|
|
|
|
|
std::vector<std::string> codes;
|
|
|
|
|
std::string step1;
|
|
|
|
|
size_t p = 0;
|
|
|
|
|
while (p < s.size()) {
|
|
|
|
|
if (s[p] == '`') {
|
|
|
|
|
size_t start = p + 1;
|
|
|
|
|
size_t end = s.find('`', start);
|
|
|
|
|
if (end != std::string_view::npos && end > start) {
|
|
|
|
|
std::string_view ctext = s.substr(start, end - start);
|
|
|
|
|
std::string ct = cstr(ctext, std::array{33});
|
|
|
|
|
codes.push_back(ct);
|
|
|
|
|
str_appendf(step1, "\x01%zu\x01", codes.size() - 1);
|
|
|
|
|
p = end + 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
step1 += s[p]; p++;
|
|
|
|
|
} else {
|
|
|
|
|
step1 += s[p]; p++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
std::string s2; apply_links(s2, step1);
|
|
|
|
|
std::string s3; wrap_pairs(s3, s2, "***", "***", std::array{1, 3});
|
|
|
|
|
std::string s4; wrap_pairs(s4, s3, "___", "___", std::array{1, 3});
|
|
|
|
|
std::string s5; wrap_pairs(s5, s4, "**", "**", std::array{1});
|
|
|
|
|
std::string s6; wrap_pairs(s6, s5, "__", "__", std::array{1}, true);
|
|
|
|
|
std::string s7; wrap_pairs(s7, s6, "*", "*", std::array{3});
|
|
|
|
|
std::string s8; wrap_pairs(s8, s7, "_", "_", std::array{3}, true);
|
|
|
|
|
std::string s9; wrap_pairs(s9, s8, "~~", "~~", std::array{9});
|
|
|
|
|
std::string out;
|
|
|
|
|
size_t q = 0;
|
|
|
|
|
while (q < s9.size()) {
|
|
|
|
|
if (s9[q] == '\x01') {
|
|
|
|
|
size_t e = s9.find('\x01', q + 1);
|
|
|
|
|
if (e != std::string::npos) {
|
|
|
|
|
int idx = to_int_val(std::string_view(s9).substr(q + 1, e - (q + 1)), -1);
|
|
|
|
|
if (idx >= 0 && idx < (int)codes.size()) out += codes[idx];
|
|
|
|
|
q = e + 1; continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out += s9[q]; q++;
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- line ----------------------------------- */
|
|
|
|
|
static int indent_spaces(std::string_view line) {
|
|
|
|
|
int n = 0;
|
|
|
|
|
while (n < (int)line.size() && line[n] == ' ') n++;
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
void render_md_line_print(std::string_view line, MdState &st) {
|
|
|
|
|
if (!g_col) { printf("%s\n", std::string(line).c_str()); return; }
|
|
|
|
|
std::string_view t = trim_spaces(line);
|
|
|
|
|
if (t.size() >= 3 && t.substr(0, 3) == "```") {
|
|
|
|
|
if (!st.in_code) {
|
|
|
|
|
st.in_code = true;
|
|
|
|
|
std::string_view lang = t.substr(3);
|
|
|
|
|
while (!lang.empty() && lang.front() == ' ') lang.remove_prefix(1);
|
|
|
|
|
std::string title;
|
|
|
|
|
if (!lang.empty()) title = sfmt(" [ %s ]", std::string(lang).c_str());
|
|
|
|
|
std::string s = "───" + title;
|
|
|
|
|
for (int i = 0; i < 40; i++) s += "─";
|
|
|
|
|
std::string h = cstr(s, std::array{2});
|
|
|
|
|
printf("%s\n", h.c_str());
|
|
|
|
|
} else {
|
|
|
|
|
st.in_code = false;
|
|
|
|
|
std::string s;
|
|
|
|
|
for (int i = 0; i < 50; i++) s += "─";
|
|
|
|
|
std::string h = cstr(s, std::array{2});
|
|
|
|
|
printf("%s\n", h.c_str());
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (st.in_code) {
|
|
|
|
|
std::string pre = cstr(" ", std::array{2});
|
|
|
|
|
std::string body = cstr(line, std::array{32});
|
|
|
|
|
printf("%s%s\n", pre.c_str(), body.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (t == "---" || t == "***" || t == "___" || t == "----" || t == "------") {
|
|
|
|
|
std::string s;
|
|
|
|
|
for (int i = 0; i < 50; i++) s += "─";
|
|
|
|
|
std::string h = cstr(s, std::array{2});
|
|
|
|
|
printf("%s\n", h.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!t.empty() && t[0] == '#') {
|
|
|
|
|
int lvl = 0;
|
|
|
|
|
while (lvl < (int)t.size() && t[lvl] == '#') lvl++;
|
|
|
|
|
if (lvl < (int)t.size() && t[lvl] == ' ') {
|
|
|
|
|
std::string_view htext = trim_spaces(t.substr(lvl));
|
|
|
|
|
if (lvl == 1) { std::string a = cstr("■ ", std::array{35}); std::string b = cstr(htext, std::array{1, 36}); printf("%s%s\n", a.c_str(), b.c_str()); }
|
|
|
|
|
else if (lvl == 2) { std::string a = cstr("▲ ", std::array{35}); std::string b = cstr(htext, std::array{1, 36}); printf("%s%s\n", a.c_str(), b.c_str()); }
|
|
|
|
|
else if (lvl == 3) { std::string a = cstr("● ", std::array{35}); std::string b = cstr(htext, std::array{1, 32}); printf("%s%s\n", a.c_str(), b.c_str()); }
|
|
|
|
|
else if (lvl == 4) { std::string a = cstr("◆ ", std::array{35}); std::string b = cstr(htext, std::array{1, 33}); printf("%s%s\n", a.c_str(), b.c_str()); }
|
|
|
|
|
else { std::string b = cstr(htext, std::array{1}); printf("%s\n", b.c_str()); }
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!t.empty() && t[0] == '>') {
|
|
|
|
|
std::string_view q = trim_spaces(t.substr(1));
|
|
|
|
|
std::string bar = cstr("▎ ", std::array{34});
|
|
|
|
|
std::string body = render_inline(q);
|
|
|
|
|
std::string cbody = cstr(body, std::array{3});
|
|
|
|
|
printf("%s%s\n", bar.c_str(), cbody.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (t.size() >= 6 && (t.substr(0, 6) == "- [ ] " || t.substr(0, 6) == "* [ ] ")) {
|
|
|
|
|
int ind = indent_spaces(line);
|
|
|
|
|
std::string box = cstr("☐ ", std::array{33});
|
|
|
|
|
std::string rest = render_inline(t.substr(6));
|
|
|
|
|
printf("%*s%s%s\n", ind, "", box.c_str(), rest.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (t.size() >= 6 && (t.substr(0, 6) == "- [x] " || t.substr(0, 6) == "* [x] " ||
|
|
|
|
|
t.substr(0, 6) == "- [X] " || t.substr(0, 6) == "* [X] ")) {
|
|
|
|
|
int ind = indent_spaces(line);
|
|
|
|
|
std::string box = cstr("☑ ", std::array{32});
|
|
|
|
|
std::string rest = render_inline(t.substr(6));
|
|
|
|
|
printf("%*s%s%s\n", ind, "", box.c_str(), rest.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (t.size() >= 2 && (t.substr(0, 2) == "- " || t.substr(0, 2) == "* " || t.substr(0, 2) == "+ ")) {
|
|
|
|
|
int ind = indent_spaces(line);
|
|
|
|
|
std::string bullet = cstr("• ", std::array{36});
|
|
|
|
|
std::string rest = render_inline(t.substr(2));
|
|
|
|
|
printf("%*s%s%s\n", ind, "", bullet.c_str(), rest.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::string_view num = t;
|
|
|
|
|
while (!num.empty() && std::isdigit((unsigned char)num.front())) num.remove_prefix(1);
|
|
|
|
|
if (!num.empty() && num.front() == '.' && num.size() > 1 && num[1] == ' ') {
|
|
|
|
|
int ind = indent_spaces(line);
|
|
|
|
|
size_t nlen = num.data() - t.data();
|
|
|
|
|
std::string_view nstr = t.substr(0, nlen);
|
|
|
|
|
std::string bullet = cstr(nstr, std::array{33});
|
|
|
|
|
std::string rest = render_inline(t.substr(nlen + 2));
|
|
|
|
|
printf("%*s%s %s\n", ind, "", bullet.c_str(), rest.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::string r = render_inline(line);
|
|
|
|
|
printf("%s\n", r.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- table ---------------------------------- */
|
|
|
|
|
static std::vector<std::string> wrap_cell(std::string_view s, int width) {
|
|
|
|
|
std::vector<std::string> lines;
|
|
|
|
|
if (width <= 0) { lines.push_back(""); return lines; }
|
|
|
|
|
std::string copy(s);
|
|
|
|
|
std::vector<std::string> blocks;
|
|
|
|
|
std::string cur;
|
|
|
|
|
size_t p = 0;
|
|
|
|
|
while (p < copy.size()) {
|
|
|
|
|
if (copy.compare(p, 4, "<br>") == 0) { blocks.push_back(cur); cur.clear(); p += 4; continue; }
|
|
|
|
|
if (copy.compare(p, 5, "<br/>") == 0) { blocks.push_back(cur); cur.clear(); p += 5; continue; }
|
|
|
|
|
if (copy.compare(p, 6, "<br />") == 0) { blocks.push_back(cur); cur.clear(); p += 6; continue; }
|
|
|
|
|
cur += copy[p]; p++;
|
|
|
|
|
}
|
|
|
|
|
if (!cur.empty() || blocks.empty()) blocks.push_back(cur);
|
|
|
|
|
std::vector<std::string> wrapped;
|
|
|
|
|
for (auto &cl : blocks) {
|
|
|
|
|
std::vector<std::string> words;
|
|
|
|
|
size_t wp = 0;
|
|
|
|
|
while (wp < cl.size()) {
|
|
|
|
|
while (wp < cl.size() && cl[wp] == ' ') wp++;
|
|
|
|
|
if (wp >= cl.size()) break;
|
|
|
|
|
size_t ws = wp;
|
|
|
|
|
while (wp < cl.size() && cl[wp] != ' ') wp++;
|
|
|
|
|
words.push_back(cl.substr(ws, wp - ws));
|
|
|
|
|
}
|
|
|
|
|
std::string curline;
|
|
|
|
|
for (auto &word : words) {
|
|
|
|
|
int wlen = visible_len(word);
|
|
|
|
|
if (wlen > width) {
|
|
|
|
|
std::string w = word;
|
|
|
|
|
size_t wi = 0;
|
|
|
|
|
while (wi < w.size()) {
|
|
|
|
|
if (!curline.empty()) { wrapped.push_back(curline); curline.clear(); }
|
|
|
|
|
std::string chunk;
|
|
|
|
|
int clen = 0;
|
|
|
|
|
size_t ci = wi;
|
|
|
|
|
while (ci < w.size() && clen < width) {
|
|
|
|
|
if (w[ci] == 0x1b) {
|
|
|
|
|
size_t j = ci + 1;
|
|
|
|
|
if (j < w.size() && w[j] == '[') {
|
|
|
|
|
j++;
|
|
|
|
|
while (j < w.size() && !(w[j] >= 0x40 && w[j] <= 0x7e)) j++;
|
|
|
|
|
if (j < w.size()) j++;
|
|
|
|
|
}
|
|
|
|
|
chunk += w.substr(ci, j - ci); ci = j;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
size_t pl; int cp = utf8_decode((const unsigned char *)(w.data() + ci), w.size() - ci, &pl);
|
|
|
|
|
chunk += w.substr(ci, pl); ci += pl; clen += rune_width(cp);
|
|
|
|
|
}
|
|
|
|
|
wrapped.push_back(chunk);
|
|
|
|
|
wi = ci;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
int curlen = visible_len(curline);
|
|
|
|
|
if (curline.empty()) curline = word;
|
|
|
|
|
else if (curlen + 1 + wlen <= width) { curline += ' '; curline += word; }
|
|
|
|
|
else { wrapped.push_back(curline); curline = word; }
|
|
|
|
|
}
|
|
|
|
|
if (!curline.empty()) wrapped.push_back(curline);
|
|
|
|
|
}
|
|
|
|
|
if (wrapped.empty()) wrapped.push_back("");
|
|
|
|
|
return wrapped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void print_row(int num_cols, const std::vector<int> &col_widths,
|
|
|
|
|
const std::vector<std::string> &cells, bool is_header) {
|
|
|
|
|
std::string s = "│";
|
|
|
|
|
for (int i = 0; i < num_cols; i++) {
|
|
|
|
|
std::string_view txt = (i < (int)cells.size()) ? std::string_view(cells[i]) : std::string_view();
|
|
|
|
|
int pad = col_widths[i] - visible_len(txt);
|
|
|
|
|
if (pad < 0) pad = 0;
|
|
|
|
|
std::string cell = " ";
|
|
|
|
|
cell.append(txt);
|
|
|
|
|
cell.append((size_t)pad + 1, ' ');
|
|
|
|
|
if (is_header) { std::string cc = cstr(cell, std::array{1}); s += cc; }
|
|
|
|
|
else { std::string cc = cstr(cell, std::array{2}); s += cc; }
|
|
|
|
|
s += "│";
|
|
|
|
|
}
|
|
|
|
|
std::string h = cstr(s, std::array{2});
|
|
|
|
|
printf("%s\n", h.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void render_table_print(const std::vector<std::string> &lines) {
|
|
|
|
|
if (lines.empty()) return;
|
|
|
|
|
std::vector<std::vector<std::string>> rows_cells;
|
|
|
|
|
std::vector<std::string> header_cells;
|
|
|
|
|
bool has_header = false;
|
|
|
|
|
for (auto &line : lines) {
|
|
|
|
|
auto cells = parse_table_cells(line);
|
|
|
|
|
if (is_table_sep(cells)) {
|
|
|
|
|
if (!rows_cells.empty() && !has_header) {
|
|
|
|
|
header_cells = std::move(rows_cells.back());
|
|
|
|
|
rows_cells.pop_back();
|
|
|
|
|
has_header = true;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
rows_cells.push_back(std::move(cells));
|
|
|
|
|
}
|
|
|
|
|
if (!has_header && lines.size() < 2) {
|
|
|
|
|
MdState st;
|
|
|
|
|
for (auto &ln : lines) render_md_line_print(ln, st);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
int num_cols = (int)header_cells.size();
|
|
|
|
|
for (auto &row : rows_cells) if ((int)row.size() > num_cols) num_cols = (int)row.size();
|
|
|
|
|
if (num_cols == 0) return;
|
|
|
|
|
if (has_header) { while ((int)header_cells.size() < num_cols) header_cells.push_back(""); }
|
|
|
|
|
std::vector<int> col_widths(num_cols, 0);
|
|
|
|
|
for (int i = 0; i < num_cols; i++) {
|
|
|
|
|
if (has_header) { int vl = visible_len(header_cells[i]); if (vl > col_widths[i]) col_widths[i] = vl; }
|
|
|
|
|
for (auto &row : rows_cells) {
|
|
|
|
|
std::string_view cell = (i < (int)row.size()) ? std::string_view(row[i]) : std::string_view();
|
|
|
|
|
std::string ri = render_inline(cell);
|
|
|
|
|
int vl = visible_len(ri);
|
|
|
|
|
if (vl > col_widths[i]) col_widths[i] = vl;
|
|
|
|
|
}
|
|
|
|
|
if (col_widths[i] < 3) col_widths[i] = 3;
|
|
|
|
|
}
|
|
|
|
|
int max_table_width = term_width();
|
|
|
|
|
if (max_table_width < 20) max_table_width = 80;
|
|
|
|
|
int overhead = 3 * num_cols + 1;
|
|
|
|
|
int avail = max_table_width - overhead;
|
|
|
|
|
if (avail < num_cols * 3) avail = num_cols * 3;
|
|
|
|
|
int tot = 0;
|
|
|
|
|
for (int i = 0; i < num_cols; i++) tot += col_widths[i];
|
|
|
|
|
while (tot > avail) {
|
|
|
|
|
int maxIdx = 0, maxVal = col_widths[0];
|
|
|
|
|
for (int i = 1; i < num_cols; i++) if (col_widths[i] > maxVal) { maxVal = col_widths[i]; maxIdx = i; }
|
|
|
|
|
if (maxVal <= 3) break;
|
|
|
|
|
col_widths[maxIdx]--; tot--;
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
std::string s = "┌";
|
|
|
|
|
for (int i = 0; i < num_cols; i++) { for (int j = 0; j < col_widths[i] + 2; j++) s += "─"; if (i < num_cols - 1) s += "┬"; }
|
|
|
|
|
s += "┐";
|
|
|
|
|
std::string h = cstr(s, std::array{2});
|
|
|
|
|
printf("%s\n", h.c_str());
|
|
|
|
|
}
|
|
|
|
|
if (has_header) {
|
|
|
|
|
std::vector<std::vector<std::string>> hw(num_cols);
|
|
|
|
|
int maxh = 1;
|
|
|
|
|
for (int i = 0; i < num_cols; i++) {
|
|
|
|
|
std::string ri = render_inline(header_cells[i]);
|
|
|
|
|
hw[i] = wrap_cell(ri, col_widths[i]);
|
|
|
|
|
if ((int)hw[i].size() > maxh) maxh = (int)hw[i].size();
|
|
|
|
|
}
|
|
|
|
|
for (int lr = 0; lr < maxh; lr++) {
|
|
|
|
|
std::vector<std::string> rowview(num_cols);
|
|
|
|
|
for (int i = 0; i < num_cols; i++) rowview[i] = (lr < (int)hw[i].size()) ? hw[i][lr] : "";
|
|
|
|
|
print_row(num_cols, col_widths, rowview, true);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
std::string s = "├";
|
|
|
|
|
for (int i = 0; i < num_cols; i++) { for (int j = 0; j < col_widths[i] + 2; j++) s += "─"; if (i < num_cols - 1) s += "┼"; }
|
|
|
|
|
s += "┤";
|
|
|
|
|
std::string h = cstr(s, std::array{2});
|
|
|
|
|
printf("%s\n", h.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (auto &row : rows_cells) {
|
|
|
|
|
std::vector<std::vector<std::string>> rw(num_cols);
|
|
|
|
|
int maxr = 1;
|
|
|
|
|
for (int i = 0; i < num_cols; i++) {
|
|
|
|
|
std::string_view cell = (i < (int)row.size()) ? std::string_view(row[i]) : std::string_view();
|
|
|
|
|
std::string ri = render_inline(cell);
|
|
|
|
|
rw[i] = wrap_cell(ri, col_widths[i]);
|
|
|
|
|
if ((int)rw[i].size() > maxr) maxr = (int)rw[i].size();
|
|
|
|
|
}
|
|
|
|
|
for (int lr = 0; lr < maxr; lr++) {
|
|
|
|
|
std::vector<std::string> rowview(num_cols);
|
|
|
|
|
for (int i = 0; i < num_cols; i++) rowview[i] = (lr < (int)rw[i].size()) ? rw[i][lr] : "";
|
|
|
|
|
print_row(num_cols, col_widths, rowview, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
std::string s = "└";
|
|
|
|
|
for (int i = 0; i < num_cols; i++) { for (int j = 0; j < col_widths[i] + 2; j++) s += "─"; if (i < num_cols - 1) s += "┴"; }
|
|
|
|
|
s += "┘";
|
|
|
|
|
std::string h = cstr(s, std::array{2});
|
|
|
|
|
printf("%s\n", h.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----------------------------- block ---------------------------------- */
|
|
|
|
|
void render_md_print(std::string_view text) {
|
|
|
|
|
if (!g_col) { if (!text.empty()) fputs(std::string(text).c_str(), stdout); return; }
|
|
|
|
|
if (text.empty()) return;
|
|
|
|
|
std::vector<std::string> tbl;
|
|
|
|
|
MdState st;
|
|
|
|
|
size_t start = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
auto nl = text.find('\n', start);
|
|
|
|
|
std::string_view line = (nl == std::string_view::npos) ? text.substr(start) : text.substr(start, nl - start);
|
|
|
|
|
std::string lstr(line);
|
|
|
|
|
std::string_view t = trim_spaces(lstr);
|
|
|
|
|
bool is_tbl = (!st.in_code && !(t.size() >= 3 && t.substr(0, 3) == "```") && is_table_line(lstr));
|
|
|
|
|
if (is_tbl) {
|
|
|
|
|
tbl.push_back(lstr);
|
|
|
|
|
} else {
|
|
|
|
|
if (!tbl.empty()) { render_table_print(tbl); tbl.clear(); }
|
|
|
|
|
render_md_line_print(lstr, st);
|
|
|
|
|
}
|
|
|
|
|
if (nl == std::string_view::npos) break;
|
|
|
|
|
start = nl + 1;
|
|
|
|
|
}
|
|
|
|
|
if (!tbl.empty()) render_table_print(tbl);
|
|
|
|
|
}
|