1 #include "change.h"
2 #include "buffer.h"
3 #include "debug.h"
4 #include "error.h"
5 #include "util/xmalloc.h"
6 #include "view.h"
7
8 static ChangeMergeEnum change_merge;
9 static ChangeMergeEnum prev_change_merge;
10
alloc_change(void)11 static Change *alloc_change(void)
12 {
13 return xcalloc(sizeof(Change));
14 }
15
add_change(Change * change)16 static void add_change(Change *change)
17 {
18 Change *head = buffer->cur_change;
19
20 change->next = head;
21 xrenew(head->prev, head->nr_prev + 1);
22 head->prev[head->nr_prev++] = change;
23
24 buffer->cur_change = change;
25 }
26
27 // This doesn't need to be local to buffer because commands are atomic
28 static Change *change_barrier;
29
is_change_chain_barrier(const Change * change)30 static bool is_change_chain_barrier(const Change *change)
31 {
32 return !change->ins_count && !change->del_count;
33 }
34
new_change(void)35 static Change *new_change(void)
36 {
37 if (change_barrier) {
38 /*
39 * We are recording series of changes (:replace for example)
40 * and now we have just made the first change so we have to
41 * mark beginning of the chain.
42 *
43 * We could have done this before when starting the change
44 * chain but then we may have ended up with an empty chain.
45 * We don't want to record empty changes ever.
46 */
47 add_change(change_barrier);
48 change_barrier = NULL;
49 }
50
51 Change *change = alloc_change();
52 add_change(change);
53 return change;
54 }
55
buffer_offset(void)56 static size_t buffer_offset(void)
57 {
58 return block_iter_get_offset(&view->cursor);
59 }
60
record_insert(size_t len)61 static void record_insert(size_t len)
62 {
63 Change *change = buffer->cur_change;
64
65 BUG_ON(!len);
66 if (
67 change_merge == prev_change_merge
68 && change_merge == CHANGE_MERGE_INSERT
69 ) {
70 BUG_ON(change->del_count);
71 change->ins_count += len;
72 return;
73 }
74
75 change = new_change();
76 change->offset = buffer_offset();
77 change->ins_count = len;
78 }
79
record_delete(char * buf,size_t len,bool move_after)80 static void record_delete(char *buf, size_t len, bool move_after)
81 {
82 BUG_ON(!len);
83 BUG_ON(!buf);
84
85 Change *change = buffer->cur_change;
86 if (change_merge == prev_change_merge) {
87 if (change_merge == CHANGE_MERGE_DELETE) {
88 xrenew(change->buf, change->del_count + len);
89 memcpy(change->buf + change->del_count, buf, len);
90 change->del_count += len;
91 free(buf);
92 return;
93 }
94 if (change_merge == CHANGE_MERGE_ERASE) {
95 xrenew(buf, len + change->del_count);
96 memcpy(buf + len, change->buf, change->del_count);
97 change->del_count += len;
98 free(change->buf);
99 change->buf = buf;
100 change->offset -= len;
101 return;
102 }
103 }
104
105 change = new_change();
106 change->offset = buffer_offset();
107 change->del_count = len;
108 change->move_after = move_after;
109 change->buf = buf;
110 }
111
record_replace(char * deleted,size_t del_count,size_t ins_count)112 static void record_replace(char *deleted, size_t del_count, size_t ins_count)
113 {
114 BUG_ON(del_count && !deleted);
115 BUG_ON(!del_count && deleted);
116 BUG_ON(!del_count && !ins_count);
117
118 Change *change = new_change();
119 change->offset = buffer_offset();
120 change->ins_count = ins_count;
121 change->del_count = del_count;
122 change->buf = deleted;
123 }
124
begin_change(ChangeMergeEnum m)125 void begin_change(ChangeMergeEnum m)
126 {
127 change_merge = m;
128 }
129
end_change(void)130 void end_change(void)
131 {
132 prev_change_merge = change_merge;
133 }
134
begin_change_chain(void)135 void begin_change_chain(void)
136 {
137 BUG_ON(change_barrier);
138
139 // Allocate change chain barrier but add it to the change tree only if
140 // there will be any real changes
141 change_barrier = alloc_change();
142 change_merge = CHANGE_MERGE_NONE;
143 }
144
end_change_chain(void)145 void end_change_chain(void)
146 {
147 if (change_barrier) {
148 // There were no changes in this change chain.
149 free(change_barrier);
150 change_barrier = NULL;
151 } else {
152 // There were some changes. Add end of chain marker.
153 add_change(alloc_change());
154 }
155 }
156
fix_cursors(size_t offset,size_t del,size_t ins)157 static void fix_cursors(size_t offset, size_t del, size_t ins)
158 {
159 for (size_t i = 0, n = buffer->views.count; i < n; i++) {
160 View *v = buffer->views.ptrs[i];
161 if (v != view && offset < v->saved_cursor_offset) {
162 if (offset + del <= v->saved_cursor_offset) {
163 v->saved_cursor_offset -= del;
164 v->saved_cursor_offset += ins;
165 } else {
166 v->saved_cursor_offset = offset;
167 }
168 }
169 }
170 }
171
reverse_change(Change * change)172 static void reverse_change(Change *change)
173 {
174 if (buffer->views.count > 1) {
175 fix_cursors(change->offset, change->ins_count, change->del_count);
176 }
177
178 block_iter_goto_offset(&view->cursor, change->offset);
179 if (!change->ins_count) {
180 // Convert delete to insert
181 do_insert(change->buf, change->del_count);
182 if (change->move_after) {
183 block_iter_skip_bytes(&view->cursor, change->del_count);
184 }
185 change->ins_count = change->del_count;
186 change->del_count = 0;
187 free(change->buf);
188 change->buf = NULL;
189 } else if (change->del_count) {
190 // Reverse replace
191 size_t del_count = change->ins_count;
192 size_t ins_count = change->del_count;
193 char *buf = do_replace(del_count, change->buf, ins_count);
194
195 free(change->buf);
196 change->buf = buf;
197 change->ins_count = ins_count;
198 change->del_count = del_count;
199 } else {
200 // Convert insert to delete
201 change->buf = do_delete(change->ins_count);
202 change->del_count = change->ins_count;
203 change->ins_count = 0;
204 }
205 }
206
undo(void)207 bool undo(void)
208 {
209 Change *change = buffer->cur_change;
210
211 view_reset_preferred_x(view);
212 if (!change->next) {
213 return false;
214 }
215
216 if (is_change_chain_barrier(change)) {
217 unsigned long count = 0;
218
219 while (1) {
220 change = change->next;
221 if (is_change_chain_barrier(change)) {
222 break;
223 }
224 reverse_change(change);
225 count++;
226 }
227 if (count > 1) {
228 info_msg("Undid %lu changes.", count);
229 }
230 } else {
231 reverse_change(change);
232 }
233 buffer->cur_change = change->next;
234 return true;
235 }
236
redo(unsigned long change_id)237 bool redo(unsigned long change_id)
238 {
239 Change *change = buffer->cur_change;
240
241 view_reset_preferred_x(view);
242 if (!change->prev) {
243 // Don't complain if change_id is 0
244 if (change_id) {
245 error_msg("Nothing to redo.");
246 }
247 return false;
248 }
249
250 if (change_id) {
251 if (--change_id >= change->nr_prev) {
252 error_msg (
253 "There are only %lu possible changes to redo.",
254 change->nr_prev
255 );
256 return false;
257 }
258 } else {
259 // Default to newest change
260 change_id = change->nr_prev - 1;
261 if (change->nr_prev > 1) {
262 info_msg (
263 "Redoing newest (%lu) of %lu possible changes.",
264 change_id + 1,
265 change->nr_prev
266 );
267 }
268 }
269
270 change = change->prev[change_id];
271 if (is_change_chain_barrier(change)) {
272 unsigned long count = 0;
273
274 while (1) {
275 change = change->prev[change->nr_prev - 1];
276 if (is_change_chain_barrier(change)) {
277 break;
278 }
279 reverse_change(change);
280 count++;
281 }
282 if (count > 1) {
283 info_msg("Redid %lu changes.", count);
284 }
285 } else {
286 reverse_change(change);
287 }
288 buffer->cur_change = change;
289 return true;
290 }
291
free_changes(Change * ch)292 void free_changes(Change *ch)
293 {
294 top:
295 while (ch->nr_prev) {
296 ch = ch->prev[ch->nr_prev - 1];
297 }
298
299 // ch is leaf now
300 while (ch->next) {
301 Change *next = ch->next;
302
303 free(ch->buf);
304 free(ch);
305
306 ch = next;
307 if (--ch->nr_prev) {
308 goto top;
309 }
310
311 // We have become leaf
312 free(ch->prev);
313 }
314 }
315
buffer_insert_bytes(const char * buf,const size_t len)316 void buffer_insert_bytes(const char *buf, const size_t len)
317 {
318 size_t rec_len = len;
319
320 view_reset_preferred_x(view);
321 if (len == 0) {
322 return;
323 }
324
325 if (buf[len - 1] != '\n' && block_iter_is_eof(&view->cursor)) {
326 // Force newline at EOF
327 do_insert("\n", 1);
328 rec_len++;
329 }
330
331 do_insert(buf, len);
332 record_insert(rec_len);
333
334 if (buffer->views.count > 1) {
335 fix_cursors(block_iter_get_offset(&view->cursor), len, 0);
336 }
337 }
338
would_delete_last_bytes(size_t count)339 static bool would_delete_last_bytes(size_t count)
340 {
341 const Block *blk = view->cursor.blk;
342 size_t offset = view->cursor.offset;
343
344 while (1) {
345 size_t avail = blk->size - offset;
346
347 if (avail > count) {
348 return false;
349 }
350
351 if (blk->node.next == view->cursor.head) {
352 return true;
353 }
354
355 count -= avail;
356 blk = BLOCK(blk->node.next);
357 offset = 0;
358 }
359 }
360
buffer_delete_bytes_internal(size_t len,bool move_after)361 static void buffer_delete_bytes_internal(size_t len, bool move_after)
362 {
363 view_reset_preferred_x(view);
364 if (len == 0) {
365 return;
366 }
367
368 // Check if all newlines from EOF would be deleted
369 if (would_delete_last_bytes(len)) {
370 BlockIter bi = view->cursor;
371 CodePoint u;
372 if (block_iter_prev_char(&bi, &u) && u != '\n') {
373 // No newline before cursor
374 if (--len == 0) {
375 begin_change(CHANGE_MERGE_NONE);
376 return;
377 }
378 }
379 }
380 record_delete(do_delete(len), len, move_after);
381
382 if (buffer->views.count > 1) {
383 fix_cursors(block_iter_get_offset(&view->cursor), len, 0);
384 }
385 }
386
buffer_delete_bytes(size_t len)387 void buffer_delete_bytes(size_t len)
388 {
389 buffer_delete_bytes_internal(len, false);
390 }
391
buffer_erase_bytes(size_t len)392 void buffer_erase_bytes(size_t len)
393 {
394 buffer_delete_bytes_internal(len, true);
395 }
396
buffer_replace_bytes(size_t del_count,const char * const inserted,size_t ins_count)397 void buffer_replace_bytes (
398 size_t del_count,
399 const char *const inserted,
400 size_t ins_count
401 ) {
402 view_reset_preferred_x(view);
403 if (del_count == 0) {
404 buffer_insert_bytes(inserted, ins_count);
405 return;
406 }
407 if (ins_count == 0) {
408 buffer_delete_bytes(del_count);
409 return;
410 }
411
412 // Check if all newlines from EOF would be deleted
413 if (would_delete_last_bytes(del_count)) {
414 if (inserted[ins_count - 1] != '\n') {
415 // Don't replace last newline
416 if (--del_count == 0) {
417 buffer_insert_bytes(inserted, ins_count);
418 return;
419 }
420 }
421 }
422
423 char *deleted = do_replace(del_count, inserted, ins_count);
424 record_replace(deleted, del_count, ins_count);
425
426 if (buffer->views.count > 1) {
427 fix_cursors(block_iter_get_offset(&view->cursor), del_count, ins_count);
428 }
429 }
430