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 */
341 WButton
*replace_btn
;
343 WButton
*cancel_button
;
346 /* calculate the dialog metrics */
347 xpos
= (COLS
- sug_dlg_w
) / 2;
348 ypos
= (LINES
- sug_dlg_h
) * 2 / 3;
350 /* Sometimes menu can hide replaced text. I don't like it */
351 if ((edit
->curs_row
>= ypos
- 1) && (edit
->curs_row
<= ypos
+ sug_dlg_h
- 1))
354 add_btn
= button_new (5, 28, B_ADD_WORD
, NORMAL_BUTTON
, _("&Add word"), 0);
355 replace_btn
= button_new (7, 28, B_ENTER
, NORMAL_BUTTON
, _("&Replace"), 0);
356 replace_len
= button_get_len (replace_btn
);
357 skip_btn
= button_new (9, 28, B_SKIP_WORD
, NORMAL_BUTTON
, _("&Skip"), 0);
358 skip_len
= button_get_len (skip_btn
);
359 cancel_button
= button_new (11, 28, B_CANCEL
, NORMAL_BUTTON
, _("&Cancel"), 0);
360 cancel_len
= button_get_len (cancel_button
);
362 max_btn_len
= MAX (replace_len
, skip_len
);
363 max_btn_len
= MAX (max_btn_len
, cancel_len
);
365 lang_label
= g_strdup_printf ("%s: %s", _("Language"), aspell_get_lang ());
366 word_label
= g_strdup_printf ("%s: %s", _("Misspelled"), word
);
367 word_label_len
= str_term_width1 (word_label
) + 5;
369 sug_dlg_w
+= max_btn_len
;
370 sug_dlg_w
= MAX (sug_dlg_w
, word_label_len
) + 1;
372 sug_dlg
= dlg_create (TRUE
, ypos
, xpos
, sug_dlg_h
, sug_dlg_w
, WPOS_KEEP_DEFAULT
, TRUE
,
373 dialog_colors
, NULL
, NULL
, "[ASpell]", _("Check word"));
376 group_add_widget (g
, label_new (1, 2, lang_label
));
377 group_add_widget (g
, label_new (3, 2, word_label
));
379 group_add_widget (g
, groupbox_new (4, 2, sug_dlg_h
- 5, 25, _("Suggest")));
381 sug_list
= listbox_new (5, 2, sug_dlg_h
- 7, 24, FALSE
, NULL
);
382 for (i
= 0; i
< suggest
->len
; i
++)
383 listbox_add_item (sug_list
, LISTBOX_APPEND_AT_END
, 0, g_ptr_array_index (suggest
, i
), NULL
,
385 group_add_widget (g
, sug_list
);
387 group_add_widget (g
, add_btn
);
388 group_add_widget (g
, replace_btn
);
389 group_add_widget (g
, skip_btn
);
390 group_add_widget (g
, cancel_button
);
392 res
= dlg_run (sug_dlg
);
397 listbox_get_current (sug_list
, &curr
, NULL
);
398 *new_word
= g_strdup (curr
);
401 widget_destroy (WIDGET (sug_dlg
));
408 /* --------------------------------------------------------------------------------------------- */
410 * Add word to personal dictionary.
412 * @param word Word for spell check
413 * @param word_size Word size (in bytes)
414 * @return FALSE or error
417 aspell_add_to_dict (const char *word
, int word_size
)
419 mc_aspell_speller_add_to_personal (global_speller
->speller
, word
, word_size
);
421 if (mc_aspell_speller_error (global_speller
->speller
) != 0)
423 edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller
->speller
));
427 mc_aspell_speller_save_all_word_lists (global_speller
->speller
);
429 if (mc_aspell_speller_error (global_speller
->speller
) != 0)
431 edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller
->speller
));
438 /* --------------------------------------------------------------------------------------------- */
440 * Examine dictionaries and suggest possible words that may repalce the incorrect word.
442 * @param suggest array of words to iterate through
443 * @param word Word for spell check
444 * @param word_size Word size (in bytes)
445 * @return count of suggests for the word
449 aspell_suggest (GPtrArray
*suggest
, const char *word
, const int word_size
)
451 unsigned int size
= 0;
453 if (word
!= NULL
&& global_speller
!= NULL
&& global_speller
->speller
!= NULL
)
455 const AspellWordList
*wordlist
;
457 wordlist
= mc_aspell_speller_suggest (global_speller
->speller
, word
, word_size
);
458 if (wordlist
!= NULL
)
460 AspellStringEnumeration
*elements
= NULL
;
463 elements
= mc_aspell_word_list_elements (wordlist
);
464 size
= mc_aspell_word_list_size (wordlist
);
466 for (i
= 0; i
< size
; i
++)
468 const char *cur_sugg_word
;
470 cur_sugg_word
= mc_aspell_string_enumeration_next (elements
);
471 if (cur_sugg_word
!= NULL
)
472 g_ptr_array_add (suggest
, g_strdup (cur_sugg_word
));
475 mc_delete_aspell_string_enumeration (elements
);
482 /* --------------------------------------------------------------------------------------------- */
486 * @param word Word for spell check
487 * @param word_size Word size (in bytes)
488 * @return FALSE if word is not in the dictionary
492 aspell_check (const char *word
, const int word_size
)
496 if (word
!= NULL
&& global_speller
!= NULL
&& global_speller
->speller
!= NULL
)
497 res
= mc_aspell_speller_check (global_speller
->speller
, word
, word_size
);
502 /* --------------------------------------------------------------------------------------------- */
504 * Clear the array of languages.
506 * @param array Array of languages
510 aspell_array_clean (GPtrArray
*array
)
513 g_ptr_array_free (array
, TRUE
);
516 /* --------------------------------------------------------------------------------------------- */
517 /*** public functions ****************************************************************************/
518 /* --------------------------------------------------------------------------------------------- */
520 * Initialization of Aspell support.
526 AspellCanHaveError
*error
= NULL
;
528 if (strcmp (spell_language
, "NONE") == 0)
531 if (global_speller
!= NULL
)
534 global_speller
= g_try_malloc (sizeof (spell_t
));
535 if (global_speller
== NULL
)
538 if (!spell_available ())
540 MC_PTR_FREE (global_speller
);
544 global_speller
->config
= mc_new_aspell_config ();
545 global_speller
->speller
= NULL
;
547 if (spell_language
!= NULL
)
548 mc_aspell_config_replace (global_speller
->config
, "lang", spell_language
);
550 error
= mc_new_aspell_speller (global_speller
->config
);
552 if (mc_aspell_error_number (error
) == 0)
553 global_speller
->speller
= mc_to_aspell_speller (error
);
556 edit_error_dialog (_("Error"), mc_aspell_error_message (error
));
557 mc_delete_aspell_can_have_error (error
);
562 /* --------------------------------------------------------------------------------------------- */
564 * Deinitialization of Aspell support.
570 if (global_speller
== NULL
)
573 if (global_speller
->speller
!= NULL
)
574 mc_delete_aspell_speller (global_speller
->speller
);
576 if (global_speller
->config
!= NULL
)
577 mc_delete_aspell_config (global_speller
->config
);
579 MC_PTR_FREE (global_speller
);
581 g_module_close (spell_module
);
585 /* --------------------------------------------------------------------------------------------- */
588 edit_suggest_current_word (WEdit
*edit
)
592 off_t word_start
= 0;
593 int retval
= B_SKIP_WORD
;
596 /* search start of word to spell check */
597 match_word
= edit_buffer_get_word_from_pos (&edit
->buffer
, edit
->buffer
.curs1
, &word_start
,
599 word_len
= match_word
->len
;
602 if (mc_global
.source_codepage
>= 0 && mc_global
.source_codepage
!= mc_global
.display_codepage
)
606 tmp_word
= str_convert_to_display (match_word
->str
);
607 g_string_free (match_word
, TRUE
);
608 match_word
= tmp_word
;
611 if (match_word
!= NULL
)
613 if (!aspell_check (match_word
->str
, (int) word_len
))
619 suggest
= g_ptr_array_new_with_free_func (g_free
);
621 res
= aspell_suggest (suggest
, match_word
->str
, (int) word_len
);
624 char *new_word
= NULL
;
626 edit
->found_start
= word_start
;
627 edit
->found_len
= word_len
;
628 edit
->force
|= REDRAW_PAGE
;
629 edit_scroll_screen_over_cursor (edit
);
630 edit_render_keypress (edit
);
633 spell_dialog_spell_suggest_show (edit
, match_word
->str
, &new_word
, suggest
);
634 edit_cursor_move (edit
, word_len
- cut_len
);
636 if (retval
== B_ENTER
&& new_word
!= NULL
)
639 if (mc_global
.source_codepage
>= 0 &&
640 (mc_global
.source_codepage
!= mc_global
.display_codepage
))
644 tmp_word
= str_convert_to_input (new_word
);
645 MC_PTR_FREE (new_word
);
646 if (tmp_word
!= NULL
)
647 new_word
= g_string_free (tmp_word
, FALSE
);
650 for (i
= 0; i
< word_len
; i
++)
651 edit_backspace (edit
, TRUE
);
652 if (new_word
!= NULL
)
654 for (i
= 0; new_word
[i
] != '\0'; i
++)
655 edit_insert (edit
, new_word
[i
]);
659 else if (retval
== B_ADD_WORD
)
660 aspell_add_to_dict (match_word
->str
, (int) word_len
);
663 g_ptr_array_free (suggest
, TRUE
);
664 edit
->found_start
= 0;
668 g_string_free (match_word
, TRUE
);
674 /* --------------------------------------------------------------------------------------------- */
677 edit_spellcheck_file (WEdit
*edit
)
679 if (edit
->buffer
.curs_line
> 0)
681 edit_cursor_move (edit
, -edit
->buffer
.curs1
);
682 edit_move_to_prev_col (edit
, 0);
683 edit_update_curs_row (edit
);
690 c2
= edit_buffer_get_current_byte (&edit
->buffer
);
694 if (edit
->buffer
.curs1
>= edit
->buffer
.size
)
698 edit_cursor_move (edit
, 1);
699 c2
= edit_buffer_get_current_byte (&edit
->buffer
);
701 while (is_break_char (c1
) || is_break_char (c2
));
703 while (edit_suggest_current_word (edit
) != B_CANCEL
);
706 /* --------------------------------------------------------------------------------------------- */
709 edit_set_spell_lang (void)
711 GPtrArray
*lang_list
;
713 lang_list
= g_ptr_array_new_with_free_func (g_free
);
714 if (aspell_get_lang_list (lang_list
) != 0)
718 lang
= spell_dialog_lang_list_show (lang_list
);
720 (void) aspell_set_lang (lang
);
722 aspell_array_clean (lang_list
);
725 /* --------------------------------------------------------------------------------------------- */
727 * Show dialog to select language for spell check.
729 * @param languages Array of available languages
730 * @return name of chosen language
734 spell_dialog_lang_list_show (const GPtrArray
*languages
)
737 int lang_dlg_h
= 12; /* dialog height */
738 int lang_dlg_w
= 30; /* dialog width */
739 const char *selected_lang
= NULL
;
745 lang_list
= listbox_window_centered_new (-1, -1, lang_dlg_h
, lang_dlg_w
,
746 _("Select language"), "[ASpell]");
748 for (i
= 0; i
< languages
->len
; i
++)
749 LISTBOX_APPEND_TEXT (lang_list
, 0, g_ptr_array_index (languages
, i
), NULL
, FALSE
);
751 res
= listbox_run (lang_list
);
753 selected_lang
= g_ptr_array_index (languages
, (unsigned int) res
);
755 return selected_lang
;
758 /* --------------------------------------------------------------------------------------------- */