From 1d4ca9608a5ec786c1e439a3b99845ba28f4d38d Mon Sep 17 00:00:00 2001 From: Ilia Maslakov Date: Thu, 26 Apr 2012 14:05:48 +0400 Subject: [PATCH 1/2] Ticket #2788 (aspell support) Add aspell support for internal editor. The aspell library is dinamycally loaded. Signed-off-by: Ilia Maslakov --- acinclude.m4 | 2 + configure.ac | 28 +- doc/INSTALL | 5 + lib/keybind.c | 7 +- lib/keybind.h | 3 + m4.include/ac-glib.m4 | 51 ++-- m4.include/mc-with-edit.m4 | 46 +++ m4.include/mc-with-x.m4 | 22 ++ misc/mc.keymap.default | 3 + misc/mc.keymap.emacs | 3 + src/editor/Makefile.am | 11 +- src/editor/edit-impl.h | 11 + src/editor/edit.c | 194 +++++++----- src/editor/edit.h | 3 +- src/editor/editcmd.c | 146 ++++++++- src/editor/editcmd_dialogs.c | 4 + src/editor/editmenu.c | 10 +- src/editor/editwidget.c | 11 + src/editor/spell.c | 558 +++++++++++++++++++++++++++++++++++ src/editor/spell.h | 28 ++ src/editor/spell_dialogs.c | 182 ++++++++++++ src/editor/spell_dialogs.h | 25 ++ src/keybind-defaults.c | 3 + 23 files changed, 1224 insertions(+), 132 deletions(-) create mode 100644 m4.include/mc-with-edit.m4 create mode 100644 m4.include/mc-with-x.m4 create mode 100644 src/editor/spell.c create mode 100644 src/editor/spell.h create mode 100644 src/editor/spell_dialogs.c create mode 100644 src/editor/spell_dialogs.h diff --git a/acinclude.m4 b/acinclude.m4 index 762d95081..8fb93f215 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -8,8 +8,10 @@ m4_include([m4.include/fstypename.m4]) m4_include([m4.include/fsusage.m4]) m4_include([m4.include/mountlist.m4]) m4_include([m4.include/mc-get-fs-info.m4]) +m4_include([m4.include/mc-with-x.m4]) m4_include([m4.include/mc-use-termcap.m4]) m4_include([m4.include/mc-with-screen.m4]) +m4_include([m4.include/mc-with-edit.m4]) m4_include([m4.include/mc-background.m4]) m4_include([m4.include/ac-glib.m4]) m4_include([m4.include/mc-vfs.m4]) diff --git a/configure.ac b/configure.ac index 9fc6b7d74..0ffb31c35 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,7 @@ DX_INIT_DOXYGEN(mc,doxygen.cfg,devel) dnl PKG_CHECK_MODULES([CHECK], [check >= 0.9.4]) AC_CHECK_GLIB +AC_G_MODULE_SUPPORTED AC_ARG_ENABLE([mclib], [AS_HELP_STRING([--enable-mclib], [Compile shared library libmc.so @<:@no@:>@])], @@ -210,14 +211,9 @@ AC_FUNC_STRCOLL mc_AC_GET_FS_INFO -dnl dnl X11 support. dnl Used to read keyboard modifiers when running under X11. -AC_PATH_XTRA - -dnl -dnl Check if the gmodule functionality supported on this system. -AC_G_MODULE_SUPPORTED +MC_WITH_X dnl dnl Sequent wants getprocessstats @@ -360,24 +356,9 @@ fi AC_MSG_RESULT([$result]) subshell="$result" - MC_WITH_SCREEN - -dnl -dnl Internal editor support. -dnl -AC_ARG_WITH(edit, - [ --with-edit Enable internal editor [[yes]]]) - -if test x$with_edit != xno; then - AC_DEFINE(USE_INTERNAL_EDIT, 1, [Define to enable internal editor]) - use_edit=yes - edit_msg="yes" - AC_MSG_NOTICE([using internal editor]) -else - edit_msg="no" -fi +MC_WITH_EDIT dnl dnl Diff viewer support. @@ -495,7 +476,8 @@ AC_SUBST(MAN_DATE) AM_CONDITIONAL(USE_NLS, [test x"$USE_NLS" = xyes]) AM_CONDITIONAL(USE_MAINTAINER_MODE, [test x"$USE_MAINTAINER_MODE" = xyes]) AM_CONDITIONAL(USE_SCREEN_SLANG, [test x"$with_screen" = xslang]) -AM_CONDITIONAL(USE_EDIT, [test -n "$use_edit"]) +AM_CONDITIONAL(USE_EDIT, [test x"$use_edit" = xyes ]) +AM_CONDITIONAL(USE_ASPELL, [test x"$enable_aspell" = xyes ]) AM_CONDITIONAL(USE_DIFF, [test -n "$use_diff"]) AM_CONDITIONAL(CHARSET, [test -n "$have_charset"]) AM_CONDITIONAL(CONS_SAVER, [test -n "$cons_saver"]) diff --git a/doc/INSTALL b/doc/INSTALL index 6bd77bdf5..c330b777c 100644 --- a/doc/INSTALL +++ b/doc/INSTALL @@ -21,6 +21,7 @@ Build requirements for GNU Midnight Commander - gettext - cvs - libssh2 >= 1.2.5 is required only for sftp vfs (1.2.7 if you need ssh-agent support) +- libaspell to support spell checking in the internal editor Installation instructions for GNU Midnight Commander @@ -89,6 +90,10 @@ incomplete, use `configure --help' to get the full list): built-in file editor. The built-in editor is compiled in by default. +`--enable-aspell' + This option adds spell check support in the internal editor using + libaspell. Disabled by default. + `--without-gpm-mouse' Use this flag to disable gpm mouse support (e.g. if you want to use mouse only on X terminals). diff --git a/lib/keybind.c b/lib/keybind.c index df417f24b..79577690f 100644 --- a/lib/keybind.c +++ b/lib/keybind.c @@ -6,7 +6,7 @@ Written by: Vitja Makarov, 2005 - Ilia Maslakov , 2009 + Ilia Maslakov , 2009, 2012 Andrew Borodin , 2009, 2010, 2011, 2012 This file is part of the Midnight Commander. @@ -281,6 +281,11 @@ static name_keymap_t command_names[] = { {"MacroStartStopRecord", CK_MacroStartStopRecord}, {"MacroDelete", CK_MacroDelete}, {"RepeatStartStopRecord", CK_RepeatStartStopRecord}, +#ifdef HAVE_ASPELL + {"SpellCheck", CK_SpellCheck}, + {"SpellCheckCurrentWord", CK_SpellCheckCurrentWord}, + {"SpellCheckSelectLang", CK_SpellCheckSelectLang}, +#endif /* HAVE_ASPELL */ {"BookmarkFlush", CK_BookmarkFlush}, {"BookmarkNext", CK_BookmarkNext}, {"BookmarkPrev", CK_BookmarkPrev}, diff --git a/lib/keybind.h b/lib/keybind.h index dc86cbc4b..1258b44ce 100644 --- a/lib/keybind.h +++ b/lib/keybind.h @@ -288,6 +288,9 @@ enum CK_WindowNext, CK_WindowPrev, /* misc commands */ + CK_SpellCheck, + CK_SpellCheckCurrentWord, + CK_SpellCheckSelectLang, CK_InsertOverwrite, CK_ParagraphFormat, CK_MatchBracket, diff --git a/m4.include/ac-glib.m4 b/m4.include/ac-glib.m4 index 535314ad3..2277dfa06 100644 --- a/m4.include/ac-glib.m4 +++ b/m4.include/ac-glib.m4 @@ -6,41 +6,32 @@ dnl AC_DEFUN([AC_G_MODULE_SUPPORTED], [ g_module_supported="" - if test x"$no_x" = xyes; then - textmode_x11_support="no" + + found_gmodule=no + PKG_CHECK_MODULES(GMODULE, [gmodule-no-export-2.0 >= 2.8], [found_gmodule=yes], [:]) + if test x"$found_gmodule" = xyes; then + g_module_supported="gmodule-no-export-2.0" else - found_gmodule=no - PKG_CHECK_MODULES(GMODULE, [gmodule-no-export-2.0 >= 2.8], [found_gmodule=yes], [:]) + dnl try fallback to the generic gmodule + PKG_CHECK_MODULES(GMODULE, [gmodule-2.0 >= 2.8], [found_gmodule=yes], [:]) if test x"$found_gmodule" = xyes; then - g_module_supported="gmodule-no-export-2.0" - else - dnl try fallback to the generic gmodule - PKG_CHECK_MODULES(GMODULE, [gmodule-2.0 >= 2.8], [found_gmodule=yes], [:]) - if test x"$found_gmodule" = xyes; then - g_module_supported="gmodule-2.0" - fi + g_module_supported="gmodule-2.0" fi - - CPPFLAGS="$CPPFLAGS $X_CFLAGS" - case x"$g_module_supported" in - xgmodule-no-export-2.0|xgmodule-2.0) - if test x`$PKG_CONFIG --variable=gmodule_supported "$g_module_supported"` = xtrue; then - AC_DEFINE([HAVE_GMODULE], [1], [Defined if gmodule functionality is supported]) - else - g_module_supported="" - fi - ;; - *) - MCLIBS="$X_LIBS $X_PRE_LIBS -lX11 $X_EXTRA_LIBS" - g_module_supported="" - ;; - esac - - AC_DEFINE([HAVE_TEXTMODE_X11_SUPPORT], [1], - [Define to enable getting events from X Window System]) - textmode_x11_support="yes" fi + case x"$g_module_supported" in + xgmodule-no-export-2.0|xgmodule-2.0) + if test x`$PKG_CONFIG --variable=gmodule_supported "$g_module_supported"` = xtrue; then + AC_DEFINE([HAVE_GMODULE], [1], [Defined if gmodule functionality is supported]) + else + g_module_supported="" + fi + ;; + *) + g_module_supported="" + ;; + esac + AM_CONDITIONAL([HAVE_GMODULE], [test x"$g_module_supported" != x]) dnl diff --git a/m4.include/mc-with-edit.m4 b/m4.include/mc-with-edit.m4 new file mode 100644 index 000000000..cbfd95988 --- /dev/null +++ b/m4.include/mc-with-edit.m4 @@ -0,0 +1,46 @@ +dnl +dnl Internal editor support. +dnl +AC_DEFUN([MC_WITH_EDIT], [ + + AC_ARG_WITH([edit], AS_HELP_STRING([--with-edit], [Enable internal editor @<:@yes@:>@])) + + if test x$with_edit != xno; then + AC_DEFINE(USE_INTERNAL_EDIT, 1, [Define to enable internal editor]) + use_edit=yes + AC_MSG_NOTICE([using internal editor]) + else + use_edit=no + edit_msg="no" + fi + + dnl ASpell support. + AC_ARG_ENABLE([aspell], + AS_HELP_STRING([--enable-aspell], [Enable aspell support for internal editor @<:@no@:>@]), + [ + if test "x$enableval" = xno; then + enable_aspell=no + else + enable_aspell=yes + fi + ], + [enable_aspell=no] + ) + + if test x$with_edit != xno -a x$enable_aspell != xno; then + AC_CHECK_HEADERS([aspell.h], [], [ + AC_ERROR([Could not find aspell development headers]) + ], []) + + if test x"$g_module_supported" != x; then + AC_DEFINE(HAVE_ASPELL, 1, [Define to enable aspell support]) + edit_msg="yes with aspell support" + AC_MSG_NOTICE([using aspell for internal editor]) + else + enable_aspell=no + AC_MSG_NOTICE([aspell support is disabled because gmodule support is not available]) + fi + else + edit_msg="yes" + fi +]) diff --git a/m4.include/mc-with-x.m4 b/m4.include/mc-with-x.m4 new file mode 100644 index 000000000..4eaab915a --- /dev/null +++ b/m4.include/mc-with-x.m4 @@ -0,0 +1,22 @@ + +dnl X11 support. +dnl Used to read keyboard modifiers when running under X11. +AC_DEFUN([MC_WITH_X], [ + + AC_PATH_XTRA + + if test x"$no_x" = xyes; then + textmode_x11_support="no" + else + AC_DEFINE([HAVE_TEXTMODE_X11_SUPPORT], [1], + [Define to enable getting events from X Window System]) + textmode_x11_support="yes" + + CPPFLAGS="$CPPFLAGS $X_CFLAGS" + + if test x"$g_module_supported" = x; then + MCLIBS="$MCLIBS $X_LIBS $X_PRE_LIBS -lX11 $X_EXTRA_LIBS" + fi + fi + +]) diff --git a/misc/mc.keymap.default b/misc/mc.keymap.default index 5bacb7f6b..61fae2b97 100644 --- a/misc/mc.keymap.default +++ b/misc/mc.keymap.default @@ -337,6 +337,9 @@ RepeatStartStopRecord = SelectCodepage = alt-e Options = OptionsSaveMode = +SpellCheck = +SpellCheckCurrentWord = ctrl-p +SpellCheckSelectLang = LearnKeys = WindowMove = WindowResize = diff --git a/misc/mc.keymap.emacs b/misc/mc.keymap.emacs index 5a6469150..4d1372cdb 100644 --- a/misc/mc.keymap.emacs +++ b/misc/mc.keymap.emacs @@ -337,6 +337,9 @@ RepeatStartStopRecord = SelectCodepage = alt-e Options = OptionsSaveMode = +SpellCheck = +SpellCheckCurrentWord = +SpellCheckSelectLang = LearnKeys = WindowMove = WindowResize = diff --git a/src/editor/Makefile.am b/src/editor/Makefile.am index 16685557e..b66e76e22 100644 --- a/src/editor/Makefile.am +++ b/src/editor/Makefile.am @@ -3,7 +3,7 @@ EXTRA_DIST = if USE_EDIT noinst_LTLIBRARIES = libedit.la else -noinst_LTLIBRARIES = +noinst_LTLIBRARIES = endif libedit_la_SOURCES = \ @@ -13,5 +13,12 @@ libedit_la_SOURCES = \ syntax.c wordproc.c \ choosesyntax.c etags.c etags.h editcmd_dialogs.c editcmd_dialogs.h -libedit_la_CFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) $(PCRE_CFLAGS) +if USE_ASPELL +if HAVE_GMODULE +libedit_la_SOURCES += \ + spell.c spell.h \ + spell_dialogs.c spell_dialogs.h +endif +endif +libedit_la_CFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) $(PCRE_CFLAGS) diff --git a/src/editor/edit-impl.h b/src/editor/edit-impl.h index 13680c929..22cdf3f36 100644 --- a/src/editor/edit-impl.h +++ b/src/editor/edit-impl.h @@ -212,6 +212,7 @@ int edit_block_delete_cmd (WEdit * edit); void edit_delete_line (WEdit * edit); int edit_delete (WEdit * edit, const int byte_delete); +int edit_backspace (WEdit * edit, const int byte_delete); void edit_insert (WEdit * edit, int c); void edit_cursor_move (WEdit * edit, long increment); void edit_push_undo_action (WEdit * edit, long c, ...); @@ -242,12 +243,21 @@ mc_search_cbret_t edit_search_cmd_callback (const void *user_data, gsize char_of int *current_char); void edit_complete_word_cmd (WEdit * edit); void edit_get_match_keyword_cmd (WEdit * edit); + +#ifdef HAVE_ASPELL +int edit_suggest_current_word (WEdit * edit); +void edit_spellcheck_file (WEdit * edit); +void edit_set_spell_lang (void); +#endif + int edit_save_block (WEdit * edit, const char *filename, long start, long finish); int edit_save_block_cmd (WEdit * edit); gboolean edit_insert_file_cmd (WEdit * edit); void edit_insert_over (WEdit * edit); int edit_insert_column_of_text_from_file (WEdit * edit, int file, long *start_pos, long *end_pos, int *col1, int *col2); + +char *edit_get_word_from_pos (WEdit * edit, long start_pos, long *start, gsize *len, gsize *cut); long edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath); gboolean edit_load_back_cmd (WEdit * edit); gboolean edit_load_forward_cmd (WEdit * edit); @@ -307,6 +317,7 @@ void book_mark_serialize (WEdit * edit, int color); void book_mark_restore (WEdit * edit, int color); int line_is_blank (WEdit * edit, long line); +gboolean is_break_char (char c); int edit_indent_width (WEdit * edit, long p); void edit_insert_indent (WEdit * edit, int indent); void edit_options_dialog (Dlg_head * h); diff --git a/src/editor/edit.c b/src/editor/edit.c index 961fdd274..4d50f6cf7 100644 --- a/src/editor/edit.c +++ b/src/editor/edit.c @@ -70,6 +70,9 @@ #include "edit-impl.h" #include "editwidget.h" +#ifdef HAVE_ASPELL +#include "spell.h" +#endif /*** global variables ****************************************************************************/ @@ -717,72 +720,6 @@ edit_get_prev_utf (WEdit * edit, long byte_index, int *char_width) } } -/* --------------------------------------------------------------------------------------------- */ - -static int -edit_backspace (WEdit * edit, const int byte_delete) -{ - int p = 0; - int cw = 1; - int i; - - if (!edit->curs1) - return 0; - - cw = 1; - - if (edit->mark2 != edit->mark1) - edit_push_markers (edit); - - if (edit->utf8 && byte_delete == 0) - { - edit_get_prev_utf (edit, edit->curs1, &cw); - if (cw < 1) - cw = 1; - } - for (i = 1; i <= cw; i++) - { - if (edit->mark1 >= edit->curs1) - { - edit->mark1--; - edit->end_mark_curs--; - } - if (edit->mark2 >= edit->curs1) - edit->mark2--; - if (edit->last_get_rule >= edit->curs1) - edit->last_get_rule--; - - p = *(edit->buffers1[(edit->curs1 - 1) >> S_EDIT_BUF_SIZE] + - ((edit->curs1 - 1) & M_EDIT_BUF_SIZE)); - if (!((edit->curs1 - 1) & M_EDIT_BUF_SIZE)) - { - g_free (edit->buffers1[edit->curs1 >> S_EDIT_BUF_SIZE]); - edit->buffers1[edit->curs1 >> S_EDIT_BUF_SIZE] = NULL; - } - edit->last_byte--; - edit->curs1--; - edit_push_undo_action (edit, p); - } - edit_modification (edit); - if (p == '\n') - { - if (edit->book_mark) - book_mark_dec (edit, edit->curs_line); - edit->curs_line--; - edit->total_lines--; - edit->force |= REDRAW_AFTER_CURSOR; - } - - if (edit->curs1 < edit->start_display) - { - edit->start_display--; - if (p == '\n') - edit->start_line--; - } - - return p; -} - /* --------------------------------------------------------------------------------------------- */ /* high level cursor movement commands */ /* --------------------------------------------------------------------------------------------- */ @@ -2053,8 +1990,55 @@ edit_write_stream (WEdit * edit, FILE * f) return edit->last_byte; } +/* --------------------------------------------------------------------------------------------- */ + +gboolean +is_break_char (char c) +{ + return (isspace (c) || strchr ("{}[]()<>=|/\\!?~-+`'\",.;:#$%^&*", c)); +} + +/* --------------------------------------------------------------------------------------------- */ + +char * +edit_get_word_from_pos (WEdit *edit, long start_pos, long *start, gsize *len, gsize *cut) +{ + long word_start; + long cut_len = 0; + GString *match_expr; + unsigned char *bufpos; + int c1, c2; + + for (word_start = start_pos; word_start != 0; word_start--, cut_len++) + { + c1 = edit_get_byte (edit, word_start); + c2 = edit_get_byte (edit, word_start - 1); + + if (is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n') + break; + } + + bufpos = &edit->buffers1[word_start >> S_EDIT_BUF_SIZE][word_start & M_EDIT_BUF_SIZE]; + match_expr = g_string_sized_new (16); + + do + { + c1 = edit_get_byte (edit, word_start + match_expr->len); + c2 = edit_get_byte (edit, word_start + match_expr->len + 1); + g_string_append_c (match_expr, c1); + } + while (!(is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n')); + + *len = match_expr->len; + *start = word_start; + *cut = cut_len; + + return g_string_free (match_expr, FALSE); +} + /* --------------------------------------------------------------------------------------------- */ /** inserts a file at the cursor, returns count of inserted bytes on success */ + long edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath) { @@ -2274,6 +2258,7 @@ edit_init (WEdit * edit, int y, int x, int lines, int cols, const vfs_path_t * f } edit_load_macro_cmd (edit); + return edit; } @@ -2783,6 +2768,72 @@ edit_delete (WEdit * edit, const int byte_delete) return p; } +/* --------------------------------------------------------------------------------------------- */ + +int +edit_backspace (WEdit * edit, const int byte_delete) +{ + int p = 0; + int cw = 1; + int i; + + if (edit->curs1 == 0) + return 0; + + cw = 1; + + if (edit->mark2 != edit->mark1) + edit_push_markers (edit); + + if (edit->utf8 && byte_delete == 0) + { + edit_get_prev_utf (edit, edit->curs1, &cw); + if (cw < 1) + cw = 1; + } + for (i = 1; i <= cw; i++) + { + if (edit->mark1 >= edit->curs1) + { + edit->mark1--; + edit->end_mark_curs--; + } + if (edit->mark2 >= edit->curs1) + edit->mark2--; + if (edit->last_get_rule >= edit->curs1) + edit->last_get_rule--; + + p = *(edit->buffers1[(edit->curs1 - 1) >> S_EDIT_BUF_SIZE] + + ((edit->curs1 - 1) & M_EDIT_BUF_SIZE)); + if (!((edit->curs1 - 1) & M_EDIT_BUF_SIZE)) + { + g_free (edit->buffers1[edit->curs1 >> S_EDIT_BUF_SIZE]); + edit->buffers1[edit->curs1 >> S_EDIT_BUF_SIZE] = NULL; + } + edit->last_byte--; + edit->curs1--; + edit_push_undo_action (edit, p); + } + edit_modification (edit); + if (p == '\n') + { + if (edit->book_mark) + book_mark_dec (edit, edit->curs_line); + edit->curs_line--; + edit->total_lines--; + edit->force |= REDRAW_AFTER_CURSOR; + } + + if (edit->curs1 < edit->start_display) + { + edit->start_display--; + if (p == '\n') + edit->start_line--; + } + + return p; +} + /* --------------------------------------------------------------------------------------------- */ /** moves the cursor right or left: increment positive or negative respectively */ @@ -4075,6 +4126,17 @@ edit_execute_cmd (WEdit * edit, unsigned long command, int char_for_insertion) case CK_Find: edit_get_match_keyword_cmd (edit); break; +#ifdef HAVE_ASPELL + case CK_SpellCheckCurrentWord: + edit_suggest_current_word (edit); + break; + case CK_SpellCheck: + edit_spellcheck_file (edit); + break; + case CK_SpellCheckSelectLang: + edit_set_spell_lang (); + break; +#endif case CK_Date: { char s[BUF_MEDIUM]; diff --git a/src/editor/edit.h b/src/editor/edit.h index f6928f985..e70adcf89 100644 --- a/src/editor/edit.h +++ b/src/editor/edit.h @@ -7,7 +7,7 @@ * \author Paul Sheer * \date 1996, 1997 * \author Andrew Borodin - * \date 2009 + * \date 2009, 2012 */ #ifndef MC__EDIT_H @@ -15,6 +15,7 @@ #include "lib/global.h" /* PATH_SEP_STR */ #include "lib/fileloc.h" +#include "lib/vfs/vfs.h" /* vfs_path_t */ /*** typedefs(not structures) and defined constants **********************************************/ diff --git a/src/editor/editcmd.c b/src/editor/editcmd.c index 418d8ebd3..dd98fc690 100644 --- a/src/editor/editcmd.c +++ b/src/editor/editcmd.c @@ -8,6 +8,7 @@ Written by: Paul Sheer, 1996, 1997 Andrew Borodin 2012 + Ilia Maslakov 2012 This file is part of the Midnight Commander. @@ -78,6 +79,10 @@ #include "edit-impl.h" #include "editwidget.h" #include "editcmd_dialogs.h" +#ifdef HAVE_ASPELL +#include "spell.h" +#include "spell_dialogs.h" +#endif #include "etags.h" /*** global variables ****************************************************************************/ @@ -1082,14 +1087,6 @@ pipe_mail (WEdit * edit, char *to, char *subject, char *cc) } } -/* --------------------------------------------------------------------------------------------- */ - -static gboolean -is_break_char (char c) -{ - return (isspace (c) || strchr ("{}[]()<>=|/\\!?~-+`'\",.;:#$%^&*", c)); -} - /* --------------------------------------------------------------------------------------------- */ /** find first character of current word */ @@ -3575,3 +3572,136 @@ edit_get_match_keyword_cmd (WEdit * edit) } /* --------------------------------------------------------------------------------------------- */ + +#ifdef HAVE_ASPELL +int +edit_suggest_current_word (WEdit * edit) +{ + gsize cut_len = 0; + gsize word_len = 0; + long word_start = 0; + int retval = B_SKIP_WORD; + char *match_word; + + /* search start of word to spell check */ + match_word = edit_get_word_from_pos (edit, edit->curs1, &word_start, &word_len, &cut_len); + +#ifdef HAVE_CHARSET + if (mc_global.source_codepage >= 0 && (mc_global.source_codepage != mc_global.display_codepage)) + { + GString *tmp_word; + + tmp_word = str_convert_to_display (match_word); + g_free (match_word); + match_word = g_string_free (tmp_word, FALSE); + } +#endif + if (!aspell_check (match_word, (int) word_len)) + { + GArray *suggest; + unsigned int res; + + suggest = g_array_new (TRUE, FALSE, sizeof (char *)); + + res = aspell_suggest (suggest, match_word, (int) word_len); + if (res != 0) + { + char *new_word = NULL; + + edit->found_start = word_start; + edit->found_len = word_len; + edit->force |= REDRAW_PAGE; + edit_scroll_screen_over_cursor (edit); + edit_render_keypress (edit); + + retval = spell_dialog_spell_suggest_show (edit, match_word, &new_word, suggest); + edit_cursor_move (edit, word_len - cut_len); + + if (retval == B_ENTER && new_word != NULL) + { + guint i; + char *cp_word; + +#ifdef HAVE_CHARSET + if (mc_global.source_codepage >= 0 && + (mc_global.source_codepage != mc_global.display_codepage)) + { + GString *tmp_word; + + tmp_word = str_convert_to_input (new_word); + g_free (new_word); + new_word = g_string_free (tmp_word, FALSE); + } +#endif + cp_word = new_word; + for (i = 0; i < word_len; i++) + edit_backspace (edit, 1); + for (; *new_word; new_word++) + edit_insert (edit, *new_word); + g_free (cp_word); + } + else if (retval == B_ADD_WORD && match_word != NULL) + aspell_add_to_dict (match_word, (int) word_len); + } + + g_array_free (suggest, TRUE); + edit->found_start = 0; + edit->found_len = 0; + } + g_free (match_word); + return retval; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +edit_spellcheck_file (WEdit * edit) +{ + if (edit->curs_line > 0) + { + edit_cursor_move (edit, -edit->curs1); + edit_move_to_prev_col (edit, 0); + edit_update_curs_row (edit); + } + + do + { + int c1, c2; + + c2 = edit_get_byte (edit, edit->curs1); + + do + { + if (edit->curs1 >= edit->last_byte) + return; + + c1 = c2; + edit_cursor_move (edit, 1); + c2 = edit_get_byte (edit, edit->curs1); + } + while (is_break_char (c1) || is_break_char (c2)); + } + while (edit_suggest_current_word (edit) != B_CANCEL); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +edit_set_spell_lang (void) +{ + GArray *lang_list; + + lang_list = g_array_new (TRUE, FALSE, sizeof (char *)); + if (aspell_get_lang_list (lang_list) != 0) + { + char *lang; + + lang = spell_dialog_lang_list_show (lang_list); + if (lang != NULL) + (void) aspell_set_lang (lang); + } + aspell_array_clean (lang_list); +} +#endif /* HAVE_ASPELL */ + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/editor/editcmd_dialogs.c b/src/editor/editcmd_dialogs.c index 1642dacc6..de2fe0453 100644 --- a/src/editor/editcmd_dialogs.c +++ b/src/editor/editcmd_dialogs.c @@ -43,6 +43,10 @@ #include "src/editor/etags.h" #include "src/editor/editcmd_dialogs.h" +#ifdef HAVE_ASPELL +#include "src/editor/spell.h" +#endif + /*** global variables ****************************************************************************/ edit_search_options_t edit_search_options = { diff --git a/src/editor/editmenu.c b/src/editor/editmenu.c index 285ae818f..bdb9fb3bd 100644 --- a/src/editor/editmenu.c +++ b/src/editor/editmenu.c @@ -175,10 +175,18 @@ create_command_menu (void) g_list_prepend (entries, menu_entry_create (_("Record/Repeat &actions"), CK_RepeatStartStopRecord)); entries = g_list_prepend (entries, menu_separator_create ()); +#ifdef HAVE_ASPELL entries = - g_list_prepend (entries, menu_entry_create (_("'ispell' s&pell check"), CK_PipeBlock (1))); + g_list_prepend (entries, menu_entry_create (_("S&pell check"), CK_SpellCheck)); + entries = + g_list_prepend (entries, menu_entry_create (_("C&heck word"), CK_SpellCheckCurrentWord)); + entries = + g_list_prepend (entries, menu_entry_create (_("Change spelling &language"), CK_SpellCheckSelectLang)); + entries = g_list_prepend (entries, menu_separator_create ()); +#endif /* HAVE_ASPELL */ entries = g_list_prepend (entries, menu_entry_create (_("&Mail..."), CK_Mail)); + return g_list_reverse (entries); } diff --git a/src/editor/editwidget.c b/src/editor/editwidget.c index 8f00d318b..d1d6c8816 100644 --- a/src/editor/editwidget.c +++ b/src/editor/editwidget.c @@ -63,6 +63,9 @@ #include "edit-impl.h" #include "editwidget.h" +#ifdef HAVE_ASPELL +#include "spell.h" +#endif /*** global variables ****************************************************************************/ @@ -97,6 +100,10 @@ edit_dlg_init (void) { edit_window_state_char = mc_skin_get ("editor", "window-state-char", "*"); edit_window_close_char = mc_skin_get ("editor", "window-close-char", "X"); + +#ifdef HAVE_ASPELL + aspell_init (); +#endif } edit_dlg_init_refcounter++; @@ -117,6 +124,10 @@ edit_dlg_deinit (void) { g_free (edit_window_state_char); g_free (edit_window_close_char); + +#ifdef HAVE_ASPELL + aspell_clean (); +#endif } } diff --git a/src/editor/spell.c b/src/editor/spell.c new file mode 100644 index 000000000..638bc485d --- /dev/null +++ b/src/editor/spell.c @@ -0,0 +1,558 @@ +/* + Editor spell checker + + Copyright (C) 2012 + The Free Software Foundation, Inc. + + Written by: + Ilia Maslakov , 2012 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include + +#include +#include +#include +#include + +#ifdef HAVE_CHARSET +#include "lib/charsets.h" +#endif +#include "lib/strutil.h" + +#include "edit-impl.h" +#include "spell.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +typedef struct aspell_struct +{ + AspellConfig *config; + AspellSpeller *speller; +} spell_t; + +/*** file scope variables ************************************************************************/ + +static GModule *spell_module = NULL; +static spell_t *global_speller = NULL; + +static struct AspellConfig *(*mc_new_aspell_config) (void); +static int (*mc_aspell_config_replace) (struct AspellConfig * ths, const char *key, + const char *value); +static struct AspellCanHaveError *(*mc_new_aspell_speller) (struct AspellConfig * config); +static unsigned int (*mc_aspell_error_number) (const struct AspellCanHaveError * ths); +static const char *(*mc_aspell_speller_error_message) (const struct AspellSpeller * ths); +const struct AspellError *(*mc_aspell_speller_error) (const struct AspellSpeller * ths); + +static struct AspellSpeller *(*mc_to_aspell_speller) (struct AspellCanHaveError * obj); +static int (*mc_aspell_speller_check) (struct AspellSpeller * ths, const char *word, int word_size); +static const struct AspellWordList *(*mc_aspell_speller_suggest) (struct AspellSpeller * ths, + const char *word, int word_size); +static struct AspellStringEnumeration *(*mc_aspell_word_list_elements) (const struct AspellWordList + * ths); +static const char *(*mc_aspell_config_retrieve) (struct AspellConfig * ths, const char *key); +static void (*mc_delete_aspell_speller) (struct AspellSpeller * ths); +/* +static void (*mc_delete_aspell_config) (struct AspellConfig * ths); +*/ +static void (*mc_delete_aspell_can_have_error) (struct AspellCanHaveError * ths); +static const char *(*mc_aspell_error_message) (const struct AspellCanHaveError * ths); +static void (*mc_delete_aspell_string_enumeration) (struct AspellStringEnumeration * ths); +static struct AspellDictInfoEnumeration *(*mc_aspell_dict_info_list_elements) + (const struct AspellDictInfoList * ths); +static struct AspellDictInfoList *(*mc_get_aspell_dict_info_list) (struct AspellConfig * config); +static const struct AspellDictInfo *(*mc_aspell_dict_info_enumeration_next) + (struct AspellDictInfoEnumeration * ths); +static const char *(*mc_aspell_string_enumeration_next) (struct AspellStringEnumeration * ths); +static void (*mc_delete_aspell_dict_info_enumeration) (struct AspellDictInfoEnumeration * ths); +static unsigned int (*mc_aspell_word_list_size) (const struct AspellWordList * ths); +static const struct AspellError *(*mc_aspell_error) (const struct AspellCanHaveError * ths); +static int (*mc_aspell_speller_add_to_personal) (struct AspellSpeller * ths, const char * word, + int word_size); +static int (*mc_aspell_speller_save_all_word_lists) (struct AspellSpeller * ths); + +static struct +{ + const char *code; + const char *name; +} spell_codes_map[] = +{ + /* *INDENT-OFF* */ + {"br", "Breton"}, + {"cs", "Czech"}, + {"cy", "Welsh"}, + {"da", "Danish"}, + {"de", "German"}, + {"el", "Greek"}, + {"en", "English"}, + {"en_GB", "British English"}, + {"en_CA", "Canadian English"}, + {"en_US", "American English"}, + {"eo", "Esperanto"}, + {"es", "Spanish"}, + {"fo", "Faroese"}, + {"fr", "French"}, + {"it", "Italian"}, + {"nl", "Dutch"}, + {"no", "Norwegian"}, + {"pl", "Polish"}, + {"pt", "Portuguese"}, + {"ro", "Romanian"}, + {"ru", "Russian"}, + {"sk", "Slovak"}, + {"sv", "Swedish"}, + {"uk", "Ukrainian"}, + {NULL, NULL} + /* *INDENT-ON* */ +}; + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Found the language name by language code. For example: en_US -> American English. + * + * @param code Short name of the language (ru, en, pl, uk, etc...) + * @returns the language name + */ + +static const char * +spell_decode_lang (const char *code) +{ + size_t i; + + for (i = 0; spell_codes_map[i].code != NULL; i++) + { + if (strcmp (spell_codes_map[i].code, code) == 0) + return spell_codes_map[i].name; + } + + return code; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Checks if aspell library and symbols are available. + * + * @returns FALSE or error + */ + +static gboolean +spell_available (void) +{ + gchar *spell_module_fname; + gboolean ret = FALSE; + + if (spell_module != NULL) + return TRUE; + + spell_module_fname = g_module_build_path (NULL, "libaspell"); + spell_module = g_module_open (spell_module_fname, G_MODULE_BIND_LAZY); + + g_free (spell_module_fname); + + if (spell_module == NULL) + return FALSE; + + if (!g_module_symbol (spell_module, "new_aspell_config", (void *) &mc_new_aspell_config)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_dict_info_list_elements", + (void *) &mc_aspell_dict_info_list_elements)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_dict_info_enumeration_next", + (void *) &mc_aspell_dict_info_enumeration_next)) + goto error_ret; + + if (!g_module_symbol (spell_module, "new_aspell_speller", (void *) &mc_new_aspell_speller)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_error_number", (void *) &mc_aspell_error_number)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_speller_error_message", + (void *) &mc_aspell_speller_error_message)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_speller_error", + (void *) &mc_aspell_speller_error)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_error", (void *) &mc_aspell_error)) + goto error_ret; + + if (!g_module_symbol (spell_module, "to_aspell_speller", (void *) &mc_to_aspell_speller)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_speller_check", (void *) &mc_aspell_speller_check)) + goto error_ret; + + if (!g_module_symbol + (spell_module, "aspell_speller_suggest", (void *) &mc_aspell_speller_suggest)) + goto error_ret; + + if (!g_module_symbol + (spell_module, "aspell_word_list_elements", (void *) &mc_aspell_word_list_elements)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_string_enumeration_next", + (void *) &mc_aspell_string_enumeration_next)) + goto error_ret; + + if (!g_module_symbol + (spell_module, "aspell_config_replace", (void *) &mc_aspell_config_replace)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_error_message", (void *) &mc_aspell_error_message)) + goto error_ret; + + if (!g_module_symbol + (spell_module, "delete_aspell_speller", (void *) &mc_delete_aspell_speller)) + goto error_ret; + + if (!g_module_symbol (spell_module, "delete_aspell_config", (void *) &mc_delete_aspell_speller)) + goto error_ret; + + if (!g_module_symbol (spell_module, "delete_aspell_string_enumeration", + (void *) &mc_delete_aspell_string_enumeration)) + goto error_ret; + + if (!g_module_symbol (spell_module, "get_aspell_dict_info_list", + (void *) &mc_get_aspell_dict_info_list)) + goto error_ret; + + if (!g_module_symbol (spell_module, "delete_aspell_can_have_error", + (void *) &mc_delete_aspell_can_have_error)) + goto error_ret; + + if (!g_module_symbol (spell_module, "delete_aspell_dict_info_enumeration", + (void *) &mc_delete_aspell_dict_info_enumeration)) + goto error_ret; + + if (!g_module_symbol + (spell_module, "aspell_config_retrieve", (void *) &mc_aspell_config_retrieve)) + goto error_ret; + + if (!g_module_symbol + (spell_module, "aspell_word_list_size", (void *) &mc_aspell_word_list_size)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_speller_add_to_personal", + (void *) &mc_aspell_speller_add_to_personal)) + goto error_ret; + + if (!g_module_symbol (spell_module, "aspell_speller_save_all_word_lists", + (void *) &mc_aspell_speller_save_all_word_lists)) + goto error_ret; + + ret = TRUE; + + error_ret: + if (!ret) + { + g_module_close (spell_module); + spell_module = NULL; + } + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Initialization of Aspell support. + */ + +void +aspell_init (void) +{ + AspellCanHaveError *error = NULL; + + if (global_speller != NULL) + return; + + global_speller = g_try_malloc (sizeof (spell_t)); + if (global_speller == NULL) + return; + + if (!spell_available ()) + { + g_free (global_speller); + global_speller = NULL; + return; + } + + global_speller->config = mc_new_aspell_config (); + global_speller->speller = NULL; + + error = mc_new_aspell_speller (global_speller->config); + + if (mc_aspell_error_number (error) == 0) + global_speller->speller = mc_to_aspell_speller (error); + else + { + edit_error_dialog (_("Error"), mc_aspell_error_message (error)); + mc_delete_aspell_can_have_error (error); + g_free (global_speller); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Deinitialization of Aspell support. + */ + +void +aspell_clean (void) +{ + if (global_speller == NULL) + return; + + mc_delete_aspell_speller (global_speller->speller); + g_free (global_speller); + global_speller = NULL; + + g_module_close (spell_module); + spell_module = NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Get array of available languages. + * + * @param lang_list Array of languages. Must be cleared before use + * @returns language list length + */ + +unsigned int +aspell_get_lang_list (GArray * lang_list) +{ + AspellDictInfoList *dlist; + AspellDictInfoEnumeration *elem; + const AspellDictInfo *entry; + unsigned int i = 0; + + if (spell_module == NULL) + return 0; + + /* the returned pointer should _not_ need to be deleted */ + dlist = mc_get_aspell_dict_info_list (global_speller->config); + elem = mc_aspell_dict_info_list_elements (dlist); + + while ((entry = mc_aspell_dict_info_enumeration_next (elem)) != NULL) + { + if (entry->name != NULL) + { + char *tmp; + + tmp = g_strdup (entry->name); + g_array_append_val (lang_list, tmp); + i++; + } + } + + mc_delete_aspell_dict_info_enumeration (elem); + + return i; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Clear the array of languages. + * + * @param array Array of languages + */ + +void +aspell_array_clean (GArray * array) +{ + if (array != NULL) + { + guint i = 0; + + for (i = 0; i < array->len; ++i) + { + char *tmp; + + tmp = g_array_index (array, char *, i); + g_free (tmp); + } + g_array_free (array, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Get the current language name. + * + * @returns Language name + */ + +const char * +aspell_get_lang (void) +{ + const char *code; + + code = mc_aspell_config_retrieve (global_speller->config, "lang"); + return spell_decode_lang (code); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Set the language. + * + * @param Language name + * @returns FALSE or error + */ + +gboolean +aspell_set_lang (const char *lang) +{ + if (lang != NULL) + { + int res; + AspellCanHaveError *error; + const char *spell_codeset; + +#ifdef HAVE_CHARSET + if (mc_global.source_codepage > 0) + spell_codeset = get_codepage_id (mc_global.source_codepage); + else +#endif + spell_codeset = str_detect_termencoding (); + + res = mc_aspell_config_replace (global_speller->config, "lang", lang); + res = mc_aspell_config_replace (global_speller->config, "encoding", spell_codeset); + + /* the returned pointer should _not_ need to be deleted */ + if (global_speller->speller != NULL) + mc_delete_aspell_speller (global_speller->speller); + + global_speller->speller = NULL; + + error = mc_new_aspell_speller (global_speller->config); + if (mc_aspell_error (error) != 0) + { + mc_delete_aspell_can_have_error (error); + return FALSE; + } + + global_speller->speller = mc_to_aspell_speller (error); + } + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Check word. + * + * @param word Word for spell check + * @param word_size Word size (in bytes) + * @returns FALSE if word is not in the dictionary + */ + +gboolean +aspell_check (const char *word, const int word_size) +{ + int res = 0; + + if (word != NULL && global_speller != NULL && global_speller->speller != NULL) + res = mc_aspell_speller_check (global_speller->speller, word, word_size); + + return (res == 1); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Examine dictionaries and suggest possible words that may repalce the incorrect word. + * + * @param suggest array of words to iterate through + * @param word Word for spell check + * @param word_size Word size (in bytes) + * @returns count of suggests for the word + */ + +unsigned int +aspell_suggest (GArray * suggest, const char *word, const int word_size) +{ + unsigned int size = 0; + + if (word != NULL && global_speller != NULL && global_speller->speller != NULL) + { + const AspellWordList *wordlist; + + wordlist = mc_aspell_speller_suggest (global_speller->speller, word, word_size); + if (wordlist != NULL) + { + AspellStringEnumeration *elements = NULL; + unsigned int i; + + elements = mc_aspell_word_list_elements (wordlist); + size = mc_aspell_word_list_size (wordlist); + + for (i = 0; i < size; i++) + { + const char *cur_sugg_word; + + cur_sugg_word = g_strdup (mc_aspell_string_enumeration_next (elements)); + if (cur_sugg_word != NULL) + g_array_append_val (suggest, cur_sugg_word); + } + + mc_delete_aspell_string_enumeration (elements); + } + } + + return size; +} + +/* --------------------------------------------------------------------------------------------- */ +/* + * Add word to personal dictionary. + * + * @param word Word for spell check + * @param word_size Word size (in bytes) + * @returns FALSE or error + */ +gboolean +aspell_add_to_dict (const char *word, int word_size) +{ + mc_aspell_speller_add_to_personal (global_speller->speller, word, word_size); + + if (mc_aspell_speller_error (global_speller->speller) != 0) + { + edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller->speller)); + return FALSE; + } + + mc_aspell_speller_save_all_word_lists (global_speller->speller); + + if (mc_aspell_speller_error (global_speller->speller) != 0) + { + edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller->speller)); + return FALSE; + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/editor/spell.h b/src/editor/spell.h new file mode 100644 index 000000000..91fd1002d --- /dev/null +++ b/src/editor/spell.h @@ -0,0 +1,28 @@ +#ifndef MC__EDIT_ASPELL_H +#define MC__EDIT_ASPELL_H + +#include "lib/global.h" /* include */ + +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +void aspell_init (void); +void aspell_clean (void); +gboolean aspell_check (const char *word, const int word_size); +unsigned int aspell_suggest (GArray *suggest, const char *word, const int word_size); +void aspell_array_clean (GArray *array); +unsigned int aspell_get_lang_list (GArray *lang_list); +const char *aspell_get_lang (void); +gboolean aspell_set_lang (const char *lang); +gboolean aspell_add_to_dict (const char *word, const int word_size); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__EDIT_ASPELL_H */ diff --git a/src/editor/spell_dialogs.c b/src/editor/spell_dialogs.c new file mode 100644 index 000000000..818edff0e --- /dev/null +++ b/src/editor/spell_dialogs.c @@ -0,0 +1,182 @@ +/* + Editor spell checker dialogs + + Copyright (C) 2012 + The Free Software Foundation, Inc. + + Written by: + Ilia Maslakov , 2012 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include + +#include "lib/global.h" +#include "lib/strutil.h" /* str_term_width1 */ +#include "lib/widget.h" +#include "lib/tty/tty.h" /* COLS, LINES */ + +#include "editwidget.h" + +#include "spell.h" +#include "spell_dialogs.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/*** file scope functions ************************************************************************/ + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Show suggests for the current word. + * + * @param edit Editor object + * @param word Word for spell check + * @param new_word Word to replace the incorrect word + * @param suggest Array of suggests for current word + * @returns code of pressed button + */ + +int +spell_dialog_spell_suggest_show (WEdit * edit, const char *word, char **new_word, GArray *suggest) +{ + + int sug_dlg_h = 14; /* dialog height */ + int sug_dlg_w = 29; /* dialog width */ + int xpos, ypos; + char *lang_label; + char *word_label; + unsigned int i; + int res; + char *curr = NULL; + Dlg_head *sug_dlg; + WListbox *sug_list; + int max_btn_len = 0; + int add_len; + int replace_len; + int skip_len; + int cancel_len; + WButton *add_btn; + WButton *replace_btn; + WButton *skip_btn; + WButton *cancel_button; + int word_label_len; + + /* calculate the dialog metrics */ + xpos = (COLS - sug_dlg_w) / 2; + ypos = (LINES - sug_dlg_h) * 2 / 3; + + /* Sometimes menu can hide replaced text. I don't like it */ + if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + sug_dlg_h - 1)) + ypos -= sug_dlg_h; + + add_btn = button_new (5, 28, B_ADD_WORD, NORMAL_BUTTON, _("&Add word"), 0); + add_len = button_get_len (add_btn); + replace_btn = button_new (7, 28, B_ENTER, NORMAL_BUTTON, _("&Replace"), 0); + replace_len = button_get_len (replace_btn); + skip_btn = button_new (9, 28, B_SKIP_WORD, NORMAL_BUTTON, _("&Skip"), 0); + skip_len = button_get_len (skip_btn); + cancel_button = button_new (11, 28, B_CANCEL, NORMAL_BUTTON, _("&Cancel"), 0); + cancel_len = button_get_len (cancel_button); + + max_btn_len = max (replace_len, skip_len); + max_btn_len = max (max_btn_len, cancel_len); + + lang_label = g_strdup_printf ("%s: %s", _("Language"), aspell_get_lang ()); + word_label = g_strdup_printf ("%s: %s", _("Misspelled"), word); + word_label_len = str_term_width1 (word_label) + 5; + + sug_dlg_w += max_btn_len; + sug_dlg_w = max (sug_dlg_w, word_label_len) + 1; + sug_dlg = create_dlg (TRUE, ypos, xpos, sug_dlg_h, sug_dlg_w, + dialog_colors, NULL, NULL, "[ASpell]", _("Check word"), DLG_COMPACT); + + sug_list = listbox_new (5, 2, sug_dlg_h - 7, 24, FALSE, NULL); + for (i = 0; i < suggest->len; i++) + listbox_add_item (sug_list, LISTBOX_APPEND_AT_END, 0, g_array_index (suggest, char *, i), + NULL); + add_widget (sug_dlg, sug_list); + + add_widget (sug_dlg, add_btn); + add_widget (sug_dlg, replace_btn); + add_widget (sug_dlg, skip_btn); + add_widget (sug_dlg, cancel_button); + + add_widget (sug_dlg, label_new (1, 2, lang_label)); + add_widget (sug_dlg, label_new (3, 2, word_label)); + add_widget (sug_dlg, groupbox_new (4, 2, sug_dlg_h - 5, 25, _("Suggest"))); + + res = run_dlg (sug_dlg); + if (res == B_ENTER) + { + char *tmp = NULL; + listbox_get_current (sug_list, &curr, NULL); + + if (curr != NULL) + tmp = g_strdup (curr); + *new_word = tmp; + } + + destroy_dlg (sug_dlg); + g_free (lang_label); + g_free (word_label); + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Show dialog to select language for spell check. + * + * @param languages Array of available languages + * @returns name of choosed language + */ + +char * +spell_dialog_lang_list_show (GArray *languages) +{ + + int lang_dlg_h = 12; /* dialog height */ + int lang_dlg_w = 30; /* dialog width */ + char *selected_lang = NULL; + unsigned int i; + int res; + Listbox *lang_list; + + /* Create listbox */ + lang_list = create_listbox_window_centered (-1, -1, lang_dlg_h, lang_dlg_w, + _("Select language"), "[ASpell]"); + + for (i = 0; i < languages->len; i++) + LISTBOX_APPEND_TEXT (lang_list, 0, g_array_index (languages, char *, i), NULL); + + res = run_listbox (lang_list); + if (res >= 0) + selected_lang = g_strdup (g_array_index (languages, char *, (unsigned int) res)); + + return selected_lang; + +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/editor/spell_dialogs.h b/src/editor/spell_dialogs.h new file mode 100644 index 000000000..3a8c02920 --- /dev/null +++ b/src/editor/spell_dialogs.h @@ -0,0 +1,25 @@ +#ifndef MC__EDIT_ASPELL_DIALOGS_H +#define MC__EDIT_ASPELL_DIALOGS_H + +#include "lib/global.h" /* include */ + +/*** typedefs(not structures) and defined constants **********************************************/ + +#define B_SKIP_WORD (B_USER+3) +#define B_ADD_WORD (B_USER+4) + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +int spell_dialog_spell_suggest_show (WEdit * edit, const char *word, char **new_word, + GArray *suggest); +char *spell_dialog_lang_list_show (GArray *languages); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__EDIT_ASPELL_DIALOGS_H */ diff --git a/src/keybind-defaults.c b/src/keybind-defaults.c index b88968956..bb734207b 100644 --- a/src/keybind-defaults.c +++ b/src/keybind-defaults.c @@ -416,6 +416,9 @@ static const global_keymap_ini_t default_editor_keymap[] = { {"Sort", "alt-t"}, {"Mail", "alt-m"}, {"ExternalCommand", "alt-u"}, +#ifdef HAVE_ASPELL + {"SpellCheckCurrentWord", "ctrl-p"}, +#endif {"ExtendedKeyMap", "ctrl-x"}, {NULL, NULL} }; From 3d4ed51b7c5cd1023ba48622955ab2d50e11dfc6 Mon Sep 17 00:00:00 2001 From: Andrew Borodin Date: Sat, 7 Jul 2012 11:55:51 +0400 Subject: [PATCH 2/2] Remove misc/edit.spell.rc since aspell is supported by mcedit directly. Signed-off-by: Andrew Borodin --- misc/Makefile.am | 3 +-- misc/edit.spell.rc | 11 ----------- 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 misc/edit.spell.rc diff --git a/misc/Makefile.am b/misc/Makefile.am index 3fcdae313..fa78487cf 100644 --- a/misc/Makefile.am +++ b/misc/Makefile.am @@ -22,8 +22,7 @@ LIBFILES_CONST = \ mc.menu.sr LIBFILES_SCRIPT = \ - edit.indent.rc \ - edit.spell.rc + edit.indent.rc CLEANFILES = $(SCRIPTS_OUT) diff --git a/misc/edit.spell.rc b/misc/edit.spell.rc deleted file mode 100644 index e9dee8bbe..000000000 --- a/misc/edit.spell.rc +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/sh -# *** External Spell Checker for GNU Midnight Commander. -# arguments: -# $1 - Name of the file being edited -# $2 - Name of the file to be processed - -if aspell /dev/null 2>&1; then - aspell -c "$2" -else - ispell "$2" -fi