2
* speller.c - this file is part of Spellcheck, a Geany plugin
4
* Copyright 2008-2009 Enrico Trƶger <enrico(dot)troeger(at)uvena(dot)de>
5
* Copyright 2008-2009 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; either version 2 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
33
#include "plugindata.h"
37
#include "msgwindow.h"
40
#include "scintilla/SciLexer.h"
42
#include "geanyfunctions.h"
49
static EnchantBroker *sc_speller_broker = NULL;
50
static EnchantDict *sc_speller_dict = NULL;
54
static void dict_describe(const gchar* const lang, const gchar* const name,
55
const gchar* const desc, const gchar* const file, void *target)
57
gchar **result = (gchar**) target;
58
*result = g_strdup_printf("\"%s\" (%s)", lang, name);
62
static gint sc_speller_check_word(GeanyDocument *doc, gint line_number, const gchar *word,
63
gint start_pos, gint end_pos)
67
g_return_val_if_fail(sc_speller_dict != NULL, 0);
68
g_return_val_if_fail(doc != NULL, 0);
69
g_return_val_if_fail(word != NULL, 0);
70
g_return_val_if_fail(start_pos >= 0 && end_pos >= 0, 0);
75
/* ignore numbers or words starting with digits */
80
if (! sc_speller_is_text(doc, start_pos))
83
/* early out if the word is spelled correctly */
84
if (enchant_dict_check(sc_speller_dict, word, -1) == 0)
87
editor_indicator_set_on_range(doc->editor, GEANY_INDICATOR_ERROR, start_pos, end_pos);
89
if (sc_info->use_msgwin && line_number != -1)
95
str = g_string_sized_new(256);
96
suggs = enchant_dict_suggest(sc_speller_dict, word, -1, &n_suggs);
99
g_string_append_printf(str, "line %d: %s | ", line_number + 1, word);
101
g_string_append(str, _("Try: "));
103
/* Now find the misspellings in the line, limit suggestions to a maximum of 15 (for now) */
104
for (j = 0; j < MIN(n_suggs, 15); j++)
106
g_string_append(str, suggs[j]);
107
g_string_append_c(str, ' ');
110
msgwin_msg_add(COLOR_RED, line_number + 1, doc, "%s", str->str);
112
if (suggs != NULL && n_suggs > 0)
113
enchant_dict_free_string_list(sc_speller_dict, suggs);
115
g_string_free(str, TRUE);
122
gint sc_speller_process_line(GeanyDocument *doc, gint line_number, const gchar *line)
124
gint pos_start, pos_end;
127
gint suggestions_found = 0;
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(line != NULL, 0);
134
str = g_string_sized_new(256);
136
pos_start = sci_get_position_from_line(doc->editor->sci, line_number);
137
pos_end = sci_get_position_from_line(doc->editor->sci, line_number + 1);
139
while (pos_start < pos_end)
141
wstart = scintilla_send_message(doc->editor->sci, SCI_WORDSTARTPOSITION, pos_start, TRUE);
142
wend = scintilla_send_message(doc->editor->sci, SCI_WORDENDPOSITION, wstart, FALSE);
145
c = sci_get_char_at(doc->editor->sci, wstart);
146
/* hopefully it's enough to check for these both */
147
if (ispunct(c) || isspace(c))
153
/* ensure the string has enough allocated memory */
154
if (str->len < (guint)(wend - wstart))
155
g_string_set_size(str, wend - wstart);
157
sci_get_text_range(doc->editor->sci, wstart, wend, str->str);
159
suggestions_found += sc_speller_check_word(doc, line_number, str->str, wstart, wend);
161
pos_start = wend + 1;
164
g_string_free(str, TRUE);
165
return suggestions_found;
169
void sc_speller_check_document(GeanyDocument *doc)
173
gint first_line, last_line;
174
gchar *dict_string = NULL;
175
gint suggestions_found = 0;
177
g_return_if_fail(sc_speller_dict != NULL);
178
g_return_if_fail(doc != NULL);
180
ui_progress_bar_start(_("Checking"));
182
enchant_dict_describe(sc_speller_dict, dict_describe, &dict_string);
184
if (sci_has_selection(doc->editor->sci))
186
first_line = sci_get_line_from_position(
187
doc->editor->sci, sci_get_selection_start(doc->editor->sci));
188
last_line = sci_get_line_from_position(
189
doc->editor->sci, sci_get_selection_end(doc->editor->sci));
191
if (sc_info->use_msgwin)
192
msgwin_msg_add(COLOR_BLUE, -1, NULL,
193
_("Checking file \"%s\" (lines %d to %d using %s):"),
194
DOC_FILENAME(doc), first_line + 1, last_line + 1, dict_string);
195
g_message("Checking file \"%s\" (lines %d to %d using %s):",
196
DOC_FILENAME(doc), first_line + 1, last_line + 1, dict_string);
201
last_line = sci_get_line_count(doc->editor->sci);
202
if (sc_info->use_msgwin)
203
msgwin_msg_add(COLOR_BLUE, -1, NULL, _("Checking file \"%s\" (using %s):"),
204
DOC_FILENAME(doc), dict_string);
205
g_message("Checking file \"%s\" (using %s):", DOC_FILENAME(doc), dict_string);
209
for (i = first_line; i < last_line; i++)
211
line = sci_get_line(doc->editor->sci, i);
213
suggestions_found += sc_speller_process_line(doc, i, line);
215
/* process other GTK events to keep the GUI being responsive */
216
while (g_main_context_iteration(NULL, FALSE));
220
if (suggestions_found == 0 && sc_info->use_msgwin)
221
msgwin_msg_add(COLOR_BLUE, -1, NULL, _("The checked text is spelled correctly."));
223
ui_progress_bar_stop();
227
static void broker_init_failed(void)
229
const gchar *err = enchant_broker_get_error(sc_speller_broker);
230
dialogs_show_msgbox(GTK_MESSAGE_ERROR,
231
_("The Enchant library couldn't be initialized (%s)."),
232
(err != NULL) ? err : _("unknown error (maybe the chosen language is not available)"));
236
static void dict_compare(gpointer data, gpointer user_data)
238
gboolean *supported = user_data;
240
if (utils_str_equal(sc_info->default_language, data))
245
static gboolean check_default_lang(void)
247
gboolean supported = FALSE;
249
g_ptr_array_foreach(sc_info->dicts, dict_compare, &supported);
255
void sc_speller_reinit_enchant_dict(void)
257
gchar *lang = sc_info->default_language;
259
/* Release a previous dict object */
260
if (sc_speller_dict != NULL)
261
enchant_broker_free_dict(sc_speller_broker, sc_speller_dict);
263
/* Check if the stored default dictionary is (still) avaiable, fall back to the first
264
* one in the list if not */
265
if (! check_default_lang())
267
if (sc_info->dicts->len > 0)
269
lang = g_ptr_array_index(sc_info->dicts, 0);
270
g_warning("Stored language ('%s') could not be loaded. Falling back to '%s'",
271
sc_info->default_language, lang);
274
g_warning("Stored language ('%s') could not be loaded.", sc_info->default_language);
277
/* Request new dict object */
278
sc_speller_dict = enchant_broker_request_dict(sc_speller_broker, lang);
279
if (sc_speller_dict == NULL)
281
broker_init_failed();
282
gtk_widget_set_sensitive(sc_info->menu_item, FALSE);
286
gtk_widget_set_sensitive(sc_info->menu_item, TRUE);
291
gchar *sc_speller_get_default_lang(void)
293
const gchar *lang = g_getenv("LANG");
294
gchar *result = NULL;
298
if (*lang == 'C' || *lang == 'c')
301
{ /* if we have something like de_DE.UTF-8, strip everything from the period to the end */
302
gchar *period = strchr(lang, '.');
304
result = g_strndup(lang, g_utf8_pointer_to_offset(lang, period));
310
return (result != NULL) ? result : g_strdup(lang);
314
static void add_dict_array(const gchar* const lang_tag, const gchar* const provider_name,
315
const gchar* const provider_desc, const gchar* const provider_file,
319
gchar *result = g_strdup(lang_tag);
321
/* sometimes dictionaries are named lang-LOCALE instead of lang_LOCALE, so replace the
322
* hyphen by a dash, enchant seems to not care about it. */
323
for (i = 0; i < strlen(result); i++)
325
if (result[i] == '-')
329
/* find duplicates and skip them */
330
for (i = 0; i < sc_info->dicts->len; i++)
332
if (utils_str_equal(g_ptr_array_index(sc_info->dicts, i), result))
336
g_ptr_array_add(sc_info->dicts, result);
340
static gint sort_dicts(gconstpointer a, gconstpointer b)
341
{ /* casting mania ;-) */
342
return strcmp((gchar*)((GPtrArray*)a)->pdata, (gchar*)((GPtrArray*)b)->pdata);
346
static void create_dicts_array(void)
348
sc_info->dicts = g_ptr_array_new();
350
enchant_broker_list_dicts(sc_speller_broker, add_dict_array, sc_info->dicts);
352
g_ptr_array_sort(sc_info->dicts, sort_dicts);
356
void sc_speller_dict_free_string_list(gchar **tmp_suggs)
358
g_return_if_fail(sc_speller_dict != NULL);
360
enchant_dict_free_string_list(sc_speller_dict, tmp_suggs);
364
void sc_speller_add_word(const gchar *word)
366
g_return_if_fail(sc_speller_dict != NULL);
367
g_return_if_fail(word != NULL);
369
enchant_dict_add_to_pwl(sc_speller_dict, word, -1);
372
gboolean sc_speller_dict_check(const gchar *word)
374
g_return_val_if_fail(sc_speller_dict != NULL, FALSE);
375
g_return_val_if_fail(word != NULL, FALSE);
377
return enchant_dict_check(sc_speller_dict, word, -1);
381
gchar **sc_speller_dict_suggest(const gchar *word, gsize *n_suggs)
383
g_return_val_if_fail(sc_speller_dict != NULL, NULL);
384
g_return_val_if_fail(word != NULL, NULL);
386
return enchant_dict_suggest(sc_speller_dict, word, -1, n_suggs);
390
void sc_speller_add_word_to_session(const gchar *word)
392
g_return_if_fail(sc_speller_dict != NULL);
393
g_return_if_fail(word != NULL);
395
enchant_dict_add_to_session(sc_speller_dict, word, -1);
399
void sc_speller_store_replacement(const gchar *old_word, const gchar *new_word)
401
g_return_if_fail(sc_speller_dict != NULL);
402
g_return_if_fail(old_word != NULL);
403
g_return_if_fail(new_word != NULL);
405
enchant_dict_store_replacement(sc_speller_dict, old_word, -1, new_word, -1);
409
void sc_speller_init(void)
411
sc_speller_broker = enchant_broker_init();
413
create_dicts_array();
415
sc_speller_reinit_enchant_dict();
419
void sc_speller_free(void)
421
if (sc_speller_dict != NULL)
422
enchant_broker_free_dict(sc_speller_broker, sc_speller_dict);
423
enchant_broker_free(sc_speller_broker);
427
gboolean sc_speller_is_text(GeanyDocument *doc, gint pos)
431
g_return_val_if_fail(doc != NULL, FALSE);
432
g_return_val_if_fail(pos >= 0, FALSE);
434
style = sci_get_style_at(doc->editor->sci, pos);
435
/* early out for the default style */
436
if (style == STYLE_DEFAULT)
439
lexer = scintilla_send_message(doc->editor->sci, SCI_GETLEXER, 0, 0);
446
case SCE_ADA_DEFAULT:
447
case SCE_ADA_COMMENTLINE:
449
case SCE_ADA_STRINGEOL:
450
case SCE_ADA_CHARACTER:
451
case SCE_ADA_CHARACTEREOL:
462
case SCE_ASM_DEFAULT:
463
case SCE_ASM_COMMENT:
464
case SCE_ASM_COMMENTBLOCK:
466
case SCE_ASM_STRINGEOL:
467
case SCE_ASM_CHARACTER:
479
case SCE_SH_COMMENTLINE:
481
case SCE_SH_CHARACTER:
492
case SCE_CAML_DEFAULT:
493
case SCE_CAML_COMMENT:
494
case SCE_CAML_COMMENT1:
495
case SCE_CAML_COMMENT2:
496
case SCE_CAML_COMMENT3:
497
case SCE_CAML_STRING:
505
#ifdef SCE_PAS_DEFAULT
510
case SCE_PAS_DEFAULT:
511
case SCE_PAS_COMMENT:
512
case SCE_PAS_COMMENT2:
513
case SCE_PAS_COMMENTLINE:
515
case SCE_PAS_CHARACTER:
525
#ifndef SCE_PAS_DEFAULT
533
case SCE_C_COMMENTLINE:
534
case SCE_C_COMMENTDOC:
536
case SCE_C_CHARACTER:
537
case SCE_C_STRINGEOL:
538
case SCE_C_COMMENTLINEDOC:
549
case SCE_CMAKE_DEFAULT:
550
case SCE_CMAKE_COMMENT:
551
case SCE_CMAKE_STRINGDQ:
552
case SCE_CMAKE_STRINGLQ:
553
case SCE_CMAKE_STRINGRQ:
554
case SCE_CMAKE_STRINGVAR:
565
case SCE_CSS_DEFAULT:
566
case SCE_CSS_COMMENT:
579
case SCE_D_COMMENTLINE:
580
case SCE_D_COMMENTDOC:
581
case SCE_D_COMMENTNESTED:
583
case SCE_D_STRINGEOL:
584
case SCE_D_CHARACTER:
585
case SCE_D_COMMENTLINEDOC:
596
case SCE_DIFF_DEFAULT:
597
case SCE_DIFF_COMMENT:
598
case SCE_DIFF_HEADER:
614
case SCE_F_STRINGEOL:
621
case SCLEX_FREEBASIC:
628
case SCE_B_STRINGEOL:
641
case SCE_HA_COMMENTLINE:
642
case SCE_HA_COMMENTBLOCK:
643
case SCE_HA_COMMENTBLOCK2:
644
case SCE_HA_COMMENTBLOCK3:
646
case SCE_HA_CHARACTER:
660
case SCE_H_TAGUNKNOWN:
661
case SCE_H_ATTRIBUTEUNKNOWN:
662
case SCE_H_DOUBLESTRING:
663
case SCE_H_SINGLESTRING:
666
case SCE_H_VALUE: /* really? */
667
case SCE_H_SGML_DEFAULT:
668
case SCE_H_SGML_COMMENT:
669
case SCE_H_SGML_DOUBLESTRING:
670
case SCE_H_SGML_SIMPLESTRING:
671
case SCE_H_SGML_1ST_PARAM_COMMENT:
674
case SCE_HJ_COMMENTLINE:
675
case SCE_HJ_COMMENTDOC:
676
case SCE_HJ_DOUBLESTRING:
677
case SCE_HJ_SINGLESTRING:
678
case SCE_HJ_STRINGEOL:
680
case SCE_HB_COMMENTLINE:
682
case SCE_HB_STRINGEOL:
683
case SCE_HBA_DEFAULT:
684
case SCE_HBA_COMMENTLINE:
686
case SCE_HBA_STRINGEOL:
687
case SCE_HJA_DEFAULT:
688
case SCE_HJA_COMMENT:
689
case SCE_HJA_COMMENTLINE:
690
case SCE_HJA_COMMENTDOC:
691
case SCE_HJA_DOUBLESTRING:
692
case SCE_HJA_SINGLESTRING:
693
case SCE_HJA_STRINGEOL:
695
case SCE_HP_COMMENTLINE:
697
case SCE_HP_CHARACTER:
699
case SCE_HP_TRIPLEDOUBLE:
700
case SCE_HPA_DEFAULT:
701
case SCE_HPA_COMMENTLINE:
703
case SCE_HPA_CHARACTER:
705
case SCE_HPA_TRIPLEDOUBLE:
706
case SCE_HPHP_DEFAULT:
707
case SCE_HPHP_SIMPLESTRING:
708
case SCE_HPHP_HSTRING:
709
case SCE_HPHP_COMMENT:
710
case SCE_HPHP_COMMENTLINE:
733
case SCE_LUA_DEFAULT:
734
case SCE_LUA_COMMENT:
735
case SCE_LUA_COMMENTLINE:
736
case SCE_LUA_COMMENTDOC:
738
case SCE_LUA_CHARACTER:
739
case SCE_LUA_LITERALSTRING:
740
case SCE_LUA_STRINGEOL:
751
case SCE_MAKE_DEFAULT:
752
case SCE_MAKE_COMMENT:
763
case SCE_MATLAB_DEFAULT:
764
case SCE_MATLAB_COMMENT:
765
case SCE_MATLAB_STRING:
766
case SCE_MATLAB_DOUBLEQUOTESTRING:
777
case SCE_NSIS_DEFAULT:
778
case SCE_NSIS_COMMENT:
779
case SCE_NSIS_STRINGDQ:
780
case SCE_NSIS_STRINGLQ:
781
case SCE_NSIS_STRINGRQ:
782
case SCE_NSIS_STRINGVAR:
783
case SCE_NSIS_COMMENTBOX:
795
case SCE_PL_COMMENTLINE:
797
case SCE_PL_CHARACTER:
799
case SCE_PL_POD_VERB:
800
case SCE_PL_LONGQUOTE:
801
/* do we want SCE_PL_STRING_* too? */
814
case SCE_PO_MSGID_TEXT:
815
case SCE_PO_MSGSTR_TEXT:
816
case SCE_PO_MSGCTXT_TEXT:
823
case SCLEX_PROPERTIES:
827
case SCE_PROPS_DEFAULT:
828
case SCE_PROPS_COMMENT:
840
case SCE_P_COMMENTLINE:
842
case SCE_P_CHARACTER:
844
case SCE_P_TRIPLEDOUBLE:
845
case SCE_P_COMMENTBLOCK:
846
case SCE_P_STRINGEOL:
872
case SCE_RB_COMMENTLINE:
874
case SCE_RB_CHARACTER:
886
case SCE_SQL_DEFAULT:
887
case SCE_SQL_COMMENT:
888
case SCE_SQL_COMMENTLINE:
889
case SCE_SQL_COMMENTDOC:
891
case SCE_SQL_CHARACTER:
892
case SCE_SQL_SQLPLUS_COMMENT:
903
case SCE_TCL_DEFAULT:
904
case SCE_TCL_COMMENT:
905
case SCE_TCL_COMMENTLINE:
906
case SCE_TCL_IN_QUOTE:
917
case SCE_VHDL_DEFAULT:
918
case SCE_VHDL_COMMENT:
919
case SCE_VHDL_COMMENTLINEBANG:
920
case SCE_VHDL_STRING:
921
case SCE_VHDL_STRINGEOL:
932
case SCE_YAML_DEFAULT:
933
case SCE_YAML_COMMENT:
942
/* if the current lexer was not handled, let's say the passed position contains
943
* valid text to not ignore more than we want */