sled

Једноставни уређивач текста
git clone https://git.sr.ht/~strahinja/sled
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување 836bc9b21de2b20f2113d3f4b03539288d543e5f
родитељ 548bf8f84b00d12df8f0e5b38c003b712b2afbd9
Аутор: Страхиња Радић <sr@strahinja.org>
Датум:   Fri, 12 Jul 2024 07:27:29 +0200

Fix dealing with wide characters; update to latest termbox and update 
termbox API calls to >V1; initial experimental grapheme cluster support

Diffstat:
Mdraw.c | 78++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mdraw.h | 2+-
Msled.c | 31+++++++++++--------------------
Mtermbox.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mutil.c | 35+++++++++++++++++------------------
измењених датотека: 5, додавања: 137(+), брисања: 105(-)

diff --git a/draw.c b/draw.c @@ -165,8 +165,9 @@ calculate_line_numbers_width(const struct DrawState* state) ssize_t display_length(uint32_t* buffer, const ssize_t max_index) { - ssize_t len = 0; uint32_t* pbuf = buffer; + ssize_t len = 0; + int w; if (!buffer) return len; @@ -177,7 +178,11 @@ display_length(uint32_t* buffer, const ssize_t max_index) if (*pbuf == '\t') len += TAB_SIZE - (len % TAB_SIZE); else - len++; + { + w = wcwidth((wchar_t)*pbuf); + if (w > 0) + len += w; + } } return len; @@ -212,28 +217,28 @@ draw_box(const int startx, const int endx, const int starty, const int endy, { int x, y; - tb_change_cell(startx, starty, BORDER_NW, fg, bg); + tb_set_cell(startx, starty, BORDER_NW, fg, bg); for (x = startx + 1; x < endx - 1; x++) - tb_change_cell(x, starty, BORDER_HOR, fg, bg); - tb_change_cell(endx - 1, starty, BORDER_NE, fg, bg); + tb_set_cell(x, starty, BORDER_HOR, fg, bg); + tb_set_cell(endx - 1, starty, BORDER_NE, fg, bg); for (y = starty + 1; y < endy - 2; y++) { - tb_change_cell(startx, y, BORDER_VER, fg, bg); + tb_set_cell(startx, y, BORDER_VER, fg, bg); for (x = startx + 1; x < endx - 1; x++) - tb_change_cell(x, y, ' ', fg, bg); - tb_change_cell(endx - 1, y, BORDER_VER, fg, bg); - tb_change_cell(endx, y, ' ', SHADOW_FG, SHADOW_BG); + tb_set_cell(x, y, ' ', fg, bg); + tb_set_cell(endx - 1, y, BORDER_VER, fg, bg); + tb_set_cell(endx, y, ' ', SHADOW_FG, SHADOW_BG); } - tb_change_cell(startx, endy - 2, BORDER_SW, fg, bg); + tb_set_cell(startx, endy - 2, BORDER_SW, fg, bg); for (x = startx + 1; x < endx - 1; x++) - tb_change_cell(x, endy - 2, BORDER_HOR, fg, bg); - tb_change_cell(endx - 1, endy - 2, BORDER_SE, fg, bg); - tb_change_cell(endx, endy - 2, ' ', SHADOW_FG, SHADOW_BG); + tb_set_cell(x, endy - 2, BORDER_HOR, fg, bg); + tb_set_cell(endx - 1, endy - 2, BORDER_SE, fg, bg); + tb_set_cell(endx, endy - 2, ' ', SHADOW_FG, SHADOW_BG); for (x = startx + 1; x < endx + 1; x++) - tb_change_cell(x, endy - 1, ' ', SHADOW_FG, SHADOW_BG); + tb_set_cell(x, endy - 1, ' ', SHADOW_FG, SHADOW_BG); } int @@ -345,7 +350,7 @@ draw_rect(const int startx, const int endx, const int starty, const int endy, { for (int y = starty; y < endy; y++) for (int x = startx; x < endx; x++) - tb_change_cell(x, y, ' ', fg, bg); + tb_set_cell(x, y, ' ', fg, bg); } int @@ -412,7 +417,7 @@ draw_screen(const struct DrawState* state) ? state->line_numbers_width + 2 : 0, cy, TEXT_FG, TEXT_BG, current->text, maxx, maxx, - 0, 0, CENTER, state->first_shown.col, 0, + 0, 0, LEFT, state->first_shown.col, 0, state->search, SEARCH_FG, SEARCH_BG, ssi, sei, SEL_FG, SEL_BG); i++; @@ -796,6 +801,8 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, int ssc = 0; /* current string screen column; goes from start * of the string until ds+c */ + int ww = 0; /* wide character width */ + const int ts = TAB_SIZE; const uint32_t* ps = s; /* pointer to current character in string; @@ -820,11 +827,11 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, /* fill */ if (fill) for (int j = 0; j < fc; j++) - tb_change_cell(x + j, y, ' ', fg, bg); + tb_set_cell(x + j, y, L' ', fg, bg); /* left padding */ for (int j = 0; j < p_l; j++) - tb_change_cell(x + d + j, y, ' ', fg, bg); + tb_set_cell(x + d + j, y, L' ', fg, bg); sc = 0; @@ -839,7 +846,7 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, * */ while (ps && *ps && sc < c) { - if ((hctl == 0) && (ps - s >= ssi) && (ps - s < sei)) + if ((hctl == 0) && ((ps - s) >= ssi) && ((ps - s) < sei)) { cfg = slfg; cbg = slbg; @@ -873,9 +880,8 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, for (int j = 0; j < ssc - (ssc % ts) + ts - ds; j++) - tb_change_cell(x + d + p_l + sc - + j, - y, ' ', cfg, cbg); + tb_set_cell(x + d + p_l + sc + j, + y, L' ', cfg, cbg); sc += ssc + (ts - (ssc % ts)) - ds; hctl -= ssc + (ts - (ssc % ts)) - ds; if (hctl < 0) @@ -905,9 +911,8 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, if (ssc + (ts - (ssc % ts)) >= ds + c) { for (int j = 0; j < ds + c - ssc; j++) - tb_change_cell(x + d + p_l + sc - + j, - y, ' ', cfg, cbg); + tb_set_cell(x + d + p_l + sc + j, + y, L' ', cfg, cbg); sc = c; ssc = ds + c; hctl = 0; @@ -926,9 +931,8 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, * ds * */ for (int j = 0; j < ts - (ssc % ts); j++) - tb_change_cell(x + d + p_l + sc - + j, - y, ' ', cfg, cbg); + tb_set_cell(x + d + p_l + sc + j, + y, L' ', cfg, cbg); sc += ts - (ssc % ts); hctl -= ts - (ssc % ts); if (hctl < 0) @@ -942,9 +946,19 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, if (ssc >= ds) { if (sc < c) - tb_change_cell(x + d + p_l + sc, y, *ps, - cfg, cbg); - sc++; + { + ww = wcwidth((wchar_t)*ps); + if (ww < 0) + ww = 1; + if ((ww == 0) && (x + d + p_l + sc > 1)) + tb_extend_cell(x + d + p_l + sc + - 1, + y, *ps); + else + tb_set_cell(x + d + p_l + sc, y, + *ps, cfg, cbg); + sc += ww; + } hctl--; if (hctl < 0) hctl = 0; @@ -961,5 +975,5 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, /* right padding */ for (int j = 0; j < p_r; j++) - tb_change_cell(x + d + p_l + c + j, y, ' ', fg, bg); + tb_set_cell(x + d + p_l + c + j, y, L' ', fg, bg); } diff --git a/draw.h b/draw.h @@ -7,7 +7,7 @@ #include <unistd.h> #include <wchar.h> -#define TB_OPT_V1_COMPAT +#define TB_OPT_EGC #include "termbox.h" diff --git a/sled.c b/sled.c @@ -4,6 +4,7 @@ #include <errno.h> #include <fcntl.h> +#include <locale.h> #include <signal.h> #include <stdarg.h> #include <stdint.h> @@ -25,7 +26,6 @@ " LICENSE.termbox.\n") #define SLED_C_GLOBALS -#define TB_OPT_V1_COMPAT #define TB_IMPL #include "draw.h" @@ -1237,12 +1237,11 @@ load_file(struct DrawState* state) struct BufferLine* newchunk = NULL; struct BufferLine* line = NULL; FILE* input = NULL; + uint32_t* newtext = NULL; + uint32_t* ptext = NULL; char* input_line = NULL; ssize_t input_size = limits[L_LINE_MAX]; ssize_t input_len = 0; - uint32_t* newtext = NULL; - uint32_t* ptext = NULL; - char* temp = NULL; input_line = calloc(input_size, 1); if (!input_line) @@ -1269,10 +1268,6 @@ load_file(struct DrawState* state) newchunk[row].text = newtext; } - MEMCCPY(state->action, actions[ACT_READING], LINE_DEFAULT, temp); - - redraw_status(state); - state->buffer = newchunk; state->buffer_size = ALLOC_DELTA; state->rows_count = 0; @@ -1895,6 +1890,9 @@ reread_file(struct DrawState* state) init_drawstate(state, state->error, state->action, state->filename, state->prompt, 1, 1); + MEMCCPY(state->action, actions[ACT_READING], LINE_DEFAULT, temp); + redraw_status(state); + result = load_file(state); switch (result) { @@ -2425,15 +2423,7 @@ suspend_program(struct DrawState* state) /* --- back from suspend --- */ - /* tb_clear() won't cut it unfortunately */ - for (int y = 0; y < state->maxy; y++) - for (int x = 0; x < state->maxx; x++) - tb_set_cell(x, y, '.', TB_WHITE, TB_DEFAULT); - tb_present(); - for (int y = 0; y < state->maxy; y++) - for (int x = 0; x < state->maxx; x++) - tb_set_cell(x, y, ' ', TB_WHITE, TB_DEFAULT); - + tb_clear(); if (draw(state)) return 1; tb_present(); @@ -2794,6 +2784,9 @@ main(int argc, char** argv) exit(1); } #endif + + setlocale(LC_ALL, ""); + if (argc == 1) return print_error(1, "No filename given"); @@ -2868,9 +2861,6 @@ main(int argc, char** argv) perror(PROGRAM_NAME ": unveil"); goto main_cleanup; } -#endif - -#ifdef __OpenBSD__ if (unveil(dir, "rwc") < 0) { perror(PROGRAM_NAME ": unveil"); @@ -3088,6 +3078,7 @@ init_termbox: while (state.running) { + tb_set_clear_attrs(TEXT_FG, TEXT_BG); tb_clear(); if (state.recalculate_size) diff --git a/termbox.h b/termbox.h @@ -11,7 +11,7 @@ follows: MIT License Copyright (c) 2010-2020 nsf <no.smile.face@gmail.com> - 2015-2023 Adam Saponara <as@php.net> + 2015-2024 Adam Saponara <as@php.net> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,8 +32,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef __TERMBOX_H -#define __TERMBOX_H +#ifndef TERMBOX_H_INCL +#define TERMBOX_H_INCL #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE @@ -73,7 +73,7 @@ extern "C" { // __ffi_start -#define TB_VERSION_STR "2.4.0-dev" +#define TB_VERSION_STR "2.5.0-dev" /* The following compile-time options are supported: * @@ -399,20 +399,20 @@ typedef uint16_t uintattr_t; #endif /* The terminal screen is represented as 2d array of cells. The structure is - * optimized for dealing with single-width (wcwidth()==1) Unicode code points, + * optimized for dealing with single-width (wcwidth()==1) Unicode codepoints, * however some support for grapheme clusters (e.g., combining diacritical - * marks) and wide code points (e.g., Hiragana) is provided through ech, nech, + * marks) and wide codepoints (e.g., Hiragana) is provided through ech, nech, * cech via tb_set_cell_ex(). ech is only valid when nech>0, otherwise ch is * used. * - * For non-single-width code points, given N=wcwidth(ch)/wcswidth(ech): + * For non-single-width codepoints, given N=wcwidth(ch)/wcswidth(ech): * * when N==0: termbox forces a single-width cell. Callers should avoid this * if aiming to render text accurately. * * when N>1: termbox zeroes out the following N-1 cells and skips sending * them to the tty. So, e.g., if the caller sets x=0,y=0 to an N==2 - * code point, the caller's next set should be at x=2,y=0. Anything + * codepoint, the caller's next set should be at x=2,y=0. Anything * set at x=1,y=0 will be ignored. If there are not enough columns * remaining on the line to render N width, spaces are sent * instead. @@ -420,13 +420,13 @@ typedef uint16_t uintattr_t; * See tb_present() for implementation. */ struct tb_cell { - uint32_t ch; /* a Unicode character */ + uint32_t ch; /* a Unicode codepoint */ uintattr_t fg; /* bitwise foreground attributes */ uintattr_t bg; /* bitwise background attributes */ #ifdef TB_OPT_EGC - uint32_t *ech; /* a grapheme cluster of Unicode code points */ - size_t nech; /* length in bytes of ech, 0 means use ch instead of ech */ - size_t cech; /* capacity in bytes of ech */ + uint32_t *ech; /* a grapheme cluster of Unicode codepoints, 0-terminated */ + size_t nech; /* num elements in ech, 0 means use ch instead of ech */ + size_t cech; /* num elements allocated for ech */ #endif }; @@ -447,7 +447,7 @@ struct tb_event { uint8_t type; /* one of TB_EVENT_* constants */ uint8_t mod; /* bitwise TB_MOD_* constants */ uint16_t key; /* one of TB_KEY_* constants */ - uint32_t ch; /* a Unicode code point */ + uint32_t ch; /* a Unicode codepoint */ int32_t w; /* resize width */ int32_t h; /* resize height */ int32_t x; /* mouse x */ @@ -500,7 +500,7 @@ int tb_hide_cursor(void); * Function tb_set_cell(x, y, ch, fg, bg) is equivalent to * tb_set_cell_ex(x, y, &ch, 1, fg, bg). * - * Function tb_extend_cell() is a shortcut for appending 1 code point to + * Function tb_extend_cell() is a shortcut for appending 1 codepoint to * cell->ech. */ int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg); @@ -641,7 +641,8 @@ int tb_poll_event(struct tb_event *event); int tb_get_fds(int *ttyfd, int *resizefd); /* Print and printf functions. Specify param out_w to determine width of printed - * string. + * string. Incomplete trailing UTF-8 byte sequences are replaced with U+FFFD. + * For finer control, use tb_set_cell(). */ int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str); int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...); @@ -667,10 +668,28 @@ int tb_sendf(const char *fmt, ...); */ int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)); -/* Utility functions. */ +/* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */ int tb_utf8_char_length(char c); + +/* Convert UTF-8 null-terminated byte sequence to UTF-32 codepoint. + * + * If `c` is an empty C string, return 0. `out` is left unchanged. + * + * If a null byte is encountered in the middle of the codepoint, return a + * negative number indicating how many bytes were processed. `out` is left + * unchanged. + * + * Otherwise, return byte length of codepoint (1-6). + */ int tb_utf8_char_to_unicode(uint32_t *out, const char *c); + +/* Convert UTF-32 codepoint to UTF-8 null-terminated byte sequence. + * + * `out` must be char[7] or greater. Return byte length of codepoint (1-6). + */ int tb_utf8_unicode_to_char(char *out, uint32_t c); + +/* Library utility functions */ int tb_last_errno(void); const char *tb_strerror(int err); struct tb_cell *tb_cell_buffer(void); @@ -683,7 +702,7 @@ const char *tb_version(void); } #endif -#endif /* __TERMBOX_H */ +#endif /* TERMBOX_H_INCL */ #ifdef TB_IMPL @@ -1704,7 +1723,6 @@ int tb_hide_cursor(void) { } int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) { - if_not_init_return(); return tb_set_cell_ex(x, y, &ch, 1, fg, bg); } @@ -1825,11 +1843,17 @@ int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, *out_w = 0; } while (*str) { - str += tb_utf8_char_to_unicode(&uni, str); - w = wcwidth((wchar_t)uni); - if (w < 0) { - w = 1; + rv = tb_utf8_char_to_unicode(&uni, str); + if (rv < 0) { + uni = 0xfffd; // replace invalid UTF-8 char with U+FFFD + str += rv * -1; + } else if (rv > 0) { + str += rv; + } else { + break; // shouldn't get here } + w = wcwidth((wchar_t)uni); + if (w < 0) w = 1; if (w == 0 && x > ix) { if_err_return(rv, tb_extend_cell(x - 1, y, uni)); } else { @@ -1902,19 +1926,19 @@ int tb_utf8_char_length(char c) { } int tb_utf8_char_to_unicode(uint32_t *out, const char *c) { - if (*c == 0) { - return TB_ERR; - } + if (*c == '\0') return 0; int i; unsigned char len = tb_utf8_char_length(*c); unsigned char mask = utf8_mask[len - 1]; uint32_t result = c[0] & mask; - for (i = 1; i < len; ++i) { + for (i = 1; i < len && c[i] != '\0'; ++i) { result <<= 6; result |= c[i] & 0x3f; } + if (i != len) return i * -1; + *out = result; return (int)len; } @@ -1949,6 +1973,7 @@ int tb_utf8_unicode_to_char(char *out, uint32_t c) { c >>= 6; } out[0] = c | first; + out[len] = '\0'; return len; } @@ -2771,7 +2796,7 @@ static int extract_esc_mouse(struct tb_event *event) { enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; - char *cmp[TYPE_MAX] = {// + const char *cmp[TYPE_MAX] = {// // X10 mouse encoding, the simplest one // \x1b [ M Cb Cx Cy [TYPE_VT200] = "\x1b[M", @@ -3170,7 +3195,7 @@ static int send_char(int x, int y, uint32_t ch) { static int send_cluster(int x, int y, uint32_t *ch, size_t nch) { int rv; - char abuf[8]; + char chu8[8]; if (global.last_x != x - 1 || global.last_y != y) { if_err_return(rv, send_cursor_if(x, y)); @@ -3180,12 +3205,15 @@ static int send_cluster(int x, int y, uint32_t *ch, size_t nch) { int i; for (i = 0; i < (int)nch; i++) { - uint32_t ach = *(ch + i); - int aw = tb_utf8_unicode_to_char(abuf, ach); - if (!ach) { - abuf[0] = ' '; + uint32_t ch32 = *(ch + i); + int chu8_len; + if (ch32 == 0) { // replace null with space (from termbox 19dbee5) + chu8_len = 1; + chu8[0] = ' '; + } else { + chu8_len = tb_utf8_unicode_to_char(chu8, ch32); } - if_err_return(rv, bytebuf_nputs(&global.out, abuf, (size_t)aw)); + if_err_return(rv, bytebuf_nputs(&global.out, chu8, (size_t)chu8_len)); } return TB_OK; @@ -3241,7 +3269,7 @@ static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, } else { int rv; if_err_return(rv, cell_reserve_ech(cell, nch + 1)); - memcpy(cell->ech, ch, nch); + memcpy(cell->ech, ch, sizeof(ch) * nch); cell->ech[nch] = '\0'; cell->nech = nch; } diff --git a/util.c b/util.c @@ -19,12 +19,12 @@ u8_string_to_unicode(uint32_t* us, const char* s, const size_t max) int u8_len = 0; size_t added = 0; - while (s && *s && u8_len != TB_ERR) + while (s && *s && u8_len > 0) { u8_len = tb_utf8_char_to_unicode(&uch, s); if (added + 1 > max) break; - if (u8_len != TB_ERR) + if (u8_len > 0) { *pus++ = uch; s += u8_len; @@ -93,31 +93,30 @@ uint32_t* u32_u8_memccpy(uint32_t* dst, const char* src, uint32_t c, size_t len) { size_t copied = 0; - size_t u8_len; + int u8_len; uint32_t uch; const char* psrc = src; - if (!*psrc) - { - *dst = 0; - return dst; - } - - u8_len = tb_utf8_char_to_unicode(&uch, psrc); - while ((*dst++ = uch) != c && copied < len) + do { + if (!*psrc) + { + *dst++ = 0; + break; + } + u8_len = tb_utf8_char_to_unicode(&uch, psrc); + if (u8_len < 0) + { + /* error, replace uch with "error char" */ + uch = 0xFFFD; + u8_len = -u8_len; + } psrc += u8_len; copied += u8_len; if (copied >= len) return NULL; - if (*psrc) - u8_len = tb_utf8_char_to_unicode(&uch, psrc); - else - { - *dst = 0; - break; - } } + while ((*dst++ = uch) != c && copied < len); return dst; }