summaryrefslogtreecommitdiff
path: root/src/plugins/spell
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/spell')
-rw-r--r--src/plugins/spell/CMakeLists.txt42
-rw-r--r--src/plugins/spell/Makefile.am44
-rw-r--r--src/plugins/spell/spell-bar-item.c167
-rw-r--r--src/plugins/spell/spell-bar-item.h25
-rw-r--r--src/plugins/spell/spell-command.c507
-rw-r--r--src/plugins/spell/spell-command.h25
-rw-r--r--src/plugins/spell/spell-completion.c139
-rw-r--r--src/plugins/spell/spell-completion.h25
-rw-r--r--src/plugins/spell/spell-config.c676
-rw-r--r--src/plugins/spell/spell-config.h54
-rw-r--r--src/plugins/spell/spell-info.c89
-rw-r--r--src/plugins/spell/spell-info.h25
-rw-r--r--src/plugins/spell/spell-speller.c486
-rw-r--r--src/plugins/spell/spell-speller.h51
-rw-r--r--src/plugins/spell/spell.c1167
-rw-r--r--src/plugins/spell/spell.h52
16 files changed, 3574 insertions, 0 deletions
diff --git a/src/plugins/spell/CMakeLists.txt b/src/plugins/spell/CMakeLists.txt
new file mode 100644
index 000000000..0a1c51bb1
--- /dev/null
+++ b/src/plugins/spell/CMakeLists.txt
@@ -0,0 +1,42 @@
+#
+# Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+# Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+#
+# This file is part of WeeChat, the extensible chat client.
+#
+# WeeChat 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.
+#
+# WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+#
+
+add_library(spell MODULE
+spell.c spell.h
+spell-bar-item.c spell-bar-item.h
+spell-command.c spell-command.h
+spell-completion.c spell-completion.h
+spell-config.c spell-config.h
+spell-info.c spell-info.h
+spell-speller.c spell-speller.h)
+set_target_properties(spell PROPERTIES PREFIX "")
+
+if(ENCHANT_FOUND)
+ include_directories(${ENCHANT_INCLUDE_DIR})
+ target_link_libraries(spell ${ENCHANT_LIBRARIES})
+ add_definitions(-DUSE_ENCHANT)
+else()
+ if(ASPELL_FOUND)
+ include_directories(${ASPELL_INCLUDE_PATH})
+ target_link_libraries(spell ${ASPELL_LIBRARY})
+ endif()
+endif()
+
+install(TARGETS spell LIBRARY DESTINATION ${LIBDIR}/plugins)
diff --git a/src/plugins/spell/Makefile.am b/src/plugins/spell/Makefile.am
new file mode 100644
index 000000000..b3313d9f4
--- /dev/null
+++ b/src/plugins/spell/Makefile.am
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+# Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+#
+# This file is part of WeeChat, the extensible chat client.
+#
+# WeeChat 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.
+#
+# WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+#
+
+AM_CPPFLAGS = -DLOCALEDIR=\"$(datadir)/locale\" $(ASPELL_CFLAGS) $(ENCHANT_CFLAGS)
+
+libdir = ${weechat_libdir}/plugins
+
+lib_LTLIBRARIES = spell.la
+
+spell_la_SOURCES = spell.c \
+ spell.h \
+ spell-bar-item.c \
+ spell-bar-item.h \
+ spell-command.c \
+ spell-command.h \
+ spell-completion.c \
+ spell-completion.h \
+ spell-config.c \
+ spell-config.h \
+ spell-info.c \
+ spell-info.h \
+ spell-speller.c \
+ spell-speller.h
+spell_la_LDFLAGS = -module -no-undefined
+spell_la_LIBADD = $(ASPELL_LFLAGS) $(ENCHANT_LIBS)
+
+EXTRA_DIST = CMakeLists.txt
diff --git a/src/plugins/spell/spell-bar-item.c b/src/plugins/spell/spell-bar-item.c
new file mode 100644
index 000000000..0b9ef5453
--- /dev/null
+++ b/src/plugins/spell/spell-bar-item.c
@@ -0,0 +1,167 @@
+/*
+ * spell-bar-item.c - bar items for spell checker plugin
+ *
+ * Copyright (C) 2012 Nils Görs <weechatter@arcor.de>
+ * Copyright (C) 2012-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../weechat-plugin.h"
+#include "spell.h"
+#include "spell-config.h"
+
+
+/*
+ * Returns content of bar item "spell_dict": spell dictionary used on current
+ * buffer.
+ */
+
+char *
+spell_bar_item_dict (const void *pointer, void *data,
+ struct t_gui_bar_item *item,
+ struct t_gui_window *window,
+ struct t_gui_buffer *buffer,
+ struct t_hashtable *extra_info)
+{
+ const char *dict_list;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) item;
+ (void) window;
+ (void) extra_info;
+
+ if (!buffer)
+ return NULL;
+
+ dict_list = spell_get_dict (buffer);
+
+ return (dict_list) ? strdup (dict_list) : NULL;
+}
+
+/*
+ * Returns content of bar item "spell_suggest": spell checker suggestions.
+ */
+
+char *
+spell_bar_item_suggest (const void *pointer, void *data,
+ struct t_gui_bar_item *item,
+ struct t_gui_window *window,
+ struct t_gui_buffer *buffer,
+ struct t_hashtable *extra_info)
+{
+ const char *ptr_suggestions, *pos;
+ char **suggestions, **suggestions2, **str_suggest;
+ int i, j, num_suggestions, num_suggestions2;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) item;
+ (void) window;
+ (void) extra_info;
+
+ if (!spell_enabled)
+ return NULL;
+
+ if (!buffer)
+ return NULL;
+
+ ptr_suggestions = weechat_buffer_get_string (buffer,
+ "localvar_spell_suggest");
+ if (!ptr_suggestions)
+ return NULL;
+
+ pos = strchr (ptr_suggestions, ':');
+ if (pos)
+ pos++;
+ else
+ pos = ptr_suggestions;
+
+ str_suggest = weechat_string_dyn_alloc (256);
+ if (!str_suggest)
+ return NULL;
+
+ suggestions = weechat_string_split (pos, "/", 0, 0, &num_suggestions);
+ if (!suggestions)
+ goto end;
+
+ for (i = 0; i < num_suggestions; i++)
+ {
+ if (i > 0)
+ {
+ weechat_string_dyn_concat (
+ str_suggest,
+ weechat_color (
+ weechat_config_string (
+ spell_config_color_suggestion_delimiter_dict)));
+ weechat_string_dyn_concat (
+ str_suggest,
+ weechat_config_string (
+ spell_config_look_suggestion_delimiter_dict));
+ }
+ suggestions2 = weechat_string_split (suggestions[i], ",", 0, 0,
+ &num_suggestions2);
+ if (suggestions2)
+ {
+ for (j = 0; j < num_suggestions2; j++)
+ {
+ if (j > 0)
+ {
+ weechat_string_dyn_concat (
+ str_suggest,
+ weechat_color (
+ weechat_config_string (
+ spell_config_color_suggestion_delimiter_word)));
+ weechat_string_dyn_concat (
+ str_suggest,
+ weechat_config_string (
+ spell_config_look_suggestion_delimiter_word));
+ }
+ weechat_string_dyn_concat (
+ str_suggest,
+ weechat_color (
+ weechat_config_string (
+ spell_config_color_suggestion)));
+ weechat_string_dyn_concat (str_suggest, suggestions2[j]);
+ }
+ weechat_string_free_split (suggestions2);
+ }
+ }
+ weechat_string_free_split (suggestions);
+
+end:
+ return weechat_string_dyn_free (str_suggest, 0);
+}
+
+/*
+ * Initializes spell bar items.
+ */
+
+void
+spell_bar_item_init ()
+{
+ weechat_bar_item_new ("spell_dict",
+ &spell_bar_item_dict, NULL, NULL);
+ weechat_bar_item_new ("spell_suggest",
+ &spell_bar_item_suggest, NULL, NULL);
+}
diff --git a/src/plugins/spell/spell-bar-item.h b/src/plugins/spell/spell-bar-item.h
new file mode 100644
index 000000000..84f65681e
--- /dev/null
+++ b/src/plugins/spell/spell-bar-item.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012 Nils Görs <weechatter@arcor.de>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEECHAT_PLUGIN_SPELL_BAR_ITEM_H
+#define WEECHAT_PLUGIN_SPELL_BAR_ITEM_H
+
+extern void spell_bar_item_init ();
+
+#endif /* WEECHAT_PLUGIN_SPELL_BAR_ITEM_H */
diff --git a/src/plugins/spell/spell-command.c b/src/plugins/spell/spell-command.c
new file mode 100644
index 000000000..5d27aa9d7
--- /dev/null
+++ b/src/plugins/spell/spell-command.c
@@ -0,0 +1,507 @@
+/*
+ * spell-command.c - spell checker commands
+ *
+ * Copyright (C) 2013-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../weechat-plugin.h"
+#include "spell.h"
+#include "spell-config.h"
+#include "spell-speller.h"
+
+
+/*
+ * Converts an ISO lang code in its English full name.
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+spell_command_iso_to_lang (const char *code)
+{
+ int i;
+
+ for (i = 0; spell_langs[i].code; i++)
+ {
+ if (strcmp (spell_langs[i].code, code) == 0)
+ return strdup (spell_langs[i].name);
+ }
+
+ /* lang code not found */
+ return strdup ("Unknown");
+}
+
+/*
+ * Converts an ISO country code in its English full name.
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+spell_command_iso_to_country (const char *code)
+{
+ int i;
+
+ for (i = 0; spell_countries[i].code; i++)
+ {
+ if (strcmp (spell_countries[i].code, code) == 0)
+ return strdup (spell_countries[i].name);
+ }
+
+ /* country code not found */
+ return strdup ("Unknown");
+}
+
+/*
+ * Displays one dictionary when using enchant.
+ */
+
+#ifdef USE_ENCHANT
+void
+spell_enchant_dict_describe_cb (const char *lang_tag,
+ const char *provider_name,
+ const char *provider_desc,
+ const char *provider_file,
+ void *user_data)
+{
+ char *country, *lang, *pos, *iso;
+ char str_dict[256];
+
+ /* make C compiler happy */
+ (void) provider_name;
+ (void) provider_desc;
+ (void) provider_file;
+ (void) user_data;
+
+ lang = NULL;
+ country = NULL;
+
+ pos = strchr (lang_tag, '_');
+
+ if (pos)
+ {
+ iso = weechat_strndup (lang_tag, pos - lang_tag);
+ if (iso)
+ {
+ lang = spell_command_iso_to_lang (iso);
+ country = spell_command_iso_to_country (pos + 1);
+ free (iso);
+ }
+ }
+ else
+ lang = spell_command_iso_to_lang ((char *)lang_tag);
+
+ if (lang)
+ {
+ if (country)
+ {
+ snprintf (str_dict, sizeof (str_dict), "%-22s %s (%s)",
+ lang_tag, lang, country);
+ }
+ else
+ {
+ snprintf (str_dict, sizeof (str_dict), "%-22s %s",
+ lang_tag, lang);
+ }
+ weechat_printf (NULL, " %s", str_dict);
+ }
+
+ if (lang)
+ free (lang);
+ if (country)
+ free (country);
+}
+#endif /* USE_ENCHANT */
+
+/*
+ * Displays list of dictionaries installed on system.
+ */
+
+void
+spell_command_speller_list_dicts ()
+{
+#ifndef USE_ENCHANT
+ char *country, *lang, *pos, *iso;
+ char str_dict[256], str_country[128];
+ struct AspellConfig *config;
+ AspellDictInfoList *list;
+ AspellDictInfoEnumeration *elements;
+ const AspellDictInfo *dict;
+#endif /* USE_ENCHANT */
+
+ weechat_printf (NULL, "");
+ weechat_printf (NULL,
+ /* TRANSLATORS: "%s" is "spell" (name of plugin) */
+ _( "%s dictionaries list:"),
+ SPELL_PLUGIN_NAME);
+
+#ifdef USE_ENCHANT
+ enchant_broker_list_dicts (broker, spell_enchant_dict_describe_cb,
+ NULL);
+#else
+ config = new_aspell_config ();
+ list = get_aspell_dict_info_list (config);
+ elements = aspell_dict_info_list_elements (list);
+
+ while ((dict = aspell_dict_info_enumeration_next (elements)) != NULL)
+ {
+ lang = NULL;
+ country = NULL;
+ pos = strchr (dict->code, '_');
+
+ if (pos)
+ {
+ iso = weechat_strndup (dict->code, pos - dict->code);
+ if (iso)
+ {
+ lang = spell_command_iso_to_lang (iso);
+ country = spell_command_iso_to_country (pos + 1);
+ free (iso);
+ }
+ }
+ else
+ lang = spell_command_iso_to_lang ((char*)dict->code);
+
+ str_country[0] = '\0';
+ if (country || dict->jargon[0])
+ {
+ snprintf (str_country, sizeof (str_country), " (%s%s%s)",
+ (country) ? country : dict->jargon,
+ (country && dict->jargon[0]) ? " - " : "",
+ (country && dict->jargon[0]) ? ((dict->jargon[0]) ? dict->jargon : country) : "");
+ }
+
+ snprintf (str_dict, sizeof (str_dict), "%-22s %s%s",
+ dict->name,
+ (lang) ? lang : "?",
+ str_country);
+
+ weechat_printf (NULL, " %s", str_dict);
+
+ if (lang)
+ free (lang);
+ if (country)
+ free (country);
+ }
+
+ delete_aspell_dict_info_enumeration (elements);
+ delete_aspell_config (config);
+#endif /* USE_ENCHANT */
+}
+
+/*
+ * Sets a list of dictionaries for a buffer.
+ */
+
+void
+spell_command_set_dict (struct t_gui_buffer *buffer, const char *value)
+{
+ char *name;
+
+ name = spell_build_option_name (buffer);
+ if (!name)
+ return;
+
+ if (spell_config_set_dict (name, value) > 0)
+ {
+ if (value && value[0])
+ weechat_printf (NULL, "%s: \"%s\" => %s",
+ SPELL_PLUGIN_NAME, name, value);
+ else
+ weechat_printf (NULL, _("%s: \"%s\" removed"),
+ SPELL_PLUGIN_NAME, name);
+ }
+
+ free (name);
+}
+
+/*
+ * Adds a word in personal dictionary.
+ */
+
+void
+spell_command_add_word (struct t_gui_buffer *buffer, const char *dict,
+ const char *word)
+{
+ struct t_spell_speller_buffer *ptr_speller_buffer;
+#ifdef USE_ENCHANT
+ EnchantDict *new_speller, *ptr_speller;
+#else
+ AspellSpeller *new_speller, *ptr_speller;
+#endif /* USE_ENCHANT */
+
+ new_speller = NULL;
+
+ if (dict)
+ {
+ ptr_speller = weechat_hashtable_get (spell_spellers, dict);
+ if (!ptr_speller)
+ {
+ if (!spell_speller_dict_supported (dict))
+ {
+ weechat_printf (NULL,
+ _("%s: error: dictionary \"%s\" is not "
+ "available on your system"),
+ SPELL_PLUGIN_NAME, dict);
+ return;
+ }
+ new_speller = spell_speller_new (dict);
+ if (!new_speller)
+ return;
+ ptr_speller = new_speller;
+ }
+ }
+ else
+ {
+ ptr_speller_buffer = weechat_hashtable_get (spell_speller_buffer,
+ buffer);
+ if (!ptr_speller_buffer)
+ ptr_speller_buffer = spell_speller_buffer_new (buffer);
+ if (!ptr_speller_buffer)
+ goto error;
+ if (!ptr_speller_buffer->spellers || !ptr_speller_buffer->spellers[0])
+ {
+ weechat_printf (NULL,
+ _("%s%s: no dictionary on this buffer for "
+ "adding word"),
+ weechat_prefix ("error"),
+ SPELL_PLUGIN_NAME);
+ return;
+ }
+ else if (ptr_speller_buffer->spellers[1])
+ {
+ weechat_printf (NULL,
+ _("%s%s: many dictionaries are defined for "
+ "this buffer, please specify dictionary"),
+ weechat_prefix ("error"),
+ SPELL_PLUGIN_NAME);
+ return;
+ }
+ ptr_speller = ptr_speller_buffer->spellers[0];
+ }
+
+#ifdef USE_ENCHANT
+ enchant_dict_add (ptr_speller, word, strlen (word));
+#else
+ if (aspell_speller_add_to_personal (ptr_speller,
+ word,
+ strlen (word)) == 1)
+ {
+ weechat_printf (NULL,
+ _("%s: word \"%s\" added to personal dictionary"),
+ SPELL_PLUGIN_NAME, word);
+ }
+ else
+ goto error;
+#endif /* USE_ENCHANT */
+
+ goto end;
+
+error:
+ weechat_printf (NULL,
+ _("%s%s: failed to add word to personal "
+ "dictionary"),
+ weechat_prefix ("error"), SPELL_PLUGIN_NAME);
+
+end:
+ if (new_speller)
+ weechat_hashtable_remove (spell_spellers, dict);
+}
+
+/*
+ * Callback for command "/spell".
+ */
+
+int
+spell_command_cb (const void *pointer, void *data,
+ struct t_gui_buffer *buffer,
+ int argc, char **argv, char **argv_eol)
+{
+ char *dicts;
+ const char *default_dict;
+ struct t_infolist *infolist;
+ int number;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+
+ if (argc == 1)
+ {
+ /* display spell status */
+ weechat_printf (NULL, "");
+ weechat_printf (NULL,
+ /* TRANSLATORS: second "%s" is "aspell" or "enchant" */
+ _("%s (using %s)"),
+ (spell_enabled) ? _("Spell checking is enabled") : _("Spell checking is disabled"),
+#ifdef USE_ENCHANT
+ "enchant"
+#else
+ "aspell"
+#endif /* USE_ENCHANT */
+ );
+ default_dict = weechat_config_string (spell_config_check_default_dict);
+ weechat_printf (NULL,
+ _("Default dictionary: %s"),
+ (default_dict && default_dict[0]) ?
+ default_dict : _("(not set)"));
+ number = 0;
+ infolist = weechat_infolist_get ("option", NULL, "spell.dict.*");
+ if (infolist)
+ {
+ while (weechat_infolist_next (infolist))
+ {
+ if (number == 0)
+ weechat_printf (NULL, _("Specific dictionaries on buffers:"));
+ number++;
+ weechat_printf (NULL, " %s: %s",
+ weechat_infolist_string (infolist, "option_name"),
+ weechat_infolist_string (infolist, "value"));
+ }
+ weechat_infolist_free (infolist);
+ }
+ return WEECHAT_RC_OK;
+ }
+
+ /* enable spell */
+ if (weechat_strcasecmp (argv[1], "enable") == 0)
+ {
+ weechat_config_option_set (spell_config_check_enabled, "1", 1);
+ weechat_printf (NULL, _("Spell checker enabled"));
+ return WEECHAT_RC_OK;
+ }
+
+ /* disable spell */
+ if (weechat_strcasecmp (argv[1], "disable") == 0)
+ {
+ weechat_config_option_set (spell_config_check_enabled, "0", 1);
+ weechat_printf (NULL, _("Spell checker disabled"));
+ return WEECHAT_RC_OK;
+ }
+
+ /* toggle spell */
+ if (weechat_strcasecmp (argv[1], "toggle") == 0)
+ {
+ if (spell_enabled)
+ {
+ weechat_config_option_set (spell_config_check_enabled, "0", 1);
+ weechat_printf (NULL, _("Spell checker disabled"));
+ }
+ else
+ {
+ weechat_config_option_set (spell_config_check_enabled, "1", 1);
+ weechat_printf (NULL, _("Spell checker enabled"));
+ }
+ return WEECHAT_RC_OK;
+ }
+
+ /* list of dictionaries */
+ if (weechat_strcasecmp (argv[1], "listdict") == 0)
+ {
+ spell_command_speller_list_dicts ();
+ return WEECHAT_RC_OK;
+ }
+
+ /* set dictionary for current buffer */
+ if (weechat_strcasecmp (argv[1], "setdict") == 0)
+ {
+ WEECHAT_COMMAND_MIN_ARGS(3, "setdict");
+ dicts = weechat_string_replace (argv_eol[2], " ", "");
+ spell_command_set_dict (buffer,
+ (dicts) ? dicts : argv[2]);
+ if (dicts)
+ free (dicts);
+ return WEECHAT_RC_OK;
+ }
+
+ /* delete dictionary used on current buffer */
+ if (weechat_strcasecmp (argv[1], "deldict") == 0)
+ {
+ spell_command_set_dict (buffer, NULL);
+ return WEECHAT_RC_OK;
+ }
+
+ /* add word to personal dictionary */
+ if (weechat_strcasecmp (argv[1], "addword") == 0)
+ {
+ WEECHAT_COMMAND_MIN_ARGS(3, "addword");
+ if (argc > 3)
+ {
+ /* use a given dict */
+ spell_command_add_word (buffer, argv[2], argv_eol[3]);
+ }
+ else
+ {
+ /* use default dict */
+ spell_command_add_word (buffer, NULL, argv_eol[2]);
+ }
+ return WEECHAT_RC_OK;
+ }
+
+ WEECHAT_COMMAND_ERROR;
+}
+
+/*
+ * Hooks spell command.
+ */
+
+void
+spell_command_init ()
+{
+ weechat_hook_command (
+ "spell",
+ N_("spell plugin configuration"),
+ N_("enable|disable|toggle"
+ " || listdict"
+ " || setdict <dict>[,<dict>...]"
+ " || deldict"
+ " || addword [<dict>] <word>"),
+ N_(" enable: enable spell checker\n"
+ " disable: disable spell checker\n"
+ " toggle: toggle spell checker\n"
+ "listdict: show installed dictionaries\n"
+ " setdict: set dictionary for current buffer (multiple dictionaries "
+ "can be separated by a comma)\n"
+ " deldict: delete dictionary used on current buffer\n"
+ " addword: add a word in personal dictionary\n"
+ "\n"
+ "Input line beginning with a '/' is not checked, except for some "
+ "commands (see /set spell.check.commands).\n"
+ "\n"
+ "To enable spell checker on all buffers, use option \"default_dict\", "
+ "then enable spell checker, for example:\n"
+ " /set spell.check.default_dict \"en\"\n"
+ " /spell enable\n"
+ "\n"
+ "To display a list of suggestions in a bar, use item "
+ "\"spell_suggest\".\n"
+ "\n"
+ "Default key to toggle spell checker is alt-s."),
+ "enable"
+ " || disable"
+ " || toggle"
+ " || listdict"
+ " || setdict %(spell_dicts)"
+ " || deldict"
+ " || addword",
+ &spell_command_cb, NULL, NULL);
+}
diff --git a/src/plugins/spell/spell-command.h b/src/plugins/spell/spell-command.h
new file mode 100644
index 000000000..822955c0c
--- /dev/null
+++ b/src/plugins/spell/spell-command.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEECHAT_PLUGIN_SPELL_COMMAND_H
+#define WEECHAT_PLUGIN_SPELL_COMMAND_H
+
+extern void spell_command_init ();
+
+#endif /* WEECHAT_PLUGIN_SPELL_COMMAND_H */
diff --git a/src/plugins/spell/spell-completion.c b/src/plugins/spell/spell-completion.c
new file mode 100644
index 000000000..f80030add
--- /dev/null
+++ b/src/plugins/spell/spell-completion.c
@@ -0,0 +1,139 @@
+/*
+ * spell-completion.c - completion for spell checker commands
+ *
+ * Copyright (C) 2013-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../weechat-plugin.h"
+#include "spell.h"
+
+
+/*
+ * Adds spell langs (all langs, even for dictionaries not installed) to
+ * completion list.
+ */
+
+int
+spell_completion_langs_cb (const void *pointer, void *data,
+ const char *completion_item,
+ struct t_gui_buffer *buffer,
+ struct t_gui_completion *completion)
+{
+ int i;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) completion_item;
+ (void) buffer;
+
+ for (i = 0; spell_langs[i].code; i++)
+ {
+ weechat_hook_completion_list_add (completion,
+ spell_langs[i].code,
+ 0, WEECHAT_LIST_POS_SORT);
+ }
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Adds a dictionary to completion when using enchant.
+ */
+
+#ifdef USE_ENCHANT
+void
+spell_completion_enchant_add_dict_cb (const char *lang_tag,
+ const char *provider_name,
+ const char *provider_desc,
+ const char *provider_file,
+ void *user_data)
+{
+ /* make C compiler happy */
+ (void) provider_name;
+ (void) provider_desc;
+ (void) provider_file;
+
+ weechat_hook_completion_list_add ((struct t_gui_completion *)user_data,
+ lang_tag, 0, WEECHAT_LIST_POS_SORT);
+}
+#endif /* USE_ENCHANT */
+
+/*
+ * Adds spell dictionaries (only installed dictionaries) to completion list.
+ */
+
+int
+spell_completion_dicts_cb (const void *pointer, void *data,
+ const char *completion_item,
+ struct t_gui_buffer *buffer,
+ struct t_gui_completion *completion)
+{
+#ifndef USE_ENCHANT
+ struct AspellConfig *config;
+ AspellDictInfoList *list;
+ AspellDictInfoEnumeration *elements;
+ const AspellDictInfo *dict;
+#endif /* USE_ENCHANT */
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) completion_item;
+ (void) buffer;
+
+#ifdef USE_ENCHANT
+ enchant_broker_list_dicts (broker,
+ spell_completion_enchant_add_dict_cb,
+ completion);
+#else
+ config = new_aspell_config ();
+ list = get_aspell_dict_info_list (config);
+ elements = aspell_dict_info_list_elements (list);
+
+ while ((dict = aspell_dict_info_enumeration_next (elements)) != NULL)
+ {
+ weechat_hook_completion_list_add (completion, dict->name,
+ 0, WEECHAT_LIST_POS_SORT);
+ }
+
+ delete_aspell_dict_info_enumeration (elements);
+ delete_aspell_config (config);
+#endif /* USE_ENCHANT */
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Hooks completions.
+ */
+
+void
+spell_completion_init ()
+{
+ weechat_hook_completion ("spell_langs",
+ N_("list of all languages supported"),
+ &spell_completion_langs_cb, NULL, NULL);
+ weechat_hook_completion ("spell_dicts",
+ N_("list of installed dictionaries"),
+ &spell_completion_dicts_cb, NULL, NULL);
+}
diff --git a/src/plugins/spell/spell-completion.h b/src/plugins/spell/spell-completion.h
new file mode 100644
index 000000000..4c3ad6097
--- /dev/null
+++ b/src/plugins/spell/spell-completion.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEECHAT_PLUGIN_SPELL_COMPLETION_H
+#define WEECHAT_PLUGIN_SPELL_COMPLETION_H
+
+extern void spell_completion_init ();
+
+#endif /* WEECHAT_PLUGIN_SPELL_COMPLETION_H */
diff --git a/src/plugins/spell/spell-config.c b/src/plugins/spell/spell-config.c
new file mode 100644
index 000000000..cc24efc35
--- /dev/null
+++ b/src/plugins/spell/spell-config.c
@@ -0,0 +1,676 @@
+/*
+ * spell-config.c - spell checker configuration options (file spell.conf)
+ *
+ * Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+ * Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "../weechat-plugin.h"
+#include "spell.h"
+#include "spell-config.h"
+#include "spell-speller.h"
+
+
+struct t_config_file *spell_config_file = NULL;
+struct t_config_section *spell_config_section_dict = NULL;
+
+int spell_config_loading = 0;
+
+/* spell config, color section */
+
+struct t_config_option *spell_config_color_misspelled;
+struct t_config_option *spell_config_color_suggestion;
+struct t_config_option *spell_config_color_suggestion_delimiter_dict;
+struct t_config_option *spell_config_color_suggestion_delimiter_word;
+
+/* spell config, check section */
+
+struct t_config_option *spell_config_check_commands;
+struct t_config_option *spell_config_check_default_dict;
+struct t_config_option *spell_config_check_during_search;
+struct t_config_option *spell_config_check_enabled;
+struct t_config_option *spell_config_check_real_time;
+struct t_config_option *spell_config_check_suggestions;
+struct t_config_option *spell_config_check_word_min_length;
+
+/* spell config, look section */
+
+struct t_config_option *spell_config_look_suggestion_delimiter_dict;
+struct t_config_option *spell_config_look_suggestion_delimiter_word;
+
+
+char **spell_commands_to_check = NULL;
+int spell_count_commands_to_check = 0;
+int *spell_length_commands_to_check = NULL;
+
+
+/*
+ * Callback for changes on option "spell.check.commands".
+ */
+
+void
+spell_config_change_commands (const void *pointer, void *data,
+ struct t_config_option *option)
+{
+ const char *value;
+ int i;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+
+ if (spell_commands_to_check)
+ {
+ weechat_string_free_split (spell_commands_to_check);
+ spell_commands_to_check = NULL;
+ spell_count_commands_to_check = 0;
+ }
+
+ if (spell_length_commands_to_check)
+ {
+ free (spell_length_commands_to_check);
+ spell_length_commands_to_check = NULL;
+ }
+
+ value = weechat_config_string (option);
+ if (value && value[0])
+ {
+ spell_commands_to_check = weechat_string_split (value,
+ ",", 0, 0,
+ &spell_count_commands_to_check);
+ if (spell_count_commands_to_check > 0)
+ {
+ spell_length_commands_to_check = malloc (spell_count_commands_to_check *
+ sizeof (int));
+ for (i = 0; i < spell_count_commands_to_check; i++)
+ {
+ spell_length_commands_to_check[i] = strlen (spell_commands_to_check[i]);
+ }
+ }
+ }
+}
+
+/*
+ * Callback for changes on option "spell.check.default_dict".
+ */
+
+void
+spell_config_change_default_dict (const void *pointer, void *data,
+ struct t_config_option *option)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) option;
+
+ weechat_hashtable_remove_all (spell_speller_buffer);
+ if (!spell_config_loading)
+ spell_speller_remove_unused ();
+}
+
+/*
+ * Callback for changes on option "spell.check.enabled".
+ */
+
+void
+spell_config_change_enabled (const void *pointer, void *data,
+ struct t_config_option *option)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+
+ spell_enabled = weechat_config_boolean (option);
+
+ /* refresh input and spell suggestions */
+ weechat_bar_item_update ("input_text");
+ weechat_bar_item_update ("spell_suggest");
+}
+
+/*
+ * Callback for changes on option "spell.check.suggestions".
+ */
+
+void
+spell_config_change_suggestions (const void *pointer, void *data,
+ struct t_config_option *option)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) option;
+
+ weechat_bar_item_update ("spell_suggest");
+}
+
+/*
+ * Callback for changes on a dictionary.
+ */
+
+void
+spell_config_dict_change (const void *pointer, void *data,
+ struct t_config_option *option)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) option;
+
+ weechat_hashtable_remove_all (spell_speller_buffer);
+ if (!spell_config_loading)
+ spell_speller_remove_unused ();
+}
+
+/*
+ * Callback called when an option is deleted in section "dict".
+ */
+
+int
+spell_config_dict_delete_option (const void *pointer, void *data,
+ struct t_config_file *config_file,
+ struct t_config_section *section,
+ struct t_config_option *option)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) config_file;
+ (void) section;
+
+ weechat_config_option_free (option);
+
+ weechat_hashtable_remove_all (spell_speller_buffer);
+ if (!spell_config_loading)
+ spell_speller_remove_unused ();
+
+ return WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED;
+}
+
+/*
+ * Creates an option in section "dict".
+ */
+
+int
+spell_config_dict_create_option (const void *pointer, void *data,
+ struct t_config_file *config_file,
+ struct t_config_section *section,
+ const char *option_name,
+ const char *value)
+{
+ struct t_config_option *ptr_option;
+ int rc;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+
+ rc = WEECHAT_CONFIG_OPTION_SET_ERROR;
+
+ if (value && value[0])
+ spell_speller_check_dictionaries (value);
+
+ if (option_name)
+ {
+ ptr_option = weechat_config_search_option (config_file, section,
+ option_name);
+ if (ptr_option)
+ {
+ if (value && value[0])
+ rc = weechat_config_option_set (ptr_option, value, 0);
+ else
+ {
+ weechat_config_option_free (ptr_option);
+ rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+ }
+ else
+ {
+ if (value && value[0])
+ {
+ ptr_option = weechat_config_new_option (
+ config_file, section,
+ option_name, "string",
+ _("comma separated list of dictionaries to use on this buffer"),
+ NULL, 0, 0, "", value, 0,
+ NULL, NULL, NULL,
+ &spell_config_dict_change, NULL, NULL,
+ NULL, NULL, NULL);
+ rc = (ptr_option) ?
+ WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE : WEECHAT_CONFIG_OPTION_SET_ERROR;
+ }
+ else
+ rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+ }
+
+ if (rc == WEECHAT_CONFIG_OPTION_SET_ERROR)
+ {
+ weechat_printf (NULL,
+ _("%s%s: error creating spell dictionary \"%s\" => \"%s\""),
+ weechat_prefix ("error"), SPELL_PLUGIN_NAME,
+ option_name, value);
+ }
+ else
+ {
+ weechat_hashtable_remove_all (spell_speller_buffer);
+ if (!spell_config_loading)
+ spell_speller_remove_unused ();
+ }
+
+ return rc;
+}
+
+/*
+ * Callback for changes on an spell option.
+ */
+
+void
+spell_config_option_change (const void *pointer, void *data,
+ struct t_config_option *option)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) option;
+
+ weechat_hashtable_remove_all (spell_speller_buffer);
+ if (!spell_config_loading)
+ spell_speller_remove_unused ();
+}
+
+/*
+ * Callback called when an option is deleted in section "option".
+ */
+
+int
+spell_config_option_delete_option (const void *pointer, void *data,
+ struct t_config_file *config_file,
+ struct t_config_section *section,
+ struct t_config_option *option)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) config_file;
+ (void) section;
+
+ weechat_config_option_free (option);
+
+ weechat_hashtable_remove_all (spell_speller_buffer);
+ if (!spell_config_loading)
+ spell_speller_remove_unused ();
+
+ return WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED;
+}
+
+/*
+ * Callback called when an option is created in section "option".
+ */
+
+int
+spell_config_option_create_option (const void *pointer, void *data,
+ struct t_config_file *config_file,
+ struct t_config_section *section,
+ const char *option_name,
+ const char *value)
+{
+ struct t_config_option *ptr_option;
+ int rc;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+
+ rc = WEECHAT_CONFIG_OPTION_SET_ERROR;
+
+ if (option_name)
+ {
+ ptr_option = weechat_config_search_option (config_file, section,
+ option_name);
+ if (ptr_option)
+ {
+ if (value && value[0])
+ rc = weechat_config_option_set (ptr_option, value, 1);
+ else
+ {
+ weechat_config_option_free (ptr_option);
+ rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+ }
+ else
+ {
+ if (value && value[0])
+ {
+ ptr_option = weechat_config_new_option (
+ config_file, section,
+ option_name, "string",
+ _("option for aspell (for list of available options and "
+ "format, run command \"aspell config\" in a shell)"),
+ NULL, 0, 0, "", value, 0,
+ NULL, NULL, NULL,
+ &spell_config_option_change, NULL, NULL,
+ NULL, NULL, NULL);
+ rc = (ptr_option) ?
+ WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE : WEECHAT_CONFIG_OPTION_SET_ERROR;
+ }
+ else
+ rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+ }
+
+ if (rc == WEECHAT_CONFIG_OPTION_SET_ERROR)
+ {
+ weechat_printf (NULL,
+ _("%s%s: error creating spell option \"%s\" => \"%s\""),
+ weechat_prefix ("error"), SPELL_PLUGIN_NAME,
+ option_name, value);
+ }
+ else
+ {
+ weechat_hashtable_remove_all (spell_speller_buffer);
+ if (!spell_config_loading)
+ spell_speller_remove_unused ();
+ }
+
+ return rc;
+}
+
+/*
+ * Gets a list of dictionaries for a buffer.
+ */
+
+struct t_config_option *
+spell_config_get_dict (const char *name)
+{
+ return weechat_config_search_option (spell_config_file,
+ spell_config_section_dict,
+ name);
+}
+
+/*
+ * Sets a list of dictionaries for a buffer.
+ */
+
+int
+spell_config_set_dict (const char *name, const char *value)
+{
+ return spell_config_dict_create_option (NULL, NULL,
+ spell_config_file,
+ spell_config_section_dict,
+ name,
+ value);
+}
+
+/*
+ * Initializes spell configuration file.
+ *
+ * Returns:
+ * 1: OK
+ * 0: error
+ */
+
+int
+spell_config_init ()
+{
+ struct t_config_section *ptr_section;
+
+ spell_config_file = weechat_config_new (SPELL_CONFIG_NAME,
+ NULL, NULL, NULL);
+ if (!spell_config_file)
+ return 0;
+
+ /* color */
+ ptr_section = weechat_config_new_section (
+ spell_config_file, "color",
+ 0, 0,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (!ptr_section)
+ {
+ weechat_config_free (spell_config_file);
+ spell_config_file = NULL;
+ return 0;
+ }
+
+ spell_config_color_misspelled = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "misspelled", "color",
+ N_("text color for misspelled words (input bar)"),
+ NULL, 0, 0, "lightred", NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ spell_config_color_suggestion = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "suggestion", "color",
+ N_("text color for suggestion on a misspelled word in bar item "
+ "\"spell_suggest\""),
+ NULL, 0, 0, "default", NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ spell_config_color_suggestion_delimiter_dict = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "suggestion_delimiter_dict", "color",
+ N_("text color for delimiters displayed between two dictionaries "
+ "in bar item \"spell_suggest\""),
+ NULL, 0, 0, "cyan", NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ spell_config_color_suggestion_delimiter_word = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "suggestion_delimiter_word", "color",
+ N_("text color for delimiters displayed between two words in bar item "
+ "\"spell_suggest\""),
+ NULL, 0, 0, "cyan", NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+
+ /* check */
+ ptr_section = weechat_config_new_section (
+ spell_config_file, "check",
+ 0, 0,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (!ptr_section)
+ {
+ weechat_config_free (spell_config_file);
+ spell_config_file = NULL;
+ return 0;
+ }
+
+ spell_config_check_commands = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "commands", "string",
+ N_("comma separated list of commands for which spell checking is "
+ "enabled (spell checking is disabled for all other commands)"),
+ NULL, 0, 0,
+ "ame,amsg,away,command,cycle,kick,kickban,me,msg,notice,part,query,"
+ "quit,topic", NULL, 0,
+ NULL, NULL, NULL,
+ &spell_config_change_commands, NULL, NULL,
+ NULL, NULL, NULL);
+ spell_config_check_default_dict = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "default_dict", "string",
+ N_("default dictionary (or comma separated list of dictionaries) to "
+ "use when buffer has no dictionary defined (leave blank to disable "
+ "spell checker on buffers for which you didn't explicitly "
+ "enabled it)"),
+ NULL, 0, 0, "", NULL, 0,
+ NULL, NULL, NULL,
+ &spell_config_change_default_dict, NULL, NULL,
+ NULL, NULL, NULL);
+ spell_config_check_during_search = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "during_search", "boolean",
+ N_("check words during text search in buffer"),
+ NULL, 0, 0, "off", NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ spell_config_check_enabled = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "enabled", "boolean",
+ N_("enable spell checker for command line"),
+ NULL, 0, 0, "off", NULL, 0,
+ NULL, NULL, NULL,
+ &spell_config_change_enabled, NULL, NULL,
+ NULL, NULL, NULL);
+ spell_config_check_real_time = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "real_time", "boolean",
+ N_("real-time spell checking of words (slower, disabled by default: "
+ "words are checked only if there's delimiter after)"),
+ NULL, 0, 0, "off", NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ spell_config_check_suggestions = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "suggestions", "integer",
+ N_("number of suggestions to display in bar item \"spell_suggest\" "
+ "for each dictionary set in buffer (-1 = disable suggestions, "
+ "0 = display all possible suggestions in all languages)"),
+ NULL, -1, INT_MAX, "-1", NULL, 0,
+ NULL, NULL, NULL,
+ &spell_config_change_suggestions, NULL, NULL,
+ NULL, NULL, NULL);
+ spell_config_check_word_min_length = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "word_min_length", "integer",
+ N_("minimum length for a word to be spell checked (use 0 to check all "
+ "words)"),
+ NULL, 0, INT_MAX, "2", NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+
+ /* dict */
+ ptr_section = weechat_config_new_section (
+ spell_config_file, "dict",
+ 1, 1,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ &spell_config_dict_create_option, NULL, NULL,
+ &spell_config_dict_delete_option, NULL, NULL);
+ if (!ptr_section)
+ {
+ weechat_config_free (spell_config_file);
+ spell_config_file = NULL;
+ return 0;
+ }
+
+ spell_config_section_dict = ptr_section;
+
+ /* look */
+ ptr_section = weechat_config_new_section (
+ spell_config_file, "look",
+ 0, 0,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (!ptr_section)
+ {
+ weechat_config_free (spell_config_file);
+ spell_config_file = NULL;
+ return 0;
+ }
+
+ spell_config_look_suggestion_delimiter_dict = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "suggestion_delimiter_dict", "string",
+ N_("delimiter displayed between two dictionaries in bar item "
+ "\"spell_suggest\""),
+ NULL, 0, 0, " / ", NULL, 0,
+ NULL, NULL, NULL,
+ &spell_config_change_suggestions, NULL, NULL,
+ NULL, NULL, NULL);
+ spell_config_look_suggestion_delimiter_word = weechat_config_new_option (
+ spell_config_file, ptr_section,
+ "suggestion_delimiter_word", "string",
+ N_("delimiter displayed between two words in bar item "
+ "\"spell_suggest\""),
+ NULL, 0, 0, ",", NULL, 0,
+ NULL, NULL, NULL,
+ &spell_config_change_suggestions, NULL, NULL,
+ NULL, NULL, NULL);
+
+ /* option */
+ ptr_section = weechat_config_new_section (
+ spell_config_file, "option",
+ 1, 1,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ &spell_config_option_create_option, NULL, NULL,
+ &spell_config_option_delete_option, NULL, NULL);
+ if (!ptr_section)
+ {
+ weechat_config_free (spell_config_file);
+ spell_config_file = NULL;
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Reads spell configuration file.
+ */
+
+int
+spell_config_read ()
+{
+ int rc;
+
+ spell_config_loading = 1;
+ rc = weechat_config_read (spell_config_file);
+ spell_config_loading = 0;
+ if (rc == WEECHAT_CONFIG_READ_OK)
+ spell_config_change_commands (NULL, NULL, spell_config_check_commands);
+ spell_speller_remove_unused ();
+
+ return rc;
+}
+
+/*
+ * Writes spell configuration file.
+ */
+
+int
+spell_config_write ()
+{
+ return weechat_config_write (spell_config_file);
+}
+
+/*
+ * Frees spell configuration.
+ */
+
+void
+spell_config_free ()
+{
+ weechat_config_free (spell_config_file);
+
+ if (spell_commands_to_check)
+ weechat_string_free_split (spell_commands_to_check);
+ if (spell_length_commands_to_check)
+ free (spell_length_commands_to_check);
+}
diff --git a/src/plugins/spell/spell-config.h b/src/plugins/spell/spell-config.h
new file mode 100644
index 000000000..2e036e0a2
--- /dev/null
+++ b/src/plugins/spell/spell-config.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+ * Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEECHAT_PLUGIN_SPELL_CONFIG_H
+#define WEECHAT_PLUGIN_SPELL_CONFIG_H
+
+#define SPELL_CONFIG_NAME "spell"
+
+
+extern struct t_config_option *spell_config_color_misspelled;
+extern struct t_config_option *spell_config_color_suggestion;
+extern struct t_config_option *spell_config_color_suggestion_delimiter_dict;
+extern struct t_config_option *spell_config_color_suggestion_delimiter_word;
+
+extern struct t_config_option *spell_config_check_commands;
+extern struct t_config_option *spell_config_check_default_dict;
+extern struct t_config_option *spell_config_check_during_search;
+extern struct t_config_option *spell_config_check_enabled;
+extern struct t_config_option *spell_config_check_real_time;
+extern struct t_config_option *spell_config_check_suggestions;
+extern struct t_config_option *spell_config_check_word_min_length;
+
+extern struct t_config_option *spell_config_look_suggestion_delimiter_dict;
+extern struct t_config_option *spell_config_look_suggestion_delimiter_word;
+
+extern char **spell_commands_to_check;
+extern int spell_count_commands_to_check;
+extern int *spell_length_commands_to_check;
+
+extern struct t_config_option *spell_config_get_dict (const char *name);
+extern int spell_config_set_dict (const char *name, const char *value);
+extern int spell_config_init ();
+extern int spell_config_read ();
+extern int spell_config_write ();
+extern void spell_config_free ();
+
+#endif /* WEECHAT_PLUGIN_SPELL_CONFIG_H */
diff --git a/src/plugins/spell/spell-info.c b/src/plugins/spell/spell-info.c
new file mode 100644
index 000000000..27024c09a
--- /dev/null
+++ b/src/plugins/spell/spell-info.c
@@ -0,0 +1,89 @@
+/*
+ * spell-info.c - info for spell checker plugin
+ *
+ * Copyright (C) 2013-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../weechat-plugin.h"
+#include "spell.h"
+
+
+/*
+ * Returns spell info "spell_dict".
+ */
+
+const char *
+spell_info_info_spell_dict_cb (const void *pointer, void *data,
+ const char *info_name,
+ const char *arguments)
+{
+ int rc;
+ unsigned long value;
+ struct t_gui_buffer *buffer;
+ const char *buffer_full_name;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) info_name;
+
+ if (!arguments)
+ return NULL;
+
+ buffer_full_name = NULL;
+ if (strncmp (arguments, "0x", 2) == 0)
+ {
+ rc = sscanf (arguments, "%lx", &value);
+ if ((rc != EOF) && (rc != 0))
+ {
+ buffer = (struct t_gui_buffer *)value;
+ if (buffer)
+ {
+ buffer_full_name = weechat_buffer_get_string (buffer,
+ "full_name");
+ }
+ }
+ }
+ else
+ buffer_full_name = arguments;
+
+ if (buffer_full_name)
+ return spell_get_dict_with_buffer_name (buffer_full_name);
+
+ return NULL;
+}
+
+/*
+ * Hooks info for spell plugin.
+ */
+
+void
+spell_info_init ()
+{
+ /* info hooks */
+ weechat_hook_info (
+ "spell_dict",
+ N_("comma-separated list of dictionaries used in buffer"),
+ N_("buffer pointer (\"0x12345678\") or buffer full name "
+ "(\"irc.freenode.#weechat\")"),
+ &spell_info_info_spell_dict_cb, NULL, NULL);
+}
diff --git a/src/plugins/spell/spell-info.h b/src/plugins/spell/spell-info.h
new file mode 100644
index 000000000..e529236be
--- /dev/null
+++ b/src/plugins/spell/spell-info.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEECHAT_PLUGIN_SPELL_INFO_H
+#define WEECHAT_PLUGIN_SPELL_INFO_H
+
+extern void spell_info_init ();
+
+#endif /* WEECHAT_PLUGIN_SPELL_INFO_H */
diff --git a/src/plugins/spell/spell-speller.c b/src/plugins/spell/spell-speller.c
new file mode 100644
index 000000000..0b0ac5492
--- /dev/null
+++ b/src/plugins/spell/spell-speller.c
@@ -0,0 +1,486 @@
+/*
+ * spell-speller.c - speller management for spell checker plugin
+ *
+ * Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+ * Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "../weechat-plugin.h"
+#include "spell.h"
+#include "spell-speller.h"
+#include "spell-config.h"
+
+
+/*
+ * spellers: one by dictionary (key is name of dictionary (eg: "fr"), value is
+ * pointer on AspellSpeller)
+ */
+struct t_hashtable *spell_spellers = NULL;
+
+/*
+ * spellers by buffer (key is buffer pointer, value is pointer on
+ * struct t_spell_speller_buffer)
+ */
+struct t_hashtable *spell_speller_buffer = NULL;
+
+
+/*
+ * Checks if a spelling dictionary is supported (installed on system).
+ *
+ * Returns:
+ * 1: spell dict is supported
+ * 0: spell dict is NOT supported
+ */
+
+int
+spell_speller_dict_supported (const char *lang)
+{
+#ifdef USE_ENCHANT
+ return enchant_broker_dict_exists (broker, lang);
+#else
+ struct AspellConfig *config;
+ AspellDictInfoList *list;
+ AspellDictInfoEnumeration *elements;
+ const AspellDictInfo *dict;
+ int rc;
+
+ rc = 0;
+
+ config = new_aspell_config ();
+ list = get_aspell_dict_info_list (config);
+ elements = aspell_dict_info_list_elements (list);
+
+ while ((dict = aspell_dict_info_enumeration_next (elements)) != NULL)
+ {
+ if (strcmp (dict->name, lang) == 0)
+ {
+ rc = 1;
+ break;
+ }
+ }
+
+ delete_aspell_dict_info_enumeration (elements);
+ delete_aspell_config (config);
+
+ return rc;
+#endif /* USE_ENCHANT */
+}
+
+/*
+ * Checks if dictionaries are valid (called when user creates/changes
+ * dictionaries for a buffer).
+ *
+ * An error is displayed for each invalid dictionary found.
+ */
+
+void
+spell_speller_check_dictionaries (const char *dict_list)
+{
+ char **argv;
+ int argc, i;
+
+ if (dict_list)
+ {
+ argv = weechat_string_split (dict_list, ",", 0, 0, &argc);
+ if (argv)
+ {
+ for (i = 0; i < argc; i++)
+ {
+ if (!spell_speller_dict_supported (argv[i]))
+ {
+ weechat_printf (NULL,
+ _("%s: warning: dictionary \"%s\" is not "
+ "available on your system"),
+ SPELL_PLUGIN_NAME, argv[i]);
+ }
+ }
+ weechat_string_free_split (argv);
+ }
+ }
+}
+
+/*
+ * Creates and adds a new speller instance in the hashtable.
+ *
+ * Returns pointer to new speller, NULL if error.
+ */
+
+#ifdef USE_ENCHANT
+EnchantDict *
+#else
+AspellSpeller *
+#endif /* USE_ENCHANT */
+spell_speller_new (const char *lang)
+{
+#ifdef USE_ENCHANT
+ EnchantDict *new_speller;
+#else
+ AspellConfig *config;
+ AspellCanHaveError *ret;
+ AspellSpeller *new_speller;
+#endif /* USE_ENCHANT */
+ struct t_infolist *infolist;
+
+ if (!lang)
+ return NULL;
+
+ if (weechat_spell_plugin->debug)
+ {
+ weechat_printf (NULL,
+ "%s: creating new speller for lang \"%s\"",
+ SPELL_PLUGIN_NAME, lang);
+ }
+
+#ifdef USE_ENCHANT
+ new_speller = enchant_broker_request_dict (broker, lang);
+ if (!new_speller)
+ {
+ weechat_printf (NULL,
+ _("%s%s: error: unable to create speller for lang \"%s\""),
+ weechat_prefix ("error"), SPELL_PLUGIN_NAME,
+ lang);
+ return NULL;
+ }
+#else
+ /* create a speller instance for the newly created cell */
+ config = new_aspell_config ();
+ aspell_config_replace (config, "lang", lang);
+#endif /* USE_ENCHANT */
+
+ /* apply all options */
+ infolist = weechat_infolist_get ("option", NULL, "spell.option.*");
+ if (infolist)
+ {
+ while (weechat_infolist_next (infolist))
+ {
+#ifdef USE_ENCHANT
+ /* TODO: set option with enchant */
+#else
+ aspell_config_replace (config,
+ weechat_infolist_string (infolist, "option_name"),
+ weechat_infolist_string (infolist, "value"));
+#endif /* USE_ENCHANT */
+ }
+ weechat_infolist_free (infolist);
+ }
+
+#ifndef USE_ENCHANT
+ ret = new_aspell_speller (config);
+
+ if (aspell_error (ret) != 0)
+ {
+ weechat_printf (NULL,
+ "%s%s: error: %s",
+ weechat_prefix ("error"), SPELL_PLUGIN_NAME,
+ aspell_error_message (ret));
+ delete_aspell_config (config);
+ delete_aspell_can_have_error (ret);
+ return NULL;
+ }
+
+ new_speller = to_aspell_speller (ret);
+#endif /* USE_ENCHANT */
+
+ weechat_hashtable_set (spell_spellers, lang, new_speller);
+
+#ifndef USE_ENCHANT
+ /* free configuration */
+ delete_aspell_config (config);
+#endif /* USE_ENCHANT */
+
+ return new_speller;
+}
+
+/*
+ * Creates hashtable entries with a string containing a list of dicts.
+ */
+
+void
+spell_speller_add_dicts_to_hash (struct t_hashtable *hashtable,
+ const char *dict)
+{
+ char **dicts;
+ int num_dicts, i;
+
+ if (!dict || !dict[0])
+ return;
+
+ dicts = weechat_string_split (dict, ",", 0, 0, &num_dicts);
+ if (dicts)
+ {
+ for (i = 0; i < num_dicts; i++)
+ {
+ weechat_hashtable_set (hashtable, dicts[i], NULL);
+ }
+ weechat_string_free_split (dicts);
+ }
+}
+
+/*
+ * Removes a speller if it is NOT in hashtable "used_spellers".
+ */
+
+void
+spell_speller_remove_unused_cb (void *data,
+ struct t_hashtable *hashtable,
+ const void *key, const void *value)
+{
+ struct t_hashtable *used_spellers;
+
+ /* make C compiler happy */
+ (void) value;
+
+ used_spellers = (struct t_hashtable *)data;
+
+ /* if speller is not in "used_spellers", remove it (not used any more) */
+ if (!weechat_hashtable_has_key (used_spellers, key))
+ weechat_hashtable_remove (hashtable, key);
+}
+
+/*
+ * Removes unused spellers from hashtable "spell_spellers".
+ */
+
+void
+spell_speller_remove_unused ()
+{
+ struct t_hashtable *used_spellers;
+ struct t_infolist *infolist;
+
+ if (weechat_spell_plugin->debug)
+ {
+ weechat_printf (NULL,
+ "%s: removing unused spellers",
+ SPELL_PLUGIN_NAME);
+ }
+
+ /* create a hashtable that will contain all used spellers */
+ used_spellers = weechat_hashtable_new (32,
+ WEECHAT_HASHTABLE_STRING,
+ WEECHAT_HASHTABLE_STRING,
+ NULL, NULL);
+ if (!used_spellers)
+ return;
+
+ /* collect used spellers and store them in hashtable "used_spellers" */
+ spell_speller_add_dicts_to_hash (used_spellers,
+ weechat_config_string (spell_config_check_default_dict));
+ infolist = weechat_infolist_get ("option", NULL, "spell.dict.*");
+ if (infolist)
+ {
+ while (weechat_infolist_next (infolist))
+ {
+ spell_speller_add_dicts_to_hash (used_spellers,
+ weechat_infolist_string (infolist, "value"));
+ }
+ weechat_infolist_free (infolist);
+ }
+
+ /*
+ * look at current spellers, and remove spellers that are not in hashtable
+ * "used_spellers"
+ */
+ weechat_hashtable_map (spell_spellers,
+ &spell_speller_remove_unused_cb,
+ used_spellers);
+
+ weechat_hashtable_free (used_spellers);
+}
+
+/*
+ * Callback called when a key is removed in hashtable "spell_spellers".
+ */
+
+void
+spell_speller_free_value_cb (struct t_hashtable *hashtable,
+ const void *key, void *value)
+{
+#ifdef USE_ENCHANT
+ EnchantDict *ptr_speller;
+#else
+ AspellSpeller *ptr_speller;
+#endif /* USE_ENCHANT */
+
+ /* make C compiler happy */
+ (void) hashtable;
+
+ if (weechat_spell_plugin->debug)
+ {
+ weechat_printf (NULL,
+ "%s: removing speller for lang \"%s\"",
+ SPELL_PLUGIN_NAME, (const char *)key);
+ }
+
+ /* free speller */
+#ifdef USE_ENCHANT
+ ptr_speller = (EnchantDict *)value;
+ enchant_broker_free_dict (broker, ptr_speller);
+#else
+ ptr_speller = (AspellSpeller *)value;
+ aspell_speller_save_all_word_lists (ptr_speller);
+ delete_aspell_speller (ptr_speller);
+#endif /* USE_ENCHANT */
+}
+
+/*
+ * Creates a structure for buffer speller info in hashtable
+ * "spell_buffer_spellers".
+ */
+
+struct t_spell_speller_buffer *
+spell_speller_buffer_new (struct t_gui_buffer *buffer)
+{
+ const char *buffer_dicts;
+ char **dicts;
+ int num_dicts, i;
+ struct t_spell_speller_buffer *new_speller_buffer;
+#ifdef USE_ENCHANT
+ EnchantDict *ptr_speller;
+#else
+ AspellSpeller *ptr_speller;
+#endif /* USE_ENCHANT */
+
+ if (!buffer)
+ return NULL;
+
+ weechat_hashtable_remove (spell_speller_buffer, buffer);
+
+ new_speller_buffer = malloc (sizeof (*new_speller_buffer));
+ if (!new_speller_buffer)
+ return NULL;
+
+ new_speller_buffer->spellers = NULL;
+ new_speller_buffer->modifier_string = NULL;
+ new_speller_buffer->input_pos = -1;
+ new_speller_buffer->modifier_result = NULL;
+
+ buffer_dicts = spell_get_dict (buffer);
+ if (buffer_dicts)
+ {
+ dicts = weechat_string_split (buffer_dicts, ",", 0, 0, &num_dicts);
+ if (dicts && (num_dicts > 0))
+ {
+ new_speller_buffer->spellers =
+#ifdef USE_ENCHANT
+ malloc ((num_dicts + 1) * sizeof (EnchantDict *));
+#else
+ malloc ((num_dicts + 1) * sizeof (AspellSpeller *));
+#endif /* USE_ENCHANT */
+ if (new_speller_buffer->spellers)
+ {
+ for (i = 0; i < num_dicts; i++)
+ {
+ ptr_speller = weechat_hashtable_get (spell_spellers,
+ dicts[i]);
+ if (!ptr_speller)
+ ptr_speller = spell_speller_new (dicts[i]);
+ new_speller_buffer->spellers[i] = ptr_speller;
+ }
+ new_speller_buffer->spellers[num_dicts] = NULL;
+ }
+ }
+ if (dicts)
+ weechat_string_free_split (dicts);
+ }
+
+ weechat_hashtable_set (spell_speller_buffer,
+ buffer,
+ new_speller_buffer);
+
+ weechat_bar_item_update ("spell_dict");
+
+ return new_speller_buffer;
+}
+
+/*
+ * Callback called when a key is removed in hashtable
+ * "spell_speller_buffer".
+ */
+
+void
+spell_speller_buffer_free_value_cb (struct t_hashtable *hashtable,
+ const void *key, void *value)
+{
+ struct t_spell_speller_buffer *ptr_speller_buffer;
+
+ /* make C compiler happy */
+ (void) hashtable;
+ (void) key;
+
+ ptr_speller_buffer = (struct t_spell_speller_buffer *)value;
+
+ if (ptr_speller_buffer->spellers)
+ free (ptr_speller_buffer->spellers);
+ if (ptr_speller_buffer->modifier_string)
+ free (ptr_speller_buffer->modifier_string);
+ if (ptr_speller_buffer->modifier_result)
+ free (ptr_speller_buffer->modifier_result);
+
+ free (ptr_speller_buffer);
+}
+
+/*
+ * Initializes spellers (creates hashtables).
+ *
+ * Returns:
+ * 1: OK (hashtables created)
+ * 0: error (not enough memory)
+ */
+
+int
+spell_speller_init ()
+{
+ spell_spellers = weechat_hashtable_new (32,
+ WEECHAT_HASHTABLE_STRING,
+ WEECHAT_HASHTABLE_POINTER,
+ NULL, NULL);
+ if (!spell_spellers)
+ return 0;
+ weechat_hashtable_set_pointer (spell_spellers,
+ "callback_free_value",
+ &spell_speller_free_value_cb);
+
+ spell_speller_buffer = weechat_hashtable_new (32,
+ WEECHAT_HASHTABLE_POINTER,
+ WEECHAT_HASHTABLE_POINTER,
+ NULL, NULL);
+ if (!spell_speller_buffer)
+ {
+ weechat_hashtable_free (spell_spellers);
+ return 0;
+ }
+ weechat_hashtable_set_pointer (spell_speller_buffer,
+ "callback_free_value",
+ &spell_speller_buffer_free_value_cb);
+
+ return 1;
+}
+
+/*
+ * Ends spellers (removes hashtables).
+ */
+
+void
+spell_speller_end ()
+{
+ weechat_hashtable_free (spell_spellers);
+ weechat_hashtable_free (spell_speller_buffer);
+}
diff --git a/src/plugins/spell/spell-speller.h b/src/plugins/spell/spell-speller.h
new file mode 100644
index 000000000..2d47d7e24
--- /dev/null
+++ b/src/plugins/spell/spell-speller.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+ * Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEECHAT_PLUGIN_SPELL_SPELLER_H
+#define WEECHAT_PLUGIN_SPELL_SPELLER_H
+
+struct t_spell_speller_buffer
+{
+#ifdef USE_ENCHANT
+ EnchantDict **spellers; /* enchant spellers for buffer */
+#else
+ AspellSpeller **spellers; /* aspell spellers for buffer */
+#endif /* USE_ENCHANT */
+ char *modifier_string; /* last modifier string */
+ int input_pos; /* position of cursor in input */
+ char *modifier_result; /* last modifier result */
+};
+
+extern struct t_hashtable *spell_spellers;
+extern struct t_hashtable *spell_speller_buffer;
+
+extern int spell_speller_dict_supported (const char *lang);
+extern void spell_speller_check_dictionaries (const char *dict_list);
+#ifdef USE_ENCHANT
+extern EnchantDict *spell_speller_new (const char *lang);
+#else
+extern AspellSpeller *spell_speller_new (const char *lang);
+#endif /* USE_ENCHANT */
+extern void spell_speller_remove_unused ();
+extern struct t_spell_speller_buffer *spell_speller_buffer_new (struct t_gui_buffer *buffer);
+extern int spell_speller_init ();
+extern void spell_speller_end ();
+
+#endif /* WEECHAT_PLUGIN_SPELL_SPELLER_H */
diff --git a/src/plugins/spell/spell.c b/src/plugins/spell/spell.c
new file mode 100644
index 000000000..b39e5ce54
--- /dev/null
+++ b/src/plugins/spell/spell.c
@@ -0,0 +1,1167 @@
+/*
+ * spell.c - spell checker plugin for WeeChat
+ *
+ * Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+ * Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+ * Copyright (C) 2012 Nils Görs <weechatter@arcor.de>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <wctype.h>
+
+#include "../weechat-plugin.h"
+#include "spell.h"
+#include "spell-bar-item.h"
+#include "spell-command.h"
+#include "spell-completion.h"
+#include "spell-config.h"
+#include "spell-info.h"
+#include "spell-speller.h"
+
+
+WEECHAT_PLUGIN_NAME(SPELL_PLUGIN_NAME);
+WEECHAT_PLUGIN_DESCRIPTION(N_("Spell checker for input"));
+WEECHAT_PLUGIN_AUTHOR("Sébastien Helleu <flashcode@flashtux.org>");
+WEECHAT_PLUGIN_VERSION(WEECHAT_VERSION);
+WEECHAT_PLUGIN_LICENSE(WEECHAT_LICENSE);
+WEECHAT_PLUGIN_PRIORITY(11000);
+
+struct t_weechat_plugin *weechat_spell_plugin = NULL;
+
+int spell_enabled = 0;
+
+char *spell_nick_completer = NULL;
+int spell_len_nick_completer = 0;
+
+#ifdef USE_ENCHANT
+EnchantBroker *broker = NULL;
+#endif /* USE_ENCHANT */
+
+/*
+ * aspell supported languages, updated on 2012-07-05
+ * URL: ftp://ftp.gnu.org/gnu/aspell/dict/0index.html
+ */
+
+struct t_spell_code spell_langs[] =
+{
+ { "af", "Afrikaans" },
+ { "am", "Amharic" },
+ { "ar", "Arabic" },
+ { "ast", "Asturian" },
+ { "az", "Azerbaijani" },
+ { "be", "Belarusian" },
+ { "bg", "Bulgarian" },
+ { "bn", "Bengali" },
+ { "br", "Breton" },
+ { "ca", "Catalan" },
+ { "cs", "Czech" },
+ { "csb", "Kashubian" },
+ { "cy", "Welsh" },
+ { "da", "Danish" },
+ { "de", "German" },
+ { "de-alt", "German - Old Spelling" },
+ { "el", "Greek" },
+ { "en", "English" },
+ { "eo", "Esperanto" },
+ { "es", "Spanish" },
+ { "et", "Estonian" },
+ { "fa", "Persian" },
+ { "fi", "Finnish" },
+ { "fo", "Faroese" },
+ { "fr", "French" },
+ { "fy", "Frisian" },
+ { "ga", "Irish" },
+ { "gd", "Scottish Gaelic" },
+ { "gl", "Galician" },
+ { "grc", "Ancient Greek" },
+ { "gu", "Gujarati" },
+ { "gv", "Manx Gaelic" },
+ { "he", "Hebrew" },
+ { "hi", "Hindi" },
+ { "hil", "Hiligaynon" },
+ { "hr", "Croatian" },
+ { "hsb", "Upper Sorbian" },
+ { "hu", "Hungarian" },
+ { "hus", "Huastec" },
+ { "hy", "Armenian" },
+ { "ia", "Interlingua" },
+ { "id", "Indonesian" },
+ { "is", "Icelandic" },
+ { "it", "Italian" },
+ { "kn", "Kannada" },
+ { "ku", "Kurdi" },
+ { "ky", "Kirghiz" },
+ { "la", "Latin" },
+ { "lt", "Lithuanian" },
+ { "lv", "Latvian" },
+ { "mg", "Malagasy" },
+ { "mi", "Maori" },
+ { "mk", "Macedonian" },
+ { "ml", "Malayalam" },
+ { "mn", "Mongolian" },
+ { "mr", "Marathi" },
+ { "ms", "Malay" },
+ { "mt", "Maltese" },
+ { "nb", "Norwegian Bokmal" },
+ { "nds", "Low Saxon" },
+ { "nl", "Dutch" },
+ { "nn", "Norwegian Nynorsk" },
+ { "ny", "Chichewa" },
+ { "or", "Oriya" },
+ { "pa", "Punjabi" },
+ { "pl", "Polish" },
+ { "pt_BR", "Brazilian Portuguese" },
+ { "pt_PT", "Portuguese" },
+ { "qu", "Quechua" },
+ { "ro", "Romanian" },
+ { "ru", "Russian" },
+ { "rw", "Kinyarwanda" },
+ { "sc", "Sardinian" },
+ { "sk", "Slovak" },
+ { "sl", "Slovenian" },
+ { "sr", "Serbian" },
+ { "sv", "Swedish" },
+ { "sw", "Swahili" },
+ { "ta", "Tamil" },
+ { "te", "Telugu" },
+ { "tet", "Tetum" },
+ { "tk", "Turkmen" },
+ { "tl", "Tagalog" },
+ { "tn", "Setswana" },
+ { "tr", "Turkish" },
+ { "uk", "Ukrainian" },
+ { "uz", "Uzbek" },
+ { "vi", "Vietnamese" },
+ { "wa", "Walloon" },
+ { "yi", "Yiddish" },
+ { "zu", "Zulu" },
+ { NULL, NULL}
+};
+
+struct t_spell_code spell_countries[] =
+{
+ { "AT", "Austria" },
+ { "BR", "Brazil" },
+ { "CA", "Canada" },
+ { "CH", "Switzerland" },
+ { "DE", "Germany" },
+ { "FR", "France" },
+ { "GB", "Great Britain" },
+ { "PT", "Portugal" },
+ { "SK", "Slovakia" },
+ { "US", "United States of America" },
+ { NULL, NULL}
+};
+
+char *spell_url_prefix[] =
+{ "http:", "https:", "ftp:", "tftp:", "ftps:", "ssh:", "fish:", "dict:",
+ "ldap:", "file:", "telnet:", "gopher:", "irc:", "ircs:", "irc6:", "irc6s:",
+ "cvs:", "svn:", "svn+ssh:", "git:", NULL };
+
+
+/*
+ * Builds full name of buffer.
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+spell_build_option_name (struct t_gui_buffer *buffer)
+{
+ const char *plugin_name, *name;
+ char *option_name;
+ int length;
+
+ if (!buffer)
+ return NULL;
+
+ plugin_name = weechat_buffer_get_string (buffer, "plugin");
+ name = weechat_buffer_get_string (buffer, "name");
+
+ length = strlen (plugin_name) + 1 + strlen (name) + 1;
+ option_name = malloc (length);
+ if (!option_name)
+ return NULL;
+
+ snprintf (option_name, length, "%s.%s", plugin_name, name);
+
+ return option_name;
+}
+
+/*
+ * Gets dictionary list for a name of buffer.
+ *
+ * First tries with all arguments, then removes one by one to find dict (from
+ * specific to general dict).
+ */
+
+const char *
+spell_get_dict_with_buffer_name (const char *name)
+{
+ char *option_name, *ptr_end;
+ struct t_config_option *ptr_option;
+
+ if (!name)
+ return NULL;
+
+ option_name = strdup (name);
+ if (option_name)
+ {
+ ptr_end = option_name + strlen (option_name);
+ while (ptr_end >= option_name)
+ {
+ ptr_option = spell_config_get_dict (option_name);
+ if (ptr_option)
+ {
+ free (option_name);
+ return weechat_config_string (ptr_option);
+ }
+ ptr_end--;
+ while ((ptr_end >= option_name) && (ptr_end[0] != '.'))
+ {
+ ptr_end--;
+ }
+ if ((ptr_end >= option_name) && (ptr_end[0] == '.'))
+ ptr_end[0] = '\0';
+ }
+ ptr_option = spell_config_get_dict (option_name);
+
+ free (option_name);
+
+ if (ptr_option)
+ return weechat_config_string (ptr_option);
+ }
+
+ /* nothing found => return default dictionary (if set) */
+ if (weechat_config_string (spell_config_check_default_dict)
+ && weechat_config_string (spell_config_check_default_dict)[0])
+ {
+ return weechat_config_string (spell_config_check_default_dict);
+ }
+
+ /* no default dictionary set */
+ return NULL;
+}
+
+/*
+ * Gets dictionary list for a buffer.
+ *
+ * First tries with all arguments, then removes one by one to find dict (from
+ * specific to general dict).
+ */
+
+const char *
+spell_get_dict (struct t_gui_buffer *buffer)
+{
+ char *name;
+ const char *dict;
+
+ name = spell_build_option_name (buffer);
+ if (!name)
+ return NULL;
+
+ dict = spell_get_dict_with_buffer_name (name);
+
+ free (name);
+
+ return dict;
+}
+
+/*
+ * Checks if command is authorized for spell checking.
+ *
+ * Returns:
+ * 1: command authorized
+ * 0: command not authorized
+ */
+
+int
+spell_command_authorized (const char *command)
+{
+ int length_command, i;
+
+ if (!command)
+ return 1;
+
+ length_command = strlen (command);
+
+ for (i = 0; i < spell_count_commands_to_check; i++)
+ {
+ if ((spell_length_commands_to_check[i] == length_command)
+ && (weechat_strcasecmp (command,
+ spell_commands_to_check[i]) == 0))
+ {
+ /* command is authorized */
+ return 1;
+ }
+ }
+
+ /* command is not authorized */
+ return 0;
+}
+
+/*
+ * Checks if a word is an URL.
+ *
+ * Returns:
+ * 1: word is an URL
+ * 0: word is not an URL
+ */
+
+int
+spell_string_is_url (const char *word)
+{
+ int i;
+
+ for (i = 0; spell_url_prefix[i]; i++)
+ {
+ if (weechat_strncasecmp (word, spell_url_prefix[i],
+ strlen (spell_url_prefix[i])) == 0)
+ return 1;
+ }
+
+ /* word is not an URL */
+ return 0;
+}
+
+/*
+ * Checks if a word is a nick of nicklist.
+ *
+ * Returns:
+ * 1: word is a nick of nicklist
+ * 0: word is not a nick of nicklist
+ */
+
+int
+spell_string_is_nick (struct t_gui_buffer *buffer, const char *word)
+{
+ char *pos, *pos_nick_completer, *pos_space, saved_char;
+ const char *buffer_type, *buffer_nick, *buffer_channel;
+ int rc;
+
+ pos_nick_completer = (spell_nick_completer) ?
+ strstr (word, spell_nick_completer) : NULL;
+ pos_space = strchr (word, ' ');
+
+ pos = NULL;
+ if (pos_nick_completer && pos_space)
+ {
+ if ((pos_nick_completer < pos_space)
+ && (pos_nick_completer + spell_len_nick_completer == pos_space))
+ {
+ pos = pos_nick_completer;
+ }
+ else
+ pos = pos_space;
+ }
+ else
+ {
+ pos = (pos_nick_completer
+ && !pos_nick_completer[spell_len_nick_completer]) ?
+ pos_nick_completer : pos_space;
+ }
+
+ if (pos)
+ {
+ saved_char = pos[0];
+ pos[0] = '\0';
+ }
+
+ rc = (weechat_nicklist_search_nick (buffer, NULL, word)) ? 1 : 0;
+
+ if (!rc)
+ {
+ /* for "private" buffers, check if word is self or remote nick */
+ buffer_type = weechat_buffer_get_string (buffer, "localvar_type");
+ if (buffer_type && (strcmp (buffer_type, "private") == 0))
+ {
+ /* check self nick */
+ buffer_nick = weechat_buffer_get_string (buffer, "localvar_nick");
+ if (buffer_nick && (weechat_strcasecmp (buffer_nick, word) == 0))
+ {
+ rc = 1;
+ }
+ else
+ {
+ /* check remote nick */
+ buffer_channel = weechat_buffer_get_string (buffer,
+ "localvar_channel");
+ if (buffer_channel
+ && (weechat_strcasecmp (buffer_channel, word) == 0))
+ {
+ rc = 1;
+ }
+ }
+ }
+ }
+
+ if (pos)
+ pos[0] = saved_char;
+
+ return rc;
+}
+
+/*
+ * Checks if a word is made of digits and punctuation.
+ *
+ * Returns:
+ * 1: word has only digits and punctuation
+ * 0: word has some other chars (not digits neither punctuation)
+ */
+
+int
+spell_string_is_simili_number (const char *word)
+{
+ int code_point;
+
+ if (!word || !word[0])
+ return 0;
+
+ while (word && word[0])
+ {
+ code_point = weechat_utf8_char_int (word);
+ if (!iswpunct (code_point) && !iswdigit (code_point))
+ return 0;
+ word = weechat_utf8_next_char (word);
+ }
+
+ /* there are only digits or punctuation */
+ return 1;
+}
+
+/*
+ * Spell checks a word.
+ *
+ * Returns:
+ * 1: word is OK
+ * 0: word is misspelled
+ */
+
+int
+spell_check_word (struct t_spell_speller_buffer *speller_buffer,
+ const char *word)
+{
+ int i;
+
+ /* word too small? then do not check word */
+ if ((weechat_config_integer (spell_config_check_word_min_length) > 0)
+ && ((int)strlen (word) < weechat_config_integer (spell_config_check_word_min_length)))
+ return 1;
+
+ /* word is a number? then do not check word */
+ if (spell_string_is_simili_number (word))
+ return 1;
+
+ /* check word with all spellers (order is important) */
+ if (speller_buffer->spellers)
+ {
+ for (i = 0; speller_buffer->spellers[i]; i++)
+ {
+#ifdef USE_ENCHANT
+ if (enchant_dict_check (speller_buffer->spellers[i], word, strlen (word)) == 0)
+#else
+ if (aspell_speller_check (speller_buffer->spellers[i], word, -1) == 1)
+#endif /* USE_ENCHANT */
+ return 1;
+ }
+ }
+
+ /* misspelled word! */
+ return 0;
+}
+
+/*
+ * Gets suggestions for a word.
+ *
+ * Returns a string with format: "suggest1,suggest2,suggest3".
+ *
+ * Note: result must be freed after use (if not NULL).
+ */
+
+char *
+spell_get_suggestions (struct t_spell_speller_buffer *speller_buffer,
+ const char *word)
+{
+ int i, size, max_suggestions, num_suggestions;
+ char *suggestions, *suggestions2;
+ const char *ptr_word;
+#ifdef USE_ENCHANT
+ char **elements;
+ size_t num_elements;
+#else
+ const AspellWordList *list;
+ AspellStringEnumeration *elements;
+#endif /* USE_ENCHANT */
+
+ max_suggestions = weechat_config_integer (spell_config_check_suggestions);
+ if (max_suggestions < 0)
+ return NULL;
+
+ size = 1;
+ suggestions = malloc (size);
+ if (!suggestions)
+ return NULL;
+
+ suggestions[0] = '\0';
+ if (speller_buffer->spellers)
+ {
+ for (i = 0; speller_buffer->spellers[i]; i++)
+ {
+#ifdef USE_ENCHANT
+ elements = enchant_dict_suggest (speller_buffer->spellers[i], word,
+ -1, &num_elements);
+ if (elements)
+ {
+ if (num_elements > 0)
+ {
+ num_suggestions = 0;
+ while ((ptr_word = elements[num_suggestions]) != NULL)
+ {
+ size += strlen (ptr_word) + ((suggestions[0]) ? 1 : 0);
+ suggestions2 = realloc (suggestions, size);
+ if (!suggestions2)
+ {
+ free (suggestions);
+ enchant_dict_free_string_list (speller_buffer->spellers[i],
+ elements);
+ return NULL;
+ }
+ suggestions = suggestions2;
+ if (suggestions[0])
+ strcat (suggestions, (num_suggestions == 0) ? "/" : ",");
+ strcat (suggestions, ptr_word);
+ num_suggestions++;
+ if (num_suggestions == max_suggestions)
+ break;
+ }
+ }
+ enchant_dict_free_string_list (speller_buffer->spellers[i], elements);
+ }
+#else
+ list = aspell_speller_suggest (speller_buffer->spellers[i], word, -1);
+ if (list)
+ {
+ elements = aspell_word_list_elements (list);
+ num_suggestions = 0;
+ while ((ptr_word = aspell_string_enumeration_next (elements)) != NULL)
+ {
+ size += strlen (ptr_word) + ((suggestions[0]) ? 1 : 0);
+ suggestions2 = realloc (suggestions, size);
+ if (!suggestions2)
+ {
+ free (suggestions);
+ delete_aspell_string_enumeration (elements);
+ return NULL;
+ }
+ suggestions = suggestions2;
+ if (suggestions[0])
+ strcat (suggestions, (num_suggestions == 0) ? "/" : ",");
+ strcat (suggestions, ptr_word);
+ num_suggestions++;
+ if (num_suggestions == max_suggestions)
+ break;
+ }
+ delete_aspell_string_enumeration (elements);
+ }
+#endif /* USE_ENCHANT */
+ }
+ }
+
+ /* no suggestions found */
+ if (!suggestions[0])
+ {
+ free (suggestions);
+ return NULL;
+ }
+
+ return suggestions;
+}
+
+/*
+ * Updates input text by adding color for misspelled words.
+ */
+
+char *
+spell_modifier_cb (const void *pointer, void *data,
+ const char *modifier,
+ const char *modifier_data, const char *string)
+{
+ unsigned long value;
+ struct t_gui_buffer *buffer;
+ struct t_spell_speller_buffer *ptr_speller_buffer;
+ char *result, *ptr_string, *ptr_string_orig, *pos_space;
+ char *ptr_end, *ptr_end_valid, save_end;
+ char *misspelled_word, *old_misspelled_word, *old_suggestions, *suggestions;
+ char *word_and_suggestions;
+ const char *color_normal, *color_error, *ptr_suggestions, *pos_colon;
+ int code_point, char_size;
+ int length, index_result, length_word, word_ok;
+ int length_color_normal, length_color_error, rc;
+ int input_pos, current_pos, word_start_pos, word_end_pos, word_end_pos_valid;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) modifier;
+
+ if (!spell_enabled)
+ return NULL;
+
+ if (!string)
+ return NULL;
+
+ rc = sscanf (modifier_data, "%lx", &value);
+ if ((rc == EOF) || (rc == 0))
+ return NULL;
+
+ buffer = (struct t_gui_buffer *)value;
+
+ /* check text during search only if option is enabled */
+ if (weechat_buffer_get_integer (buffer, "text_search")
+ && !weechat_config_boolean (spell_config_check_during_search))
+ return NULL;
+
+ /* get structure with speller info for buffer */
+ ptr_speller_buffer = weechat_hashtable_get (spell_speller_buffer,
+ buffer);
+ if (!ptr_speller_buffer)
+ {
+ ptr_speller_buffer = spell_speller_buffer_new (buffer);
+ if (!ptr_speller_buffer)
+ return NULL;
+ }
+ if (!ptr_speller_buffer->spellers)
+ return NULL;
+
+ /*
+ * for performance: return last string built if input string is the
+ * same (and cursor position is the same, if suggestions are enabled)
+ */
+ input_pos = weechat_buffer_get_integer (buffer, "input_pos");
+ if (ptr_speller_buffer->modifier_string
+ && (strcmp (string, ptr_speller_buffer->modifier_string) == 0)
+ && ((weechat_config_integer (spell_config_check_suggestions) < 0)
+ || (input_pos == ptr_speller_buffer->input_pos)))
+ {
+ return (ptr_speller_buffer->modifier_result) ?
+ strdup (ptr_speller_buffer->modifier_result) : NULL;
+ }
+
+ /* free last modifier string and result */
+ if (ptr_speller_buffer->modifier_string)
+ {
+ free (ptr_speller_buffer->modifier_string);
+ ptr_speller_buffer->modifier_string = NULL;
+ }
+ if (ptr_speller_buffer->modifier_result)
+ {
+ free (ptr_speller_buffer->modifier_result);
+ ptr_speller_buffer->modifier_result = NULL;
+ }
+
+ misspelled_word = NULL;
+
+ /* save last modifier string received */
+ ptr_speller_buffer->modifier_string = strdup (string);
+ ptr_speller_buffer->input_pos = input_pos;
+
+ color_normal = weechat_color ("bar_fg");
+ length_color_normal = strlen (color_normal);
+ color_error = weechat_color (weechat_config_string (spell_config_color_misspelled));
+ length_color_error = strlen (color_error);
+
+ length = strlen (string);
+ result = malloc (length + (length * length_color_error) + 1);
+
+ if (result)
+ {
+ result[0] = '\0';
+
+ ptr_string = ptr_speller_buffer->modifier_string;
+ index_result = 0;
+
+ /* check if string is a command */
+ if (!weechat_string_input_for_buffer (ptr_string))
+ {
+ char_size = weechat_utf8_char_size (ptr_string);
+ ptr_string += char_size;
+ pos_space = ptr_string;
+ while (pos_space && pos_space[0] && (pos_space[0] != ' '))
+ {
+ pos_space = (char *)weechat_utf8_next_char (pos_space);
+ }
+ if (!pos_space || !pos_space[0])
+ {
+ free (result);
+ return NULL;
+ }
+
+ pos_space[0] = '\0';
+
+ /* exit if command is not authorized for spell checking */
+ if (!spell_command_authorized (ptr_string))
+ {
+ free (result);
+ return NULL;
+ }
+ memcpy (result + index_result,
+ ptr_speller_buffer->modifier_string,
+ char_size);
+ index_result += char_size;
+ strcpy (result + index_result, ptr_string);
+ index_result += strlen (ptr_string);
+
+ pos_space[0] = ' ';
+ ptr_string = pos_space;
+ }
+
+ current_pos = 0;
+ while (ptr_string[0])
+ {
+ ptr_string_orig = NULL;
+
+ /* find start of word: it must start with an alphanumeric char */
+ code_point = weechat_utf8_char_int (ptr_string);
+ while ((!iswalnum (code_point)) || iswspace (code_point))
+ {
+ if (!ptr_string_orig && !iswspace (code_point))
+ ptr_string_orig = ptr_string;
+ char_size = weechat_utf8_char_size (ptr_string);
+ memcpy (result + index_result, ptr_string, char_size);
+ index_result += char_size;
+ ptr_string += char_size;
+ current_pos++;
+ if (!ptr_string[0])
+ break;
+ code_point = weechat_utf8_char_int (ptr_string);
+ }
+ if (!ptr_string[0])
+ break;
+ if (!ptr_string_orig)
+ ptr_string_orig = ptr_string;
+
+ word_start_pos = current_pos;
+ word_end_pos = current_pos;
+ word_end_pos_valid = current_pos;
+
+ /* find end of word: ' and - allowed in word, but not at the end */
+ ptr_end_valid = ptr_string;
+ ptr_end = (char *)weechat_utf8_next_char (ptr_string);
+ code_point = weechat_utf8_char_int (ptr_end);
+ while (iswalnum (code_point) || (code_point == '\'')
+ || (code_point == '-'))
+ {
+ word_end_pos++;
+ if (iswalnum (code_point))
+ {
+ /* pointer to last alphanumeric char in the word */
+ ptr_end_valid = ptr_end;
+ word_end_pos_valid = word_end_pos;
+ }
+ ptr_end = (char *)weechat_utf8_next_char (ptr_end);
+ if (!ptr_end[0])
+ break;
+ code_point = weechat_utf8_char_int (ptr_end);
+ }
+ ptr_end = (char *)weechat_utf8_next_char (ptr_end_valid);
+ word_end_pos = word_end_pos_valid;
+ word_ok = 0;
+ if (spell_string_is_url (ptr_string)
+ || spell_string_is_nick (buffer, ptr_string_orig))
+ {
+ /*
+ * word is an URL or a nick, then it is OK: search for next
+ * space (will be end of word)
+ */
+ word_ok = 1;
+ if (ptr_end[0])
+ {
+ code_point = weechat_utf8_char_int (ptr_end);
+ while (!iswspace (code_point))
+ {
+ ptr_end = (char *)weechat_utf8_next_char (ptr_end);
+ if (!ptr_end[0])
+ break;
+ code_point = weechat_utf8_char_int (ptr_end);
+ }
+ }
+ }
+ save_end = ptr_end[0];
+ ptr_end[0] = '\0';
+ length_word = ptr_end - ptr_string;
+
+ if (!word_ok)
+ {
+ if ((save_end != '\0')
+ || (weechat_config_integer (spell_config_check_real_time)))
+ {
+ word_ok = spell_check_word (ptr_speller_buffer,
+ ptr_string);
+ if (!word_ok && (input_pos >= word_start_pos))
+ {
+ /*
+ * if word is misspelled and that cursor is after
+ * the beginning of this word, save the word (we will
+ * look for suggestions after this loop)
+ */
+ if (misspelled_word)
+ free (misspelled_word);
+ misspelled_word = strdup (ptr_string);
+ }
+ }
+ else
+ word_ok = 1;
+ }
+
+ /* add error color */
+ if (!word_ok)
+ {
+ strcpy (result + index_result, color_error);
+ index_result += length_color_error;
+ }
+
+ /* add word */
+ strcpy (result + index_result, ptr_string);
+ index_result += length_word;
+
+ /* add normal color (after misspelled word) */
+ if (!word_ok)
+ {
+ strcpy (result + index_result, color_normal);
+ index_result += length_color_normal;
+ }
+
+ if (save_end == '\0')
+ break;
+
+ ptr_end[0] = save_end;
+ ptr_string = ptr_end;
+ current_pos = word_end_pos + 1;
+ }
+ result[index_result] = '\0';
+ }
+
+ /* save old suggestions in buffer */
+ ptr_suggestions = weechat_buffer_get_string (buffer,
+ "localvar_spell_suggest");
+ old_suggestions = (ptr_suggestions) ? strdup (ptr_suggestions) : NULL;
+
+ /* if there is a misspelled word, get suggestions and set them in buffer */
+ if (misspelled_word)
+ {
+ /*
+ * get the old misspelled word; we'll get suggestions or clear
+ * local variable "spell_suggest" only if the current misspelled
+ * word is different
+ */
+ old_misspelled_word = NULL;
+ if (old_suggestions)
+ {
+ pos_colon = strchr (old_suggestions, ':');
+ old_misspelled_word = (pos_colon) ?
+ weechat_strndup (old_suggestions, pos_colon - old_suggestions) :
+ strdup (old_suggestions);
+ }
+
+ if (!old_misspelled_word
+ || (strcmp (old_misspelled_word, misspelled_word) != 0))
+ {
+ suggestions = spell_get_suggestions (ptr_speller_buffer,
+ misspelled_word);
+ if (suggestions)
+ {
+ length = strlen (misspelled_word) + 1 /* ":" */
+ + strlen (suggestions) + 1;
+ word_and_suggestions = malloc (length);
+ if (word_and_suggestions)
+ {
+ snprintf (word_and_suggestions, length, "%s:%s",
+ misspelled_word, suggestions);
+ weechat_buffer_set (buffer, "localvar_set_spell_suggest",
+ word_and_suggestions);
+ free (word_and_suggestions);
+ }
+ else
+ {
+ weechat_buffer_set (buffer,
+ "localvar_del_spell_suggest", "");
+ }
+ free (suggestions);
+ }
+ else
+ {
+ /* set a misspelled word in buffer, also without suggestions */
+ weechat_buffer_set (buffer, "localvar_set_spell_suggest",
+ misspelled_word);
+ }
+ }
+
+ if (old_misspelled_word)
+ free (old_misspelled_word);
+
+ free (misspelled_word);
+ }
+ else
+ {
+ weechat_buffer_set (buffer, "localvar_del_spell_suggest", "");
+ }
+
+ /*
+ * if suggestions have changed, update the bar item
+ * and send signal "spell_suggest"
+ */
+ ptr_suggestions = weechat_buffer_get_string (buffer,
+ "localvar_spell_suggest");
+ if ((old_suggestions && !ptr_suggestions)
+ || (!old_suggestions && ptr_suggestions)
+ || (old_suggestions && ptr_suggestions
+ && (strcmp (old_suggestions, ptr_suggestions) != 0)))
+ {
+ weechat_bar_item_update ("spell_suggest");
+ (void) weechat_hook_signal_send ("spell_suggest",
+ WEECHAT_HOOK_SIGNAL_POINTER, buffer);
+ }
+ if (old_suggestions)
+ free (old_suggestions);
+
+ if (!result)
+ return NULL;
+
+ ptr_speller_buffer->modifier_result = strdup (result);
+
+ return result;
+}
+
+/*
+ * Refreshes bar items on signal "buffer_switch".
+ */
+
+int
+spell_buffer_switch_cb (const void *pointer, void *data, const char *signal,
+ const char *type_data, void *signal_data)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) signal;
+ (void) type_data;
+ (void) signal_data;
+
+ /* refresh bar items (for root bars) */
+ weechat_bar_item_update ("spell_dict");
+ weechat_bar_item_update ("spell_suggest");
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Refreshes bar items on signal "window_switch".
+ */
+
+int
+spell_window_switch_cb (const void *pointer, void *data, const char *signal,
+ const char *type_data, void *signal_data)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) signal;
+ (void) type_data;
+ (void) signal_data;
+
+ /* refresh bar items (for root bars) */
+ weechat_bar_item_update ("spell_dict");
+ weechat_bar_item_update ("spell_suggest");
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Removes struct for buffer in hashtable "spell_speller_buffer" on
+ * signal "buffer_closed".
+ */
+
+int
+spell_buffer_closed_cb (const void *pointer, void *data, const char *signal,
+ const char *type_data, void *signal_data)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) signal;
+ (void) type_data;
+
+ weechat_hashtable_remove (spell_speller_buffer, signal_data);
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Display infos about external libraries used.
+ */
+
+int
+spell_debug_libs_cb (const void *pointer, void *data, const char *signal,
+ const char *type_data, void *signal_data)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) signal;
+ (void) type_data;
+ (void) signal_data;
+
+#ifdef USE_ENCHANT
+#ifdef HAVE_ENCHANT_GET_VERSION
+ weechat_printf (NULL, " %s: enchant %s",
+ SPELL_PLUGIN_NAME, enchant_get_version ());
+#else
+ weechat_printf (NULL, " %s: enchant (?)", SPELL_PLUGIN_NAME);
+#endif /* HAVE_ENCHANT_GET_VERSION */
+#else
+#ifdef HAVE_ASPELL_VERSION_STRING
+ weechat_printf (NULL, " %s: aspell %s",
+ SPELL_PLUGIN_NAME, aspell_version_string ());
+#else
+ weechat_printf (NULL, " %s: aspell (?)", SPELL_PLUGIN_NAME);
+#endif /* HAVE_ASPELL_VERSION_STRING */
+#endif /* USE_ENCHANT */
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Callback for changes to option "weechat.completion.nick_completer".
+ */
+
+int
+spell_config_change_nick_completer_cb (const void *pointer, void *data,
+ const char *option, const char *value)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) option;
+
+ if (spell_nick_completer)
+ free (spell_nick_completer);
+
+ spell_nick_completer = weechat_string_strip (value, 0, 1, " ");
+ spell_len_nick_completer =
+ (spell_nick_completer) ? strlen (spell_nick_completer) : 0;
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Initializes spell plugin.
+ */
+
+int
+weechat_plugin_init (struct t_weechat_plugin *plugin, int argc, char *argv[])
+{
+ /* make C compiler happy */
+ (void) argc;
+ (void) argv;
+
+ weechat_plugin = plugin;
+
+#ifdef USE_ENCHANT
+ /* acquire enchant broker */
+ broker = enchant_broker_init ();
+ if (!broker)
+ return WEECHAT_RC_ERROR;
+#endif /* USE_ENCHANT */
+
+ if (!spell_speller_init ())
+ return WEECHAT_RC_ERROR;
+
+ if (!spell_config_init ())
+ return WEECHAT_RC_ERROR;
+
+ spell_config_read ();
+
+ spell_command_init ();
+
+ spell_completion_init ();
+
+ /*
+ * callback for spell checking input text
+ * we use a low priority here, so that other modifiers "input_text_display"
+ * (from other plugins) will be called before this one
+ */
+ weechat_hook_modifier ("500|input_text_display",
+ &spell_modifier_cb, NULL, NULL);
+
+ spell_bar_item_init ();
+
+ spell_info_init ();
+
+ weechat_hook_signal ("buffer_switch",
+ &spell_buffer_switch_cb, NULL, NULL);
+ weechat_hook_signal ("window_switch",
+ &spell_window_switch_cb, NULL, NULL);
+ weechat_hook_signal ("buffer_closed",
+ &spell_buffer_closed_cb, NULL, NULL);
+ weechat_hook_signal ("debug_libs",
+ &spell_debug_libs_cb, NULL, NULL);
+
+ weechat_hook_config ("weechat.completion.nick_completer",
+ &spell_config_change_nick_completer_cb,
+ NULL, NULL);
+ /* manually call callback to initialize */
+ spell_config_change_nick_completer_cb (
+ NULL, NULL, "weechat.completion.nick_completer",
+ weechat_config_string (
+ weechat_config_get ("weechat.completion.nick_completer")));
+
+ return WEECHAT_RC_OK;
+}
+
+/*
+ * Ends spell plugin.
+ */
+
+int
+weechat_plugin_end (struct t_weechat_plugin *plugin)
+{
+ /* make C compiler happy */
+ (void) plugin;
+
+ spell_config_write ();
+ spell_config_free ();
+
+ spell_speller_end ();
+
+#ifdef USE_ENCHANT
+ /* release enchant broker */
+ enchant_broker_free (broker);
+#endif /* USE_ENCHANT */
+
+ if (spell_nick_completer)
+ free (spell_nick_completer);
+
+ return WEECHAT_RC_OK;
+}
diff --git a/src/plugins/spell/spell.h b/src/plugins/spell/spell.h
new file mode 100644
index 000000000..8625ea7db
--- /dev/null
+++ b/src/plugins/spell/spell.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2006 Emmanuel Bouthenot <kolter@openics.org>
+ * Copyright (C) 2006-2019 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat 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.
+ *
+ * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEECHAT_PLUGIN_SPELL_H
+#define WEECHAT_PLUGIN_SPELL_H
+
+#ifdef USE_ENCHANT
+#include <enchant.h>
+#else
+#include <aspell.h>
+#endif /* USE_ENCHANT */
+
+#define weechat_plugin weechat_spell_plugin
+#define SPELL_PLUGIN_NAME "spell"
+
+struct t_spell_code
+{
+ char *code;
+ char *name;
+};
+
+#ifdef USE_ENCHANT
+extern EnchantBroker *broker;
+#endif /* USE_ENCHANT */
+
+extern struct t_weechat_plugin *weechat_spell_plugin;
+extern int spell_enabled;
+extern struct t_spell_code spell_langs[];
+extern struct t_spell_code spell_countries[];
+
+extern char *spell_build_option_name (struct t_gui_buffer *buffer);
+extern const char *spell_get_dict_with_buffer_name (const char *name);
+extern const char *spell_get_dict (struct t_gui_buffer *buffer);
+
+#endif /* WEECHAT_PLUGIN_SPELL_H */