- Drag, double-click (word) and triple-click (line) selection in the log, auto-scroll at the edges, soft-wrapped lines joined on copy. - sashidown ANSI renderer: delimiter runs with flanking rules, so intraword underscores (test_apply_patch.c) stay literal; unmatched * _ and backticks are shown as-is; * and _ do not close each other. - tests: test_textsel, test_markdown.
662 lines
20 KiB
C
662 lines
20 KiB
C
#include "render_ansi.h"
|
|
#include "ansi_style.h"
|
|
#include "block_scan.h"
|
|
#include "table_stats.h"
|
|
#include "fsm.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
typedef struct {
|
|
uint8_t bold;
|
|
uint8_t italic;
|
|
uint8_t code;
|
|
uint8_t latex;
|
|
uint8_t strike;
|
|
char bold_ch; /* '*' or '_' that opened bold / italic */
|
|
char italic_ch;
|
|
} ansi_inline_t;
|
|
|
|
/* --- emphasis delimiters (CommonMark flanking rules, simplified) ---------- */
|
|
|
|
/* Neighbour of a delimiter run: whitespace (also line start/end), ASCII
|
|
* punctuation, or anything else. Non-ASCII counts as a letter so that CJK
|
|
* text and its full-width punctuation behave like words. */
|
|
enum { MD_SPACE, MD_PUNCT, MD_OTHER };
|
|
|
|
static int md_class(unsigned char c)
|
|
{
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
|
|
return MD_SPACE;
|
|
if ((c >= 0x21 && c <= 0x2F) || (c >= 0x3A && c <= 0x40) ||
|
|
(c >= 0x5B && c <= 0x60) || (c >= 0x7B && c <= 0x7E))
|
|
return MD_PUNCT;
|
|
return MD_OTHER;
|
|
}
|
|
|
|
static size_t md_run(const char *input, size_t end, size_t i)
|
|
{
|
|
size_t j = i;
|
|
while (j < end && input[j] == input[i])
|
|
j++;
|
|
return j - i;
|
|
}
|
|
|
|
/* Flanking of the run [i, i+n) within [start, end). */
|
|
static void md_flank(const char *input, size_t start, size_t end, size_t i,
|
|
size_t n, int *left, int *right, int *pc, int *nc)
|
|
{
|
|
*pc = md_class(i > start ? (unsigned char)input[i - 1] : ' ');
|
|
*nc = md_class(i + n < end ? (unsigned char)input[i + n] : ' ');
|
|
*left = *nc != MD_SPACE && (*nc != MD_PUNCT || *pc != MD_OTHER);
|
|
*right = *pc != MD_SPACE && (*pc != MD_PUNCT || *nc != MD_OTHER);
|
|
}
|
|
|
|
/* '_' must not open or close inside a word (snake_case, __init__). */
|
|
static int md_can_open(const char *input, size_t start, size_t end, size_t i,
|
|
size_t n)
|
|
{
|
|
int l, r, pc, nc;
|
|
md_flank(input, start, end, i, n, &l, &r, &pc, &nc);
|
|
return input[i] == '*' ? l : l && (!r || pc == MD_PUNCT);
|
|
}
|
|
|
|
static int md_can_close(const char *input, size_t start, size_t end, size_t i,
|
|
size_t n)
|
|
{
|
|
int l, r, pc, nc;
|
|
md_flank(input, start, end, i, n, &l, &r, &pc, &nc);
|
|
return input[i] == '*' ? r : r && (!l || nc == MD_PUNCT);
|
|
}
|
|
|
|
/* Whether a run of n delimiters like input[i] that can close follows on this
|
|
* line, outside code spans. */
|
|
static int md_has_closer(const char *input, size_t start, size_t end,
|
|
size_t i, size_t n)
|
|
{
|
|
char d = input[i];
|
|
size_t k = i + n;
|
|
while (k < end) {
|
|
char c = input[k];
|
|
if (c == '\\' && k + 1 < end) {
|
|
k += 2;
|
|
} else if (c == '`') {
|
|
size_t j = block_scan_until_byte(input, end, k + 1, '`');
|
|
k = j < end ? j + 1 : k + 1;
|
|
} else if (c == d) {
|
|
size_t m = md_run(input, end, k);
|
|
if (m == n && md_can_close(input, start, end, k, m))
|
|
return 1;
|
|
k += m;
|
|
} else {
|
|
k++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
uint8_t ul;
|
|
uint8_t ol;
|
|
uint8_t bq;
|
|
uint8_t para;
|
|
int ol_num;
|
|
} ansi_env_t;
|
|
|
|
static void ansi_s(sashidown_ctx_t *ctx, const char *s)
|
|
{
|
|
if (s && s[0])
|
|
fsm_append_str(ctx, s);
|
|
}
|
|
|
|
static void ansi_close_lists(ansi_env_t *env, sashidown_ctx_t *ctx)
|
|
{
|
|
(void)ctx;
|
|
env->ul = 0;
|
|
env->ol = 0;
|
|
env->ol_num = 0;
|
|
}
|
|
|
|
static void ansi_close_para(ansi_env_t *env, sashidown_ctx_t *ctx)
|
|
{
|
|
if (env->para) {
|
|
fsm_append(ctx, '\n');
|
|
env->para = 0;
|
|
}
|
|
}
|
|
|
|
static void ansi_close_all(ansi_env_t *env, sashidown_ctx_t *ctx)
|
|
{
|
|
ansi_close_lists(env, ctx);
|
|
ansi_close_para(env, ctx);
|
|
env->bq = 0;
|
|
}
|
|
|
|
static int ansi_render_link(sashidown_ctx_t *ctx, const ansi_theme_t *th,
|
|
const char *input, size_t len,
|
|
size_t *pos, int is_image)
|
|
{
|
|
size_t i = *pos + 1;
|
|
if (is_image) {
|
|
if (i >= len || input[i] != '[')
|
|
return 0;
|
|
i++;
|
|
}
|
|
size_t text_start = i;
|
|
i = block_scan_until_byte(input, len, i, ']');
|
|
if (i >= len || input[i] != ']') return 0;
|
|
size_t text_end = i;
|
|
i++;
|
|
if (i >= len || input[i] != '(') return 0;
|
|
i++;
|
|
size_t url_start = i;
|
|
i = block_scan_until_byte(input, len, i, ')');
|
|
if (i >= len) return 0;
|
|
|
|
if (is_image) {
|
|
ansi_s(ctx, th->image_on);
|
|
fsm_append_lit(ctx, "[img:");
|
|
fsm_append_len(ctx, input + text_start, text_end - text_start);
|
|
fsm_append_lit(ctx, "](");
|
|
ansi_s(ctx, th->url_on);
|
|
fsm_append_len(ctx, input + url_start, i - url_start);
|
|
ansi_s(ctx, th->url_off);
|
|
fsm_append(ctx, ')');
|
|
ansi_s(ctx, th->image_off);
|
|
} else {
|
|
ansi_s(ctx, th->link_on);
|
|
fsm_append_len(ctx, input + text_start, text_end - text_start);
|
|
ansi_s(ctx, th->link_off);
|
|
fsm_append_lit(ctx, " (");
|
|
ansi_s(ctx, th->url_on);
|
|
fsm_append_len(ctx, input + url_start, i - url_start);
|
|
ansi_s(ctx, th->url_off);
|
|
fsm_append(ctx, ')');
|
|
}
|
|
*pos = i + 1;
|
|
return 1;
|
|
}
|
|
|
|
static void ansi_render_inline(sashidown_ctx_t *ctx, const ansi_theme_t *th,
|
|
const char *input, size_t len,
|
|
size_t start, size_t end)
|
|
{
|
|
ansi_inline_t st = {0, 0, 0, 0, 0, 0, 0};
|
|
size_t i = start;
|
|
|
|
while (i < end) {
|
|
if (st.latex) {
|
|
size_t j = block_scan_until_byte(input, end, i, '$');
|
|
if (j > i)
|
|
fsm_append_len(ctx, input + i, j - i);
|
|
if (j < end) {
|
|
fsm_append(ctx, '$');
|
|
ansi_s(ctx, th->latex_off);
|
|
st.latex = 0;
|
|
i = j + 1;
|
|
} else {
|
|
i = j;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (st.code) {
|
|
size_t j = block_scan_until_byte(input, end, i, '`');
|
|
if (j > i)
|
|
fsm_append_len(ctx, input + i, j - i);
|
|
if (j < end) {
|
|
ansi_s(ctx, th->code_off);
|
|
st.code = 0;
|
|
i = j + 1;
|
|
} else {
|
|
i = j;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
unsigned char ch = (unsigned char)input[i];
|
|
|
|
if ((ch & 0x80) == 0) {
|
|
size_t j = block_scan_plain_ascii(input, end, i);
|
|
if (j > i) {
|
|
fsm_append_len(ctx, input + i, j - i);
|
|
i = j;
|
|
if (i >= end)
|
|
break;
|
|
ch = (unsigned char)input[i];
|
|
}
|
|
} else {
|
|
size_t j = block_scan_utf8_run(input, end, i);
|
|
if (j > i) {
|
|
fsm_append_len(ctx, input + i, j - i);
|
|
i = j;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ch == '$' && i + 1 < end && input[i + 1] == '$') {
|
|
size_t j = block_scan_until_2byte(input, end, i + 2, '$', '$');
|
|
if (j < end)
|
|
j += 2;
|
|
ansi_s(ctx, th->latex_on);
|
|
fsm_append_len(ctx, input + i, j - i);
|
|
ansi_s(ctx, th->latex_off);
|
|
i = j;
|
|
continue;
|
|
}
|
|
|
|
if (ch == '$') {
|
|
ansi_s(ctx, th->latex_on);
|
|
fsm_append(ctx, '$');
|
|
st.latex = 1;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (ch == '!' && i + 1 < end && input[i + 1] == '[') {
|
|
if (ansi_render_link(ctx, th, input, len, &i, 1))
|
|
continue;
|
|
}
|
|
|
|
if (ch == '[') {
|
|
if (ansi_render_link(ctx, th, input, len, &i, 0))
|
|
continue;
|
|
}
|
|
|
|
if (ch == '\\' && i + 1 < end) {
|
|
unsigned char next = (unsigned char)input[i + 1];
|
|
if ((next >= 0x21 && next <= 0x2F) ||
|
|
(next >= 0x3A && next <= 0x40) ||
|
|
(next >= 0x5B && next <= 0x60) ||
|
|
(next >= 0x7B && next <= 0x7E)) {
|
|
fsm_append(ctx, (char)next);
|
|
i += 2;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ch == '*' || ch == '_') {
|
|
/* run of 1 = italic, 2 = bold, 3 = both; others are literal */
|
|
size_t n = md_run(input, end, i);
|
|
int b = n >= 2 && n <= 3, it = n == 1 || n == 3;
|
|
int open_b = st.bold && st.bold_ch == (char)ch;
|
|
int open_i = st.italic && st.italic_ch == (char)ch;
|
|
if (n <= 3 && (!b || open_b) && (!it || open_i) &&
|
|
md_can_close(input, start, end, i, n)) {
|
|
if (it) {
|
|
ansi_s(ctx, th->emph_off);
|
|
st.italic = 0;
|
|
}
|
|
if (b) {
|
|
ansi_s(ctx, th->strong_off);
|
|
st.bold = 0;
|
|
}
|
|
} else if (n <= 3 && (!b || !st.bold) && (!it || !st.italic) &&
|
|
md_can_open(input, start, end, i, n) &&
|
|
md_has_closer(input, start, end, i, n)) {
|
|
if (b) {
|
|
ansi_s(ctx, th->strong_on);
|
|
st.bold = 1;
|
|
st.bold_ch = (char)ch;
|
|
}
|
|
if (it) {
|
|
ansi_s(ctx, th->emph_on);
|
|
st.italic = 1;
|
|
st.italic_ch = (char)ch;
|
|
}
|
|
} else {
|
|
fsm_append_len(ctx, input + i, n);
|
|
}
|
|
i += n;
|
|
continue;
|
|
}
|
|
|
|
if (ch == '~' && i + 1 < end && input[i + 1] == '~') {
|
|
ansi_s(ctx, st.strike ? th->strike_off : th->strike_on);
|
|
st.strike = !st.strike;
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
if (ch == '`' && block_scan_until_byte(input, end, i + 1, '`') < end) {
|
|
ansi_s(ctx, th->code_on);
|
|
st.code = 1;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
fsm_append(ctx, (char)ch);
|
|
i++;
|
|
}
|
|
|
|
if (st.bold) ansi_s(ctx, th->strong_off);
|
|
if (st.italic) ansi_s(ctx, th->emph_off);
|
|
if (st.code) ansi_s(ctx, th->code_off);
|
|
if (st.strike) ansi_s(ctx, th->strike_off);
|
|
if (st.latex) ansi_s(ctx, th->latex_off);
|
|
}
|
|
|
|
static void trim_cell(const char *input, size_t *start, size_t *end)
|
|
{
|
|
while (*start < *end && input[*start] == ' ')
|
|
(*start)++;
|
|
while (*end > *start && input[*end - 1] == ' ')
|
|
(*end)--;
|
|
}
|
|
|
|
static void ansi_table_row_at(sashidown_ctx_t *ctx, const ansi_theme_t *th,
|
|
const char *input, size_t len,
|
|
size_t pos, size_t le, int cols, int is_header)
|
|
{
|
|
size_t i = pos;
|
|
while (i < le && (input[i] == ' ' || input[i] == '\t')) i++;
|
|
if (i < le && input[i] == '|')
|
|
i++;
|
|
|
|
for (int col = 0; col < cols; col++) {
|
|
size_t cs = i;
|
|
while (i < le && input[i] != '|') i++;
|
|
size_t ce = i;
|
|
trim_cell(input, &cs, &ce);
|
|
if (col)
|
|
ansi_s(ctx, th->sep);
|
|
if (is_header)
|
|
ansi_s(ctx, th->th_on);
|
|
ansi_render_inline(ctx, th, input, len, cs, ce);
|
|
if (is_header)
|
|
ansi_s(ctx, th->th_off);
|
|
if (i < le && input[i] == '|') i++;
|
|
}
|
|
fsm_append(ctx, '\n');
|
|
}
|
|
|
|
/* Scheme C (batch): header+sep commit, then body rows one-pass. Returns 1 if
|
|
* a table was consumed (*pos_io advanced past body). */
|
|
static int ansi_try_consume_table(sashidown_ctx_t *ctx, const ansi_theme_t *th,
|
|
ansi_env_t *env, const char *input, size_t len,
|
|
size_t pos, size_t le, size_t *pos_io)
|
|
{
|
|
int has_pipe = 0;
|
|
int cols = table_count_cols(input + pos, le - pos, &has_pipe);
|
|
if (!has_pipe)
|
|
return 0;
|
|
if (le >= len || input[le] != '\n')
|
|
return 0;
|
|
|
|
size_t next = le + 1;
|
|
if (next >= len)
|
|
return 0;
|
|
size_t nle = block_line_end(ctx, input, len, next);
|
|
if (!table_is_separator_row(input + next, nle - next)) {
|
|
block_line_seek(ctx, pos);
|
|
return 0;
|
|
}
|
|
|
|
ansi_close_all(env, ctx);
|
|
ansi_table_row_at(ctx, th, input, len, pos, le, cols, 1);
|
|
pos = block_skip_line(ctx, input, len, next);
|
|
|
|
while (pos < len) {
|
|
size_t ble = block_line_end(ctx, input, len, pos);
|
|
int empty = (pos == ble || (pos + 1 == ble && input[pos] == '\r'));
|
|
if (empty)
|
|
break;
|
|
size_t blen = ble - pos;
|
|
if (!memchr(input + pos, '|', blen) ||
|
|
table_is_separator_row(input + pos, blen))
|
|
break;
|
|
ansi_table_row_at(ctx, th, input, len, pos, ble, cols, 0);
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
}
|
|
fsm_append(ctx, '\n');
|
|
*pos_io = pos;
|
|
return 1;
|
|
}
|
|
|
|
static void ansi_render_codeblock(sashidown_ctx_t *ctx, const ansi_theme_t *th,
|
|
const char *input, size_t len,
|
|
size_t *pos, int fence_len, char fence_char)
|
|
{
|
|
*pos = block_skip_line(ctx, input, len, *pos);
|
|
ansi_s(ctx, th->fence_on);
|
|
while (*pos < len) {
|
|
int fl = 0;
|
|
char fc = 0;
|
|
if (block_is_fence(input, len, *pos, &fl, &fc) &&
|
|
fc == fence_char && fl >= fence_len) {
|
|
size_t is, ie;
|
|
block_fence_info_range(input, len, *pos, fl, fc, &is, &ie);
|
|
if (ie <= is) {
|
|
*pos = block_skip_line(ctx, input, len, *pos);
|
|
break;
|
|
}
|
|
}
|
|
size_t le = block_line_end(ctx, input, len, *pos);
|
|
fsm_append_lit(ctx, " ");
|
|
fsm_append_len(ctx, input + *pos, le - *pos);
|
|
fsm_append(ctx, '\n');
|
|
*pos = block_skip_line(ctx, input, len, *pos);
|
|
}
|
|
ansi_s(ctx, th->fence_off);
|
|
fsm_append(ctx, '\n');
|
|
}
|
|
|
|
static void ansi_render_latex_block(sashidown_ctx_t *ctx, const ansi_theme_t *th,
|
|
const char *input, size_t len,
|
|
size_t *pos)
|
|
{
|
|
size_t i = *pos + 2;
|
|
size_t start = i;
|
|
while (i + 1 < len) {
|
|
if (input[i] == '$' && input[i + 1] == '$') {
|
|
ansi_s(ctx, th->latex_on);
|
|
fsm_append_lit(ctx, "$$");
|
|
if (i > start)
|
|
fsm_append_len(ctx, input + start, i - start);
|
|
fsm_append_lit(ctx, "$$");
|
|
ansi_s(ctx, th->latex_off);
|
|
fsm_append(ctx, '\n');
|
|
i += 2;
|
|
if (i < len && input[i] == '\n') i++;
|
|
*pos = i;
|
|
return;
|
|
}
|
|
i++;
|
|
}
|
|
ansi_s(ctx, th->latex_on);
|
|
fsm_append_lit(ctx, "$$");
|
|
if (i > start)
|
|
fsm_append_len(ctx, input + start, (i < len ? i : len) - start);
|
|
fsm_append_lit(ctx, "$$");
|
|
ansi_s(ctx, th->latex_off);
|
|
fsm_append(ctx, '\n');
|
|
*pos = len;
|
|
}
|
|
|
|
static int sashidown_render_ansi_theme(sashidown_ctx_t *ctx, const char *input, size_t len,
|
|
const ansi_theme_t *th)
|
|
{
|
|
ansi_env_t env;
|
|
memset(&env, 0, sizeof(env));
|
|
size_t pos = 0;
|
|
|
|
fsm_write_begin(ctx);
|
|
|
|
while (pos < len) {
|
|
size_t le = block_line_end(ctx, input, len, pos);
|
|
if (pos == le || (pos + 1 == le && input[pos] == '\r')) {
|
|
ansi_close_all(&env, ctx);
|
|
if (env.bq)
|
|
env.bq = 0;
|
|
fsm_append(ctx, '\n');
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
continue;
|
|
}
|
|
|
|
if (memchr(input + pos, '|', le - pos) != NULL) {
|
|
size_t newt = 0;
|
|
if (ansi_try_consume_table(ctx, th, &env, input, len, pos, le, &newt)) {
|
|
pos = newt;
|
|
block_line_seek(ctx, pos);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
line_info_t li = block_classify_line(ctx, input, len, pos, le);
|
|
|
|
if (li.kind == LK_PARA) {
|
|
line_info_t sli;
|
|
size_t send = 0;
|
|
int sr = block_try_setext(ctx, input, len, pos, le, len, 1, &sli, &send);
|
|
if (sr > 0) {
|
|
int lvl = sli.heading_level;
|
|
if (lvl < 1) lvl = 1;
|
|
if (lvl > 6) lvl = 6;
|
|
ansi_close_all(&env, ctx);
|
|
ansi_s(ctx, th->heading[lvl]);
|
|
ansi_render_inline(ctx, th, input, len, sli.content_start, sli.content_end);
|
|
ansi_s(ctx, th->heading_end);
|
|
fsm_append(ctx, '\n');
|
|
if (lvl <= 2)
|
|
fsm_append(ctx, '\n');
|
|
pos = send;
|
|
block_line_seek(ctx, pos);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch (li.kind) {
|
|
case LK_EMPTY:
|
|
ansi_close_all(&env, ctx);
|
|
if (env.bq)
|
|
env.bq = 0;
|
|
fsm_append(ctx, '\n');
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
break;
|
|
|
|
case LK_HR:
|
|
ansi_close_all(&env, ctx);
|
|
fsm_append_lit(ctx, "---\n\n");
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
break;
|
|
|
|
case LK_FENCE:
|
|
ansi_close_all(&env, ctx);
|
|
ansi_render_codeblock(ctx, th, input, len, &pos, li.fence_len, li.fence_char);
|
|
break;
|
|
|
|
case LK_LATEX_LINE:
|
|
ansi_close_all(&env, ctx);
|
|
ansi_s(ctx, th->latex_on);
|
|
fsm_append_lit(ctx, "$$");
|
|
fsm_append_len(ctx, input + li.content_start,
|
|
li.content_end - li.content_start);
|
|
fsm_append_lit(ctx, "$$");
|
|
ansi_s(ctx, th->latex_off);
|
|
fsm_append(ctx, '\n');
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
break;
|
|
|
|
case LK_LATEX_BLOCK:
|
|
ansi_close_all(&env, ctx);
|
|
ansi_render_latex_block(ctx, th, input, len, &pos);
|
|
break;
|
|
|
|
case LK_HEADING: {
|
|
int lvl = li.heading_level;
|
|
if (lvl < 1) lvl = 1;
|
|
if (lvl > 6) lvl = 6;
|
|
ansi_close_all(&env, ctx);
|
|
ansi_s(ctx, th->heading[lvl]);
|
|
ansi_render_inline(ctx, th, input, len, li.content_start, le);
|
|
ansi_s(ctx, th->heading_end);
|
|
fsm_append(ctx, '\n');
|
|
if (lvl <= 2)
|
|
fsm_append(ctx, '\n');
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
break;
|
|
}
|
|
|
|
case LK_BQ:
|
|
ansi_close_lists(&env, ctx);
|
|
env.bq = 1;
|
|
ansi_s(ctx, th->bq_prefix);
|
|
ansi_render_inline(ctx, th, input, len, li.content_start, le);
|
|
fsm_append(ctx, '\n');
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
break;
|
|
|
|
case LK_UL:
|
|
if (env.ol) { env.ol = 0; env.ol_num = 0; }
|
|
env.ul = 1;
|
|
ansi_s(ctx, th->bullet);
|
|
if (li.task == 1)
|
|
fsm_append_lit(ctx, "[ ] ");
|
|
else if (li.task == 2)
|
|
fsm_append_lit(ctx, "[x] ");
|
|
ansi_render_inline(ctx, th, input, len, li.content_start, le);
|
|
ansi_s(ctx, th->bullet_end);
|
|
fsm_append(ctx, '\n');
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
break;
|
|
|
|
case LK_OL: {
|
|
int num = 0;
|
|
size_t cs = li.content_start;
|
|
block_is_ol(input, len, pos, &cs, &num);
|
|
if (env.ul) env.ul = 0;
|
|
env.ol = 1;
|
|
char buf[32];
|
|
int n = snprintf(buf, sizeof(buf), "%d. ", num > 0 ? num : ++env.ol_num);
|
|
ansi_s(ctx, th->ol_on);
|
|
if (n > 0)
|
|
fsm_append_len(ctx, buf, (size_t)n);
|
|
ansi_s(ctx, th->ol_off);
|
|
ansi_render_inline(ctx, th, input, len, li.content_start, le);
|
|
fsm_append(ctx, '\n');
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
break;
|
|
}
|
|
|
|
case LK_PARA:
|
|
default:
|
|
if (env.bq) env.bq = 0;
|
|
ansi_close_lists(&env, ctx);
|
|
if (!env.para) {
|
|
env.para = 1;
|
|
} else {
|
|
fsm_append(ctx, '\n');
|
|
}
|
|
ansi_render_inline(ctx, th, input, len, pos, le);
|
|
pos = block_skip_line(ctx, input, len, pos);
|
|
if (pos >= len) {
|
|
ansi_close_para(&env, ctx);
|
|
} else {
|
|
size_t nle = block_line_end(ctx, input, len, pos);
|
|
line_info_t nli = block_classify_line(ctx, input, len, pos, nle);
|
|
if (nli.kind != LK_PARA)
|
|
ansi_close_para(&env, ctx);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (ctx->overflow) {
|
|
fsm_write_end(ctx);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ansi_close_all(&env, ctx);
|
|
fsm_write_end(ctx);
|
|
return ctx->overflow ? -1 : 0;
|
|
}
|
|
|
|
int sashidown_render_ansi(sashidown_ctx_t *ctx, const char *input, size_t len)
|
|
{
|
|
return sashidown_render_ansi_theme(ctx, input, len, ansi_theme_color());
|
|
}
|
|
|
|
int sashidown_render_ansi_plain(sashidown_ctx_t *ctx, const char *input, size_t len)
|
|
{
|
|
return sashidown_render_ansi_theme(ctx, input, len, ansi_theme_plain());
|
|
}
|