feat: add spellcheck highlighting support

Introduce optional spellcheck highlighting in the input window using
the Enchant-2 library.

```
/spellcheck on
/spellcheck lang en_US
```

New theme color `input.misspelled`.

Fixes: https://github.com/profanity-im/profanity/issues/183
Signed-off-by: Michael Vetter <jubalh@iodoru.org>
This commit is contained in:
Michael Vetter
2026-03-26 22:37:06 +01:00
parent 88b80c33ef
commit 4922366366
17 changed files with 335 additions and 0 deletions

107
src/tools/spellcheck.c Normal file
View File

@@ -0,0 +1,107 @@
/*
* 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;
}
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

52
src/tools/spellcheck.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* 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);
#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;
}
#endif
#endif