mdview2: выровненная отрисовка таблиц с рамкой
Таблица обрабатывается как блок в два прохода: - проход 1: границы блока + ОТРЕНДЕРЕННЫЕ ширины колонок (мерим тем же inline_scan, что и при отрисовке — невидимые маркеры стиля **/код не раздувают столбцы); - проход 2: верхняя рамка ┌┬┐ → строки данных │ ячейка<pad> │ → разделитель заголовка ├┼┤ (из строки |---|) → нижняя рамка └┴┘. Колонки выровнены по содержимому, рамка CP866 box-drawing. Широкая таблица остаётся nowrap+hscroll. Ячейки разбиваются cell-итератором (без массивов на стеке); пустой g_cells под измерение освобождается ручным флашем pending-сегмента (флаг g_skip_flush в emit_seg, чтобы не флашить повторно). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+152
-10
@@ -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;
|
||||
}
|
||||
while (p < file_size && fb(p) != '\n') p++;
|
||||
if (p < file_size) p++;
|
||||
c++;
|
||||
}
|
||||
if (c > ncols) ncols = c;
|
||||
r = (le < file_size) ? le + 1 : le;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user