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