spellcheck_mac.mm 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "spellcheck/platform/mac/spellcheck_mac.h"
  8. #include "base/platform/mac/base_utilities_mac.h"
  9. #import <AppKit/NSSpellChecker.h>
  10. #import <QuartzCore/QuartzCore.h>
  11. #include <QtCore/QLocale>
  12. using Platform::Q2NSString;
  13. using Platform::NS2QString;
  14. namespace {
  15. // +[NSSpellChecker sharedSpellChecker] can throw exceptions depending
  16. // on the state of the pasteboard, or possibly as a result of
  17. // third-party code (when setting up services entries). The following
  18. // receives nil if an exception is thrown, in which case
  19. // spell-checking will not work, but it also will not crash the
  20. // browser.
  21. NSSpellChecker *SharedSpellChecker() {
  22. @try {
  23. return [NSSpellChecker sharedSpellChecker];
  24. } @catch (id exception) {
  25. return nil;
  26. }
  27. }
  28. inline auto SystemLanguages() {
  29. static auto languages = std::vector<QString>();
  30. if (!languages.size()) {
  31. const auto uiLanguages = QLocale::system().uiLanguages();
  32. languages = (
  33. uiLanguages
  34. ) | ranges::views::transform([&](const auto &lang) {
  35. return lang.left(std::max(lang.indexOf('_'), lang.indexOf('-')));
  36. }) | ranges::views::unique | ranges::to_vector;
  37. }
  38. return languages;
  39. }
  40. } // namespace
  41. namespace Platform::Spellchecker {
  42. void Init() {
  43. }
  44. std::vector<QString> ActiveLanguages() {
  45. return SystemLanguages();
  46. }
  47. bool CheckSpelling(const QString &wordToCheck) {
  48. const auto wordLength = wordToCheck.length();
  49. NSArray<NSTextCheckingResult*> *spellRanges =
  50. [SharedSpellChecker()
  51. checkString:Q2NSString(std::move(wordToCheck))
  52. range:NSMakeRange(0, wordLength)
  53. types:NSTextCheckingTypeSpelling
  54. options:nil
  55. inSpellDocumentWithTag:0
  56. orthography:nil
  57. wordCount:nil];
  58. // If the length of the misspelled word == 0,
  59. // then there is no misspelled word.
  60. return (spellRanges.count == 0);
  61. }
  62. // There's no need to check the language on the Mac.
  63. void CheckSpellingText(
  64. const QString &text,
  65. MisspelledWords *misspelledWords) {
  66. // Probably never gonna be defined.
  67. #ifdef SPELLCHECKER_MAC_AUTO_CHECK_TEXT
  68. NSArray<NSTextCheckingResult*> *spellRanges =
  69. [SharedSpellChecker()
  70. checkString:Q2NSString(text)
  71. range:NSMakeRange(0, text.length())
  72. types:NSTextCheckingTypeSpelling
  73. options:nil
  74. inSpellDocumentWithTag:0
  75. orthography:nil
  76. wordCount:nil];
  77. misspelledWords->reserve(spellRanges.count);
  78. for (NSTextCheckingResult *result in spellRanges) {
  79. if (result.resultType != NSTextCheckingTypeSpelling) {
  80. continue;
  81. }
  82. misspelledWords->push_back({
  83. result.range.location,
  84. result.range.length});
  85. }
  86. #else
  87. // Known Issue: Despite the explicitly defined parameter,
  88. // the correctness of a single word depends on the rest of the text.
  89. // For example, "testt testtttyy" - this string will be marked as correct.
  90. // But at the same time "testtttyy" will be marked as misspelled word.
  91. // So we have to manually split the text into words and check them separately.
  92. *misspelledWords = ::Spellchecker::RangesFromText(
  93. text,
  94. ::Spellchecker::CheckSkipAndSpell);
  95. #endif
  96. }
  97. void FillSuggestionList(
  98. const QString &wrongWord,
  99. std::vector<QString> *optionalSuggestions) {
  100. const auto wordRange = NSMakeRange(0, wrongWord.length());
  101. auto *nsWord = Q2NSString(wrongWord);
  102. const auto guesses = [&](auto *lang) {
  103. return [SharedSpellChecker() guessesForWordRange:wordRange
  104. inString:nsWord
  105. language:lang
  106. inSpellDocumentWithTag:0];
  107. };
  108. auto wordCounter = 0;
  109. const auto wordScript = ::Spellchecker::WordScript(wrongWord);
  110. optionalSuggestions->reserve(kMaxSuggestions);
  111. // for (NSString *lang in [SharedSpellChecker() availableLanguages]) {
  112. for (const auto &lang : SystemLanguages()) {
  113. if (wordScript != ::Spellchecker::LocaleToScriptCode(lang)) {
  114. continue;
  115. }
  116. for (NSString *guess in guesses(Q2NSString(lang))) {
  117. optionalSuggestions->push_back(NS2QString(guess));
  118. if (++wordCounter >= kMaxSuggestions) {
  119. return;
  120. }
  121. }
  122. }
  123. }
  124. void AddWord(const QString &word) {
  125. [SharedSpellChecker() learnWord:Q2NSString(word)];
  126. }
  127. void RemoveWord(const QString &word) {
  128. [SharedSpellChecker() unlearnWord:Q2NSString(word)];
  129. }
  130. void IgnoreWord(const QString &word) {
  131. [SharedSpellChecker() ignoreWord:Q2NSString(word)
  132. inSpellDocumentWithTag:0];
  133. }
  134. bool IsWordInDictionary(const QString &wordToCheck) {
  135. return [SharedSpellChecker() hasLearnedWord:Q2NSString(wordToCheck)];
  136. }
  137. bool IsSystemSpellchecker() {
  138. return true;
  139. }
  140. void UpdateLanguages(std::vector<int> languages) {
  141. ::Spellchecker::UpdateSupportedScripts(SystemLanguages());
  142. }
  143. } // namespace Platform::Spellchecker