1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // Integration with OS X built-in spellchecker.
7 #include "chrome/browser/spellchecker/spellcheck_platform.h"
9 #import <Cocoa/Cocoa.h>
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/logging.h"
14 #include "base/mac/foundation_util.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/time/time.h"
17 #include "chrome/common/spellcheck_common.h"
18 #include "chrome/common/spellcheck_messages.h"
19 #include "chrome/common/spellcheck_result.h"
20 #include "content/public/browser/browser_message_filter.h"
21 #include "content/public/browser/browser_thread.h"
23 using base::TimeTicks;
24 using content::BrowserMessageFilter;
25 using content::BrowserThread;
28 // The number of characters in the first part of the language code.
29 const unsigned int kShortLanguageCodeSize = 2;
31 // +[NSSpellChecker sharedSpellChecker] can throw exceptions depending
32 // on the state of the pasteboard, or possibly as a result of
33 // third-party code (when setting up services entries). The following
34 // receives nil if an exception is thrown, in which case
35 // spell-checking will not work, but it also will not crash the
37 NSSpellChecker* SharedSpellChecker() {
39 return [NSSpellChecker sharedSpellChecker];
40 } @catch (id exception) {
45 // A private utility function to convert hunspell language codes to OS X
47 NSString* ConvertLanguageCodeToMac(const std::string& hunspell_lang_code) {
48 NSString* whole_code = base::SysUTF8ToNSString(hunspell_lang_code);
50 if ([whole_code length] > kShortLanguageCodeSize) {
51 NSString* lang_code = [whole_code
52 substringToIndex:kShortLanguageCodeSize];
53 // Add 1 here to skip the underscore.
54 NSString* region_code = [whole_code
55 substringFromIndex:(kShortLanguageCodeSize + 1)];
57 // Check for the special case of en-US and pt-PT, since OS X lists these
58 // as just en and pt respectively.
59 // TODO(pwicks): Find out if there are other special cases for languages
60 // not installed on the system by default. Are there others like pt-PT?
61 if (([lang_code isEqualToString:@"en"] &&
62 [region_code isEqualToString:@"US"]) ||
63 ([lang_code isEqualToString:@"pt"] &&
64 [region_code isEqualToString:@"PT"])) {
68 // Otherwise, just build a string that uses an underscore instead of a
69 // dash between the language and the region code, since this is the
70 // format that OS X uses.
71 NSString* os_x_language =
72 [NSString stringWithFormat:@"%@_%@", lang_code, region_code];
75 // Special case for Polish.
76 if ([whole_code isEqualToString:@"pl"]) {
79 // This is just a language code with the same format as OS X
85 std::string ConvertLanguageCodeFromMac(NSString* lang_code) {
86 // TODO(pwicks):figure out what to do about Multilingual
87 // Guards for strange cases.
88 if ([lang_code isEqualToString:@"en"]) return std::string("en-US");
89 if ([lang_code isEqualToString:@"pt"]) return std::string("pt-PT");
90 if ([lang_code isEqualToString:@"pl_PL"]) return std::string("pl");
92 if ([lang_code length] > kShortLanguageCodeSize &&
93 [lang_code characterAtIndex:kShortLanguageCodeSize] == '_') {
94 return base::SysNSStringToUTF8([NSString stringWithFormat:@"%@-%@",
95 [lang_code substringToIndex:kShortLanguageCodeSize],
96 [lang_code substringFromIndex:(kShortLanguageCodeSize + 1)]]);
98 return base::SysNSStringToUTF8(lang_code);
103 namespace spellcheck_platform {
105 void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) {
106 NSArray* availableLanguages = [SharedSpellChecker() availableLanguages];
107 for (NSString* lang_code in availableLanguages) {
108 spellcheck_languages->push_back(
109 ConvertLanguageCodeFromMac(lang_code));
113 bool SpellCheckerAvailable() {
114 // If this file was compiled, then we know that we are on OS X 10.5 at least
115 // and can safely return true here.
119 bool SpellCheckerProvidesPanel() {
120 // OS X has a Spelling Panel, so we can return true here.
124 bool SpellingPanelVisible() {
125 // This should only be called from the main thread.
126 DCHECK([NSThread currentThread] == [NSThread mainThread]);
127 return [[SharedSpellChecker() spellingPanel] isVisible];
130 void ShowSpellingPanel(bool show) {
132 [[SharedSpellChecker() spellingPanel]
133 performSelectorOnMainThread:@selector(makeKeyAndOrderFront:)
137 [[SharedSpellChecker() spellingPanel]
138 performSelectorOnMainThread:@selector(close)
144 void UpdateSpellingPanelWithMisspelledWord(const base::string16& word) {
145 NSString * word_to_display = base::SysUTF16ToNSString(word);
146 [SharedSpellChecker()
147 performSelectorOnMainThread:
148 @selector(updateSpellingPanelWithMisspelledWord:)
149 withObject:word_to_display
153 bool PlatformSupportsLanguage(const std::string& current_language) {
154 // First, convert the language to an OS X language code.
155 NSString* mac_lang_code = ConvertLanguageCodeToMac(current_language);
157 // Then grab the languages available.
158 NSArray* availableLanguages = [SharedSpellChecker() availableLanguages];
160 // Return true if the given language is supported by OS X.
161 return [availableLanguages containsObject:mac_lang_code];
164 void SetLanguage(const std::string& lang_to_set) {
165 // Do not set any language right now, since Chrome should honor the
166 // system spellcheck settings. (http://crbug.com/166046)
167 // Fix this once Chrome actually allows setting a spellcheck language
168 // in chrome://settings.
169 // NSString* NS_lang_to_set = ConvertLanguageCodeToMac(lang_to_set);
170 // [SharedSpellChecker() setLanguage:NS_lang_to_set];
173 static int last_seen_tag_;
175 bool CheckSpelling(const base::string16& word_to_check, int tag) {
176 last_seen_tag_ = tag;
178 // -[NSSpellChecker checkSpellingOfString] returns an NSRange that
179 // we can look at to determine if a word is misspelled.
180 NSRange spell_range = {0,0};
182 // Convert the word to an NSString.
183 NSString* NS_word_to_check = base::SysUTF16ToNSString(word_to_check);
184 // Check the spelling, starting at the beginning of the word.
185 spell_range = [SharedSpellChecker()
186 checkSpellingOfString:NS_word_to_check startingAt:0
187 language:nil wrap:NO inSpellDocumentWithTag:tag
190 // If the length of the misspelled word == 0,
191 // then there is no misspelled word.
192 bool word_correct = (spell_range.length == 0);
196 void FillSuggestionList(const base::string16& wrong_word,
197 std::vector<base::string16>* optional_suggestions) {
198 NSString* ns_wrong_word = base::SysUTF16ToNSString(wrong_word);
199 // The suggested words for |wrong_word|.
200 // TODO(groby): guessesForWord: has been deprecated since OSX 10.6.
201 // http://www.crbug.com/479014.
202 #pragma clang diagnostic push
203 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
204 NSArray* guesses = [SharedSpellChecker() guessesForWord:ns_wrong_word];
205 #pragma clang diagnostic pop
207 for (NSString* guess in guesses) {
208 optional_suggestions->push_back(base::SysNSStringToUTF16(guess));
209 if (++i >= chrome::spellcheck_common::kMaxSuggestions)
214 void AddWord(const base::string16& word) {
215 NSString* word_to_add = base::SysUTF16ToNSString(word);
216 [SharedSpellChecker() learnWord:word_to_add];
219 void RemoveWord(const base::string16& word) {
220 NSString *word_to_remove = base::SysUTF16ToNSString(word);
221 [SharedSpellChecker() unlearnWord:word_to_remove];
224 int GetDocumentTag() {
225 NSInteger doc_tag = [NSSpellChecker uniqueSpellDocumentTag];
226 return static_cast<int>(doc_tag);
229 void IgnoreWord(const base::string16& word) {
230 [SharedSpellChecker() ignoreWord:base::SysUTF16ToNSString(word)
231 inSpellDocumentWithTag:last_seen_tag_];
234 void CloseDocumentWithTag(int tag) {
235 [SharedSpellChecker() closeSpellDocumentWithTag:static_cast<NSInteger>(tag)];
238 void RequestTextCheck(int document_tag,
239 const base::string16& text,
240 TextCheckCompleteCallback callback) {
241 NSString* text_to_check = base::SysUTF16ToNSString(text);
242 NSRange range_to_check = NSMakeRange(0, [text_to_check length]);
244 [SharedSpellChecker()
245 requestCheckingOfString:text_to_check
247 types:NSTextCheckingTypeSpelling
249 inSpellDocumentWithTag:document_tag
250 completionHandler:^(NSInteger,
254 std::vector<SpellCheckResult> check_results;
255 for (NSTextCheckingResult* result in results) {
256 // Deliberately ignore non-spelling results. OSX at the very least
257 // delivers a result of NSTextCheckingTypeOrthography for the
258 // whole fragment, which underlines the entire checked range.
259 if ([result resultType] != NSTextCheckingTypeSpelling)
262 // In this use case, the spell checker should never
263 // return anything but a single range per result.
264 check_results.push_back(SpellCheckResult(
265 SpellCheckResult::SPELLING,
266 [result range].location,
267 [result range].length));
269 // TODO(groby): Verify we don't need to post from here.
270 callback.Run(check_results);
274 class SpellcheckerStateInternal {
276 SpellcheckerStateInternal();
277 ~SpellcheckerStateInternal();
280 BOOL automaticallyIdentifiesLanguages_;
284 SpellcheckerStateInternal::SpellcheckerStateInternal() {
285 language_ = [SharedSpellChecker() language];
286 automaticallyIdentifiesLanguages_ =
287 [SharedSpellChecker() automaticallyIdentifiesLanguages];
288 [SharedSpellChecker() setLanguage:@"en"];
289 [SharedSpellChecker() setAutomaticallyIdentifiesLanguages:NO];
292 SpellcheckerStateInternal::~SpellcheckerStateInternal() {
293 [SharedSpellChecker() setLanguage:language_];
294 [SharedSpellChecker() setAutomaticallyIdentifiesLanguages:
295 automaticallyIdentifiesLanguages_];
298 ScopedEnglishLanguageForTest::ScopedEnglishLanguageForTest()
299 : state_(new SpellcheckerStateInternal) {
302 ScopedEnglishLanguageForTest::~ScopedEnglishLanguageForTest() {
306 } // namespace spellcheck_platform