1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * This file is part of GtkSourceView
4 *
5 * Copyright (C) 1998, 1999 - Alex Roberts, Evan Lawrence
6 * Copyright (C) 2000, 2001 - Chema Celorio, Paolo Maggi
7 * Copyright (C) 2002-2005 - Paolo Maggi
8 * Copyright (C) 2014, 2015 - Sébastien Wilmet <swilmet@gnome.org>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this library; if not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include "gtksourceundomanagerdefault.h"
29 #include <string.h>
30 #include "gtksourceundomanager.h"
31
32 /* unlimited by default */
33 #define DEFAULT_MAX_UNDO_LEVELS -1
34
35 typedef struct _Action Action;
36 typedef struct _ActionGroup ActionGroup;
37
38 typedef enum _ActionType
39 {
40 ACTION_TYPE_INSERT,
41 ACTION_TYPE_DELETE
42 } ActionType;
43
44 /* A more precise deletion type. But currently it's only a guess, we are not
45 * 100% sure of the deletion type. To be sure, we would need to listen to key
46 * events on the GtkSourceView widget, which is more complicated than simply
47 * listening to the insert-text and delete-range GtkTextBuffer signals.
48 */
49 typedef enum _DeletionType
50 {
51 DELETION_TYPE_SELECTION_DELETED,
52 DELETION_TYPE_BACKSPACE_KEY,
53 DELETION_TYPE_DELETE_KEY,
54 DELETION_TYPE_PROGRAMMATICALLY
55 } DeletionType;
56
57 struct _Action
58 {
59 ActionType type;
60
61 /* Character offset for the start of @text in the GtkTextBuffer. */
62 gint start;
63
64 /* Character offset for the end of @text in the GtkTextBuffer. */
65 gint end;
66
67 /* Nul-terminated text.
68 * TODO A possible memory optimization is to store the text only when
69 * needed. For an insertion that is located in the history on the undo
70 * side, the text is not needed since it is already present in the
71 * buffer. The same for a deletion on the redo side. But the last action
72 * text is needed for the merging.
73 */
74 gchar *text;
75
76 /* Character offsets of the insert and selection bound marks.
77 * They are both -1 or they both match @start or @end.
78 * If the text cursor or the selected text is not related to the action,
79 * the selection is not stored (i.e. -1).
80 * If not -1, when undoing or redoing an action, the insert and
81 * selection bound marks are restored to where they were.
82 * For an insert, @selection_insert and @selection_bound must match
83 * @start, otherwise the selection or cursor position is unrelated to
84 * the insertion.
85 * For a deletion, if @selection_insert and @selection_bound are -1, it
86 * corresponds to DELETION_TYPE_PROGRAMMATICALLY. For all the other
87 * deletion types, the selection is stored.
88 */
89 gint selection_insert;
90 gint selection_bound;
91 };
92
93 struct _ActionGroup
94 {
95 /* One or several Action's that forms a single undo or redo step. The
96 * most recent action is at the end of the list.
97 * In fact, actions can be grouped with
98 * gtk_text_buffer_begin_user_action() and
99 * gtk_text_buffer_end_user_action().
100 */
101 GQueue *actions;
102
103 /* If force_not_mergeable is FALSE, there are dynamic checks to see if
104 * the action group is mergeable. For example if the saved_location is
105 * just after the action group, the action group is not mergeable, so
106 * the saved_location isn't lost.
107 */
108 guint force_not_mergeable : 1;
109 };
110
111 struct _GtkSourceUndoManagerDefaultPrivate
112 {
113 /* Weak ref to the buffer. */
114 GtkTextBuffer *buffer;
115
116 /* List of ActionGroup's. The most recent ActionGroup is at the end of
117 * the list.
118 */
119 GQueue *action_groups;
120
121 /* Current location in 'action_groups', where we are located in the
122 * history. The redo steps are on the right of the pointer, and the undo
123 * steps are on the left. In other words, the next redo step is
124 * location->data. The next undo step is location->prev->data. But the
125 * location should not be seen as a node, it should be seen as a
126 * vertical bar between two nodes, like a GtkTextIter between two
127 * characters.
128 */
129 GList *location;
130
131 /* A new ActionGroup that is created when some text is inserted or
132 * deleted in the buffer. As long as a user action is running (when
133 * 'running_user_action' is TRUE) the new actions are inserted into
134 * 'new_action_group'. When the user action ends, we try to merge
135 * 'new_action_group' with the previous ActionGroup in 'action_groups'
136 * (the node on the left of 'location'). If the merging fails, a new
137 * node is inserted on the left of 'location'.
138 */
139 ActionGroup *new_action_group;
140
141 /* The number of nested calls to
142 * gtk_source_buffer_begin_not_undoable_action().
143 */
144 guint running_not_undoable_actions;
145
146 /* Max number of action groups. */
147 gint max_undo_levels;
148
149 /* The location in 'action_groups' where the buffer is saved. I.e. when
150 * gtk_text_buffer_set_modified (buffer, FALSE) was called for the last
151 * time.
152 * NULL is for the end of 'action_groups'.
153 * 'has_saved_location' is FALSE if the history doesn't contain a saved
154 * location.
155 */
156 GList *saved_location;
157 guint has_saved_location : 1;
158
159 guint can_undo : 1;
160 guint can_redo : 1;
161
162 /* Whether we are between a begin-user-action and a end-user-action.
163 * Some operations, like undo and redo, are not allowed during a user
164 * action (it would screw up the history).
165 * At the beginning of a user action, a new action group is created. At
166 * the end of the user action, we try to merge the group with the
167 * previous one. So when an insertion or deletion occurs when
168 * running_user_action is TRUE, we don't need to create a new group. But
169 * when running_user_action is FALSE, we need to put the insertion or
170 * deletion into a new group and try to merge it directly with the
171 * previous group.
172 */
173 guint running_user_action : 1;
174 };
175
176 enum
177 {
178 PROP_0,
179 PROP_BUFFER,
180 PROP_MAX_UNDO_LEVELS
181 };
182
183 static void gtk_source_undo_manager_iface_init (GtkSourceUndoManagerIface *iface);
184
185 static gboolean action_merge (Action *action,
186 Action *new_action);
187
G_DEFINE_TYPE_WITH_CODE(GtkSourceUndoManagerDefault,gtk_source_undo_manager_default,G_TYPE_OBJECT,G_ADD_PRIVATE (GtkSourceUndoManagerDefault)G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_UNDO_MANAGER,gtk_source_undo_manager_iface_init))188 G_DEFINE_TYPE_WITH_CODE (GtkSourceUndoManagerDefault,
189 gtk_source_undo_manager_default,
190 G_TYPE_OBJECT,
191 G_ADD_PRIVATE (GtkSourceUndoManagerDefault)
192 G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_UNDO_MANAGER,
193 gtk_source_undo_manager_iface_init))
194
195 /* Utility functions */
196
197 static Action *
198 action_new (void)
199 {
200 Action *action;
201
202 action = g_slice_new0 (Action);
203
204 action->selection_insert = -1;
205 action->selection_bound = -1;
206
207 return action;
208 }
209
210 static void
action_free(Action * action)211 action_free (Action *action)
212 {
213 if (action != NULL)
214 {
215 g_free (action->text);
216 g_slice_free (Action, action);
217 }
218 }
219
220 static ActionGroup *
action_group_new(void)221 action_group_new (void)
222 {
223 ActionGroup *group;
224
225 group = g_slice_new (ActionGroup);
226 group->actions = g_queue_new ();
227 group->force_not_mergeable = FALSE;
228
229 return group;
230 }
231
232 static void
action_group_free(ActionGroup * group)233 action_group_free (ActionGroup *group)
234 {
235 if (group != NULL)
236 {
237 g_queue_free_full (group->actions, (GDestroyNotify) action_free);
238 g_slice_free (ActionGroup, group);
239 }
240 }
241
242 static void
update_can_undo_can_redo(GtkSourceUndoManagerDefault * manager)243 update_can_undo_can_redo (GtkSourceUndoManagerDefault *manager)
244 {
245 gboolean can_undo;
246 gboolean can_redo;
247
248 if (manager->priv->running_user_action)
249 {
250 can_undo = FALSE;
251 can_redo = FALSE;
252 }
253 else if (manager->priv->location != NULL)
254 {
255 can_undo = manager->priv->location->prev != NULL;
256 can_redo = TRUE;
257 }
258 else
259 {
260 can_undo = manager->priv->action_groups->tail != NULL;
261 can_redo = FALSE;
262 }
263
264 if (manager->priv->can_undo != can_undo)
265 {
266 manager->priv->can_undo = can_undo;
267 gtk_source_undo_manager_can_undo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
268 }
269
270 if (manager->priv->can_redo != can_redo)
271 {
272 manager->priv->can_redo = can_redo;
273 gtk_source_undo_manager_can_redo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
274 }
275 }
276
277 static void
clear_all(GtkSourceUndoManagerDefault * manager)278 clear_all (GtkSourceUndoManagerDefault *manager)
279 {
280 GList *l;
281
282 if (manager->priv->has_saved_location &&
283 manager->priv->saved_location != manager->priv->location)
284 {
285 manager->priv->has_saved_location = FALSE;
286 }
287
288 for (l = manager->priv->action_groups->head; l != NULL; l = l->next)
289 {
290 ActionGroup *group = l->data;
291 action_group_free (group);
292 }
293
294 g_queue_clear (manager->priv->action_groups);
295 manager->priv->location = NULL;
296 manager->priv->saved_location = NULL;
297
298 action_group_free (manager->priv->new_action_group);
299 manager->priv->new_action_group = NULL;
300
301 update_can_undo_can_redo (manager);
302 }
303
304 static void
remove_last_action_group(GtkSourceUndoManagerDefault * manager)305 remove_last_action_group (GtkSourceUndoManagerDefault *manager)
306 {
307 ActionGroup *group;
308
309 if (manager->priv->action_groups->length == 0)
310 {
311 return;
312 }
313
314 if (manager->priv->location == manager->priv->action_groups->tail)
315 {
316 manager->priv->location = NULL;
317 }
318
319 if (manager->priv->has_saved_location)
320 {
321 if (manager->priv->saved_location == NULL)
322 {
323 manager->priv->has_saved_location = FALSE;
324 }
325 else if (manager->priv->saved_location == manager->priv->action_groups->tail)
326 {
327 manager->priv->saved_location = NULL;
328 }
329 }
330
331 group = g_queue_pop_tail (manager->priv->action_groups);
332 action_group_free (group);
333 }
334
335 static void
remove_first_action_group(GtkSourceUndoManagerDefault * manager)336 remove_first_action_group (GtkSourceUndoManagerDefault *manager)
337 {
338 GList *first_node;
339 ActionGroup *group;
340
341 first_node = manager->priv->action_groups->head;
342
343 if (first_node == NULL)
344 {
345 return;
346 }
347
348 if (manager->priv->location == first_node)
349 {
350 manager->priv->location = first_node->next;
351 }
352
353 if (manager->priv->has_saved_location &&
354 manager->priv->saved_location == first_node)
355 {
356 manager->priv->has_saved_location = FALSE;
357 }
358
359 group = g_queue_pop_head (manager->priv->action_groups);
360 action_group_free (group);
361 }
362
363 static void
check_history_size(GtkSourceUndoManagerDefault * manager)364 check_history_size (GtkSourceUndoManagerDefault *manager)
365 {
366 if (manager->priv->max_undo_levels == -1)
367 {
368 return;
369 }
370
371 if (manager->priv->max_undo_levels == 0)
372 {
373 clear_all (manager);
374 return;
375 }
376
377 g_return_if_fail (manager->priv->max_undo_levels > 0);
378
379 while (manager->priv->action_groups->length > (guint)manager->priv->max_undo_levels)
380 {
381 /* Strip redo action groups first. */
382 if (manager->priv->location != NULL)
383 {
384 remove_last_action_group (manager);
385 }
386 else
387 {
388 remove_first_action_group (manager);
389 }
390 }
391
392 update_can_undo_can_redo (manager);
393 }
394
395 static void
remove_redo_action_groups(GtkSourceUndoManagerDefault * manager)396 remove_redo_action_groups (GtkSourceUndoManagerDefault *manager)
397 {
398 while (manager->priv->location != NULL)
399 {
400 remove_last_action_group (manager);
401 }
402 }
403
404 /* Try to merge @new_group into @group. Returns TRUE if merged. It is up to the
405 * caller to free @new_group.
406 */
407 static gboolean
action_group_merge(ActionGroup * group,ActionGroup * new_group)408 action_group_merge (ActionGroup *group,
409 ActionGroup *new_group)
410 {
411 Action *action;
412 Action *new_action;
413
414 g_assert (group != NULL);
415 g_assert (new_group != NULL);
416
417 if (new_group->actions->length == 0)
418 {
419 return TRUE;
420 }
421
422 if (group->force_not_mergeable ||
423 new_group->force_not_mergeable ||
424 group->actions->length > 1 ||
425 new_group->actions->length > 1)
426 {
427 return FALSE;
428 }
429
430 action = g_queue_peek_head (group->actions);
431 new_action = g_queue_peek_head (new_group->actions);
432
433 return action_merge (action, new_action);
434 }
435
436 /* Try to merge the new action group with the previous one (the one located on
437 * the left of priv->location). If the merge fails, a new node is inserted into
438 * the history.
439 */
440 static void
insert_new_action_group(GtkSourceUndoManagerDefault * manager)441 insert_new_action_group (GtkSourceUndoManagerDefault *manager)
442 {
443 GList *prev_node = NULL;
444 ActionGroup *prev_group = NULL;
445 ActionGroup *new_group = manager->priv->new_action_group;
446 gboolean can_merge = TRUE;
447
448 if (new_group == NULL || new_group->actions->length == 0)
449 {
450 return;
451 }
452
453 remove_redo_action_groups (manager);
454 g_assert (manager->priv->location == NULL);
455
456 prev_node = manager->priv->action_groups->tail;
457
458 if (prev_node != NULL)
459 {
460 prev_group = prev_node->data;
461
462 /* If the previous group is empty, it means that it was not correctly
463 * inserted into the history.
464 */
465 g_assert_cmpuint (prev_group->actions->length, >, 0);
466 }
467
468 /* If the saved_location is equal to the current location, the two
469 * ActionGroups cannot be merged, to not lose the saved_location.
470 */
471 if (manager->priv->has_saved_location &&
472 manager->priv->saved_location == manager->priv->location)
473 {
474 g_assert (manager->priv->saved_location == NULL);
475 can_merge = FALSE;
476 }
477
478 if (can_merge &&
479 prev_group != NULL &&
480 action_group_merge (prev_group, new_group))
481 {
482 /* new_group merged into prev_group */
483 action_group_free (manager->priv->new_action_group);
484 manager->priv->new_action_group = NULL;
485
486 update_can_undo_can_redo (manager);
487 return;
488 }
489
490 g_queue_push_tail (manager->priv->action_groups, new_group);
491 manager->priv->new_action_group = NULL;
492
493 if (manager->priv->has_saved_location &&
494 manager->priv->saved_location == NULL)
495 {
496 manager->priv->saved_location = manager->priv->action_groups->tail;
497 }
498
499 /* "Archive" prev_group. It will never be mergeable again. If the user
500 * does some undo's to return to this location, a new action won't be
501 * merged with an "archived" action group.
502 */
503 if (prev_group != NULL)
504 {
505 prev_group->force_not_mergeable = TRUE;
506 }
507
508 check_history_size (manager);
509 update_can_undo_can_redo (manager);
510 }
511
512 static void
insert_action(GtkSourceUndoManagerDefault * manager,Action * new_action)513 insert_action (GtkSourceUndoManagerDefault *manager,
514 Action *new_action)
515 {
516 ActionGroup *new_group;
517
518 g_assert (new_action != NULL);
519
520 if (manager->priv->new_action_group == NULL)
521 {
522 manager->priv->new_action_group = action_group_new ();
523 }
524
525 new_group = manager->priv->new_action_group;
526
527 /* Inside a group, don't try to merge the actions. It is needed to keep
528 * them separate so when undoing or redoing, the cursor position is set
529 * at the right place.
530 * For example with the search and replace, we replace all occurrences
531 * of 'a' by '' (i.e. delete all a's). The text "aaba" becomes "b". On
532 * undo, the cursor position should be placed at "a|aba", not "aa|ba"
533 * (but it's a detail).
534 */
535 g_queue_push_tail (new_group->actions, new_action);
536
537 /* An action is mergeable only for an insertion or deletion of a single
538 * character. If the text contains several characters, the new_action
539 * can for example come from a copy/paste.
540 */
541 if (new_action->end - new_action->start > 1 ||
542 g_str_equal (new_action->text, "\n"))
543 {
544 new_group->force_not_mergeable = TRUE;
545 }
546
547 if (!manager->priv->running_user_action)
548 {
549 insert_new_action_group (manager);
550 }
551 }
552
553 static void
delete_text(GtkTextBuffer * buffer,gint start,gint end)554 delete_text (GtkTextBuffer *buffer,
555 gint start,
556 gint end)
557 {
558 GtkTextIter start_iter;
559 GtkTextIter end_iter;
560
561 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
562 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
563
564 gtk_text_buffer_begin_user_action (buffer);
565 gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
566 gtk_text_buffer_end_user_action (buffer);
567 }
568
569 static void
insert_text(GtkTextBuffer * buffer,gint offset,const gchar * text)570 insert_text (GtkTextBuffer *buffer,
571 gint offset,
572 const gchar *text)
573 {
574 GtkTextIter iter;
575
576 gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset);
577
578 gtk_text_buffer_begin_user_action (buffer);
579 gtk_text_buffer_insert (buffer, &iter, text, -1);
580 gtk_text_buffer_end_user_action (buffer);
581 }
582
583 static gunichar
get_last_char(const gchar * text)584 get_last_char (const gchar *text)
585 {
586 gchar *pos;
587
588 pos = g_utf8_find_prev_char (text, text + strlen (text));
589
590 if (pos == NULL)
591 {
592 return '\0';
593 }
594
595 return g_utf8_get_char (pos);
596 }
597
598 /* ActionInsert implementation */
599
600 static void
action_insert_undo(GtkTextBuffer * buffer,Action * action)601 action_insert_undo (GtkTextBuffer *buffer,
602 Action *action)
603 {
604 g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
605
606 delete_text (buffer, action->start, action->end);
607 }
608
609 static void
action_insert_redo(GtkTextBuffer * buffer,Action * action)610 action_insert_redo (GtkTextBuffer *buffer,
611 Action *action)
612 {
613 g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
614
615 insert_text (buffer, action->start, action->text);
616 }
617
618 static gboolean
action_insert_merge(Action * action,Action * new_action)619 action_insert_merge (Action *action,
620 Action *new_action)
621 {
622 gint new_text_length;
623 gunichar new_char;
624 gunichar last_char;
625 gchar *merged_text;
626
627 g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
628 g_assert_cmpint (new_action->type, ==, ACTION_TYPE_INSERT);
629
630 new_text_length = new_action->end - new_action->start;
631 g_assert_cmpint (new_text_length, ==, 1);
632
633 new_char = g_utf8_get_char (new_action->text);
634 g_assert (new_char != '\n');
635
636 if (action->end != new_action->start)
637 {
638 return FALSE;
639 }
640
641 last_char = get_last_char (action->text);
642
643 /* If I type character by character the text "hello world", there will
644 * be two actions: "hello" and " world". If I click on undo, only
645 * "hello" remains, not the space. The space makes sense only when
646 * a second word is present.
647 * Note that the spaces or tabs at the beginning of a line (for code
648 * indentation) are removed with the first word of the line. For example
649 * if I type character by character " return FALSE;", there are two
650 * actions: " return" and " FALSE;". If I undo two times, maybe I still
651 * want the indentation. But with auto-indent, when we press Enter to
652 * create a newline, the indentation is part of the action that adds the
653 * newline, i.e. we have the three actions "\n ", "return" and
654 * " FALSE;".
655 */
656 if ((new_char == ' ' || new_char == '\t') &&
657 (last_char != ' ' && last_char != '\t'))
658 {
659 return FALSE;
660 }
661
662 merged_text = g_strdup_printf ("%s%s", action->text, new_action->text);
663
664 g_free (action->text);
665 action->text = merged_text;
666
667 action->end = new_action->end;
668
669 /* No need to update the selection, action->start is not modified. */
670 g_assert ((action->selection_insert == -1 &&
671 action->selection_bound == -1) ||
672 (action->selection_insert == action->start &&
673 action->selection_bound == action->start));
674
675 return TRUE;
676 }
677
678 static void
action_insert_restore_selection(GtkTextBuffer * buffer,Action * action,gboolean undo)679 action_insert_restore_selection (GtkTextBuffer *buffer,
680 Action *action,
681 gboolean undo)
682 {
683 GtkTextIter iter;
684
685 g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
686
687 /* No need to take into account action->selection_insert and
688 * action->selection_bound, because:
689 * - If they are both -1, we still want to place the cursor correctly,
690 * as done below, because if the cursor is not moved the user won't
691 * see the modification.
692 * - If they are set, their values are both action->start, so the undo
693 * works as expeceted in this case. The redo is also the expected
694 * behavior because after inserting a character the cursor is _after_
695 * the character, not before.
696 */
697
698 if (undo)
699 {
700 gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->start);
701 }
702 else /* redo */
703 {
704 gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->end);
705 }
706
707 gtk_text_buffer_place_cursor (buffer, &iter);
708 }
709
710 /* ActionDelete implementation */
711
712 static void
action_delete_undo(GtkTextBuffer * buffer,Action * action)713 action_delete_undo (GtkTextBuffer *buffer,
714 Action *action)
715 {
716 g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
717
718 insert_text (buffer, action->start, action->text);
719 }
720
721 static void
action_delete_redo(GtkTextBuffer * buffer,Action * action)722 action_delete_redo (GtkTextBuffer *buffer,
723 Action *action)
724 {
725 g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
726
727 delete_text (buffer, action->start, action->end);
728 }
729
730 static DeletionType
get_deletion_type(Action * action)731 get_deletion_type (Action *action)
732 {
733 g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
734
735 if (action->selection_insert == -1)
736 {
737 g_assert_cmpint (action->selection_bound, ==, -1);
738 return DELETION_TYPE_PROGRAMMATICALLY;
739 }
740
741 if (action->selection_insert == action->end &&
742 action->selection_bound == action->end)
743 {
744 return DELETION_TYPE_BACKSPACE_KEY;
745 }
746
747 if (action->selection_insert == action->start &&
748 action->selection_bound == action->start)
749 {
750 return DELETION_TYPE_DELETE_KEY;
751 }
752
753 g_assert (action->selection_insert == action->start ||
754 action->selection_insert == action->end);
755 g_assert (action->selection_bound == action->start ||
756 action->selection_bound == action->end);
757
758 return DELETION_TYPE_SELECTION_DELETED;
759 }
760
761 static gboolean
action_delete_merge(Action * action,Action * new_action)762 action_delete_merge (Action *action,
763 Action *new_action)
764 {
765 gint new_text_length;
766 gunichar new_char;
767 DeletionType deletion_type;
768 DeletionType new_deletion_type;
769
770 g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
771 g_assert_cmpint (new_action->type, ==, ACTION_TYPE_DELETE);
772
773 new_text_length = new_action->end - new_action->start;
774 g_assert_cmpint (new_text_length, ==, 1);
775
776 new_char = g_utf8_get_char (new_action->text);
777 g_assert (new_char != '\n');
778
779 deletion_type = get_deletion_type (action);
780 new_deletion_type = get_deletion_type (new_action);
781
782 if (deletion_type != new_deletion_type)
783 {
784 return FALSE;
785 }
786
787 switch (deletion_type)
788 {
789 /* If the user has selected some text and then has deleted it,
790 * it should be seen as a single action group, not mergeable. A
791 * good reason for that is to correctly restore the selection.
792 */
793 case DELETION_TYPE_SELECTION_DELETED:
794 return FALSE;
795
796 /* For memory use it would be better to take it into account,
797 * but the code is simpler like that.
798 */
799 case DELETION_TYPE_PROGRAMMATICALLY:
800 return FALSE;
801
802 /* Two Backspaces or two Deletes must follow each other. In
803 * "abc", if the cursor is at offset 2 and I press the Backspace
804 * key, then move the cursor after 'c' and press Backspace
805 * again, the two deletes won't be merged, since there was a
806 * cursor movement in between.
807 */
808
809 case DELETION_TYPE_DELETE_KEY:
810 /* Not consecutive deletes. */
811 if (action->start != new_action->start)
812 {
813 return FALSE;
814 }
815 break;
816
817 case DELETION_TYPE_BACKSPACE_KEY:
818 /* Not consecutive backspaces. */
819 if (action->start != new_action->end)
820 {
821 return FALSE;
822 }
823 break;
824
825 default:
826 g_assert_not_reached ();
827 }
828
829 /* Delete key pressed several times. */
830 if (action->start == new_action->start)
831 {
832 gunichar last_char;
833 gchar *merged_text;
834
835 last_char = get_last_char (action->text);
836
837 /* Same as action_insert_merge(). */
838 if ((new_char == ' ' || new_char == '\t') &&
839 (last_char != ' ' && last_char != '\t'))
840 {
841 return FALSE;
842 }
843
844 merged_text = g_strdup_printf ("%s%s", action->text, new_action->text);
845
846 g_free (action->text);
847 action->text = merged_text;
848
849 action->end += new_text_length;
850
851 /* No need to update the selection, action->start is not
852 * modified.
853 */
854 g_assert_cmpint (action->selection_insert, ==, action->start);
855 g_assert_cmpint (action->selection_bound, ==, action->start);
856
857 return TRUE;
858 }
859
860 /* Backspace key pressed several times. */
861 if (action->start == new_action->end)
862 {
863 gunichar last_char;
864 gchar *merged_text;
865
866 /* The last char deleted, but since it's with the Backspace key,
867 * it's the first char in action->text.
868 */
869 last_char = g_utf8_get_char (action->text);
870
871 /* Same as action_insert_merge(). */
872 if ((new_char != ' ' && new_char != '\t') &&
873 (last_char == ' ' || last_char == '\t'))
874 {
875 return FALSE;
876 }
877
878 merged_text = g_strdup_printf ("%s%s", new_action->text, action->text);
879
880 g_free (action->text);
881 action->text = merged_text;
882
883 action->start = new_action->start;
884
885 /* No need to update the selection, action->end is not modified. */
886 g_assert_cmpint (action->selection_insert, ==, action->end);
887 g_assert_cmpint (action->selection_bound, ==, action->end);
888
889 return TRUE;
890 }
891
892 g_assert_not_reached ();
893 return FALSE;
894 }
895
896 static void
action_delete_restore_selection(GtkTextBuffer * buffer,Action * action,gboolean undo)897 action_delete_restore_selection (GtkTextBuffer *buffer,
898 Action *action,
899 gboolean undo)
900 {
901
902 g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
903
904 if (undo)
905 {
906 if (action->selection_insert == -1)
907 {
908 GtkTextIter iter;
909
910 g_assert_cmpint (action->selection_bound, ==, -1);
911
912 gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->end);
913 gtk_text_buffer_place_cursor (buffer, &iter);
914 }
915 else
916 {
917 GtkTextIter insert_iter;
918 GtkTextIter bound_iter;
919
920 gtk_text_buffer_get_iter_at_offset (buffer,
921 &insert_iter,
922 action->selection_insert);
923
924 gtk_text_buffer_get_iter_at_offset (buffer,
925 &bound_iter,
926 action->selection_bound);
927
928 gtk_text_buffer_select_range (buffer, &insert_iter, &bound_iter);
929 }
930 }
931 else /* redo */
932 {
933 GtkTextIter iter;
934
935 gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->start);
936 gtk_text_buffer_place_cursor (buffer, &iter);
937 }
938 }
939
940 /* Action interface.
941 * The Action struct can be seen as an interface. All the explicit case analysis
942 * on the action type are grouped in this code section. This can easily be
943 * modified as an object-oriented architecture with polymorphism.
944 */
945
946 static void
action_undo(GtkTextBuffer * buffer,Action * action)947 action_undo (GtkTextBuffer *buffer,
948 Action *action)
949 {
950 g_assert (action != NULL);
951
952 switch (action->type)
953 {
954 case ACTION_TYPE_INSERT:
955 action_insert_undo (buffer, action);
956 break;
957
958 case ACTION_TYPE_DELETE:
959 action_delete_undo (buffer, action);
960 break;
961
962 default:
963 g_return_if_reached ();
964 break;
965 }
966 }
967
968 static void
action_redo(GtkTextBuffer * buffer,Action * action)969 action_redo (GtkTextBuffer *buffer,
970 Action *action)
971 {
972 g_assert (action != NULL);
973
974 switch (action->type)
975 {
976 case ACTION_TYPE_INSERT:
977 action_insert_redo (buffer, action);
978 break;
979
980 case ACTION_TYPE_DELETE:
981 action_delete_redo (buffer, action);
982 break;
983
984 default:
985 g_return_if_reached ();
986 break;
987 }
988 }
989
990 /* Try to merge @new_action into @action. Returns TRUE if merged. It is up to
991 * the caller to free @new_action if needed.
992 */
993 static gboolean
action_merge(Action * action,Action * new_action)994 action_merge (Action *action,
995 Action *new_action)
996 {
997 g_assert (action != NULL);
998 g_assert (new_action != NULL);
999
1000 if (action->type != new_action->type)
1001 {
1002 return FALSE;
1003 }
1004
1005 switch (action->type)
1006 {
1007 case ACTION_TYPE_INSERT:
1008 return action_insert_merge (action, new_action);
1009
1010 case ACTION_TYPE_DELETE:
1011 return action_delete_merge (action, new_action);
1012
1013 default:
1014 g_return_val_if_reached (FALSE);
1015 break;
1016 }
1017 }
1018
1019 /* Restore the selection (or cursor position) according to @action.
1020 * If @undo is TRUE, @action has just been undone. If @undo is FALSE, @action
1021 * has just been redone.
1022 */
1023 static void
action_restore_selection(GtkTextBuffer * buffer,Action * action,gboolean undo)1024 action_restore_selection (GtkTextBuffer *buffer,
1025 Action *action,
1026 gboolean undo)
1027 {
1028 g_assert (action != NULL);
1029
1030 switch (action->type)
1031 {
1032 case ACTION_TYPE_INSERT:
1033 action_insert_restore_selection (buffer, action, undo);
1034 break;
1035
1036 case ACTION_TYPE_DELETE:
1037 action_delete_restore_selection (buffer, action, undo);
1038 break;
1039
1040 default:
1041 g_return_if_reached ();
1042 break;
1043 }
1044 }
1045
1046 /* Buffer signal handlers */
1047
1048 static void
set_selection_bounds(GtkTextBuffer * buffer,Action * action)1049 set_selection_bounds (GtkTextBuffer *buffer,
1050 Action *action)
1051 {
1052 GtkTextMark *insert_mark;
1053 GtkTextMark *bound_mark;
1054 GtkTextIter insert_iter;
1055 GtkTextIter bound_iter;
1056
1057 insert_mark = gtk_text_buffer_get_insert (buffer);
1058 bound_mark = gtk_text_buffer_get_selection_bound (buffer);
1059
1060 gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, insert_mark);
1061 gtk_text_buffer_get_iter_at_mark (buffer, &bound_iter, bound_mark);
1062
1063 action->selection_insert = gtk_text_iter_get_offset (&insert_iter);
1064 action->selection_bound = gtk_text_iter_get_offset (&bound_iter);
1065 }
1066
1067 static void
insert_text_cb(GtkTextBuffer * buffer,GtkTextIter * location,const gchar * text,gint length,GtkSourceUndoManagerDefault * manager)1068 insert_text_cb (GtkTextBuffer *buffer,
1069 GtkTextIter *location,
1070 const gchar *text,
1071 gint length,
1072 GtkSourceUndoManagerDefault *manager)
1073 {
1074 Action *action = action_new ();
1075
1076 action->type = ACTION_TYPE_INSERT;
1077 action->start = gtk_text_iter_get_offset (location);
1078 action->text = g_strndup (text, length);
1079 action->end = action->start + g_utf8_strlen (action->text, -1);
1080
1081 set_selection_bounds (buffer, action);
1082
1083 if (action->selection_insert != action->selection_bound ||
1084 action->selection_insert != action->start)
1085 {
1086 action->selection_insert = -1;
1087 action->selection_bound = -1;
1088 }
1089 else
1090 {
1091 /* The insertion occurred at the cursor. */
1092 g_assert_cmpint (action->selection_insert, ==, action->start);
1093 g_assert_cmpint (action->selection_bound, ==, action->start);
1094 }
1095
1096 insert_action (manager, action);
1097 }
1098
1099 static void
delete_range_cb(GtkTextBuffer * buffer,GtkTextIter * start,GtkTextIter * end,GtkSourceUndoManagerDefault * manager)1100 delete_range_cb (GtkTextBuffer *buffer,
1101 GtkTextIter *start,
1102 GtkTextIter *end,
1103 GtkSourceUndoManagerDefault *manager)
1104 {
1105 Action *action = action_new ();
1106
1107 action->type = ACTION_TYPE_DELETE;
1108 action->start = gtk_text_iter_get_offset (start);
1109 action->end = gtk_text_iter_get_offset (end);
1110 action->text = gtk_text_buffer_get_slice (buffer, start, end, TRUE);
1111
1112 g_assert_cmpint (action->start, <, action->end);
1113
1114 set_selection_bounds (buffer, action);
1115
1116 if ((action->selection_insert != action->start &&
1117 action->selection_insert != action->end) ||
1118 (action->selection_bound != action->start &&
1119 action->selection_bound != action->end))
1120 {
1121 action->selection_insert = -1;
1122 action->selection_bound = -1;
1123 }
1124
1125 insert_action (manager, action);
1126 }
1127
1128 static void
begin_user_action_cb(GtkTextBuffer * buffer,GtkSourceUndoManagerDefault * manager)1129 begin_user_action_cb (GtkTextBuffer *buffer,
1130 GtkSourceUndoManagerDefault *manager)
1131 {
1132 manager->priv->running_user_action = TRUE;
1133 update_can_undo_can_redo (manager);
1134 }
1135
1136 static void
end_user_action_cb(GtkTextBuffer * buffer,GtkSourceUndoManagerDefault * manager)1137 end_user_action_cb (GtkTextBuffer *buffer,
1138 GtkSourceUndoManagerDefault *manager)
1139 {
1140 insert_new_action_group (manager);
1141
1142 manager->priv->running_user_action = FALSE;
1143 update_can_undo_can_redo (manager);
1144 }
1145
1146 static void
modified_changed_cb(GtkTextBuffer * buffer,GtkSourceUndoManagerDefault * manager)1147 modified_changed_cb (GtkTextBuffer *buffer,
1148 GtkSourceUndoManagerDefault *manager)
1149 {
1150 if (gtk_text_buffer_get_modified (buffer))
1151 {
1152 /* It can happen for example when the file on disk has been
1153 * deleted.
1154 */
1155 if (manager->priv->has_saved_location &&
1156 manager->priv->saved_location == manager->priv->location &&
1157 (manager->priv->new_action_group == NULL ||
1158 manager->priv->new_action_group->actions->length == 0))
1159 {
1160 manager->priv->has_saved_location = FALSE;
1161 }
1162 }
1163
1164 /* saved */
1165 else
1166 {
1167 /* Saving a buffer during a user action is allowed, the user
1168 * action is split.
1169 * FIXME and/or a warning should be printed?
1170 */
1171 if (manager->priv->running_user_action)
1172 {
1173 insert_new_action_group (manager);
1174 }
1175
1176 manager->priv->saved_location = manager->priv->location;
1177 manager->priv->has_saved_location = TRUE;
1178 }
1179 }
1180
1181 static void
block_signal_handlers(GtkSourceUndoManagerDefault * manager)1182 block_signal_handlers (GtkSourceUndoManagerDefault *manager)
1183 {
1184 if (manager->priv->buffer == NULL)
1185 {
1186 return;
1187 }
1188
1189 g_signal_handlers_block_by_func (manager->priv->buffer,
1190 insert_text_cb,
1191 manager);
1192
1193 g_signal_handlers_block_by_func (manager->priv->buffer,
1194 delete_range_cb,
1195 manager);
1196
1197 g_signal_handlers_block_by_func (manager->priv->buffer,
1198 modified_changed_cb,
1199 manager);
1200 }
1201
1202 static void
unblock_signal_handlers(GtkSourceUndoManagerDefault * manager)1203 unblock_signal_handlers (GtkSourceUndoManagerDefault *manager)
1204 {
1205 if (manager->priv->buffer == NULL)
1206 {
1207 return;
1208 }
1209
1210 g_signal_handlers_unblock_by_func (manager->priv->buffer,
1211 insert_text_cb,
1212 manager);
1213
1214 g_signal_handlers_unblock_by_func (manager->priv->buffer,
1215 delete_range_cb,
1216 manager);
1217
1218 g_signal_handlers_unblock_by_func (manager->priv->buffer,
1219 modified_changed_cb,
1220 manager);
1221 }
1222
1223 static void
set_buffer(GtkSourceUndoManagerDefault * manager,GtkTextBuffer * buffer)1224 set_buffer (GtkSourceUndoManagerDefault *manager,
1225 GtkTextBuffer *buffer)
1226 {
1227 g_assert (manager->priv->buffer == NULL);
1228
1229 if (buffer == NULL)
1230 {
1231 return;
1232 }
1233
1234 manager->priv->buffer = buffer;
1235
1236 g_object_add_weak_pointer (G_OBJECT (buffer),
1237 (gpointer *)&manager->priv->buffer);
1238
1239 g_signal_connect_object (buffer,
1240 "insert-text",
1241 G_CALLBACK (insert_text_cb),
1242 manager,
1243 0);
1244
1245 g_signal_connect_object (buffer,
1246 "delete-range",
1247 G_CALLBACK (delete_range_cb),
1248 manager,
1249 0);
1250
1251 g_signal_connect_object (buffer,
1252 "begin-user-action",
1253 G_CALLBACK (begin_user_action_cb),
1254 manager,
1255 0);
1256
1257 g_signal_connect_object (buffer,
1258 "end-user-action",
1259 G_CALLBACK (end_user_action_cb),
1260 manager,
1261 0);
1262
1263 g_signal_connect_object (buffer,
1264 "modified-changed",
1265 G_CALLBACK (modified_changed_cb),
1266 manager,
1267 0);
1268
1269 modified_changed_cb (manager->priv->buffer, manager);
1270 }
1271
1272 /* GObject construction, destruction and properties */
1273
1274 static void
gtk_source_undo_manager_default_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1275 gtk_source_undo_manager_default_set_property (GObject *object,
1276 guint prop_id,
1277 const GValue *value,
1278 GParamSpec *pspec)
1279 {
1280 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
1281
1282 switch (prop_id)
1283 {
1284 case PROP_BUFFER:
1285 set_buffer (manager, g_value_get_object (value));
1286 break;
1287
1288 case PROP_MAX_UNDO_LEVELS:
1289 gtk_source_undo_manager_default_set_max_undo_levels (manager, g_value_get_int (value));
1290 break;
1291
1292 default:
1293 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1294 break;
1295 }
1296 }
1297
1298 static void
gtk_source_undo_manager_default_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1299 gtk_source_undo_manager_default_get_property (GObject *object,
1300 guint prop_id,
1301 GValue *value,
1302 GParamSpec *pspec)
1303 {
1304 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
1305
1306 switch (prop_id)
1307 {
1308 case PROP_BUFFER:
1309 g_value_set_object (value, manager->priv->buffer);
1310 break;
1311
1312 case PROP_MAX_UNDO_LEVELS:
1313 g_value_set_int (value, manager->priv->max_undo_levels);
1314 break;
1315
1316 default:
1317 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1318 break;
1319 }
1320 }
1321
1322 static void
gtk_source_undo_manager_default_dispose(GObject * object)1323 gtk_source_undo_manager_default_dispose (GObject *object)
1324 {
1325 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
1326
1327 if (manager->priv->buffer != NULL)
1328 {
1329 g_object_remove_weak_pointer (G_OBJECT (manager->priv->buffer),
1330 (gpointer *)&manager->priv->buffer);
1331
1332 manager->priv->buffer = NULL;
1333 }
1334
1335 G_OBJECT_CLASS (gtk_source_undo_manager_default_parent_class)->dispose (object);
1336 }
1337
1338 static void
gtk_source_undo_manager_default_finalize(GObject * object)1339 gtk_source_undo_manager_default_finalize (GObject *object)
1340 {
1341 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
1342
1343 g_queue_free_full (manager->priv->action_groups,
1344 (GDestroyNotify) action_group_free);
1345
1346 action_group_free (manager->priv->new_action_group);
1347
1348 G_OBJECT_CLASS (gtk_source_undo_manager_default_parent_class)->finalize (object);
1349 }
1350
1351 static void
gtk_source_undo_manager_default_class_init(GtkSourceUndoManagerDefaultClass * klass)1352 gtk_source_undo_manager_default_class_init (GtkSourceUndoManagerDefaultClass *klass)
1353 {
1354 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1355
1356 object_class->set_property = gtk_source_undo_manager_default_set_property;
1357 object_class->get_property = gtk_source_undo_manager_default_get_property;
1358 object_class->dispose = gtk_source_undo_manager_default_dispose;
1359 object_class->finalize = gtk_source_undo_manager_default_finalize;
1360
1361 g_object_class_install_property (object_class,
1362 PROP_BUFFER,
1363 g_param_spec_object ("buffer",
1364 "Buffer",
1365 "The text buffer to add undo support on",
1366 GTK_TYPE_TEXT_BUFFER,
1367 G_PARAM_READWRITE |
1368 G_PARAM_CONSTRUCT_ONLY |
1369 G_PARAM_STATIC_STRINGS));
1370
1371 g_object_class_install_property (object_class,
1372 PROP_MAX_UNDO_LEVELS,
1373 g_param_spec_int ("max-undo-levels",
1374 "Max Undo Levels",
1375 "Number of undo levels for the buffer",
1376 -1,
1377 G_MAXINT,
1378 DEFAULT_MAX_UNDO_LEVELS,
1379 G_PARAM_READWRITE |
1380 G_PARAM_STATIC_STRINGS));
1381 }
1382
1383 static void
gtk_source_undo_manager_default_init(GtkSourceUndoManagerDefault * manager)1384 gtk_source_undo_manager_default_init (GtkSourceUndoManagerDefault *manager)
1385 {
1386 manager->priv = gtk_source_undo_manager_default_get_instance_private (manager);
1387
1388 manager->priv->action_groups = g_queue_new ();
1389 manager->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
1390 }
1391
1392 /* Interface implementation */
1393
1394 static gboolean
gtk_source_undo_manager_can_undo_impl(GtkSourceUndoManager * undo_manager)1395 gtk_source_undo_manager_can_undo_impl (GtkSourceUndoManager *undo_manager)
1396 {
1397 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
1398 return manager->priv->can_undo;
1399 }
1400
1401 static gboolean
gtk_source_undo_manager_can_redo_impl(GtkSourceUndoManager * undo_manager)1402 gtk_source_undo_manager_can_redo_impl (GtkSourceUndoManager *undo_manager)
1403 {
1404 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
1405 return manager->priv->can_redo;
1406 }
1407
1408 static void
restore_modified_state(GtkSourceUndoManagerDefault * manager,GList * old_location,GList * new_location)1409 restore_modified_state (GtkSourceUndoManagerDefault *manager,
1410 GList *old_location,
1411 GList *new_location)
1412 {
1413 if (manager->priv->has_saved_location)
1414 {
1415 if (old_location == manager->priv->saved_location)
1416 {
1417 gtk_text_buffer_set_modified (manager->priv->buffer, TRUE);
1418 }
1419 else if (new_location == manager->priv->saved_location)
1420 {
1421 gtk_text_buffer_set_modified (manager->priv->buffer, FALSE);
1422 }
1423 }
1424 }
1425
1426 static void
gtk_source_undo_manager_undo_impl(GtkSourceUndoManager * undo_manager)1427 gtk_source_undo_manager_undo_impl (GtkSourceUndoManager *undo_manager)
1428 {
1429 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
1430 GList *old_location;
1431 GList *new_location;
1432 ActionGroup *group;
1433 Action *action;
1434 GList *l;
1435
1436 g_return_if_fail (manager->priv->can_undo);
1437
1438 old_location = manager->priv->location;
1439
1440 if (old_location != NULL)
1441 {
1442 new_location = manager->priv->location->prev;
1443 }
1444 else
1445 {
1446 new_location = manager->priv->action_groups->tail;
1447 }
1448
1449 g_assert (new_location != NULL);
1450
1451 group = new_location->data;
1452 g_assert_cmpuint (group->actions->length, >, 0);
1453
1454 block_signal_handlers (manager);
1455
1456 for (l = group->actions->tail; l != NULL; l = l->prev)
1457 {
1458 action = l->data;
1459 action_undo (manager->priv->buffer, action);
1460 }
1461
1462 restore_modified_state (manager, old_location, new_location);
1463
1464 /* After an undo, place the cursor at the first action in the group. For
1465 * a search and replace, it will be the first occurrence in the buffer.
1466 */
1467 action = g_queue_peek_head (group->actions);
1468 action_restore_selection (manager->priv->buffer, action, TRUE);
1469
1470 unblock_signal_handlers (manager);
1471
1472 manager->priv->location = new_location;
1473 update_can_undo_can_redo (manager);
1474 }
1475
1476 static void
gtk_source_undo_manager_redo_impl(GtkSourceUndoManager * undo_manager)1477 gtk_source_undo_manager_redo_impl (GtkSourceUndoManager *undo_manager)
1478 {
1479 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
1480 GList *old_location;
1481 GList *new_location;
1482 ActionGroup *group;
1483 GList *l;
1484
1485 g_return_if_fail (manager->priv->can_redo);
1486
1487 old_location = manager->priv->location;
1488 g_assert (old_location != NULL);
1489
1490 new_location = old_location->next;
1491
1492 group = old_location->data;
1493
1494 block_signal_handlers (manager);
1495
1496 for (l = group->actions->head; l != NULL; l = l->next)
1497 {
1498 Action *action = l->data;
1499 action_redo (manager->priv->buffer, action);
1500
1501 /* For a redo, place the cursor at the first action in the
1502 * group. For an undo the first action is also chosen, so when
1503 * undoing/redoing a search and replace, the cursor position
1504 * stays at the first occurrence and the user can see the
1505 * replacement easily.
1506 * For a redo, if we choose the last action in the group, when
1507 * undoing/redoing a search and replace, the cursor position
1508 * will jump between the first occurrence and the last
1509 * occurrence. Staying at the same place is probably better.
1510 */
1511 if (l == group->actions->head)
1512 {
1513 action_restore_selection (manager->priv->buffer, action, FALSE);
1514 }
1515 }
1516
1517 restore_modified_state (manager, old_location, new_location);
1518
1519 unblock_signal_handlers (manager);
1520
1521 manager->priv->location = new_location;
1522 update_can_undo_can_redo (manager);
1523 }
1524
1525 static void
gtk_source_undo_manager_begin_not_undoable_action_impl(GtkSourceUndoManager * undo_manager)1526 gtk_source_undo_manager_begin_not_undoable_action_impl (GtkSourceUndoManager *undo_manager)
1527 {
1528 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
1529 manager->priv->running_not_undoable_actions++;
1530
1531 if (manager->priv->running_not_undoable_actions == 1)
1532 {
1533 block_signal_handlers (manager);
1534 }
1535 }
1536
1537 static void
gtk_source_undo_manager_end_not_undoable_action_impl(GtkSourceUndoManager * undo_manager)1538 gtk_source_undo_manager_end_not_undoable_action_impl (GtkSourceUndoManager *undo_manager)
1539 {
1540 GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
1541
1542 g_return_if_fail (manager->priv->running_not_undoable_actions > 0);
1543
1544 manager->priv->running_not_undoable_actions--;
1545
1546 if (manager->priv->running_not_undoable_actions == 0)
1547 {
1548 unblock_signal_handlers (manager);
1549 clear_all (manager);
1550 modified_changed_cb (manager->priv->buffer, manager);
1551 }
1552 }
1553
1554 static void
gtk_source_undo_manager_iface_init(GtkSourceUndoManagerIface * iface)1555 gtk_source_undo_manager_iface_init (GtkSourceUndoManagerIface *iface)
1556 {
1557 iface->can_undo = gtk_source_undo_manager_can_undo_impl;
1558 iface->can_redo = gtk_source_undo_manager_can_redo_impl;
1559 iface->undo = gtk_source_undo_manager_undo_impl;
1560 iface->redo = gtk_source_undo_manager_redo_impl;
1561 iface->begin_not_undoable_action = gtk_source_undo_manager_begin_not_undoable_action_impl;
1562 iface->end_not_undoable_action = gtk_source_undo_manager_end_not_undoable_action_impl;
1563 }
1564
1565 /* Public functions */
1566
1567 void
gtk_source_undo_manager_default_set_max_undo_levels(GtkSourceUndoManagerDefault * manager,gint max_undo_levels)1568 gtk_source_undo_manager_default_set_max_undo_levels (GtkSourceUndoManagerDefault *manager,
1569 gint max_undo_levels)
1570 {
1571 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER_DEFAULT (manager));
1572 g_return_if_fail (max_undo_levels >= -1);
1573
1574 if (manager->priv->max_undo_levels != max_undo_levels)
1575 {
1576 if (max_undo_levels == 0)
1577 {
1578 /* disable the undo manager */
1579 block_signal_handlers (manager);
1580 }
1581 else if (manager->priv->max_undo_levels == 0)
1582 {
1583 unblock_signal_handlers (manager);
1584 modified_changed_cb (manager->priv->buffer, manager);
1585 }
1586
1587 manager->priv->max_undo_levels = max_undo_levels;
1588 check_history_size (manager);
1589
1590 g_object_notify (G_OBJECT (manager), "max-undo-levels");
1591 }
1592 }
1593