Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 13ef8d2fa5 | |||
| 68201dca44 |
+221
-18
@@ -489,6 +489,14 @@ static uint8_t is_emph_flanked(char prev_ch, char next_ch)
|
|||||||
return (uint8_t)(ws_or_eol(prev_ch) != ws_or_eol_or_delim(next_ch));
|
return (uint8_t)(ws_or_eol(prev_ch) != ws_or_eol_or_delim(next_ch));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Символ можно экранировать обратным слешем (\X): любая печатная ASCII-
|
||||||
|
* пунктуация (как в CommonMark). \* \_ \` \[ и т.п. → литерал X. */
|
||||||
|
static uint8_t is_escapable(char c)
|
||||||
|
{
|
||||||
|
return (uint8_t)(c >= '!' && c <= '~' &&
|
||||||
|
!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')));
|
||||||
|
}
|
||||||
|
|
||||||
/* Тип сегмента-продолжения, передаваемый в emit_seg().
|
/* Тип сегмента-продолжения, передаваемый в emit_seg().
|
||||||
* Явно в индексе не хранится: рендер при необходимости восстанавливает
|
* Явно в индексе не хранится: рендер при необходимости восстанавливает
|
||||||
* тип через classify_line(), но параметр оставлен для читаемости вызовов. */
|
* тип через classify_line(), но параметр оставлен для читаемости вызовов. */
|
||||||
@@ -795,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_cells[MAX_CACHE_LINE_LEN * 2];
|
||||||
static uint8_t g_ncells = 0; /* ячеек в текущем сегменте */
|
static uint8_t g_ncells = 0; /* ячеек в текущем сегменте */
|
||||||
static uint8_t g_ncells_at_space = 0; /* снимок g_ncells на последнем пробеле (для усечения при переносе) */
|
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, молча игнорируя переполнение
|
/* Пишет одну (char,attr)-ячейку в g_cells, молча игнорируя переполнение
|
||||||
* MAX_CACHE_LINE_LEN. Оставлена ФУНКЦИЕЙ намеренно: инлайн в сильно
|
* MAX_CACHE_LINE_LEN. Оставлена ФУНКЦИЕЙ намеренно: инлайн в сильно
|
||||||
@@ -848,7 +857,10 @@ static void emit_seg(uint32_t off, uint8_t style, uint8_t ckind, uint8_t cont)
|
|||||||
cur_seg_off = off;
|
cur_seg_off = off;
|
||||||
n_lines++;
|
n_lines++;
|
||||||
/* Флаш ячеек ПРЕДЫДУЩего сегмента (lag-1): он полностью собран форвард-сканером. */
|
/* Флаш ячеек ПРЕДЫДУЩего сегмента (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 = 0; /* буфер ячеек под новый сегмент */
|
||||||
g_ncells_at_space = 0;
|
g_ncells_at_space = 0;
|
||||||
}
|
}
|
||||||
@@ -865,6 +877,15 @@ static uint8_t is_line_blank(uint32_t p)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Число ведущих пробелов строки (отступ). Используется для отделения
|
||||||
|
* dedent-пунктов списка (#3) и для отступа обычного абзаца (#4). */
|
||||||
|
static uint8_t leading_spaces(uint32_t p)
|
||||||
|
{
|
||||||
|
uint8_t n = 0;
|
||||||
|
while (p < file_size && fb(p) == ' ' && n < 250) { n++; p++; }
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
/* Быстрая проверка границы fenced-блока по маркеру ``` .
|
/* Быстрая проверка границы fenced-блока по маркеру ``` .
|
||||||
* Ведущие пробелы/табы пропускаются — fence может быть с отступом
|
* Ведущие пробелы/табы пропускаются — fence может быть с отступом
|
||||||
* (например, ``` ```c ``` внутри элемента списка с отступом 2+ пробела).
|
* (например, ``` ```c ``` внутри элемента списка с отступом 2+ пробела).
|
||||||
@@ -936,6 +957,21 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Экранированная пунктуация: \* \_ \` … → литерал (не внутри кода). */
|
||||||
|
if (line_style != INIT_STYLE_CODE && ch == '\\' && (q + 1) < q_end && is_escapable(fb(q + 1))) {
|
||||||
|
char nb = fb(q + 1);
|
||||||
|
gc_put(nb, emph_to_attr(line_style, base));
|
||||||
|
seg_col++; q += 2; prev_ch = nb; continue;
|
||||||
|
}
|
||||||
|
/* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */
|
||||||
|
if (line_style != INIT_STYLE_CODE && ch == '[' && (q + 2) < q_end && fb(q + 2) == ']') {
|
||||||
|
char mid = fb(q + 1);
|
||||||
|
gc_put('[', ATTR_TEXT_BOLD);
|
||||||
|
gc_put(mid, ATTR_TEXT_BOLD);
|
||||||
|
gc_put(']', ATTR_TEXT_BOLD);
|
||||||
|
seg_col += 3; q += 3; prev_ch = ']'; continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (ch == '`') {
|
if (ch == '`') {
|
||||||
if (line_style == INIT_STYLE_PLAIN) { line_style = INIT_STYLE_CODE; q++; continue; }
|
if (line_style == INIT_STYLE_PLAIN) { line_style = INIT_STYLE_CODE; q++; continue; }
|
||||||
if (line_style == INIT_STYLE_CODE) { line_style = INIT_STYLE_PLAIN; q++; continue; }
|
if (line_style == INIT_STYLE_CODE) { line_style = INIT_STYLE_PLAIN; q++; continue; }
|
||||||
@@ -1143,6 +1179,21 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Экранированная пунктуация: \* \_ \` … → литерал (не внутри кода). */
|
||||||
|
if (line_style != INIT_STYLE_CODE && ch == '\\' && (q + 1) < file_size && is_escapable(fb(q + 1))) {
|
||||||
|
char nb = fb(q + 1);
|
||||||
|
gc_put(nb, styles_map[line_style]);
|
||||||
|
seg_col++; q += 2; prev_ch = nb; continue;
|
||||||
|
}
|
||||||
|
/* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */
|
||||||
|
if (line_style != INIT_STYLE_CODE && ch == '[' && (q + 2) < file_size && fb(q + 2) == ']') {
|
||||||
|
char mid = fb(q + 1);
|
||||||
|
gc_put('[', ATTR_TEXT_BOLD);
|
||||||
|
gc_put(mid, ATTR_TEXT_BOLD);
|
||||||
|
gc_put(']', ATTR_TEXT_BOLD);
|
||||||
|
seg_col += 3; q += 3; prev_ch = ']'; continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (ch == '`') {
|
if (ch == '`') {
|
||||||
/* Как handle_inline_marker в render: backtick переключает code-режим
|
/* Как handle_inline_marker в render: backtick переключает code-режим
|
||||||
* ТОЛЬКО из PLAIN или CODE; если активен эмфазис (bold/italic/...),
|
* ТОЛЬКО из PLAIN или CODE; если активен эмфазис (bold/italic/...),
|
||||||
@@ -1261,9 +1312,10 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
|
|||||||
uint8_t wrap_style = use_ls ? last_space_style : line_style;
|
uint8_t wrap_style = use_ls ? last_space_style : line_style;
|
||||||
if (use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */
|
if (use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */
|
||||||
emit_seg(wrap_at, wrap_style, ckind, 1);
|
emit_seg(wrap_at, wrap_style, ckind, 1);
|
||||||
/* Префикс continuation-сегмента (как render в cont-блоке): список —
|
/* Префикс continuation-сегмента: список и отступленный абзац (#4) —
|
||||||
* col пробелов; цитата — отступ + маркер 0xB3 + пробел; plain — без префикса. */
|
* col пробелов выравнивания; цитата — отступ + маркер 0xB3 + пробел.
|
||||||
if (ckind == CK_LIST) {
|
* Для обычного абзаца без отступа col==0 → префикса нет. */
|
||||||
|
if (ckind == CK_LIST || ckind == CK_PLAIN) {
|
||||||
gc_fill(' ', ATTR_TEXT, col);
|
gc_fill(' ', ATTR_TEXT, col);
|
||||||
} else if (ckind == CK_QUOTE) {
|
} else if (ckind == CK_QUOTE) {
|
||||||
uint32_t content_off2 = para_start + col;
|
uint32_t content_off2 = para_start + col;
|
||||||
@@ -1291,6 +1343,102 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
|
|||||||
return q;
|
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)
|
static void index_lines(void)
|
||||||
{
|
{
|
||||||
@@ -1369,16 +1517,58 @@ static void index_lines(void)
|
|||||||
|
|
||||||
/* ---- строка таблицы (| ... |): один nowrap-сегмент на исходную строку ---- */
|
/* ---- строка таблицы (| ... |): один nowrap-сегмент на исходную строку ---- */
|
||||||
if (is_table_raw(p)) {
|
if (is_table_raw(p)) {
|
||||||
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
|
/* Освобождаем g_cells под измерение ширин: коммитим pending-сегмент
|
||||||
set_nowrap_cur();
|
* сейчас; первый emit_seg таблицы пропустит свой lag-флаш. */
|
||||||
set_hscroll_cur(); /* таблица скроллится целым блоком */
|
if (n_lines >= 1) { seg_commit_cells((uint16_t)(n_lines - 1)); g_skip_flush = 1; }
|
||||||
{
|
g_ncells = 0;
|
||||||
uint32_t line_end = p;
|
|
||||||
while (line_end < file_size && fb(line_end) != '\n') line_end++;
|
/* Проход 1: границы блока + ОТРЕНДЕРЕННЫЕ ширины колонок (мерим тем же
|
||||||
(void)inline_scan(p, line_end, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 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++;
|
g_ncells = 0; /* чисто для верхней рамки */
|
||||||
if (p < file_size) p++;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1449,8 +1639,13 @@ static void index_lines(void)
|
|||||||
uint32_t d = t;
|
uint32_t d = t;
|
||||||
uint8_t nk = classify_line(t, &d);
|
uint8_t nk = classify_line(t, &d);
|
||||||
if (nk == LK_ULIST || nk == LK_OLIST) {
|
if (nk == LK_ULIST || nk == LK_OLIST) {
|
||||||
p = t; /* подавляем единственную пустую строку */
|
/* #3: подавляем blank только если следующий пункт того же
|
||||||
continue;
|
* уровня или глубже; при МЕНЬШЕМ отступе (dedent) пустую
|
||||||
|
* строку оставляем — пункты должны визуально разделиться. */
|
||||||
|
if (leading_spaces(t) >= leading_spaces(p)) {
|
||||||
|
p = t; /* подавляем единственную пустую строку */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1485,10 +1680,18 @@ static void index_lines(void)
|
|||||||
/* ---- Обычный текстовый параграф: склейка через soft-break ---- */
|
/* ---- Обычный текстовый параграф: склейка через soft-break ---- */
|
||||||
{
|
{
|
||||||
uint8_t line_style = INIT_STYLE_PLAIN;
|
uint8_t line_style = INIT_STYLE_PLAIN;
|
||||||
emit_seg(p, line_style, CK_PLAIN, 0);
|
/* #4: если абзац начинается с отступа — все его перенесённые строки
|
||||||
|
* получают такой же отступ (как continuation-префикс списка, но без
|
||||||
|
* маркера). Контент сканируем после отступа. Патологически большой
|
||||||
|
* отступ игнорируем. */
|
||||||
|
uint8_t indent = leading_spaces(p);
|
||||||
|
if (indent >= SCREEN_W / 2) indent = 0;
|
||||||
|
|
||||||
uint32_t q = p;
|
emit_seg(p, line_style, CK_PLAIN, 0);
|
||||||
q = scan_join_stream(q, p, 0, CK_PLAIN, &line_style, JOIN_MODE_PLAIN);
|
if (indent) gc_fill(' ', ATTR_TEXT, indent); /* префикс первого сегмента */
|
||||||
|
|
||||||
|
uint32_t q = p + indent;
|
||||||
|
q = scan_join_stream(q, p, indent, CK_PLAIN, &line_style, JOIN_MODE_PLAIN);
|
||||||
|
|
||||||
if (q >= file_size) {
|
if (q >= file_size) {
|
||||||
p = file_size;
|
p = file_size;
|
||||||
|
|||||||
Reference in New Issue
Block a user