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 (33598B)


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