init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "spellcheck/platform/mac/spellcheck_mac.h"
|
||||
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
#include "spellcheck/third_party/language_cld3.h"
|
||||
|
||||
#import <AppKit/NSSpellChecker.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#include <QtCore/QLocale>
|
||||
#include <set>
|
||||
|
||||
using Platform::Q2NSString;
|
||||
using Platform::NS2QString;
|
||||
|
||||
namespace {
|
||||
|
||||
// +[NSSpellChecker sharedSpellChecker] can throw exceptions depending
|
||||
// on the state of the pasteboard, or possibly as a result of
|
||||
// third-party code (when setting up services entries). The following
|
||||
// receives nil if an exception is thrown, in which case
|
||||
// spell-checking will not work, but it also will not crash the
|
||||
// browser.
|
||||
NSSpellChecker *SharedSpellChecker() {
|
||||
@try {
|
||||
return [NSSpellChecker sharedSpellChecker];
|
||||
} @catch (id exception) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
inline auto SystemLanguages() {
|
||||
static auto languages = std::vector<QString>();
|
||||
if (!languages.size()) {
|
||||
const auto uiLanguages = QLocale::system().uiLanguages();
|
||||
languages = (
|
||||
uiLanguages
|
||||
) | ranges::views::transform([&](const auto &lang) {
|
||||
return lang.left(std::max(lang.indexOf('_'), lang.indexOf('-')));
|
||||
}) | ranges::views::unique | ranges::to_vector;
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto RegionalVariantMap() {
|
||||
static auto map = std::map<QString, QString>();
|
||||
static auto initialized = false;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
const auto uiLanguages = QLocale::system().uiLanguages();
|
||||
for (const auto &ui : uiLanguages) {
|
||||
if (ui == u"en-AU"_q || ui == u"en_AU"_q) {
|
||||
map[u"en"_q] = ui;
|
||||
} else if (ui == u"en-GB"_q || ui == u"en_GB"_q) {
|
||||
if (!map.contains(u"en"_q)) {
|
||||
map[u"en"_q] = ui;
|
||||
}
|
||||
} else if (ui == u"en-CA"_q || ui == u"en_CA"_q) {
|
||||
if (!map.contains(u"en"_q)) {
|
||||
map[u"en"_q] = ui;
|
||||
}
|
||||
} else if (ui == u"pt-BR"_q || ui == u"pt_BR"_q) {
|
||||
map[u"pt"_q] = ui;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto PreferredRegionalVariant(const QString &lang) {
|
||||
const auto &map = RegionalVariantMap();
|
||||
if (const auto it = map.find(lang); it != map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return lang;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Platform::Spellchecker {
|
||||
|
||||
void Init() {
|
||||
}
|
||||
|
||||
std::vector<QString> ActiveLanguages() {
|
||||
return SystemLanguages();
|
||||
}
|
||||
|
||||
bool CheckSpelling(const QString &word) {
|
||||
if (@available(macOS 10.14, *)) {
|
||||
const auto lang = PreferredRegionalVariant(
|
||||
Language::Recognize(word).twoLetterCode());
|
||||
return [SharedSpellChecker()
|
||||
checkSpellingOfString:Q2NSString(word)
|
||||
startingAt:0
|
||||
language:Q2NSString(lang)
|
||||
wrap:false
|
||||
inSpellDocumentWithTag:0
|
||||
wordCount:nil].location == NSNotFound;
|
||||
}
|
||||
const auto wordLength = word.length();
|
||||
NSArray<NSTextCheckingResult*> *spellRanges =
|
||||
[SharedSpellChecker()
|
||||
checkString:Q2NSString(word)
|
||||
range:NSMakeRange(0, wordLength)
|
||||
types:NSTextCheckingTypeSpelling
|
||||
options:nil
|
||||
inSpellDocumentWithTag:0
|
||||
orthography:nil
|
||||
wordCount:nil];
|
||||
// If the length of the misspelled word == 0,
|
||||
// then there is no misspelled word.
|
||||
return (spellRanges.count == 0);
|
||||
}
|
||||
|
||||
|
||||
// There's no need to check the language on the Mac.
|
||||
void CheckSpellingText(
|
||||
const QString &text,
|
||||
MisspelledWords *misspelledWords) {
|
||||
// Probably never gonna be defined.
|
||||
#ifdef SPELLCHECKER_MAC_AUTO_CHECK_TEXT
|
||||
|
||||
NSArray<NSTextCheckingResult*> *spellRanges =
|
||||
[SharedSpellChecker()
|
||||
checkString:Q2NSString(text)
|
||||
range:NSMakeRange(0, text.length())
|
||||
types:NSTextCheckingTypeSpelling
|
||||
options:nil
|
||||
inSpellDocumentWithTag:0
|
||||
orthography:nil
|
||||
wordCount:nil];
|
||||
|
||||
misspelledWords->reserve(spellRanges.count);
|
||||
for (NSTextCheckingResult *result in spellRanges) {
|
||||
if (result.resultType != NSTextCheckingTypeSpelling) {
|
||||
continue;
|
||||
}
|
||||
misspelledWords->push_back({
|
||||
result.range.location,
|
||||
result.range.length});
|
||||
}
|
||||
|
||||
#else
|
||||
// Known Issue: Despite the explicitly defined parameter,
|
||||
// the correctness of a single word depends on the rest of the text.
|
||||
// For example, "testt testtttyy" - this string will be marked as correct.
|
||||
// But at the same time "testtttyy" will be marked as misspelled word.
|
||||
|
||||
// So we have to manually split the text into words and check them separately.
|
||||
*misspelledWords = ::Spellchecker::RangesFromText(
|
||||
text,
|
||||
::Spellchecker::CheckSkipAndSpell);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void FillSuggestionList(
|
||||
const QString &wrongWord,
|
||||
std::vector<QString> *optionalSuggestions) {
|
||||
|
||||
const auto wordRange = NSMakeRange(0, wrongWord.length());
|
||||
auto *nsWord = Q2NSString(wrongWord);
|
||||
const auto guesses = [&](auto *lang) {
|
||||
return [SharedSpellChecker() guessesForWordRange:wordRange
|
||||
inString:nsWord
|
||||
language:lang
|
||||
inSpellDocumentWithTag:0];
|
||||
};
|
||||
|
||||
auto wordCounter = 0;
|
||||
const auto wordScript = ::Spellchecker::WordScript(wrongWord);
|
||||
optionalSuggestions->reserve(kMaxSuggestions);
|
||||
|
||||
// for (NSString *lang in [SharedSpellChecker() availableLanguages]) {
|
||||
for (const auto &lang : SystemLanguages()) {
|
||||
if (wordScript != ::Spellchecker::LocaleToScriptCode(lang)) {
|
||||
continue;
|
||||
}
|
||||
for (NSString *guess in guesses(Q2NSString(lang))) {
|
||||
optionalSuggestions->push_back(NS2QString(guess));
|
||||
if (++wordCounter >= kMaxSuggestions) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddWord(const QString &word) {
|
||||
[SharedSpellChecker() learnWord:Q2NSString(word)];
|
||||
}
|
||||
|
||||
void RemoveWord(const QString &word) {
|
||||
[SharedSpellChecker() unlearnWord:Q2NSString(word)];
|
||||
}
|
||||
|
||||
void IgnoreWord(const QString &word) {
|
||||
[SharedSpellChecker() ignoreWord:Q2NSString(word)
|
||||
inSpellDocumentWithTag:0];
|
||||
}
|
||||
|
||||
bool IsWordInDictionary(const QString &wordToCheck) {
|
||||
return [SharedSpellChecker() hasLearnedWord:Q2NSString(wordToCheck)];
|
||||
}
|
||||
|
||||
bool IsSystemSpellchecker() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateLanguages(std::vector<int> languages) {
|
||||
::Spellchecker::UpdateSupportedScripts(SystemLanguages());
|
||||
}
|
||||
|
||||
} // namespace Platform::Spellchecker
|
||||
Reference in New Issue
Block a user