edo

Experimental text editor.
Log | Files | Refs | LICENSE

tui.c (7101B)


      1 #define _XOPEN_SOURCE
      2 #define _BSD_SOURCE
      3 #include <wchar.h>
      4 
      5 #include <assert.h>
      6 #include <stdarg.h>
      7 #include <stdio.h>
      8 #include <locale.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <sys/ioctl.h>
     12 #include <termios.h>
     13 #include <unistd.h>
     14 
     15 #include "utf8.h"
     16 #include "ui.h"
     17 
     18 #define CURPOS          "\33[%d;%dH"
     19 //#define CLEARLEFT       "\33[1K"
     20 #define CLEARRIGHT      "\33[0K"
     21 #define CURHIDE         "\33[?25l"
     22 #define CURSHOW         "\33[?25h"
     23 #define ERASECHAR       "\33[1X"
     24 
     25 #define IS_RIS(c) ((c) >= 0x1F1E6 && (c) <= 0x1F1FF)
     26 
     27 typedef struct {
     28 	char *buf;
     29 	int len;
     30 	int cap;
     31 } Abuf;
     32 
     33 /* globals */
     34 struct termios origti;
     35 struct winsize ws;
     36 Abuf frame;
     37 int compat_mode;
     38 
     39 /* TODO: edo.h? */
     40 extern void *ecalloc(size_t nmemb, size_t size);
     41 extern void *erealloc(void *p, size_t size);
     42 extern void die(const char *fmt, ...);
     43 extern char *cell_get_text(Cell *cell, char *pool_base);
     44 
     45 /* function declarations */
     46 void ab_free(Abuf *ab);
     47 void ab_ensure_cap(Abuf *ab, size_t addlen);
     48 void ab_write(Abuf *ab, const char *s, size_t len);
     49 int ab_printf(Abuf *ab, const char *fmt, ...);
     50 void ab_flush(Abuf *ab);
     51 void tui_frame_start(void);
     52 void tui_frame_flush(void);
     53 int tui_text_width(char *s, int len, int x);
     54 int tui_text_len(char *s, int len);
     55 void tui_get_window_size(int *rows, int *cols);
     56 void tui_exit(void);
     57 void tui_move_cursor(int x, int y);
     58 void tui_draw_line(UI *ui, int x, int y, Cell *cells, int screen_cols);
     59 void tui_draw_symbol(int r, int c, Symbol sym);
     60 void tui_init(void);
     61 
     62 /* function implementations */
     63 void
     64 ab_free(Abuf *ab) {
     65 	if(!ab->buf)
     66 		return;
     67 	free(ab->buf);
     68 	ab->buf = NULL;
     69 	ab->len = 0;
     70 	ab->cap = 0;
     71 }
     72 
     73 void
     74 ab_ensure_cap(Abuf *ab, size_t addlen) {
     75 	size_t newlen = ab->len + addlen;
     76 
     77 	if(newlen <= ab->cap)
     78 		return;
     79 	while(newlen > ab->cap)
     80 		ab->cap = ab->cap ? ab->cap * 2 : 8;
     81 	ab->buf = erealloc(ab->buf, ab->cap);
     82 }
     83 
     84 void
     85 ab_write(Abuf *ab, const char *s, size_t len) {
     86 	ab_ensure_cap(&frame, len);
     87 	memcpy(ab->buf + ab->len, s, len);
     88 	ab->len += len;
     89 }
     90 
     91 int
     92 ab_printf(Abuf *ab, const char *fmt, ...) {
     93 	va_list ap;
     94 	int len;
     95 
     96 	va_start(ap, fmt);
     97 	len = vsnprintf(NULL, 0, fmt, ap);
     98 	assert(len >= 0);
     99 	va_end(ap);
    100 
    101 	ab_ensure_cap(ab, len + 1);
    102 
    103 	va_start(ap, fmt);
    104 	vsnprintf(ab->buf + ab->len, len + 1, fmt, ap);
    105 	va_end(ap);
    106 
    107 	ab->len += len;
    108 	return len;
    109 }
    110 
    111 void
    112 ab_flush(Abuf *ab) {
    113 	write(STDOUT_FILENO, ab->buf, ab->len);
    114 	ab_free(ab);
    115 }
    116 
    117 void
    118 tui_frame_start(void) {
    119 	ab_printf(&frame, CURHIDE);
    120 }
    121 
    122 void
    123 tui_frame_flush(void) {
    124 	ab_printf(&frame, CURSHOW);
    125 	ab_flush(&frame);
    126 }
    127 
    128 int
    129 tui_text_width(char *s, int len, int x) {
    130 	int tabstop = 8;
    131 	int w = 0, i;
    132 	int step, wc;
    133 	unsigned int cp;
    134 
    135 	for(i = 0; i < len; i += step) {
    136 		step = utf8_decode(s + i, len - i, &cp);
    137 		if(cp == '\t') {
    138 			w += tabstop - x % tabstop;
    139 			continue;
    140 		}
    141 
    142 		wc = -1;
    143 		if(compat_mode) {
    144 			if(cp == 0x200D) {
    145 				w += 6;
    146 				continue;
    147 			}
    148 			if(IS_RIS(cp)) wc = 2;
    149 		} else {
    150 			/* force 2 cells width for emoji followed by VS16 */
    151 			int nxi = i + step;
    152 			if(nxi < len) {
    153 				unsigned int nxcp;
    154 				utf8_decode(s + nxi, len - nxi, &nxcp);
    155 
    156 				if(nxcp == 0xFE0F && ((cp >= 0x203C && cp <= 0x3299) || cp >= 0x1F000))
    157 					wc = 2;
    158 			}
    159 		}
    160 
    161 		if(wc == -1) wc = wcwidth(cp);
    162 		if(wc > 0) w += wc;
    163 	}
    164 	return w;
    165 }
    166 
    167 int
    168 tui_text_len(char *s, int len) {
    169 	return compat_mode ? utf8_len_compat(s, len) : utf8_len(s, len);
    170 }
    171 
    172 void
    173 tui_get_window_size(int *rows, int *cols) {
    174 	*rows = ws.ws_row;
    175 	*cols = ws.ws_col;
    176 }
    177 
    178 void
    179 tui_exit(void) {
    180 	tcsetattr(0, TCSANOW, &origti);
    181 	printf(CURPOS CLEARRIGHT, ws.ws_row, 0);
    182 }
    183 
    184 void
    185 tui_move_cursor(int c, int r) {
    186 	int x = c + 1, y = r + 1; /* TERM coords are 1-based */
    187 	ab_printf(&frame, CURPOS, y, x);
    188 }
    189 
    190 void
    191 tui_draw_line_compat(UI *ui, int x, int y, Cell *cells, int count) {
    192 	char *txt;
    193 	unsigned int cp = 0;
    194 	int was_emoji = 0;
    195 	int i;
    196 
    197 	tui_move_cursor(x, y);
    198 	for(i = 0; i < count; i++) {
    199 		x += cells[i].width;
    200 		txt = cell_get_text(cells + i, ui->pool.data);
    201 
    202 		int cw = cells[i].width;
    203 
    204 		/* TODO: temp code for testing, we'll se how to deal with this later */
    205 		if(txt[0] == '\t') {
    206 			ab_printf(&frame, "%*s", cells[i].width, " ");
    207 		} else {
    208 			int o = 0;
    209 
    210 			while(o < cells[i].len) {
    211 				int step = utf8_decode(txt + o, cells[i].len - o, &cp);
    212 
    213 				if(cp == 0x200D) {
    214 					ab_write(&frame, "<200d>", cells[i].width);
    215 				} else {
    216 					int w = wcwidth(cp);
    217 					if(w > 0) cw = w;
    218 
    219 					if(was_emoji) {
    220 						/* ERASECHAR to clear eventual garbage state */
    221 						const char t[] = "\x1b[48;5;232m"ERASECHAR;
    222 						ab_write(&frame, t, sizeof t - 1);
    223 					}
    224 
    225 					ab_write(&frame, txt + o, step);
    226 
    227 					if(was_emoji) {
    228 						const char t[] = "\x1b[0m";
    229 						ab_write(&frame, t, sizeof t - 1);
    230 					}
    231 				}
    232 				o += step;
    233 			}
    234 		}
    235 
    236 		/* pad if needed */
    237 		if(cw < cells[i].width) {
    238 			tui_move_cursor(x - cells[i].width + cw, y);
    239 
    240 			const char t[] = "\x1b[48;5;233m";
    241 			ab_write(&frame, t, sizeof t - 1);
    242 
    243 			while(cw++ < cells[i].width) ab_write(&frame, " ", 1);
    244 
    245 			const char t2[] = "\x1b[0m";
    246 			ab_write(&frame, t2, sizeof t2 - 1);
    247 		}
    248 
    249 		was_emoji = cells[i].len > 1 || IS_RIS(cp);
    250 		if(was_emoji) tui_move_cursor(x, y);
    251 	}
    252 
    253 	/* TODO: this only happens with ambi characters? */
    254 	if(was_emoji) {
    255 		const char t[] = "\x1b[48;5;232m \x1b[0m";
    256 		ab_write(&frame, t, sizeof t - 1);
    257 	}
    258 
    259 	ab_write(&frame, CLEARRIGHT, strlen(CLEARRIGHT));
    260 }
    261 
    262 
    263 void
    264 tui_draw_line(UI *ui, int x, int y, Cell *cells, int count) {
    265 	assert(x < ws.ws_col && y < ws.ws_row);
    266 
    267 	if(compat_mode) {
    268 		tui_draw_line_compat(ui, x, y, cells, count);
    269 		return;
    270 	}
    271 
    272 	char *txt;
    273 	int i;
    274 
    275 	tui_move_cursor(x, y);
    276 	for(i = 0; i < count; i++) {
    277 		x += cells[i].width;
    278 		txt = cell_get_text(cells + i, ui->pool.data);
    279 
    280 		/* TODO: temp code for testing, we'll se how to deal with this later */
    281 		if(txt[0] == '\t')
    282 			ab_printf(&frame, "%*s", cells[i].width, " ");
    283 		else
    284 			ab_write(&frame, txt, cells[i].len);
    285 	}
    286 	ab_write(&frame, CLEARRIGHT, strlen(CLEARRIGHT));
    287 }
    288 
    289 void
    290 tui_draw_symbol(int c, int r, Symbol sym) {
    291 	int symch;
    292 
    293 	switch(sym) {
    294 	case SYM_EMPTYLINE: symch = '~'; break;
    295 	default: symch = '?'; break;
    296 	}
    297 
    298 	tui_move_cursor(c, r);
    299 	ab_printf(&frame, "%c" CLEARRIGHT, symch);
    300 }
    301 
    302 void
    303 tui_init(void) {
    304 	struct termios ti;
    305 
    306 	setlocale(LC_CTYPE, "");
    307 	tcgetattr(0, &origti);
    308 	cfmakeraw(&ti);
    309 	ti.c_iflag |= ICRNL;
    310 	ti.c_cc[VMIN] = 1;
    311 	ti.c_cc[VTIME] = 0;
    312 	tcsetattr(0, TCSAFLUSH, &ti);
    313 	setbuf(stdout, NULL);
    314 	ioctl(0, TIOCGWINSZ, &ws);
    315 
    316 	/* check for compat mode */
    317 	/* TODO: auto-detect */
    318 	compat_mode = 1;
    319 }
    320 
    321 int
    322 tui_read_byte(void) {
    323 	char c;
    324 
    325 	if(read(STDIN_FILENO, &c, 1) == 1)
    326 		return c;
    327 	return -1;
    328 }
    329 
    330 Event
    331 tui_next_event(void) {
    332 	Event ev;
    333 	int c = tui_read_byte();
    334 
    335 	if(c == 0x1B) {
    336 		ev.type = EV_UKN;
    337 		return ev;
    338 	}
    339 
    340 	ev.type = EV_KEY;
    341 	ev.key = c;
    342 	return ev;
    343 }
    344 
    345 UI ui_tui = {
    346 	.name = "TUI",
    347 	.init = tui_init,
    348 	.exit = tui_exit,
    349 	.frame_start = tui_frame_start,
    350 	.frame_flush = tui_frame_flush,
    351 	.text_width = tui_text_width,
    352 	.text_len = tui_text_len,
    353 	.move_cursor = tui_move_cursor,
    354 	.draw_line = tui_draw_line,
    355 	.draw_symbol = tui_draw_symbol,
    356 	.get_window_size = tui_get_window_size,
    357 	.next_event = tui_next_event
    358 };