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
30 #include "templates.h"
34 #include "encodingsprivate.h"
35 #include "filetypes.h"
37 #include "geanymenubuttonaction.h"
38 #include "geanyobject.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
)
74 if (! g_file_get_contents(locale_fname
, &contents
, &length
, 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
);
87 str
= g_string_new(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
);
111 /* called when inserting templates into an existing document */
112 static void convert_eol_characters(GString
*template, GeanyDocument
*doc
)
116 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
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
)
147 shortname
= g_strdup(GEANY_STRING_UNTITLED
);
149 shortname
= g_strconcat(GEANY_STRING_UNTITLED
, ".", ft
->extension
, NULL
);
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
: "",
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
,
172 static gchar
*get_template_from_file(const gchar
*locale_fname
, const gchar
*doc_filename
,
177 content
= read_file(locale_fname
);
182 GString
*template = g_string_new(content
);
184 file_header
= get_template_fileheader(ft
);
185 templates_replace_valist(template,
186 "{fileheader}", file_header
,
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
);
198 on_new_with_file_template(GtkMenuItem
*menuitem
, G_GNUC_UNUSED gpointer user_data
)
200 gchar
*fname
= ui_menu_item_get_text(menuitem
);
203 const gchar
*extension
= strrchr(fname
, '.'); /* easy way to get the file extension */
204 gchar
*new_filename
= g_strconcat(GEANY_STRING_UNTITLED
, extension
, NULL
);
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
);
216 /* try the system 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
);
224 /* line endings will be converted */
225 document_new_file(new_filename
, ft
, template);
229 SETPTR(fname
, utils_get_utf8_from_locale(fname
));
230 ui_set_statusbar(TRUE
, _("Could not find file '%s'."), fname
);
234 g_free(new_filename
);
239 static void add_file_item(const gchar
*fname
, GtkWidget
*menu
)
241 GtkWidget
*tmp_button
;
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
);
265 static void populate_file_template_menu(GtkWidget
*menu
)
267 GSList
*list
= utils_get_config_files(GEANY_TEMPLATES_SUBDIR G_DIR_SEPARATOR_S
"files");
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
);
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
);
305 ui_menu_sort_by_label(GTK_MENU(menu
));
309 static void create_file_template_menu(void)
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
)
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();
342 /* warning: also called when reloading template settings */
343 void templates_init(void)
345 static gboolean init_done
= FALSE
;
347 init_general_templates();
351 create_file_template_menu();
352 g_signal_connect(geany_object
, "document-save", G_CALLBACK(on_document_save
), NULL
);
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 */
374 gint template_eol_mode
;
375 const gchar
*template_eol_char
;
376 GeanyFiletype
*ft
= filetypes_index(filetype_idx
);
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
);
391 frame_start
= g_strconcat(co
, template_eol_char
, NULL
);
392 frame_end
= g_strconcat(cc
, template_eol_char
, NULL
);
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
);
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
));
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
);
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
++)
431 lines
[i
] = g_strconcat(prefix
, tmp
, NULL
);
435 tmp
= g_strjoinv(template_eol_char
, lines
);
437 /* clear old contents */
438 g_string_erase(comment_text
, 0, -1);
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
);
454 gchar
*templates_get_template_licence(GeanyDocument
*doc
, gint licence_type
)
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
],
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 */
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
);
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
)
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
)
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)
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
, ...)
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
*);
596 utils_string_replace_all(text
, key
, value
);
598 key
= va_arg(args
, gchar
*);
599 if (key
== NULL
|| text
== NULL
)
601 value
= va_arg(args
, gchar
*);
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
,
620 "{datetime}", datetime
,
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
;
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
,
645 if (spawn_sync(NULL
, command
, NULL
, env
, NULL
, output
, NULL
, NULL
, &error
))
647 result
= g_string_free(output
, FALSE
);
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
);
662 static void templates_replace_command(GString
*text
, const gchar
*file_name
,
663 const gchar
*file_type
, const gchar
*func_name
)
667 g_return_if_fail(text
!= NULL
);
669 while ((match
= strstr(text
->str
, "{command:")) != NULL
)
675 while (*match
!= '}' && *match
!= '\0')
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
);
684 result
= g_strstrip(result
);
685 utils_string_replace_first(text
, wildcard
, result
);
689 utils_string_replace_first(text
, wildcard
, "");