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));
|
||||
}
|
||||
|
||||
/* Символ можно экранировать обратным слешем (\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().
|
||||
* Явно в индексе не хранится: рендер при необходимости восстанавливает
|
||||
* тип через classify_line(), но параметр оставлен для читаемости вызовов. */
|
||||
@@ -865,6 +873,15 @@ static uint8_t is_line_blank(uint32_t p)
|
||||
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-блока по маркеру ``` .
|
||||
* Ведущие пробелы/табы пропускаются — fence может быть с отступом
|
||||
* (например, ``` ```c ``` внутри элемента списка с отступом 2+ пробела).
|
||||
@@ -936,6 +953,21 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
|
||||
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 (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; }
|
||||
@@ -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 == '`') {
|
||||
/* Как handle_inline_marker в render: backtick переключает code-режим
|
||||
* ТОЛЬКО из 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;
|
||||
if (use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */
|
||||
emit_seg(wrap_at, wrap_style, ckind, 1);
|
||||
/* Префикс continuation-сегмента (как render в cont-блоке): список —
|
||||
* col пробелов; цитата — отступ + маркер 0xB3 + пробел; plain — без префикса. */
|
||||
if (ckind == CK_LIST) {
|
||||
/* Префикс continuation-сегмента: список и отступленный абзац (#4) —
|
||||
* col пробелов выравнивания; цитата — отступ + маркер 0xB3 + пробел.
|
||||
* Для обычного абзаца без отступа col==0 → префикса нет. */
|
||||
if (ckind == CK_LIST || ckind == CK_PLAIN) {
|
||||
gc_fill(' ', ATTR_TEXT, col);
|
||||
} else if (ckind == CK_QUOTE) {
|
||||
uint32_t content_off2 = para_start + col;
|
||||
@@ -1449,8 +1497,13 @@ static void index_lines(void)
|
||||
uint32_t d = t;
|
||||
uint8_t nk = classify_line(t, &d);
|
||||
if (nk == LK_ULIST || nk == LK_OLIST) {
|
||||
p = t; /* подавляем единственную пустую строку */
|
||||
continue;
|
||||
/* #3: подавляем blank только если следующий пункт того же
|
||||
* уровня или глубже; при МЕНЬШЕМ отступе (dedent) пустую
|
||||
* строку оставляем — пункты должны визуально разделиться. */
|
||||
if (leading_spaces(t) >= leading_spaces(p)) {
|
||||
p = t; /* подавляем единственную пустую строку */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1485,10 +1538,18 @@ static void index_lines(void)
|
||||
/* ---- Обычный текстовый параграф: склейка через soft-break ---- */
|
||||
{
|
||||
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;
|
||||
q = scan_join_stream(q, p, 0, CK_PLAIN, &line_style, JOIN_MODE_PLAIN);
|
||||
emit_seg(p, line_style, CK_PLAIN, 0);
|
||||
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) {
|
||||
p = file_size;
|
||||
|
||||
Reference in New Issue
Block a user