URI: 
       buffer.c - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }