mdview: wide-break '\\', strikethrough, horizontal scroll bounds + '<'
- A trailing backslash before a newline now forces an in-paragraph line break (like two trailing spaces); render consumes the marker (non-code). - Add ~~strikethrough~~ inline style (INIT_STYLE_STRIKE / EM_STRIKE), parsed in inline_scan, the paragraph merger and render, mirroring **. - Horizontal pan is bounded by the widest nowrap segment on screen, and a '<' indicator marks hidden content off the left edge. Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
+98
-11
@@ -93,6 +93,7 @@ static const uint8_t md_pallete[16 * 4] = {
|
||||
#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||
#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE)
|
||||
#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
||||
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
||||
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||
#define ATTR_HR COLOR(COLOR_LIGHTGRAY, COLOR_BLUE)
|
||||
@@ -435,9 +436,11 @@ static uint8_t is_fence_delim(uint16_t idx)
|
||||
#define INIT_STYLE_ITALIC 0x2
|
||||
#define INIT_STYLE_UNDER 0x3
|
||||
#define INIT_STYLE_CODE 0x4
|
||||
#define INIT_STYLE_STRIKE 0x5
|
||||
|
||||
/* Indexed by init_style & 7; order must match INIT_STYLE_* / EM_* below. */
|
||||
static const uint8_t styles_map[] = {
|
||||
ATTR_TEXT, ATTR_TEXT_BOLD, ATTR_TEXT_ITALIC, ATTR_TEXT_UNDERSORE, ATTR_TEXT_CODE
|
||||
ATTR_TEXT, ATTR_TEXT_BOLD, ATTR_TEXT_ITALIC, ATTR_TEXT_UNDERSORE, ATTR_TEXT_CODE, ATTR_TEXT_STRIKE
|
||||
};
|
||||
|
||||
static uint8_t get_init_style(uint16_t idx)
|
||||
@@ -705,6 +708,17 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
|
||||
}
|
||||
seg_col += 2; q += 2; prev_ch = '*'; continue;
|
||||
}
|
||||
if (ch == '~' && (q + 1) < q_end && fb(q + 1) == '~') {
|
||||
char next_ch = ((q + 2) < q_end) ? fb(q + 2) : '\n';
|
||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
||||
line_style = INIT_STYLE_STRIKE; q += 2; continue;
|
||||
} else if (line_style == INIT_STYLE_STRIKE && ws_or_eol_or_delim(next_ch)) {
|
||||
line_style = INIT_STYLE_PLAIN; q += 2; continue;
|
||||
}
|
||||
}
|
||||
seg_col += 2; q += 2; prev_ch = '~'; continue;
|
||||
}
|
||||
if (ch == '*' || ch == '_') {
|
||||
char next_ch = ((q + 1) < q_end) ? fb(q + 1) : '\n';
|
||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
||||
@@ -854,13 +868,19 @@ static void index_lines(void)
|
||||
if (classify_line(next, &dummy) != LK_PLAIN) break;
|
||||
if (is_line_blank(next)) break;
|
||||
|
||||
/* Hard break? */
|
||||
/* Wide break? Two trailing spaces/tabs OR a trailing
|
||||
* backslash before the newline force a line break inside
|
||||
* the paragraph (the inline style is preserved). */
|
||||
uint8_t is_hard = 0;
|
||||
if (q >= p + 2) {
|
||||
if (q >= p + 1) {
|
||||
char b1 = fb(q - 1);
|
||||
char b2 = fb(q - 2);
|
||||
if ((b1 == ' ' || b1 == '\t') && (b2 == ' ' || b2 == '\t'))
|
||||
if (b1 == '\\') {
|
||||
is_hard = 1;
|
||||
} else if (q >= p + 2) {
|
||||
char b2 = fb(q - 2);
|
||||
if ((b1 == ' ' || b1 == '\t') && (b2 == ' ' || b2 == '\t'))
|
||||
is_hard = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_hard) {
|
||||
@@ -899,6 +919,23 @@ static void index_lines(void)
|
||||
if (!soft_break) q += 2; else q++;
|
||||
prev_ch = '*'; continue;
|
||||
}
|
||||
if (ch == '~' && (q + 1) < file_size && fb(q + 1) == '~') {
|
||||
char next_ch = ((q + 2) < file_size) ? fb(q + 2) : '\n';
|
||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
||||
line_style = INIT_STYLE_STRIKE;
|
||||
if (!soft_break) q += 2; else q++;
|
||||
continue;
|
||||
} else if (line_style == INIT_STYLE_STRIKE && ws_or_eol_or_delim(next_ch)) {
|
||||
line_style = INIT_STYLE_PLAIN;
|
||||
if (!soft_break) q += 2; else q++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
seg_col += 2;
|
||||
if (!soft_break) q += 2; else q++;
|
||||
prev_ch = '~'; continue;
|
||||
}
|
||||
if (ch == '*' || ch == '_') {
|
||||
char next_ch = ((q + 1) < file_size) ? fb(q + 1) : '\n';
|
||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
||||
@@ -960,6 +997,7 @@ static void index_lines(void)
|
||||
#define EM_ITALIC 2
|
||||
#define EM_UNDER 3
|
||||
#define EM_CODE 4
|
||||
#define EM_STRIKE 5
|
||||
|
||||
static void render_line(uint16_t line_idx, uint8_t row)
|
||||
{
|
||||
@@ -1089,6 +1127,13 @@ static void render_line(uint16_t line_idx, uint8_t row)
|
||||
char prev_ch = ' '; /* for `_` flanking check */
|
||||
while (p < seg_end && col < SCREEN_W) {
|
||||
char ch = fb(p);
|
||||
/* A backslash immediately before this segment's end-of-line is a
|
||||
* wide-break marker (index_lines split here) — consume it without
|
||||
* drawing. Not applied to verbatim code bodies. */
|
||||
if (parse_inline && ch == '\\' && (p + 1) < seg_end) {
|
||||
char nb = fb(p + 1);
|
||||
if (nb == '\n' || nb == '\r') { p++; continue; }
|
||||
}
|
||||
/* Soft break (newline inside a paragraph segment) — render as a
|
||||
* single space. Hard breaks are already separate segments. */
|
||||
if (ch == '\n' || ch == '\r') ch = ' ';
|
||||
@@ -1125,6 +1170,27 @@ static void render_line(uint16_t line_idx, uint8_t row)
|
||||
prev_ch = '*';
|
||||
continue;
|
||||
}
|
||||
/* `~~` (strikethrough) — same flanking rule as `**` */
|
||||
if (ch == '~' && (p + 1) < seg_end && fb(p + 1) == '~') {
|
||||
char next_ch = ((p + 2) < seg_end) ? fb(p + 2) : '\n';
|
||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
||||
if (emph == EM_NONE && ws_or_eol(prev_ch)) {
|
||||
emph = EM_STRIKE; cur_attr = ATTR_TEXT_STRIKE;
|
||||
p += 2; continue;
|
||||
} else if (emph == EM_STRIKE && ws_or_eol_or_delim(next_ch)) {
|
||||
emph = EM_NONE; cur_attr = base_attr;
|
||||
p += 2; continue;
|
||||
}
|
||||
}
|
||||
/* literal `~~` — render both tildes */
|
||||
p += 2;
|
||||
if (cc >= effective_vx && col < SCREEN_W) wrchar(col++, row, '~', cur_attr);
|
||||
cc++;
|
||||
if (cc >= effective_vx && col < SCREEN_W) wrchar(col++, row, '~', cur_attr);
|
||||
cc++;
|
||||
prev_ch = '~';
|
||||
continue;
|
||||
}
|
||||
/* `*` (italic) and `_` (underline) — same flanking rule */
|
||||
if (ch == '*' || ch == '_') {
|
||||
char next_ch = ((p + 1) < seg_end) ? fb(p + 1) : '\n';
|
||||
@@ -1197,6 +1263,12 @@ static void render_line(uint16_t line_idx, uint8_t row)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Left-edge indicator: when panned right, show that nowrap content
|
||||
* is hidden off the left side. Overlays column 0. */
|
||||
if (is_nowrap(line_idx) && viewport_x > 0) {
|
||||
wrchar(0, row, '<', ATTR_TRUNC);
|
||||
}
|
||||
}
|
||||
/* Pad rest of the row so previous render content is wiped. For code-
|
||||
* body rows use the code attribute so the block extends to the right
|
||||
@@ -1306,17 +1378,32 @@ static void scroll_down(uint16_t n)
|
||||
}
|
||||
}
|
||||
|
||||
/* Horizontal pan (only when viewport contains nowrap lines). */
|
||||
/* Horizontal pan (only when the viewport contains nowrap lines). The pan
|
||||
* is bounded by the widest nowrap segment currently on screen, so the user
|
||||
* cannot scroll past the end of the longest code/table line. */
|
||||
static void scroll_h(int8_t delta)
|
||||
{
|
||||
uint8_t any_nowrap = 0;
|
||||
uint16_t maxw = 0;
|
||||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||
if (is_nowrap((uint16_t)(top_line + i))) { any_nowrap = 1; break; }
|
||||
uint16_t li = (uint16_t)(top_line + i);
|
||||
if (li >= n_lines) break;
|
||||
if (!is_nowrap(li)) continue;
|
||||
uint32_t a = seg_off(li);
|
||||
uint32_t b = seg_off((uint16_t)(li + 1));
|
||||
if (b > file_size) b = file_size;
|
||||
uint16_t w = (b > a) ? (uint16_t)(b - a) : 0; /* ~visible width */
|
||||
if (w > maxw) maxw = w;
|
||||
}
|
||||
if (!any_nowrap) return;
|
||||
if (maxw == 0) return; /* no nowrap content in view */
|
||||
|
||||
/* Max pan = width beyond the screen, capped to the uint8 pan range. */
|
||||
uint16_t over = (maxw > SCREEN_W) ? (uint16_t)(maxw - SCREEN_W) : 0;
|
||||
if (over > 248u) over = 248u;
|
||||
uint8_t max_vx = (uint8_t)over;
|
||||
|
||||
int16_t nx = (int16_t)viewport_x + delta;
|
||||
if (nx < 0) nx = 0;
|
||||
if (nx > 240) nx = 240;
|
||||
if (nx < 0) nx = 0;
|
||||
if (nx > (int16_t)max_vx) nx = max_vx;
|
||||
if ((uint8_t)nx != viewport_x) {
|
||||
viewport_x = (uint8_t)nx;
|
||||
render_viewport();
|
||||
|
||||
Reference in New Issue
Block a user