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