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