4 Copyright (C) 2012-2024
5 Free Software Foundation, Inc.
8 Ilia Maslakov <il.smind@gmail.com>, 2012
9 Andrew Borodin <aborodin@vmail.ru>, 2013-2024
12 This file is part of the Midnight Commander.
14 The Midnight Commander is free software: you can redistribute it
15 and/or modify it under the terms of the GNU General Public License as
16 published by the Free Software Foundation, either version 3 of the License,
17 or (at your option) any later version.
19 The Midnight Commander is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
24 You should have received a copy of the GNU General Public License
25 along with this program. If not, see <http://www.gnu.org/licenses/>.
36 #include "lib/charsets.h"
38 #include "lib/strutil.h"
39 #include "lib/util.h" /* MC_PTR_FREE() */
40 #include "lib/tty/tty.h" /* COLS, LINES */
42 #include "src/setup.h"
44 #include "editwidget.h"
48 /*** global variables ****************************************************************************/
50 /*** file scope macro definitions ****************************************************************/
52 #define B_SKIP_WORD (B_USER+3)
53 #define B_ADD_WORD (B_USER+4)
55 #define ASPELL_FUNCTION_AVAILABLE(f) \
56 g_module_symbol (spell_module, #f, (void *) &mc_##f)
58 /*** file scope type declarations ****************************************************************/
60 typedef struct aspell_struct
63 AspellSpeller
*speller
;
66 /*** forward declarations (file scope functions) *************************************************/
68 /*** file scope variables ************************************************************************/
70 static GModule
*spell_module
= NULL
;
71 static spell_t
*global_speller
= NULL
;
73 static AspellConfig
*(*mc_new_aspell_config
) (void);
74 static int (*mc_aspell_config_replace
) (AspellConfig
* ths
, const char *key
, const char *value
);
75 static AspellCanHaveError
*(*mc_new_aspell_speller
) (AspellConfig
* config
);
76 static unsigned int (*mc_aspell_error_number
) (const AspellCanHaveError
* ths
);
77 static const char *(*mc_aspell_speller_error_message
) (const AspellSpeller
* ths
);
78 static const AspellError
*(*mc_aspell_speller_error
) (const AspellSpeller
* ths
);
80 static AspellSpeller
*(*mc_to_aspell_speller
) (AspellCanHaveError
* obj
);
81 static int (*mc_aspell_speller_check
) (AspellSpeller
* ths
, const char *word
, int word_size
);
82 static const AspellWordList
*(*mc_aspell_speller_suggest
) (AspellSpeller
* ths
,
83 const char *word
, int word_size
);
84 static AspellStringEnumeration
*(*mc_aspell_word_list_elements
) (const struct AspellWordList
* ths
);
85 static const char *(*mc_aspell_config_retrieve
) (AspellConfig
* ths
, const char *key
);
86 static void (*mc_delete_aspell_speller
) (AspellSpeller
* ths
);
87 static void (*mc_delete_aspell_config
) (AspellConfig
* ths
);
88 static void (*mc_delete_aspell_can_have_error
) (AspellCanHaveError
* ths
);
89 static const char *(*mc_aspell_error_message
) (const AspellCanHaveError
* ths
);
90 static void (*mc_delete_aspell_string_enumeration
) (AspellStringEnumeration
* ths
);
91 static AspellDictInfoEnumeration
*(*mc_aspell_dict_info_list_elements
)
92 (const AspellDictInfoList
* ths
);
93 static AspellDictInfoList
*(*mc_get_aspell_dict_info_list
) (AspellConfig
* config
);
94 static const AspellDictInfo
*(*mc_aspell_dict_info_enumeration_next
)
95 (AspellDictInfoEnumeration
* ths
);
96 static const char *(*mc_aspell_string_enumeration_next
) (AspellStringEnumeration
* ths
);
97 static void (*mc_delete_aspell_dict_info_enumeration
) (AspellDictInfoEnumeration
* ths
);
98 static unsigned int (*mc_aspell_word_list_size
) (const AspellWordList
* ths
);
99 static const AspellError
*(*mc_aspell_error
) (const AspellCanHaveError
* ths
);
100 static int (*mc_aspell_speller_add_to_personal
) (AspellSpeller
* ths
, const char *word
,
102 static int (*mc_aspell_speller_save_all_word_lists
) (AspellSpeller
* ths
);
108 } spell_codes_map
[] = {
110 {"br", N_("Breton")},
113 {"da", N_("Danish")},
114 {"de", N_("German")},
116 {"en", N_("English")},
117 {"en_GB", N_("British English")},
118 {"en_CA", N_("Canadian English")},
119 {"en_US", N_("American English")},
120 {"eo", N_("Esperanto")},
121 {"es", N_("Spanish")},
122 {"fo", N_("Faroese")},
123 {"fr", N_("French")},
124 {"it", N_("Italian")},
126 {"no", N_("Norwegian")},
127 {"pl", N_("Polish")},
128 {"pt", N_("Portuguese")},
129 {"ro", N_("Romanian")},
130 {"ru", N_("Russian")},
131 {"sk", N_("Slovak")},
132 {"sv", N_("Swedish")},
133 {"uk", N_("Ukrainian")},
138 /* --------------------------------------------------------------------------------------------- */
139 /*** file scope functions ************************************************************************/
140 /* --------------------------------------------------------------------------------------------- */
142 * Found the language name by language code. For example: en_US -> American English.
144 * @param code Short name of the language (ru, en, pl, uk, etc...)
145 * @return language name
149 spell_decode_lang (const char *code
)
153 for (i
= 0; spell_codes_map
[i
].code
!= NULL
; i
++)
155 if (strcmp (spell_codes_map
[i
].code
, code
) == 0)
156 return _(spell_codes_map
[i
].name
);
162 /* --------------------------------------------------------------------------------------------- */
164 * Checks if aspell library and symbols are available.
166 * @return FALSE or error
170 spell_available (void)
172 if (spell_module
!= NULL
)
175 spell_module
= g_module_open ("libaspell", G_MODULE_BIND_LAZY
);
177 if (spell_module
== NULL
)
180 if (ASPELL_FUNCTION_AVAILABLE (new_aspell_config
)
181 && ASPELL_FUNCTION_AVAILABLE (aspell_dict_info_list_elements
)
182 && ASPELL_FUNCTION_AVAILABLE (aspell_dict_info_enumeration_next
)
183 && ASPELL_FUNCTION_AVAILABLE (new_aspell_speller
)
184 && ASPELL_FUNCTION_AVAILABLE (aspell_error_number
)
185 && ASPELL_FUNCTION_AVAILABLE (aspell_speller_error_message
)
186 && ASPELL_FUNCTION_AVAILABLE (aspell_speller_error
)
187 && ASPELL_FUNCTION_AVAILABLE (aspell_error
)
188 && ASPELL_FUNCTION_AVAILABLE (to_aspell_speller
)
189 && ASPELL_FUNCTION_AVAILABLE (aspell_speller_check
)
190 && ASPELL_FUNCTION_AVAILABLE (aspell_speller_suggest
)
191 && ASPELL_FUNCTION_AVAILABLE (aspell_word_list_elements
)
192 && ASPELL_FUNCTION_AVAILABLE (aspell_string_enumeration_next
)
193 && ASPELL_FUNCTION_AVAILABLE (aspell_config_replace
)
194 && ASPELL_FUNCTION_AVAILABLE (aspell_error_message
)
195 && ASPELL_FUNCTION_AVAILABLE (delete_aspell_speller
)
196 && ASPELL_FUNCTION_AVAILABLE (delete_aspell_config
)
197 && ASPELL_FUNCTION_AVAILABLE (delete_aspell_string_enumeration
)
198 && ASPELL_FUNCTION_AVAILABLE (get_aspell_dict_info_list
)
199 && ASPELL_FUNCTION_AVAILABLE (delete_aspell_can_have_error
)
200 && ASPELL_FUNCTION_AVAILABLE (delete_aspell_dict_info_enumeration
)
201 && ASPELL_FUNCTION_AVAILABLE (aspell_config_retrieve
)
202 && ASPELL_FUNCTION_AVAILABLE (aspell_word_list_size
)
203 && ASPELL_FUNCTION_AVAILABLE (aspell_speller_add_to_personal
)
204 && ASPELL_FUNCTION_AVAILABLE (aspell_speller_save_all_word_lists
))
207 g_module_close (spell_module
);
212 /* --------------------------------------------------------------------------------------------- */
214 * Get the current language name.
216 * @return language name
220 aspell_get_lang (void)
224 code
= mc_aspell_config_retrieve (global_speller
->config
, "lang");
225 return spell_decode_lang (code
);
228 /* --------------------------------------------------------------------------------------------- */
230 * Get array of available languages.
232 * @param lang_list Array of languages. Must be cleared before use
233 * @return language list length
237 aspell_get_lang_list (GPtrArray
*lang_list
)
239 AspellDictInfoList
*dlist
;
240 AspellDictInfoEnumeration
*elem
;
241 const AspellDictInfo
*entry
;
244 if (spell_module
== NULL
)
247 /* the returned pointer should _not_ need to be deleted */
248 dlist
= mc_get_aspell_dict_info_list (global_speller
->config
);
249 elem
= mc_aspell_dict_info_list_elements (dlist
);
251 while ((entry
= mc_aspell_dict_info_enumeration_next (elem
)) != NULL
)
252 if (entry
->name
!= NULL
)
254 g_ptr_array_add (lang_list
, g_strdup (entry
->name
));
258 mc_delete_aspell_dict_info_enumeration (elem
);
263 /* --------------------------------------------------------------------------------------------- */
267 * @param lang Language name
268 * @return FALSE or error
272 aspell_set_lang (const char *lang
)
276 AspellCanHaveError
*error
;
277 const char *spell_codeset
;
279 g_free (spell_language
);
280 spell_language
= g_strdup (lang
);
283 if (mc_global
.source_codepage
> 0)
284 spell_codeset
= get_codepage_id (mc_global
.source_codepage
);
287 spell_codeset
= str_detect_termencoding ();
289 mc_aspell_config_replace (global_speller
->config
, "lang", lang
);
290 mc_aspell_config_replace (global_speller
->config
, "encoding", spell_codeset
);
292 /* the returned pointer should _not_ need to be deleted */
293 if (global_speller
->speller
!= NULL
)
294 mc_delete_aspell_speller (global_speller
->speller
);
296 global_speller
->speller
= NULL
;
298 error
= mc_new_aspell_speller (global_speller
->config
);
299 if (mc_aspell_error (error
) != 0)
301 mc_delete_aspell_can_have_error (error
);
305 global_speller
->speller
= mc_to_aspell_speller (error
);
310 /* --------------------------------------------------------------------------------------------- */
312 * Show suggests for the current word.
314 * @param edit Editor object
315 * @param word Word for spell check
316 * @param new_word Word to replace the incorrect word
317 * @param suggest Array of suggests for current word
318 * @return code of pressed button
322 spell_dialog_spell_suggest_show (WEdit
*edit
, const char *word
, char **new_word
,
323 const GPtrArray
*suggest
)
326 int sug_dlg_h
= 14; /* dialog height */
327 int sug_dlg_w
= 29; /* dialog width */
342 WButton
*replace_btn
;
344 WButton
*cancel_button
;
347 /* calculate the dialog metrics */
348 xpos
= (COLS
- sug_dlg_w
) / 2;
349 ypos
= (LINES
- sug_dlg_h
) * 2 / 3;
351 /* Sometimes menu can hide replaced text. I don't like it */
352 if ((edit
->curs_row
>= ypos
- 1) && (edit
->curs_row
<= ypos
+ sug_dlg_h
- 1))
355 add_btn
= button_new (5, 28, B_ADD_WORD
, NORMAL_BUTTON
, _("&Add word"), 0);
356 replace_btn
= button_new (7, 28, B_ENTER
, NORMAL_BUTTON
, _("&Replace"), 0);
357 replace_len
= button_get_len (replace_btn
);
358 skip_btn
= button_new (9, 28, B_SKIP_WORD
, NORMAL_BUTTON
, _("&Skip"), 0);
359 skip_len
= button_get_len (skip_btn
);
360 cancel_button
= button_new (11, 28, B_CANCEL
, NORMAL_BUTTON
, _("&Cancel"), 0);
361 cancel_len
= button_get_len (cancel_button
);
363 max_btn_len
= MAX (replace_len
, skip_len
);
364 max_btn_len
= MAX (max_btn_len
, cancel_len
);
366 lang_label
= g_strdup_printf ("%s: %s", _("Language"), aspell_get_lang ());
367 word_label
= g_strdup_printf ("%s: %s", _("Misspelled"), word
);
368 word_label_len
= str_term_width1 (word_label
) + 5;
370 sug_dlg_w
+= max_btn_len
;
371 sug_dlg_w
= MAX (sug_dlg_w
, word_label_len
) + 1;
373 sug_dlg
= dlg_create (TRUE
, ypos
, xpos
, sug_dlg_h
, sug_dlg_w
, WPOS_KEEP_DEFAULT
, TRUE
,
374 dialog_colors
, NULL
, NULL
, "[ASpell]", _("Check word"));
377 group_add_widget (g
, label_new (1, 2, lang_label
));
378 group_add_widget (g
, label_new (3, 2, word_label
));
380 group_add_widget (g
, groupbox_new (4, 2, sug_dlg_h
- 5, 25, _("Suggest")));
382 sug_list
= listbox_new (5, 2, sug_dlg_h
- 7, 24, FALSE
, NULL
);
383 for (i
= 0; i
< suggest
->len
; i
++)
384 listbox_add_item (sug_list
, LISTBOX_APPEND_AT_END
, 0, g_ptr_array_index (suggest
, i
), NULL
,
386 group_add_widget (g
, sug_list
);
388 group_add_widget (g
, add_btn
);
389 group_add_widget (g
, replace_btn
);
390 group_add_widget (g
, skip_btn
);
391 group_add_widget (g
, cancel_button
);
393 res
= dlg_run (sug_dlg
);
397 listbox_get_current (sug_list
, &curr
, NULL
);
400 tmp
= g_strdup (curr
);
404 widget_destroy (WIDGET (sug_dlg
));
411 /* --------------------------------------------------------------------------------------------- */
413 * Add word to personal dictionary.
415 * @param word Word for spell check
416 * @param word_size Word size (in bytes)
417 * @return FALSE or error
420 aspell_add_to_dict (const char *word
, int word_size
)
422 mc_aspell_speller_add_to_personal (global_speller
->speller
, word
, word_size
);
424 if (mc_aspell_speller_error (global_speller
->speller
) != 0)
426 edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller
->speller
));
430 mc_aspell_speller_save_all_word_lists (global_speller
->speller
);
432 if (mc_aspell_speller_error (global_speller
->speller
) != 0)
434 edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller
->speller
));
441 /* --------------------------------------------------------------------------------------------- */
443 * Examine dictionaries and suggest possible words that may repalce the incorrect word.
445 * @param suggest array of words to iterate through
446 * @param word Word for spell check
447 * @param word_size Word size (in bytes)
448 * @return count of suggests for the word
452 aspell_suggest (GPtrArray
*suggest
, const char *word
, const int word_size
)
454 unsigned int size
= 0;
456 if (word
!= NULL
&& global_speller
!= NULL
&& global_speller
->speller
!= NULL
)
458 const AspellWordList
*wordlist
;
460 wordlist
= mc_aspell_speller_suggest (global_speller
->speller
, word
, word_size
);
461 if (wordlist
!= NULL
)
463 AspellStringEnumeration
*elements
= NULL
;
466 elements
= mc_aspell_word_list_elements (wordlist
);
467 size
= mc_aspell_word_list_size (wordlist
);
469 for (i
= 0; i
< size
; i
++)
471 const char *cur_sugg_word
;
473 cur_sugg_word
= mc_aspell_string_enumeration_next (elements
);
474 if (cur_sugg_word
!= NULL
)
475 g_ptr_array_add (suggest
, g_strdup (cur_sugg_word
));
478 mc_delete_aspell_string_enumeration (elements
);
485 /* --------------------------------------------------------------------------------------------- */
489 * @param word Word for spell check
490 * @param word_size Word size (in bytes)
491 * @return FALSE if word is not in the dictionary
495 aspell_check (const char *word
, const int word_size
)
499 if (word
!= NULL
&& global_speller
!= NULL
&& global_speller
->speller
!= NULL
)
500 res
= mc_aspell_speller_check (global_speller
->speller
, word
, word_size
);
505 /* --------------------------------------------------------------------------------------------- */
507 * Clear the array of languages.
509 * @param array Array of languages
513 aspell_array_clean (GPtrArray
*array
)
516 g_ptr_array_free (array
, TRUE
);
519 /* --------------------------------------------------------------------------------------------- */
520 /*** public functions ****************************************************************************/
521 /* --------------------------------------------------------------------------------------------- */
523 * Initialization of Aspell support.
529 AspellCanHaveError
*error
= NULL
;
531 if (strcmp (spell_language
, "NONE") == 0)
534 if (global_speller
!= NULL
)
537 global_speller
= g_try_malloc (sizeof (spell_t
));
538 if (global_speller
== NULL
)
541 if (!spell_available ())
543 MC_PTR_FREE (global_speller
);
547 global_speller
->config
= mc_new_aspell_config ();
548 global_speller
->speller
= NULL
;
550 if (spell_language
!= NULL
)
551 mc_aspell_config_replace (global_speller
->config
, "lang", spell_language
);
553 error
= mc_new_aspell_speller (global_speller
->config
);
555 if (mc_aspell_error_number (error
) == 0)
556 global_speller
->speller
= mc_to_aspell_speller (error
);
559 edit_error_dialog (_("Error"), mc_aspell_error_message (error
));
560 mc_delete_aspell_can_have_error (error
);
565 /* --------------------------------------------------------------------------------------------- */
567 * Deinitialization of Aspell support.
573 if (global_speller
== NULL
)
576 if (global_speller
->speller
!= NULL
)
577 mc_delete_aspell_speller (global_speller
->speller
);
579 if (global_speller
->config
!= NULL
)
580 mc_delete_aspell_config (global_speller
->config
);
582 MC_PTR_FREE (global_speller
);
584 g_module_close (spell_module
);
588 /* --------------------------------------------------------------------------------------------- */
591 edit_suggest_current_word (WEdit
*edit
)
595 off_t word_start
= 0;
596 int retval
= B_SKIP_WORD
;
599 /* search start of word to spell check */
600 match_word
= edit_buffer_get_word_from_pos (&edit
->buffer
, edit
->buffer
.curs1
, &word_start
,
602 word_len
= match_word
->len
;
605 if (mc_global
.source_codepage
>= 0 && mc_global
.source_codepage
!= mc_global
.display_codepage
)
609 tmp_word
= str_convert_to_display (match_word
->str
);
610 g_string_free (match_word
, TRUE
);
611 match_word
= tmp_word
;
614 if (match_word
!= NULL
)
616 if (!aspell_check (match_word
->str
, (int) word_len
))
622 suggest
= g_ptr_array_new_with_free_func (g_free
);
624 res
= aspell_suggest (suggest
, match_word
->str
, (int) word_len
);
627 char *new_word
= NULL
;
629 edit
->found_start
= word_start
;
630 edit
->found_len
= word_len
;
631 edit
->force
|= REDRAW_PAGE
;
632 edit_scroll_screen_over_cursor (edit
);
633 edit_render_keypress (edit
);
636 spell_dialog_spell_suggest_show (edit
, match_word
->str
, &new_word
, suggest
);
637 edit_cursor_move (edit
, word_len
- cut_len
);
639 if (retval
== B_ENTER
&& new_word
!= NULL
)
642 if (mc_global
.source_codepage
>= 0 &&
643 (mc_global
.source_codepage
!= mc_global
.display_codepage
))
647 tmp_word
= str_convert_to_input (new_word
);
648 MC_PTR_FREE (new_word
);
649 if (tmp_word
!= NULL
)
650 new_word
= g_string_free (tmp_word
, FALSE
);
653 for (i
= 0; i
< word_len
; i
++)
654 edit_backspace (edit
, TRUE
);
655 if (new_word
!= NULL
)
657 for (i
= 0; new_word
[i
] != '\0'; i
++)
658 edit_insert (edit
, new_word
[i
]);
662 else if (retval
== B_ADD_WORD
)
663 aspell_add_to_dict (match_word
->str
, (int) word_len
);
666 g_ptr_array_free (suggest
, TRUE
);
667 edit
->found_start
= 0;
671 g_string_free (match_word
, TRUE
);
677 /* --------------------------------------------------------------------------------------------- */
680 edit_spellcheck_file (WEdit
*edit
)
682 if (edit
->buffer
.curs_line
> 0)
684 edit_cursor_move (edit
, -edit
->buffer
.curs1
);
685 edit_move_to_prev_col (edit
, 0);
686 edit_update_curs_row (edit
);
693 c2
= edit_buffer_get_current_byte (&edit
->buffer
);
697 if (edit
->buffer
.curs1
>= edit
->buffer
.size
)
701 edit_cursor_move (edit
, 1);
702 c2
= edit_buffer_get_current_byte (&edit
->buffer
);
704 while (is_break_char (c1
) || is_break_char (c2
));
706 while (edit_suggest_current_word (edit
) != B_CANCEL
);
709 /* --------------------------------------------------------------------------------------------- */
712 edit_set_spell_lang (void)
714 GPtrArray
*lang_list
;
716 lang_list
= g_ptr_array_new_with_free_func (g_free
);
717 if (aspell_get_lang_list (lang_list
) != 0)
721 lang
= spell_dialog_lang_list_show (lang_list
);
723 (void) aspell_set_lang (lang
);
725 aspell_array_clean (lang_list
);
728 /* --------------------------------------------------------------------------------------------- */
730 * Show dialog to select language for spell check.
732 * @param languages Array of available languages
733 * @return name of chosen language
737 spell_dialog_lang_list_show (const GPtrArray
*languages
)
740 int lang_dlg_h
= 12; /* dialog height */
741 int lang_dlg_w
= 30; /* dialog width */
742 const char *selected_lang
= NULL
;
748 lang_list
= listbox_window_centered_new (-1, -1, lang_dlg_h
, lang_dlg_w
,
749 _("Select language"), "[ASpell]");
751 for (i
= 0; i
< languages
->len
; i
++)
752 LISTBOX_APPEND_TEXT (lang_list
, 0, g_ptr_array_index (languages
, i
), NULL
, FALSE
);
754 res
= listbox_run (lang_list
);
756 selected_lang
= g_ptr_array_index (languages
, (unsigned int) res
);
758 return selected_lang
;
761 /* --------------------------------------------------------------------------------------------- */