edo

Experimental text editor.
Log | Files | Refs | LICENSE

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 }