diff --git a/examples/mdview2/mdview2.c b/examples/mdview2/mdview2.c index e15f537..8d13db8 100644 --- a/examples/mdview2/mdview2.c +++ b/examples/mdview2/mdview2.c @@ -803,6 +803,7 @@ static uint8_t marker_visible_col(uint8_t kind, uint32_t p_start, uint32_t conte static uint8_t g_cells[MAX_CACHE_LINE_LEN * 2]; static uint8_t g_ncells = 0; /* ячеек в текущем сегменте */ static uint8_t g_ncells_at_space = 0; /* снимок g_ncells на последнем пробеле (для усечения при переносе) */ +static uint8_t g_skip_flush = 0; /* следующий emit_seg пропустит lag-флаш (pending уже зафлашен вручную, напр. перед таблицей) */ /* Пишет одну (char,attr)-ячейку в g_cells, молча игнорируя переполнение * MAX_CACHE_LINE_LEN. Оставлена ФУНКЦИЕЙ намеренно: инлайн в сильно @@ -856,7 +857,10 @@ static void emit_seg(uint32_t off, uint8_t style, uint8_t ckind, uint8_t cont) cur_seg_off = off; n_lines++; /* Флаш ячеек ПРЕДЫДУЩего сегмента (lag-1): он полностью собран форвард-сканером. */ - if (n_lines >= 2) seg_commit_cells((uint16_t)(n_lines - 2)); + if (n_lines >= 2) { + if (g_skip_flush) g_skip_flush = 0; /* pending уже зафлашен вручную */ + else seg_commit_cells((uint16_t)(n_lines - 2)); + } g_ncells = 0; /* буфер ячеек под новый сегмент */ g_ncells_at_space = 0; } @@ -1339,6 +1343,102 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, return q; } +/* ====================== Таблицы: выровненная отрисовка ==================== */ +#define TBL_MAX_COLS 16 +#define TBL_ATTR ATTR_TEXT +/* CP866 light box-drawing. */ +#define TBL_H 0xC4 /* ─ */ +#define TBL_V 0xB3 /* │ */ +#define TBL_TL 0xDA /* ┌ */ +#define TBL_TM 0xC2 /* ┬ */ +#define TBL_TR 0xBF /* ┐ */ +#define TBL_ML 0xC3 /* ├ */ +#define TBL_MM 0xC5 /* ┼ */ +#define TBL_MR 0xB4 /* ┤ */ +#define TBL_BL 0xC0 /* └ */ +#define TBL_BM 0xC1 /* ┴ */ +#define TBL_BR 0xD9 /* ┘ */ + +/* Смещение завершающего '\n' строки (или file_size). */ +static uint32_t row_end(uint32_t p) +{ + while (p < file_size && fb(p) != '\n') p++; + return p; +} + +/* Начало контента первой ячейки строки (после ведущих пробелов и '|'). */ +static uint32_t table_first_cell(uint32_t row_start) +{ + uint32_t p = row_start; + while (p < file_size && fb(p) == ' ') p++; + if (p < file_size && fb(p) == '|') p++; + return p; +} + +/* Следующая ячейка: [*cs,*ce) — обрезанный диапазон; *pp продвигается за '|'. + * 0 — ячеек больше нет (включая хвостовую пустоту после последнего '|'). */ +static uint8_t table_next_cell(uint32_t *pp, uint32_t lineend, uint32_t *cs, uint32_t *ce) +{ + uint32_t p = *pp; + if (p >= lineend) return 0; + uint32_t a = p; + while (p < lineend && fb(p) != '|') p++; + uint32_t b = p; + uint8_t had_pipe = (uint8_t)(p < lineend && fb(p) == '|'); + if (had_pipe) p++; + *pp = p; + while (a < b && fb(a) == ' ') a++; + while (b > a && fb(b - 1) == ' ') b--; + if (!had_pipe && a == b) return 0; + *cs = a; *ce = b; + return 1; +} + +/* Строка-разделитель (|---|:-:|): каждая ячейка непустая и только '-'/':'. */ +static uint8_t table_is_sep_row(uint32_t row_start, uint32_t lineend) +{ + uint32_t p = table_first_cell(row_start); + uint32_t cs, ce; + uint8_t n = 0; + while (table_next_cell(&p, lineend, &cs, &ce)) { + if (cs >= ce) return 0; + for (uint32_t q = cs; q < ce; q++) { char c = fb(q); if (c != '-' && c != ':') return 0; } + n++; + } + return (uint8_t)(n > 0); +} + +/* Горизонтальная рамка в g_cells: left + (H×(w+2) + mid|right) по колонкам. */ +static void table_border(const uint8_t *widths, uint8_t ncols, char left, char mid, char right) +{ + gc_put(left, TBL_ATTR); + for (uint8_t c = 0; c < ncols; c++) { + gc_fill(TBL_H, TBL_ATTR, (uint8_t)(widths[c] + 2)); + gc_put((c == (uint8_t)(ncols - 1)) ? right : mid, TBL_ATTR); + } +} + +/* Строка данных в g_cells: │ <ячейка, добитая до widths[c]> │ … */ +static void table_data_row(uint32_t row_start, uint32_t lineend, const uint8_t *widths, uint8_t ncols) +{ + uint32_t p = table_first_cell(row_start); + uint32_t cs, ce; + gc_put(TBL_V, TBL_ATTR); + for (uint8_t c = 0; c < ncols; c++) { + uint8_t got = table_next_cell(&p, lineend, &cs, &ce); + gc_put(' ', TBL_ATTR); + uint8_t rendered = 0; + if (got && ce > cs) { + uint8_t before = g_ncells; + (void)inline_scan(cs, ce, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1); + rendered = (uint8_t)(g_ncells - before); + } + if (rendered < widths[c]) gc_fill(' ', ATTR_TEXT, (uint8_t)(widths[c] - rendered)); + gc_put(' ', TBL_ATTR); + gc_put(TBL_V, TBL_ATTR); + } +} + /* Строит индекс сегментов рендера по загруженному файлу. */ static void index_lines(void) { @@ -1417,16 +1517,58 @@ static void index_lines(void) /* ---- строка таблицы (| ... |): один nowrap-сегмент на исходную строку ---- */ if (is_table_raw(p)) { - emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0); - set_nowrap_cur(); - set_hscroll_cur(); /* таблица скроллится целым блоком */ - { - uint32_t line_end = p; - while (line_end < file_size && fb(line_end) != '\n') line_end++; - (void)inline_scan(p, line_end, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1); + /* Освобождаем g_cells под измерение ширин: коммитим pending-сегмент + * сейчас; первый emit_seg таблицы пропустит свой lag-флаш. */ + if (n_lines >= 1) { seg_commit_cells((uint16_t)(n_lines - 1)); g_skip_flush = 1; } + g_ncells = 0; + + /* Проход 1: границы блока + ОТРЕНДЕРЕННЫЕ ширины колонок (мерим тем же + * inline_scan, что и при отрисовке — невидимые маркеры стиля не считаются). */ + uint8_t widths[TBL_MAX_COLS]; + for (uint8_t i = 0; i < TBL_MAX_COLS; i++) widths[i] = 0; + uint8_t ncols = 0; + uint32_t r = p; + while (r < file_size && is_table_raw(r)) { + uint32_t le = row_end(r); + uint8_t sep = table_is_sep_row(r, le); + uint32_t cp = table_first_cell(r); + uint32_t cs, ce; + uint8_t c = 0; + while (c < TBL_MAX_COLS && table_next_cell(&cp, le, &cs, &ce)) { + if (!sep && ce > cs) { + g_ncells = 0; + (void)inline_scan(cs, ce, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1); + if (g_ncells > widths[c]) widths[c] = g_ncells; + } + c++; + } + if (c > ncols) ncols = c; + r = (le < file_size) ? le + 1 : le; } - while (p < file_size && fb(p) != '\n') p++; - if (p < file_size) p++; + g_ncells = 0; /* чисто для верхней рамки */ + uint32_t tbl_end = r; + if (ncols == 0) ncols = 1; + + /* Проход 2: верхняя рамка → строки/разделитель → нижняя рамка. */ + emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0); + set_nowrap_cur(); set_hscroll_cur(); + table_border(widths, ncols, TBL_TL, TBL_TM, TBL_TR); + + r = p; + while (r < tbl_end) { + uint32_t le = row_end(r); + emit_seg(r, INIT_STYLE_PLAIN, CK_OTHER, 0); + set_nowrap_cur(); set_hscroll_cur(); + if (table_is_sep_row(r, le)) table_border(widths, ncols, TBL_ML, TBL_MM, TBL_MR); + else table_data_row(r, le, widths, ncols); + r = (le < file_size) ? le + 1 : le; + } + + emit_seg(tbl_end, INIT_STYLE_PLAIN, CK_OTHER, 0); + set_nowrap_cur(); set_hscroll_cur(); + table_border(widths, ncols, TBL_BL, TBL_BM, TBL_BR); + + p = tbl_end; continue; }