#ifndef _GNU_SOURCE #define _GNU_SOURCE /* wcwidth */ #endif #include "cst_textsel.h" #include "os_compat.h" #include #include #include int cst_utf8_decode(const char *s, size_t avail, wchar_t *wc, size_t *eaten) { unsigned long cp = 0; int n; if (avail == 0) return -1; /* os_utf8_decode(), not mbrtowc(): the latter follows setlocale(), which on * Windows means the ANSI code page, so UTF-8 text would be decoded as GBK * and rendered as mojibake. */ n = os_utf8_decode(s, avail, &cp); if (n <= 0) { /* Not a valid sequence: fall back to one raw byte, as before. */ *wc = (wchar_t)(unsigned char)s[0]; *eaten = 1; return 0; } *wc = (wchar_t)cp; *eaten = (size_t)n; return 0; } int cst_wcwidth(wchar_t wc) { #ifdef _WIN32 unsigned long c = (unsigned long)wc; if (c == 0) return 0; if (c < 32 || (c >= 0x7f && c < 0xa0)) return -1; if (c >= 0x1100 && (c <= 0x115f || c == 0x2329 || c == 0x232a || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || (c >= 0xac00 && c <= 0xd7a3) || (c >= 0xf900 && c <= 0xfaff) || (c >= 0xfe10 && c <= 0xfe19) || (c >= 0xfe30 && c <= 0xfe6f) || (c >= 0xff00 && c <= 0xff60) || (c >= 0xffe0 && c <= 0xffe6) || (c >= 0x20000 && c <= 0x2fffd) || (c >= 0x30000 && c <= 0x3fffd))) return 2; return 1; #else return wcwidth(wc); #endif } int cst_utf8_width(wchar_t wc) { int w = cst_wcwidth(wc); if (w < 0) return 1; return w; } size_t cst_ansi_skip(const char *s, size_t n) { if (n < 2 || (unsigned char)s[0] != 0x1b) return 0; if (s[1] == '[') { size_t i = 2; while (i < n) { unsigned char c = (unsigned char)s[i++]; if (c >= 0x40 && c <= 0x7e) return i; } return 0; } return 2; } void cst_selbuf_free(cst_selbuf_t *b) { free(b->s); b->s = NULL; b->n = b->cap = 0; } static int selbuf_put(cst_selbuf_t *b, const char *p, size_t n) { if (b->n + n + 1 > b->cap) { size_t nc = b->cap ? b->cap * 2 : 256; while (nc < b->n + n + 1) nc *= 2; char *ns = realloc(b->s, nc); if (!ns) return -1; b->s = ns; b->cap = nc; } memcpy(b->s + b->n, p, n); b->n += n; b->s[b->n] = 0; return 0; } int cst_selbuf_putc(cst_selbuf_t *b, char c) { return selbuf_put(b, &c, 1); } /* One drawn character: its bytes in the line and the columns it covers. */ typedef struct { size_t off, len; int col, width; wchar_t wc; } cell_t; /* Advances to the next drawn character of line, skipping escapes. */ static int next_cell(const char *line, size_t n, size_t *i, int *col, cell_t *c) { while (*i < n) { size_t skip = cst_ansi_skip(line + *i, n - *i); if (skip) { *i += skip; continue; } size_t eaten; cst_utf8_decode(line + *i, n - *i, &c->wc, &eaten); c->off = *i; c->len = eaten; c->col = *col; c->width = cst_utf8_width(c->wc); *i += eaten; *col += c->width; return 1; } return 0; } int cst_textsel_append(cst_selbuf_t *b, const char *line, int indent, int c1, int c2, int trim) { size_t n = line ? strlen(line) : 0, i = 0; size_t start = b->n; int col = indent; cell_t c; while (next_cell(line, n, &i, &col, &c)) { if (c2 >= 0 && c.col > c2) break; if (c.width == 0 || c.col + c.width - 1 < c1) continue; if (selbuf_put(b, line + c.off, c.len) < 0) return -1; } if (trim) while (b->n > start && b->s[b->n - 1] == ' ') b->s[--b->n] = 0; return 0; } static int is_word(wchar_t wc) { return iswalnum((wint_t)wc) || (wc < 128 && wc && strchr("_-./:@~%+=?&#$", (int)wc)); } void cst_textsel_snap(const char *line, int indent, int *c1, int *c2) { size_t n = line ? strlen(line) : 0, i = 0; int x = indent; cell_t c; while (next_cell(line, n, &i, &x, &c)) { if (c.width < 2) continue; int last = c.col + c.width - 1; if (*c1 > c.col && *c1 <= last) *c1 = c.col; if (*c2 >= c.col && *c2 < last) *c2 = last; } } void cst_textsel_word(const char *line, int indent, int col, int *c1, int *c2) { *c1 = *c2 = col; size_t n = line ? strlen(line) : 0, i = 0; int x = indent; cell_t c; int run_start = -1, in_run = 0; while (next_cell(line, n, &i, &x, &c)) { if (c.width == 0) continue; int word = is_word(c.wc); if (word && !in_run) run_start = c.col; in_run = word; if (col >= c.col && col < c.col + c.width) { if (!word) { *c1 = c.col; *c2 = c.col + c.width - 1; return; } *c1 = run_start; *c2 = c.col + c.width - 1; cell_t d; while (next_cell(line, n, &i, &x, &d)) { if (d.width == 0) continue; if (!is_word(d.wc)) break; *c2 = d.col + d.width - 1; } return; } } }