1 /*
2  *      speller.c - this file is part of Spellcheck, a Geany plugin
3  *
4  *      Copyright 2008-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5  *      Copyright 2008-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
6  *
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.
11  *
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.
16  *
17  *      You should have received a copy of the GNU General Public License
18  *      along with this program; if not, write to the Free Software
19  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  *      MA 02110-1301, USA.
21  *
22  * $Id$
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 #include <geanyplugin.h>
30 #include <scintilla/SciLexer.h>
31 
32 #include <string.h>
33 #include <ctype.h>
34 #include <enchant.h>
35 
36 #include "speller.h"
37 #include "scplugin.h"
38 
39 
40 
41 static EnchantBroker *sc_speller_broker = NULL;
42 static EnchantDict *sc_speller_dict = NULL;
43 
44 
45 
dict_describe(const gchar * const lang,const gchar * const name,const gchar * const desc,const gchar * const file,void * target)46 static void dict_describe(const gchar* const lang, const gchar* const name,
47 						  const gchar* const desc, const gchar* const file, void *target)
48 {
49 	gchar **result = (gchar**) target;
50 	*result = g_strdup_printf("\"%s\" (%s)", lang, name);
51 }
52 
53 
is_word_sep(gunichar c)54 static gboolean is_word_sep(gunichar c)
55 {
56 	return g_unichar_isspace(c) || g_unichar_ispunct(c);
57 }
58 
59 
60 /* Strip punctuation and white space, more or less Unicode-safe.
61  * The offset of the start of the word is stored in offset if non-NULL. */
strip_word(const gchar * word_to_check,gint * result_offset)62 static gchar *strip_word(const gchar *word_to_check, gint *result_offset)
63 {
64 	gunichar c;
65 	gchar *word = g_strdup(word_to_check);
66 	gchar *word_start = word;
67 	gchar *word_end;
68 	gint offset = 0;
69 	gint word_len;
70 	gint new_word_len;
71 
72 	/* strip from the left */
73 	do
74 	{
75 		c = g_utf8_get_char_validated(word, -1);
76 		if (is_word_sep(c))
77 		{	/* skip this character */
78 			word = g_utf8_next_char(word);
79 		}
80 		else
81 			break;
82 	} while (c != (gunichar) -1 && c != 0 && *word != '\0');
83 	word_len = strlen(word_to_check);
84 	offset = word - word_start;
85 	new_word_len = word_len - offset;
86 
87 	if (new_word_len <= 0)
88 	{	/* empty or only punctuation in input string */
89 		*result_offset = 0;
90 		g_free(word_start);
91 		return NULL;
92 	}
93 	/* move the string in-place and truncate it */
94 	g_memmove(word_start, word, new_word_len);
95 	word = word_start;
96 	word[new_word_len] = '\0';
97 	if (EMPTY(word))
98 	{
99 		g_free(word);
100 		return NULL;
101 	}
102 	/* strip from the right */
103 	word_end = word + strlen(word);
104 	do
105 	{
106 		word_end = g_utf8_prev_char(word_end);
107 		c = g_utf8_get_char_validated(word_end, -1);
108 		if (is_word_sep(c))
109 		{	/* skip this character */
110 			*word_end = '\0';
111 		}
112 		else
113 			break;
114 	} while (c != (gunichar) -1 && word_end >= word);
115 
116 	if (result_offset != NULL)
117 		*result_offset = offset;
118 
119 	return word;
120 }
121 
122 
sc_speller_check_word(GeanyDocument * doc,gint line_number,const gchar * word,gint start_pos,gint end_pos)123 static gint sc_speller_check_word(GeanyDocument *doc, gint line_number, const gchar *word,
124 						   gint start_pos, gint end_pos)
125 {
126 	gsize n_suggs = 0;
127 	gchar *word_to_check;
128 	gint offset;
129 
130 	g_return_val_if_fail(sc_speller_dict != NULL, 0);
131 	g_return_val_if_fail(doc != NULL, 0);
132 	g_return_val_if_fail(word != NULL, 0);
133 	g_return_val_if_fail(start_pos >= 0 && end_pos >= 0, 0);
134 
135 	if (EMPTY(word))
136 		return 0;
137 
138 	/* ignore numbers or words starting with digits */
139 	if (isdigit(*word))
140 		return 0;
141 
142 	/* ignore non-text */
143 	if (! sc_speller_is_text(doc, start_pos))
144 		return 0;
145 
146 	/* strip punctuation and white space */
147 	word_to_check = strip_word(word, &offset);
148 	if (EMPTY(word_to_check))
149 	{
150 		g_free(word_to_check);
151 		return 0;
152 	}
153 
154 	/* recalculate start_pos and end_pos */
155 	start_pos += offset;
156 	end_pos = start_pos + strlen(word_to_check);
157 
158 	/* early out if the word is spelled correctly */
159 	if (enchant_dict_check(sc_speller_dict, word_to_check, -1) == 0)
160 	{
161 		g_free(word_to_check);
162 		return 0;
163 	}
164 
165 	editor_indicator_set_on_range(doc->editor, GEANY_INDICATOR_ERROR, start_pos, end_pos);
166 
167 	if (sc_info->use_msgwin && line_number != -1)
168 	{
169 		gsize j;
170 		gchar **suggs;
171 		GString *str;
172 
173 		str = g_string_sized_new(256);
174 		suggs = enchant_dict_suggest(sc_speller_dict, word_to_check, -1, &n_suggs);
175 		if (suggs != NULL)
176 		{
177 			g_string_append_printf(str, "line %d: %s | ",  line_number + 1, word_to_check);
178 
179 			g_string_append(str, _("Try: "));
180 
181 			/* Now find the misspellings in the line, limit suggestions to a maximum of 15 (for now) */
182 			for (j = 0; j < MIN(n_suggs, 15); j++)
183 			{
184 				g_string_append(str, suggs[j]);
185 				g_string_append_c(str, ' ');
186 			}
187 
188 			msgwin_msg_add(COLOR_RED, line_number + 1, doc, "%s", str->str);
189 
190 			if (suggs != NULL && n_suggs > 0)
191 				enchant_dict_free_string_list(sc_speller_dict, suggs);
192 		}
193 		g_string_free(str, TRUE);
194 	}
195 
196 	g_free(word_to_check);
197 	return n_suggs;
198 }
199 
200 
sc_speller_process_line(GeanyDocument * doc,gint line_number)201 gint sc_speller_process_line(GeanyDocument *doc, gint line_number)
202 {
203 	gint pos_start, pos_end;
204 	gint wstart, wend;
205 	gint suggestions_found = 0;
206 	gint wordchars_len;
207 	gchar *wordchars;
208 	gchar *underscore_in_wordchars = NULL;
209 	gboolean wordchars_modified = FALSE;
210 
211 	g_return_val_if_fail(sc_speller_dict != NULL, 0);
212 	g_return_val_if_fail(doc != NULL, 0);
213 
214 	if (! DOC_VALID(doc))
215 		return 0; /* current document has been closed */
216 
217 	/* add ' (single quote) temporarily to wordchars
218 	 * to be able to check for "doesn't", "isn't" and similar */
219 	wordchars_len = scintilla_send_message(doc->editor->sci, SCI_GETWORDCHARS, 0, 0);
220 	wordchars = g_malloc0(wordchars_len + 2); /* 2 = temporarily added "'" and "\0" */
221 	scintilla_send_message(doc->editor->sci, SCI_GETWORDCHARS, 0, (sptr_t)wordchars);
222 	if (! strchr(wordchars, '\''))
223 	{
224 		/* temporarily add "'" to the wordchars */
225 		wordchars[wordchars_len] = '\'';
226 		wordchars_modified = TRUE;
227 	}
228 	underscore_in_wordchars = strchr(wordchars, '_');
229 	if (underscore_in_wordchars != NULL)
230 	{
231 		/* Temporarily remove underscore from the wordchars to treat
232 		 * it as a word seperator. Replace it by a "'" which we added already above. */
233 		*underscore_in_wordchars = '\'';
234 		wordchars_modified = TRUE;
235 	}
236 	if (wordchars_modified)
237 	{
238 		/* apply previously changed WORDCHARS setting */
239 		scintilla_send_message(doc->editor->sci, SCI_SETWORDCHARS, 0, (sptr_t)wordchars);
240 	}
241 	pos_start = sci_get_position_from_line(doc->editor->sci, line_number);
242 	pos_end = sci_get_position_from_line(doc->editor->sci, line_number + 1);
243 
244 	while (pos_start < pos_end)
245 	{
246 		gchar *word;
247 
248 		wstart = scintilla_send_message(doc->editor->sci, SCI_WORDSTARTPOSITION, pos_start, TRUE);
249 		wend = scintilla_send_message(doc->editor->sci, SCI_WORDENDPOSITION, wstart, FALSE);
250 		if (wstart == wend)
251 			break;
252 
253 		word = sci_get_contents_range(doc->editor->sci, wstart, wend);
254 
255 		suggestions_found += sc_speller_check_word(doc, line_number, word, wstart, wend);
256 
257 		pos_start = wend + 1;
258 
259 		g_free(word);
260 	}
261 
262 	if (wordchars_modified)
263 	{
264 		if (underscore_in_wordchars != NULL)
265 			/* re-add underscore if we removed it above */
266 			*underscore_in_wordchars = '_';
267 		/* reset wordchars for the current document */
268 		wordchars[wordchars_len] = '\0';
269 		scintilla_send_message(doc->editor->sci, SCI_SETWORDCHARS, 0, (sptr_t)wordchars);
270 	}
271 	g_free(wordchars);
272 	return suggestions_found;
273 }
274 
275 
sc_speller_check_document(GeanyDocument * doc)276 void sc_speller_check_document(GeanyDocument *doc)
277 {
278 	gint i;
279 	gint first_line, last_line;
280 	gchar *dict_string = NULL;
281 	gint suggestions_found = 0;
282 
283 	g_return_if_fail(sc_speller_dict != NULL);
284 	g_return_if_fail(doc != NULL);
285 
286 	ui_progress_bar_start(_("Checking"));
287 
288 	enchant_dict_describe(sc_speller_dict, dict_describe, &dict_string);
289 
290 	if (sci_has_selection(doc->editor->sci))
291 	{
292 		first_line = sci_get_line_from_position(
293 			doc->editor->sci, sci_get_selection_start(doc->editor->sci));
294 		last_line = sci_get_line_from_position(
295 			doc->editor->sci, sci_get_selection_end(doc->editor->sci));
296 
297 		if (sc_info->use_msgwin)
298 			msgwin_msg_add(COLOR_BLUE, -1, NULL,
299 				_("Checking file \"%s\" (lines %d to %d using %s):"),
300 				DOC_FILENAME(doc), first_line + 1, last_line + 1, dict_string);
301 		g_message("Checking file \"%s\" (lines %d to %d using %s):",
302 			DOC_FILENAME(doc), first_line + 1, last_line + 1, dict_string);
303 	}
304 	else
305 	{
306 		first_line = 0;
307 		last_line = sci_get_line_count(doc->editor->sci);
308 		if (sc_info->use_msgwin)
309 			msgwin_msg_add(COLOR_BLUE, -1, NULL, _("Checking file \"%s\" (using %s):"),
310 				DOC_FILENAME(doc), dict_string);
311 		g_message("Checking file \"%s\" (using %s):", DOC_FILENAME(doc), dict_string);
312 	}
313 	g_free(dict_string);
314 
315 	if (first_line == last_line)
316 	{
317 		suggestions_found += sc_speller_process_line(doc, first_line);
318 	}
319 	else
320 	{
321 		for (i = first_line; i < last_line; i++)
322 		{
323 			if (! DOC_VALID(doc))
324 			{	/* current document has been closed (might happen while checking large files) */
325 				ui_progress_bar_stop();
326 				return;
327 			}
328 			suggestions_found += sc_speller_process_line(doc, i);
329 
330 			/* process other GTK events to keep the GUI being responsive */
331 			while (g_main_context_iteration(NULL, FALSE));
332 		}
333 	}
334 	if (suggestions_found == 0 && sc_info->use_msgwin)
335 		msgwin_msg_add(COLOR_BLUE, -1, NULL, _("The checked text is spelled correctly."));
336 
337 	ui_progress_bar_stop();
338 }
339 
340 
broker_init_failed(void)341 static void broker_init_failed(void)
342 {
343 	const gchar *err = enchant_broker_get_error(sc_speller_broker);
344 	gchar *msg = g_strdup_printf(
345 		_("The Enchant library couldn't be initialized (%s)."),
346 		(err != NULL) ? err : _("unknown error (maybe the chosen language is not available)"));
347 
348 	msgwin_status_add("%s", msg);
349 	if (main_is_realized())
350 		/* show dialog only after Geany has been loaded already, i.e. not while starting up */
351 		dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", msg);
352 
353 	g_free(msg);
354 }
355 
356 
dict_compare(gpointer data,gpointer user_data)357 static void dict_compare(gpointer data, gpointer user_data)
358 {
359 	gboolean *supported = user_data;
360 
361 	if (utils_str_equal(sc_info->default_language, data))
362 		*supported = TRUE;
363 }
364 
365 
check_default_lang(void)366 static gboolean check_default_lang(void)
367 {
368 	gboolean supported = FALSE;
369 
370 	g_ptr_array_foreach(sc_info->dicts, dict_compare, &supported);
371 
372 	return supported;
373 }
374 
375 
sc_speller_get_default_lang(void)376 gchar *sc_speller_get_default_lang(void)
377 {
378 	const gchar *lang = g_getenv("LANG");
379 	gchar *result = NULL;
380 
381 	if (! EMPTY(lang))
382 	{
383 		if (*lang == 'C' || *lang == 'c')
384 			lang = "en";
385 		else
386 		{	/* if we have something like de_DE.UTF-8, strip everything from the period to the end */
387 			gchar *period = strchr(lang, '.');
388 			if (period != NULL)
389 				result = g_strndup(lang, period - lang);
390 		}
391 	}
392 	else
393 		lang = "en";
394 
395 	return (result != NULL) ? result : g_strdup(lang);
396 }
397 
398 
add_dict_array(const gchar * const lang_tag,const gchar * const provider_name,const gchar * const provider_desc,const gchar * const provider_file,gpointer user_data)399 static void add_dict_array(const gchar* const lang_tag, const gchar* const provider_name,
400 						   const gchar* const provider_desc, const gchar* const provider_file,
401 						   gpointer user_data)
402 {
403 	guint i;
404 	gchar *result = g_strdup(lang_tag);
405 
406 	/* sometimes dictionaries are named lang-LOCALE instead of lang_LOCALE, so replace the
407 	 * hyphen by a dash, enchant seems to not care about it. */
408 	for (i = 0; i < strlen(result); i++)
409 	{
410 		if (result[i] == '-')
411 			result[i] = '_';
412 	}
413 
414 	/* find duplicates and skip them */
415 	for (i = 0; i < sc_info->dicts->len; i++)
416 	{
417 		if (utils_str_equal(g_ptr_array_index(sc_info->dicts, i), result))
418 		{
419 			g_free(result);
420 			return;
421 		}
422 	}
423 
424 	g_ptr_array_add(sc_info->dicts, result);
425 }
426 
427 
sort_dicts(gconstpointer a,gconstpointer b)428 static gint sort_dicts(gconstpointer a, gconstpointer b)
429 {	/* casting mania ;-) */
430 	return strcmp((gchar*)((GPtrArray*)a)->pdata, (gchar*)((GPtrArray*)b)->pdata);
431 }
432 
433 
sc_speller_dicts_free(void)434 static void sc_speller_dicts_free(void)
435 {
436 	guint i;
437 	if (sc_info->dicts != NULL)
438 	{
439 		for (i = 0; i < sc_info->dicts->len; i++)
440 		{
441 			g_free(g_ptr_array_index(sc_info->dicts, i));
442 		}
443 		g_ptr_array_free(sc_info->dicts, TRUE);
444 	}
445 }
446 
447 
create_dicts_array(void)448 static void create_dicts_array(void)
449 {
450 	sc_speller_dicts_free();
451 
452 	sc_info->dicts = g_ptr_array_new();
453 
454 	enchant_broker_list_dicts(sc_speller_broker, add_dict_array, sc_info->dicts);
455 
456 	g_ptr_array_sort(sc_info->dicts, sort_dicts);
457 }
458 
459 
sc_speller_dict_free_string_list(gchar ** tmp_suggs)460 void sc_speller_dict_free_string_list(gchar **tmp_suggs)
461 {
462 	g_return_if_fail(sc_speller_dict != NULL);
463 
464 	enchant_dict_free_string_list(sc_speller_dict, tmp_suggs);
465 }
466 
467 
sc_speller_add_word(const gchar * word)468 void sc_speller_add_word(const gchar *word)
469 {
470 	g_return_if_fail(sc_speller_dict != NULL);
471 	g_return_if_fail(word != NULL);
472 
473 #ifdef HAVE_ENCHANT_1_5
474 	/* enchant_dict_add() is available since Enchant 1.4 */
475 	enchant_dict_add(sc_speller_dict, word, -1);
476 #else
477 	enchant_dict_add_to_pwl(sc_speller_dict, word, -1);
478 #endif
479 }
480 
sc_speller_dict_check(const gchar * word)481 gboolean sc_speller_dict_check(const gchar *word)
482 {
483 	g_return_val_if_fail(sc_speller_dict != NULL, FALSE);
484 	g_return_val_if_fail(word != NULL, FALSE);
485 
486 	return enchant_dict_check(sc_speller_dict, word, -1);
487 }
488 
489 
sc_speller_dict_suggest(const gchar * word,gsize * n_suggs)490 gchar **sc_speller_dict_suggest(const gchar *word, gsize *n_suggs)
491 {
492 	g_return_val_if_fail(sc_speller_dict != NULL, NULL);
493 	g_return_val_if_fail(word != NULL, NULL);
494 
495 	return enchant_dict_suggest(sc_speller_dict, word, -1, n_suggs);
496 }
497 
498 
sc_speller_add_word_to_session(const gchar * word)499 void sc_speller_add_word_to_session(const gchar *word)
500 {
501 	g_return_if_fail(sc_speller_dict != NULL);
502 	g_return_if_fail(word != NULL);
503 
504 	enchant_dict_add_to_session(sc_speller_dict, word, -1);
505 }
506 
507 
sc_speller_store_replacement(const gchar * old_word,const gchar * new_word)508 void sc_speller_store_replacement(const gchar *old_word, const gchar *new_word)
509 {
510 	g_return_if_fail(sc_speller_dict != NULL);
511 	g_return_if_fail(old_word != NULL);
512 	g_return_if_fail(new_word != NULL);
513 
514 	enchant_dict_store_replacement(sc_speller_dict, old_word, -1, new_word, -1);
515 }
516 
517 
sc_speller_reinit_enchant_dict(void)518 void sc_speller_reinit_enchant_dict(void)
519 {
520 	const gchar *lang = sc_info->default_language;
521 
522 	/* Release a previous dict object */
523 	if (sc_speller_dict != NULL)
524 		enchant_broker_free_dict(sc_speller_broker, sc_speller_dict);
525 
526 #ifdef HAVE_ENCHANT_2_0
527 	#define ENCHANT_CONFIG_ENV_NAME "ENCHANT_CONFIG_DIR"
528 	/* set custom configuration path for enchant (Enchant will look for dictionaries there) */
529 	if (! EMPTY(sc_info->dictionary_dir))
530 	{
531 		g_setenv(ENCHANT_CONFIG_ENV_NAME, sc_info->dictionary_dir, TRUE);
532 	}
533 	else
534 	{
535 		g_unsetenv(ENCHANT_CONFIG_ENV_NAME);
536 	}
537 #elif HAVE_ENCHANT_1_5
538 	{
539 		const gchar *old_path;
540 		gchar *new_path;
541 
542 		/* add custom dictionary path for myspell (primarily used on Windows) */
543 		old_path = enchant_broker_get_param(sc_speller_broker, "enchant.myspell.dictionary.path");
544 		if (old_path != NULL)
545 			new_path = g_strconcat(
546 				old_path, G_SEARCHPATH_SEPARATOR_S, sc_info->dictionary_dir, NULL);
547 		else
548 			new_path = sc_info->dictionary_dir;
549 
550 		enchant_broker_set_param(sc_speller_broker, "enchant.myspell.dictionary.path", new_path);
551 		if (new_path != sc_info->dictionary_dir)
552 			g_free(new_path);
553 	}
554 #endif
555 	create_dicts_array();
556 
557 	/* Check if the stored default dictionary is (still) available, fall back to the first
558 	 * one in the list if not */
559 	if (EMPTY(lang) || ! check_default_lang())
560 	{
561 		if (sc_info->dicts->len > 0)
562 		{
563 			lang = g_ptr_array_index(sc_info->dicts, 0);
564 			g_warning("Stored language ('%s') could not be loaded. Falling back to '%s'",
565 				sc_info->default_language, lang);
566 		}
567 		else
568 			g_warning("Stored language ('%s') could not be loaded.", sc_info->default_language);
569 	}
570 
571 	/* Request new dict object */
572 	if (! EMPTY(lang))
573 		sc_speller_dict = enchant_broker_request_dict(sc_speller_broker, lang);
574 	else
575 		sc_speller_dict = NULL;
576 	if (sc_speller_dict == NULL)
577 	{
578 		broker_init_failed();
579 		gtk_widget_set_sensitive(sc_info->menu_item, FALSE);
580 	}
581 	else
582 	{
583 		gtk_widget_set_sensitive(sc_info->menu_item, TRUE);
584 	}
585 }
586 
587 
log_enchant_version(void)588 static void log_enchant_version(void)
589 {
590 #ifdef HAVE_ENCHANT_2_0
591 	const gchar *enchant_version = enchant_get_version();
592 #else
593 	const gchar *enchant_version = "1.6 or older";
594 #endif
595 
596 	g_debug("Initializing Enchant library version %s", enchant_version);
597 }
598 
599 
sc_speller_init(void)600 void sc_speller_init(void)
601 {
602 	log_enchant_version();
603 	sc_speller_broker = enchant_broker_init();
604 
605 	sc_speller_reinit_enchant_dict();
606 }
607 
608 
sc_speller_free(void)609 void sc_speller_free(void)
610 {
611 	sc_speller_dicts_free();
612 	if (sc_speller_dict != NULL)
613 		enchant_broker_free_dict(sc_speller_broker, sc_speller_dict);
614 	enchant_broker_free(sc_speller_broker);
615 }
616 
617 
sc_speller_is_text(GeanyDocument * doc,gint pos)618 gboolean sc_speller_is_text(GeanyDocument *doc, gint pos)
619 {
620 	gint lexer, style;
621 
622 	g_return_val_if_fail(doc != NULL, FALSE);
623 	g_return_val_if_fail(pos >= 0, FALSE);
624 
625 	style = sci_get_style_at(doc->editor->sci, pos);
626 	/* early out for the default style */
627 	if (style == STYLE_DEFAULT)
628 		return TRUE;
629 
630 	lexer = scintilla_send_message(doc->editor->sci, SCI_GETLEXER, 0, 0);
631 	switch (lexer)
632 	{
633 		case SCLEX_ABAQUS:
634 		{
635 			switch (style)
636 			{
637 				case SCE_ABAQUS_DEFAULT:
638 				case SCE_ABAQUS_COMMENT:
639 				case SCE_ABAQUS_COMMENTBLOCK:
640 				case SCE_ABAQUS_STRING:
641 					return TRUE;
642 				default:
643 					return FALSE;
644 			}
645 			break;
646 		}
647 		case SCLEX_ADA:
648 		{
649 			switch (style)
650 			{
651 				case SCE_ADA_DEFAULT:
652 				case SCE_ADA_COMMENTLINE:
653 				case SCE_ADA_STRING:
654 				case SCE_ADA_STRINGEOL:
655 				case SCE_ADA_CHARACTER:
656 				case SCE_ADA_CHARACTEREOL:
657 					return TRUE;
658 				default:
659 					return FALSE;
660 			}
661 			break;
662 		}
663 		case SCLEX_ASM:
664 		{
665 			switch (style)
666 			{
667 				case SCE_ASM_DEFAULT:
668 				case SCE_ASM_COMMENT:
669 				case SCE_ASM_COMMENTBLOCK:
670 				case SCE_ASM_STRING:
671 				case SCE_ASM_STRINGEOL:
672 				case SCE_ASM_CHARACTER:
673 					return TRUE;
674 				default:
675 					return FALSE;
676 			}
677 			break;
678 		}
679 		case SCLEX_BASH:
680 		{
681 			switch (style)
682 			{
683 				case SCE_SH_DEFAULT:
684 				case SCE_SH_COMMENTLINE:
685 				case SCE_SH_STRING:
686 				case SCE_SH_CHARACTER:
687 					return TRUE;
688 				default:
689 					return FALSE;
690 			}
691 			break;
692 		}
693 		case SCLEX_BATCH:
694 		{
695 			switch (style)
696 			{
697 				case SCE_BAT_DEFAULT:
698 				case SCE_BAT_COMMENT:
699 					return TRUE;
700 				default:
701 					return FALSE;
702 			}
703 			break;
704 		}
705 		case SCLEX_CAML:
706 		{
707 			switch (style)
708 			{
709 				case SCE_CAML_DEFAULT:
710 				case SCE_CAML_COMMENT:
711 				case SCE_CAML_COMMENT1:
712 				case SCE_CAML_COMMENT2:
713 				case SCE_CAML_COMMENT3:
714 				case SCE_CAML_STRING:
715 				case SCE_CAML_CHAR:
716 					return TRUE;
717 				default:
718 					return FALSE;
719 			}
720 			break;
721 		}
722 		case SCLEX_CMAKE:
723 		{
724 			switch (style)
725 			{
726 				case SCE_CMAKE_DEFAULT:
727 				case SCE_CMAKE_COMMENT:
728 				case SCE_CMAKE_STRINGDQ:
729 				case SCE_CMAKE_STRINGLQ:
730 				case SCE_CMAKE_STRINGRQ:
731 					return TRUE;
732 				default:
733 					return FALSE;
734 			}
735 			break;
736 		}
737 #ifdef SCE_PAS_DEFAULT
738 		case SCLEX_PASCAL:
739 		{
740 			switch (style)
741 			{
742 				case SCE_PAS_DEFAULT:
743 				case SCE_PAS_COMMENT:
744 				case SCE_PAS_COMMENT2:
745 				case SCE_PAS_COMMENTLINE:
746 				case SCE_PAS_STRING:
747 				case SCE_PAS_CHARACTER:
748 					return TRUE;
749 				default:
750 					return FALSE;
751 			}
752 			break;
753 		}
754 #else
755 		case SCLEX_PASCAL:
756 #endif
757 		case SCLEX_COBOL:
758 		case SCLEX_CPP:
759 		{
760 			switch (style)
761 			{
762 				case SCE_C_DEFAULT:
763 				case SCE_C_COMMENT:
764 				case SCE_C_COMMENTLINE:
765 				case SCE_C_COMMENTDOC:
766 				case SCE_C_STRING:
767 				case SCE_C_CHARACTER:
768 				case SCE_C_STRINGEOL:
769 				case SCE_C_COMMENTLINEDOC:
770 					return TRUE;
771 				default:
772 					return FALSE;
773 			}
774 			break;
775 		}
776 		case SCLEX_COFFEESCRIPT:
777 		{
778 			switch (style)
779 			{
780 				case SCE_COFFEESCRIPT_CHARACTER:
781 				case SCE_COFFEESCRIPT_COMMENTBLOCK:
782 				case SCE_COFFEESCRIPT_COMMENTDOCKEYWORD:
783 				case SCE_COFFEESCRIPT_COMMENTDOCKEYWORDERROR:
784 				case SCE_COFFEESCRIPT_COMMENTLINEDOC:
785 				case SCE_COFFEESCRIPT_STRING:
786 				case SCE_COFFEESCRIPT_STRINGEOL:
787 				case SCE_COFFEESCRIPT_STRINGRAW:
788 					return TRUE;
789 				default:
790 					return FALSE;
791 			}
792 			break;
793 		}
794 		case SCLEX_CSS:
795 		{
796 			switch (style)
797 			{
798 				case SCE_CSS_DEFAULT:
799 				case SCE_CSS_COMMENT:
800 					return TRUE;
801 				default:
802 					return FALSE;
803 			}
804 			break;
805 		}
806 		case SCLEX_D:
807 		{
808 			switch (style)
809 			{
810 				case SCE_D_DEFAULT:
811 				case SCE_D_COMMENT:
812 				case SCE_D_COMMENTLINE:
813 				case SCE_D_COMMENTDOC:
814 				case SCE_D_COMMENTNESTED:
815 				case SCE_D_STRING:
816 				case SCE_D_STRINGEOL:
817 				case SCE_D_CHARACTER:
818 				case SCE_D_COMMENTLINEDOC:
819 					return TRUE;
820 				default:
821 					return FALSE;
822 			}
823 			break;
824 		}
825 		case SCLEX_DIFF:
826 		{
827 			switch (style)
828 			{
829 				case SCE_DIFF_DEFAULT:
830 				case SCE_DIFF_COMMENT:
831 				case SCE_DIFF_HEADER:
832 					return TRUE;
833 				default:
834 					return FALSE;
835 			}
836 			break;
837 		}
838 		case SCLEX_ERLANG:
839 		{
840 			switch (style)
841 			{
842 				case SCE_ERLANG_DEFAULT:
843 				case SCE_ERLANG_COMMENT:
844 				case SCE_ERLANG_STRING:
845 				case SCE_ERLANG_CHARACTER:
846 				case SCE_ERLANG_COMMENT_FUNCTION:
847 				case SCE_ERLANG_COMMENT_MODULE:
848 				case SCE_ERLANG_COMMENT_DOC:
849 				case SCE_ERLANG_COMMENT_DOC_MACRO:
850 					return TRUE;
851 				default:
852 					return FALSE;
853 			}
854 			break;
855 		}
856 		case SCLEX_F77:
857 		case SCLEX_FORTRAN:
858 		{
859 			switch (style)
860 			{
861 				case SCE_F_DEFAULT:
862 				case SCE_F_COMMENT:
863 				case SCE_F_STRING1:
864 				case SCE_F_STRING2:
865 				case SCE_F_STRINGEOL:
866 					return TRUE;
867 				default:
868 					return FALSE;
869 			}
870 			break;
871 		}
872 		case SCLEX_FORTH:
873 		{
874 			switch (style)
875 			{
876 				case SCE_FORTH_DEFAULT:
877 				case SCE_FORTH_COMMENT:
878 				case SCE_FORTH_COMMENT_ML:
879 				case SCE_FORTH_STRING:
880 				case SCE_FORTH_LOCALE:
881 					return TRUE;
882 				default:
883 					return FALSE;
884 			}
885 			break;
886 		}
887 		case SCLEX_FREEBASIC:
888 		{
889 			switch (style)
890 			{
891 				case SCE_B_DEFAULT:
892 				case SCE_B_COMMENT:
893 				case SCE_B_STRING:
894 				case SCE_B_STRINGEOL:
895 				case SCE_B_CONSTANT:
896 					return TRUE;
897 				default:
898 					return FALSE;
899 			}
900 			break;
901 		}
902 		case SCLEX_HASKELL:
903 		{
904 			switch (style)
905 			{
906 				case SCE_HA_DEFAULT:
907 				case SCE_HA_COMMENTLINE:
908 				case SCE_HA_COMMENTBLOCK:
909 				case SCE_HA_COMMENTBLOCK2:
910 				case SCE_HA_COMMENTBLOCK3:
911 				case SCE_HA_STRING:
912 				case SCE_HA_CHARACTER:
913 				case SCE_HA_DATA:
914 					return TRUE;
915 				default:
916 					return FALSE;
917 			}
918 			break;
919 		}
920 		case SCLEX_HTML:
921 		case SCLEX_PHPSCRIPT:
922 		case SCLEX_XML:
923 		{
924 			switch (style)
925 			{
926 				case SCE_H_DEFAULT:
927 				case SCE_H_TAGUNKNOWN:
928 				case SCE_H_ATTRIBUTEUNKNOWN:
929 				case SCE_H_DOUBLESTRING:
930 				case SCE_H_SINGLESTRING:
931 				case SCE_H_COMMENT:
932 				case SCE_H_CDATA:
933 				case SCE_H_VALUE: /* really? */
934 				case SCE_H_SGML_DEFAULT:
935 				case SCE_H_SGML_COMMENT:
936 				case SCE_H_SGML_DOUBLESTRING:
937 				case SCE_H_SGML_SIMPLESTRING:
938 				case SCE_H_SGML_1ST_PARAM_COMMENT:
939 				case SCE_HJ_COMMENT:
940 				case SCE_HJ_COMMENTLINE:
941 				case SCE_HJ_COMMENTDOC:
942 				case SCE_HJ_DOUBLESTRING:
943 				case SCE_HJ_SINGLESTRING:
944 				case SCE_HJ_STRINGEOL:
945 				case SCE_HB_COMMENTLINE:
946 				case SCE_HB_STRING:
947 				case SCE_HB_STRINGEOL:
948 				case SCE_HBA_COMMENTLINE:
949 				case SCE_HBA_STRING:
950 				case SCE_HBA_STRINGEOL:
951 				case SCE_HJA_COMMENT:
952 				case SCE_HJA_COMMENTLINE:
953 				case SCE_HJA_COMMENTDOC:
954 				case SCE_HJA_DOUBLESTRING:
955 				case SCE_HJA_SINGLESTRING:
956 				case SCE_HJA_STRINGEOL:
957 				case SCE_HP_COMMENTLINE:
958 				case SCE_HP_STRING:
959 				case SCE_HP_CHARACTER:
960 				case SCE_HP_TRIPLE:
961 				case SCE_HP_TRIPLEDOUBLE:
962 				case SCE_HPA_COMMENTLINE:
963 				case SCE_HPA_STRING:
964 				case SCE_HPA_CHARACTER:
965 				case SCE_HPA_TRIPLE:
966 				case SCE_HPA_TRIPLEDOUBLE:
967 				case SCE_HPHP_SIMPLESTRING:
968 				case SCE_HPHP_HSTRING:
969 				case SCE_HPHP_COMMENT:
970 				case SCE_HPHP_COMMENTLINE:
971 					return TRUE;
972 				default:
973 					return FALSE;
974 			}
975 			break;
976 		}
977 		case SCLEX_LATEX:
978 		{
979 			switch (style)
980 			{
981 				case SCE_L_DEFAULT:
982 				case SCE_L_COMMENT:
983 					return TRUE;
984 				default:
985 					return FALSE;
986 			}
987 			break;
988 		}
989 		case SCLEX_LISP:
990 		{
991 			switch (style)
992 			{
993 				case SCE_LISP_DEFAULT:
994 				case SCE_LISP_COMMENT:
995 				case SCE_LISP_STRING:
996 				case SCE_LISP_STRINGEOL:
997 				case SCE_LISP_MULTI_COMMENT:
998 					return TRUE;
999 				default:
1000 					return FALSE;
1001 			}
1002 			break;
1003 		}
1004 		case SCLEX_LUA:
1005 		{
1006 			switch (style)
1007 			{
1008 				case SCE_LUA_DEFAULT:
1009 				case SCE_LUA_COMMENT:
1010 				case SCE_LUA_COMMENTLINE:
1011 				case SCE_LUA_COMMENTDOC:
1012 				case SCE_LUA_STRING:
1013 				case SCE_LUA_CHARACTER:
1014 				case SCE_LUA_LITERALSTRING:
1015 				case SCE_LUA_STRINGEOL:
1016 					return TRUE;
1017 				default:
1018 					return FALSE;
1019 			}
1020 			break;
1021 		}
1022 		case SCLEX_MAKEFILE:
1023 		{
1024 			switch (style)
1025 			{
1026 				case SCE_MAKE_DEFAULT:
1027 				case SCE_MAKE_COMMENT:
1028 					return TRUE;
1029 				default:
1030 					return FALSE;
1031 			}
1032 			break;
1033 		}
1034 		case SCLEX_MARKDOWN:
1035 		{
1036 			return TRUE;
1037 			break;
1038 		}
1039 		case SCLEX_MATLAB:
1040 		case SCLEX_OCTAVE:
1041 		{
1042 			switch (style)
1043 			{
1044 				case SCE_MATLAB_DEFAULT:
1045 				case SCE_MATLAB_COMMENT:
1046 				case SCE_MATLAB_STRING:
1047 				case SCE_MATLAB_DOUBLEQUOTESTRING:
1048 					return TRUE;
1049 				default:
1050 					return FALSE;
1051 			}
1052 			break;
1053 		}
1054 		case SCLEX_NSIS:
1055 		{
1056 			switch (style)
1057 			{
1058 				case SCE_NSIS_DEFAULT:
1059 				case SCE_NSIS_COMMENT:
1060 				case SCE_NSIS_STRINGDQ:
1061 				case SCE_NSIS_STRINGLQ:
1062 				case SCE_NSIS_STRINGRQ:
1063 				case SCE_NSIS_STRINGVAR:
1064 				case SCE_NSIS_COMMENTBOX:
1065 					return TRUE;
1066 				default:
1067 					return FALSE;
1068 			}
1069 			break;
1070 		}
1071 		case SCLEX_PERL:
1072 		{
1073 			switch (style)
1074 			{
1075 				case SCE_PL_DEFAULT:
1076 				case SCE_PL_COMMENTLINE:
1077 				case SCE_PL_STRING:
1078 				case SCE_PL_CHARACTER:
1079 				case SCE_PL_POD:
1080 				case SCE_PL_POD_VERB:
1081 				case SCE_PL_LONGQUOTE:
1082 				/* do we want SCE_PL_STRING_* too? */
1083 					return TRUE;
1084 				default:
1085 					return FALSE;
1086 			}
1087 			break;
1088 		}
1089 		case SCLEX_PO:
1090 		{
1091 			switch (style)
1092 			{
1093 				case SCE_PO_DEFAULT:
1094 				case SCE_PO_COMMENT:
1095 				case SCE_PO_MSGID_TEXT:
1096 				case SCE_PO_MSGSTR_TEXT:
1097 				case SCE_PO_MSGCTXT_TEXT:
1098 					return TRUE;
1099 				default:
1100 					return FALSE;
1101 			}
1102 			break;
1103 		}
1104 		case SCLEX_POWERSHELL:
1105 		{
1106 			switch (style)
1107 			{
1108 				case SCE_POWERSHELL_DEFAULT:
1109 				case SCE_POWERSHELL_COMMENT:
1110 				case SCE_POWERSHELL_STRING:
1111 				case SCE_POWERSHELL_COMMENTSTREAM:
1112 				case SCE_POWERSHELL_COMMENTDOCKEYWORD:
1113 					return TRUE;
1114 				default:
1115 					return FALSE;
1116 			}
1117 			break;
1118 		}
1119 		case SCLEX_PROPERTIES:
1120 		{
1121 			switch (style)
1122 			{
1123 				case SCE_PROPS_DEFAULT:
1124 				case SCE_PROPS_COMMENT:
1125 					return TRUE;
1126 				default:
1127 					return FALSE;
1128 			}
1129 			break;
1130 		}
1131 		case SCLEX_PYTHON:
1132 		{
1133 			switch (style)
1134 			{
1135 				case SCE_P_DEFAULT:
1136 				case SCE_P_COMMENTLINE:
1137 				case SCE_P_STRING:
1138 				case SCE_P_CHARACTER:
1139 				case SCE_P_TRIPLE:
1140 				case SCE_P_TRIPLEDOUBLE:
1141 				case SCE_P_COMMENTBLOCK:
1142 				case SCE_P_STRINGEOL:
1143 					return TRUE;
1144 				default:
1145 					return FALSE;
1146 			}
1147 			break;
1148 		}
1149 		case SCLEX_R:
1150 		{
1151 			switch (style)
1152 			{
1153 				case SCE_R_DEFAULT:
1154 				case SCE_R_COMMENT:
1155 				case SCE_R_STRING:
1156 				case SCE_R_STRING2:
1157 					return TRUE;
1158 				default:
1159 					return FALSE;
1160 			}
1161 			break;
1162 		}
1163 		case SCLEX_RUBY:
1164 		{
1165 			switch (style)
1166 			{
1167 				case SCE_RB_DEFAULT:
1168 				case SCE_RB_COMMENTLINE:
1169 				case SCE_RB_STRING:
1170 				case SCE_RB_CHARACTER:
1171 				case SCE_RB_POD:
1172 					return TRUE;
1173 				default:
1174 					return FALSE;
1175 			}
1176 			break;
1177 		}
1178 		case SCLEX_RUST:
1179 		{
1180 			switch (style)
1181 			{
1182 				case SCE_RUST_DEFAULT:
1183 				case SCE_RUST_COMMENTBLOCK:
1184 				case SCE_RUST_COMMENTBLOCKDOC:
1185 				case SCE_RUST_COMMENTLINE:
1186 				case SCE_RUST_COMMENTLINEDOC:
1187 				case SCE_RUST_STRING:
1188 				case SCE_RUST_STRINGR:
1189 				case SCE_RUST_BYTESTRING:
1190 				case SCE_RUST_BYTESTRINGR:
1191 				case SCE_RUST_LEXERROR:
1192 					return TRUE;
1193 				default:
1194 					return FALSE;
1195 			}
1196 			break;
1197 		}
1198 		case SCLEX_SQL:
1199 		{
1200 			switch (style)
1201 			{
1202 				case SCE_SQL_DEFAULT:
1203 				case SCE_SQL_COMMENT:
1204 				case SCE_SQL_COMMENTLINE:
1205 				case SCE_SQL_COMMENTDOC:
1206 				case SCE_SQL_STRING:
1207 				case SCE_SQL_CHARACTER:
1208 				case SCE_SQL_SQLPLUS_COMMENT:
1209 					return TRUE;
1210 				default:
1211 					return FALSE;
1212 			}
1213 			break;
1214 		}
1215 		case SCLEX_TCL:
1216 		{
1217 			switch (style)
1218 			{
1219 				case SCE_TCL_DEFAULT:
1220 				case SCE_TCL_COMMENT:
1221 				case SCE_TCL_COMMENTLINE:
1222 				case SCE_TCL_IN_QUOTE:
1223 					return TRUE;
1224 				default:
1225 					return FALSE;
1226 			}
1227 			break;
1228 		}
1229 		case SCLEX_TXT2TAGS:
1230 		{
1231 			return TRUE;
1232 			break;
1233 		}
1234 		case SCLEX_VERILOG:
1235 		{
1236 			switch (style)
1237 			{
1238 				case SCE_V_DEFAULT:
1239 				case SCE_V_COMMENT:
1240 				case SCE_V_COMMENTLINE:
1241 				case SCE_V_COMMENTLINEBANG:
1242 				case SCE_V_STRING:
1243 				case SCE_V_STRINGEOL:
1244 					return TRUE;
1245 				default:
1246 					return FALSE;
1247 			}
1248 			break;
1249 		}
1250 		case SCLEX_VHDL:
1251 		{
1252 			switch (style)
1253 			{
1254 				case SCE_VHDL_DEFAULT:
1255 				case SCE_VHDL_COMMENT:
1256 				case SCE_VHDL_COMMENTLINEBANG:
1257 				case SCE_VHDL_STRING:
1258 				case SCE_VHDL_STRINGEOL:
1259 					return TRUE;
1260 				default:
1261 					return FALSE;
1262 			}
1263 			break;
1264 		}
1265 		case SCLEX_YAML:
1266 		{
1267 			switch (style)
1268 			{
1269 				case SCE_YAML_DEFAULT:
1270 				case SCE_YAML_COMMENT:
1271 				case SCE_YAML_TEXT:
1272 					return TRUE;
1273 				default:
1274 					return FALSE;
1275 			}
1276 			break;
1277 		}
1278 	}
1279 	/* if the current lexer was not handled, let's say the passed position contains
1280 	 * valid text to not ignore more than we want */
1281 	return TRUE;
1282 }
1283