tui.c (4713B)
1 #include "ui.h" 2 3 #include <assert.h> 4 #include <stdarg.h> 5 #include <stdio.h> 6 #include <locale.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <sys/ioctl.h> 10 #include <termios.h> 11 #include <unistd.h> 12 13 #define CURPOS "\33[%d;%dH" 14 //#define CLEARLEFT "\33[1K" 15 #define CLEARRIGHT "\33[0K" 16 #define CURHIDE "\33[?25l" 17 #define CURSHOW "\33[?25h" 18 19 typedef struct { 20 char *buf; 21 int len; 22 int cap; 23 } Abuf; 24 25 struct termios origti; 26 struct winsize ws; 27 Abuf frame; 28 29 extern void *ecalloc(size_t nmemb, size_t size); 30 extern void die(const char *fmt, ...); 31 32 /* function declarations */ 33 void ab_free(Abuf *ab); 34 void ab_ensure_cap(Abuf *ab, size_t addlen); 35 void ab_write(Abuf *ab, const char *s, size_t len); 36 int ab_printf(Abuf *ab, const char *fmt, ...); 37 void ab_flush(Abuf *ab); 38 void tui_frame_start(void); 39 void tui_frame_flush(void); 40 int tui_text_width(char *s, int len); 41 void tui_get_window_size(int *rows, int *cols); 42 void tui_exit(void); 43 void tui_move_cursor(int x, int y); 44 void tui_draw_line(int c, int r, char *txt, int len); 45 void tui_draw_symbol(int r, int c, Symbol sym); 46 void tui_init(void); 47 48 /* function implementations */ 49 void 50 ab_free(Abuf *ab) { 51 if(!ab->buf) 52 return; 53 free(ab->buf); 54 ab->buf = NULL; 55 ab->len = 0; 56 ab->cap = 0; 57 } 58 59 void 60 ab_ensure_cap(Abuf *ab, size_t addlen) { 61 size_t newlen = ab->len + addlen; 62 63 if(newlen <= ab->cap) 64 return; 65 while(newlen > ab->cap) 66 ab->cap = ab->cap ? ab->cap * 2 : 8; 67 /* TODO: panic and save the dump the data before exit */ 68 if(!(ab->buf = realloc(ab->buf, ab->cap))) 69 die("Cannot reallocate memory."); 70 } 71 72 void 73 ab_write(Abuf *ab, const char *s, size_t len) { 74 ab_ensure_cap(&frame, len); 75 memcpy(ab->buf + ab->len, s, len); 76 ab->len += len; 77 } 78 79 int 80 ab_printf(Abuf *ab, const char *fmt, ...) { 81 va_list ap; 82 int len; 83 84 va_start(ap, fmt); 85 len = vsnprintf(NULL, 0, fmt, ap); 86 assert(len >= 0); 87 va_end(ap); 88 89 ab_ensure_cap(ab, len + 1); 90 91 va_start(ap, fmt); 92 vsnprintf(ab->buf + ab->len, len + 1, fmt, ap); 93 va_end(ap); 94 95 ab->len += len; 96 return len; 97 } 98 99 void 100 ab_flush(Abuf *ab) { 101 write(STDOUT_FILENO, ab->buf, ab->len); 102 ab_free(ab); 103 } 104 105 void 106 tui_frame_start(void) { 107 ab_printf(&frame, CURHIDE); 108 } 109 110 void 111 tui_frame_flush(void) { 112 ab_printf(&frame, CURSHOW); 113 ab_flush(&frame); 114 } 115 116 int 117 tui_text_width(char *s, int len) { 118 int tabstop = 8; 119 int w = 0, i; 120 121 for(i = 0; i < len; i++) { 122 if(s[i] == '\t') 123 w += tabstop - w % tabstop; 124 else 125 ++w; 126 } 127 return w; 128 } 129 130 int 131 tui_text_index_at(char *str, int target_x) { 132 int tabstop = 8; 133 int x = 0, i = 0, w; 134 135 while(str[i]) { 136 w = (str[i] == '\t') ? tabstop : 1; 137 if (x + w > target_x) 138 return i; 139 x += w; 140 i++; 141 } 142 return i; 143 } 144 145 void 146 tui_get_window_size(int *rows, int *cols) { 147 *rows = ws.ws_row; 148 *cols = ws.ws_col; 149 } 150 151 void 152 tui_exit(void) { 153 tcsetattr(0, TCSANOW, &origti); 154 } 155 156 void 157 tui_move_cursor(int c, int r) { 158 int x = c + 1, y = r + 1; /* TERM coords are 1-based */ 159 ab_printf(&frame, CURPOS, y, x); 160 } 161 162 void 163 tui_draw_line(int c, int r, char *txt, int len) { 164 if(c >= ws.ws_col || r >= ws.ws_row) return; 165 166 int x = c; 167 int tabstop = 8; 168 int i = 0; 169 int cw, clen; 170 171 tui_move_cursor(x < 0 ? 0 : x, r); 172 while(i < len) { 173 cw = (txt[i] == '\t' ? tabstop : 1); 174 175 if(x + cw <= 0) 176 continue; 177 178 if(txt[i] == '\t') { 179 int spaces = tabstop; 180 clen = 1; 181 182 if(x < 0) spaces += x; 183 if(x + cw > ws.ws_col) spaces -= (x + cw - ws.ws_col); 184 while(spaces--) ab_write(&frame, " ", 1); 185 } else { 186 if(x + cw > ws.ws_col) break; 187 clen = 1; 188 ab_write(&frame, txt + i, clen); 189 } 190 191 x += cw; 192 i += clen; 193 } 194 ab_write(&frame, CLEARRIGHT, strlen(CLEARRIGHT)); 195 } 196 197 void 198 tui_draw_symbol(int c, int r, Symbol sym) { 199 int symch; 200 201 switch(sym) { 202 case SYM_EMPTYLINE: symch = '~'; break; 203 default: symch = '?'; break; 204 } 205 206 tui_move_cursor(c, r); 207 ab_printf(&frame, "%c" CLEARRIGHT, symch); 208 } 209 210 void 211 tui_init(void) { 212 struct termios ti; 213 214 setlocale(LC_CTYPE, ""); 215 tcgetattr(0, &origti); 216 cfmakeraw(&ti); 217 ti.c_iflag |= ICRNL; 218 ti.c_cc[VMIN] = 1; 219 ti.c_cc[VTIME] = 0; 220 tcsetattr(0, TCSAFLUSH, &ti); 221 setbuf(stdout, NULL); 222 ioctl(0, TIOCGWINSZ, &ws); 223 } 224 225 int 226 tui_read_byte(void) { 227 char c; 228 229 if (read(STDIN_FILENO, &c, 1) == 1) 230 return c; 231 return -1; 232 } 233 234 Event 235 tui_next_event(void) { 236 Event ev; 237 int c = tui_read_byte(); 238 239 if(c == 0x1B) { 240 ev.type = EV_UKN; 241 return ev; 242 } 243 244 ev.type = EV_KEY; 245 ev.key = c; 246 return ev; 247 } 248 249 UI ui_tui = { 250 .name = "TUI", 251 .init = tui_init, 252 .exit = tui_exit, 253 .frame_start = tui_frame_start, 254 .frame_flush = tui_frame_flush, 255 .text_width = tui_text_width, 256 .text_index_at = tui_text_index_at, 257 .move_cursor = tui_move_cursor, 258 .draw_line = tui_draw_line, 259 .draw_symbol = tui_draw_symbol, 260 .get_window_size = tui_get_window_size, 261 .next_event = tui_next_event 262 };