commit 420caa07a66b197ee51017a96eba8ae98f099c11
parent 9f51eb99fd04cf87cbb83fe5ef594abd3153b892
Author: Claudio Alessi <smoppy@gmail.com>
Date: Wed, 14 Jan 2026 22:50:10 +0100
Handle horizontal truncation in compat mode.
Add new flags attribute into the cell struct to coordinate specific
scenarious with the backend. Truncation is one of this and it's the only
one implemented and only in compat mode.
This commit also cleanup the code a bit and fix a few corner cases.
Diffstat:
| M | edo.c | | | 22 | ++++++++++++++++------ |
| M | tui.c | | | 103 | ++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------- |
| M | ui.h | | | 7 | +++++++ |
| M | utf8.h | | | 6 | ++++++ |
4 files changed, 95 insertions(+), 43 deletions(-)
diff --git a/edo.c b/edo.c
@@ -384,11 +384,10 @@ render(Cell *cells, char *buf, int buflen, int xoff, int cols) {
len = ui->text_len(buf + i, buflen - i);
w = ui->text_width(buf + i, len, vx);
- if(vx + w <= xoff) goto next; /* horizontal scroll */
-
+ /* horizontal scroll skip */
+ if(vx + w <= xoff) goto next;
x = vx - xoff;
if(x >= cols) break; /* screen has been filled */
- if(x + w > cols) break; /* truncated character (TODO: draw a symbol?) */
if(len > CELL_POOL_THRESHOLD)
cells[nc].data.pool_idx = textpool_insert(&ui->pool, buf + i, len);
@@ -396,10 +395,21 @@ render(Cell *cells, char *buf, int buflen, int xoff, int cols) {
memcpy(cells[nc].data.text, buf + i, len);
cells[nc].len = len;
- cells[nc].width = w;
+ cells[nc].flags = 0;
- /* TODO: handle truncated multi-column characters */
- if(vx < xoff) cells[nc].width -= xoff - vx; /* partial rendering */
+ if(x + w > cols) {
+ cells[nc].flags |= CELL_TRUNC_R;
+ w = cols - x;
+ cells[nc].width = w;
+ nc++;
+ break;
+ }
+
+ cells[nc].width = w;
+ if(vx < xoff) {
+ cells[nc].flags |= CELL_TRUNC_L;
+ cells[nc].width -= (xoff - vx);
+ }
++nc;
next:
diff --git a/tui.c b/tui.c
@@ -15,16 +15,13 @@
#include "utf8.h"
#include "ui.h"
-#define CURPOS "\33[%d;%dH"
-//#define CLEARLEFT "\33[1K"
-#define CLEARRIGHT "\33[0K"
-#define CURHIDE "\33[?25l"
-#define CURSHOW "\33[?25h"
-#define ERASECHAR "\33[1X"
-#define ZWNJ "\xe2\x80\x8c"
-
-/* UTF-8 Regional Indicator Symbol */
-#define IS_RIS(c) ((c) >= 0x1F1E6 && (c) <= 0x1F1FF)
+#define ESC "\x1b"
+#define CURPOS ESC"[%d;%dH"
+#define CLEARRIGHT ESC"[0K"
+#define CURHIDE ESC"[?25l"
+#define CURSHOW ESC"[?25h"
+//#define CLEARLEFT ESC"[1K"
+//#define ERASECHAR ESC"[1X"
typedef struct {
char *buf;
@@ -118,12 +115,12 @@ ab_flush(Abuf *ab) {
void
tui_frame_start(void) {
- ab_printf(&frame, CURHIDE);
+ ab_write(&frame, CURHIDE, sizeof CURHIDE - 1);
}
void
tui_frame_flush(void) {
- ab_printf(&frame, CURSHOW);
+ ab_write(&frame, CURSHOW, sizeof CURSHOW - 1);
ab_flush(&frame);
}
@@ -143,11 +140,8 @@ tui_text_width(char *s, int len, int x) {
wc = -1;
if(compat_mode) {
- if(cp == 0x200D) {
- w += 6;
- continue;
- }
- if(IS_RIS(cp)) wc = 2;
+ if(cp == 0x200D) wc = 6;
+ else if(IS_RIS(cp)) wc = 2;
} else {
/* force 2 cells width for emoji followed by VS16 */
int nxi = i + step;
@@ -200,58 +194,74 @@ tui_draw_line_compat(UI *ui, int x, int y, Cell *cells, int count) {
unsigned int cp;
int o = 0;
+ int vw = 0;
while(o < cells[i].len) {
int step = utf8_decode(txt + o, cells[i].len - o, &cp);
int cw = cells[i].width;
int neederase = cells[i].len > 1 && (cells[i].width == 1 || IS_RIS(cp));
+ char tag[] = "<200d>";
+ int tagw = 6;
+ int offset = 0;
switch(cp) {
case 0x200D:
- /* TODO: temporarly hardcode, must *always* match cells[i].width */
- ab_write(&frame, "<200d>", 6);
+ /* TODO: */
+ if(cells[i].flags & CELL_TRUNC_L) {
+ offset = tagw - cells[i].width;
+ if(offset < 0) offset = 0;
+ }
+ int len_to_print = cells[i].width;
+ assert(offset + len_to_print <= tagw);
+
+ if(len_to_print > 0) ab_write(&frame, tag + offset, len_to_print);
break;
case '\t':
for(int t = 0; t < cells[i].width; t++)
ab_write(&frame, " ", 1);
break;
default:
- /* don't expect negative values here */
- cw = wcwidth(cp);
- if(cw < 0) cw = 0;
+ if(cells[i].flags & CELL_TRUNC_L) {
+ if(!cells[i].width) break;
+ ab_write(&frame, "<", 1);
+ cw = 1;
+ break;
+ }
+ if(cells[i].flags & CELL_TRUNC_R) {
+ if(!cells[i].width) break;
+ ab_write(&frame, ">", 1);
+ cw = 1;
+ break;
+ }
if(neederase) {
- const char t[] = "\x1b[48;5;232m"ERASECHAR;
+ const char t[] = ESC"[48;5;232m";
ab_write(&frame, t, sizeof t - 1);
}
+ /* don't expect negative values here */
+ cw = wcwidth(cp);
+ if(cw < 0) cw = 0;
+
ab_write(&frame, txt + o, step);
/* to preserve coherence between terminals always split RIS
* so that we can see individual components. This is needed
* to ensure cursor synchronization between terminals that
* renders the same glyph with 2 different widths. */
- if(IS_RIS(cp)) {
- const char t[] = ZWNJ;
- ab_write(&frame, t, sizeof t - 1);
- }
+ if(IS_RIS(cp)) ab_write(&frame, ZWNJ, sizeof ZWNJ - 1);
if(neederase) {
- const char t[] = "\x1b[0m";
+ const char t[] = ESC"[0m";
ab_write(&frame, t, sizeof t - 1);
}
break;
}
- /* pad clusters having unexpected width */
- if(cw < cells[i].width) {
- /* this is needed to handle inconsistences between terminals
- * whose may use single cell or 2-cell cursors for the same
- * exact character. */
- tui_move_cursor(x + cw, y);
+ vw += cw;
- while(cw++ < cells[i].width) ab_write(&frame, " ", 1);
- }
+ /* stop processing after truncation */
+ if(cells[i].flags & (CELL_TRUNC_L | CELL_TRUNC_R)) break;
/* no more visual characters expected for this cell */
if(neederase) break;
@@ -259,11 +269,23 @@ tui_draw_line_compat(UI *ui, int x, int y, Cell *cells, int count) {
o += step;
}
+ /* pad clusters having unexpected width */
+ if(vw < cells[i].width) {
+ /* this is needed to handle inconsistences between terminals
+ * whose may use single cell or 2-cell cursors for the same
+ * exact character. */
+ tui_move_cursor(x + vw, y);
+
+ while(vw++ < cells[i].width) ab_write(&frame, " ", 1);
+ }
+
x += cells[i].width;
}
- ab_write(&frame, CLEARRIGHT, strlen(CLEARRIGHT));
+
+ if(x < ws.ws_col) ab_write(&frame, CLEARRIGHT, strlen(CLEARRIGHT));
}
+/* TODO: add support for cell flags */
void
tui_draw_line(UI *ui, int x, int y, Cell *cells, int count) {
assert(x < ws.ws_col && y < ws.ws_row);
@@ -310,7 +332,14 @@ tui_init(void) {
setlocale(LC_CTYPE, "");
tcgetattr(0, &origti);
cfmakeraw(&ti);
+
ti.c_iflag |= ICRNL;
+ /*
+ ti.c_iflag &= ~(BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+ ti.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+ ti.c_cflag &= ~(CSIZE|PARENB);
+ ti.c_cflag |= CS8;
+ */
ti.c_cc[VMIN] = 1;
ti.c_cc[VTIME] = 0;
tcsetattr(0, TCSAFLUSH, &ti);
diff --git a/ui.h b/ui.h
@@ -27,6 +27,12 @@ typedef struct {
size_t len;
} TextPool;
+enum CellFlags {
+ CELL_DEFAULT,
+ CELL_TRUNC_L,
+ CELL_TRUNC_R
+};
+
#define CELL_POOL_THRESHOLD 8
typedef struct {
union {
@@ -35,6 +41,7 @@ typedef struct {
} data;
uint16_t len;
uint16_t width;
+ int flags;
} Cell;
typedef struct UI UI;
diff --git a/utf8.h b/utf8.h
@@ -1,5 +1,11 @@
#include <stdlib.h>
+/* Regional Indicator Symbol */
+#define IS_RIS(c) ((c) >= 0x1F1E6 && (c) <= 0x1F1FF)
+
+/* Zero-Width Non-Joiner */
+#define ZWNJ "\xe2\x80\x8c"
+
int utf8_len(char *buf, int len);
size_t utf8_len_compat(char *buf, int len);
int utf8_decode(char *buf, int len, unsigned int *cp);