1 /* Copyright (C) 2019 Red Hat, Inc.
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "config.h"
18 
19 #include "gtkistringprivate.h"
20 #include "gtktexthistoryprivate.h"
21 
22 /*
23  * The GtkTextHistory works in a way that allows text widgets to deliver
24  * information about changes to the underlying text at given offsets within
25  * their text. The GtkTextHistory object uses a series of callback functions
26  * (see GtkTextHistoryFuncs) to apply changes as undo/redo is performed.
27  *
28  * The GtkTextHistory object is careful to avoid tracking changes while
29  * applying specific undo/redo actions.
30  *
31  * Changes are tracked within a series of actions, contained in groups.  The
32  * group may be coalesced when gtk_text_history_end_user_action() is
33  * called.
34  *
35  * Calling gtk_text_history_begin_irreversible_action() and
36  * gtk_text_history_end_irreversible_action() can be used to denote a
37  * section of operations that cannot be undone. This will cause all previous
38  * changes tracked by the GtkTextHistory to be discarded.
39  */
40 
41 typedef struct _Action     Action;
42 typedef enum   _ActionKind ActionKind;
43 
44 enum _ActionKind
45 {
46   ACTION_KIND_BARRIER             = 1,
47   ACTION_KIND_DELETE_BACKSPACE    = 2,
48   ACTION_KIND_DELETE_KEY          = 3,
49   ACTION_KIND_DELETE_PROGRAMMATIC = 4,
50   ACTION_KIND_DELETE_SELECTION    = 5,
51   ACTION_KIND_GROUP               = 6,
52   ACTION_KIND_INSERT              = 7,
53 };
54 
55 struct _Action
56 {
57   ActionKind kind;
58   GList link;
59   guint is_modified : 1;
60   guint is_modified_set : 1;
61   union {
62     struct {
63       IString istr;
64       guint begin;
65       guint end;
66     } insert;
67     struct {
68       IString istr;
69       guint begin;
70       guint end;
71       struct {
72         int insert;
73         int bound;
74       } selection;
75     } delete;
76     struct {
77       GQueue actions;
78       guint  depth;
79     } group;
80   } u;
81 };
82 
83 struct _GtkTextHistory
84 {
85   GObject             parent_instance;
86 
87   GtkTextHistoryFuncs funcs;
88   gpointer            funcs_data;
89 
90   GQueue              undo_queue;
91   GQueue              redo_queue;
92 
93   struct {
94     int insert;
95     int bound;
96   } selection;
97 
98   guint               irreversible;
99   guint               in_user;
100   guint               max_undo_levels;
101 
102   guint               can_undo : 1;
103   guint               can_redo : 1;
104   guint               is_modified : 1;
105   guint               is_modified_set : 1;
106   guint               applying : 1;
107   guint               enabled : 1;
108 };
109 
110 static void action_free (Action *action);
111 
G_DEFINE_TYPE(GtkTextHistory,gtk_text_history,G_TYPE_OBJECT)112 G_DEFINE_TYPE (GtkTextHistory, gtk_text_history, G_TYPE_OBJECT)
113 
114 #define return_if_applying(instance)     \
115   G_STMT_START {                         \
116     if ((instance)->applying)            \
117       return;                            \
118   } G_STMT_END
119 #define return_if_irreversible(instance) \
120   G_STMT_START {                         \
121     if ((instance)->irreversible)        \
122       return;                            \
123   } G_STMT_END
124 #define return_if_not_enabled(instance)  \
125   G_STMT_START {                         \
126     if (!(instance)->enabled)            \
127       return;                            \
128   } G_STMT_END
129 
130 static inline void
131 uint_order (guint *a,
132             guint *b)
133 {
134   if (*a > *b)
135     {
136       guint tmp = *a;
137       *a = *b;
138       *b = tmp;
139     }
140 }
141 
142 static void
clear_action_queue(GQueue * queue)143 clear_action_queue (GQueue *queue)
144 {
145   g_assert (queue != NULL);
146 
147   while (queue->length > 0)
148     {
149       Action *action = g_queue_peek_head (queue);
150       g_queue_unlink (queue, &action->link);
151       action_free (action);
152     }
153 }
154 
155 static Action *
action_new(ActionKind kind)156 action_new (ActionKind kind)
157 {
158   Action *action;
159 
160   action = g_slice_new0 (Action);
161   action->kind = kind;
162   action->link.data = action;
163 
164   return action;
165 }
166 
167 static void
action_free(Action * action)168 action_free (Action *action)
169 {
170   if (action->kind == ACTION_KIND_INSERT)
171     istring_clear (&action->u.insert.istr);
172   else if (action->kind == ACTION_KIND_DELETE_BACKSPACE ||
173            action->kind == ACTION_KIND_DELETE_KEY ||
174            action->kind == ACTION_KIND_DELETE_PROGRAMMATIC ||
175            action->kind == ACTION_KIND_DELETE_SELECTION)
176     istring_clear (&action->u.delete.istr);
177   else if (action->kind == ACTION_KIND_GROUP)
178     clear_action_queue (&action->u.group.actions);
179 
180   g_slice_free (Action, action);
181 }
182 
183 static gboolean
action_group_is_empty(const Action * action)184 action_group_is_empty (const Action *action)
185 {
186   const GList *iter;
187 
188   g_assert (action->kind == ACTION_KIND_GROUP);
189 
190   for (iter = action->u.group.actions.head; iter; iter = iter->next)
191     {
192       const Action *child = iter->data;
193 
194       if (child->kind == ACTION_KIND_BARRIER)
195         continue;
196 
197       if (child->kind == ACTION_KIND_GROUP && action_group_is_empty (child))
198         continue;
199 
200       return FALSE;
201     }
202 
203   return TRUE;
204 }
205 
206 static gboolean
action_chain(Action * action,Action * other,gboolean in_user_action)207 action_chain (Action   *action,
208               Action   *other,
209               gboolean  in_user_action)
210 {
211   g_assert (action != NULL);
212   g_assert (other != NULL);
213 
214   if (action->kind == ACTION_KIND_GROUP)
215     {
216       Action *tail = g_queue_peek_tail (&action->u.group.actions);
217 
218       /* Always push new items onto a group, so that we can coalesce
219        * items when gtk_text_history_end_user_action() is called.
220        *
221        * But we don't care if this is a barrier since we will always
222        * apply things as a group anyway.
223        */
224 
225       if (other->kind == ACTION_KIND_BARRIER)
226         {
227           /* If we're not in a user action, this barrier is meant to
228            * stop items from coallescing into this group.
229            */
230           if (!in_user_action && action->u.group.depth == 0)
231             return FALSE;
232 
233           action_free (other);
234           return TRUE;
235         }
236 
237       /* Try to chain onto the tail item in the group to increase
238        * the chances we have a single action within the group. That
239        * way we are more likely to hoist out of the group when the
240        * user action is ended.
241        */
242       if (tail != NULL && tail->kind == other->kind)
243         {
244           if (action_chain (tail, other, in_user_action))
245             return TRUE;
246         }
247 
248       g_queue_push_tail_link (&action->u.group.actions, &other->link);
249 
250       return TRUE;
251     }
252 
253   /* The rest can only be merged to themselves */
254   if (action->kind != other->kind)
255     return FALSE;
256 
257   switch (action->kind)
258     {
259     case ACTION_KIND_INSERT: {
260 
261       /* Make sure the new insert is at the end of the previous */
262       if (action->u.insert.end != other->u.insert.begin)
263         return FALSE;
264 
265       /* If we are not within a user action, be more selective */
266       if (!in_user_action)
267         {
268           /* Avoid pathological cases */
269           if (other->u.insert.istr.n_chars > 1000)
270             return FALSE;
271 
272           /* We will coalesce space, but not new lines. */
273           if (istring_contains_unichar (&action->u.insert.istr, '\n') ||
274               istring_contains_unichar (&other->u.insert.istr, '\n'))
275             return FALSE;
276 
277           /* Chain space to items that ended in space. This is generally
278            * just at the start of a line where we could have indentation
279            * space.
280            */
281           if ((istring_empty (&action->u.insert.istr) ||
282                istring_ends_with_space (&action->u.insert.istr)) &&
283               istring_only_contains_space (&other->u.insert.istr))
284             goto do_chain;
285 
286           /* Starting a new word, don't chain this */
287           if (istring_starts_with_space (&other->u.insert.istr))
288             return FALSE;
289 
290           /* Check for possible paste (multi-character input) or word input that
291            * has spaces in it (and should treat as one operation).
292            */
293           if (other->u.insert.istr.n_chars > 1 &&
294               istring_contains_space (&other->u.insert.istr))
295             return FALSE;
296         }
297 
298     do_chain:
299 
300       istring_append (&action->u.insert.istr, &other->u.insert.istr);
301       action->u.insert.end += other->u.insert.end - other->u.insert.begin;
302       action_free (other);
303 
304       return TRUE;
305     }
306 
307     case ACTION_KIND_DELETE_PROGRAMMATIC:
308       /* We can't tell if this should be chained because we don't
309        * have a group to coalesce. But unless each action deletes
310        * a single character, the overhead isn't too bad as we embed
311        * the strings in the action.
312        */
313       return FALSE;
314 
315     case ACTION_KIND_DELETE_SELECTION:
316       /* Don't join selection deletes as they should appear as a single
317        * operation and have selection reinstanted when performing undo.
318        */
319       return FALSE;
320 
321     case ACTION_KIND_DELETE_BACKSPACE:
322       if (other->u.delete.end == action->u.delete.begin)
323         {
324           istring_prepend (&action->u.delete.istr,
325                            &other->u.delete.istr);
326           action->u.delete.begin = other->u.delete.begin;
327           action_free (other);
328           return TRUE;
329         }
330 
331       return FALSE;
332 
333     case ACTION_KIND_DELETE_KEY:
334       if (action->u.delete.begin == other->u.delete.begin)
335         {
336           if (!istring_contains_space (&other->u.delete.istr) ||
337               istring_only_contains_space (&action->u.delete.istr))
338             {
339               istring_append (&action->u.delete.istr, &other->u.delete.istr);
340               action->u.delete.end += other->u.delete.istr.n_chars;
341               action_free (other);
342               return TRUE;
343             }
344         }
345 
346       return FALSE;
347 
348     case ACTION_KIND_BARRIER:
349       /* Only allow a single barrier to be added. */
350       action_free (other);
351       return TRUE;
352 
353     case ACTION_KIND_GROUP:
354     default:
355       g_return_val_if_reached (FALSE);
356     }
357 }
358 
359 static void
gtk_text_history_do_change_state(GtkTextHistory * self,gboolean is_modified,gboolean can_undo,gboolean can_redo)360 gtk_text_history_do_change_state (GtkTextHistory *self,
361                                   gboolean        is_modified,
362                                   gboolean        can_undo,
363                                   gboolean        can_redo)
364 {
365   g_assert (GTK_IS_TEXT_HISTORY (self));
366 
367   self->funcs.change_state (self->funcs_data, is_modified, can_undo, can_redo);
368 }
369 
370 static void
gtk_text_history_do_insert(GtkTextHistory * self,guint begin,guint end,const char * text,guint len)371 gtk_text_history_do_insert (GtkTextHistory *self,
372                             guint           begin,
373                             guint           end,
374                             const char     *text,
375                             guint           len)
376 {
377   g_assert (GTK_IS_TEXT_HISTORY (self));
378   g_assert (text != NULL);
379 
380   uint_order (&begin, &end);
381 
382   self->funcs.insert (self->funcs_data, begin, end, text, len);
383 }
384 
385 static void
gtk_text_history_do_delete(GtkTextHistory * self,guint begin,guint end,const char * expected_text,guint len)386 gtk_text_history_do_delete (GtkTextHistory *self,
387                             guint           begin,
388                             guint           end,
389                             const char     *expected_text,
390                             guint           len)
391 {
392   g_assert (GTK_IS_TEXT_HISTORY (self));
393 
394   uint_order (&begin, &end);
395 
396   self->funcs.delete (self->funcs_data, begin, end, expected_text, len);
397 }
398 
399 static void
gtk_text_history_do_select(GtkTextHistory * self,guint selection_insert,guint selection_bound)400 gtk_text_history_do_select (GtkTextHistory *self,
401                             guint           selection_insert,
402                             guint           selection_bound)
403 {
404   g_assert (GTK_IS_TEXT_HISTORY (self));
405 
406   self->funcs.select (self->funcs_data, selection_insert, selection_bound);
407 }
408 
409 static void
gtk_text_history_truncate_one(GtkTextHistory * self)410 gtk_text_history_truncate_one (GtkTextHistory *self)
411 {
412   if (self->undo_queue.length > 0)
413     {
414       Action *action = g_queue_peek_head (&self->undo_queue);
415       g_queue_unlink (&self->undo_queue, &action->link);
416       action_free (action);
417     }
418   else if (self->redo_queue.length > 0)
419     {
420       Action *action = g_queue_peek_tail (&self->redo_queue);
421       g_queue_unlink (&self->redo_queue, &action->link);
422       action_free (action);
423     }
424   else
425     {
426       g_assert_not_reached ();
427     }
428 }
429 
430 static void
gtk_text_history_truncate(GtkTextHistory * self)431 gtk_text_history_truncate (GtkTextHistory *self)
432 {
433   g_assert (GTK_IS_TEXT_HISTORY (self));
434 
435   if (self->max_undo_levels == 0)
436     return;
437 
438   while (self->undo_queue.length + self->redo_queue.length > self->max_undo_levels)
439     gtk_text_history_truncate_one (self);
440 }
441 
442 static void
gtk_text_history_finalize(GObject * object)443 gtk_text_history_finalize (GObject *object)
444 {
445   GtkTextHistory *self = (GtkTextHistory *)object;
446 
447   clear_action_queue (&self->undo_queue);
448   clear_action_queue (&self->redo_queue);
449 
450   G_OBJECT_CLASS (gtk_text_history_parent_class)->finalize (object);
451 }
452 
453 static void
gtk_text_history_class_init(GtkTextHistoryClass * klass)454 gtk_text_history_class_init (GtkTextHistoryClass *klass)
455 {
456   GObjectClass *object_class = G_OBJECT_CLASS (klass);
457 
458   object_class->finalize = gtk_text_history_finalize;
459 }
460 
461 static void
gtk_text_history_init(GtkTextHistory * self)462 gtk_text_history_init (GtkTextHistory *self)
463 {
464   self->enabled = TRUE;
465   self->selection.insert = -1;
466   self->selection.bound = -1;
467 }
468 
469 static gboolean
has_actionable(const GQueue * queue)470 has_actionable (const GQueue *queue)
471 {
472   const GList *iter;
473 
474   for (iter = queue->head; iter; iter = iter->next)
475     {
476       const Action *action = iter->data;
477 
478       if (action->kind == ACTION_KIND_BARRIER)
479         continue;
480 
481       if (action->kind == ACTION_KIND_GROUP)
482         {
483           if (has_actionable (&action->u.group.actions))
484             return TRUE;
485           else
486             continue;
487         }
488 
489       return TRUE;
490     }
491 
492   return FALSE;
493 }
494 
495 static void
gtk_text_history_update_state(GtkTextHistory * self)496 gtk_text_history_update_state (GtkTextHistory *self)
497 {
498   g_assert (GTK_IS_TEXT_HISTORY (self));
499 
500   if (self->irreversible || self->in_user)
501     {
502       self->can_undo = FALSE;
503       self->can_redo = FALSE;
504     }
505   else
506     {
507       self->can_undo = has_actionable (&self->undo_queue);
508       self->can_redo = has_actionable (&self->redo_queue);
509     }
510 
511   gtk_text_history_do_change_state (self, self->is_modified, self->can_undo, self->can_redo);
512 }
513 
514 static void
gtk_text_history_push(GtkTextHistory * self,Action * action)515 gtk_text_history_push (GtkTextHistory *self,
516                        Action         *action)
517 {
518   Action *peek;
519   gboolean in_user_action;
520 
521   g_assert (GTK_IS_TEXT_HISTORY (self));
522   g_assert (self->enabled);
523   g_assert (action != NULL);
524 
525   while (self->redo_queue.length > 0)
526     {
527       peek = g_queue_peek_head (&self->redo_queue);
528       g_queue_unlink (&self->redo_queue, &peek->link);
529       action_free (peek);
530     }
531 
532   peek = g_queue_peek_tail (&self->undo_queue);
533   in_user_action = self->in_user > 0;
534 
535   if (peek == NULL || !action_chain (peek, action, in_user_action))
536     g_queue_push_tail_link (&self->undo_queue, &action->link);
537 
538   gtk_text_history_truncate (self);
539   gtk_text_history_update_state (self);
540 }
541 
542 GtkTextHistory *
gtk_text_history_new(const GtkTextHistoryFuncs * funcs,gpointer funcs_data)543 gtk_text_history_new (const GtkTextHistoryFuncs *funcs,
544                       gpointer                   funcs_data)
545 {
546   GtkTextHistory *self;
547 
548   g_return_val_if_fail (funcs != NULL, NULL);
549 
550   self = g_object_new (GTK_TYPE_TEXT_HISTORY, NULL);
551   self->funcs = *funcs;
552   self->funcs_data = funcs_data;
553 
554   return g_steal_pointer (&self);
555 }
556 
557 gboolean
gtk_text_history_get_can_undo(GtkTextHistory * self)558 gtk_text_history_get_can_undo (GtkTextHistory *self)
559 {
560   g_return_val_if_fail (GTK_IS_TEXT_HISTORY (self), FALSE);
561 
562   return self->can_undo;
563 }
564 
565 gboolean
gtk_text_history_get_can_redo(GtkTextHistory * self)566 gtk_text_history_get_can_redo (GtkTextHistory *self)
567 {
568   g_return_val_if_fail (GTK_IS_TEXT_HISTORY (self), FALSE);
569 
570   return self->can_redo;
571 }
572 
573 static void
gtk_text_history_apply(GtkTextHistory * self,Action * action,Action * peek)574 gtk_text_history_apply (GtkTextHistory *self,
575                         Action         *action,
576                         Action         *peek)
577 {
578   g_assert (GTK_IS_TEXT_HISTORY (self));
579   g_assert (action != NULL);
580 
581   switch (action->kind)
582     {
583     case ACTION_KIND_INSERT:
584       gtk_text_history_do_insert (self,
585                                   action->u.insert.begin,
586                                   action->u.insert.end,
587                                   istring_str (&action->u.insert.istr),
588                                   action->u.insert.istr.n_bytes);
589 
590       /* If the next item is a DELETE_SELECTION, then we want to
591        * pre-select the text for the user. Otherwise, just place
592        * the cursor were we think it was.
593        */
594       if (peek != NULL && peek->kind == ACTION_KIND_DELETE_SELECTION)
595         gtk_text_history_do_select (self,
596                                     peek->u.delete.begin,
597                                     peek->u.delete.end);
598       else
599         gtk_text_history_do_select (self,
600                                     action->u.insert.end,
601                                     action->u.insert.end);
602 
603       break;
604 
605     case ACTION_KIND_DELETE_BACKSPACE:
606     case ACTION_KIND_DELETE_KEY:
607     case ACTION_KIND_DELETE_PROGRAMMATIC:
608     case ACTION_KIND_DELETE_SELECTION:
609       gtk_text_history_do_delete (self,
610                                   action->u.delete.begin,
611                                   action->u.delete.end,
612                                   istring_str (&action->u.delete.istr),
613                                   action->u.delete.istr.n_bytes);
614       gtk_text_history_do_select (self,
615                                   action->u.delete.begin,
616                                   action->u.delete.begin);
617       break;
618 
619     case ACTION_KIND_GROUP: {
620       const GList *actions = action->u.group.actions.head;
621 
622       for (const GList *iter = actions; iter; iter = iter->next)
623         gtk_text_history_apply (self, iter->data, NULL);
624 
625       break;
626     }
627 
628     case ACTION_KIND_BARRIER:
629       break;
630 
631     default:
632       g_assert_not_reached ();
633     }
634 
635   if (action->is_modified_set)
636     self->is_modified = action->is_modified;
637 }
638 
639 static void
gtk_text_history_reverse(GtkTextHistory * self,Action * action)640 gtk_text_history_reverse (GtkTextHistory *self,
641                           Action         *action)
642 {
643   g_assert (GTK_IS_TEXT_HISTORY (self));
644   g_assert (action != NULL);
645 
646   switch (action->kind)
647     {
648     case ACTION_KIND_INSERT:
649       gtk_text_history_do_delete (self,
650                                   action->u.insert.begin,
651                                   action->u.insert.end,
652                                   istring_str (&action->u.insert.istr),
653                                   action->u.insert.istr.n_bytes);
654       gtk_text_history_do_select (self,
655                                   action->u.insert.begin,
656                                   action->u.insert.begin);
657       break;
658 
659     case ACTION_KIND_DELETE_BACKSPACE:
660     case ACTION_KIND_DELETE_KEY:
661     case ACTION_KIND_DELETE_PROGRAMMATIC:
662     case ACTION_KIND_DELETE_SELECTION:
663       gtk_text_history_do_insert (self,
664                                   action->u.delete.begin,
665                                   action->u.delete.end,
666                                   istring_str (&action->u.delete.istr),
667                                   action->u.delete.istr.n_bytes);
668       if (action->u.delete.selection.insert != -1 &&
669           action->u.delete.selection.bound != -1)
670         gtk_text_history_do_select (self,
671                                     action->u.delete.selection.insert,
672                                     action->u.delete.selection.bound);
673       else if (action->u.delete.selection.insert != -1)
674         gtk_text_history_do_select (self,
675                                     action->u.delete.selection.insert,
676                                     action->u.delete.selection.insert);
677       break;
678 
679     case ACTION_KIND_GROUP: {
680       const GList *actions = action->u.group.actions.tail;
681 
682       for (const GList *iter = actions; iter; iter = iter->prev)
683         gtk_text_history_reverse (self, iter->data);
684 
685       break;
686     }
687 
688     case ACTION_KIND_BARRIER:
689       break;
690 
691     default:
692       g_assert_not_reached ();
693     }
694 
695   if (action->is_modified_set)
696     self->is_modified = !action->is_modified;
697 }
698 
699 static void
move_barrier(GQueue * from_queue,Action * action,GQueue * to_queue,gboolean head)700 move_barrier (GQueue   *from_queue,
701               Action   *action,
702               GQueue   *to_queue,
703               gboolean  head)
704 {
705   g_queue_unlink (from_queue, &action->link);
706 
707   if (head)
708     g_queue_push_head_link (to_queue, &action->link);
709   else
710     g_queue_push_tail_link (to_queue, &action->link);
711 }
712 
713 void
gtk_text_history_undo(GtkTextHistory * self)714 gtk_text_history_undo (GtkTextHistory *self)
715 {
716   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
717 
718   return_if_not_enabled (self);
719   return_if_applying (self);
720   return_if_irreversible (self);
721 
722   if (gtk_text_history_get_can_undo (self))
723     {
724       Action *action;
725 
726       self->applying = TRUE;
727 
728       action = g_queue_peek_tail (&self->undo_queue);
729 
730       if (action->kind == ACTION_KIND_BARRIER)
731         {
732           move_barrier (&self->undo_queue, action, &self->redo_queue, TRUE);
733           action = g_queue_peek_tail (&self->undo_queue);
734         }
735 
736       g_queue_unlink (&self->undo_queue, &action->link);
737       g_queue_push_head_link (&self->redo_queue, &action->link);
738       gtk_text_history_reverse (self, action);
739       gtk_text_history_update_state (self);
740 
741       self->applying = FALSE;
742     }
743 }
744 
745 void
gtk_text_history_redo(GtkTextHistory * self)746 gtk_text_history_redo (GtkTextHistory *self)
747 {
748   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
749 
750   return_if_not_enabled (self);
751   return_if_applying (self);
752   return_if_irreversible (self);
753 
754   if (gtk_text_history_get_can_redo (self))
755     {
756       Action *action;
757       Action *peek;
758 
759       self->applying = TRUE;
760 
761       action = g_queue_peek_head (&self->redo_queue);
762 
763       if (action->kind == ACTION_KIND_BARRIER)
764         {
765           move_barrier (&self->redo_queue, action, &self->undo_queue, FALSE);
766           action = g_queue_peek_head (&self->redo_queue);
767         }
768 
769       g_queue_unlink (&self->redo_queue, &action->link);
770       g_queue_push_tail_link (&self->undo_queue, &action->link);
771 
772       peek = g_queue_peek_head (&self->redo_queue);
773 
774       gtk_text_history_apply (self, action, peek);
775       gtk_text_history_update_state (self);
776 
777       self->applying = FALSE;
778     }
779 }
780 
781 void
gtk_text_history_begin_user_action(GtkTextHistory * self)782 gtk_text_history_begin_user_action (GtkTextHistory *self)
783 {
784   Action *group;
785 
786   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
787 
788   return_if_not_enabled (self);
789   return_if_applying (self);
790   return_if_irreversible (self);
791 
792   self->in_user++;
793 
794   group = g_queue_peek_tail (&self->undo_queue);
795 
796   if (group == NULL || group->kind != ACTION_KIND_GROUP)
797     {
798       group = action_new (ACTION_KIND_GROUP);
799       gtk_text_history_push (self, group);
800     }
801 
802   group->u.group.depth++;
803 
804   gtk_text_history_update_state (self);
805 }
806 
807 void
gtk_text_history_end_user_action(GtkTextHistory * self)808 gtk_text_history_end_user_action (GtkTextHistory *self)
809 {
810   Action *peek;
811 
812   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
813 
814   return_if_not_enabled (self);
815   return_if_applying (self);
816   return_if_irreversible (self);
817 
818   clear_action_queue (&self->redo_queue);
819 
820   peek = g_queue_peek_tail (&self->undo_queue);
821 
822   if (peek->kind != ACTION_KIND_GROUP)
823     {
824       g_warning ("miss-matched %s end_user_action. Expected group, got %d",
825                  G_OBJECT_TYPE_NAME (self),
826                  peek->kind);
827       return;
828     }
829 
830   self->in_user--;
831   peek->u.group.depth--;
832 
833   /* Unless this is the last user action, short-circuit */
834   if (peek->u.group.depth > 0)
835     return;
836 
837   /* Unlikely, but if the group is empty, just remove it */
838   if (action_group_is_empty (peek))
839     {
840       g_queue_unlink (&self->undo_queue, &peek->link);
841       action_free (peek);
842       goto update_state;
843     }
844 
845   /* If there is a single item within the group, we can hoist
846    * it up increasing the chances that we can join actions.
847    */
848   if (peek->u.group.actions.length == 1)
849     {
850       GList *link_ = peek->u.group.actions.head;
851       Action *replaced = link_->data;
852 
853       replaced->is_modified = peek->is_modified;
854       replaced->is_modified_set = peek->is_modified_set;
855 
856       g_queue_unlink (&peek->u.group.actions, link_);
857       g_queue_unlink (&self->undo_queue, &peek->link);
858       action_free (peek);
859 
860       gtk_text_history_push (self, replaced);
861 
862       goto update_state;
863     }
864 
865   /* Now insert a barrier action so we don't allow
866    * joining items to this node in the future.
867    */
868   gtk_text_history_push (self, action_new (ACTION_KIND_BARRIER));
869 
870 update_state:
871   gtk_text_history_update_state (self);
872 }
873 
874 void
gtk_text_history_begin_irreversible_action(GtkTextHistory * self)875 gtk_text_history_begin_irreversible_action (GtkTextHistory *self)
876 {
877   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
878 
879   return_if_not_enabled (self);
880   return_if_applying (self);
881 
882   if (self->in_user)
883     {
884       g_warning ("Cannot begin irreversible action while in user action");
885       return;
886     }
887 
888   self->irreversible++;
889 
890   clear_action_queue (&self->undo_queue);
891   clear_action_queue (&self->redo_queue);
892 
893   gtk_text_history_update_state (self);
894 }
895 
896 void
gtk_text_history_end_irreversible_action(GtkTextHistory * self)897 gtk_text_history_end_irreversible_action (GtkTextHistory *self)
898 {
899   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
900 
901   return_if_not_enabled (self);
902   return_if_applying (self);
903 
904   if (self->in_user)
905     {
906       g_warning ("Cannot end irreversible action while in user action");
907       return;
908     }
909 
910   self->irreversible--;
911 
912   clear_action_queue (&self->undo_queue);
913   clear_action_queue (&self->redo_queue);
914 
915   gtk_text_history_update_state (self);
916 }
917 
918 static void
gtk_text_history_clear_modified(GtkTextHistory * self)919 gtk_text_history_clear_modified (GtkTextHistory *self)
920 {
921   const GList *iter;
922 
923   for (iter = self->undo_queue.head; iter; iter = iter->next)
924     {
925       Action *action = iter->data;
926 
927       action->is_modified = FALSE;
928       action->is_modified_set = FALSE;
929     }
930 
931   for (iter = self->redo_queue.head; iter; iter = iter->next)
932     {
933       Action *action = iter->data;
934 
935       action->is_modified = FALSE;
936       action->is_modified_set = FALSE;
937     }
938 }
939 
940 void
gtk_text_history_modified_changed(GtkTextHistory * self,gboolean modified)941 gtk_text_history_modified_changed (GtkTextHistory *self,
942                                    gboolean        modified)
943 {
944   Action *peek;
945 
946   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
947 
948   return_if_not_enabled (self);
949   return_if_applying (self);
950   return_if_irreversible (self);
951 
952   /* If we have a new save point, clear all previous modified states. */
953   gtk_text_history_clear_modified (self);
954 
955   if ((peek = g_queue_peek_tail (&self->undo_queue)))
956     {
957       if (peek->kind == ACTION_KIND_BARRIER)
958         {
959           if (!(peek = peek->link.prev->data))
960             return;
961         }
962 
963       peek->is_modified = !!modified;
964       peek->is_modified_set = TRUE;
965     }
966 
967   self->is_modified = !!modified;
968   self->is_modified_set = TRUE;
969 
970   gtk_text_history_update_state (self);
971 }
972 
973 void
gtk_text_history_selection_changed(GtkTextHistory * self,int selection_insert,int selection_bound)974 gtk_text_history_selection_changed (GtkTextHistory *self,
975                                     int             selection_insert,
976                                     int             selection_bound)
977 {
978   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
979 
980   return_if_not_enabled (self);
981   return_if_applying (self);
982   return_if_irreversible (self);
983 
984   if (self->in_user == 0 && self->irreversible == 0)
985     {
986       self->selection.insert = CLAMP (selection_insert, -1, G_MAXINT);
987       self->selection.bound = CLAMP (selection_bound, -1, G_MAXINT);
988     }
989 }
990 
991 void
gtk_text_history_text_inserted(GtkTextHistory * self,guint position,const char * text,int len)992 gtk_text_history_text_inserted (GtkTextHistory *self,
993                                 guint           position,
994                                 const char     *text,
995                                 int             len)
996 {
997   Action *action;
998   guint n_chars;
999 
1000   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
1001 
1002   return_if_not_enabled (self);
1003   return_if_applying (self);
1004   return_if_irreversible (self);
1005 
1006   if (len < 0)
1007     len = strlen (text);
1008   n_chars = g_utf8_strlen (text, len);
1009 
1010   action = action_new (ACTION_KIND_INSERT);
1011   action->u.insert.begin = position;
1012   action->u.insert.end = position + n_chars;
1013   istring_set (&action->u.insert.istr, text, len, n_chars);
1014 
1015   gtk_text_history_push (self, action);
1016 }
1017 
1018 void
gtk_text_history_text_deleted(GtkTextHistory * self,guint begin,guint end,const char * text,int len)1019 gtk_text_history_text_deleted (GtkTextHistory *self,
1020                                guint           begin,
1021                                guint           end,
1022                                const char     *text,
1023                                int             len)
1024 {
1025   Action *action;
1026   ActionKind kind;
1027 
1028   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
1029 
1030   return_if_not_enabled (self);
1031   return_if_applying (self);
1032   return_if_irreversible (self);
1033 
1034   if (len < 0)
1035     len = strlen (text);
1036 
1037   if (self->selection.insert == -1 && self->selection.bound == -1)
1038     kind = ACTION_KIND_DELETE_PROGRAMMATIC;
1039   else if (self->selection.insert == end && self->selection.bound == -1)
1040     kind = ACTION_KIND_DELETE_BACKSPACE;
1041   else if (self->selection.insert == begin && self->selection.bound == -1)
1042     kind = ACTION_KIND_DELETE_KEY;
1043   else
1044     kind = ACTION_KIND_DELETE_SELECTION;
1045 
1046   action = action_new (kind);
1047   action->u.delete.begin = begin;
1048   action->u.delete.end = end;
1049   action->u.delete.selection.insert = self->selection.insert;
1050   action->u.delete.selection.bound = self->selection.bound;
1051   istring_set (&action->u.delete.istr, text, len, ABS (end - begin));
1052 
1053   gtk_text_history_push (self, action);
1054 }
1055 
1056 gboolean
gtk_text_history_get_enabled(GtkTextHistory * self)1057 gtk_text_history_get_enabled (GtkTextHistory *self)
1058 {
1059   g_return_val_if_fail (GTK_IS_TEXT_HISTORY (self), FALSE);
1060 
1061   return self->enabled;
1062 }
1063 
1064 void
gtk_text_history_set_enabled(GtkTextHistory * self,gboolean enabled)1065 gtk_text_history_set_enabled (GtkTextHistory *self,
1066                               gboolean        enabled)
1067 {
1068   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
1069 
1070   enabled = !!enabled;
1071 
1072   if (self->enabled != enabled)
1073     {
1074       self->enabled = enabled;
1075 
1076       if (!self->enabled)
1077         {
1078           self->irreversible = 0;
1079           self->in_user = 0;
1080           clear_action_queue (&self->undo_queue);
1081           clear_action_queue (&self->redo_queue);
1082         }
1083 
1084       gtk_text_history_update_state (self);
1085     }
1086 }
1087 
1088 guint
gtk_text_history_get_max_undo_levels(GtkTextHistory * self)1089 gtk_text_history_get_max_undo_levels (GtkTextHistory *self)
1090 {
1091   g_return_val_if_fail (GTK_IS_TEXT_HISTORY (self), 0);
1092 
1093   return self->max_undo_levels;
1094 }
1095 
1096 void
gtk_text_history_set_max_undo_levels(GtkTextHistory * self,guint max_undo_levels)1097 gtk_text_history_set_max_undo_levels (GtkTextHistory *self,
1098                                       guint           max_undo_levels)
1099 {
1100   g_return_if_fail (GTK_IS_TEXT_HISTORY (self));
1101 
1102   if (self->max_undo_levels != max_undo_levels)
1103     {
1104       self->max_undo_levels = max_undo_levels;
1105       gtk_text_history_truncate (self);
1106     }
1107 }
1108