undo.c - 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/… | |
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, sizeo… | |
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, c… | |
110 undo->cur, undo->cur_valid, undo->change_mode_group, undo->l… | |
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 '%.… | |
117 "op_range (%zu,%zu;%zu,%zu), cursor_range (%zu,%zu;%… | |
118 e->type, e->mode, e->group, e->mode_group, (int)e->t… | |
119 e->op_range.line1, e->op_range.byte1, e->op_range.li… | |
120 e->cursor_range.line1, e->cursor_range.byte1, e->cur… | |
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 : las… | |
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_sam… | |
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 … | |
200 otherwise it can happen that some iterations are perf… | |
201 because the mode group (but not the normal group) is … | |
202 same, and then the next normal group is also undone b… | |
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 modifi… | |
209 /* printf("delete %zu,%zu; %zu,%zu\n", e->op_ran… | |
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_ran… | |
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 no… | |
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 tha… | |
240 conflicts - when text is inserted/deleted on the current line… | |
241 view, the cursor is updated, but if the current line was alre… | |
242 here before the end of the undo run, it's possible for multip… | |
243 to have a cursor highlight set in the end (at least when work… | |
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].t… | |
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_sam… | |
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 no… | |
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 } |