1 /*
2  *      filetypes.c - this file is part of Geany, a fast and lightweight IDE
3  *
4  *      Copyright 2005 The Geany contributors
5  *
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.
10  *
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.
15  *
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.
19  */
20 
21 /**
22  * @file filetypes.h
23  * Filetype detection, file extensions and filetype menu items.
24  */
25 
26 /* Note: we use GeanyFiletypeID for some function arguments, but GeanyFiletype is better; we should
27  * only use GeanyFiletype for API functions. */
28 
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32 
33 #include "filetypes.h"
34 
35 #include "app.h"
36 #include "callbacks.h" /* FIXME: for ignore_callback */
37 #include "document.h"
38 #include "filetypesprivate.h"
39 #include "geany.h"
40 #include "geanyobject.h"
41 #include "highlighting.h"
42 #include "projectprivate.h"
43 #include "sciwrappers.h"
44 #include "support.h"
45 #include "symbols.h"
46 #include "tm_parser.h"
47 #include "utils.h"
48 #include "ui_utils.h"
49 
50 #include <stdlib.h>
51 #include <string.h>
52 
53 #include <glib/gstdio.h>
54 
55 #define GEANY_FILETYPE_SEARCH_LINES 2 /* lines of file to search for filetype */
56 
57 GPtrArray *filetypes_array = NULL;
58 static GHashTable *filetypes_hash = NULL;	/* Hash of filetype pointers based on name keys */
59 GSList *filetypes_by_title = NULL;
60 
61 
62 static void create_radio_menu_item(GtkWidget *menu, GeanyFiletype *ftype);
63 
64 static gchar *filetypes_get_conf_extension(const GeanyFiletype *ft);
65 static void read_filetype_config(void);
66 static void create_set_filetype_menu(gboolean config);
67 static gchar *filetypes_get_filename(GeanyFiletype *ft, gboolean user);
68 
69 
70 enum TitleType
71 {
72 	TITLE_NONE,
73 	TITLE_SOURCE_FILE,
74 	TITLE_FILE,
75 	TITLE_SCRIPT,
76 	TITLE_DOCUMENT
77 };
78 
79 /* Save adding many translation strings if the filetype name doesn't need translating */
filetype_make_title(const char * name,enum TitleType type)80 static gchar *filetype_make_title(const char *name, enum TitleType type)
81 {
82 	g_return_val_if_fail(name != NULL, NULL);
83 
84 	switch (type)
85 	{
86 		case TITLE_SOURCE_FILE:	return g_strdup_printf(_("%s source file"), name);
87 		case TITLE_FILE:		return g_strdup_printf(_("%s file"), name);
88 		case TITLE_SCRIPT:		return g_strdup_printf(_("%s script"), name);
89 		case TITLE_DOCUMENT:	return g_strdup_printf(_("%s document"), name);
90 		case TITLE_NONE: /* fall through */
91 		default:				return g_strdup(name);
92 	}
93 }
94 
95 
96 /* name argument (ie filetype name) must not be translated as it is used for
97  * filetype lookup. Use filetypes_get_display_name() instead.*/
ft_init(GeanyFiletypeID ft_id,TMParserType lang,const char * name,const char * title_name,enum TitleType title_type,GeanyFiletypeGroupID group_id)98 static void ft_init(GeanyFiletypeID ft_id, TMParserType lang, const char *name,
99 	const char *title_name, enum TitleType title_type,
100 	GeanyFiletypeGroupID group_id)
101 {
102 	GeanyFiletype *ft = filetypes[ft_id];
103 	ft->lang = lang;
104 	ft->name = g_strdup(name);
105 	ft->title = filetype_make_title((title_name != NULL) ? title_name : ft->name, title_type);
106 	ft->group = group_id;
107 }
108 
109 /* Evil macro to save typing and make init_builtin_filetypes() more readable */
110 #define FT_INIT(ft_id, parser_id, name, title_name, title_type, group_id) \
111 	ft_init(GEANY_FILETYPES_##ft_id, TM_PARSER_##parser_id, name, title_name, \
112 		TITLE_##title_type, GEANY_FILETYPE_GROUP_##group_id)
113 
114 
115 /* Note: remember to update HACKING if this function is renamed. */
init_builtin_filetypes(void)116 static void init_builtin_filetypes(void)
117 {
118 	/* Column legend:
119 	 *   [0] = Filetype constant (GEANY_FILETYPES_*)
120 	 *   [1] = CTags parser (TM_PARSER_*)
121 	 *   [2] = Non-translated filetype name (*not* label for display)
122 	 *   [3] = Translatable human filetype title prefix or NULL to use [2]
123 	 *   [4] = Title type (TITLE_*) constant (ex. TITLE_SOURCE_FILE is 'source file' suffix)
124 	 *   [5] = The filetype group constant (GEANY_FILETYPE_GROUP_*)
125 	 * --------------------------------------------------------------------------------------------------------------------------
126 	 *       [0]         [1]           [2]                 [3]                        [4]          [5]      */
127 	FT_INIT( NONE,       NONE,         "None",             _("None"),                 NONE,        NONE     );
128 	FT_INIT( C,          C,            "C",                NULL,                      SOURCE_FILE, COMPILED );
129 	FT_INIT( CPP,        CPP,          "C++",              NULL,                      SOURCE_FILE, COMPILED );
130 	FT_INIT( OBJECTIVEC, OBJC,         "Objective-C",      NULL,                      SOURCE_FILE, COMPILED );
131 	FT_INIT( CS,         CSHARP,       "C#",               NULL,                      SOURCE_FILE, COMPILED );
132 	FT_INIT( VALA,       VALA,         "Vala",             NULL,                      SOURCE_FILE, COMPILED );
133 	FT_INIT( D,          D,            "D",                NULL,                      SOURCE_FILE, COMPILED );
134 	FT_INIT( JAVA,       JAVA,         "Java",             NULL,                      SOURCE_FILE, COMPILED );
135 	FT_INIT( PASCAL,     PASCAL,       "Pascal",           NULL,                      SOURCE_FILE, COMPILED );
136 	FT_INIT( ASM,        ASM,          "ASM",              "Assembler",               SOURCE_FILE, COMPILED );
137 	FT_INIT( BASIC,      FREEBASIC,    "FreeBasic",        NULL,                      SOURCE_FILE, COMPILED );
138 	FT_INIT( FORTRAN,    FORTRAN,      "Fortran",          "Fortran (F90)",           SOURCE_FILE, COMPILED );
139 	FT_INIT( F77,        F77,          "F77",              "Fortran (F77)",           SOURCE_FILE, COMPILED );
140 	FT_INIT( GLSL,       GLSL,         "GLSL",             NULL,                      SOURCE_FILE, COMPILED );
141 	FT_INIT( CAML,       NONE,         "CAML",             "(O)Caml",                 SOURCE_FILE, COMPILED );
142 	FT_INIT( PERL,       PERL,         "Perl",             NULL,                      SOURCE_FILE, SCRIPT   );
143 	FT_INIT( PHP,        PHP,          "PHP",              NULL,                      SOURCE_FILE, SCRIPT   );
144 	FT_INIT( JS,         JAVASCRIPT,   "Javascript",       NULL,                      SOURCE_FILE, SCRIPT   );
145 	FT_INIT( PYTHON,     PYTHON,       "Python",           NULL,                      SOURCE_FILE, SCRIPT   );
146 	FT_INIT( RUBY,       RUBY,         "Ruby",             NULL,                      SOURCE_FILE, SCRIPT   );
147 	FT_INIT( TCL,        TCL,          "Tcl",              NULL,                      SOURCE_FILE, SCRIPT   );
148 	FT_INIT( LUA,        LUA,          "Lua",              NULL,                      SOURCE_FILE, SCRIPT   );
149 	FT_INIT( FERITE,     FERITE,       "Ferite",           NULL,                      SOURCE_FILE, SCRIPT   );
150 	FT_INIT( HASKELL,    HASKELL,      "Haskell",          NULL,                      SOURCE_FILE, COMPILED );
151 	FT_INIT( MARKDOWN,   MARKDOWN,     "Markdown",         NULL,                      SOURCE_FILE, MARKUP   );
152 	FT_INIT( TXT2TAGS,   TXT2TAGS,     "Txt2tags",         NULL,                      SOURCE_FILE, MARKUP   );
153 	FT_INIT( ABC,        ABC,          "Abc",              NULL,                      FILE,        MISC     );
154 	FT_INIT( SH,         SH,           "Sh",               _("Shell"),                SCRIPT,      SCRIPT   );
155 	FT_INIT( MAKE,       MAKEFILE,     "Make",             _("Makefile"),             NONE,        SCRIPT   );
156 	FT_INIT( XML,        NONE,         "XML",              NULL,                      DOCUMENT,    MARKUP   );
157 	FT_INIT( DOCBOOK,    DOCBOOK,      "Docbook",          NULL,                      DOCUMENT,    MARKUP   );
158 	FT_INIT( HTML,       HTML,         "HTML",             NULL,                      DOCUMENT,    MARKUP   );
159 	FT_INIT( CSS,        CSS,          "CSS",              _("Cascading Stylesheet"), NONE,        MARKUP   ); /* not really markup but fit quite well to HTML */
160 	FT_INIT( SQL,        SQL,          "SQL",              NULL,                      FILE,        MISC     );
161 	FT_INIT( COBOL,      COBOL,        "COBOL",            NULL,                      SOURCE_FILE, COMPILED );
162 	FT_INIT( LATEX,      LATEX,        "LaTeX",            NULL,                      SOURCE_FILE, MARKUP   );
163 	FT_INIT( BIBTEX,     BIBTEX,       "BibTeX",           NULL,                      SOURCE_FILE, MARKUP   );
164 	FT_INIT( VHDL,       VHDL,         "VHDL",             NULL,                      SOURCE_FILE, COMPILED );
165 	FT_INIT( VERILOG,    VERILOG,      "Verilog",          NULL,                      SOURCE_FILE, COMPILED );
166 	FT_INIT( DIFF,       DIFF,         "Diff",             NULL,                      FILE,        MISC     );
167 	FT_INIT( LISP,       NONE,         "Lisp",             NULL,                      SOURCE_FILE, SCRIPT   );
168 	FT_INIT( ERLANG,     ERLANG,       "Erlang",           NULL,                      SOURCE_FILE, COMPILED );
169 	FT_INIT( CONF,       CONF,         "Conf",             _("Config"),               FILE,        MISC     );
170 	FT_INIT( PO,         NONE,         "Po",               _("Gettext translation"),  FILE,        MISC     );
171 	FT_INIT( HAXE,       HAXE,         "Haxe",             NULL,                      SOURCE_FILE, COMPILED );
172 	FT_INIT( AS,         ACTIONSCRIPT, "ActionScript",     NULL,                      SOURCE_FILE, SCRIPT   );
173 	FT_INIT( R,          R,            "R",                NULL,                      SOURCE_FILE, SCRIPT   );
174 	FT_INIT( REST,       REST,         "reStructuredText", NULL,                      SOURCE_FILE, MARKUP   );
175 	FT_INIT( MATLAB,     MATLAB,       "Matlab/Octave",    NULL,                      SOURCE_FILE, SCRIPT   );
176 	FT_INIT( YAML,       NONE,         "YAML",             NULL,                      FILE,        MISC     );
177 	FT_INIT( CMAKE,      NONE,         "CMake",            NULL,                      SOURCE_FILE, SCRIPT   );
178 	FT_INIT( NSIS,       NSIS,         "NSIS",             NULL,                      SOURCE_FILE, SCRIPT   );
179 	FT_INIT( ADA,        NONE,         "Ada",              NULL,                      SOURCE_FILE, COMPILED );
180 	FT_INIT( FORTH,      NONE,         "Forth",            NULL,                      SOURCE_FILE, SCRIPT   );
181 	FT_INIT( ASCIIDOC,   ASCIIDOC,     "Asciidoc",         NULL,                      SOURCE_FILE, MARKUP   );
182 	FT_INIT( ABAQUS,     ABAQUS,       "Abaqus",           NULL,                      SOURCE_FILE, SCRIPT   );
183 	FT_INIT( BATCH,      NONE,         "Batch",            NULL,                      SCRIPT,      SCRIPT   );
184 	FT_INIT( POWERSHELL, POWERSHELL,   "PowerShell",       NULL,                      SOURCE_FILE, SCRIPT   );
185 	FT_INIT( RUST,       RUST,         "Rust",             NULL,                      SOURCE_FILE, COMPILED );
186 	FT_INIT( COFFEESCRIPT, NONE,       "CoffeeScript",     NULL,                      SOURCE_FILE, SCRIPT   );
187 	FT_INIT( GO,         GO,           "Go",               NULL,                      SOURCE_FILE, COMPILED );
188 	FT_INIT( ZEPHIR,     ZEPHIR,       "Zephir",           NULL,                      SOURCE_FILE, COMPILED );
189 	FT_INIT( SMALLTALK,  NONE,         "Smalltalk",        NULL,                      SOURCE_FILE, SCRIPT   );
190 }
191 
192 
193 /* initialize fields. */
filetype_new(void)194 static GeanyFiletype *filetype_new(void)
195 {
196 	GeanyFiletype *ft = g_new0(GeanyFiletype, 1);
197 
198 	ft->group = GEANY_FILETYPE_GROUP_NONE;
199 	ft->lang = TM_PARSER_NONE;	/* assume no tagmanager parser */
200 	/* pattern must not be null */
201 	ft->pattern = g_new0(gchar*, 1);
202 	ft->indent_width = -1;
203 	ft->indent_type = -1;
204 
205 	ft->priv = g_new0(GeanyFiletypePrivate, 1);
206 	ft->priv->project_list_entry = -1; /* no entry */
207 
208 	return ft;
209 }
210 
211 
cmp_filetype(gconstpointer pft1,gconstpointer pft2,gpointer data)212 static gint cmp_filetype(gconstpointer pft1, gconstpointer pft2, gpointer data)
213 {
214 	gboolean by_name = GPOINTER_TO_INT(data);
215 	const GeanyFiletype *ft1 = pft1, *ft2 = pft2;
216 
217 	if (G_UNLIKELY(ft1->id == GEANY_FILETYPES_NONE))
218 		return -1;
219 	if (G_UNLIKELY(ft2->id == GEANY_FILETYPES_NONE))
220 		return 1;
221 
222 	return by_name ?
223 		utils_str_casecmp(ft1->name, ft2->name) :
224 		utils_str_casecmp(ft1->title, ft2->title);
225 }
226 
227 
228 /** Gets a list of filetype pointers sorted by name.
229  * The list does not change on subsequent calls.
230  * @return @elementtype{GeanyFiletype} @transfer{none} The list - do not free.
231  * @see filetypes_by_title. */
232 GEANY_API_SYMBOL
filetypes_get_sorted_by_name(void)233 const GSList *filetypes_get_sorted_by_name(void)
234 {
235 	static GSList *list = NULL;
236 
237 	g_return_val_if_fail(filetypes_by_title, NULL);
238 
239 	if (!list)
240 	{
241 		list = g_slist_copy(filetypes_by_title);
242 		list = g_slist_sort_with_data(list, cmp_filetype, GINT_TO_POINTER(TRUE));
243 	}
244 	return list;
245 }
246 
247 
248 /* Add a filetype pointer to the lists of available filetypes,
249  * and set the filetype::id field. */
filetype_add(GeanyFiletype * ft)250 static void filetype_add(GeanyFiletype *ft)
251 {
252 	g_return_if_fail(ft);
253 	g_return_if_fail(ft->name);
254 
255 	ft->id = filetypes_array->len;	/* len will be the index for filetype_array */
256 	g_ptr_array_add(filetypes_array, ft);
257 	g_hash_table_insert(filetypes_hash, ft->name, ft);
258 
259 	/* list will be sorted later */
260 	filetypes_by_title = g_slist_prepend(filetypes_by_title, ft);
261 }
262 
263 
add_custom_filetype(const gchar * filename)264 static void add_custom_filetype(const gchar *filename)
265 {
266 	gchar *fn = utils_strdupa(strstr(filename, ".") + 1);
267 	gchar *dot = g_strrstr(fn, ".conf");
268 	GeanyFiletype *ft;
269 
270 	g_return_if_fail(dot);
271 
272 	*dot = 0x0;
273 
274 	if (g_hash_table_lookup(filetypes_hash, fn))
275 		return;
276 
277 	ft = filetype_new();
278 	ft->name = g_strdup(fn);
279 	ft->title = filetype_make_title(ft->name, TITLE_FILE);
280 	ft->priv->custom = TRUE;
281 	filetype_add(ft);
282 	geany_debug("Added filetype %s (%d).", ft->name, ft->id);
283 }
284 
285 
init_custom_filetypes(const gchar * path)286 static void init_custom_filetypes(const gchar *path)
287 {
288 	GDir *dir;
289 	const gchar *filename;
290 
291 	g_return_if_fail(path);
292 
293 	dir = g_dir_open(path, 0, NULL);
294 	if (dir == NULL)
295 		return;
296 
297 	foreach_dir(filename, dir)
298 	{
299 		const gchar prefix[] = "filetypes.";
300 
301 		if (g_str_has_prefix(filename, prefix) &&
302 			g_str_has_suffix(filename + strlen(prefix), ".conf"))
303 		{
304 			add_custom_filetype(filename);
305 		}
306 	}
307 	g_dir_close(dir);
308 }
309 
310 
311 /* Create the filetypes array and fill it with the known filetypes.
312  * Warning: GTK isn't necessarily initialized yet. */
filetypes_init_types(void)313 void filetypes_init_types(void)
314 {
315 	GeanyFiletypeID ft_id;
316 	gchar *f;
317 
318 	g_return_if_fail(filetypes_array == NULL);
319 	g_return_if_fail(filetypes_hash == NULL);
320 
321 	filetypes_array = g_ptr_array_sized_new(GEANY_MAX_BUILT_IN_FILETYPES);
322 	filetypes_hash = g_hash_table_new(g_str_hash, g_str_equal);
323 
324 	/* Create built-in filetypes */
325 	for (ft_id = 0; ft_id < GEANY_MAX_BUILT_IN_FILETYPES; ft_id++)
326 	{
327 		filetypes[ft_id] = filetype_new();
328 	}
329 	init_builtin_filetypes();
330 
331 	/* Add built-in filetypes to the hash now the name fields are set */
332 	for (ft_id = 0; ft_id < GEANY_MAX_BUILT_IN_FILETYPES; ft_id++)
333 	{
334 		filetype_add(filetypes[ft_id]);
335 	}
336 	f = g_build_filename(app->datadir, GEANY_FILEDEFS_SUBDIR, NULL);
337 	init_custom_filetypes(f);
338 	g_free(f);
339 	f = g_build_filename(app->configdir, GEANY_FILEDEFS_SUBDIR, NULL);
340 	init_custom_filetypes(f);
341 	g_free(f);
342 
343 	/* sort last instead of on insertion to prevent exponential time */
344 	filetypes_by_title = g_slist_sort_with_data(filetypes_by_title,
345 		cmp_filetype, GINT_TO_POINTER(FALSE));
346 
347 	read_filetype_config();
348 }
349 
350 
on_document_save(G_GNUC_UNUSED GObject * object,GeanyDocument * doc)351 static void on_document_save(G_GNUC_UNUSED GObject *object, GeanyDocument *doc)
352 {
353 	gchar *f, *basename;
354 
355 	g_return_if_fail(!EMPTY(doc->real_path));
356 
357 	f = g_build_filename(app->configdir, "filetype_extensions.conf", NULL);
358 	if (utils_str_equal(doc->real_path, f))
359 		filetypes_reload_extensions();
360 	g_free(f);
361 
362 	basename = g_path_get_basename(doc->real_path);
363 	if (g_str_has_prefix(basename, "filetypes."))
364 	{
365 		guint i;
366 
367 		for (i = 0; i < filetypes_array->len; i++)
368 		{
369 			GeanyFiletype *ft = filetypes[i];
370 
371 			f = filetypes_get_filename(ft, TRUE);
372 			if (utils_str_equal(doc->real_path, f))
373 			{
374 				guint j;
375 
376 				/* Note: we don't reload other filetypes, even though the named styles may have changed.
377 				 * The user can do this manually with 'Tools->Reload Configuration' */
378 				filetypes_load_config(i, TRUE);
379 
380 				foreach_document(j)
381 					document_reload_config(documents[j]);
382 
383 				g_free(f);
384 				break;
385 			}
386 			g_free(f);
387 		}
388 	}
389 	g_free(basename);
390 }
391 
392 
setup_config_file_menus(void)393 static void setup_config_file_menus(void)
394 {
395 	gchar *f;
396 
397 	f = g_build_filename(app->configdir, "filetype_extensions.conf", NULL);
398 	ui_add_config_file_menu_item(f, NULL, NULL);
399 	SETPTR(f, g_build_filename(app->configdir, GEANY_FILEDEFS_SUBDIR, "filetypes.common", NULL));
400 	ui_add_config_file_menu_item(f, NULL, NULL);
401 	g_free(f);
402 
403 	create_set_filetype_menu(TRUE);
404 
405 	g_signal_connect(geany_object, "document-save", G_CALLBACK(on_document_save), NULL);
406 }
407 
408 
create_sub_menu(GtkWidget * parent,const gchar * title)409 static GtkWidget *create_sub_menu(GtkWidget *parent, const gchar *title)
410 {
411 	GtkWidget *menu, *item;
412 
413 	menu = gtk_menu_new();
414 	item = gtk_menu_item_new_with_mnemonic((title));
415 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
416 	gtk_container_add(GTK_CONTAINER(parent), item);
417 	gtk_widget_show(item);
418 
419 	return menu;
420 }
421 
422 
create_set_filetype_menu(gboolean config)423 static void create_set_filetype_menu(gboolean config)
424 {
425 	GtkWidget *group_menus[GEANY_FILETYPE_GROUP_COUNT] = {NULL};
426 	GSList *node;
427 	GtkWidget *menu;
428 
429 	menu = config ? ui_widgets.config_files_filetype_menu :
430 		ui_lookup_widget(main_widgets.window, "set_filetype1_menu");
431 
432 	group_menus[GEANY_FILETYPE_GROUP_COMPILED] = create_sub_menu(menu, _("_Programming Languages"));
433 	group_menus[GEANY_FILETYPE_GROUP_SCRIPT] = create_sub_menu(menu, _("_Scripting Languages"));
434 	group_menus[GEANY_FILETYPE_GROUP_MARKUP] = create_sub_menu(menu, _("_Markup Languages"));
435 	group_menus[GEANY_FILETYPE_GROUP_MISC] = create_sub_menu(menu, _("M_iscellaneous"));
436 
437 	/* Append all filetypes to the menu */
438 	foreach_slist(node, filetypes_by_title)
439 	{
440 		GeanyFiletype *ft = node->data;
441 		GtkWidget *parent = (ft->group != GEANY_FILETYPE_GROUP_NONE) ? group_menus[ft->group] : menu;
442 
443 		/* we already have filetypes.common config entry */
444 		if (config && ft->id == GEANY_FILETYPES_NONE)
445 			continue;
446 
447 		if (config)
448 		{
449 			gchar *filename = filetypes_get_filename(ft, TRUE);
450 
451 			ui_add_config_file_menu_item(filename, NULL, GTK_CONTAINER(parent));
452 			g_free(filename);
453 		}
454 		else
455 			create_radio_menu_item(parent, ft);
456 	}
457 }
458 
459 
filetypes_init(void)460 void filetypes_init(void)
461 {
462 	filetypes_init_types();
463 	create_set_filetype_menu(FALSE);
464 	setup_config_file_menus();
465 }
466 
467 
match_basename(const GeanyFiletype * ft,const gchar * base_filename)468 static guint match_basename(const GeanyFiletype *ft, const gchar *base_filename)
469 {
470 	if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
471 		return 0;
472 
473 	for (guint j = 0; ft->pattern[j] != NULL; j++)
474 	{
475 		gchar *pat = ft->pattern[j];
476 
477 		if (g_pattern_match_simple(pat, base_filename))
478 		{
479 			return strlen(pat);
480 		}
481 	}
482 	return 0;
483 }
484 
485 
detect_filetype_conf_file(const gchar * utf8_filename)486 static GeanyFiletype *detect_filetype_conf_file(const gchar *utf8_filename)
487 {
488 	gchar *lfn = NULL;
489 	gchar *path;
490 	gboolean found = FALSE;
491 
492 #ifdef G_OS_WIN32
493 	/* use lower case basename */
494 	lfn = g_utf8_strdown(utf8_filename, -1);
495 #else
496 	lfn = g_strdup(utf8_filename);
497 #endif
498 	SETPTR(lfn, utils_get_locale_from_utf8(lfn));
499 
500 	path = g_build_filename(app->configdir, GEANY_FILEDEFS_SUBDIR, "filetypes.", NULL);
501 	if (g_str_has_prefix(lfn, path))
502 		found = TRUE;
503 
504 	SETPTR(path, g_build_filename(app->datadir, GEANY_FILEDEFS_SUBDIR, "filetypes.", NULL));
505 	if (g_str_has_prefix(lfn, path))
506 		found = TRUE;
507 
508 	g_free(path);
509 	g_free(lfn);
510 	return found ? filetypes[GEANY_FILETYPES_CONF] : NULL;
511 }
512 
513 
514 /* Detect filetype only based on the filename extension.
515  * utf8_filename can include the full path. */
filetypes_detect_from_extension(const gchar * utf8_filename)516 GeanyFiletype *filetypes_detect_from_extension(const gchar *utf8_filename)
517 {
518 	gchar *base_filename;
519 	GeanyFiletype *ft;
520 	guint plen = 0;
521 
522 	ft = detect_filetype_conf_file(utf8_filename);
523 	if (ft)
524 		return ft;
525 
526 	/* to match against the basename of the file (because of Makefile*) */
527 	base_filename = g_path_get_basename(utf8_filename);
528 #ifdef G_OS_WIN32
529 	/* use lower case basename */
530 	SETPTR(base_filename, g_utf8_strdown(base_filename, -1));
531 #endif
532 
533 	for (guint i = 0; i < filetypes_array->len; i++)
534 	{
535 		guint mlen = match_basename(filetypes[i], base_filename);
536 
537 		if (mlen > plen)
538 		{	// longest pattern match wins
539 			plen = mlen;
540 			ft = filetypes[i];
541 		}
542 		else if (mlen == plen && ft && !ft->priv->user_extensions &&
543 			filetypes[i]->priv->user_extensions)
544 		{	// user config overrides system if pattern len same
545 			ft = filetypes[i];
546 		}
547 	}
548 	if (ft == NULL)
549 		ft = filetypes[GEANY_FILETYPES_NONE];
550 
551 	g_free(base_filename);
552 	return ft;
553 }
554 
555 
556 /* This detects the filetype of the file pointed by 'utf8_filename' and a list of filetype id's,
557  * terminated by -1.
558  * The detected filetype of the file is checked against every id in the passed list and if
559  * there is a match, TRUE is returned. */
shebang_find_and_match_filetype(const gchar * utf8_filename,gint first,...)560 static gboolean shebang_find_and_match_filetype(const gchar *utf8_filename, gint first, ...)
561 {
562 	GeanyFiletype *ft = NULL;
563 	gint test;
564 	gboolean result = FALSE;
565 	va_list args;
566 
567 	ft = filetypes_detect_from_extension(utf8_filename);
568 	if (ft == NULL || ft->id >= filetypes_array->len)
569 		return FALSE;
570 
571 	va_start(args, first);
572 	test = first;
573 	while (1)
574 	{
575 		if (test == -1)
576 			break;
577 
578 		if (ft->id == (guint) test)
579 		{
580 			result = TRUE;
581 			break;
582 		}
583 		test = va_arg(args, gint);
584 	}
585 	va_end(args);
586 
587 	return result;
588 }
589 
590 
find_shebang(const gchar * utf8_filename,const gchar * line)591 static GeanyFiletype *find_shebang(const gchar *utf8_filename, const gchar *line)
592 {
593 	GeanyFiletype *ft = NULL;
594 
595 	if (strlen(line) > 2 && line[0] == '#' && line[1] == '!')
596 	{
597 		static const struct {
598 			const gchar *name;
599 			GeanyFiletypeID filetype;
600 		} intepreter_map[] = {
601 			{ "sh",		GEANY_FILETYPES_SH },
602 			{ "bash",	GEANY_FILETYPES_SH },
603 			{ "dash",	GEANY_FILETYPES_SH },
604 			{ "perl",	GEANY_FILETYPES_PERL },
605 			{ "python",	GEANY_FILETYPES_PYTHON },
606 			{ "php",	GEANY_FILETYPES_PHP },
607 			{ "ruby",	GEANY_FILETYPES_RUBY },
608 			{ "tcl",	GEANY_FILETYPES_TCL },
609 			{ "make",	GEANY_FILETYPES_MAKE },
610 			{ "zsh",	GEANY_FILETYPES_SH },
611 			{ "ksh",	GEANY_FILETYPES_SH },
612 			{ "mksh",	GEANY_FILETYPES_SH },
613 			{ "csh",	GEANY_FILETYPES_SH },
614 			{ "tcsh",	GEANY_FILETYPES_SH },
615 			{ "ash",	GEANY_FILETYPES_SH },
616 			{ "dmd",	GEANY_FILETYPES_D },
617 			{ "wish",	GEANY_FILETYPES_TCL },
618 			{ "node",	GEANY_FILETYPES_JS },
619 			{ "rust",	GEANY_FILETYPES_RUST }
620 		};
621 		gchar *tmp = g_path_get_basename(line + 2);
622 		gchar *basename_interpreter = tmp;
623 		guint i;
624 
625 		if (g_str_has_prefix(tmp, "env "))
626 		{	/* skip "env" and read the following interpreter */
627 			basename_interpreter += 4;
628 		}
629 
630 		for (i = 0; ! ft && i < G_N_ELEMENTS(intepreter_map); i++)
631 		{
632 			if (g_str_has_prefix(basename_interpreter, intepreter_map[i].name))
633 				ft = filetypes[intepreter_map[i].filetype];
634 		}
635 		g_free(tmp);
636 	}
637 	/* detect HTML files */
638 	if (g_str_has_prefix(line, "<!DOCTYPE html") || g_str_has_prefix(line, "<html"))
639 	{
640 		/* PHP, Perl and Python files might also start with <html, so detect them based on filename
641 		 * extension and use the detected filetype, else assume HTML */
642 		if (! shebang_find_and_match_filetype(utf8_filename,
643 				GEANY_FILETYPES_PERL, GEANY_FILETYPES_PHP, GEANY_FILETYPES_PYTHON, -1))
644 		{
645 			ft = filetypes[GEANY_FILETYPES_HTML];
646 		}
647 	}
648 	/* detect XML files */
649 	else if (utf8_filename && g_str_has_prefix(line, "<?xml"))
650 	{
651 		/* HTML and DocBook files might also start with <?xml, so detect them based on filename
652 		 * extension and use the detected filetype, else assume XML */
653 		if (! shebang_find_and_match_filetype(utf8_filename,
654 				GEANY_FILETYPES_HTML, GEANY_FILETYPES_DOCBOOK,
655 				/* Perl, Python and PHP only to be safe */
656 				GEANY_FILETYPES_PERL, GEANY_FILETYPES_PHP, GEANY_FILETYPES_PYTHON, -1))
657 		{
658 			ft = filetypes[GEANY_FILETYPES_XML];
659 		}
660 	}
661 	else if (g_str_has_prefix(line, "<?php"))
662 	{
663 		ft = filetypes[GEANY_FILETYPES_PHP];
664 	}
665 	return ft;
666 }
667 
668 
669 /* Detect the filetype checking for a shebang, then filename extension.
670  * @lines: an strv of the lines to scan (must containing at least one line) */
filetypes_detect_from_file_internal(const gchar * utf8_filename,gchar ** lines)671 static GeanyFiletype *filetypes_detect_from_file_internal(const gchar *utf8_filename,
672 														  gchar **lines)
673 {
674 	GeanyFiletype	*ft;
675 	gint			 i;
676 	GRegex			*ft_regex;
677 	GMatchInfo		*match;
678 	GError			*regex_error = NULL;
679 
680 	/* try to find a shebang and if found use it prior to the filename extension
681 	 * also checks for <?xml */
682 	ft = find_shebang(utf8_filename, lines[0]);
683 	if (ft != NULL)
684 		return ft;
685 
686 	/* try to extract the filetype using a regex capture */
687 	ft_regex = g_regex_new(file_prefs.extract_filetype_regex,
688 			G_REGEX_RAW | G_REGEX_MULTILINE, 0, &regex_error);
689 	if (ft_regex != NULL)
690 	{
691 		for (i = 0; ft == NULL && lines[i] != NULL; i++)
692 		{
693 			if (g_regex_match(ft_regex, lines[i], 0, &match))
694 			{
695 				gchar *capture = g_match_info_fetch(match, 1);
696 				if (capture != NULL)
697 				{
698 					ft = filetypes_lookup_by_name(capture);
699 					g_free(capture);
700 				}
701 			}
702 			g_match_info_free(match);
703 		}
704 		g_regex_unref(ft_regex);
705 	}
706 	else if (regex_error != NULL)
707 	{
708 		geany_debug("Filetype extract regex ignored: %s", regex_error->message);
709 		g_error_free(regex_error);
710 	}
711 	if (ft != NULL)
712 		return ft;
713 
714 	if (utf8_filename == NULL)
715 		return filetypes[GEANY_FILETYPES_NONE];
716 
717 	return filetypes_detect_from_extension(utf8_filename);
718 }
719 
720 
721 /* Detect the filetype for the document, checking for a shebang, then filename extension. */
filetypes_detect_from_document(GeanyDocument * doc)722 GeanyFiletype *filetypes_detect_from_document(GeanyDocument *doc)
723 {
724 	GeanyFiletype 	*ft;
725 	gchar 			*lines[GEANY_FILETYPE_SEARCH_LINES + 1];
726 	gint			 i;
727 
728 	g_return_val_if_fail(doc == NULL || doc->is_valid, filetypes[GEANY_FILETYPES_NONE]);
729 
730 	if (doc == NULL)
731 		return filetypes[GEANY_FILETYPES_NONE];
732 
733 	for (i = 0; i < GEANY_FILETYPE_SEARCH_LINES; ++i)
734 	{
735 		lines[i] = sci_get_line(doc->editor->sci, i);
736 	}
737 	lines[i] = NULL;
738 	ft = filetypes_detect_from_file_internal(doc->file_name, lines);
739 	for (i = 0; i < GEANY_FILETYPE_SEARCH_LINES; ++i)
740 	{
741 		g_free(lines[i]);
742 	}
743 	return ft;
744 }
745 
746 
747 #ifdef HAVE_PLUGINS
748 /* Currently only used by external plugins (e.g. geanyprj). */
749 /**
750  *  Detects filetype based on a shebang line in the file or the filename extension.
751  *
752  *  @param utf8_filename The filename in UTF-8 encoding.
753  *
754  *  @return @transfer{none} The detected filetype for @a utf8_filename or
755  *           @c filetypes[GEANY_FILETYPES_NONE] if it could not be detected.
756  **/
757 GEANY_API_SYMBOL
filetypes_detect_from_file(const gchar * utf8_filename)758 GeanyFiletype *filetypes_detect_from_file(const gchar *utf8_filename)
759 {
760 	gchar line[1024];
761 	gchar *lines[2];
762 	FILE  *f;
763 	gchar *locale_name = utils_get_locale_from_utf8(utf8_filename);
764 
765 	f = g_fopen(locale_name, "r");
766 	g_free(locale_name);
767 	if (f != NULL)
768 	{
769 		if (fgets(line, sizeof(line), f) != NULL)
770 		{
771 			fclose(f);
772 			lines[0] = line;
773 			lines[1] = NULL;
774 			return filetypes_detect_from_file_internal(utf8_filename, lines);
775 		}
776 		fclose(f);
777 	}
778 	return filetypes_detect_from_extension(utf8_filename);
779 }
780 #endif
781 
782 
filetypes_select_radio_item(const GeanyFiletype * ft)783 void filetypes_select_radio_item(const GeanyFiletype *ft)
784 {
785 	/* ignore_callback has to be set by the caller */
786 	g_return_if_fail(ignore_callback);
787 
788 	if (ft == NULL)
789 		ft = filetypes[GEANY_FILETYPES_NONE];
790 
791 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ft->priv->menu_item), TRUE);
792 }
793 
794 
795 static void
on_filetype_change(GtkCheckMenuItem * menuitem,gpointer user_data)796 on_filetype_change(GtkCheckMenuItem *menuitem, gpointer user_data)
797 {
798 	GeanyDocument *doc = document_get_current();
799 	if (ignore_callback || doc == NULL || ! gtk_check_menu_item_get_active(menuitem))
800 		return;
801 
802 	document_set_filetype(doc, (GeanyFiletype*)user_data);
803 }
804 
805 
create_radio_menu_item(GtkWidget * menu,GeanyFiletype * ftype)806 static void create_radio_menu_item(GtkWidget *menu, GeanyFiletype *ftype)
807 {
808 	static GSList *group = NULL;
809 	GtkWidget *tmp;
810 
811 	tmp = gtk_radio_menu_item_new_with_label(group, ftype->title);
812 	group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(tmp));
813 	ftype->priv->menu_item = tmp;
814 	gtk_widget_show(tmp);
815 	gtk_container_add(GTK_CONTAINER(menu), tmp);
816 	g_signal_connect(tmp, "activate", G_CALLBACK(on_filetype_change), (gpointer) ftype);
817 }
818 
819 
filetype_free(gpointer data,G_GNUC_UNUSED gpointer user_data)820 static void filetype_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
821 {
822 	GeanyFiletype *ft = data;
823 
824 	g_return_if_fail(ft != NULL);
825 
826 	g_free(ft->name);
827 	g_free(ft->title);
828 	g_free(ft->extension);
829 	g_free(ft->mime_type);
830 	g_free(ft->comment_open);
831 	g_free(ft->comment_close);
832 	g_free(ft->comment_single);
833 	g_free(ft->context_action_cmd);
834 	g_free(ft->priv->filecmds);
835 	g_free(ft->priv->ftdefcmds);
836 	g_free(ft->priv->execcmds);
837 	g_free(ft->error_regex_string);
838 	if (ft->icon)
839 		g_object_unref(ft->icon);
840 	g_strfreev(ft->pattern);
841 
842 	if (ft->priv->error_regex)
843 		g_regex_unref(ft->priv->error_regex);
844 	g_slist_foreach(ft->priv->tag_files, (GFunc) g_free, NULL);
845 	g_slist_free(ft->priv->tag_files);
846 
847 	g_free(ft->priv);
848 	g_free(ft);
849 }
850 
851 
852 /* frees the array and all related pointers */
filetypes_free_types(void)853 void filetypes_free_types(void)
854 {
855 	g_return_if_fail(filetypes_array != NULL);
856 	g_return_if_fail(filetypes_hash != NULL);
857 
858 	g_ptr_array_foreach(filetypes_array, filetype_free, NULL);
859 	g_ptr_array_free(filetypes_array, TRUE);
860 	g_hash_table_destroy(filetypes_hash);
861 }
862 
863 
load_indent_settings(GeanyFiletype * ft,GKeyFile * config,GKeyFile * configh)864 static void load_indent_settings(GeanyFiletype *ft, GKeyFile *config, GKeyFile *configh)
865 {
866 	ft->indent_width = utils_get_setting(integer, configh, config, "indentation", "width", -1);
867 	ft->indent_type = utils_get_setting(integer, configh, config, "indentation", "type", -1);
868 	/* check whether the indent type is OK */
869 	switch (ft->indent_type)
870 	{
871 		case GEANY_INDENT_TYPE_TABS:
872 		case GEANY_INDENT_TYPE_SPACES:
873 		case GEANY_INDENT_TYPE_BOTH:
874 		case -1:
875 			break;
876 
877 		default:
878 			g_warning("Invalid indent type %d in file type %s", ft->indent_type, ft->name);
879 			ft->indent_type = -1;
880 			break;
881 	}
882 }
883 
884 
load_settings(guint ft_id,GKeyFile * config,GKeyFile * configh)885 static void load_settings(guint ft_id, GKeyFile *config, GKeyFile *configh)
886 {
887 	GeanyFiletype *ft = filetypes[ft_id];
888 	gchar *result;
889 
890 	/* default extension */
891 	result = utils_get_setting(string, configh, config, "settings", "extension", NULL);
892 	if (result != NULL)
893 	{
894 		SETPTR(filetypes[ft_id]->extension, result);
895 	}
896 
897 	/* MIME type */
898 	result = utils_get_setting(string, configh, config, "settings", "mime_type", "text/plain");
899 	SETPTR(filetypes[ft_id]->mime_type, result);
900 
901 	/* read comment notes */
902 	result = utils_get_setting(string, configh, config, "settings", "comment_open", NULL);
903 	if (result != NULL)
904 	{
905 		SETPTR(filetypes[ft_id]->comment_open, result);
906 	}
907 
908 	result = utils_get_setting(string, configh, config, "settings", "comment_close", NULL);
909 	if (result != NULL)
910 	{
911 		SETPTR(filetypes[ft_id]->comment_close, result);
912 	}
913 
914 	result = utils_get_setting(string, configh, config, "settings", "comment_single", NULL);
915 	if (result != NULL)
916 	{
917 		SETPTR(filetypes[ft_id]->comment_single, result);
918 	}
919 	/* import correctly filetypes that use old-style single comments */
920 	else if (EMPTY(filetypes[ft_id]->comment_close))
921 	{
922 		SETPTR(filetypes[ft_id]->comment_single, filetypes[ft_id]->comment_open);
923 		filetypes[ft_id]->comment_open = NULL;
924 	}
925 
926 	filetypes[ft_id]->comment_use_indent = utils_get_setting(boolean, configh, config,
927 			"settings", "comment_use_indent", FALSE);
928 
929 	/* read context action */
930 	result = utils_get_setting(string, configh, config, "settings", "context_action_cmd", NULL);
931 	if (result != NULL)
932 	{
933 		SETPTR(filetypes[ft_id]->context_action_cmd, result);
934 	}
935 
936 	result = utils_get_setting(string, configh, config, "settings", "tag_parser", NULL);
937 	if (result != NULL)
938 	{
939 		ft->lang = tm_source_file_get_named_lang(result);
940 		if (ft->lang == TM_PARSER_NONE)
941 			geany_debug("Cannot find tags parser '%s' for custom filetype '%s'.", result, ft->name);
942 		g_free(result);
943 	}
944 
945 	result = utils_get_setting(string, configh, config, "settings", "lexer_filetype", NULL);
946 	if (result != NULL)
947 	{
948 		ft->lexer_filetype = filetypes_lookup_by_name(result);
949 		if (!ft->lexer_filetype)
950 			geany_debug("Cannot find lexer filetype '%s' for custom filetype '%s'.", result, ft->name);
951 		g_free(result);
952 	}
953 
954 	ft->priv->symbol_list_sort_mode = utils_get_setting(integer, configh, config, "settings",
955 		"symbol_list_sort_mode", SYMBOLS_SORT_USE_PREVIOUS);
956 	ft->priv->xml_indent_tags = utils_get_setting(boolean, configh, config, "settings",
957 		"xml_indent_tags", FALSE);
958 
959 	/* read indent settings */
960 	load_indent_settings(ft, config, configh);
961 
962 	/* read build settings */
963 	build_load_menu(config, GEANY_BCS_FT, (gpointer)ft);
964 	build_load_menu(configh, GEANY_BCS_HOME_FT, (gpointer)ft);
965 }
966 
967 
copy_keys(GKeyFile * dest,const gchar * dest_group,GKeyFile * src,const gchar * src_group)968 static void copy_keys(GKeyFile *dest, const gchar *dest_group,
969 		GKeyFile *src, const gchar *src_group)
970 {
971 	gchar **keys = g_key_file_get_keys(src, src_group, NULL, NULL);
972 	gchar **ptr;
973 
974 	foreach_strv(ptr, keys)
975 	{
976 		gchar *key = *ptr;
977 		gchar *value = g_key_file_get_value(src, src_group, key, NULL);
978 
979 		g_key_file_set_value(dest, dest_group, key, value);
980 		g_free(value);
981 	}
982 	g_strfreev(keys);
983 }
984 
985 
filetypes_get_filename(GeanyFiletype * ft,gboolean user)986 static gchar *filetypes_get_filename(GeanyFiletype *ft, gboolean user)
987 {
988 	gchar *ext = filetypes_get_conf_extension(ft);
989 	gchar *base_name = g_strconcat("filetypes.", ext, NULL);
990 	gchar *file_name;
991 
992 	if (user)
993 		file_name = g_build_filename(app->configdir, GEANY_FILEDEFS_SUBDIR, base_name, NULL);
994 	else
995 		file_name = g_build_filename(app->datadir, GEANY_FILEDEFS_SUBDIR, base_name, NULL);
996 
997 	g_free(ext);
998 	g_free(base_name);
999 
1000 	return file_name;
1001 }
1002 
1003 
add_group_keys(GKeyFile * kf,const gchar * group,GeanyFiletype * ft)1004 static void add_group_keys(GKeyFile *kf, const gchar *group, GeanyFiletype *ft)
1005 {
1006 	gchar *files[2];
1007 	gboolean loaded = FALSE;
1008 	guint i;
1009 
1010 	files[0] = filetypes_get_filename(ft, FALSE);
1011 	files[1] = filetypes_get_filename(ft, TRUE);
1012 
1013 	for (i = 0; i < G_N_ELEMENTS(files); i++)
1014 	{
1015 		GKeyFile *src = g_key_file_new();
1016 
1017 		if (g_key_file_load_from_file(src, files[i], G_KEY_FILE_NONE, NULL))
1018 		{
1019 			copy_keys(kf, group, src, group);
1020 			loaded = TRUE;
1021 		}
1022 		g_key_file_free(src);
1023 	}
1024 
1025 	if (!loaded)
1026 		geany_debug("Could not read config file %s for [%s=%s]!", files[0], group, ft->name);
1027 
1028 	g_free(files[0]);
1029 	g_free(files[1]);
1030 }
1031 
1032 
copy_ft_groups(GKeyFile * kf)1033 static void copy_ft_groups(GKeyFile *kf)
1034 {
1035 	gchar **groups = g_key_file_get_groups(kf, NULL);
1036 	gchar **ptr;
1037 
1038 	foreach_strv(ptr, groups)
1039 	{
1040 		gchar *group = *ptr;
1041 		gchar *old_group;
1042 		gchar *name = strchr(*ptr, '=');
1043 		GeanyFiletype *ft;
1044 
1045 		if (!name || !name[1]) /* no name or no parent name */
1046 			continue;
1047 
1048 		old_group = g_strdup(group);
1049 
1050 		/* terminate group at '=' */
1051 		*name = 0;
1052 		name++;
1053 
1054 		ft = filetypes_lookup_by_name(name);
1055 		if (ft)
1056 		{
1057 			add_group_keys(kf, group, ft);
1058 			/* move old group keys (foo=bar) to proper group name (foo) */
1059 			copy_keys(kf, group, kf, old_group);
1060 		}
1061 		g_free(old_group);
1062 	}
1063 	g_strfreev(groups);
1064 }
1065 
1066 
1067 /* simple wrapper function to print file errors in DEBUG mode */
load_system_keyfile(GKeyFile * key_file,const gchar * file,GKeyFileFlags flags,GeanyFiletype * ft)1068 static void load_system_keyfile(GKeyFile *key_file, const gchar *file, GKeyFileFlags flags,
1069 		GeanyFiletype *ft)
1070 {
1071 	GError *error = NULL;
1072 	gboolean done = g_key_file_load_from_file(key_file, file, flags, &error);
1073 
1074 	if (error != NULL)
1075 	{
1076 		if (!done && !ft->priv->custom)
1077 			geany_debug("Failed to open %s (%s)", file, error->message);
1078 
1079 		g_error_free(error);
1080 		error = NULL;
1081 	}
1082 }
1083 
1084 
1085 /* Load the configuration file for the associated filetype id.
1086  * This should only be called when the filetype is needed, to save loading
1087  * 20+ configuration files all at once. */
filetypes_load_config(guint ft_id,gboolean reload)1088 void filetypes_load_config(guint ft_id, gboolean reload)
1089 {
1090 	GKeyFile *config, *config_home;
1091 	GeanyFiletypePrivate *pft;
1092 	GeanyFiletype *ft;
1093 
1094 	g_return_if_fail(ft_id < filetypes_array->len);
1095 
1096 	ft = filetypes[ft_id];
1097 	pft = ft->priv;
1098 
1099 	/* when reloading, proceed only if the settings were already loaded */
1100 	if (G_UNLIKELY(reload && ! pft->keyfile_loaded))
1101 		return;
1102 
1103 	/* when not reloading, load the settings only once */
1104 	if (G_LIKELY(! reload && pft->keyfile_loaded))
1105 		return;
1106 	pft->keyfile_loaded = TRUE;
1107 
1108 	config = g_key_file_new();
1109 	config_home = g_key_file_new();
1110 	{
1111 		/* highlighting uses GEANY_FILETYPES_NONE for common settings */
1112 		gchar *f;
1113 
1114 		f = filetypes_get_filename(ft, FALSE);
1115 		load_system_keyfile(config, f, G_KEY_FILE_KEEP_COMMENTS, ft);
1116 
1117 		SETPTR(f, filetypes_get_filename(ft, TRUE));
1118 		g_key_file_load_from_file(config_home, f, G_KEY_FILE_KEEP_COMMENTS, NULL);
1119 		g_free(f);
1120 	}
1121 	/* Copy keys for any groups with [group=C] from system keyfile */
1122 	copy_ft_groups(config);
1123 	copy_ft_groups(config_home);
1124 
1125 	load_settings(ft_id, config, config_home);
1126 	highlighting_init_styles(ft_id, config, config_home);
1127 
1128 	if (ft->icon)
1129 		g_object_unref(ft->icon);
1130 	ft->icon = ui_get_mime_icon(ft->mime_type);
1131 
1132 	g_key_file_free(config);
1133 	g_key_file_free(config_home);
1134 }
1135 
1136 
filetypes_get_conf_extension(const GeanyFiletype * ft)1137 static gchar *filetypes_get_conf_extension(const GeanyFiletype *ft)
1138 {
1139 	gchar *result;
1140 
1141 	if (ft->priv->custom)
1142 		return g_strconcat(ft->name, ".conf", NULL);
1143 
1144 	/* Handle any special extensions different from lowercase filetype->name */
1145 	switch (ft->id)
1146 	{
1147 		case GEANY_FILETYPES_CPP: result = g_strdup("cpp"); break;
1148 		case GEANY_FILETYPES_CS: result = g_strdup("cs"); break;
1149 		case GEANY_FILETYPES_MAKE: result = g_strdup("makefile"); break;
1150 		case GEANY_FILETYPES_NONE: result = g_strdup("common"); break;
1151 		/* name is Matlab/Octave */
1152 		case GEANY_FILETYPES_MATLAB: result = g_strdup("matlab"); break;
1153 		/* name is Objective-C, and we don't want the hyphen */
1154 		case GEANY_FILETYPES_OBJECTIVEC: result = g_strdup("objectivec"); break;
1155 		default:
1156 			result = g_ascii_strdown(ft->name, -1);
1157 			break;
1158 	}
1159 	return result;
1160 }
1161 
1162 
filetypes_save_commands(GeanyFiletype * ft)1163 void filetypes_save_commands(GeanyFiletype *ft)
1164 {
1165 	GKeyFile *config_home;
1166 	gchar *fname, *data;
1167 
1168 	fname = filetypes_get_filename(ft, TRUE);
1169 	config_home = g_key_file_new();
1170 	g_key_file_load_from_file(config_home, fname, G_KEY_FILE_KEEP_COMMENTS, NULL);
1171 	build_save_menu(config_home, ft, GEANY_BCS_HOME_FT);
1172 	data = g_key_file_to_data(config_home, NULL, NULL);
1173 	utils_write_file(fname, data);
1174 	g_free(data);
1175 	g_key_file_free(config_home);
1176 	g_free(fname);
1177 }
1178 
1179 
1180 /* create one file filter which has each file pattern of each filetype */
filetypes_create_file_filter_all_source(void)1181 GtkFileFilter *filetypes_create_file_filter_all_source(void)
1182 {
1183 	GtkFileFilter *new_filter;
1184 	guint i, j;
1185 
1186 	new_filter = gtk_file_filter_new();
1187 	gtk_file_filter_set_name(new_filter, _("All Source"));
1188 
1189 	for (i = 0; i < filetypes_array->len; i++)
1190 	{
1191 		if (G_UNLIKELY(i == GEANY_FILETYPES_NONE))
1192 			continue;
1193 
1194 		for (j = 0; filetypes[i]->pattern[j]; j++)
1195 		{
1196 			gtk_file_filter_add_pattern(new_filter, filetypes[i]->pattern[j]);
1197 		}
1198 	}
1199 	return new_filter;
1200 }
1201 
1202 
filetypes_create_file_filter(const GeanyFiletype * ft)1203 GtkFileFilter *filetypes_create_file_filter(const GeanyFiletype *ft)
1204 {
1205 	GtkFileFilter *new_filter;
1206 	gint i;
1207 	const gchar *title;
1208 
1209 	g_return_val_if_fail(ft != NULL, NULL);
1210 
1211 	new_filter = gtk_file_filter_new();
1212 	title = ft->id == GEANY_FILETYPES_NONE ? _("All files") : ft->title;
1213 	gtk_file_filter_set_name(new_filter, title);
1214 
1215 	for (i = 0; ft->pattern[i]; i++)
1216 	{
1217 		gtk_file_filter_add_pattern(new_filter, ft->pattern[i]);
1218 	}
1219 
1220 	return new_filter;
1221 }
1222 
1223 
1224 /* Indicates whether there is a tag parser for the filetype or not.
1225  * Only works for custom filetypes if the filetype settings have been loaded. */
filetype_has_tags(GeanyFiletype * ft)1226 gboolean filetype_has_tags(GeanyFiletype *ft)
1227 {
1228 	g_return_val_if_fail(ft != NULL, FALSE);
1229 
1230 	return ft->lang != TM_PARSER_NONE;
1231 }
1232 
1233 
1234 /** Finds a filetype pointer from its @a name field.
1235  * @param name Filetype name.
1236  * @return @transfer{none} @nullable The filetype found, or @c NULL.
1237  *
1238  * @since 0.15
1239  **/
1240 GEANY_API_SYMBOL
filetypes_lookup_by_name(const gchar * name)1241 GeanyFiletype *filetypes_lookup_by_name(const gchar *name)
1242 {
1243 	GeanyFiletype *ft;
1244 
1245 	g_return_val_if_fail(!EMPTY(name), NULL);
1246 
1247 	ft = g_hash_table_lookup(filetypes_hash, name);
1248 	if (G_UNLIKELY(ft == NULL))
1249 		geany_debug("Could not find filetype '%s'.", name);
1250 	return ft;
1251 }
1252 
1253 
compile_regex(GeanyFiletype * ft,gchar * regstr)1254 static void compile_regex(GeanyFiletype *ft, gchar *regstr)
1255 {
1256 	GError *error = NULL;
1257 	GRegex *regex = g_regex_new(regstr, 0, 0, &error);
1258 
1259 	if (!regex)
1260 	{
1261 		ui_set_statusbar(TRUE, _("Bad regex for filetype %s: %s"),
1262 			filetypes_get_display_name(ft), error->message);
1263 		g_error_free(error);
1264 	}
1265 	if (ft->priv->error_regex)
1266 		g_regex_unref(ft->priv->error_regex);
1267 	ft->priv->error_regex = regex;
1268 }
1269 
1270 
filetypes_parse_error_message(GeanyFiletype * ft,const gchar * message,gchar ** filename,gint * line)1271 gboolean filetypes_parse_error_message(GeanyFiletype *ft, const gchar *message,
1272 		gchar **filename, gint *line)
1273 {
1274 	gchar *regstr;
1275 	gchar **tmp;
1276 	GeanyDocument *doc;
1277 	GMatchInfo *minfo;
1278 	gint i, n_match_groups;
1279 	gchar *first, *second;
1280 
1281 	if (ft == NULL)
1282 	{
1283 		doc = document_get_current();
1284 		if (doc != NULL)
1285 			ft = doc->file_type;
1286 	}
1287 	tmp = build_get_regex(build_info.grp, ft, NULL);
1288 	if (tmp == NULL)
1289 		return FALSE;
1290 	regstr = *tmp;
1291 
1292 	*filename = NULL;
1293 	*line = -1;
1294 
1295 	if (G_UNLIKELY(EMPTY(regstr)))
1296 		return FALSE;
1297 
1298 	if (!ft->priv->error_regex || regstr != ft->priv->last_error_pattern)
1299 	{
1300 		compile_regex(ft, regstr);
1301 		ft->priv->last_error_pattern = regstr;
1302 	}
1303 	if (!ft->priv->error_regex)
1304 		return FALSE;
1305 
1306 	if (!g_regex_match(ft->priv->error_regex, message, 0, &minfo))
1307 	{
1308 		g_match_info_free(minfo);
1309 		return FALSE;
1310 	}
1311 
1312 	n_match_groups = g_match_info_get_match_count(minfo);
1313 	first = second = NULL;
1314 
1315 	for (i = 1; i < n_match_groups; i++)
1316 	{
1317 		gint start_pos;
1318 
1319 		g_match_info_fetch_pos(minfo, i, &start_pos, NULL);
1320 		if (start_pos != -1)
1321 		{
1322 			if (first == NULL)
1323 				first = g_match_info_fetch(minfo, i);
1324 			else
1325 			{
1326 				second = g_match_info_fetch(minfo, i);
1327 				break;
1328 			}
1329 		}
1330 	}
1331 
1332 	if (second)
1333 	{
1334 		gchar *end;
1335 		glong l;
1336 
1337 		l = strtol(first, &end, 10);
1338 		if (*end == '\0')	/* first is purely decimals */
1339 		{
1340 			*line = l;
1341 			g_free(first);
1342 			*filename = second;
1343 		}
1344 		else
1345 		{
1346 			l = strtol(second, &end, 10);
1347 			if (*end == '\0')
1348 			{
1349 				*line = l;
1350 				g_free(second);
1351 				*filename = first;
1352 			}
1353 			else
1354 			{
1355 				g_free(first);
1356 				g_free(second);
1357 			}
1358 		}
1359 	}
1360 	else
1361 		g_free(first);
1362 
1363 	g_match_info_free(minfo);
1364 	return *filename != NULL;
1365 }
1366 
1367 
1368 #ifdef G_OS_WIN32
convert_filetype_extensions_to_lower_case(gchar ** patterns,gsize len)1369 static void convert_filetype_extensions_to_lower_case(gchar **patterns, gsize len)
1370 {
1371 	guint i;
1372 	for (i = 0; i < len; i++)
1373 	{
1374 		SETPTR(patterns[i], g_ascii_strdown(patterns[i], -1));
1375 	}
1376 }
1377 #endif
1378 
1379 
read_extensions(GKeyFile * sysconfig,GKeyFile * userconfig)1380 static void read_extensions(GKeyFile *sysconfig, GKeyFile *userconfig)
1381 {
1382 	guint i;
1383 	gsize len = 0;
1384 
1385 	/* read the keys */
1386 	for (i = 0; i < filetypes_array->len; i++)
1387 	{
1388 		gboolean userset =
1389 			g_key_file_has_key(userconfig, "Extensions", filetypes[i]->name, NULL);
1390 		gchar **list = g_key_file_get_string_list(
1391 			(userset) ? userconfig : sysconfig, "Extensions", filetypes[i]->name, &len, NULL);
1392 
1393 		filetypes[i]->priv->user_extensions = userset;
1394 		g_strfreev(filetypes[i]->pattern);
1395 		/* Note: we allow 'Foo=' to remove all patterns */
1396 		if (!list)
1397 			list = g_new0(gchar*, 1);
1398 		filetypes[i]->pattern = list;
1399 
1400 #ifdef G_OS_WIN32
1401 		convert_filetype_extensions_to_lower_case(filetypes[i]->pattern, len);
1402 #endif
1403 	}
1404 }
1405 
1406 
read_group(GKeyFile * config,const gchar * group_name,GeanyFiletypeGroupID group_id)1407 static void read_group(GKeyFile *config, const gchar *group_name, GeanyFiletypeGroupID group_id)
1408 {
1409 	gchar **names = g_key_file_get_string_list(config, "Groups", group_name, NULL, NULL);
1410 	gchar **name;
1411 
1412 	foreach_strv(name, names)
1413 	{
1414 		GeanyFiletype *ft = filetypes_lookup_by_name(*name);
1415 
1416 		if (ft)
1417 		{
1418 			ft->group = group_id;
1419 			if (ft->priv->custom &&
1420 				(group_id == GEANY_FILETYPE_GROUP_COMPILED || group_id == GEANY_FILETYPE_GROUP_SCRIPT))
1421 			{
1422 				SETPTR(ft->title, filetype_make_title(ft->name, TITLE_SOURCE_FILE));
1423 			}
1424 		}
1425 		else
1426 			geany_debug("Filetype '%s' not found for group '%s'!", *name, group_name);
1427 	}
1428 	g_strfreev(names);
1429 }
1430 
1431 
read_groups(GKeyFile * config)1432 static void read_groups(GKeyFile *config)
1433 {
1434 	read_group(config, "Programming", GEANY_FILETYPE_GROUP_COMPILED);
1435 	read_group(config, "Script", GEANY_FILETYPE_GROUP_SCRIPT);
1436 	read_group(config, "Markup", GEANY_FILETYPE_GROUP_MARKUP);
1437 	read_group(config, "Misc", GEANY_FILETYPE_GROUP_MISC);
1438 	read_group(config, "None", GEANY_FILETYPE_GROUP_NONE);
1439 }
1440 
1441 
read_filetype_config(void)1442 static void read_filetype_config(void)
1443 {
1444 	gchar *sysconfigfile = g_build_filename(app->datadir, "filetype_extensions.conf", NULL);
1445 	gchar *userconfigfile = g_build_filename(app->configdir, "filetype_extensions.conf", NULL);
1446 	GKeyFile *sysconfig = g_key_file_new();
1447 	GKeyFile *userconfig = g_key_file_new();
1448 
1449 	g_key_file_load_from_file(sysconfig, sysconfigfile, G_KEY_FILE_NONE, NULL);
1450 	g_key_file_load_from_file(userconfig, userconfigfile, G_KEY_FILE_NONE, NULL);
1451 
1452 	read_extensions(sysconfig, userconfig);
1453 	read_groups(sysconfig);
1454 	read_groups(userconfig);
1455 
1456 	g_free(sysconfigfile);
1457 	g_free(userconfigfile);
1458 	g_key_file_free(sysconfig);
1459 	g_key_file_free(userconfig);
1460 }
1461 
1462 
filetypes_reload_extensions(void)1463 void filetypes_reload_extensions(void)
1464 {
1465 	guint i;
1466 
1467 	read_filetype_config();
1468 
1469 	/* Redetect filetype of any documents with none set */
1470 	foreach_document(i)
1471 	{
1472 		GeanyDocument *doc = documents[i];
1473 		if (doc->file_type->id != GEANY_FILETYPES_NONE)
1474 			continue;
1475 		document_set_filetype(doc, filetypes_detect_from_document(doc));
1476 	}
1477 }
1478 
1479 
1480 /** Accessor function for @ref GeanyData::filetypes_array items.
1481  * Example: @code ft = filetypes_index(GEANY_FILETYPES_C); @endcode
1482  * @param idx @c filetypes_array index.
1483  * @return @transfer{none} @nullable The filetype, or @c NULL if @a idx is out of range.
1484  *
1485  *  @since 0.16
1486  */
1487 GEANY_API_SYMBOL
filetypes_index(gint idx)1488 GeanyFiletype *filetypes_index(gint idx)
1489 {
1490 	return (idx >= 0 && idx < (gint) filetypes_array->len) ? filetypes[idx] : NULL;
1491 }
1492 
1493 
filetypes_reload(void)1494 void filetypes_reload(void)
1495 {
1496 	guint i;
1497 	GeanyDocument *current_doc;
1498 
1499 	/* reload filetype configs */
1500 	for (i = 0; i < filetypes_array->len; i++)
1501 	{
1502 		/* filetypes_load_config() will skip not loaded filetypes */
1503 		filetypes_load_config(i, TRUE);
1504 	}
1505 
1506 	current_doc = document_get_current();
1507 	if (!current_doc)
1508 		return;
1509 
1510 	/* update document styling */
1511 	foreach_document(i)
1512 	{
1513 		if (current_doc != documents[i])
1514 			document_reload_config(documents[i]);
1515 	}
1516 	/* process the current document at last */
1517 	document_reload_config(current_doc);
1518 }
1519 
1520 
1521 /** Gets @c ft->name or a translation for filetype None.
1522  * @param ft .
1523  * @return .
1524  * @since Geany 0.20 */
1525 GEANY_API_SYMBOL
filetypes_get_display_name(GeanyFiletype * ft)1526 const gchar *filetypes_get_display_name(GeanyFiletype *ft)
1527 {
1528 	return ft->id == GEANY_FILETYPES_NONE ? _("None") : ft->name;
1529 }
1530 
1531 
1532 /* gets comment_open/comment_close/comment_single strings from the filetype
1533  * @param single_first: whether single comment is preferred if both available
1534  * returns true if at least comment_open is set, false otherwise */
filetype_get_comment_open_close(const GeanyFiletype * ft,gboolean single_first,const gchar ** co,const gchar ** cc)1535 gboolean filetype_get_comment_open_close(const GeanyFiletype *ft, gboolean single_first,
1536 		const gchar **co, const gchar **cc)
1537 {
1538 	g_return_val_if_fail(ft != NULL, FALSE);
1539 	g_return_val_if_fail(co != NULL, FALSE);
1540 	g_return_val_if_fail(cc != NULL, FALSE);
1541 
1542 	if (single_first)
1543 	{
1544 		*co = ft->comment_single;
1545 		if (!EMPTY(*co))
1546 			*cc = NULL;
1547 		else
1548 		{
1549 			*co = ft->comment_open;
1550 			*cc = ft->comment_close;
1551 		}
1552 	}
1553 	else
1554 	{
1555 		*co = ft->comment_open;
1556 		if (!EMPTY(*co))
1557 			*cc = ft->comment_close;
1558 		else
1559 		{
1560 			*co = ft->comment_single;
1561 			*cc = NULL;
1562 		}
1563 	}
1564 
1565 	return !EMPTY(*co);
1566 }
1567 
copy_(void * src)1568 static void        *copy_(void *src) { return src; }
free_(void * doc)1569 static void         free_(void *doc) { }
1570 
1571 /** @gironly
1572  * Gets the GType of GeanyFiletype
1573  *
1574  * @return the GeanyFiletype type */
1575 GEANY_API_SYMBOL
1576 GType filetype_get_type (void);
1577 
1578 G_DEFINE_BOXED_TYPE(GeanyFiletype, filetype, copy_, free_);
1579