Merge pull request #232 from stefan-it/enhancement/lang-de-corrections
[geany-mirror.git] / src / templates.c
blob4233c0d0013f1a3bf4bf85ce88c2666c5677c335
1 /*
2 * templates.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * Templates to insert into the current document, or file templates to create a new
24 * document from.
27 #include <time.h>
28 #include <string.h>
30 #include "geany.h"
32 #include "templates.h"
33 #include "support.h"
34 #include "utils.h"
35 #include "document.h"
36 #include "encodings.h"
37 #include "editor.h"
38 #include "filetypes.h"
39 #include "ui_utils.h"
40 #include "toolbar.h"
41 #include "geanymenubuttonaction.h"
42 #include "project.h"
45 GeanyTemplatePrefs template_prefs;
47 static GtkWidget *new_with_template_menu = NULL; /* submenu used for both file menu and toolbar */
49 /* TODO: implement custom insertion templates instead? */
50 static gchar *templates[GEANY_MAX_TEMPLATES];
53 static void replace_static_values(GString *text);
54 static gchar *get_template_fileheader(GeanyFiletype *ft);
56 /* called by templates_replace_common */
57 static void templates_replace_default_dates(GString *text);
58 static void templates_replace_command(GString *text, const gchar *file_name,
59 const gchar *file_type, const gchar *func_name);
62 static gchar *read_file(const gchar *locale_fname)
64 gchar *contents;
65 gsize length;
66 GString *str;
68 if (! g_file_get_contents(locale_fname, &contents, &length, NULL))
69 return NULL;
71 if (! encodings_convert_to_utf8_auto(&contents, &length, NULL, NULL, NULL, NULL))
73 gchar *utf8_fname = utils_get_utf8_from_locale(locale_fname);
75 ui_set_statusbar(TRUE, _("Failed to convert template file \"%s\" to UTF-8"), utf8_fname);
76 g_free(utf8_fname);
77 g_free(contents);
78 return NULL;
81 str = g_string_new(contents);
82 g_free(contents);
84 /* convert to LF endings for consistency in mixing templates */
85 utils_ensure_same_eol_characters(str, SC_EOL_LF);
86 return g_string_free(str, FALSE);
90 static void read_template(const gchar *name, gint id)
92 gchar *fname = g_build_path(G_DIR_SEPARATOR_S, app->configdir,
93 GEANY_TEMPLATES_SUBDIR, name, NULL);
95 /* try system if user template doesn't exist */
96 if (!g_file_test(fname, G_FILE_TEST_EXISTS))
97 SETPTR(fname, g_build_path(G_DIR_SEPARATOR_S, app->datadir,
98 GEANY_TEMPLATES_SUBDIR, name, NULL));
100 templates[id] = read_file(fname);
101 g_free(fname);
105 /* called when inserting templates into an existing document */
106 static void convert_eol_characters(GString *template, GeanyDocument *doc)
108 gint doc_eol_mode;
110 g_return_if_fail(doc == NULL || doc->is_valid);
112 if (doc == NULL)
113 doc = document_get_current();
115 g_return_if_fail(doc != NULL);
117 doc_eol_mode = editor_get_eol_char_mode(doc->editor);
118 utils_ensure_same_eol_characters(template, doc_eol_mode);
122 static void init_general_templates(void)
124 /* read the contents */
125 read_template("fileheader", GEANY_TEMPLATE_FILEHEADER);
126 read_template("gpl", GEANY_TEMPLATE_GPL);
127 read_template("bsd", GEANY_TEMPLATE_BSD);
128 read_template("function", GEANY_TEMPLATE_FUNCTION);
129 read_template("changelog", GEANY_TEMPLATE_CHANGELOG);
133 void templates_replace_common(GString *tmpl, const gchar *fname,
134 GeanyFiletype *ft, const gchar *func_name)
136 gchar *shortname;
138 if (fname == NULL)
140 if (!ft->extension)
141 shortname = g_strdup(GEANY_STRING_UNTITLED);
142 else
143 shortname = g_strconcat(GEANY_STRING_UNTITLED, ".", ft->extension, NULL);
145 else
146 shortname = g_path_get_basename(fname);
148 templates_replace_valist(tmpl,
149 "{filename}", shortname,
150 "{project}", app->project ? app->project->name : "",
151 "{description}", app->project ? app->project->description : "",
152 NULL);
153 g_free(shortname);
155 templates_replace_default_dates(tmpl);
156 templates_replace_command(tmpl, fname, ft->name, func_name);
157 /* Bug: command results could have {ob} {cb} strings in! */
158 /* replace braces last */
159 templates_replace_valist(tmpl,
160 "{ob}", "{",
161 "{cb}", "}",
162 NULL);
166 static gchar *get_template_from_file(const gchar *locale_fname, const gchar *doc_filename,
167 GeanyFiletype *ft)
169 gchar *content;
170 GString *template = NULL;
172 content = read_file(locale_fname);
174 if (content != NULL)
176 gchar *file_header;
178 template = g_string_new(content);
180 file_header = get_template_fileheader(ft);
181 templates_replace_valist(template,
182 "{fileheader}", file_header,
183 NULL);
184 templates_replace_common(template, doc_filename, ft, NULL);
186 utils_free_pointers(2, file_header, content, NULL);
187 return g_string_free(template, FALSE);
189 return NULL;
193 static void
194 on_new_with_file_template(GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
196 gchar *fname = ui_menu_item_get_text(menuitem);
197 GeanyFiletype *ft;
198 gchar *template;
199 const gchar *extension = strrchr(fname, '.'); /* easy way to get the file extension */
200 gchar *new_filename = g_strconcat(GEANY_STRING_UNTITLED, extension, NULL);
201 gchar *path;
203 ft = filetypes_detect_from_extension(fname);
204 SETPTR(fname, utils_get_locale_from_utf8(fname));
206 /* fname is just the basename from the menu item, so prepend the custom files path */
207 path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, GEANY_TEMPLATES_SUBDIR,
208 "files", fname, NULL);
209 template = get_template_from_file(path, new_filename, ft);
210 if (!template)
212 /* try the system path */
213 g_free(path);
214 path = g_build_path(G_DIR_SEPARATOR_S, app->datadir, GEANY_TEMPLATES_SUBDIR,
215 "files", fname, NULL);
216 template = get_template_from_file(path, new_filename, ft);
218 if (template)
220 /* line endings will be converted */
221 document_new_file(new_filename, ft, template);
223 else
225 SETPTR(fname, utils_get_utf8_from_locale(fname));
226 ui_set_statusbar(TRUE, _("Could not find file '%s'."), fname);
228 g_free(template);
229 g_free(path);
230 g_free(new_filename);
231 g_free(fname);
235 static void add_file_item(const gchar *fname, GtkWidget *menu)
237 GtkWidget *tmp_button;
238 gchar *label;
240 g_return_if_fail(fname);
241 g_return_if_fail(menu);
243 label = utils_get_utf8_from_locale(fname);
245 tmp_button = gtk_menu_item_new_with_label(label);
246 gtk_widget_show(tmp_button);
247 gtk_container_add(GTK_CONTAINER(menu), tmp_button);
248 g_signal_connect(tmp_button, "activate", G_CALLBACK(on_new_with_file_template), NULL);
250 g_free(label);
254 static gboolean add_custom_template_items(void)
256 GSList *list = utils_get_config_files(GEANY_TEMPLATES_SUBDIR G_DIR_SEPARATOR_S "files");
257 GSList *node;
259 foreach_slist(node, list)
261 gchar *fname = node->data;
263 add_file_item(fname, new_with_template_menu);
264 g_free(fname);
266 g_slist_free(list);
267 return list != NULL;
271 static void create_file_template_menu(void)
273 new_with_template_menu = gtk_menu_new();
274 add_custom_template_items();
276 /* unless the file menu is showing, menu should be in the toolbar widget */
277 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
278 toolbar_get_action_by_name("New")), new_with_template_menu);
282 static void on_file_menu_show(GtkWidget *item)
284 geany_menu_button_action_set_menu(
285 GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("New")), NULL);
286 item = ui_lookup_widget(main_widgets.window, "menu_new_with_template1");
287 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), new_with_template_menu);
291 static void on_file_menu_hide(GtkWidget *item)
293 item = ui_lookup_widget(main_widgets.window, "menu_new_with_template1");
294 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), NULL);
295 geany_menu_button_action_set_menu(
296 GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("New")), new_with_template_menu);
300 /* reload templates if any file in the templates path is saved */
301 static void on_document_save(G_GNUC_UNUSED GObject *object, GeanyDocument *doc)
303 gchar *path = g_build_filename(app->configdir, GEANY_TEMPLATES_SUBDIR, NULL);
305 g_return_if_fail(!EMPTY(doc->real_path));
307 if (strncmp(doc->real_path, path, strlen(path)) == 0)
309 /* reload templates */
310 templates_free_templates();
311 templates_init();
313 g_free(path);
317 /* warning: also called when reloading template settings */
318 void templates_init(void)
320 static gboolean init_done = FALSE;
322 init_general_templates();
324 create_file_template_menu();
325 /* we hold our own ref for the menu as it has no parent whilst being moved */
326 g_object_ref(new_with_template_menu);
328 /* only connect signals to persistent objects once */
329 if (!init_done)
331 GtkWidget *item;
332 /* reparent the template menu as needed */
333 item = ui_lookup_widget(main_widgets.window, "file1");
334 item = gtk_menu_item_get_submenu(GTK_MENU_ITEM(item));
335 g_signal_connect(item, "show", G_CALLBACK(on_file_menu_show), NULL);
336 g_signal_connect(item, "hide", G_CALLBACK(on_file_menu_hide), NULL);
338 g_signal_connect(geany_object, "document-save", G_CALLBACK(on_document_save), NULL);
340 init_done = TRUE;
344 /* indent is used to make some whitespace between comment char and real start of the line
345 * e.g. indent = 8 prints " * here comes the text of the line"
346 * indent is meant to be the whole amount of characters before the real line content follows, i.e.
347 * 6 characters are filled with whitespace when the comment characters include " *" */
348 static void make_comment_block(GString *comment_text, gint filetype_idx, guint indent)
350 gchar *frame_start; /* to add before comment_text */
351 gchar *frame_end; /* to add after comment_text */
352 const gchar *line_prefix; /* to add before every line in comment_text */
353 gchar *tmp;
354 gchar *prefix;
355 gchar **lines;
356 gsize i, len;
357 gint template_eol_mode;
358 const gchar *template_eol_char;
359 GeanyFiletype *ft = filetypes_index(filetype_idx);
360 const gchar *co;
361 const gchar *cc;
363 g_return_if_fail(comment_text != NULL);
364 g_return_if_fail(ft != NULL);
366 template_eol_mode = utils_get_line_endings(comment_text->str, comment_text->len);
367 template_eol_char = utils_get_eol_char(template_eol_mode);
369 filetype_get_comment_open_close(ft, FALSE, &co, &cc);
370 if (!EMPTY(co))
372 if (!EMPTY(cc))
374 frame_start = g_strconcat(co, template_eol_char, NULL);
375 frame_end = g_strconcat(cc, template_eol_char, NULL);
376 line_prefix = "";
378 else
380 frame_start = NULL;
381 frame_end = NULL;
382 line_prefix = co;
385 else
386 { /* use C-like multi-line comments as fallback */
387 frame_start = g_strconcat("/*", template_eol_char, NULL);
388 frame_end = g_strconcat("*/", template_eol_char, NULL);
389 line_prefix = "";
392 /* do some magic to nicely format C-like multi-line comments */
393 if (!EMPTY(frame_start) && frame_start[1] == '*')
395 /* prefix the string with a space */
396 SETPTR(frame_end, g_strconcat(" ", frame_end, NULL));
397 line_prefix = " *";
400 /* construct the real prefix with given amount of whitespace */
401 i = (indent > strlen(line_prefix)) ? (indent - strlen(line_prefix)) : strlen(line_prefix);
402 tmp = g_strnfill(i, ' ');
403 prefix = g_strconcat(line_prefix, tmp, NULL);
404 g_free(tmp);
406 /* add line_prefix to every line of comment_text */
407 lines = g_strsplit(comment_text->str, template_eol_char, -1);
408 len = g_strv_length(lines);
409 if (len > 0) /* prevent unsigned wraparound if comment_text is empty */
411 for (i = 0; i < len - 1; i++)
413 tmp = lines[i];
414 lines[i] = g_strconcat(prefix, tmp, NULL);
415 g_free(tmp);
418 tmp = g_strjoinv(template_eol_char, lines);
420 /* clear old contents */
421 g_string_erase(comment_text, 0, -1);
423 /* add frame_end */
424 if (frame_start != NULL)
425 g_string_append(comment_text, frame_start);
426 /* add the new main content */
427 g_string_append(comment_text, tmp);
428 /* add frame_start */
429 if (frame_end != NULL)
430 g_string_append(comment_text, frame_end);
432 utils_free_pointers(4, prefix, tmp, frame_start, frame_end, NULL);
433 g_strfreev(lines);
437 gchar *templates_get_template_licence(GeanyDocument *doc, gint licence_type)
439 GString *template;
441 g_return_val_if_fail(DOC_VALID(doc), NULL);
442 g_return_val_if_fail(licence_type == GEANY_TEMPLATE_GPL || licence_type == GEANY_TEMPLATE_BSD, NULL);
444 template = g_string_new(templates[licence_type]);
445 replace_static_values(template);
446 templates_replace_default_dates(template);
447 templates_replace_command(template, DOC_FILENAME(doc), doc->file_type->name, NULL);
449 make_comment_block(template, doc->file_type->id, GEANY_TEMPLATES_INDENT);
450 convert_eol_characters(template, doc);
452 return g_string_free(template, FALSE);
456 static gchar *get_template_fileheader(GeanyFiletype *ft)
458 GString *template = g_string_new(templates[GEANY_TEMPLATE_FILEHEADER]);
460 filetypes_load_config(ft->id, FALSE); /* load any user extension setting */
462 templates_replace_valist(template,
463 "{gpl}", templates[GEANY_TEMPLATE_GPL],
464 "{bsd}", templates[GEANY_TEMPLATE_BSD],
465 NULL);
467 /* we don't replace other wildcards here otherwise they would get done twice for files */
468 make_comment_block(template, ft->id, GEANY_TEMPLATES_INDENT);
469 return g_string_free(template, FALSE);
473 /* TODO change the signature to take a GeanyDocument? this would break plugin API/ABI */
474 gchar *templates_get_template_fileheader(gint filetype_idx, const gchar *fname)
476 GeanyFiletype *ft = filetypes[filetype_idx];
477 gchar *str = get_template_fileheader(ft);
478 GString *template = g_string_new(str);
480 g_free(str);
481 templates_replace_common(template, fname, ft, NULL);
482 convert_eol_characters(template, NULL);
483 return g_string_free(template, FALSE);
487 gchar *templates_get_template_function(GeanyDocument *doc, const gchar *func_name)
489 GString *text;
491 func_name = (func_name != NULL) ? func_name : "";
492 text = g_string_new(templates[GEANY_TEMPLATE_FUNCTION]);
494 templates_replace_valist(text, "{functionname}", func_name, NULL);
495 templates_replace_default_dates(text);
496 templates_replace_command(text, DOC_FILENAME(doc), doc->file_type->name, func_name);
498 make_comment_block(text, doc->file_type->id, GEANY_TEMPLATES_INDENT);
499 convert_eol_characters(text, doc);
501 return g_string_free(text, FALSE);
505 gchar *templates_get_template_changelog(GeanyDocument *doc)
507 GString *result;
508 const gchar *file_type_name;
510 g_return_val_if_fail(DOC_VALID(doc), NULL);
512 result = g_string_new(templates[GEANY_TEMPLATE_CHANGELOG]);
513 file_type_name = (doc->file_type != NULL) ? doc->file_type->name : "";
514 replace_static_values(result);
515 templates_replace_default_dates(result);
516 templates_replace_command(result, DOC_FILENAME(doc), file_type_name, NULL);
517 convert_eol_characters(result, doc);
519 return g_string_free(result, FALSE);
523 void templates_free_templates(void)
525 gint i;
526 GList *children, *item;
528 /* disconnect the menu from the action widget, so destroying the items below doesn't
529 * trigger rebuilding of the menu on each item destroy */
530 geany_menu_button_action_set_menu(
531 GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("New")), NULL);
533 for (i = 0; i < GEANY_MAX_TEMPLATES; i++)
535 g_free(templates[i]);
537 /* destroy "New with template" sub menu items (in case we want to reload the templates) */
538 children = gtk_container_get_children(GTK_CONTAINER(new_with_template_menu));
539 foreach_list(item, children)
541 gtk_widget_destroy(GTK_WIDGET(item->data));
543 g_list_free(children);
545 g_object_unref(new_with_template_menu);
546 new_with_template_menu = NULL;
550 static void replace_static_values(GString *text)
552 utils_string_replace_all(text, "{version}", template_prefs.version);
553 utils_string_replace_all(text, "{initial}", template_prefs.initials);
554 utils_string_replace_all(text, "{developer}", template_prefs.developer);
555 utils_string_replace_all(text, "{mail}", template_prefs.mail);
556 utils_string_replace_all(text, "{company}", template_prefs.company);
557 utils_string_replace_all(text, "{untitled}", GEANY_STRING_UNTITLED);
558 utils_string_replace_all(text, "{geanyversion}", "Geany " VERSION);
562 /* Replaces all static template wildcards (version, mail, company, name, ...)
563 * plus those wildcard, value pairs which are passed, e.g.
565 * templates_replace_valist(text, "{some_wildcard}", "some value",
566 * "{another_wildcard}", "another value", NULL);
568 * The argument list must be terminated with NULL. */
569 void templates_replace_valist(GString *text, const gchar *first_wildcard, ...)
571 va_list args;
572 const gchar *key, *value;
574 g_return_if_fail(text != NULL);
576 va_start(args, first_wildcard);
578 key = first_wildcard;
579 value = va_arg(args, gchar*);
581 while (key != NULL)
583 utils_string_replace_all(text, key, value);
585 key = va_arg(args, gchar*);
586 if (key == NULL || text == NULL)
587 break;
588 value = va_arg(args, gchar*);
590 va_end(args);
592 replace_static_values(text);
596 static void templates_replace_default_dates(GString *text)
598 gchar *year = utils_get_date_time(template_prefs.year_format, NULL);
599 gchar *date = utils_get_date_time(template_prefs.date_format, NULL);
600 gchar *datetime = utils_get_date_time(template_prefs.datetime_format, NULL);
602 g_return_if_fail(text != NULL);
604 templates_replace_valist(text,
605 "{year}", year,
606 "{date}", date,
607 "{datetime}", datetime,
608 NULL);
610 utils_free_pointers(3, year, date, datetime, NULL);
614 static gchar *run_command(const gchar *command, const gchar *file_name,
615 const gchar *file_type, const gchar *func_name)
617 gchar *result = NULL;
618 gchar **argv;
620 if (g_shell_parse_argv(command, NULL, &argv, NULL))
622 GError *error = NULL;
623 gchar **env;
625 file_name = (file_name != NULL) ? file_name : "";
626 file_type = (file_type != NULL) ? file_type : "";
627 func_name = (func_name != NULL) ? func_name : "";
629 env = utils_copy_environment(NULL,
630 "GEANY_FILENAME", file_name,
631 "GEANY_FILETYPE", file_type,
632 "GEANY_FUNCNAME", func_name,
633 NULL);
634 if (! utils_spawn_sync(NULL, argv, env, G_SPAWN_SEARCH_PATH,
635 NULL, NULL, &result, NULL, NULL, &error))
637 g_warning("templates_replace_command: %s", error->message);
638 g_error_free(error);
639 result = NULL;
641 g_strfreev(argv);
642 g_strfreev(env);
644 return result;
648 static void templates_replace_command(GString *text, const gchar *file_name,
649 const gchar *file_type, const gchar *func_name)
651 gchar *match = NULL;
652 gchar *wildcard = NULL;
653 gchar *cmd;
654 gchar *result;
656 g_return_if_fail(text != NULL);
658 while ((match = strstr(text->str, "{command:")) != NULL)
660 cmd = match;
661 while (*match != '}' && *match != '\0')
662 match++;
664 wildcard = g_strndup(cmd, (gsize) (match - cmd + 1));
665 cmd = g_strndup(wildcard + 9, strlen(wildcard) - 10);
667 result = run_command(cmd, file_name, file_type, func_name);
668 if (result != NULL)
670 result = g_strstrip(result);
671 utils_string_replace_first(text, wildcard, result);
672 g_free(result);
674 else
675 utils_string_replace_first(text, wildcard, "");
677 g_free(wildcard);
678 g_free(cmd);