ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/ledit.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
Log | Files | Refs | README | LICENSE

buffer.c (33596B)


      1 /* FIXME: maybe use separate unicode grapheme library so all functions
      2    that need grapheme boundaries can be included here instead of in the views */
      3 
      4 #include <stdio.h>
      5 #include <errno.h>
      6 #include <string.h>
      7 #include <limits.h>
      8 #include <stdlib.h>
      9 #include <unistd.h>
     10 
     11 #include <X11/Xlib.h>
     12 #include <X11/Xutil.h>
     13 #include <X11/Xatom.h>
     14 #include <pango/pangoxft.h>
     15 #include <X11/extensions/Xdbe.h>
     16 
     17 #include "util.h"
     18 #include "undo.h"
     19 #include "cache.h"
     20 #include "memory.h"
     21 #include "common.h"
     22 #include "txtbuf.h"
     23 #include "window.h"
     24 #include "buffer.h"
     25 #include "assert.h"
     26 #include "pango-compat.h"
     27 
     28 /*
     29  * Important notes:
     30  * - Lines must be null-terminated for some things to work (e.g. text search),
     31  *   but the '\0' is not included in 'len'.
     32  * - When the line is not normalized, the '\0' is not always included! This
     33  *   should maybe be changed for consistency, but for now, the resizing just
     34  *   always makes sure to add one so there's enough space when the text is
     35  *   normalized. This is a bit ugly, but oh well.
     36 */
     37 
     38 /*
     39  * Note: "line gap buffer" refers to the gap buffer containing
     40  * all lines, not to the gap buffer of the text in one line.
     41  */
     42 
     43 /*
     44  * Move the gap of the line gap buffer to 'index'.
     45  */
     46 static void move_line_gap(ledit_buffer *buffer, size_t index);
     47 
     48 /*
     49  * Resize the line so it can hold at least 'min_size' bytes and move the gap
     50  * to byte position 'index'.
     51  */
     52 static void resize_and_move_text_gap(ledit_line *line, size_t min_size, size_t index);
     53 
     54 /*
     55  * Resize the line gap buffer so it can hold at least 'min_size' lines and
     56  * move the gap to line at position 'index'.
     57  */
     58 static void resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index);
     59 
     60 /*
     61  * Similar to strchr, but the length of the text is given separately
     62  */
     63 static char *strchr_len(char *text, char c, size_t len);
     64 
     65 /*
     66  * Initialize a line with default values for its struct members.
     67  */
     68 static void init_line(ledit_buffer *buffer, ledit_line *line);
     69 
     70 /*
     71  * Insert 'src_len' bytes from 'src_line' starting at byte position 'src_index'
     72  * into line 'dst_line' at byte position 'dst_index'.
     73  * 'dst_line' must not be the same as 'src_line'.
     74  * If 'text_ret' is not NULL, the copied text is additionally copied into 'text_ret'.
     75  * This function does not update the views or normalize the lines, so it should
     76  * only be used for efficiency purposes when performing multiple operations.
     77  */
     78 static void buffer_insert_text_from_line_base(
     79     ledit_buffer *buffer,
     80     size_t dst_line, size_t dst_index,
     81     size_t src_line, size_t src_index, size_t src_len,
     82     txtbuf *text_ret
     83 );
     84 
     85 /*
     86  * Insert text 'text' with length 'len' at line 'line_index' and byte position 'index'.
     87  * The text must not contain newlines.
     88  * This function does not update the views or normalize the lines, so it should
     89  * only be used for efficiency purposes when performing multiple operations.
     90  */
     91 static void buffer_insert_text_base(
     92     ledit_buffer *buffer,
     93     size_t line_index, size_t index,
     94     char *text, size_t len
     95 );
     96 
     97 /* Insert text 'text' with length 'len' at line 'line_index' and byte position 'index.
     98  * The text may contain newlines.
     99  * If end_line_ret is not NULL, the line index at the end of the insertion is
    100  * written into it.
    101  * If end_byte_ret is not NULL, the byte position at the end of the insertion is
    102  * written into it.
    103  * This function does not update the views or normalize the lines, so it should
    104  * only be used for efficiency purposes when performing multiple operations.
    105  */
    106 static void buffer_insert_text_with_newlines_base(
    107     ledit_buffer *buffer,
    108     size_t line_index, size_t index,
    109     char *text, size_t len,
    110     size_t *end_line_ret, size_t *end_char_ret
    111 );
    112 
    113 /*
    114  * Same as buffer_insert_text_with_newlines_base, but the views are updated afterwards.
    115  */
    116 static void buffer_insert_text_with_newlines(
    117     ledit_buffer *buffer,
    118     size_t line_index, size_t index,
    119     char *text, size_t len,
    120     size_t *end_line_ret, size_t *end_char_ret
    121 );
    122 
    123 /*
    124  * Append line after line at 'line_index'.
    125  * if 'break_text' is not 0, the text on line 'line_index' starting at
    126  * byte index 'text_index' is moved to the newly appended line.
    127  * The views are notified that a line has been appended, but not told to update
    128  * their line heights and offsets.
    129  */
    130 static void buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text);
    131 
    132 /*
    133  * Delete lines between 'index1' and 'index2' (inclusive).
    134  * The views are notified of the deletion but not told to
    135  * update their line heights and offsets.
    136  */
    137 static void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2);
    138 
    139 /*
    140  * Delete the section of line 'line' starting at byte 'start' with length 'length'
    141  * and notify the views.
    142  * Note that this does not tell the views to recalculate their line heights and offsets.
    143  */
    144 static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length);
    145 
    146 /*
    147  * Copy text range into given buffer.
    148  * - dst is null-terminated
    149  * - dst must be large enough to contain the text and NUL (only use this together with buffer_textlen)
    150  * - the range must be sorted already
    151  */
    152 static void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2);
    153 
    154 static void marklist_destroy(ledit_buffer_marklist *marklist);
    155 static ledit_buffer_marklist *marklist_create(void);
    156 
    157 static void
    158 marklist_destroy(ledit_buffer_marklist *marklist) {
    159 	for (size_t i = 0; i < marklist->len; i++) {
    160 		free(marklist->marks[i].text);
    161 	}
    162 	free(marklist->marks);
    163 	free(marklist);
    164 }
    165 
    166 void
    167 buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte) {
    168 	ledit_buffer_marklist *marklist = buffer->marklist;
    169 	for (size_t i = 0; i < marklist->len; i++) {
    170 		if (!strncmp(mark, marklist->marks[i].text, len)) {
    171 			marklist->marks[i].line = line;
    172 			marklist->marks[i].byte = byte;
    173 			return;
    174 		}
    175 	}
    176 	if (marklist->len == marklist->alloc) {
    177 		size_t new_alloc = ideal_array_size(marklist->alloc, add_sz(marklist->len, 1));
    178 		marklist->marks = ledit_reallocarray(
    179 		    marklist->marks, new_alloc, sizeof(ledit_buffer_mark)
    180 		);
    181 		marklist->alloc = new_alloc;
    182 	}
    183 	ledit_buffer_mark *m = &marklist->marks[marklist->len];
    184 	m->text = ledit_strndup(mark, len);
    185 	m->line = line;
    186 	m->byte = byte;
    187 	marklist->len++;
    188 }
    189 
    190 /* FIXME: check that byte is actually at grapheme boundary
    191    (difficult because that can't currently be done by the buffer) */
    192 int
    193 buffer_get_mark(ledit_buffer *buffer, char *mark, size_t len, size_t *line_ret, size_t *byte_ret) {
    194 	ledit_buffer_marklist *marklist = buffer->marklist;
    195 	int ret = 1;
    196 	for (size_t i = 0; i < marklist->len; i++) {
    197 		if (!strncmp(mark, marklist->marks[i].text, len)) {
    198 			ledit_line *ll;
    199 			ledit_buffer_mark *m = &marklist->marks[i];
    200 			if (m->line >= buffer->lines_num) {
    201 				*line_ret = buffer->lines_num - 1;
    202 				ll = buffer_get_line(buffer, *line_ret);
    203 				*byte_ret = ll->len;
    204 			} else {
    205 				*line_ret = m->line;
    206 				ll = buffer_get_line(buffer, m->line);
    207 				if (m->byte >= ll->len)
    208 					*byte_ret = ll->len;
    209 				else
    210 					*byte_ret = m->byte;
    211 			}
    212 			ret = 0;
    213 			break;
    214 		}
    215 	}
    216 	return ret;
    217 }
    218 
    219 static ledit_buffer_marklist *
    220 marklist_create(void) {
    221 	ledit_buffer_marklist *marklist = ledit_malloc(sizeof(ledit_buffer_marklist));
    222 	marklist->len = marklist->alloc = 0;
    223 	marklist->marks = NULL;
    224 	return marklist;
    225 }
    226 
    227 ledit_buffer *
    228 buffer_create(ledit_common *common, ledit_clipboard *clipboard) {
    229 	ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer));
    230 	buffer->common = common;
    231 	buffer->clipboard = clipboard;
    232 	buffer->undo = undo_stack_create();
    233 	buffer->marklist = marklist_create();
    234 
    235 	buffer->filename = NULL;
    236 	memset(&buffer->file_mtime, 0, sizeof(buffer->file_mtime));
    237 	buffer->lines = NULL;
    238 	buffer->lines_num = 0;
    239 	buffer->lines_cap = 0;
    240 	buffer->lines_gap = 0;
    241 	buffer->views = NULL;
    242 	buffer->views_num = 0;
    243 	buffer->hard_line_based = 1;
    244 	buffer->modified = 0;
    245 
    246 	/* add one empty line to buffer */
    247 	resize_and_move_line_gap(buffer, 1, 0);
    248 	buffer->lines_num++;
    249 	buffer->lines_gap++;
    250 	ledit_line *ll = buffer_get_line(buffer, 0);
    251 	init_line(buffer, ll);
    252 
    253 	return buffer;
    254 }
    255 
    256 void
    257 buffer_lock_all_views_except(ledit_buffer *buffer, ledit_view *view, char *lock_text) {
    258 	for (size_t i = 0; i < buffer->views_num; i++) {
    259 		if (buffer->views[i] != view) {
    260 			view_lock(buffer->views[i], lock_text);
    261 		}
    262 	}
    263 }
    264 
    265 void
    266 buffer_unlock_all_views(ledit_buffer *buffer) {
    267 	for (size_t i = 0; i < buffer->views_num; i++) {
    268 		view_unlock(buffer->views[i]);
    269 	}
    270 }
    271 
    272 void
    273 buffer_set_hard_line_based(ledit_buffer *buffer, int hl) {
    274 	buffer->hard_line_based = hl;
    275 }
    276 
    277 /* FIXME: lock view if others are locked! */
    278 void
    279 buffer_add_view(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos, long scroll_offset) {
    280 	size_t new_num = add_sz(buffer->views_num, 1);
    281 	buffer->views = ledit_reallocarray(buffer->views, new_num, sizeof(ledit_view *));
    282 	buffer->views[buffer->views_num] = view_create(buffer, mode, line, pos);
    283 	view_scroll(buffer->views[buffer->views_num], scroll_offset);
    284 	buffer->views_num = new_num;
    285 }
    286 
    287 /* FIXME: error checking */
    288 void
    289 buffer_remove_view(ledit_buffer *buffer, ledit_view *view) {
    290 	size_t i = 0;
    291 	int found = 0;
    292 	for (; i < buffer->views_num; i++) {
    293 		if (buffer->views[i] == view) {
    294 			found = 1;
    295 			break;
    296 		}
    297 	}
    298 	if (found) {
    299 		view_destroy(buffer->views[i]);
    300 		memmove(
    301 		    buffer->views + i,
    302 		    buffer->views + i + 1,
    303 		    (buffer->views_num - i - 1) * sizeof(ledit_view *)
    304 		);
    305 		--buffer->views_num;
    306 		/* FIXME: use generic "vector" library to avoid problems like this */
    307 		if (buffer->views_num == 0) {
    308 			free(buffer->views);
    309 			buffer->views = NULL;
    310 		} else {
    311 			buffer->views = ledit_reallocarray(buffer->views, buffer->views_num, sizeof(ledit_view *));
    312 		}
    313 	}
    314 }
    315 
    316 void
    317 buffer_recalc_all_views_from_line(ledit_buffer *buffer, size_t line) {
    318 	for (size_t i = 0; i < buffer->views_num; i++) {
    319 		view_recalc_from_line(buffer->views[i], line);
    320 	}
    321 }
    322 
    323 /* WARNING: errstr must be copied as soon as possible! */
    324 int
    325 buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errstr) {
    326 	long len;
    327 	int off = 0;
    328 	ledit_line *ll;
    329 	char *file_contents;
    330 	FILE *file;
    331 
    332 	/* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */
    333 	file = fopen(filename, "r");
    334 	if (!file) goto error;
    335 	if (fseek(file, 0, SEEK_END)) goto errorclose;
    336 	len = ftell(file);
    337 	if (len < 0) goto errorclose;
    338 	if (fseek(file, 0, SEEK_SET)) goto errorclose;
    339 	size_t lenz = add_sz((size_t)len, 2);
    340 
    341 	ll = buffer_get_line(buffer, line);
    342 	/* FIXME: insert in chunks instead of allocating huge buffer */
    343 	file_contents = ledit_malloc(lenz);
    344 	/* mimic nvi (or at least the openbsd version) - if the line
    345 	   is empty, insert directly, otherwise insert after the line */
    346 	if (ll->len > 0) {
    347 		off = 1;
    348 		file_contents[0] = '\n';
    349 	}
    350 	clearerr(file);
    351 	fread(file_contents + off, 1, (size_t)len, file);
    352 	if (ferror(file)) goto errorclose;
    353 	file_contents[len + off] = '\0';
    354 	/* don't generate extra newline at end */
    355 	if (len + off > 0 && file_contents[len + off - 1] == '\n') {
    356 		file_contents[len + off - 1] = '\0';
    357 		len--;
    358 	}
    359 	if (fclose(file)) goto error;
    360 
    361 	buffer_insert_text_with_newlines(
    362 	    buffer, line, ll->len, file_contents, len + off, NULL, NULL
    363 	);
    364 	free(file_contents);
    365 	buffer->modified = 0;
    366 	return 0;
    367 error:
    368 	if (errstr)
    369 		*errstr = strerror(errno);
    370 	return 1;
    371 errorclose:
    372 	if (errstr)
    373 		*errstr = strerror(errno);
    374 	fclose(file);
    375 	return 1;
    376 }
    377 
    378 int
    379 buffer_write_to_file(ledit_buffer *buffer, FILE *file, char **errstr) {
    380 	ledit_line *ll;
    381 	clearerr(file);
    382 	for (size_t i = 0; i < buffer->lines_num; i++) {
    383 		ll = buffer_get_line(buffer, i);
    384 		buffer_normalize_line(ll);
    385 		if (fprintf(file, "%s\n", ll->text) < 0) goto errorclose;
    386 	}
    387 	if (fclose(file)) goto error;
    388 	buffer->modified = 0;
    389 	return 0;
    390 error:
    391 	if (errstr)
    392 		*errstr = strerror(errno);
    393 	return 1;
    394 errorclose:
    395 	if (errstr)
    396 		*errstr = strerror(errno);
    397 	fclose(file);
    398 	return 1;
    399 }
    400 
    401 int
    402 buffer_write_to_fd(ledit_buffer *buffer, int fd, char **errstr) {
    403 	FILE *file = fdopen(fd, "w");
    404 	if (!file) goto error;
    405 	return buffer_write_to_file(buffer, file, errstr);
    406 error:
    407 	if (errstr)
    408 		*errstr = strerror(errno);
    409 	/* catching errors on the close wouldn't
    410 	   really make much sense anymore */
    411 	close(fd);
    412 	return 1;
    413 }
    414 
    415 /* FIXME: allow to write only certain lines */
    416 int
    417 buffer_write_to_filename(ledit_buffer *buffer, char *filename, char **errstr) {
    418 	FILE *file = fopen(filename, "w");
    419 	if (!file) goto error;
    420 	return buffer_write_to_file(buffer, file, errstr);
    421 error:
    422 	if (errstr)
    423 		*errstr = strerror(errno);
    424 	return 1;
    425 }
    426 
    427 void
    428 buffer_destroy(ledit_buffer *buffer) {
    429 	ledit_line *l;
    430 	for (size_t i = 0; i < buffer->lines_num; i++) {
    431 		l = buffer_get_line(buffer, i);
    432 		free(l->text);
    433 	}
    434 	undo_stack_destroy(buffer->undo);
    435 	free(buffer->lines);
    436 	if (buffer->filename)
    437 		free(buffer->filename);
    438 	marklist_destroy(buffer->marklist);
    439 	for (size_t i = 0; i < buffer->views_num; i++) {
    440 		view_destroy(buffer->views[i]);
    441 	}
    442 	free(buffer->views);
    443 	free(buffer);
    444 }
    445 
    446 void
    447 buffer_normalize_line(ledit_line *line) {
    448 	if (line->gap < line->len) {
    449 		memmove(
    450 		    line->text + line->gap,
    451 		    line->text + line->gap + line->cap - line->len,
    452 		    line->len - line->gap
    453 		);
    454 		line->gap = line->len;
    455 	}
    456 	/* this should never happen because the functions always
    457 	   make sure to allocate one more for the '\0' */
    458 	ledit_assert(line->len < line->cap);
    459 	line->text[line->len] = '\0';
    460 }
    461 
    462 /* FIXME: To simplify this a bit, maybe just copy text to txtbuf first and
    463    then insert it in one go instead of having this complex logic */
    464 /* FIXME: check if there can be bugs when a newline is inserted in some way
    465    other than pasting or pressing enter */
    466 
    467 static void
    468 buffer_insert_text_from_line_base(
    469     ledit_buffer *buffer,
    470     size_t dst_line, size_t dst_index,
    471     size_t src_line, size_t src_index, size_t src_len,
    472     txtbuf *text_ret) {
    473 	ledit_assert(dst_line != src_line);
    474 	ledit_line *ll = buffer_get_line(buffer, src_line);
    475 	ledit_assert(add_sz(src_index, src_len) <= ll->len);
    476 	if (text_ret != NULL) {
    477 		txtbuf_resize(text_ret, src_len);
    478 		text_ret->len = src_len;
    479 	}
    480 	if (src_index >= ll->gap) {
    481 		/* all text to insert is after gap */
    482 		buffer_insert_text_base(
    483 		    buffer, dst_line, dst_index,
    484 		    ll->text + src_index + ll->cap - ll->len, src_len
    485 		);
    486 		if (text_ret != NULL) {
    487 			memcpy(
    488 			    text_ret->text,
    489 			    ll->text + src_index + ll->cap - ll->len,
    490 			    src_len
    491 			);
    492 		}
    493 	} else if (ll->gap - src_index >= src_len) {
    494 		/* all text to insert is before gap */
    495 		buffer_insert_text_base(
    496 		    buffer, dst_line, dst_index,
    497 		    ll->text + src_index, src_len
    498 		);
    499 		if (text_ret != NULL) {
    500 			memcpy(
    501 			    text_ret->text,
    502 			    ll->text + src_index,
    503 			    src_len
    504 			);
    505 		}
    506 	} else {
    507 		/* insert part of text before gap */
    508 		buffer_insert_text_base(
    509 		    buffer, dst_line, dst_index,
    510 		    ll->text + src_index, ll->gap - src_index
    511 		);
    512 		/* insert part of text after gap */
    513 		buffer_insert_text_base(
    514 		    buffer, dst_line, dst_index + ll->gap - src_index,
    515 		    ll->text + ll->gap + ll->cap - ll->len,
    516 		    src_len - ll->gap + src_index
    517 		);
    518 		if (text_ret != NULL) {
    519 			memcpy(
    520 			    text_ret->text,
    521 			    ll->text + src_index,
    522 			    ll->gap - src_index
    523 			);
    524 			memcpy(
    525 			    text_ret + ll->gap - src_index,
    526 			    ll->text + ll->gap + ll->cap - ll->len,
    527 			    src_len - ll->gap + src_index
    528 			);
    529 		}
    530 
    531 	}
    532 	for (size_t i = 0; i < buffer->views_num; i++) {
    533 		view_notify_insert_text(buffer->views[i], dst_line, dst_index, src_len);
    534 	}
    535 }
    536 
    537 static void
    538 move_line_gap(ledit_buffer *buffer, size_t index) {
    539 	move_gap(
    540 	    buffer->lines, sizeof(ledit_line), index,
    541 	    buffer->lines_gap, buffer->lines_cap, buffer->lines_num,
    542 	    &buffer->lines_gap
    543 	);
    544 }
    545 
    546 /* FIXME: add "final" versions of the functions that include the
    547    normalization, i.e. if they have to move the gap anyways, they
    548    just move it to the end */
    549 
    550 static void
    551 resize_and_move_text_gap(ledit_line *line, size_t min_size, size_t index) {
    552 	/* yes, I know sizeof(char) == 1 anyways */
    553 	line->text = resize_and_move_gap(
    554 	    line->text, sizeof(char),
    555 	    line->gap, line->cap, line->len,
    556 	    min_size, index,
    557 	    &line->gap, &line->cap
    558 	);
    559 }
    560 
    561 static void
    562 resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index) {
    563 	buffer->lines = resize_and_move_gap(
    564 	    buffer->lines, sizeof(ledit_line),
    565 	    buffer->lines_gap, buffer->lines_cap, buffer->lines_num,
    566 	    min_size, index,
    567 	    &buffer->lines_gap, &buffer->lines_cap
    568 	);
    569 }
    570 
    571 static void
    572 buffer_insert_text_base(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) {
    573 	ledit_line *line = buffer_get_line(buffer, line_index);
    574 	ledit_assert(index <= line->len);
    575 	/* \0 is not included in line->len */
    576 	resize_and_move_text_gap(line, add_sz3(line->len, len, 1), index);
    577 	/* the gap is now located at 'index' and at least large enough to hold the new text */
    578 	memmove(line->text + index, text, len);
    579 	line->gap += len;
    580 	line->len += len;
    581 	for (size_t i = 0; i < buffer->views_num; i++) {
    582 		view_notify_insert_text(buffer->views[i], line_index, index, len);
    583 	}
    584 }
    585 
    586 /* FIXME: this isn't optimized like the standard version, but whatever */
    587 static char *
    588 strchr_len(char *text, char c, size_t len) {
    589 	for (size_t i = 0; i < len; i++) {
    590 		if (text[i] == c)
    591 			return text + i;
    592 	}
    593 	return NULL;
    594 }
    595 
    596 /* FIXME: make these functions that call recalc* also be final as described above */
    597 static void
    598 buffer_insert_text_with_newlines(
    599     ledit_buffer *buffer,
    600     size_t line_index, size_t index,
    601     char *text, size_t len,
    602     size_t *end_line_ret, size_t *end_byte_ret) {
    603 	size_t end;
    604 	buffer_insert_text_with_newlines_base(
    605 	    buffer, line_index, index, text, len,
    606 	    &end, end_byte_ret
    607 	);
    608 	if (end_line_ret)
    609 		*end_line_ret = end;
    610 	if (line_index == end)
    611 		buffer_recalc_line(buffer, line_index);
    612 	else
    613 		buffer_recalc_from_line(buffer, line_index);
    614 }
    615 
    616 /* FIXME: also look for \r */
    617 static void
    618 buffer_insert_text_with_newlines_base(
    619     ledit_buffer *buffer,
    620     size_t line_index, size_t index,
    621     char *text, size_t len,
    622     size_t *end_line_ret, size_t *end_byte_ret) {
    623 	size_t rem_len = len;
    624 	char *cur, *last = text;
    625 	size_t cur_line = line_index;
    626 	size_t cur_index = index;
    627 	/* FIXME: strchr_len isn't really needed when the lines are normalized anyways */
    628 	while ((cur = strchr_len(last, '\n', rem_len)) != NULL) {
    629 		/* FIXME: this is probably inefficient, but I don't have time to
    630 		   think about it right now */
    631 		buffer_append_line_base(buffer, cur_line, cur_index, 1);
    632 		buffer_insert_text_base(buffer, cur_line, cur_index, last, cur - last);
    633 		cur_index = 0;
    634 		cur_line++;
    635 		rem_len -= cur - last + 1;
    636 		last = cur + 1;
    637 	}
    638 	/* FIXME: check how legal this casting between pointers and size_t's is */
    639 	buffer_insert_text_base(buffer, cur_line, cur_index, last, text + len - last);
    640 	if (end_line_ret)
    641 		*end_line_ret = cur_line;
    642 	if (end_byte_ret)
    643 		*end_byte_ret = cur_index + text + len - last;
    644 }
    645 
    646 static void
    647 init_line(ledit_buffer *buffer, ledit_line *line) {
    648 	line->parent_buffer = buffer;
    649 	line->gap = 0;
    650 	line->cap = 2; /* arbitrary */
    651 	line->text = ledit_malloc(line->cap);
    652 	line->text[0] = '\0';
    653 	line->len = 0;
    654 }
    655 
    656 static void
    657 buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) {
    658 	size_t new_len = add_sz(buffer->lines_num, 1);
    659 	size_t insert_index = add_sz(line_index, 1);
    660 	resize_and_move_line_gap(buffer, new_len, insert_index);
    661 	buffer->lines_num++;
    662 	buffer->lines_gap++;
    663 	ledit_line *new_l = buffer_get_line(buffer, line_index + 1);
    664 	init_line(buffer, new_l);
    665 	for (size_t i = 0; i < buffer->views_num; i++) {
    666 		view_notify_append_line(buffer->views[i], line_index);
    667 	}
    668 	if (break_text) {
    669 		ledit_line *l = buffer_get_line(buffer, line_index);
    670 		buffer_insert_text_from_line_base(
    671 		    buffer, line_index + 1, 0,
    672 		    line_index, text_index, l->len - text_index, NULL
    673 		);
    674 		buffer_delete_line_section_base(
    675 		    buffer, line_index,
    676 		    text_index, l->len - text_index
    677 		);
    678 	}
    679 }
    680 
    681 /* IMPORTANT: buffer_recalc_from_line needs to be called sometime after this! */
    682 static void
    683 buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2) {
    684 	ledit_line *l;
    685 	ledit_assert(index2 >= index1);
    686 	/* it isn't allowed to delete all lines */
    687 	ledit_assert(index2 - index1 != buffer->lines_num);
    688 	for (size_t i = index1; i <= index2; i++) {
    689 		l = buffer_get_line(buffer, i);
    690 		free(l->text);
    691 	}
    692 	move_line_gap(buffer, index1);
    693 	buffer->lines_num -= index2 - index1 + 1;
    694 	/* possibly decrease size of array - this needs to be after
    695 	   actually deleting the lines so the length is already less */
    696 	size_t min_size = ideal_array_size(buffer->lines_cap, buffer->lines_num);
    697 	if (min_size != buffer->lines_cap)
    698 		resize_and_move_line_gap(buffer, buffer->lines_num, buffer->lines_gap);
    699 	for (size_t i = 0; i < buffer->views_num; i++) {
    700 		view_notify_delete_lines(buffer->views[i], index1, index2);
    701 	}
    702 }
    703 
    704 ledit_line *
    705 buffer_get_line(ledit_buffer *buffer, size_t index) {
    706 	ledit_assert(index < buffer->lines_num);
    707 	return index < buffer->lines_gap ?
    708 	       &buffer->lines[index] :
    709 	       &buffer->lines[index + buffer->lines_cap - buffer->lines_num];
    710 }
    711 
    712 void
    713 buffer_recalc_line(ledit_buffer *buffer, size_t line) {
    714 	for (size_t i = 0; i < buffer->views_num; i++) {
    715 		view_recalc_line(buffer->views[i], line);
    716 	}
    717 }
    718 
    719 void
    720 buffer_recalc_from_line(ledit_buffer *buffer, size_t line) {
    721 	for (size_t i = 0; i < buffer->views_num; i++) {
    722 		view_recalc_from_line(buffer->views[i], line);
    723 	}
    724 }
    725 
    726 void
    727 buffer_recalc_all_lines(ledit_buffer *buffer) {
    728 	for (size_t i = 0; i < buffer->views_num; i++) {
    729 		view_recalc_all_lines(buffer->views[i]);
    730 	}
    731 }
    732 
    733 size_t
    734 buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, size_t byte2) {
    735 	ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
    736 	size_t len = 0;
    737 	ledit_line *ll = buffer_get_line(buffer, line1);
    738 	ledit_line *ll2 = buffer_get_line(buffer, line2);
    739 	ledit_assert(byte1 <= ll->len);
    740 	ledit_assert(byte2 <= ll2->len);
    741 	if (line1 == line2) {
    742 		len = byte2 - byte1;
    743 	} else {
    744 		/* + 1 for newline */
    745 		len = add_sz3(ll->len - byte1, byte2, 1);
    746 		for (size_t i = line1 + 1; i < line2; i++) {
    747 			ll = buffer_get_line(buffer, i);
    748 			/* ll->len + 1 should be valid anyways
    749 			   because there *should* always be
    750 			   space for '\0' at the end, i.e. ll->cap
    751 			   should be at least ll->len + 1 */
    752 			/* FIXME: also, this overflow checking is
    753 			   probably completely useless (it definitely
    754 			   is really ugly) */
    755 			len += add_sz(ll->len, 1);
    756 		}
    757 	}
    758 	return len;
    759 }
    760 
    761 /* FIXME: Only copy new text in selection when expanding it */
    762 /* FIXME: Work directly with gap buffer instead of normalizing first
    763    -> I guess it doesn't matter right now because copying text is
    764       only done when it is re-rendered (and thus normalized because
    765       of pango's requirements). If a more efficient rendering
    766       backend is added, it would be good to optimize this, though. */
    767 static void
    768 buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) {
    769 	ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
    770 	ledit_line *ll1 = buffer_get_line(buffer, line1);
    771 	ledit_line *ll2 = buffer_get_line(buffer, line2);
    772 	buffer_normalize_line(ll1);
    773 	if (line1 == line2) {
    774 		memcpy(dst, ll1->text + byte1, byte2 - byte1);
    775 		dst[byte2 - byte1] = '\0';
    776 	} else {
    777 		size_t cur_pos = 0;
    778 		memcpy(dst, ll1->text + byte1, ll1->len - byte1);
    779 		cur_pos += ll1->len - byte1;
    780 		dst[cur_pos] = '\n';
    781 		cur_pos++;
    782 		for (int i = line1 + 1; i < line2; i++) {
    783 			ledit_line *ll = buffer_get_line(buffer, i);
    784 			buffer_normalize_line(ll);
    785 			memcpy(dst + cur_pos, ll->text, ll->len);
    786 			cur_pos += ll->len;
    787 			dst[cur_pos] = '\n';
    788 			cur_pos++;
    789 		}
    790 		buffer_normalize_line(ll2);
    791 		memcpy(dst + cur_pos, ll2->text, byte2);
    792 		cur_pos += byte2;
    793 		dst[cur_pos] = '\0';
    794 	}
    795 }
    796 
    797 void
    798 buffer_copy_text_to_txtbuf(
    799     ledit_buffer *buffer,
    800     txtbuf *buf,
    801     size_t line1, size_t byte1,
    802     size_t line2, size_t byte2) {
    803 	ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
    804 	size_t len = buffer_textlen(buffer, line1, byte1, line2, byte2);
    805 	txtbuf_resize(buf, len);
    806 	buffer_copy_text(buffer, buf->text, line1, byte1, line2, byte2);
    807 	buf->len = len;
    808 }
    809 
    810 /* get char with logical index i from line */
    811 #define LINE_CHAR(line, i) ((i) < (line)->gap ? (line)->text[i] : (line)->text[i + (line)->cap - (line)->len])
    812 
    813 size_t
    814 line_prev_utf8(ledit_line *line, size_t index) {
    815 	if (index == 0)
    816 		return 0;
    817 	size_t i = index - 1;
    818 	/* find valid utf8 char - this probably needs to be improved */
    819 	while (i > 0 && ((LINE_CHAR(line, i) & 0xC0) == 0x80))
    820 		i--;
    821 	return i;
    822 }
    823 
    824 size_t
    825 line_next_utf8(ledit_line *line, size_t index) {
    826 	if (index >= line->len)
    827 		return line->len;
    828 	size_t i = index + 1;
    829 	while (i < line->len && ((LINE_CHAR(line, i) & 0xC0) == 0x80))
    830 		i++;
    831 	return i;
    832 }
    833 
    834 /* Warning: this is very inefficient! */
    835 /* FIXME: Any way to optimize this? */
    836 size_t
    837 line_byte_to_char(ledit_line *line, size_t byte) {
    838 	size_t c = 0;
    839 	size_t i = 0;
    840 	size_t b = byte > line->len ? line->len : byte; /* maybe not necessary */
    841 	while (i < b) {
    842 		c++;
    843 		i = line_next_utf8(line, i);
    844 	}
    845 	return c;
    846 }
    847 
    848 /* FIXME: It might make more sense to add a flag to view_{next,prev}_char_pos
    849    since this is essentially the same, just without the check for is_cursor_position */
    850 void
    851 buffer_next_char_pos(
    852     ledit_buffer *buffer,
    853     size_t line, size_t byte,
    854     int num, int multiline,
    855     size_t *line_ret, size_t *byte_ret) {
    856 	ledit_line *ll = buffer_get_line(buffer, line);
    857 	size_t c = line_byte_to_char(ll, byte);
    858 	size_t cur_byte = byte;
    859 	for (int i = 0; i < num; i++) {
    860 		if (cur_byte >= ll->len) {
    861 			if (multiline && line < buffer->lines_num - 1) {
    862 				line++;
    863 				ll = buffer_get_line(buffer, line);
    864 				c = 0;
    865 				cur_byte = 0;
    866 				i++;
    867 				continue;
    868 			} else {
    869 				break;
    870 			}
    871 		}
    872 		cur_byte = line_next_utf8(ll, cur_byte);
    873 		c++;
    874 	}
    875 	if (line_ret)
    876 		*line_ret = line;
    877 	if (byte_ret)
    878 		*byte_ret = cur_byte <= ll->len ? cur_byte : ll->len;
    879 }
    880 
    881 void
    882 buffer_prev_char_pos(
    883     ledit_buffer *buffer,
    884     size_t line, size_t byte,
    885     int num, int multiline,
    886     size_t *line_ret, size_t *byte_ret) {
    887 	ledit_line *ll = buffer_get_line(buffer, line);
    888 	size_t c = line_byte_to_char(ll, byte);
    889 	size_t cur_byte = byte;
    890 	for (int i = 0; i < num; i++) {
    891 		if (cur_byte == 0) {
    892 			if (multiline && line > 0) {
    893 				line--;
    894 				ll = buffer_get_line(buffer, line);
    895 				c = line_byte_to_char(ll, ll->len);
    896 				cur_byte = ll->len;
    897 				i++;
    898 				continue;
    899 			} else {
    900 				break;
    901 			}
    902 		}
    903 		cur_byte = line_prev_utf8(ll, cur_byte);
    904 		c--;
    905 	}
    906 	if (line_ret)
    907 		*line_ret = line;
    908 	if (byte_ret)
    909 		*byte_ret = cur_byte;
    910 }
    911 
    912 /* The check for length == 0 in buffer_delete_line_section_base and the check for
    913    empty range in delete_range_base shouldn't be needed, but I added them just in case. */
    914 static void
    915 buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) {
    916 	if (length == 0)
    917 		return;
    918 	ledit_line *l = buffer_get_line(buffer, line);
    919 	/* FIXME: somehow make sure this doesn't get optimized out? */
    920 	(void)add_sz(start, length); /* just check that no overflow */
    921 	ledit_assert(start + length <= l->len);
    922 	if (start <= l->gap && start + length >= l->gap) {
    923 		/* range includes gap */
    924 		l->gap = start;
    925 	} else if (start < l->gap && start + length <= l->gap) {
    926 		/* entire range is before gap */
    927 		memmove(
    928 		    l->text + l->cap - l->len + start + length,
    929 		    l->text + start + length,
    930 		    l->gap - start - length
    931 		);
    932 		l->gap = start;
    933 	} else {
    934 		/* entire range is after gap */
    935 		/* move piece between end of gap and
    936 		   start of range to beginning of gap */
    937 		memmove(
    938 		    l->text + l->gap,
    939 		    l->text + l->gap + l->cap - l->len,
    940 		    start - l->gap
    941 		);
    942 		l->gap += start - l->gap;
    943 	}
    944 	l->len -= length;
    945 	/* possibly decrease size of line */
    946 	size_t cap = ideal_array_size(l->cap, add_sz(l->len, 1));
    947 	if (cap != l->cap)
    948 		resize_and_move_text_gap(l, l->len + 1, l->gap);
    949 	for (size_t i = 0; i < buffer->views_num; i++) {
    950 		view_notify_delete_text(buffer->views[i], line, start, length);
    951 	}
    952 }
    953 
    954 static void
    955 delete_range_base(
    956     ledit_buffer *buffer,
    957     size_t line_index1, size_t byte_index1,
    958     size_t line_index2, size_t byte_index2,
    959     txtbuf *text_ret) {
    960 	if (line_index1 == line_index2 && byte_index1 == byte_index2)
    961 		return;
    962 	sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2);
    963 	if (line_index1 == line_index2) {
    964 		if (text_ret) {
    965 			buffer_copy_text_to_txtbuf(
    966 			    buffer, text_ret,
    967 			    line_index1, byte_index1,
    968 			    line_index2, byte_index2
    969 			);
    970 		}
    971 		buffer_delete_line_section_base(
    972 		    buffer, line_index1, byte_index1, byte_index2 - byte_index1
    973 		);
    974 	} else {
    975 		if (text_ret) {
    976 			buffer_copy_text_to_txtbuf(
    977 			    buffer, text_ret,
    978 			    line_index1, byte_index1,
    979 			    line_index2, byte_index2
    980 			);
    981 		}
    982 		ledit_line *line1 = buffer_get_line(buffer, line_index1);
    983 		ledit_line *line2 = buffer_get_line(buffer, line_index2);
    984 		ledit_assert(byte_index1 <= line1->len);
    985 		ledit_assert(byte_index2 <= line2->len);
    986 		buffer_delete_line_section_base(
    987 		    buffer, line_index1, byte_index1, line1->len - byte_index1
    988 		);
    989 		buffer_insert_text_from_line_base(
    990 		    buffer,
    991 		    line_index1, byte_index1,
    992 		    line_index2, byte_index2,
    993 		    line2->len - byte_index2, NULL
    994 		);
    995 		buffer_delete_line_entries_base(
    996 		    buffer, line_index1 + 1, line_index2
    997 		);
    998 	}
    999 }
   1000 
   1001 static void
   1002 undo_insert_helper(void *data, size_t line, size_t byte, char *text, size_t text_len) {
   1003 	ledit_buffer *buffer = (ledit_buffer *)data;
   1004 	buffer_insert_text_with_newlines_base(buffer, line, byte, text, text_len, NULL, NULL);
   1005 }
   1006 
   1007 static void
   1008 undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t byte2) {
   1009 	ledit_buffer *buffer = (ledit_buffer *)data;
   1010 	delete_range_base(buffer, line1, byte1, line2, byte2, NULL);
   1011 }
   1012 
   1013 undo_status
   1014 buffer_undo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
   1015 	size_t min_line;
   1016 	undo_status s = ledit_undo(
   1017 	    buffer->undo, mode, buffer, &undo_insert_helper,
   1018 	    &undo_delete_helper, cur_line, cur_byte, &min_line
   1019 	);
   1020 	if (min_line < buffer->lines_num) {
   1021 		buffer_recalc_all_views_from_line(
   1022 		    buffer, min_line > 0 ? min_line - 1 : min_line
   1023 		);
   1024 	}
   1025 	buffer->modified = 1;
   1026 	return s;
   1027 }
   1028 
   1029 undo_status
   1030 buffer_redo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
   1031 	size_t min_line;
   1032 	undo_status s = ledit_redo(
   1033 	    buffer->undo, mode, buffer, &undo_insert_helper,
   1034 	    &undo_delete_helper, cur_line, cur_byte, &min_line
   1035 	);
   1036 	if (min_line < buffer->lines_num) {
   1037 		buffer_recalc_all_views_from_line(
   1038 		    buffer, min_line > 0 ? min_line - 1 : min_line
   1039 		);
   1040 	}
   1041 	buffer->modified = 1;
   1042 	return s;
   1043 }
   1044 
   1045 void
   1046 buffer_delete_with_undo_base(
   1047     ledit_buffer *buffer, ledit_range cur_range,
   1048     int start_undo_group, ledit_mode mode, /* for undo */
   1049     size_t line_index1, size_t byte_index1,
   1050     size_t line_index2, size_t byte_index2,
   1051     txtbuf *text_ret) {
   1052 	/* FIXME: global txtbuf to avoid allocating each time */
   1053 	/* actually, could just use stack variable here */
   1054 	txtbuf *buf = text_ret != NULL ? text_ret : txtbuf_new();
   1055 	sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2);
   1056 	delete_range_base(
   1057 	    buffer, line_index1, byte_index1, line_index2, byte_index2, buf
   1058 	);
   1059 	ledit_range del_range = {
   1060 	    .line1 = line_index1, .byte1 = byte_index1,
   1061 	    .line2 = line_index2, .byte2 = byte_index2
   1062 	};
   1063 	undo_push_delete(
   1064 	    buffer->undo, buf, del_range, cur_range, start_undo_group, mode
   1065 	);
   1066 	if (text_ret == NULL)
   1067 		txtbuf_destroy(buf);
   1068 	buffer->modified = 1;
   1069 }
   1070 
   1071 void
   1072 buffer_delete_with_undo(
   1073     ledit_buffer *buffer, ledit_range cur_range,
   1074     int start_undo_group, ledit_mode mode, /* for undo */
   1075     size_t line_index1, size_t byte_index1,
   1076     size_t line_index2, size_t byte_index2,
   1077     txtbuf *text_ret) {
   1078 	buffer_delete_with_undo_base(
   1079 	    buffer, cur_range,
   1080 	    start_undo_group, mode,
   1081 	    line_index1, byte_index1,
   1082 	    line_index2, byte_index2,
   1083 	    text_ret
   1084 	);
   1085 	size_t min = line_index1 < line_index2 ? line_index1 : line_index2;
   1086 	buffer_recalc_all_views_from_line(buffer, min);
   1087 }
   1088 
   1089 void
   1090 buffer_insert_with_undo_base(
   1091     ledit_buffer *buffer,
   1092     ledit_range cur_range, int set_range_end,
   1093     int start_undo_group, ledit_mode mode,
   1094     size_t line, size_t byte,
   1095     char *text, size_t len,
   1096     size_t *line_ret, size_t *byte_ret) {
   1097 	txtbuf ins_buf = {.text = text, .len = len, .cap = len};
   1098 	ledit_range ins_range;
   1099 	ins_range.line1 = line;
   1100 	ins_range.byte1 = byte;
   1101 	size_t new_line, new_byte;
   1102 	buffer_insert_text_with_newlines_base(
   1103 	    buffer, line, byte, text, len,
   1104 	    &new_line, &new_byte
   1105 	);
   1106 	if (set_range_end) {
   1107 		cur_range.line2 = new_line;
   1108 		cur_range.byte2 = new_byte;
   1109 	}
   1110 	ins_range.line2 = new_line;
   1111 	ins_range.byte2 = new_byte;
   1112 	undo_push_insert(
   1113 	    buffer->undo, &ins_buf, ins_range, cur_range, start_undo_group, mode
   1114 	);
   1115 	if (line_ret != NULL)
   1116 		*line_ret = new_line;
   1117 	if (byte_ret != NULL)
   1118 		*byte_ret = new_byte;
   1119 	buffer->modified = 1;
   1120 }
   1121 
   1122 void
   1123 buffer_insert_with_undo(
   1124     ledit_buffer *buffer,
   1125     ledit_range cur_range, int set_range_end,
   1126     int start_undo_group, ledit_mode mode,
   1127     size_t line, size_t byte,
   1128     char *text, size_t len,
   1129     size_t *line_ret, size_t *byte_ret) {
   1130 	buffer_insert_with_undo_base(
   1131 	    buffer,
   1132 	    cur_range, set_range_end,
   1133 	    start_undo_group, mode,
   1134 	    line, byte, text, len,
   1135 	    line_ret, byte_ret
   1136 	);
   1137 	buffer_recalc_all_views_from_line(buffer, line);
   1138 }