| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // Author: Nicholas Guriev <guriev-ns@ya.ru>, public domain, 2019
- // License: CC0, https://creativecommons.org/publicdomain/zero/1.0/legalcode
- #include <set>
- #include <QLocale>
- #include "spellcheck/platform/linux/linux_enchant.h"
- #include "spellcheck/platform/linux/spellcheck_linux.h"
- #include "base/debug_log.h"
- namespace Platform::Spellchecker {
- namespace {
- constexpr auto kHspell = "hspell";
- constexpr auto kMySpell = "myspell";
- constexpr auto kHunspell = "hunspell";
- constexpr auto kOrdering = "hspell,aspell,hunspell,myspell";
- constexpr auto kMaxValidators = 10;
- constexpr auto kMaxMySpellCount = 3;
- constexpr auto kMaxWordLength = 15;
- using DictPtr = std::unique_ptr<enchant::Dict>;
- auto CheckProvider(DictPtr &validator, const std::string &provider) {
- auto p = validator->get_provider_name();
- std::transform(begin(p), end(p), begin(p), ::tolower);
- return (p.find(provider) == 0); // startsWith.
- }
- auto IsHebrew(const QString &word) {
- // Words with mixed scripts will be automatically ignored,
- // so this check should be fine.
- return ::Spellchecker::WordScript(word) == QChar::Script_Hebrew;
- }
- class EnchantSpellChecker {
- public:
- auto knownLanguages();
- bool checkSpelling(const QString &word);
- auto findSuggestions(const QString &word);
- void addWord(const QString &wordToAdd);
- void ignoreWord(const QString &word);
- void removeWord(const QString &word);
- bool isWordInDictionary(const QString &word);
- static EnchantSpellChecker *instance();
- private:
- EnchantSpellChecker();
- EnchantSpellChecker(const EnchantSpellChecker&) = delete;
- EnchantSpellChecker& operator =(const EnchantSpellChecker&) = delete;
- std::unique_ptr<enchant::Broker> _brokerHandle;
- std::vector<DictPtr> _validators;
- std::vector<not_null<enchant::Dict*>> _hspells;
- };
- EnchantSpellChecker::EnchantSpellChecker() {
- if (!enchant::loader::do_explicit_linking()) return;
- std::set<std::string> langs;
- _brokerHandle = std::make_unique<enchant::Broker>();
- _brokerHandle->list_dicts([](
- const char *language,
- const char *provider,
- const char *description,
- const char *filename,
- void *our_payload) {
- static_cast<decltype(langs)*>(our_payload)->insert(language);
- }, &langs);
- _validators.reserve(langs.size());
- try {
- std::string langTag = QLocale::system().name().toStdString();
- _brokerHandle->set_ordering(langTag, kOrdering);
- _validators.push_back(DictPtr(_brokerHandle->request_dict(langTag)));
- langs.erase(langTag);
- } catch (const enchant::Exception &e) {
- // no first dictionary found
- }
- auto mySpellCount = 0;
- for (const std::string &language : langs) {
- try {
- _brokerHandle->set_ordering(language, kOrdering);
- auto validator = DictPtr(_brokerHandle->request_dict(language));
- if (!validator) {
- continue;
- }
- if (CheckProvider(validator, kHspell)) {
- _hspells.push_back(validator.get());
- }
- if (CheckProvider(validator, kMySpell)
- || CheckProvider(validator, kHunspell)) {
- if (mySpellCount > kMaxMySpellCount) {
- continue;
- } else {
- mySpellCount++;
- }
- }
- _validators.push_back(std::move(validator));
- if (_validators.size() > kMaxValidators) {
- break;
- }
- } catch (const enchant::Exception &e) {
- DEBUG_LOG(("Catch after request_dict: %1").arg(e.what()));
- }
- }
- }
- EnchantSpellChecker *EnchantSpellChecker::instance() {
- static EnchantSpellChecker capsule;
- return &capsule;
- }
- auto EnchantSpellChecker::knownLanguages() {
- return _validators | ranges::views::transform([](const auto &validator) {
- return QString(validator->get_lang().c_str());
- }) | ranges::to_vector;
- }
- bool EnchantSpellChecker::checkSpelling(const QString &word) {
- auto w = word.toStdString();
- const auto checkWord = [&](const auto &validator, auto w) {
- try {
- return validator->check(w);
- } catch (const enchant::Exception &e) {
- DEBUG_LOG(("Catch after check '%1': %2").arg(word, e.what()));
- return true;
- }
- };
- if (IsHebrew(word) && _hspells.size()) {
- return ranges::any_of(_hspells, [&](const auto &validator) {
- return checkWord(validator, w);
- });
- }
- return ranges::any_of(_validators, [&](const auto &validator) {
- // Hspell is the spell checker that only checks words in Hebrew.
- // It returns 'true' for any non-Hebrew word,
- // so we should skip Hspell if a word is not in Hebrew.
- if (ranges::any_of(_hspells, [&](auto &v) {
- return v == validator.get();
- })) {
- return false;
- }
- if (validator->get_lang().find("uk") == 0) {
- return false;
- }
- return checkWord(validator, w);
- }) || _validators.empty();
- }
- auto EnchantSpellChecker::findSuggestions(const QString &word) {
- const auto wordScript = ::Spellchecker::WordScript(word);
- auto w = word.toStdString();
- std::vector<QString> result;
- if (!_validators.size()) {
- return result;
- }
- const auto convertSuggestions = [&](auto suggestions) {
- for (const auto &replacement : suggestions) {
- if (result.size() >= kMaxSuggestions) {
- break;
- }
- if (!replacement.empty()) {
- result.push_back(replacement.c_str());
- }
- }
- };
- if (word.size() >= kMaxWordLength) {
- // The first element is the validator of the system language.
- auto *v = _validators[0].get();
- const auto lang = QString::fromStdString(v->get_lang());
- if (wordScript == ::Spellchecker::LocaleToScriptCode(lang)) {
- convertSuggestions(v->suggest(w));
- }
- return result;
- }
- if (IsHebrew(word) && _hspells.size()) {
- for (const auto &h : _hspells) {
- convertSuggestions(h->suggest(w));
- if (result.size()) {
- return result;
- }
- }
- }
- for (const auto &validator : _validators) {
- const auto lang = QString::fromStdString(validator->get_lang());
- if (wordScript != ::Spellchecker::LocaleToScriptCode(lang)) {
- continue;
- }
- convertSuggestions(validator->suggest(w));
- if (!result.empty()) {
- break;
- }
- }
- return result;
- }
- void EnchantSpellChecker::addWord(const QString &wordToAdd) {
- auto word = wordToAdd.toStdString();
- auto &&first = _validators.at(0);
- first->add(word);
- first->add_to_session(word);
- }
- void EnchantSpellChecker::ignoreWord(const QString &word) {
- _validators.at(0)->add_to_session(word.toStdString());
- }
- void EnchantSpellChecker::removeWord(const QString &word) {
- auto w = word.toStdString();
- for (const auto &validator : _validators) {
- validator->remove_from_session(w);
- validator->remove(w);
- }
- }
- bool EnchantSpellChecker::isWordInDictionary(const QString &word) {
- auto w = word.toStdString();
- return ranges::any_of(_validators, [&w](const auto &validator) {
- return validator->is_added(w);
- });
- }
- } // namespace
- void Init() {
- }
- std::vector<QString> ActiveLanguages() {
- return EnchantSpellChecker::instance()->knownLanguages();
- }
- void UpdateLanguages(std::vector<int> languages) {
- ::Spellchecker::UpdateSupportedScripts(ActiveLanguages());
- crl::async([=] {
- const auto result = ActiveLanguages();
- crl::on_main([=] {
- ::Spellchecker::UpdateSupportedScripts(result);
- });
- });
- }
- bool CheckSpelling(const QString &wordToCheck) {
- return EnchantSpellChecker::instance()->checkSpelling(wordToCheck);
- }
- void FillSuggestionList(
- const QString &wrongWord,
- std::vector<QString> *variants) {
- *variants = EnchantSpellChecker::instance()->findSuggestions(wrongWord);
- }
- void AddWord(const QString &word) {
- EnchantSpellChecker::instance()->addWord(word);
- }
- void RemoveWord(const QString &word) {
- EnchantSpellChecker::instance()->removeWord(word);
- }
- void IgnoreWord(const QString &word) {
- EnchantSpellChecker::instance()->ignoreWord(word);
- }
- bool IsWordInDictionary(const QString &wordToCheck) {
- return EnchantSpellChecker::instance()->isWordInDictionary(wordToCheck);
- }
- void CheckSpellingText(
- const QString &text,
- MisspelledWords *misspelledWords) {
- *misspelledWords = ::Spellchecker::RangesFromText(
- text,
- ::Spellchecker::CheckSkipAndSpell);
- }
- bool IsSystemSpellchecker() {
- return true;
- }
- } // namespace Platform::Spellchecker
|