edo

Experimental text editor.
Log | Files | Refs | LICENSE

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 }