Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 78 additions & 39 deletions spellcheck/platform/linux/spellcheck_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ 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>;

Expand All @@ -40,22 +34,26 @@ auto IsHebrew(const QString &word) {
class EnchantSpellChecker {
public:
auto knownLanguages();
auto availableLanguages();
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);
void updateLanguages(std::vector<int> languages);
static EnchantSpellChecker *instance();

private:
EnchantSpellChecker();
EnchantSpellChecker(const EnchantSpellChecker&) = delete;
EnchantSpellChecker& operator =(const EnchantSpellChecker&) = delete;

void loadValidators(const std::vector<std::string> &langs);

std::unique_ptr<enchant::Broker> _brokerHandle;
std::vector<DictPtr> _validators;

std::vector<std::string> _availableLanguages;
std::vector<not_null<enchant::Dict*>> _hspells;
};

Expand All @@ -71,38 +69,41 @@ EnchantSpellChecker::EnchantSpellChecker() {
void *our_payload) {
static_cast<decltype(langs)*>(our_payload)->insert(language);
}, &langs);

_availableLanguages = std::vector<std::string>(langs.begin(), langs.end());
}

void EnchantSpellChecker::loadValidators(const std::vector<std::string> &langs) {
_validators.clear();
_hspells.clear();
_validators.reserve(langs.size());

// Try the system language first.
try {
std::string langTag = QLocale::system().name().toStdString();
_brokerHandle->set_ordering(langTag, kOrdering);
_validators.push_back(DictPtr(_brokerHandle->request_dict(langTag)));
langs.erase(langTag);
if (ranges::contains(langs, langTag)) {
_validators.push_back(DictPtr(_brokerHandle->request_dict(langTag)));
}
} catch (const enchant::Exception &e) {
// no first dictionary found
}
auto mySpellCount = 0;

for (const std::string &language : langs) {
// Skip system language if already added.
if (!_validators.empty()
&& _validators[0]->get_lang() == language) {
continue;
}

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()));
}
Expand Down Expand Up @@ -146,9 +147,6 @@ bool EnchantSpellChecker::checkSpelling(const QString &word) {
})) {
return false;
}
if (validator->get_lang().find("uk") == 0) {
return false;
}
return checkWord(validator, w);
}) || _validators.empty();
}
Expand All @@ -172,16 +170,6 @@ auto EnchantSpellChecker::findSuggestions(const QString &word) {
}
};

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));
Expand All @@ -190,16 +178,27 @@ auto EnchantSpellChecker::findSuggestions(const QString &word) {
}
}
}

// Collect suggestions from all enabled dictionaries.
std::set<std::string> uniqueSuggestions; // Set avoids duplicates.
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;
const auto suggestions = validator->suggest(w);
for (const auto &suggestion : suggestions) {
if (!suggestion.empty()) {
uniqueSuggestions.insert(suggestion);
}
}
}

// Convert set to result vector.
convertSuggestions(std::vector<std::string>(
uniqueSuggestions.begin(),
uniqueSuggestions.end()));

return result;
}

Expand Down Expand Up @@ -229,6 +228,37 @@ bool EnchantSpellChecker::isWordInDictionary(const QString &word) {
});
}

auto EnchantSpellChecker::availableLanguages() {
return _availableLanguages | ranges::views::transform([](const auto &locale) {
return QString::fromStdString(locale);
}) | ranges::to_vector;
}

void EnchantSpellChecker::updateLanguages(std::vector<int> languages) {
if (languages.empty()) {
// Empty list means disable all
loadValidators({});
return;
}

// Convert language IDs to locale strings.
// Load ALL available variants for each requested langId.
// E.g., if en_GB is requested, load both en_GB and en_GB-large.
std::vector<std::string> requestedLocales;
for (const auto langId : languages) {
// Find all available locales that match this langId
for (const auto &availableLocale : _availableLanguages) {
const auto availableLangId = ::Spellchecker::LangIdFromLocale(
QString::fromStdString(availableLocale));
if (availableLangId == langId) {
requestedLocales.push_back(availableLocale);
}
}
}

loadValidators(requestedLocales);
}

} // namespace

void Init() {
Expand All @@ -238,7 +268,12 @@ std::vector<QString> ActiveLanguages() {
return EnchantSpellChecker::instance()->knownLanguages();
}

std::vector<QString> AvailableLanguages() {
return EnchantSpellChecker::instance()->availableLanguages();
}

void UpdateLanguages(std::vector<int> languages) {
EnchantSpellChecker::instance()->updateLanguages(languages);
::Spellchecker::UpdateSupportedScripts(ActiveLanguages());
crl::async([=] {
const auto result = ActiveLanguages();
Expand Down Expand Up @@ -286,4 +321,8 @@ bool IsSystemSpellchecker() {
return true;
}

bool SupportsToggleDictionaries() {
return true;
}

} // namespace Platform::Spellchecker
12 changes: 11 additions & 1 deletion spellcheck/platform/mac/spellcheck_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ void Init() {
return SystemLanguages();
}

std::vector<QString> AvailableLanguages() {
// SupportsToggleDictionaries() returns false; not supported.
return {};
}

bool CheckSpelling(const QString &word) {
if (@available(macOS 10.14, *)) {
return [SharedSpellChecker()
Expand Down Expand Up @@ -176,8 +181,13 @@ bool IsSystemSpellchecker() {
return true;
}

bool SupportsToggleDictionaries() {
// Apparently not fusible; can only be toggled in system settings.
return false;
}

void UpdateLanguages(std::vector<int> languages) {
::Spellchecker::UpdateSupportedScripts(SystemLanguages());
// SupportsToggleDictionaries() returns false; not supported.
}

} // namespace Platform::Spellchecker
2 changes: 2 additions & 0 deletions spellcheck/platform/platform_spellcheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ namespace Platform::Spellchecker {
constexpr auto kMaxSuggestions = 5;

[[nodiscard]] bool IsSystemSpellchecker();
[[nodiscard]] bool SupportsToggleDictionaries();
[[nodiscard]] bool CheckSpelling(const QString &wordToCheck);
[[nodiscard]] bool IsWordInDictionary(const QString &wordToCheck);

void Init();
std::vector<QString> ActiveLanguages();
std::vector<QString> AvailableLanguages();
void FillSuggestionList(
const QString &wrongWord,
std::vector<QString> *optionalSuggestions);
Expand Down
17 changes: 17 additions & 0 deletions spellcheck/platform/win/spellcheck_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class WindowsSpellChecker {
MisspelledWords *misspelledWordRanges,
int offset);
[[nodiscard]] std::vector<QString> systemLanguages();
[[nodiscard]] std::vector<QString> availableLanguages();
void chunkedCheckSpellingText(
QStringView textView,
MisspelledWords *misspelledWords);
Expand Down Expand Up @@ -275,6 +276,11 @@ std::vector<QString> WindowsSpellChecker::systemLanguages() {
return ranges::views::keys(_spellcheckerMap) | ranges::to_vector;
}

std::vector<QString> WindowsSpellChecker::availableLanguages() {
// SupportsToggleDictionaries() returns false; not implemented.
return {};
}
Comment on lines +279 to +282
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WhyNotHugo I dont like these names. If it used for dictionaries, so let it be like "available languages for dictionaries".


void WindowsSpellChecker::chunkedCheckSpellingText(
QStringView textView,
MisspelledWords *misspelledWords) {
Expand Down Expand Up @@ -338,13 +344,24 @@ bool IsSystemSpellchecker() {
return IsWindows8OrGreater();
}

bool SupportsToggleDictionaries() {
// Not implemented for Windows.
// Feasible with native spellchecker, unknown with hunspell.
return false;
}

std::vector<QString> ActiveLanguages() {
if (IsSystemSpellchecker()) {
return SharedSpellChecker().systemLanguages();
}
return ThirdParty::ActiveLanguages();
}

std::vector<QString> AvailableLanguages() {
// SupportsToggleDictionaries() returns false; not implemented.
return {};
}

bool CheckSpelling(const QString &wordToCheck) {
if (!IsSystemSpellchecker()) {
return ThirdParty::CheckSpelling(wordToCheck);
Expand Down
19 changes: 19 additions & 0 deletions spellcheck/spellcheck_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,23 @@ QLocale LocaleFromLangId(int langId) {
return QLocale(lang, country);
}

int LangIdFromLocale(const QString &locale) {
const auto qlocale = QLocale(locale);
const auto lang = qlocale.language();
const auto country = qlocale.country();

if (country != QLocale::AnyCountry) {
constexpr QLocale::Language kLangsForLWC[] = { QLocale::English, QLocale::Portuguese };
constexpr QLocale::Country kDefaultCountries[] = { QLocale::UnitedStates, QLocale::Brazil };

if (ranges::contains(kLangsForLWC, lang)
&& ranges::contains(kDefaultCountries, country)) {
return int(lang);
}

return (int(lang) * kFactor) + int(country);
}
return int(lang);
}

} // namespace Spellchecker
1 change: 1 addition & 0 deletions spellcheck/spellcheck_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ MisspelledWords RangesFromText(
bool CheckSkipAndSpell(const QString &word);

QLocale LocaleFromLangId(int langId);
int LangIdFromLocale(const QString &locale);

void UpdateSupportedScripts(std::vector<QString> languages);
rpl::producer<> SupportedScriptsChanged();
Expand Down
3 changes: 1 addition & 2 deletions spellcheck/spelling_highlighter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -824,8 +824,7 @@ void SpellingHighlighter::fillSpellcheckerMenu(
not_null<QMenu*> menu,
QTextCursor cursorForPosition,
FnMut<void(int firstSuggestionIndex)> show) {
const auto customItem = !Platform::Spellchecker::IsSystemSpellchecker()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove !Platform::Spellchecker::IsSystemSpellchecker()?

&& _customContextMenuItem.has_value();
const auto customItem = _customContextMenuItem.has_value();

cursorForPosition.select(QTextCursor::WordUnderCursor);

Expand Down
12 changes: 11 additions & 1 deletion spellcheck/third_party/spellcheck_hunspell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,18 @@ bool IsSystemSpellchecker() {
return false;
}

bool SupportsToggleDictionaries() {
return false;
}

std::vector<QString> AvailableLanguages() {
// SupportsToggleDictionaries() returns false; not supported.
// Maybe we can enumerate $WORKINGDIR}/$LANG/$LANG.{aff,dic}?
return {};
}

void UpdateLanguages(std::vector<int> languages) {
ThirdParty::UpdateLanguages(languages);
// SupportsToggleDictionaries() returns false; not supported.
}

} // namespace Platform::Spellchecker