mdview2: 4 улучшения рендеринга markdown
1. Экранирование пунктуации: \* \_ \` \[ … → литерал, маркером НЕ считается (CommonMark ASCII-punctuation). Напр. "**...FILE\***" → болд "...FILE*". Не действует внутри инлайн-кода. 2. [x] / [ ] — ровно один символ в квадратных скобках → болд (нестандартно, для читабельного отображения task-list checkbox-ов). Не внутри кода. 3. Соседние пункты списка: если следующий имеет МЕНЬШИЙ отступ (dedent), пустую строку между ними больше не подавляем — пункты визуально разделены. 4. Абзац с ведущим отступом: все его перенесённые строки получают такой же отступ (continuation-префикс из col пробелов, как у списка/цитаты). Реализация — в обоих inline-сканерах (inline_scan/scan_join_stream) + ветках index_lines; новый helper is_escapable() и leading_spaces(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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(), но параметр оставлен для читаемости вызовов. */
|
||||||
@@ -865,6 +873,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 +953,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 +1175,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 +1308,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;
|
||||||
@@ -1449,8 +1497,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 +1538,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