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

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 }