edo.c (10757B)
1 /* osaentuhaosenuhaoesnuthaoesnutha oesnthaoesuntha snethu asoenhu saoenhtuaoesn uthaoesunthaoesuntaoeh usaoneth asoenth aoesnth aoesnthaoseuthaoseuthaoesunthaoeusnh asoentuh */ 2 3 /* 👩❤️💋👩 */ 4 5 #include <assert.h> 6 #include <stdarg.h> 7 #include <stdint.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <unistd.h> 12 13 #include "ui.h" 14 15 typedef struct { 16 char *buf; 17 size_t cap; 18 int len; 19 } Line; 20 21 typedef struct { 22 Line **lines; 23 char *file_name; 24 int file_size; 25 int lines_cap; 26 size_t lines_tot; 27 //int ref_count; 28 } Buffer; 29 30 typedef struct { 31 Buffer *buf; 32 int line_num; 33 int col_num; 34 int row_offset; 35 int col_offset; 36 int screen_rows; 37 int screen_cols; 38 //int pref_col; 39 } View; 40 41 /* variables */ 42 int running = 1; 43 View *vcur; 44 UI *ui; 45 46 /* function declarations */ 47 void die(const char *fmt, ...); 48 void *ecalloc(size_t nmemb, size_t size); 49 void *erealloc(void *p, size_t size); 50 void insert_char(char *dst, char c, int len); 51 void delete_char(char *dst, int count, int len); 52 void line_insert_char(Line *line, size_t index, char c); 53 void line_delete_char(Line *line, int index, int count); 54 Line *line_create(char *content); 55 void line_destroy(Line *l); 56 void buffer_insert_line(Buffer *b, int index, Line *line); 57 void buffer_delete_line(Buffer *b, int index, int count); 58 int buffer_load_file(Buffer *b); 59 Line *buffer_get_line(Buffer *b, int index); 60 Buffer *buffer_create(char *fn); 61 void buffer_destroy(Buffer *b); 62 View *view_create(Buffer *b); 63 void view_destroy(View *v); 64 void view_cursor_hfix(View *v); 65 void view_cursor_left(View *v); 66 void view_cursor_right(View *v); 67 void view_cursor_up(View *v); 68 void view_cursor_down(View *v); 69 int view_idx2col(View *v, Line *line, int idx); 70 void view_scroll_fix(View *v); 71 int measure_span(char *s, int len, int start_x); 72 int render(Cell *cells, char *buf, int buflen, int xoff, int cols); 73 char *cell_get_text(Cell *cell, char *pool_base); 74 void view_place_cursor(View *v); 75 void draw_view(View *v); 76 void textpool_ensure_cap(TextPool *pool, int len); 77 int textpool_insert(TextPool *pool, char *s, int len); 78 79 /* function implementations */ 80 void 81 die(const char *fmt, ...) { 82 va_list ap; 83 va_start(ap, fmt); 84 vfprintf(stderr, fmt, ap); 85 va_end(ap); 86 87 if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 88 fputc(' ', stderr); 89 perror(NULL); 90 } else { 91 fputc('\n', stderr); 92 } 93 exit(0); 94 } 95 96 void * 97 ecalloc(size_t nmemb, size_t size) { 98 void *p; 99 100 if(!(p = calloc(nmemb, size))) 101 die("Cannot allocate memory."); 102 return p; 103 } 104 105 void * 106 erealloc(void *p, size_t size) { 107 if(!(p = realloc(p, size))) 108 die("Cannot reallocate memory."); 109 return p; 110 } 111 112 void 113 insert_char(char *dst, char c, int len) { 114 memmove(dst + 1, dst, len); 115 memcpy(dst, &c, 1); 116 } 117 118 void 119 delete_char(char *dst, int count, int len) { 120 memmove(dst, dst + count, len); 121 } 122 123 void 124 line_insert_char(Line *line, size_t index, char c) { 125 size_t newlen = line->len + 1; 126 127 assert(index >= 0 && index <= line->len); 128 if(newlen > line->cap) { 129 line->cap = line->cap ? line->cap * 2 : 16; 130 line->buf = erealloc(line->buf, line->cap); 131 } 132 insert_char(&line->buf[index], c, line->len - index); 133 line->len = newlen; 134 } 135 136 void 137 line_delete_char(Line *line, int index, int count) { 138 delete_char(&line->buf[index], count, line->len - index); 139 line->len -= count; 140 } 141 142 Line * 143 line_create(char *content) { 144 Line *l = ecalloc(1, sizeof(Line)); 145 146 if(content) { 147 l->buf = strdup(content); 148 l->len = strlen(l->buf); 149 l->cap = l->len + 1; 150 } 151 return l; 152 } 153 154 void 155 line_destroy(Line *l) { 156 if(l->buf) 157 free(l->buf); 158 free(l); 159 } 160 161 void 162 buffer_insert_line(Buffer *b, int index, Line *line) { 163 size_t nb = (b->lines_tot - index) * sizeof(Line *); 164 165 assert(index >= 0 && index <= b->lines_tot); 166 if(b->lines_tot >= b->lines_cap) { 167 b->lines_cap = b->lines_cap ? b->lines_cap * 2 : 64; 168 b->lines = erealloc(b->lines, sizeof(Line *) * b->lines_cap); 169 } 170 if(nb) 171 memmove(b->lines + index + 1, b->lines + index, nb); 172 b->lines[index] = line; 173 ++b->lines_tot; 174 } 175 176 void 177 buffer_delete_line(Buffer *b, int index, int count) { 178 int lines_remaining, nb; 179 int last = index + count; 180 int i; 181 182 assert(count && index < b->lines_tot && last <= b->lines_tot); 183 for(i = 0; i < count; i++) 184 line_destroy(b->lines[index + i]); 185 lines_remaining = b->lines_tot - last; 186 if(lines_remaining) { 187 nb = lines_remaining * sizeof(Line *); 188 memmove(b->lines + index, b->lines + last, nb); 189 } 190 b->lines_tot -= count; 191 } 192 193 int 194 buffer_load_file(Buffer *b) { 195 Line *l; 196 FILE *fp; 197 char *buf = NULL; 198 size_t cap; 199 int len; 200 201 if(!(fp = fopen(b->file_name, "r"))) 202 return -1; 203 while((len = getline(&buf, &cap, fp)) != -1) { 204 buf[len - 1] = 0; 205 b->file_size += len; 206 l = line_create(buf); 207 buffer_insert_line(b, b->lines_tot, l); 208 } 209 fclose(fp); 210 return 0; 211 } 212 213 Line * 214 buffer_get_line(Buffer *b, int index) { 215 if(!b->lines) 216 return NULL; 217 return b->lines[index]; 218 } 219 220 Buffer * 221 buffer_create(char *fn) { 222 Buffer *b = ecalloc(1, sizeof(Buffer)); 223 224 b->lines = NULL; 225 b->lines_tot = 0; 226 b->file_size = 0; 227 if(fn) { 228 if(!(b->file_name = strdup(fn))) 229 return NULL; 230 if(buffer_load_file(b)) 231 printf("%s: cannot load file\n", fn); 232 } 233 234 /* ensure we have at least a line */ 235 if(!b->lines_tot) buffer_insert_line(b, 0, line_create(NULL)); 236 237 return b; 238 } 239 240 void 241 buffer_destroy(Buffer *b) { 242 int i; 243 244 if(b->lines) { 245 for(i = 0; i < b->lines_tot; i++) 246 line_destroy(b->lines[i]); 247 free(b->lines); 248 } 249 if(b->file_name) 250 free(b->file_name); 251 free(b); 252 } 253 254 View * 255 view_create(Buffer *b) { 256 View *v = ecalloc(1, sizeof(View)); 257 258 v->line_num = 0; 259 v->col_num = 0; 260 v->row_offset = 0; 261 v->col_offset = 0; 262 v->buf = b; 263 264 ui->get_window_size(&v->screen_rows, &v->screen_cols); 265 return v; 266 } 267 268 void 269 view_destroy(View *v) { 270 free(v); 271 } 272 273 /* actual invariant for the cursor */ 274 void 275 view_cursor_hfix(View *v) { 276 assert(v->line_num >= 0 && v->line_num< v->buf->lines_tot); 277 278 Line *l = v->buf->lines[v->line_num]; 279 280 /* TODO: < 0 needed? */ 281 if(v->col_num < 0) 282 v->col_num = 0; 283 else if(v->col_num > l->len) 284 v->col_num = l->len; 285 } 286 287 void 288 view_cursor_left(View *v) { 289 if(v->col_num) 290 --v->col_num; 291 } 292 293 void 294 view_cursor_right(View *v) { 295 Line *l = v->buf->lines[v->line_num]; 296 297 if(v->col_num < l->len) 298 ++v->col_num; 299 } 300 301 void 302 view_cursor_up(View *v) { 303 if(v->line_num) { 304 --v->line_num; 305 view_cursor_hfix(v); 306 } 307 } 308 309 void 310 view_cursor_down(View *v) { 311 if(v->line_num < v->buf->lines_tot - 1) { 312 ++v->line_num; 313 view_cursor_hfix(v); 314 } 315 } 316 317 int 318 view_idx2col(View *v, Line *line, int idx) { 319 (void)v; 320 if(!line->len) return 0; 321 return measure_span(line->buf, idx, 0); 322 } 323 324 void 325 view_scroll_fix(View *v) { 326 /* vertical */ 327 if (v->line_num < v->row_offset) 328 v->row_offset = v->line_num; 329 if (v->line_num >= v->row_offset + v->screen_rows) 330 v->row_offset = v->line_num - v->screen_rows + 1; 331 332 /* horizontal */ 333 Line *l = buffer_get_line(v->buf, v->line_num); 334 int vx = view_idx2col(v, l, v->col_num); 335 336 if(vx < v->col_offset) 337 v->col_offset = vx; 338 if(vx >= v->col_offset + v->screen_cols) 339 v->col_offset = vx - v->screen_cols + 1; 340 } 341 342 int 343 measure_span(char *s, int slen, int start_x) { 344 int x = start_x; 345 int len, i; 346 347 for(i = 0; i < slen; i += len) { 348 len = 1; /* TODO: decode UTF8 */ 349 x += ui->text_width(s + i, len, x); 350 } 351 return x - start_x; 352 } 353 354 int 355 render(Cell *cells, char *buf, int buflen, int xoff, int cols) { 356 int nc = 0, vx = 0, i = 0; 357 int w, len, x; 358 359 while(i < buflen) { 360 len = 1; /* TODO: decode UTF8 */ 361 w = ui->text_width(buf + i, len, vx); 362 363 if(vx + w <= xoff) goto next; /* horizontal scroll */ 364 365 x = vx - xoff; 366 if(x >= cols) break; /* screen has been filled */ 367 if(x + w > cols) break; /* truncated character (TODO: draw a symbol?) */ 368 369 if(len > CELL_POOL_THRESHOLD) { 370 /* TODO: manage pool */ 371 die("Arena pool to be implemented.\n"); 372 } 373 else { 374 memcpy(cells[nc].data.text, buf + i, len); 375 } 376 377 cells[nc].len = len; 378 cells[nc].width = w; 379 if(vx < xoff) cells[nc].width -= xoff - vx; /* partial rendering */ 380 381 ++nc; 382 next: 383 vx += w; 384 i += len; 385 } 386 387 return nc; 388 } 389 390 /* TODO: is this the cleaner way to do it? */ 391 void 392 view_place_cursor(View *v) { 393 Line *l; 394 int x, y; 395 396 x = v->col_offset; 397 l = buffer_get_line(v->buf, v->line_num); 398 if(l) { 399 x = view_idx2col(v, l, v->col_num); 400 x -= v->col_offset; 401 y = v->line_num - v->row_offset; 402 } else { 403 x = y = 0; 404 } 405 ui->move_cursor(x, y); 406 } 407 408 void 409 draw_view(View *v) { 410 Line *l; 411 int row, y, nc; 412 413 ui->frame_start(); 414 view_scroll_fix(v); 415 416 Cell *cells = ecalloc(1, sizeof(Cell) * v->screen_cols); 417 418 for(y = 0; y < v->screen_rows; y++) { 419 row = v->row_offset + y; 420 l = buffer_get_line(v->buf, row); 421 if(!l) { 422 ui->draw_symbol(0, y, SYM_EMPTYLINE); 423 continue; 424 } 425 nc = render(cells, l->buf, l->len, v->col_offset, v->screen_cols); 426 ui->draw_line(ui, 0, y, cells, nc); 427 } 428 429 free(cells); 430 431 view_place_cursor(v); 432 ui->frame_flush(); 433 } 434 435 void 436 textpool_ensure_cap(TextPool *pool, int len) { 437 size_t newlen = pool->len + len; 438 439 if(newlen <= pool->cap) return; 440 pool->cap *= 2; 441 if(pool->cap < newlen) pool->cap = newlen + 1024; 442 pool->data = erealloc(pool->data, pool->cap); 443 } 444 445 int 446 textpool_insert(TextPool *pool, char *s, int len) { 447 int olen = len; 448 449 textpool_ensure_cap(pool, len); 450 memcpy(pool->data + pool->len, s, len); 451 pool->len += len; 452 return olen; 453 } 454 455 char * 456 cell_get_text(Cell *c, char *pool_base) { 457 if(c->len > CELL_POOL_THRESHOLD) 458 return pool_base + c->data.pool_idx; 459 return c->data.text; 460 } 461 462 void 463 run(void) { 464 Event ev; 465 466 while(running) { 467 ev = ui->next_event(); 468 switch(ev.type) { 469 case EV_KEY: 470 if(ev.key == 'k') view_cursor_up(vcur); 471 else if(ev.key == 'j') view_cursor_down(vcur); 472 else if(ev.key == 'h') view_cursor_left(vcur); 473 else if(ev.key == 'l') view_cursor_right(vcur); 474 else if(ev.key == 'q') running = 0; 475 else if(ev.key == 'K') { 476 Line *l = line_create(NULL); 477 buffer_insert_line(vcur->buf, vcur->line_num, l); 478 479 /* we should call view_cursor_hfix() here since we're moving into 480 * another line (the new one). Since only col_num may be wrong we 481 * can avoid a function call by setting it manually. */ 482 vcur->col_num = 0; 483 } 484 else if(ev.key == 'J' || ev.key == '\n') { 485 Line *l = line_create(NULL); 486 buffer_insert_line(vcur->buf, vcur->line_num + 1, l); 487 view_cursor_down(vcur); 488 } else { 489 line_insert_char(vcur->buf->lines[vcur->line_num], vcur->col_num, ev.key); 490 view_cursor_right(vcur); 491 } 492 break; 493 case EV_UKN: 494 break; 495 } 496 draw_view(vcur); 497 } 498 } 499 500 int 501 main(int argc, char *argv[]) { 502 char *fn = NULL; 503 504 if(argc > 2) die("Usage: %s [file]", argv[0]); 505 if(argc == 2) fn = argv[1]; 506 507 ui = &ui_tui; /* the one and only... */ 508 atexit(ui->exit); 509 ui->init(); 510 Buffer *b = buffer_create(fn); 511 View *v = view_create(b); 512 vcur = v; /* current view */ 513 draw_view(v); 514 run(); 515 buffer_destroy(v->buf); 516 view_destroy(v); 517 ui->exit(); 518 free(ui->pool.data); 519 return 0; 520 }