1 /**
2  * @file    snippets.c
3  * @brief   handle snppets
4  *
5  * Copyright (C) 2009 Gummi Developers
6  * All Rights reserved.
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use,
12  * copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the
14  * Software is furnished to do so, subject to the following
15  * conditions:
16  *
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27  * OTHER DEALINGS IN THE SOFTWARE.
28  */
29 
30 #include "snippets.h"
31 
32 #include <assert.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 
38 #include <glib.h>
39 
40 #include "constants.h"
41 #include "editor.h"
42 #include "environment.h"
43 #include "utils.h"
44 
snippets_init()45 GuSnippets* snippets_init () {
46     GuSnippets* s = g_new0 (GuSnippets, 1);
47 
48     gchar* filename = g_build_filename (C_GUMMI_CONFDIR, "snippets.cfg", NULL);
49     gchar* dirname = g_path_get_dirname (filename);
50 
51     g_mkdir_with_parents (dirname, DIR_PERMS);
52     g_free (dirname);
53 
54     slog (L_INFO, "Snippets : %s\n", filename);
55 
56     s->filename = g_strdup (filename);
57     s->accel_group = gtk_accel_group_new ();
58     s->stackframe = NULL;
59 
60     snippets_load (s);
61     return s;
62 }
63 
snippets_set_default(GuSnippets * sc)64 void snippets_set_default (GuSnippets* sc) {
65     GError* err = NULL;
66     gchar* snip = g_build_filename (GUMMI_DATA, "snippets", "snippets.cfg", NULL);
67     GList* current = sc->closure_data;
68 
69     /* Remove all accelerator */
70     while (current) {
71         gtk_accel_group_disconnect (sc->accel_group,
72                 TUPLE2 (current->data)->second);
73         slog (L_DEBUG, "Accelerator for `%s' disconnected\n",
74                 TUPLE2 (current->data)->first);
75         current = g_list_next (current);
76     }
77     g_list_free (sc->closure_data);
78     sc->closure_data = NULL;
79 
80     if (!utils_copy_file (snip, sc->filename, &err)) {
81         slog (L_G_ERROR, "can't open snippets file for writing, snippets may "
82                 "not work properly\n");
83         g_error_free (err);
84     } else {
85         snippets_load (sc);
86     }
87     g_free (snip);
88 }
89 
snippets_load(GuSnippets * sc)90 void snippets_load (GuSnippets* sc) {
91     FILE* fh = 0;
92     gchar buf[BUFSIZ];
93     gchar* rot = NULL;
94     gchar* seg = NULL;
95     slist* current = NULL;
96     slist* prev = NULL;
97 
98     if (sc->head)
99         snippets_clean_up (sc);
100 
101     if (! (fh = fopen (sc->filename, "r"))) {
102         slog (L_ERROR, "can't find snippets file, reseting to default\n");
103         snippets_set_default (sc);
104         return;
105     }
106 
107     while (fgets (buf, BUFSIZ, fh)) {
108         current = g_new0 (slist, 1);
109         if (!sc->head)
110             sc->head = prev = current;
111         prev->next = current;
112         buf[strlen (buf) -1] = 0; /* remove trailing '\n' */
113 
114         if (buf[0] != '\t') {
115             if ('#' == buf[0] || !strlen(buf)) {
116                 current->first = g_strdup (buf);
117             } else {
118                 seg = strstr (buf, " ") + 1;
119                 current->first = g_strdup ((seg == buf)? "Invalid": seg);
120                 snippets_set_accelerator (sc, current->first);
121             }
122         } else {
123             if (!prev->second) {
124                 prev->second = g_strdup (buf + 1);
125                 continue;
126             }
127             rot = g_strdup (prev->second);
128             g_free (prev->second);
129             prev->second = g_strconcat (rot, "\n", buf + 1, NULL);
130             g_free (rot);
131             g_free (current);
132             current = prev;
133         }
134         prev = current;
135     }
136     if (prev) prev->next = NULL;
137     fclose (fh);
138 }
139 
snippets_save(GuSnippets * sc)140 void snippets_save (GuSnippets* sc) {
141     FILE* fh = 0;
142     slist* current = sc->head;
143     gint i = 0, count = 0, len = 0;
144     gchar* buf = 0;
145 
146     if (! (fh = fopen (sc->filename, "w")))
147         slog (L_FATAL, "can't open snippets file for writing... abort\n");
148 
149     while (current) {
150         /* skip comments */
151         if (!current->second) {
152             fputs (current->first, fh);
153             fputs ("\n", fh);
154             current = current->next;
155             continue;
156         }
157         fputs ("snippet ", fh);
158         fputs (current->first, fh);
159         fputs ("\n\t", fh);
160 
161         len = strlen (current->second) + 1;
162         buf = (gchar*)g_malloc (len * 2);
163         memset (buf, 0, len * 2);
164         /* replace '\n' with '\n\t' for options with multi-line content */
165         for (i = 0; i < len; ++i) {
166             if (count + 2 == len * 2) break;
167             buf[count++] = current->second[i];
168             if (i != len -2 && '\n' == current->second[i])
169                 buf[count++] = '\t';
170         }
171         fputs (buf, fh);
172         fputs ("\n", fh);
173         current = current->next;
174         count = 0;
175         g_free (buf);
176     }
177     fclose (fh);
178 }
179 
snippets_clean_up(GuSnippets * sc)180 void snippets_clean_up (GuSnippets* sc) {
181     slist* prev = sc->head;
182     slist* current;
183     while (prev) {
184         current = prev->next;
185         g_free (prev);
186         prev = current;
187     }
188     sc->head = NULL;
189 }
190 
snippets_get_value(GuSnippets * sc,const gchar * term)191 gchar* snippets_get_value (GuSnippets* sc, const gchar* term) {
192     gchar* key = g_strdup_printf ("%s,", term);
193     slist* index = slist_find (sc->head, key, TRUE, FALSE);
194     g_free (key);
195     return (index)? index->second: NULL;
196 }
197 
snippets_set_accelerator(GuSnippets * sc,gchar * config)198 void snippets_set_accelerator (GuSnippets* sc, gchar* config) {
199     /* config has the form: Key,Accel_key,Name */
200     GClosure* closure = NULL;
201     GdkModifierType mod;
202     guint keyval = 0;
203     gchar** configs = g_strsplit (config, ",", 0);
204     Tuple2* data = g_new0 (Tuple2, 1);
205     Tuple2* closure_data = g_new0 (Tuple2, 1);
206 
207     /* Return if config does not contains accelerator */
208     if (strlen (configs[1]) == 0) {
209         g_strfreev (configs);
210         return;
211     }
212 
213     data->first = (gpointer)sc;
214     data->second = (gpointer)g_strdup (configs[0]);
215 
216 
217     closure = g_cclosure_new (G_CALLBACK (snippets_accel_cb), data, NULL);
218     closure_data->first = (gpointer)data->second;
219     closure_data->second = (gpointer)closure;
220 
221     sc->closure_data = g_list_append (sc->closure_data, closure_data);
222     gtk_accelerator_parse (configs[1], &keyval, &mod);
223 
224     /* Return without connect if accel is not valid */
225     if (!gtk_accelerator_valid (keyval, mod)) return;
226 
227     snippets_accel_connect (sc, keyval, mod, closure);
228     g_strfreev (configs);
229 }
230 
snippets_activate(GuSnippets * sc,GuEditor * ec,gchar * key)231 void snippets_activate (GuSnippets* sc, GuEditor* ec, gchar* key) {
232     gchar* snippet = NULL;
233     GuSnippetInfo* new_info = NULL;
234     GtkTextIter start, end;
235 
236     slog (L_DEBUG, "Snippet `%s' activated\n", key);
237 
238     snippet = snippets_get_value (sc, key);
239     g_return_if_fail (snippet != NULL);
240 
241     new_info = snippets_parse (snippet);
242 
243     gtk_text_buffer_get_selection_bounds (ec_buffer, &start, &end);
244     new_info->start_offset = gtk_text_iter_get_offset (&start);
245     new_info->sel_text = gtk_text_iter_get_text (&start, &end);
246     GSList* marks = gtk_text_iter_get_marks (&start);
247     new_info->sel_start = *GTK_TEXT_MARK (marks->data);
248     g_slist_free (marks);
249 
250     gtk_text_buffer_insert (ec_buffer, &start, new_info->expanded, -1);
251     snippet_info_create_marks (new_info, ec);
252     snippet_info_initial_expand (new_info, ec);
253     gtk_text_buffer_set_modified (ec_buffer, TRUE);
254 
255     if (sc->info) {
256         snippet_info_sync_group (sc->info, ec);
257         sc->stackframe = g_list_append (sc->stackframe, sc->info);
258     }
259     sc->info = new_info;
260 
261     if (!snippet_info_goto_next_placeholder (sc->info, ec))
262         snippets_deactivate (sc, ec);
263 }
264 
snippets_deactivate(GuSnippets * sc,GuEditor * ec)265 void snippets_deactivate (GuSnippets* sc, GuEditor* ec) {
266     GList* last = NULL;
267     snippet_info_free (sc->info, ec);
268     last = g_list_last (sc->stackframe);
269     if (last) {
270         sc->info = last->data;
271         sc->stackframe = g_list_remove (sc->stackframe, last->data);
272     } else
273         sc->info = NULL;
274     slog (L_DEBUG, "Snippet deactivated\n");
275 }
276 
snippets_key_press_cb(GuSnippets * sc,GuEditor * ec,GdkEventKey * ev)277 gboolean snippets_key_press_cb (GuSnippets* sc, GuEditor* ec, GdkEventKey* ev) {
278     GtkTextIter current, start;
279 
280     if (ev->keyval == GDK_KEY_Tab) {
281         gchar* key = NULL;
282         editor_get_current_iter (ec, &current);
283         if (gtk_text_iter_ends_word (&current)) {
284             start = current;
285             gtk_text_iter_backward_word_start (&start);
286             key = gtk_text_iter_get_text (&start, &current);
287 
288             if (snippets_get_value (sc, key)) {
289                 gtk_text_buffer_delete (ec_buffer, &start, &current);
290                 snippets_activate (sc, ec, key);
291                 g_free (key);
292                 return TRUE;
293             }
294             g_free (key);
295         }
296     }
297 
298     if (sc->info) {
299         if (ev->keyval == GDK_KEY_Tab) {
300             if (!snippet_info_goto_next_placeholder (sc->info, ec))
301                 snippets_deactivate (sc, ec);
302             return TRUE;
303         } else if (ev->keyval == GDK_KEY_ISO_Left_Tab
304                    && ev->state & GDK_SHIFT_MASK) {
305             if (!snippet_info_goto_prev_placeholder (sc->info, ec))
306                 snippets_deactivate (sc, ec);
307             return TRUE;
308         }
309         /* Deactivate snippet if the current insert range is not within the
310          * snippet */
311         editor_get_current_iter (ec, &current);
312         gint offset = gtk_text_iter_get_offset (&current);
313         GList* last = g_list_last (sc->info->einfo);
314         if (last) {
315             gtk_text_buffer_get_iter_at_mark (ec_buffer, &current,
316                     GU_SNIPPET_EXPAND_INFO (last->data)->left_mark);
317             gint bound_end = gtk_text_iter_get_offset (&current);
318             if (offset < sc->info->start_offset || offset > bound_end)
319                 snippets_deactivate (sc, ec);
320         }
321     }
322 
323     return FALSE;
324 }
325 
snippets_key_release_cb(GuSnippets * sc,GuEditor * ec,GdkEventKey * ev)326 gboolean snippets_key_release_cb (GuSnippets* sc, GuEditor* ec, GdkEventKey* ev) {
327 
328     if (ev->keyval != GDK_KEY_Tab && !sc->info)
329         return FALSE;
330 
331     if (sc->info)
332         snippet_info_sync_group (sc->info, ec);
333 
334     return FALSE;
335 }
336 
snippets_parse(char * snippet)337 GuSnippetInfo* snippets_parse (char* snippet) {
338     gint i, start, end;
339     GError* err = NULL;
340     gchar** result = NULL;
341     GRegex* regex = NULL;
342     GMatchInfo* match_info = NULL;
343     const gchar* holders[] = { "\\$([0-9]+)", "\\${([0-9]*):?([^}]*)}",
344                                "\\$(FILENAME)", "\\${(FILENAME)}",
345                                "\\$(BASENAME)", "\\${(BASENAME)}",
346                                "\\$(SELECTED_TEXT)", "\\${(SELECTED_TEXT)}"
347                                /* Anyway to combine these? */
348                             };
349 
350     GuSnippetInfo* info = snippet_info_new (snippet);
351 
352     for (i = 0; i < sizeof(holders) / sizeof(holders[0]); ++i) {
353         if (! (regex = g_regex_new (holders[i], G_REGEX_DOTALL, 0, &err))) {
354             slog (L_ERROR, "g_regex_new (): %s\n", err->message);
355             g_error_free (err);
356             return info;
357         }
358         g_regex_match (regex, snippet, 0, &match_info);
359         while (g_match_info_matches (match_info)) {
360             result = g_match_info_fetch_all (match_info);
361             g_match_info_fetch_pos (match_info, 0, &start, &end);
362 
363             /* Convert start, end to UTF8 offset */
364             char* s_start = g_substr(snippet, 0, start);
365             char* s_end = g_substr(snippet, 0, end);
366             start = g_utf8_strlen(s_start, -1);
367             end = g_utf8_strlen(s_end, -1);
368             g_free(s_start);
369             g_free(s_end);
370 
371             if (i < 2) {
372                 snippet_info_append_holder (info, atoi (result[1]), start,
373                         end -start, result[2]);
374             } else {
375                 snippet_info_append_holder (info, -1, start, end -start,
376                         result[1]);
377             }
378             slog (L_DEBUG, "Placeholder: (%s, %s, %s)\n", result[0], result[1],
379                     result[2]);
380             g_match_info_next (match_info, NULL);
381             g_strfreev (result);
382         }
383         g_regex_unref (regex);
384         g_match_info_free (match_info);
385     }
386     info->einfo = g_list_sort (info->einfo, snippet_info_pos_cmp);
387     info->einfo_sorted = g_list_copy (info->einfo);
388     info->einfo_sorted = g_list_sort (info->einfo_sorted, snippet_info_num_cmp);
389 
390     return info;
391 }
392 
snippets_accel_cb(GtkAccelGroup * accel_group,GObject * obj,guint keyval,GdkModifierType mods,Tuple2 * udata)393 void snippets_accel_cb (GtkAccelGroup* accel_group, GObject* obj,
394         guint keyval, GdkModifierType mods, Tuple2* udata) {
395     GuSnippets* sc = GU_SNIPPETS (udata->first);
396     gchar* key = (gchar*)udata->second;
397     /* XXX: Don't know how to avoid using gummi_get_active_editor () here. Since
398      * gtk_accel_group must be connect when load, we can not specify the
399      * editor in user_data, because snippets should only have effect on
400      * active tab */
401     snippets_activate (sc, gummi_get_active_editor (), key);
402 }
403 
snippets_accel_connect(GuSnippets * sc,guint keyval,GdkModifierType mod,GClosure * closure)404 void snippets_accel_connect (GuSnippets* sc, guint keyval, GdkModifierType mod,
405         GClosure* closure) {
406     gchar* acc = NULL;
407     gtk_accel_group_connect (sc->accel_group, keyval,
408             gtk_accelerator_get_default_mod_mask () & mod, GTK_ACCEL_VISIBLE,
409             closure);
410 
411     acc = gtk_accelerator_get_label (keyval,
412             gtk_accelerator_get_default_mod_mask () & mod);
413     slog (L_DEBUG, "Accelerator `%s' connected\n", acc);
414     g_free (acc);
415 }
416 
snippets_accel_disconnect(GuSnippets * sc,const gchar * key)417 void snippets_accel_disconnect (GuSnippets* sc, const gchar* key) {
418     Tuple2* closure_data = NULL;
419     GList* current = NULL;
420 
421     g_return_if_fail (key != NULL);
422 
423     current = sc->closure_data;
424     while (current) {
425         closure_data = TUPLE2 (current->data);
426         if (STR_EQU (closure_data->first, key))
427             break;
428         current = g_list_next (current);
429     }
430     if (current) {
431         gtk_accel_group_disconnect (sc->accel_group, closure_data->second);
432         sc->closure_data = g_list_remove (sc->closure_data, closure_data);
433         g_free (closure_data);
434         slog (L_DEBUG, "Accelerator for `%s' disconnected\n",
435                 closure_data->first);
436     }
437 }
438 
snippet_info_new(gchar * snippet)439 GuSnippetInfo* snippet_info_new (gchar* snippet) {
440     GuSnippetInfo* info = g_new0 (GuSnippetInfo, 1);
441     info->snippet = g_strdup (snippet);
442     info->expanded = g_strdup (snippet);
443     info->einfo = NULL;
444     info->einfo_sorted = NULL;
445     return info;
446 }
447 
snippet_info_free(GuSnippetInfo * info,GuEditor * ec)448 void snippet_info_free (GuSnippetInfo* info, GuEditor* ec) {
449     snippet_info_remove_marks (info, ec);
450     GList* current = g_list_first (info->einfo);
451     while (current) {
452         g_free (GU_SNIPPET_EXPAND_INFO (current->data)->text);
453         current = g_list_next (current);
454     }
455     g_list_free (info->einfo);
456     g_list_free (info->einfo_unique);
457     g_list_free (info->einfo_sorted);
458     g_free (info);
459 }
460 
snippet_info_goto_next_placeholder(GuSnippetInfo * info,GuEditor * ec)461 gboolean snippet_info_goto_next_placeholder (GuSnippetInfo* info, GuEditor* ec) {
462     GuSnippetExpandInfo* einfo = NULL;
463     GtkTextIter start, end;
464     gboolean success = TRUE;
465 
466     /* Snippet just activated */
467     if (!info->current) {
468         /* Skip $0, $-1 and jump to next placeholder */
469         if (info->einfo_unique) {
470             info->current = info->einfo_unique;
471             while (info->current &&
472                 GU_SNIPPET_EXPAND_INFO (info->current->data)->group_number <= 0)
473                 info->current = g_list_next (info->current);
474         }
475     } else
476         info->current = g_list_next (info->current);
477 
478     /* No placeholder left */
479     if (!info->current) {
480         info->current = g_list_first (info->einfo_sorted);
481         while (info->current &&
482                GU_SNIPPET_EXPAND_INFO (info->current->data)->group_number != 0)
483             info->current = g_list_next(info->current);
484 
485         if (!info->current)
486             return FALSE;
487         else /* This is the last one($0) set to false to deactivate snippet */
488             success = FALSE;
489     }
490 
491     einfo = GU_SNIPPET_EXPAND_INFO (info->current->data);
492     gtk_text_buffer_get_iter_at_mark (ec_buffer, &start, einfo->left_mark);
493     gtk_text_buffer_get_iter_at_mark (ec_buffer, &end, einfo->right_mark);
494     gtk_text_buffer_place_cursor (ec_buffer, &start);
495     gtk_text_buffer_select_range (ec_buffer, &start, &end);
496     return success;
497 }
498 
snippet_info_goto_prev_placeholder(GuSnippetInfo * info,GuEditor * ec)499 gboolean snippet_info_goto_prev_placeholder (GuSnippetInfo* info, GuEditor* ec) {
500     GuSnippetExpandInfo* einfo = NULL;
501     GtkTextIter start, end;
502     info->current = g_list_previous (info->current);
503 
504     /* Return false to deactivate snippet */
505     if (!info->current ||
506         GU_SNIPPET_EXPAND_INFO(info->current->data)->group_number < 0)
507         return FALSE;
508 
509     einfo = GU_SNIPPET_EXPAND_INFO (info->current->data);
510     gtk_text_buffer_get_iter_at_mark (ec_buffer, &start, einfo->left_mark);
511     gtk_text_buffer_get_iter_at_mark (ec_buffer, &end, einfo->right_mark);
512     gtk_text_buffer_place_cursor (ec_buffer, &start);
513     gtk_text_buffer_select_range (ec_buffer, &start, &end);
514     return TRUE;
515 }
516 
snippet_info_append_holder(GuSnippetInfo * info,gint group,gint start,gint len,gchar * text)517 void snippet_info_append_holder (GuSnippetInfo* info, gint group, gint start,
518         gint len, gchar* text) {
519     GuSnippetExpandInfo* einfo = g_new0 (GuSnippetExpandInfo, 1);
520     einfo->group_number = group;
521     einfo->start = start;
522     einfo->len = len;
523     einfo->text = g_strdup (text? text: "");
524     info->einfo = g_list_append (info->einfo, einfo);
525 }
526 
snippet_info_create_marks(GuSnippetInfo * info,GuEditor * ec)527 void snippet_info_create_marks (GuSnippetInfo* info, GuEditor* ec) {
528     GList* current = g_list_first (info->einfo);
529     GtkTextIter start, end;
530 
531     while (current) {
532         GuSnippetExpandInfo* einfo = GU_SNIPPET_EXPAND_INFO (current->data);
533         gtk_text_buffer_get_iter_at_offset (ec_buffer, &start,
534                 info->start_offset + einfo->start);
535         gtk_text_buffer_get_iter_at_offset (ec_buffer, &end,
536                 info->start_offset + einfo->start + einfo->len);
537         einfo->left_mark = gtk_text_mark_new (NULL, TRUE);
538         einfo->right_mark = gtk_text_mark_new (NULL, FALSE);
539         gtk_text_buffer_add_mark (ec_buffer, einfo->left_mark, &start);
540         gtk_text_buffer_add_mark (ec_buffer, einfo->right_mark, &end);
541         current = g_list_next (current);
542     }
543     slog (L_DEBUG, "Marks created\n");
544 }
545 
snippet_info_remove_marks(GuSnippetInfo * info,GuEditor * ec)546 void snippet_info_remove_marks (GuSnippetInfo* info, GuEditor* ec) {
547     GList* current = g_list_first (info->einfo);
548 
549     while (current) {
550         GuSnippetExpandInfo* einfo = GU_SNIPPET_EXPAND_INFO (current->data);
551         gtk_text_buffer_delete_mark (ec_buffer, einfo->left_mark);
552         gtk_text_buffer_delete_mark (ec_buffer, einfo->right_mark);
553         current = g_list_next (current);
554     }
555     slog (L_DEBUG, "Marks removed\n");
556 }
557 
snippet_info_initial_expand(GuSnippetInfo * info,GuEditor * ec)558 void snippet_info_initial_expand (GuSnippetInfo* info, GuEditor* ec) {
559     GuSnippetExpandInfo* value = NULL;
560     GtkTextIter start, end;
561     GHashTable* map = NULL;
562     GList* current = NULL;
563     gchar* text = NULL;
564     gint key = 0;
565 
566     map = g_hash_table_new (NULL, NULL);
567     current = g_list_first (info->einfo);
568 
569     while (current) {
570         GuSnippetExpandInfo* einfo = GU_SNIPPET_EXPAND_INFO (current->data);
571         if (!g_hash_table_lookup_extended (map, ((gpointer)einfo->group_number),
572                     (gpointer)&key, (gpointer)&value)) {
573             g_hash_table_insert (map, (gpointer)einfo->group_number, einfo);
574             info->einfo_unique = g_list_append (info->einfo_unique, einfo);
575         }
576         current = g_list_next (current);
577     }
578     info->einfo_unique = g_list_sort (info->einfo_unique, snippet_info_num_cmp);
579 
580     current = g_list_first (info->einfo);
581     info->offset = 0;
582 
583     while (current) {
584         GuSnippetExpandInfo* einfo = GU_SNIPPET_EXPAND_INFO (current->data);
585         g_hash_table_lookup_extended (map, (gpointer)einfo->group_number,
586                 (gpointer)&key, (gpointer)&value);
587         gtk_text_buffer_get_iter_at_mark (ec_buffer, &start, einfo->left_mark);
588         gtk_text_buffer_get_iter_at_mark (ec_buffer, &end, einfo->right_mark);
589 
590         /* Expand macros */
591         text = GU_SNIPPET_EXPAND_INFO(current->data)->text;
592         if (STR_EQU (text, "SELECTED_TEXT")) {
593             GtkTextIter ms, me;
594             gtk_text_buffer_delete (ec_buffer, &start, &end);
595             gtk_text_buffer_insert (ec_buffer, &start, info->sel_text, -1);
596             gtk_text_buffer_get_iter_at_mark (ec_buffer, &ms, &info->sel_start);
597             me = ms;
598             gtk_text_iter_forward_chars (&me, strlen (info->sel_text));
599             gtk_text_buffer_delete (ec_buffer, &ms, &me);
600         } else if (STR_EQU (text, "FILENAME")) {
601             gtk_text_buffer_delete (ec_buffer, &start, &end);
602             gtk_text_buffer_insert (ec_buffer, &start,
603                     ec->filename? ec->filename: "", -1);
604         } else if (STR_EQU (text, "BASENAME")) {
605             gchar* basename = g_path_get_basename(ec->filename?ec->filename:"");
606             gtk_text_buffer_delete (ec_buffer, &start, &end);
607             gtk_text_buffer_insert (ec_buffer, &start, basename, -1);
608             g_free (basename);
609         } else {
610             /* Expand text of same group with with text of group leader */
611             gtk_text_buffer_delete (ec_buffer, &start, &end);
612             gtk_text_buffer_insert (ec_buffer, &start, value->text, -1);
613         }
614 
615         current = g_list_next (current);
616     }
617     g_hash_table_destroy (map);
618 }
619 
snippet_info_sync_group(GuSnippetInfo * info,GuEditor * ec)620 void snippet_info_sync_group (GuSnippetInfo* info, GuEditor* ec) {
621     if (!info->current ||
622         GU_SNIPPET_EXPAND_INFO(info->current->data)->group_number == -1)
623         return;
624 
625     GuSnippetExpandInfo* active = GU_SNIPPET_EXPAND_INFO (info->current->data);
626     GList* current = g_list_first (info->einfo);
627     gchar* text = NULL;
628     GtkTextIter start, end;
629 
630     gtk_text_buffer_get_iter_at_mark (ec_buffer, &start, active->left_mark);
631     gtk_text_buffer_get_iter_at_mark (ec_buffer, &end, active->right_mark);
632     text = gtk_text_buffer_get_text (ec_buffer, &start, &end, TRUE);
633 
634     while (current) {
635         GuSnippetExpandInfo* einfo = GU_SNIPPET_EXPAND_INFO (current->data);
636         if (einfo != active && einfo->group_number == active->group_number) {
637             gtk_text_buffer_get_iter_at_mark (ec_buffer, &start,
638                     einfo->left_mark);
639             gtk_text_buffer_get_iter_at_mark (ec_buffer, &end,
640                     einfo->right_mark);
641             gtk_text_buffer_delete (ec_buffer, &start, &end);
642             gtk_text_buffer_insert (ec_buffer, &start, text, -1);
643         }
644         current = g_list_next (current);
645     }
646     g_free (text);
647 }
648 
snippet_info_num_cmp(gconstpointer a,gconstpointer b)649 gint snippet_info_num_cmp (gconstpointer a, gconstpointer b) {
650     return ( (GU_SNIPPET_EXPAND_INFO (a)->group_number <
651                 GU_SNIPPET_EXPAND_INFO (b)->group_number)? -1: 1);
652 }
653 
snippet_info_pos_cmp(gconstpointer a,gconstpointer b)654 gint snippet_info_pos_cmp (gconstpointer a, gconstpointer b) {
655     return ( (GU_SNIPPET_EXPAND_INFO (a)->start <
656                 GU_SNIPPET_EXPAND_INFO (b)->start)? -1: 1);
657 }
658