Improve tag-goto popup
[geany-mirror.git] / src / templates.c
blob5181ab97d0013aeae2194c60af881ff1b2ed9375
1 /*
2 * templates.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * Templates to insert into the current document, or file templates to create a new
23 * document from.
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include "templates.h"
32 #include "app.h"
33 #include "document.h"
34 #include "encodingsprivate.h"
35 #include "filetypes.h"
36 #include "geany.h"
37 #include "geanymenubuttonaction.h"
38 #include "geanyobject.h"
39 #include "spawn.h"
40 #include "support.h"
41 #include "toolbar.h"
42 #include "ui_utils.h"
43 #include "utils.h"
45 #include <time.h>
46 #include <string.h>
47 #include <gtk/gtk.h>
50 GeanyTemplatePrefs template_prefs;
52 static GtkWidget *new_with_template_menu = NULL;
53 static GtkWidget *new_with_template_toolbar_menu = NULL;
55 /* TODO: implement custom insertion templates instead? */
56 static gchar *templates[GEANY_MAX_TEMPLATES];
59 static void replace_static_values(GString *text);
60 static gchar *get_template_fileheader(GeanyFiletype *ft);
62 /* called by templates_replace_common */
63 static void templates_replace_default_dates(GString *text);
64 static void templates_replace_command(GString *text, const gchar *file_name,
65 const gchar *file_type, const gchar *func_name);
68 static gchar *read_file(const gchar *locale_fname)
70 gchar *contents;
71 gsize length;
72 GString *str;
74 if (! g_file_get_contents(locale_fname, &contents, &length, NULL))
75 return NULL;
77 if (! encodings_convert_to_utf8_auto(&contents, &length, NULL, NULL, NULL, NULL))
79 gchar *utf8_fname = utils_get_utf8_from_locale(locale_fname);
81 ui_set_statusbar(TRUE, _("Failed to convert template file \"%s\" to UTF-8"), utf8_fname);
82 g_free(utf8_fname);
83 g_free(contents);
84 return NULL;
87 str = g_string_new(contents);
88 g_free(contents);
90 /* convert to LF endings for consistency in mixing templates */
91 utils_ensure_same_eol_characters(str, SC_EOL_LF);
92 return g_string_free(str, FALSE);
96 static void read_template(const gchar *name, gint id)
98 gchar *fname = g_build_path(G_DIR_SEPARATOR_S, app->configdir,
99 GEANY_TEMPLATES_SUBDIR, name, NULL);
101 /* try system if user template doesn't exist */
102 if (!g_file_test(fname, G_FILE_TEST_EXISTS))
103 SETPTR(fname, g_build_path(G_DIR_SEPARATOR_S, app->datadir,
104 GEANY_TEMPLATES_SUBDIR, name, NULL));
106 templates[id] = read_file(fname);
107 g_free(fname);
111 /* called when inserting templates into an existing document */
112 static void convert_eol_characters(GString *template, GeanyDocument *doc)
114 gint doc_eol_mode;
116 g_return_if_fail(doc == NULL || doc->is_valid);
118 if (doc == NULL)
119 doc = document_get_current();
121 g_return_if_fail(doc != NULL);
123 doc_eol_mode = editor_get_eol_char_mode(doc->editor);
124 utils_ensure_same_eol_characters(template, doc_eol_mode);
128 static void init_general_templates(void)
130 /* read the contents */
131 read_template("fileheader", GEANY_TEMPLATE_FILEHEADER);
132 read_template("gpl", GEANY_TEMPLATE_GPL);
133 read_template("bsd", GEANY_TEMPLATE_BSD);
134 read_template("function", GEANY_TEMPLATE_FUNCTION);
135 read_template("changelog", GEANY_TEMPLATE_CHANGELOG);
139 void templates_replace_common(GString *tmpl, const gchar *fname,
140 GeanyFiletype *ft, const gchar *func_name)
142 gchar *shortname;
144 if (fname == NULL)
146 if (!ft->extension)
147 shortname = g_strdup(GEANY_STRING_UNTITLED);
148 else
149 shortname = g_strconcat(GEANY_STRING_UNTITLED, ".", ft->extension, NULL);
151 else
152 shortname = g_path_get_basename(fname);
154 templates_replace_valist(tmpl,
155 "{filename}", shortname,
156 "{project}", app->project ? app->project->name : "",
157 "{description}", app->project ? app->project->description : "",
158 NULL);
159 g_free(shortname);
161 templates_replace_default_dates(tmpl);
162 templates_replace_command(tmpl, fname, ft->name, func_name);
163 /* Bug: command results could have {ob} {cb} strings in! */
164 /* replace braces last */
165 templates_replace_valist(tmpl,
166 "{ob}", "{",
167 "{cb}", "}",
168 NULL);
172 static gchar *get_template_from_file(const gchar *locale_fname, const gchar *doc_filename,
173 GeanyFiletype *ft)
175 gchar *content;
177 content = read_file(locale_fname);
179 if (content != NULL)
181 gchar *file_header;
182 GString *template = g_string_new(content);
184 file_header = get_template_fileheader(ft);
185 templates_replace_valist(template,
186 "{fileheader}", file_header,
187 NULL);
188 templates_replace_common(template, doc_filename, ft, NULL);
190 utils_free_pointers(2, file_header, content, NULL);
191 return g_string_free(template, FALSE);
193 return NULL;
197 static void
198 on_new_with_file_template(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
200 gchar *fname = ui_menu_item_get_text(menuitem);
201 GeanyFiletype *ft;
202 gchar *template;
203 const gchar *extension = strrchr(fname, '.'); /* easy way to get the file extension */
204 gchar *new_filename = g_strconcat(GEANY_STRING_UNTITLED, extension, NULL);
205 gchar *path;
207 ft = filetypes_detect_from_extension(fname);
208 SETPTR(fname, utils_get_locale_from_utf8(fname));
210 /* fname is just the basename from the menu item, so prepend the custom files path */
211 path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, GEANY_TEMPLATES_SUBDIR,
212 "files", fname, NULL);
213 template = get_template_from_file(path, new_filename, ft);
214 if (!template)
216 /* try the system path */
217 g_free(path);
218 path = g_build_path(G_DIR_SEPARATOR_S, app->datadir, GEANY_TEMPLATES_SUBDIR,
219 "files", fname, NULL);
220 template = get_template_from_file(path, new_filename, ft);
222 if (template)
224 /* line endings will be converted */
225 document_new_file(new_filename, ft, template);
227 else
229 SETPTR(fname, utils_get_utf8_from_locale(fname));
230 ui_set_statusbar(TRUE, _("Could not find file '%s'."), fname);
232 g_free(template);
233 g_free(path);
234 g_free(new_filename);
235 g_free(fname);
239 static void add_file_item(const gchar *fname, GtkWidget *menu)
241 GtkWidget *tmp_button;
242 gchar *label;
244 g_return_if_fail(fname);
245 g_return_if_fail(menu);
247 label = utils_get_utf8_from_locale(fname);
249 tmp_button = gtk_menu_item_new_with_label(label);
250 gtk_widget_show(tmp_button);
251 gtk_container_add(GTK_CONTAINER(menu), tmp_button);
252 g_signal_connect(tmp_button, "activate", G_CALLBACK(on_new_with_file_template), NULL);
254 g_free(label);
258 typedef struct
260 gint count;
261 GtkWidget *menu;
263 FTMenu;
265 static void populate_file_template_menu(GtkWidget *menu)
267 GSList *list = utils_get_config_files(GEANY_TEMPLATES_SUBDIR G_DIR_SEPARATOR_S "files");
268 GSList *node;
269 FTMenu *ft_groups;
270 gint nbytes = sizeof(FTMenu) * filetypes_array->len;
272 ft_groups = g_alloca(nbytes);
273 memset(ft_groups, 0, nbytes);
275 foreach_slist(node, list)
277 gchar *fname = node->data;
278 GeanyFiletype *ft = filetypes_detect_from_extension(fname);
280 ft_groups[ft->id].count++;
282 foreach_slist(node, list)
284 gchar *fname = node->data;
285 GeanyFiletype *ft = filetypes_detect_from_extension(fname);
286 FTMenu *group = &ft_groups[ft->id];
288 if (group->count == 1)
289 add_file_item(fname, menu);
290 else
292 if (!group->menu)
294 GtkWidget *item = gtk_menu_item_new_with_label(ft->name);
295 gtk_widget_show(item);
296 gtk_container_add(GTK_CONTAINER(menu), item);
297 group->menu = gtk_menu_new();
298 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), group->menu);
300 add_file_item(fname, group->menu);
302 g_free(fname);
304 g_slist_free(list);
305 ui_menu_sort_by_label(GTK_MENU(menu));
309 static void create_file_template_menu(void)
311 GtkWidget *item;
313 new_with_template_menu = gtk_menu_new();
314 item = ui_lookup_widget(main_widgets.window, "menu_new_with_template1");
315 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), new_with_template_menu);
317 new_with_template_toolbar_menu = gtk_menu_new();
318 g_object_ref(new_with_template_toolbar_menu);
319 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("New")),
320 new_with_template_toolbar_menu);
324 /* reload templates if any file in the templates path is saved */
325 static void on_document_save(G_GNUC_UNUSED GObject *object, GeanyDocument *doc)
327 gchar *path;
329 g_return_if_fail(!EMPTY(doc->real_path));
331 path = g_build_filename(app->configdir, GEANY_TEMPLATES_SUBDIR, NULL);
332 if (strncmp(doc->real_path, path, strlen(path)) == 0)
334 /* reload templates */
335 templates_free_templates();
336 templates_init();
338 g_free(path);
342 /* warning: also called when reloading template settings */
343 void templates_init(void)
345 static gboolean init_done = FALSE;
347 init_general_templates();
349 if (!init_done)
351 create_file_template_menu();
352 g_signal_connect(geany_object, "document-save", G_CALLBACK(on_document_save), NULL);
353 init_done = TRUE;
356 populate_file_template_menu(new_with_template_menu);
357 populate_file_template_menu(new_with_template_toolbar_menu);
361 /* indent is used to make some whitespace between comment char and real start of the line
362 * e.g. indent = 8 prints " * here comes the text of the line"
363 * indent is meant to be the whole amount of characters before the real line content follows, i.e.
364 * 6 characters are filled with whitespace when the comment characters include " *" */
365 static void make_comment_block(GString *comment_text, gint filetype_idx, guint indent)
367 gchar *frame_start; /* to add before comment_text */
368 gchar *frame_end; /* to add after comment_text */
369 const gchar *line_prefix; /* to add before every line in comment_text */
370 gchar *tmp;
371 gchar *prefix;
372 gchar **lines;
373 gsize i, len;
374 gint template_eol_mode;
375 const gchar *template_eol_char;
376 GeanyFiletype *ft = filetypes_index(filetype_idx);
377 const gchar *co;
378 const gchar *cc;
380 g_return_if_fail(comment_text != NULL);
381 g_return_if_fail(ft != NULL);
383 template_eol_mode = utils_get_line_endings(comment_text->str, comment_text->len);
384 template_eol_char = utils_get_eol_char(template_eol_mode);
386 filetype_get_comment_open_close(ft, FALSE, &co, &cc);
387 if (!EMPTY(co))
389 if (!EMPTY(cc))
391 frame_start = g_strconcat(co, template_eol_char, NULL);
392 frame_end = g_strconcat(cc, template_eol_char, NULL);
393 line_prefix = "";
395 else
397 frame_start = NULL;
398 frame_end = NULL;
399 line_prefix = co;
402 else
403 { /* use C-like multi-line comments as fallback */
404 frame_start = g_strconcat("/*", template_eol_char, NULL);
405 frame_end = g_strconcat("*/", template_eol_char, NULL);
406 line_prefix = "";
409 /* do some magic to nicely format C-like multi-line comments */
410 if (!EMPTY(frame_start) && frame_start[1] == '*')
412 /* prefix the string with a space */
413 SETPTR(frame_end, g_strconcat(" ", frame_end, NULL));
414 line_prefix = " *";
417 /* construct the real prefix with given amount of whitespace */
418 i = (indent > strlen(line_prefix)) ? (indent - strlen(line_prefix)) : strlen(line_prefix);
419 tmp = g_strnfill(i, ' ');
420 prefix = g_strconcat(line_prefix, tmp, NULL);
421 g_free(tmp);
423 /* add line_prefix to every line of comment_text */
424 lines = g_strsplit(comment_text->str, template_eol_char, -1);
425 len = g_strv_length(lines);
426 if (len > 0) /* prevent unsigned wraparound if comment_text is empty */
428 for (i = 0; i < len - 1; i++)
430 tmp = lines[i];
431 lines[i] = g_strconcat(prefix, tmp, NULL);
432 g_free(tmp);
435 tmp = g_strjoinv(template_eol_char, lines);
437 /* clear old contents */
438 g_string_erase(comment_text, 0, -1);
440 /* add frame_end */
441 if (frame_start != NULL)
442 g_string_append(comment_text, frame_start);
443 /* add the new main content */
444 g_string_append(comment_text, tmp);
445 /* add frame_start */
446 if (frame_end != NULL)
447 g_string_append(comment_text, frame_end);
449 utils_free_pointers(4, prefix, tmp, frame_start, frame_end, NULL);
450 g_strfreev(lines);
454 gchar *templates_get_template_licence(GeanyDocument *doc, gint licence_type)
456 GString *template;
458 g_return_val_if_fail(DOC_VALID(doc), NULL);
459 g_return_val_if_fail(licence_type == GEANY_TEMPLATE_GPL || licence_type == GEANY_TEMPLATE_BSD, NULL);
461 template = g_string_new(templates[licence_type]);
462 replace_static_values(template);
463 templates_replace_default_dates(template);
464 templates_replace_command(template, DOC_FILENAME(doc), doc->file_type->name, NULL);
466 make_comment_block(template, doc->file_type->id, GEANY_TEMPLATES_INDENT);
467 convert_eol_characters(template, doc);
469 return g_string_free(template, FALSE);
473 static gchar *get_template_fileheader(GeanyFiletype *ft)
475 GString *template = g_string_new(templates[GEANY_TEMPLATE_FILEHEADER]);
477 filetypes_load_config(ft->id, FALSE); /* load any user extension setting */
479 templates_replace_valist(template,
480 "{gpl}", templates[GEANY_TEMPLATE_GPL],
481 "{bsd}", templates[GEANY_TEMPLATE_BSD],
482 NULL);
484 /* we don't replace other wildcards here otherwise they would get done twice for files */
485 make_comment_block(template, ft->id, GEANY_TEMPLATES_INDENT);
486 return g_string_free(template, FALSE);
490 /* TODO change the signature to take a GeanyDocument? this would break plugin API/ABI */
491 GEANY_API_SYMBOL
492 gchar *templates_get_template_fileheader(gint filetype_idx, const gchar *fname)
494 GeanyFiletype *ft = filetypes[filetype_idx];
495 gchar *str = get_template_fileheader(ft);
496 GString *template = g_string_new(str);
498 g_free(str);
499 templates_replace_common(template, fname, ft, NULL);
500 convert_eol_characters(template, NULL);
501 return g_string_free(template, FALSE);
505 gchar *templates_get_template_function(GeanyDocument *doc, const gchar *func_name)
507 GString *text;
509 func_name = (func_name != NULL) ? func_name : "";
510 text = g_string_new(templates[GEANY_TEMPLATE_FUNCTION]);
512 templates_replace_valist(text, "{functionname}", func_name, NULL);
513 templates_replace_default_dates(text);
514 templates_replace_command(text, DOC_FILENAME(doc), doc->file_type->name, func_name);
516 make_comment_block(text, doc->file_type->id, GEANY_TEMPLATES_INDENT);
517 convert_eol_characters(text, doc);
519 return g_string_free(text, FALSE);
523 gchar *templates_get_template_changelog(GeanyDocument *doc)
525 GString *result;
526 const gchar *file_type_name;
528 g_return_val_if_fail(DOC_VALID(doc), NULL);
530 result = g_string_new(templates[GEANY_TEMPLATE_CHANGELOG]);
531 file_type_name = (doc->file_type != NULL) ? doc->file_type->name : "";
532 replace_static_values(result);
533 templates_replace_default_dates(result);
534 templates_replace_command(result, DOC_FILENAME(doc), file_type_name, NULL);
535 convert_eol_characters(result, doc);
537 return g_string_free(result, FALSE);
541 static void free_template_menu_items(GtkWidget *menu)
543 GList *children, *item;
545 children = gtk_container_get_children(GTK_CONTAINER(menu));
546 foreach_list(item, children)
547 gtk_widget_destroy(GTK_WIDGET(item->data));
548 g_list_free(children);
552 void templates_free_templates(void)
554 gint i;
556 for (i = 0; i < GEANY_MAX_TEMPLATES; i++)
557 g_free(templates[i]);
558 free_template_menu_items(new_with_template_menu);
559 free_template_menu_items(new_with_template_toolbar_menu);
563 static void replace_static_values(GString *text)
565 utils_string_replace_all(text, "{version}", template_prefs.version);
566 utils_string_replace_all(text, "{initial}", template_prefs.initials);
567 utils_string_replace_all(text, "{developer}", template_prefs.developer);
568 utils_string_replace_all(text, "{mail}", template_prefs.mail);
569 utils_string_replace_all(text, "{company}", template_prefs.company);
570 utils_string_replace_all(text, "{untitled}", GEANY_STRING_UNTITLED);
571 utils_string_replace_all(text, "{geanyversion}", PACKAGE_STRING);
575 /* Replaces all static template wildcards (version, mail, company, name, ...)
576 * plus those wildcard, value pairs which are passed, e.g.
578 * templates_replace_valist(text, "{some_wildcard}", "some value",
579 * "{another_wildcard}", "another value", NULL);
581 * The argument list must be terminated with NULL. */
582 void templates_replace_valist(GString *text, const gchar *first_wildcard, ...)
584 va_list args;
585 const gchar *key, *value;
587 g_return_if_fail(text != NULL);
589 va_start(args, first_wildcard);
591 key = first_wildcard;
592 value = va_arg(args, gchar*);
594 while (key != NULL)
596 utils_string_replace_all(text, key, value);
598 key = va_arg(args, gchar*);
599 if (key == NULL || text == NULL)
600 break;
601 value = va_arg(args, gchar*);
603 va_end(args);
605 replace_static_values(text);
609 static void templates_replace_default_dates(GString *text)
611 gchar *year = utils_get_date_time(template_prefs.year_format, NULL);
612 gchar *date = utils_get_date_time(template_prefs.date_format, NULL);
613 gchar *datetime = utils_get_date_time(template_prefs.datetime_format, NULL);
615 g_return_if_fail(text != NULL);
617 templates_replace_valist(text,
618 "{year}", year,
619 "{date}", date,
620 "{datetime}", datetime,
621 NULL);
623 utils_free_pointers(3, year, date, datetime, NULL);
627 static gchar *run_command(const gchar *command, const gchar *file_name,
628 const gchar *file_type, const gchar *func_name)
630 GString *output = g_string_new(NULL);
631 gchar *result = NULL;
632 GError *error = NULL;
633 gchar **env;
635 file_name = (file_name != NULL) ? file_name : "";
636 file_type = (file_type != NULL) ? file_type : "";
637 func_name = (func_name != NULL) ? func_name : "";
639 env = utils_copy_environment(NULL,
640 "GEANY_FILENAME", file_name,
641 "GEANY_FILETYPE", file_type,
642 "GEANY_FUNCNAME", func_name,
643 NULL);
645 if (spawn_sync(NULL, command, NULL, env, NULL, output, NULL, NULL, &error))
647 result = g_string_free(output, FALSE);
649 else
651 g_warning(_("Cannot execute template command \"%s\". "
652 "Hint: incorrect paths in the command are a common cause of errors. "
653 "Error: %s."), command, error->message);
654 g_error_free(error);
657 g_strfreev(env);
658 return result;
662 static void templates_replace_command(GString *text, const gchar *file_name,
663 const gchar *file_type, const gchar *func_name)
665 gchar *match;
667 g_return_if_fail(text != NULL);
669 while ((match = strstr(text->str, "{command:")) != NULL)
671 gchar *wildcard;
672 gchar *cmd = match;
673 gchar *result;
675 while (*match != '}' && *match != '\0')
676 match++;
678 wildcard = g_strndup(cmd, (gsize) (match - cmd + 1));
679 cmd = g_strndup(wildcard + 9, strlen(wildcard) - 10);
681 result = run_command(cmd, file_name, file_type, func_name);
682 if (result != NULL)
684 result = g_strstrip(result);
685 utils_string_replace_first(text, wildcard, result);
686 g_free(result);
688 else
689 utils_string_replace_first(text, wildcard, "");
691 g_free(wildcard);
692 g_free(cmd);