чување 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:
| M | draw.c | | | 78 | ++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
| M | draw.h | | | 2 | +- |
| M | sled.c | | | 31 | +++++++++++-------------------- |
| M | termbox.h | | | 96 | +++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- |
| M | util.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;
}