xdebug

next generation of mdebug (work in progress)
git clone http://frotz.net/git/xdebug.git
Log | Files | Refs | README

tui.c (8723B)


      1 // Copyright 2023, Brian Swetland <swetland@frotz.net>
      2 // Licensed under the Apache License, Version 2.0.
      3 
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <stdint.h>
      8 #include <pthread.h>
      9 
     10 #include <tui.h>
     11 #include <termbox.h>
     12 
     13 #define MAXWIDTH 128
     14 #define MAXCMD (MAXWIDTH - 1)
     15 
     16 typedef struct line LINE;
     17 typedef struct ux UX;
     18 
     19 struct line {
     20 	LINE* prev;
     21 	LINE* next;
     22 	uint16_t len;
     23 	uint8_t fg;
     24 	uint8_t bg;
     25 	uint8_t text[MAXWIDTH];
     26 };
     27 
     28 struct ux {
     29 	pthread_mutex_t lock;
     30 
     31 	int w;
     32 	int h;
     33 	int invalid;
     34 	int running;
     35 
     36 	char status_lhs[32];
     37 	char status_rhs[32];
     38 
     39 	LINE list;
     40 
     41 	// edit buffer and head of the circular history list
     42 	LINE history;
     43 
     44 	// points at active edit buffer
     45 	LINE *cmd;
     46 
     47 	// points at the line *before* the bottom-most list line
     48 	LINE *display;
     49 };
     50 
     51 
     52 static void tui_add_cmd(UX* ux, uint8_t* text, unsigned len) {
     53 	LINE* line = malloc(sizeof(LINE));
     54 	if (line == NULL) {
     55 		return;
     56 	}
     57 	line->len = len;
     58 	line->fg = line->bg = TB_DEFAULT;
     59 	memcpy(line->text, text, len);
     60 
     61 	line->prev = ux->history.prev;
     62 	line->next = &ux->history;
     63 
     64 	line->prev->next = line;
     65 	ux->history.prev = line;
     66 }
     67 
     68 static void paint(int x, int y, const char* str) {
     69 	while (*str) {
     70 		tb_change_cell(x++, y, *str++, TB_DEFAULT, TB_DEFAULT);
     71 	}
     72 }
     73 
     74 static void paint_titlebar(UX *ux) {
     75 }
     76 
     77 static void paint_infobar(UX *ux) {
     78 	int lhw = strlen(ux->status_lhs);
     79 	int rhw = strlen(ux->status_rhs);
     80 	int gap = ux->w - 2 - lhw - rhw;
     81 	if (gap < 0) gap = 0;
     82 
     83 	unsigned fg = TB_DEFAULT | TB_REVERSE;
     84 	unsigned bg = TB_DEFAULT;
     85 	int x = 0;
     86 	int y = ux->h - 2;
     87 
     88 	tb_change_cell(x++, y, '-', fg, bg);
     89 	char *s = ux->status_lhs;
     90 	while (x < ux->w && lhw-- > 0) {
     91 		tb_change_cell(x++, y, *s++, fg, bg);
     92 	}
     93 	while (x < ux->w && gap-- > 0) {
     94 		tb_change_cell(x++, y, '-', fg, bg);
     95 	}
     96 	s = ux->status_rhs;
     97 	while (x < ux->w && rhw-- > 0) {
     98 		tb_change_cell(x++, y, *s++, fg, bg);
     99 	}
    100 	tb_change_cell(x, y, '-', fg, bg);
    101 }
    102 
    103 static void paint_cmdline(UX *ux) {
    104 	uint8_t* ch = ux->cmd->text;
    105 	int len = ux->cmd->len;
    106 	int y = ux->h - 1;
    107 	int w = ux->w;
    108 	for (int x = 0; x < w; x++) {
    109 		if (x < len) {
    110 			tb_change_cell(x, y, *ch++, TB_DEFAULT, TB_DEFAULT);
    111 		} else {
    112 			tb_change_cell(x, y, ' ', TB_DEFAULT, TB_DEFAULT);
    113 		}
    114 	}
    115 	tb_set_cursor(len >= w ? w - 1 : len, y);
    116 }
    117 
    118 static void paint_log(UX *ux) {
    119 	LINE* list = &ux->list;
    120 	int y = ux->h - 3;
    121 	int w = ux->w;
    122 	uint8_t c;
    123 
    124 	for (LINE* line = ux->display->prev; (line != list) && (y >= 0); line = line->prev) {
    125 		for (int x = 0; x < w; x++) {
    126 			c = (x < line->len) ? line->text[x] : ' ';
    127 			tb_change_cell(x, y, c, line->fg, line->bg);
    128 		}
    129 		y--;
    130 	}
    131 }
    132 
    133 static int repaint(UX* ux) {
    134 	// clear entire display and adjust to any resize events
    135 	tb_clear();
    136 	ux->w = tb_width();
    137 	ux->h = tb_height();
    138 
    139 	if ((ux->w < 40) || (ux->h < 8)) {
    140 		paint(0, 0, "WINDOW TOO SMALL");
    141 		return 1;
    142 	}
    143 
    144 	paint_titlebar(ux);
    145 	paint_infobar(ux);
    146 	paint_cmdline(ux);
    147 	paint_log(ux);
    148 
    149 	if (ux->display != &ux->list) {
    150 		int x = ux->w - 8;
    151 		char *s = " SCROLL ";
    152 		while (*s != 0) {
    153 			tb_change_cell(x++, 0, *s++, TB_REVERSE | TB_DEFAULT, TB_DEFAULT);
    154 		}
    155 	}
    156 	return 0;
    157 }
    158 
    159 static void tui_scroll(UX* ux, int delta) {
    160 	LINE* list = &ux->list;
    161 	LINE* line = ux->display;
    162 
    163 	while (delta > 0) {
    164 		if (line->prev == list) goto done;
    165 		delta--;
    166 		line = line->prev;
    167 	}
    168 	while (delta < 0) {
    169 		if (line == list) goto done;
    170 		delta++;
    171 		line = line->next;
    172 	}
    173 done:
    174 	ux->display = line;
    175 	repaint(ux);
    176 	tb_present();
    177 }
    178 
    179 static int handle_event(UX* ux, struct tb_event* ev, char* line, unsigned* len) {
    180 	// always process full repaints due to resize or user request
    181 	if ((ev->type == TB_EVENT_RESIZE) ||
    182 	    (ev->key == TB_KEY_CTRL_L)) {
    183 		ux->invalid = repaint(ux);
    184 		return 0;
    185 	}
    186 
    187 	// ignore other input of the window is too small
    188 	if (ux->invalid) {
    189 		return 0;
    190 	}
    191 		
    192 	switch (ev->key) {
    193 	case 0: // printable character
    194 		// ignore fancy unicode characters
    195 		if (ev->ch > 255) {
    196 			break;
    197 		}
    198 		if (ux->cmd->len >= MAXCMD) {
    199 			break;
    200 		}
    201 		ux->cmd->text[ux->cmd->len++] = ev->ch;
    202 		paint_cmdline(ux);
    203 		break;
    204 	case TB_KEY_BACKSPACE:
    205 	case TB_KEY_BACKSPACE2:
    206 		if (ux->cmd->len > 0 ) {
    207 			ux->cmd->len--;
    208 			paint_cmdline(ux);
    209 		}
    210 		break;
    211 	case TB_KEY_ENTER: {
    212 		// pass commandline out to caller
    213 		*len = ux->cmd->len;
    214 		memcpy(line, ux->cmd->text, ux->cmd->len);
    215 		line[ux->cmd->len] = 0;
    216 
    217 		// add new command to history
    218 		tui_add_cmd(ux, ux->cmd->text, ux->cmd->len);
    219 
    220 		// reset to an empty edit buffer
    221 		ux->cmd = &ux->history;
    222 		ux->cmd->len = 0;
    223 
    224 		// update display
    225 		paint_cmdline(ux);
    226 		tb_present();
    227 
    228 		return 1;
    229 	}
    230 	case TB_KEY_ARROW_LEFT:
    231 	case TB_KEY_ARROW_RIGHT:
    232 	case TB_KEY_HOME:
    233 	case TB_KEY_END:
    234 	case TB_KEY_INSERT:
    235 	case TB_KEY_DELETE:
    236 		break;
    237 	case TB_KEY_ARROW_UP:
    238 		if (ux->cmd->prev != &ux->history) {
    239 			ux->cmd = ux->cmd->prev;
    240 			paint_cmdline(ux);
    241 			tb_present();
    242 		}
    243 		break;
    244 	case TB_KEY_ARROW_DOWN:
    245 		if (ux->cmd != &ux->history) {
    246 			ux->cmd = ux->cmd->next;
    247 			paint_cmdline(ux);
    248 			tb_present();
    249 		}
    250 		break;
    251 	case TB_KEY_PGUP:
    252 		tui_scroll(ux, ux->h - 3);
    253 		break;
    254 	case TB_KEY_PGDN:
    255 		tui_scroll(ux, -(ux->h - 3));
    256 		break;
    257 	case TB_KEY_ESC: {
    258 		*len = 5;
    259 		memcpy(line, "@ESC@", 6);
    260 		return 1;
    261 	}
    262 	default:
    263 #if 0 // debug unexpected keys
    264 		if (ux->len < (MAXWIDTH - 6)) {
    265 			char tmp[5];
    266 			sprintf(tmp, "%04x", ev->key);
    267 			ux->cmd[ux->len++] = '<';
    268 			ux->cmd[ux->len++] = tmp[0];
    269 			ux->cmd[ux->len++] = tmp[1];
    270 			ux->cmd[ux->len++] = tmp[2];
    271 			ux->cmd[ux->len++] = tmp[3];
    272 			ux->cmd[ux->len++] = '>';
    273 			paint_cmdline(ux);
    274 		}
    275 #endif
    276 		break;
    277 	}
    278 	return 0;
    279 }
    280 
    281 static UX ux = {
    282 	.lock = PTHREAD_MUTEX_INITIALIZER,
    283 	.list = {
    284 		.prev = &ux.list,
    285 		.next = &ux.list,
    286 	},
    287 	.history = {
    288 		.prev = &ux.history,
    289 		.next = &ux.history,
    290 	},
    291 	.cmd = &ux.history,
    292 	.display = &ux.list,
    293 	.running = 1,
    294 };
    295 
    296 void tui_init(void) {
    297 	if (tb_init()) {
    298 		fprintf(stderr, "termbox init failed\n");
    299 		return;
    300 	}
    301 	tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_SPACE);
    302 	repaint(&ux);
    303 }
    304 
    305 void tui_exit(void) {
    306 	pthread_mutex_lock(&ux.lock);
    307 	tb_shutdown();
    308 	ux.running = 0;
    309 	pthread_mutex_unlock(&ux.lock);
    310 }
    311 
    312 int tui_handle_event(void (*cb)(char*, unsigned)) {
    313 	struct tb_event ev;
    314 	char line[MAXWIDTH];
    315 	unsigned len;
    316 	int r;
    317 
    318 	pthread_mutex_lock(&ux.lock);
    319 	if (ux.running) {
    320 		tb_present();
    321 	}
    322 	pthread_mutex_unlock(&ux.lock);
    323 
    324 	if ((tb_poll_event(&ev) < 0) ||
    325 	    (ev.key == TB_KEY_CTRL_C)) {
    326 		return -1;
    327 	}
    328 
    329 	pthread_mutex_lock(&ux.lock);
    330 	if (ux.running) {
    331 		r = handle_event(&ux, &ev, line, &len);
    332 	} else {
    333 		r = -1;
    334 	}
    335 	pthread_mutex_unlock(&ux.lock);
    336 
    337 	if (r == 1) {
    338 		cb(line, len);
    339 		return 0;
    340 	} else {
    341 		return r;
    342 	}
    343 }
    344 
    345 void tui_status_rhs(const char* status) {
    346 	pthread_mutex_lock(&ux.lock);
    347 	if (ux.running) {
    348 		strncpy(ux.status_rhs, status, sizeof(ux.status_rhs) - 1);
    349 		paint_infobar(&ux);
    350 		tb_present();
    351 	}
    352 	pthread_mutex_unlock(&ux.lock);
    353 }
    354 
    355 void tui_status_lhs(const char* status) {
    356 	pthread_mutex_lock(&ux.lock);
    357 	if (ux.running) {
    358 		strncpy(ux.status_lhs, status, sizeof(ux.status_lhs) - 1);
    359 		paint_infobar(&ux);
    360 		tb_present();
    361 	}
    362 	pthread_mutex_unlock(&ux.lock);
    363 }
    364 
    365 static void tui_logline(uint8_t* text, unsigned len) {
    366 	LINE* line = malloc(sizeof(LINE));
    367 	if (line == NULL) return;
    368 	memcpy(line->text, text, len);
    369 	line->len = len;
    370 	line->fg = TB_DEFAULT;
    371 	line->bg = TB_DEFAULT;
    372 
    373 	pthread_mutex_lock(&ux.lock);
    374 	if (ux.running) {
    375 		// add line to the log
    376 		line->prev = ux.list.prev;
    377 		line->next = &ux.list;
    378 		line->prev->next = line;
    379 		ux.list.prev = line;
    380 
    381 		// refresh the log
    382 		paint_log(&ux);
    383 		tb_present();
    384 	}
    385 	pthread_mutex_unlock(&ux.lock);
    386 }
    387 
    388 struct tui_ch {
    389 	unsigned len;
    390 	uint8_t buffer[MAXWIDTH];
    391 };
    392 
    393 int tui_ch_create(tui_ch_t** out, unsigned flags) {
    394 	tui_ch_t* ch = calloc(1, sizeof(tui_ch_t));
    395 	if (ch == NULL) {
    396 		return -1;
    397 	} else {
    398 		*out = ch;
    399 		return 0;
    400 	}
    401 }
    402 
    403 void tui_ch_destroy(tui_ch_t* ch) {
    404 	free(ch);
    405 }
    406 
    407 void tui_ch_vprintf(tui_ch_t* ch, const char* fmt, va_list ap) {
    408 	char tmp[1024];
    409 	int n = vsnprintf(tmp, sizeof(tmp), fmt, ap);
    410 	char *x = tmp;
    411 	while (n > 0) {
    412 		uint8_t c = *x++;
    413 		n--;
    414 		if ((c == '\n') && (ch->len > 0)) {
    415 			tui_logline(ch->buffer, ch->len);
    416 			ch->len = 0;
    417 			continue;
    418 		}
    419 		if ((c < ' ') || (c > 0x7e)) {
    420 			continue;
    421 		}
    422 		if (ch->len < MAXWIDTH) {
    423 			ch->buffer[ch->len++] = c;
    424 		}
    425 	}
    426 }
    427 
    428 void tui_ch_printf(tui_ch_t* ch, const char* fmt, ...) {
    429 	va_list ap;
    430 	va_start(ap, fmt);
    431 	tui_ch_vprintf(ch, fmt, ap);
    432 	va_end(ap);
    433 }
    434 
    435 void tui_printf(const char* fmt, ...) {
    436 	va_list ap;
    437 	tui_ch_t ch = { 0 };
    438 	va_start(ap, fmt);
    439 	tui_ch_vprintf(&ch, fmt, ap);
    440 	va_end(ap);
    441 	if (ch.len > 0) {
    442 		tui_logline(ch.buffer, ch.len);
    443 	}
    444 }
    445 
    446 void tui_vprintf(const char* fmt, va_list ap) {
    447 	tui_ch_t ch = { 0 };
    448 	tui_ch_vprintf(&ch, fmt, ap);
    449 	if (ch.len > 0) {
    450 		tui_logline(ch.buffer, ch.len);
    451 	}
    452 }
    453