URI: 
       undo.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
       ---
       undo.c (9769B)
       ---
            1 #include <string.h>
            2 #include <stdlib.h>
            3 #include <stdint.h>
            4 
            5 #include <X11/Xlib.h>
            6 #include <X11/Xutil.h>
            7 #include <pango/pangoxft.h>
            8 
            9 #include "util.h"
           10 #include "memory.h"
           11 #include "common.h"
           12 #include "txtbuf.h"
           13 #include "cache.h"
           14 #include "undo.h"
           15 #include "assert.h"
           16 
           17 /* FIXME: more sanity checks in case text is
           18    inserted/deleted without adding to undo stack */
           19 /* FIXME: cursor positions can be a bit weird when
           20    undo is used across different insert sessions in
           21    insert mode - e.g. if some text is inserted, then
           22    'o' is used in normal mode to append a new line and
           23    type some text, the cursor position at a certain
           24    undo position will differ in insert mode depending
           25    on whether it was reached with undo or redo since
           26    'o' saves the position at which it was pressed,
           27    not the position of the last insert */
           28 
           29 enum operation {
           30         UNDO_INSERT,
           31         UNDO_DELETE
           32 };
           33 
           34 typedef struct {
           35         txtbuf *text;
           36         enum operation type;
           37         ledit_mode mode;
           38         ledit_range op_range;
           39         ledit_range cursor_range;
           40         int group;
           41         int mode_group;
           42 } undo_elem;
           43 
           44 struct undo_stack {
           45         size_t len, cur, cap;
           46         undo_elem *stack;
           47         int cur_valid;
           48         int change_mode_group;
           49 };
           50 
           51 undo_stack *
           52 undo_stack_create(void) {
           53         undo_stack *undo = ledit_malloc(sizeof(undo_stack));
           54         undo->len = undo->cap = 0;
           55         undo->cur = 0;
           56         undo->cur_valid = 0;
           57         undo->stack = NULL;
           58         undo->change_mode_group = 0;
           59         return undo;
           60 }
           61 
           62 void
           63 undo_stack_destroy(undo_stack *undo) {
           64         for (size_t i = 0; i < undo->cap; i++) {
           65                 if (undo->stack[i].text != NULL)
           66                         txtbuf_destroy(undo->stack[i].text);
           67         }
           68         free(undo->stack);
           69         free(undo);
           70 }
           71 
           72 /* FIXME: resize text buffers when they aren't needed anymore */
           73 static undo_elem *
           74 push_undo_elem(undo_stack *undo) {
           75         if (undo->cur_valid)
           76                 undo->cur++;
           77         else
           78                 undo->cur = 0;
           79         undo->cur_valid = 1;
           80         undo->len = add_sz(undo->cur, 1);
           81         if (undo->len > undo->cap) {
           82                 size_t cap = ideal_array_size(undo->cap, undo->len);
           83                 undo->stack = ledit_reallocarray(undo->stack, cap, sizeof(undo_elem));
           84                 for (size_t i = undo->cap; i < cap; i++) {
           85                         undo->stack[i].text = NULL;
           86                 }
           87                 undo->cap = cap;
           88         }
           89         return &undo->stack[undo->cur];
           90 }
           91 
           92 static undo_elem *
           93 peek_undo_elem(undo_stack *undo) {
           94         if (!undo->cur_valid)
           95                 return NULL;
           96         return &undo->stack[undo->cur];
           97 }
           98 
           99 void
          100 undo_change_mode_group(undo_stack *undo) {
          101         undo->change_mode_group = 1;
          102 }
          103 
          104 #if TEST
          105 void
          106 dump_undo_stack(FILE *file, undo_stack *undo) {
          107         fprintf(
          108             file,
          109             "cur: %zu, cur_valid: %d, change_mode_group: %d, len: %zu, cap: %zu\n",
          110             undo->cur, undo->cur_valid, undo->change_mode_group, undo->len, undo->cap
          111         );
          112         for (size_t i = 0; i < undo->len; i++) {
          113                 undo_elem *e = &undo->stack[i];
          114                 fprintf(
          115                     file,
          116                     "type %d, mode %d, group %d, mode_group %d, text '%.*s', "
          117                     "op_range (%zu,%zu;%zu,%zu), cursor_range (%zu,%zu;%zu,%zu)\n",
          118                     e->type, e->mode, e->group, e->mode_group, (int)e->text->len, e->text->text,
          119                     e->op_range.line1, e->op_range.byte1, e->op_range.line2, e->op_range.byte2,
          120                     e->cursor_range.line1, e->cursor_range.byte1, e->cursor_range.line2, e->cursor_range.byte2
          121                 );
          122         }
          123 }
          124 #endif
          125 
          126 static void
          127 push_undo(
          128     undo_stack *undo, txtbuf *text,
          129     ledit_range insert_range, /* maybe not the best name */
          130     ledit_range cursor_range,
          131     int start_group,
          132     enum operation type, ledit_mode mode) {
          133         undo_elem *old = peek_undo_elem(undo);
          134         int last_group = old == NULL ? 0 : old->group;
          135         int last_mode_group = old == NULL ? 0 : old->mode_group;
          136         undo_elem *e = push_undo_elem(undo);
          137         e->group = start_group ? !last_group : last_group;
          138         e->mode_group = undo->change_mode_group ? !last_mode_group : last_mode_group;
          139         undo->change_mode_group = 0;
          140         e->op_range = insert_range;
          141         e->cursor_range = cursor_range;
          142         e->mode = mode;
          143         e->type = type;
          144         if (e->text != NULL)
          145                 txtbuf_copy(e->text, text);
          146         else
          147                 e->text = txtbuf_dup(text);
          148 }
          149 
          150 void
          151 undo_push_insert(
          152     undo_stack *undo, txtbuf *text,
          153     ledit_range insert_range, ledit_range cursor_range,
          154     int start_group, ledit_mode mode) {
          155         push_undo(
          156             undo, text, insert_range, cursor_range,
          157             start_group, UNDO_INSERT, mode
          158         );
          159 }
          160 
          161 void
          162 undo_push_delete(
          163     undo_stack *undo, txtbuf *text,
          164     ledit_range delete_range, ledit_range cursor_range,
          165     int start_group, ledit_mode mode) {
          166         push_undo(
          167             undo, text, delete_range, cursor_range,
          168             start_group, UNDO_DELETE, mode
          169         );
          170 }
          171 
          172 undo_status
          173 ledit_undo(undo_stack *undo, ledit_mode mode, void *callback_data,
          174     undo_insert_callback insert_cb, undo_delete_callback delete_cb,
          175     size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret) {
          176         undo_elem *e;
          177         /* skip empty elements */
          178         while (undo->cur_valid && undo->stack[undo->cur].text->len == 0) {
          179                 if (undo->cur == 0)
          180                         undo->cur_valid = 0;
          181                 else
          182                         undo->cur--;
          183         }
          184         if (!undo->cur_valid)
          185                 return UNDO_OLDEST_CHANGE;
          186         int group = undo->stack[undo->cur].group;
          187         int mode_group = undo->stack[undo->cur].mode_group;
          188         size_t min_line = SIZE_MAX;
          189         int mode_group_same = 0;
          190         size_t cur_line = 0;
          191         size_t cur_index = 0;
          192         while (undo->cur_valid &&
          193                (undo->stack[undo->cur].group == group || (mode_group_same =
          194                 ((mode == NORMAL ||
          195                   mode == VISUAL) &&
          196                  undo->stack[undo->cur].mode == INSERT &&
          197                  undo->stack[undo->cur].mode_group == mode_group)))) {
          198                 e = &undo->stack[undo->cur];
          199                 /* if the mode group is the same, we need to update the group,
          200                    otherwise it can happen that some iterations are performed
          201                    because the mode group (but not the normal group) is the
          202                    same, and then the next normal group is also undone because
          203                    it has the same group id as the original group here */
          204                 if (mode_group_same)
          205                         group = e->group;
          206                 switch (e->type) {
          207                 case UNDO_INSERT:
          208                         /* FIXME: should the paste buffer also be modified? */
          209                         /* printf("delete %zu,%zu; %zu,%zu\n", e->op_range.line1, e->op_range.byte1, e->op_range.line2, e->op_range.byte2); */
          210                         delete_cb(
          211                             callback_data,
          212                             e->op_range.line1, e->op_range.byte1,
          213                             e->op_range.line2, e->op_range.byte2
          214                         );
          215                         break;
          216                 case UNDO_DELETE:
          217                         /* printf("delete %zu,%zu; %zu,%zu\n", e->op_range.line1, e->op_range.byte1, e->op_range.line2, e->op_range.byte2); */
          218                         insert_cb(
          219                             callback_data,
          220                             e->op_range.line1, e->op_range.byte1,
          221                             e->text->text, e->text->len
          222                         );
          223                         break;
          224                 default:
          225                         /* FIXME: show this as a message */
          226                         fprintf(stderr, "Error with undo. This should not happen. Fix the code please.\n");
          227                         break;
          228                 }
          229                 /* FIXME: make sure this is always sorted already */
          230                 if (e->op_range.line1 < min_line)
          231                         min_line = e->op_range.line1;
          232                 if (undo->cur == 0)
          233                         undo->cur_valid = 0;
          234                 else
          235                         undo->cur--;
          236                 cur_line = e->cursor_range.line1;
          237                 cur_index = e->cursor_range.byte1;
          238         }
          239         /* these can't be written directly in the while loop because that causes
          240            conflicts - when text is inserted/deleted on the current line of the
          241            view, the cursor is updated, but if the current line was already changed
          242            here before the end of the undo run, it's possible for multiple lines
          243            to have a cursor highlight set in the end (at least when working with
          244            multiple views) */
          245         *cur_line_ret = cur_line;
          246         *cur_index_ret = cur_index;
          247         *min_line_ret = min_line;
          248         if (mode == NORMAL || mode == VISUAL)
          249                 undo_change_mode_group(undo);
          250         return UNDO_NORMAL;
          251 }
          252 
          253 undo_status
          254 ledit_redo(undo_stack *undo, ledit_mode mode, void *callback_data,
          255     undo_insert_callback insert_cb, undo_delete_callback delete_cb,
          256     size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret) {
          257         undo_elem *e;
          258         if (undo->len == 0)
          259                 return UNDO_NEWEST_CHANGE;
          260         /* skip elements where no text is changed */
          261         while (undo->cur < undo->len - 1 && undo->stack[undo->cur + 1].text->len == 0) {
          262                 undo->cur++;
          263         }
          264         if (undo->cur_valid && undo->cur >= undo->len - 1)
          265                 return UNDO_NEWEST_CHANGE;
          266         if (!undo->cur_valid) {
          267                 undo->cur_valid = 1;
          268                 undo->cur = 0;
          269         } else {
          270                 undo->cur++;
          271         }
          272         int group = undo->stack[undo->cur].group;
          273         int mode_group = undo->stack[undo->cur].mode_group;
          274         size_t min_line = SIZE_MAX;
          275         int mode_group_same = 0;
          276         size_t cur_line = 0;
          277         size_t cur_index = 0;
          278         while (undo->cur < undo->len &&
          279                (undo->stack[undo->cur].group == group || (mode_group_same =
          280                 ((mode == NORMAL ||
          281                   mode == VISUAL) &&
          282                  undo->stack[undo->cur].mode == INSERT &&
          283                  undo->stack[undo->cur].mode_group == mode_group)))) {
          284                 e = &undo->stack[undo->cur];
          285                 if (mode_group_same)
          286                         group = e->group;
          287                 switch (e->type) {
          288                 case UNDO_INSERT:
          289                         insert_cb(
          290                             callback_data,
          291                             e->op_range.line1, e->op_range.byte1,
          292                             e->text->text, e->text->len
          293                         );
          294                         break;
          295                 case UNDO_DELETE:
          296                         delete_cb(
          297                             callback_data,
          298                             e->op_range.line1, e->op_range.byte1,
          299                             e->op_range.line2, e->op_range.byte2
          300                         );
          301                         break;
          302                 default:
          303                         fprintf(stderr, "Error with redo. This should not happen. Fix the code please.\n");
          304                         break;
          305                 }
          306                 if (e->op_range.line1 < min_line)
          307                         min_line = e->op_range.line1;
          308                 undo->cur++;
          309                 cur_line = e->cursor_range.line2;
          310                 cur_index = e->cursor_range.byte2;
          311         }
          312         *cur_line_ret = cur_line;
          313         *cur_index_ret = cur_index;
          314         /* it should theoretically never be 0 anyways, but whatever */
          315         if (undo->cur > 0)
          316                 undo->cur--;
          317         *min_line_ret = min_line;
          318         if (mode == NORMAL || mode == VISUAL)
          319                 undo_change_mode_group(undo);
          320         return UNDO_NORMAL;
          321 }
          322 
          323 void
          324 undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range) {
          325         undo_elem *e = peek_undo_elem(undo);
          326         if (e != NULL)
          327                 e->cursor_range = cur_range;
          328 }
          329 
          330 char *
          331 undo_state_to_str(undo_status s) {
          332         switch (s) {
          333         case UNDO_NORMAL:
          334                 return "Performed undo/redo";
          335         case UNDO_OLDEST_CHANGE:
          336                 return "Already at oldest change";
          337         case UNDO_NEWEST_CHANGE:
          338                 return "Already at newest change";
          339         default:
          340                 return "This is a bug. Tell lumidify about it.";
          341         }
          342 }