Merge pull request #2132 from profanity-im/feat/spellcheck
Add spellchecking
This commit is contained in:
13
meson.build
13
meson.build
@@ -160,6 +160,7 @@ gdk_pixbuf_dep = disabler()
|
|||||||
omemo_dep = disabler()
|
omemo_dep = disabler()
|
||||||
gcrypt_dep = disabler()
|
gcrypt_dep = disabler()
|
||||||
qrencode_dep = disabler()
|
qrencode_dep = disabler()
|
||||||
|
enchant_dep = disabler()
|
||||||
|
|
||||||
# Dependencies for functional tests
|
# Dependencies for functional tests
|
||||||
stabber_dep = cc.find_library('stabber', required: false)
|
stabber_dep = cc.find_library('stabber', required: false)
|
||||||
@@ -269,6 +270,12 @@ if get_option('omemo-qrcode').enabled()
|
|||||||
conf_data.set('HAVE_QRENCODE', 1)
|
conf_data.set('HAVE_QRENCODE', 1)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Spellcheck support
|
||||||
|
if get_option('spellcheck').enabled()
|
||||||
|
enchant_dep = dependency('enchant-2', required: true)
|
||||||
|
conf_data.set('HAVE_SPELLCHECK', 1)
|
||||||
|
endif
|
||||||
|
|
||||||
# Set installation paths
|
# Set installation paths
|
||||||
themes_path = get_option('themes_path')
|
themes_path = get_option('themes_path')
|
||||||
if themes_path == ''
|
if themes_path == ''
|
||||||
@@ -355,6 +362,10 @@ if qrencode_dep.found()
|
|||||||
profanity_deps += qrencode_dep
|
profanity_deps += qrencode_dep
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if enchant_dep.found()
|
||||||
|
profanity_deps += enchant_dep
|
||||||
|
endif
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
inc = include_directories('.', 'src')
|
inc = include_directories('.', 'src')
|
||||||
|
|
||||||
@@ -419,6 +430,7 @@ core_sources = files(
|
|||||||
'src/tools/autocomplete.c',
|
'src/tools/autocomplete.c',
|
||||||
'src/tools/clipboard.c',
|
'src/tools/clipboard.c',
|
||||||
'src/tools/editor.c',
|
'src/tools/editor.c',
|
||||||
|
'src/tools/spellcheck.c',
|
||||||
'src/config/files.c',
|
'src/config/files.c',
|
||||||
'src/config/conflists.c',
|
'src/config/conflists.c',
|
||||||
'src/config/accounts.c',
|
'src/config/accounts.c',
|
||||||
@@ -584,6 +596,7 @@ if get_option('tests')
|
|||||||
'src/tools/autocomplete.c',
|
'src/tools/autocomplete.c',
|
||||||
'src/tools/clipboard.c',
|
'src/tools/clipboard.c',
|
||||||
'src/tools/editor.c',
|
'src/tools/editor.c',
|
||||||
|
'src/tools/spellcheck.c',
|
||||||
'src/tools/bookmark_ignore.c',
|
'src/tools/bookmark_ignore.c',
|
||||||
'src/config/account.c',
|
'src/config/account.c',
|
||||||
'src/config/files.c',
|
'src/config/files.c',
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ option('omemo-qrcode',
|
|||||||
description: 'Enable ability to display OMEMO QR code'
|
description: 'Enable ability to display OMEMO QR code'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
option('spellcheck',
|
||||||
|
type: 'feature',
|
||||||
|
value: 'disabled',
|
||||||
|
description: 'Enable spellchecking support'
|
||||||
|
)
|
||||||
|
|
||||||
# Other:
|
# Other:
|
||||||
option('python_framework',
|
option('python_framework',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
static char* _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _theme_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _theme_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
|
static char* _spellcheck_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _autoaway_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _autoaway_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _autoconnect_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _autoconnect_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _account_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _account_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
@@ -138,6 +139,7 @@ static Autocomplete autoconnect_ac;
|
|||||||
static Autocomplete wintitle_ac;
|
static Autocomplete wintitle_ac;
|
||||||
static Autocomplete theme_ac;
|
static Autocomplete theme_ac;
|
||||||
static Autocomplete theme_load_ac;
|
static Autocomplete theme_load_ac;
|
||||||
|
static Autocomplete spellcheck_ac;
|
||||||
static Autocomplete account_ac;
|
static Autocomplete account_ac;
|
||||||
static Autocomplete account_set_ac;
|
static Autocomplete account_set_ac;
|
||||||
static Autocomplete account_clear_ac;
|
static Autocomplete account_clear_ac;
|
||||||
@@ -298,6 +300,7 @@ static Autocomplete* all_acs[] = {
|
|||||||
&autoconnect_ac,
|
&autoconnect_ac,
|
||||||
&wintitle_ac,
|
&wintitle_ac,
|
||||||
&theme_ac,
|
&theme_ac,
|
||||||
|
&spellcheck_ac,
|
||||||
&account_ac,
|
&account_ac,
|
||||||
&account_set_ac,
|
&account_set_ac,
|
||||||
&account_clear_ac,
|
&account_clear_ac,
|
||||||
@@ -563,6 +566,11 @@ cmd_ac_init(void)
|
|||||||
autocomplete_add(theme_ac, "colours");
|
autocomplete_add(theme_ac, "colours");
|
||||||
autocomplete_add(theme_ac, "properties");
|
autocomplete_add(theme_ac, "properties");
|
||||||
|
|
||||||
|
autocomplete_add(spellcheck_ac, "on");
|
||||||
|
autocomplete_add(spellcheck_ac, "off");
|
||||||
|
autocomplete_add(spellcheck_ac, "list");
|
||||||
|
autocomplete_add(spellcheck_ac, "lang");
|
||||||
|
|
||||||
autocomplete_add(disco_ac, "info");
|
autocomplete_add(disco_ac, "info");
|
||||||
autocomplete_add(disco_ac, "items");
|
autocomplete_add(disco_ac, "items");
|
||||||
|
|
||||||
@@ -1748,6 +1756,16 @@ cmd_ac_complete_filepath(const char* const input, char* const startstr, gboolean
|
|||||||
return autocomplete_param_with_ac(input, startstr, filepath_ac, TRUE, previous);
|
return autocomplete_param_with_ac(input, startstr, filepath_ac, TRUE, previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char*
|
||||||
|
_spellcheck_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
||||||
|
{
|
||||||
|
char* result = NULL;
|
||||||
|
|
||||||
|
result = autocomplete_param_with_ac(input, "/spellcheck", spellcheck_ac, TRUE, previous);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static char*
|
static char*
|
||||||
_cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous)
|
_cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous)
|
||||||
{
|
{
|
||||||
@@ -1767,6 +1785,11 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = _spellcheck_autocomplete(window, input, previous);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// autocomplete nickname in chat rooms
|
// autocomplete nickname in chat rooms
|
||||||
if (window->type == WIN_MUC) {
|
if (window->type == WIN_MUC) {
|
||||||
ProfMucWin* mucwin = (ProfMucWin*)window;
|
ProfMucWin* mucwin = (ProfMucWin*)window;
|
||||||
|
|||||||
@@ -1451,6 +1451,23 @@ static const struct cmd_t command_defs[] = {
|
|||||||
{ "on|off", "Enable or disable splash logo." })
|
{ "on|off", "Enable or disable splash logo." })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ CMD_PREAMBLE("/spellcheck",
|
||||||
|
parse_args, 1, 2, &cons_spellcheck_setting)
|
||||||
|
CMD_MAINFUNC(cmd_spellcheck)
|
||||||
|
CMD_TAGS(
|
||||||
|
CMD_TAG_UI)
|
||||||
|
CMD_SYN(
|
||||||
|
"/spellcheck on|off",
|
||||||
|
"/spellcheck list",
|
||||||
|
"/spellcheck lang <locale>")
|
||||||
|
CMD_DESC(
|
||||||
|
"Enable or disable spellchecking, or set the language.")
|
||||||
|
CMD_ARGS(
|
||||||
|
{ "on|off", "Enable or disable spellchecking." },
|
||||||
|
{ "list", "List available dictionaries recognized by Enchant." },
|
||||||
|
{ "lang <locale>", "Set the spellcheck language (en_US)." })
|
||||||
|
},
|
||||||
|
|
||||||
{ CMD_PREAMBLE("/autoconnect",
|
{ CMD_PREAMBLE("/autoconnect",
|
||||||
parse_args, 1, 2, &cons_autoconnect_setting)
|
parse_args, 1, 2, &cons_autoconnect_setting)
|
||||||
CMD_MAINFUNC(cmd_autoconnect)
|
CMD_MAINFUNC(cmd_autoconnect)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
#include "tools/plugin_download.h"
|
#include "tools/plugin_download.h"
|
||||||
#include "tools/bookmark_ignore.h"
|
#include "tools/bookmark_ignore.h"
|
||||||
#include "tools/editor.h"
|
#include "tools/editor.h"
|
||||||
|
#include "tools/spellcheck.h"
|
||||||
#include "plugins/plugins.h"
|
#include "plugins/plugins.h"
|
||||||
#include "ui/inputwin.h"
|
#include "ui/inputwin.h"
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
@@ -6560,6 +6561,55 @@ cmd_splash(ProfWin* window, const char* const command, gchar** args)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
cmd_spellcheck(ProfWin* window, const char* const command, gchar** args)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_SPELLCHECK
|
||||||
|
if (g_strcmp0(args[0], "on") == 0) {
|
||||||
|
prefs_set_boolean(PREF_SPELLCHECK_ENABLE, TRUE);
|
||||||
|
cons_show("Spellcheck enabled.");
|
||||||
|
} else if (g_strcmp0(args[0], "off") == 0) {
|
||||||
|
prefs_set_boolean(PREF_SPELLCHECK_ENABLE, FALSE);
|
||||||
|
cons_show("Spellcheck disabled.");
|
||||||
|
} else if (g_strcmp0(args[0], "list") == 0) {
|
||||||
|
GList* langs = spellcheck_get_available_langs();
|
||||||
|
if (langs == NULL) {
|
||||||
|
cons_show("No dictionaries found. Install Enchant-compatible dictionaries (hunspell/aspell).");
|
||||||
|
} else {
|
||||||
|
GString* lang_str = g_string_new("");
|
||||||
|
GList* curr = langs;
|
||||||
|
while (curr) {
|
||||||
|
g_string_append(lang_str, (char*)curr->data);
|
||||||
|
if (curr->next) {
|
||||||
|
g_string_append(lang_str, ", ");
|
||||||
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
|
}
|
||||||
|
cons_show("Available spellcheck dictionaries: %s", lang_str->str);
|
||||||
|
g_string_free(lang_str, TRUE);
|
||||||
|
g_list_free_full(langs, g_free);
|
||||||
|
}
|
||||||
|
} else if (g_strcmp0(args[0], "lang") == 0) {
|
||||||
|
if (args[1] == NULL) {
|
||||||
|
cons_bad_cmd_usage(command);
|
||||||
|
} else {
|
||||||
|
if (spellcheck_set_lang(args[1])) {
|
||||||
|
prefs_set_string(PREF_SPELLCHECK_LANG, args[1]);
|
||||||
|
cons_show("Spellcheck language set to: %s", args[1]);
|
||||||
|
} else {
|
||||||
|
cons_show("Failed to set spellcheck language to: %s. Is the dictionary installed?", args[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cons_bad_cmd_usage(command);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
cons_show("Profanity was built without spellcheck support.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
cmd_autoconnect(ProfWin* window, const char* const command, gchar** args)
|
cmd_autoconnect(ProfWin* window, const char* const command, gchar** args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ gboolean cmd_bookmark_ignore(ProfWin* window, const char* const command, gchar**
|
|||||||
gboolean cmd_roster(ProfWin* window, const char* const command, gchar** args);
|
gboolean cmd_roster(ProfWin* window, const char* const command, gchar** args);
|
||||||
gboolean cmd_software(ProfWin* window, const char* const command, gchar** args);
|
gboolean cmd_software(ProfWin* window, const char* const command, gchar** args);
|
||||||
gboolean cmd_splash(ProfWin* window, const char* const command, gchar** args);
|
gboolean cmd_splash(ProfWin* window, const char* const command, gchar** args);
|
||||||
|
gboolean cmd_spellcheck(ProfWin* window, const char* const command, gchar** args);
|
||||||
gboolean cmd_states(ProfWin* window, const char* const command, gchar** args);
|
gboolean cmd_states(ProfWin* window, const char* const command, gchar** args);
|
||||||
gboolean cmd_status_get(ProfWin* window, const char* const command, gchar** args);
|
gboolean cmd_status_get(ProfWin* window, const char* const command, gchar** args);
|
||||||
gboolean cmd_status_set(ProfWin* window, const char* const command, gchar** args);
|
gboolean cmd_status_set(ProfWin* window, const char* const command, gchar** args);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
#define PREF_GROUP_MUC "muc"
|
#define PREF_GROUP_MUC "muc"
|
||||||
#define PREF_GROUP_PLUGINS "plugins"
|
#define PREF_GROUP_PLUGINS "plugins"
|
||||||
#define PREF_GROUP_EXECUTABLES "executables"
|
#define PREF_GROUP_EXECUTABLES "executables"
|
||||||
|
#define PREF_GROUP_SPELLCHECK "spellcheck"
|
||||||
|
|
||||||
#define INPBLOCK_DEFAULT 1000
|
#define INPBLOCK_DEFAULT 1000
|
||||||
|
|
||||||
@@ -1909,6 +1910,9 @@ _get_group(preference_t pref)
|
|||||||
return PREF_GROUP_OMEMO;
|
return PREF_GROUP_OMEMO;
|
||||||
case PREF_OX_LOG:
|
case PREF_OX_LOG:
|
||||||
return PREF_GROUP_OX;
|
return PREF_GROUP_OX;
|
||||||
|
case PREF_SPELLCHECK_ENABLE:
|
||||||
|
case PREF_SPELLCHECK_LANG:
|
||||||
|
return PREF_GROUP_SPELLCHECK;
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -2196,6 +2200,10 @@ _get_key(preference_t pref)
|
|||||||
return "strophe.sm.enabled";
|
return "strophe.sm.enabled";
|
||||||
case PREF_STROPHE_SM_RESEND:
|
case PREF_STROPHE_SM_RESEND:
|
||||||
return "strophe.sm.resend";
|
return "strophe.sm.resend";
|
||||||
|
case PREF_SPELLCHECK_ENABLE:
|
||||||
|
return "enabled";
|
||||||
|
case PREF_SPELLCHECK_LANG:
|
||||||
|
return "lang";
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -2249,6 +2257,7 @@ _get_default_boolean(preference_t pref)
|
|||||||
case PREF_STROPHE_SM_ENABLED:
|
case PREF_STROPHE_SM_ENABLED:
|
||||||
case PREF_STROPHE_SM_RESEND:
|
case PREF_STROPHE_SM_RESEND:
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case PREF_SPELLCHECK_ENABLE:
|
||||||
case PREF_PGP_PUBKEY_AUTOIMPORT:
|
case PREF_PGP_PUBKEY_AUTOIMPORT:
|
||||||
default:
|
default:
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -2356,6 +2365,8 @@ _get_default_string(preference_t pref)
|
|||||||
return "0";
|
return "0";
|
||||||
case PREF_DBLOG:
|
case PREF_DBLOG:
|
||||||
return "on";
|
return "on";
|
||||||
|
case PREF_SPELLCHECK_LANG:
|
||||||
|
return "en_US";
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ typedef enum {
|
|||||||
PREF_STROPHE_SM_RESEND,
|
PREF_STROPHE_SM_RESEND,
|
||||||
PREF_VCARD_PHOTO_CMD,
|
PREF_VCARD_PHOTO_CMD,
|
||||||
PREF_STATUSBAR_TABMODE,
|
PREF_STATUSBAR_TABMODE,
|
||||||
|
PREF_SPELLCHECK_ENABLE,
|
||||||
|
PREF_SPELLCHECK_LANG,
|
||||||
} preference_t;
|
} preference_t;
|
||||||
|
|
||||||
typedef struct prof_alias_t
|
typedef struct prof_alias_t
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ theme_init(const char* const theme_name)
|
|||||||
g_hash_table_insert(defaults, strdup("mention"), strdup("yellow"));
|
g_hash_table_insert(defaults, strdup("mention"), strdup("yellow"));
|
||||||
g_hash_table_insert(defaults, strdup("trigger"), strdup("yellow"));
|
g_hash_table_insert(defaults, strdup("trigger"), strdup("yellow"));
|
||||||
g_hash_table_insert(defaults, strdup("input.text"), strdup("default"));
|
g_hash_table_insert(defaults, strdup("input.text"), strdup("default"));
|
||||||
|
g_hash_table_insert(defaults, strdup("input.misspelled"), strdup("red"));
|
||||||
g_hash_table_insert(defaults, strdup("main.time"), strdup("default"));
|
g_hash_table_insert(defaults, strdup("main.time"), strdup("default"));
|
||||||
g_hash_table_insert(defaults, strdup("titlebar.text"), strdup("white"));
|
g_hash_table_insert(defaults, strdup("titlebar.text"), strdup("white"));
|
||||||
g_hash_table_insert(defaults, strdup("titlebar.brackets"), strdup("cyan"));
|
g_hash_table_insert(defaults, strdup("titlebar.brackets"), strdup("cyan"));
|
||||||
@@ -713,6 +714,9 @@ theme_attrs(theme_item_t attrs)
|
|||||||
case THEME_INPUT_TEXT:
|
case THEME_INPUT_TEXT:
|
||||||
_theme_prep_fgnd("input.text", lookup_str, &bold);
|
_theme_prep_fgnd("input.text", lookup_str, &bold);
|
||||||
break;
|
break;
|
||||||
|
case THEME_INPUT_MISSPELLED:
|
||||||
|
_theme_prep_fgnd("input.misspelled", lookup_str, &bold);
|
||||||
|
break;
|
||||||
case THEME_TIME:
|
case THEME_TIME:
|
||||||
_theme_prep_fgnd("main.time", lookup_str, &bold);
|
_theme_prep_fgnd("main.time", lookup_str, &bold);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ typedef enum {
|
|||||||
THEME_MENTION,
|
THEME_MENTION,
|
||||||
THEME_TRIGGER,
|
THEME_TRIGGER,
|
||||||
THEME_INPUT_TEXT,
|
THEME_INPUT_TEXT,
|
||||||
|
THEME_INPUT_MISSPELLED,
|
||||||
THEME_TIME,
|
THEME_TIME,
|
||||||
THEME_TITLE_TEXT,
|
THEME_TITLE_TEXT,
|
||||||
THEME_TITLE_BRACKET,
|
THEME_TITLE_BRACKET,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "config/accounts.h"
|
#include "config/accounts.h"
|
||||||
#include "config/preferences.h"
|
#include "config/preferences.h"
|
||||||
#include "config/theme.h"
|
#include "config/theme.h"
|
||||||
|
#include "tools/spellcheck.h"
|
||||||
#include "config/tlscerts.h"
|
#include "config/tlscerts.h"
|
||||||
#include "config/scripts.h"
|
#include "config/scripts.h"
|
||||||
#include "command/cmd_defs.h"
|
#include "command/cmd_defs.h"
|
||||||
@@ -234,6 +235,7 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name)
|
|||||||
#ifdef HAVE_OMEMO
|
#ifdef HAVE_OMEMO
|
||||||
omemo_init();
|
omemo_init();
|
||||||
#endif
|
#endif
|
||||||
|
spellcheck_init();
|
||||||
atexit(_shutdown);
|
atexit(_shutdown);
|
||||||
plugins_init();
|
plugins_init();
|
||||||
#ifdef HAVE_GTK
|
#ifdef HAVE_GTK
|
||||||
|
|||||||
132
src/tools/spellcheck.c
Normal file
132
src/tools/spellcheck.c
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* spellcheck.c
|
||||||
|
* vim: expandtab:ts=4:sts=4:sw=4
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Michael Vetter <jubalh@iodoru.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SPELLCHECK
|
||||||
|
|
||||||
|
#include <enchant.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include "tools/spellcheck.h"
|
||||||
|
#include "config/preferences.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
static EnchantBroker* broker = NULL;
|
||||||
|
static EnchantDict* dict = NULL;
|
||||||
|
static char* current_lang = NULL;
|
||||||
|
|
||||||
|
void
|
||||||
|
spellcheck_init(void)
|
||||||
|
{
|
||||||
|
if (broker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
broker = enchant_broker_init();
|
||||||
|
if (!broker) {
|
||||||
|
log_error("Failed to initialize Enchant broker");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prof_add_shutdown_routine(spellcheck_deinit);
|
||||||
|
|
||||||
|
char* lang = prefs_get_string(PREF_SPELLCHECK_LANG);
|
||||||
|
spellcheck_set_lang(lang);
|
||||||
|
g_free(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
spellcheck_deinit(void)
|
||||||
|
{
|
||||||
|
if (dict) {
|
||||||
|
enchant_broker_free_dict(broker, dict);
|
||||||
|
dict = NULL;
|
||||||
|
}
|
||||||
|
if (broker) {
|
||||||
|
enchant_broker_free(broker);
|
||||||
|
broker = NULL;
|
||||||
|
}
|
||||||
|
g_free(current_lang);
|
||||||
|
current_lang = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
spellcheck_set_lang(const char* lang)
|
||||||
|
{
|
||||||
|
if (!broker || !lang) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_lang && strcmp(current_lang, lang) == 0 && dict) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnchantDict* new_dict = enchant_broker_request_dict(broker, lang);
|
||||||
|
if (!new_dict) {
|
||||||
|
log_error("Failed to request Enchant dictionary for language: %s", lang);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dict) {
|
||||||
|
enchant_broker_free_dict(broker, dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
dict = new_dict;
|
||||||
|
g_free(current_lang);
|
||||||
|
current_lang = g_strdup(lang);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
spellcheck_get_lang(void)
|
||||||
|
{
|
||||||
|
return current_lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_list_dicts_cb(const char* const lang_tag,
|
||||||
|
const char* const provider_name,
|
||||||
|
const char* const provider_desc,
|
||||||
|
const char* const provider_file,
|
||||||
|
void* user_data)
|
||||||
|
{
|
||||||
|
GList** list = (GList**)user_data;
|
||||||
|
if (g_list_find_custom(*list, lang_tag, (GCompareFunc)g_strcmp0) == NULL) {
|
||||||
|
*list = g_list_append(*list, g_strdup(lang_tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GList*
|
||||||
|
spellcheck_get_available_langs(void)
|
||||||
|
{
|
||||||
|
if (!broker) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
GList* list = NULL;
|
||||||
|
enchant_broker_list_dicts(broker, _list_dicts_cb, &list);
|
||||||
|
return g_list_sort(list, (GCompareFunc)g_strcmp0);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
spellcheck_is_misspelled(const char* word)
|
||||||
|
{
|
||||||
|
if (!dict || !word || word[0] == '\0') {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = enchant_dict_check(dict, word, strlen(word));
|
||||||
|
return (result != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
58
src/tools/spellcheck.h
Normal file
58
src/tools/spellcheck.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* spellcheck.h
|
||||||
|
* vim: expandtab:ts=4:sts=4:sw=4
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Michael Vetter <jubalh@iodoru.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TOOLS_SPELLCHECK_H
|
||||||
|
#define TOOLS_SPELLCHECK_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_SPELLCHECK
|
||||||
|
|
||||||
|
void spellcheck_init(void);
|
||||||
|
void spellcheck_deinit(void);
|
||||||
|
gboolean spellcheck_is_misspelled(const char* word);
|
||||||
|
gboolean spellcheck_set_lang(const char* lang);
|
||||||
|
const char* spellcheck_get_lang(void);
|
||||||
|
GList* spellcheck_get_available_langs(void);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
spellcheck_init(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
static inline void
|
||||||
|
spellcheck_deinit(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
static inline gboolean
|
||||||
|
spellcheck_is_misspelled(const char* word)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
static inline gboolean
|
||||||
|
spellcheck_set_lang(const char* lang)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
static inline const char*
|
||||||
|
spellcheck_get_lang(void)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
static inline GList*
|
||||||
|
spellcheck_get_available_langs(void)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1270,6 +1270,21 @@ cons_splash_setting(void)
|
|||||||
cons_show("Splash screen (/splash) : OFF");
|
cons_show("Splash screen (/splash) : OFF");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cons_spellcheck_setting(void)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_SPELLCHECK
|
||||||
|
if (prefs_get_boolean(PREF_SPELLCHECK_ENABLE)) {
|
||||||
|
auto_gchar gchar* lang = prefs_get_string(PREF_SPELLCHECK_LANG);
|
||||||
|
cons_show("Spellcheck (/spellcheck) : ON (%s)", lang);
|
||||||
|
} else {
|
||||||
|
cons_show("Spellcheck (/spellcheck) : OFF");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
cons_show("Spellcheck (/spellcheck) : built without spellcheck support");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
cons_occupants_setting(void)
|
cons_occupants_setting(void)
|
||||||
{
|
{
|
||||||
@@ -1589,6 +1604,7 @@ cons_show_ui_prefs(void)
|
|||||||
cons_beep_setting();
|
cons_beep_setting();
|
||||||
cons_flash_setting();
|
cons_flash_setting();
|
||||||
cons_splash_setting();
|
cons_splash_setting();
|
||||||
|
cons_spellcheck_setting();
|
||||||
cons_winpos_setting();
|
cons_winpos_setting();
|
||||||
cons_wrap_setting();
|
cons_wrap_setting();
|
||||||
cons_time_setting();
|
cons_time_setting();
|
||||||
@@ -2572,6 +2588,7 @@ cons_theme_properties(void)
|
|||||||
_cons_theme_prop(console, THEME_RECEIPT_SENT, "receipt.sent");
|
_cons_theme_prop(console, THEME_RECEIPT_SENT, "receipt.sent");
|
||||||
|
|
||||||
_cons_theme_prop(console, THEME_INPUT_TEXT, "input.text");
|
_cons_theme_prop(console, THEME_INPUT_TEXT, "input.text");
|
||||||
|
_cons_theme_prop(console, THEME_INPUT_MISSPELLED, "input.misspelled");
|
||||||
|
|
||||||
cons_show("");
|
cons_show("");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include "xmpp/roster_list.h"
|
#include "xmpp/roster_list.h"
|
||||||
#include "xmpp/chat_state.h"
|
#include "xmpp/chat_state.h"
|
||||||
#include "tools/editor.h"
|
#include "tools/editor.h"
|
||||||
|
#include "tools/spellcheck.h"
|
||||||
|
|
||||||
static WINDOW* inp_win;
|
static WINDOW* inp_win;
|
||||||
static int pad_start = 0;
|
static int pad_start = 0;
|
||||||
@@ -327,6 +328,8 @@ _inp_write(char* line, int offset)
|
|||||||
getyx(inp_win, y, x);
|
getyx(inp_win, y, x);
|
||||||
col += x;
|
col += x;
|
||||||
|
|
||||||
|
gboolean do_spell = prefs_get_boolean(PREF_SPELLCHECK_ENABLE) && (line[0] != '/');
|
||||||
|
|
||||||
for (size_t i = 0; line[i] != '\0'; i++) {
|
for (size_t i = 0; line[i] != '\0'; i++) {
|
||||||
char* c = &line[i];
|
char* c = &line[i];
|
||||||
char retc[PROF_MB_CUR_MAX] = { 0 };
|
char retc[PROF_MB_CUR_MAX] = { 0 };
|
||||||
@@ -348,6 +351,47 @@ _inp_write(char* line, int offset)
|
|||||||
i += ch_len - 1;
|
i += ch_len - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (do_spell) {
|
||||||
|
gunichar uc = g_utf8_get_char(c);
|
||||||
|
if (g_unichar_isalnum(uc) || uc == '\'') {
|
||||||
|
// start of a word
|
||||||
|
size_t start = i - (ch_len - 1);
|
||||||
|
size_t end = start + ch_len;
|
||||||
|
while (line[end] != '\0') {
|
||||||
|
size_t next_ch_len = mbrlen(&line[end], MB_CUR_MAX, NULL);
|
||||||
|
if (next_ch_len == (size_t)-2 || next_ch_len == (size_t)-1)
|
||||||
|
break;
|
||||||
|
gunichar next_uc = g_utf8_get_char(&line[end]);
|
||||||
|
if (!g_unichar_isalnum(next_uc) && next_uc != '\'')
|
||||||
|
break;
|
||||||
|
end += next_ch_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* word = g_strndup(&line[start], end - start);
|
||||||
|
gboolean misspelled = spellcheck_is_misspelled(word);
|
||||||
|
g_free(word);
|
||||||
|
|
||||||
|
if (misspelled) {
|
||||||
|
wattron(inp_win, theme_attrs(THEME_INPUT_MISSPELLED));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the word
|
||||||
|
size_t word_pos = start;
|
||||||
|
while (word_pos < end) {
|
||||||
|
size_t cur_ch_len = mbrlen(&line[word_pos], MB_CUR_MAX, NULL);
|
||||||
|
waddnstr(inp_win, &line[word_pos], cur_ch_len);
|
||||||
|
word_pos += cur_ch_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (misspelled) {
|
||||||
|
wattroff(inp_win, theme_attrs(THEME_INPUT_MISSPELLED));
|
||||||
|
}
|
||||||
|
|
||||||
|
i = end - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
waddnstr(inp_win, c, ch_len);
|
waddnstr(inp_win, c, ch_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ void cons_console_setting(void);
|
|||||||
void cons_flash_setting(void);
|
void cons_flash_setting(void);
|
||||||
void cons_tray_setting(void);
|
void cons_tray_setting(void);
|
||||||
void cons_splash_setting(void);
|
void cons_splash_setting(void);
|
||||||
|
void cons_spellcheck_setting(void);
|
||||||
void cons_titlebar_setting(void);
|
void cons_titlebar_setting(void);
|
||||||
void cons_vercheck_setting(void);
|
void cons_vercheck_setting(void);
|
||||||
void cons_occupants_setting(void);
|
void cons_occupants_setting(void);
|
||||||
|
|||||||
@@ -1008,6 +1008,10 @@ cons_splash_setting(void)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
|
cons_spellcheck_setting(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void
|
||||||
cons_vercheck_setting(void)
|
cons_vercheck_setting(void)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user