1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/extensions/spellcheck/src/mozSpellChecker.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,530 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 + 1.9 +#include "mozSpellChecker.h" 1.10 +#include "nsIServiceManager.h" 1.11 +#include "mozISpellI18NManager.h" 1.12 +#include "nsIStringEnumerator.h" 1.13 +#include "nsICategoryManager.h" 1.14 +#include "nsISupportsPrimitives.h" 1.15 +#include "nsISimpleEnumerator.h" 1.16 + 1.17 +#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1" 1.18 + 1.19 +NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker) 1.20 +NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker) 1.21 + 1.22 +NS_INTERFACE_MAP_BEGIN(mozSpellChecker) 1.23 + NS_INTERFACE_MAP_ENTRY(nsISpellChecker) 1.24 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker) 1.25 + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker) 1.26 +NS_INTERFACE_MAP_END 1.27 + 1.28 +NS_IMPL_CYCLE_COLLECTION(mozSpellChecker, 1.29 + mTsDoc, 1.30 + mPersonalDictionary) 1.31 + 1.32 +mozSpellChecker::mozSpellChecker() 1.33 +{ 1.34 +} 1.35 + 1.36 +mozSpellChecker::~mozSpellChecker() 1.37 +{ 1.38 + if(mPersonalDictionary){ 1.39 + // mPersonalDictionary->Save(); 1.40 + mPersonalDictionary->EndSession(); 1.41 + } 1.42 + mSpellCheckingEngine = nullptr; 1.43 + mPersonalDictionary = nullptr; 1.44 +} 1.45 + 1.46 +nsresult 1.47 +mozSpellChecker::Init() 1.48 +{ 1.49 + mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); 1.50 + 1.51 + mSpellCheckingEngine = nullptr; 1.52 + 1.53 + return NS_OK; 1.54 +} 1.55 + 1.56 +NS_IMETHODIMP 1.57 +mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc) 1.58 +{ 1.59 + mTsDoc = aDoc; 1.60 + mFromStart = aFromStartofDoc; 1.61 + return NS_OK; 1.62 +} 1.63 + 1.64 + 1.65 +NS_IMETHODIMP 1.66 +mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions) 1.67 +{ 1.68 + if(!aSuggestions||!mConverter) 1.69 + return NS_ERROR_NULL_POINTER; 1.70 + 1.71 + int32_t selOffset; 1.72 + int32_t begin,end; 1.73 + nsresult result; 1.74 + result = SetupDoc(&selOffset); 1.75 + bool isMisspelled,done; 1.76 + if (NS_FAILED(result)) 1.77 + return result; 1.78 + 1.79 + while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) 1.80 + { 1.81 + nsString str; 1.82 + result = mTsDoc->GetCurrentTextBlock(&str); 1.83 + 1.84 + if (NS_FAILED(result)) 1.85 + return result; 1.86 + do{ 1.87 + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); 1.88 + if(NS_SUCCEEDED(result)&&(begin != -1)){ 1.89 + const nsAString &currWord = Substring(str, begin, end - begin); 1.90 + result = CheckWord(currWord, &isMisspelled, aSuggestions); 1.91 + if(isMisspelled){ 1.92 + aWord = currWord; 1.93 + mTsDoc->SetSelection(begin, end-begin); 1.94 + // After ScrollSelectionIntoView(), the pending notifications might 1.95 + // be flushed and PresShell/PresContext/Frames may be dead. 1.96 + // See bug 418470. 1.97 + mTsDoc->ScrollSelectionIntoView(); 1.98 + return NS_OK; 1.99 + } 1.100 + } 1.101 + selOffset = end; 1.102 + }while(end != -1); 1.103 + mTsDoc->NextBlock(); 1.104 + selOffset=0; 1.105 + } 1.106 + return NS_OK; 1.107 +} 1.108 + 1.109 +NS_IMETHODIMP 1.110 +mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions) 1.111 +{ 1.112 + nsresult result; 1.113 + bool correct; 1.114 + if(!mSpellCheckingEngine) 1.115 + return NS_ERROR_NULL_POINTER; 1.116 + 1.117 + *aIsMisspelled = false; 1.118 + result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct); 1.119 + NS_ENSURE_SUCCESS(result, result); 1.120 + if(!correct){ 1.121 + if(aSuggestions){ 1.122 + uint32_t count,i; 1.123 + char16_t **words; 1.124 + 1.125 + result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count); 1.126 + NS_ENSURE_SUCCESS(result, result); 1.127 + for(i=0;i<count;i++){ 1.128 + aSuggestions->AppendElement(nsDependentString(words[i])); 1.129 + } 1.130 + 1.131 + if (count) 1.132 + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); 1.133 + } 1.134 + *aIsMisspelled = true; 1.135 + } 1.136 + return NS_OK; 1.137 +} 1.138 + 1.139 +NS_IMETHODIMP 1.140 +mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences) 1.141 +{ 1.142 + if(!mConverter) 1.143 + return NS_ERROR_NULL_POINTER; 1.144 + 1.145 + nsAutoString newWord(aNewWord); // sigh 1.146 + 1.147 + if(aAllOccurrences){ 1.148 + int32_t selOffset; 1.149 + int32_t startBlock,currentBlock,currOffset; 1.150 + int32_t begin,end; 1.151 + bool done; 1.152 + nsresult result; 1.153 + nsAutoString str; 1.154 + 1.155 + // find out where we are 1.156 + result = SetupDoc(&selOffset); 1.157 + if(NS_FAILED(result)) 1.158 + return result; 1.159 + result = GetCurrentBlockIndex(mTsDoc,&startBlock); 1.160 + if(NS_FAILED(result)) 1.161 + return result; 1.162 + 1.163 + //start at the beginning 1.164 + result = mTsDoc->FirstBlock(); 1.165 + currOffset=0; 1.166 + currentBlock = 0; 1.167 + while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) 1.168 + { 1.169 + result = mTsDoc->GetCurrentTextBlock(&str); 1.170 + do{ 1.171 + result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end); 1.172 + if(NS_SUCCEEDED(result)&&(begin != -1)){ 1.173 + if (aOldWord.Equals(Substring(str, begin, end-begin))) { 1.174 + // if we are before the current selection point but in the same block 1.175 + // move the selection point forwards 1.176 + if((currentBlock == startBlock)&&(begin < selOffset)){ 1.177 + selOffset += 1.178 + int32_t(aNewWord.Length()) - int32_t(aOldWord.Length()); 1.179 + if(selOffset < begin) selOffset=begin; 1.180 + } 1.181 + mTsDoc->SetSelection(begin, end-begin); 1.182 + mTsDoc->InsertText(&newWord); 1.183 + mTsDoc->GetCurrentTextBlock(&str); 1.184 + end += (aNewWord.Length() - aOldWord.Length()); // recursion was cute in GEB, not here. 1.185 + } 1.186 + } 1.187 + currOffset = end; 1.188 + }while(currOffset != -1); 1.189 + mTsDoc->NextBlock(); 1.190 + currentBlock++; 1.191 + currOffset=0; 1.192 + } 1.193 + 1.194 + // We are done replacing. Put the selection point back where we found it (or equivalent); 1.195 + result = mTsDoc->FirstBlock(); 1.196 + currentBlock = 0; 1.197 + while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){ 1.198 + mTsDoc->NextBlock(); 1.199 + } 1.200 + 1.201 +//After we have moved to the block where the first occurrence of replace was done, put the 1.202 +//selection to the next word following it. In case there is no word following it i.e if it happens 1.203 +//to be the last word in that block, then move to the next block and put the selection to the 1.204 +//first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock() 1.205 +//and the selection offset of the last occurrence of the replaced word is taken instead of the first 1.206 +//occurrence and things get messed up as reported in the bug 244969 1.207 + 1.208 + if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){ 1.209 + nsString str; 1.210 + result = mTsDoc->GetCurrentTextBlock(&str); 1.211 + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); 1.212 + if(end == -1) 1.213 + { 1.214 + mTsDoc->NextBlock(); 1.215 + selOffset=0; 1.216 + result = mTsDoc->GetCurrentTextBlock(&str); 1.217 + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); 1.218 + mTsDoc->SetSelection(begin, 0); 1.219 + } 1.220 + else 1.221 + mTsDoc->SetSelection(begin, 0); 1.222 + } 1.223 + } 1.224 + else{ 1.225 + mTsDoc->InsertText(&newWord); 1.226 + } 1.227 + return NS_OK; 1.228 +} 1.229 + 1.230 +NS_IMETHODIMP 1.231 +mozSpellChecker::IgnoreAll(const nsAString &aWord) 1.232 +{ 1.233 + if(mPersonalDictionary){ 1.234 + mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get()); 1.235 + } 1.236 + return NS_OK; 1.237 +} 1.238 + 1.239 +NS_IMETHODIMP 1.240 +mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord) 1.241 +{ 1.242 + nsresult res; 1.243 + char16_t empty=0; 1.244 + if (!mPersonalDictionary) 1.245 + return NS_ERROR_NULL_POINTER; 1.246 + res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty); 1.247 + return res; 1.248 +} 1.249 + 1.250 +NS_IMETHODIMP 1.251 +mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord) 1.252 +{ 1.253 + nsresult res; 1.254 + char16_t empty=0; 1.255 + if (!mPersonalDictionary) 1.256 + return NS_ERROR_NULL_POINTER; 1.257 + res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty); 1.258 + return res; 1.259 +} 1.260 + 1.261 +NS_IMETHODIMP 1.262 +mozSpellChecker::GetPersonalDictionary(nsTArray<nsString> *aWordList) 1.263 +{ 1.264 + if(!aWordList || !mPersonalDictionary) 1.265 + return NS_ERROR_NULL_POINTER; 1.266 + 1.267 + nsCOMPtr<nsIStringEnumerator> words; 1.268 + mPersonalDictionary->GetWordList(getter_AddRefs(words)); 1.269 + 1.270 + bool hasMore; 1.271 + nsAutoString word; 1.272 + while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) { 1.273 + words->GetNext(word); 1.274 + aWordList->AppendElement(word); 1.275 + } 1.276 + return NS_OK; 1.277 +} 1.278 + 1.279 +NS_IMETHODIMP 1.280 +mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList) 1.281 +{ 1.282 + nsresult rv; 1.283 + 1.284 + // For catching duplicates 1.285 + nsClassHashtable<nsStringHashKey, nsCString> dictionaries; 1.286 + 1.287 + nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines; 1.288 + rv = GetEngineList(&spellCheckingEngines); 1.289 + NS_ENSURE_SUCCESS(rv, rv); 1.290 + 1.291 + for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { 1.292 + nsCOMPtr<mozISpellCheckingEngine> engine = spellCheckingEngines[i]; 1.293 + 1.294 + uint32_t count = 0; 1.295 + char16_t **words = nullptr; 1.296 + engine->GetDictionaryList(&words, &count); 1.297 + for (uint32_t k = 0; k < count; k++) { 1.298 + nsAutoString dictName; 1.299 + 1.300 + dictName.Assign(words[k]); 1.301 + 1.302 + // Skip duplicate dictionaries. Only take the first one 1.303 + // for each name. 1.304 + if (dictionaries.Get(dictName, nullptr)) 1.305 + continue; 1.306 + 1.307 + dictionaries.Put(dictName, nullptr); 1.308 + 1.309 + if (!aDictionaryList->AppendElement(dictName)) { 1.310 + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); 1.311 + return NS_ERROR_OUT_OF_MEMORY; 1.312 + } 1.313 + } 1.314 + 1.315 + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); 1.316 + } 1.317 + 1.318 + return NS_OK; 1.319 +} 1.320 + 1.321 +NS_IMETHODIMP 1.322 +mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary) 1.323 +{ 1.324 + if (!mSpellCheckingEngine) { 1.325 + aDictionary.AssignLiteral(""); 1.326 + return NS_OK; 1.327 + } 1.328 + 1.329 + nsXPIDLString dictname; 1.330 + mSpellCheckingEngine->GetDictionary(getter_Copies(dictname)); 1.331 + aDictionary = dictname; 1.332 + return NS_OK; 1.333 +} 1.334 + 1.335 +NS_IMETHODIMP 1.336 +mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary) 1.337 +{ 1.338 + // Calls to mozISpellCheckingEngine::SetDictionary might destroy us 1.339 + nsRefPtr<mozSpellChecker> kungFuDeathGrip = this; 1.340 + 1.341 + mSpellCheckingEngine = nullptr; 1.342 + 1.343 + if (aDictionary.IsEmpty()) { 1.344 + return NS_OK; 1.345 + } 1.346 + 1.347 + nsresult rv; 1.348 + nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines; 1.349 + rv = GetEngineList(&spellCheckingEngines); 1.350 + NS_ENSURE_SUCCESS(rv, rv); 1.351 + 1.352 + for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { 1.353 + // We must set mSpellCheckingEngine before we call SetDictionary, since 1.354 + // SetDictionary calls back to this spell checker to check if the 1.355 + // dictionary was set 1.356 + mSpellCheckingEngine = spellCheckingEngines[i]; 1.357 + 1.358 + rv = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get()); 1.359 + 1.360 + if (NS_SUCCEEDED(rv)) { 1.361 + nsCOMPtr<mozIPersonalDictionary> personalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); 1.362 + mSpellCheckingEngine->SetPersonalDictionary(personalDictionary.get()); 1.363 + 1.364 + nsXPIDLString language; 1.365 + nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv)); 1.366 + NS_ENSURE_SUCCESS(rv, rv); 1.367 + return serv->GetUtil(language.get(),getter_AddRefs(mConverter)); 1.368 + } 1.369 + } 1.370 + 1.371 + mSpellCheckingEngine = nullptr; 1.372 + 1.373 + // We could not find any engine with the requested dictionary 1.374 + return NS_ERROR_NOT_AVAILABLE; 1.375 +} 1.376 + 1.377 +NS_IMETHODIMP 1.378 +mozSpellChecker::CheckCurrentDictionary() 1.379 +{ 1.380 + // If the current dictionary has been uninstalled, we need to stop using it. 1.381 + // This happens when there is a current engine, but that engine has no 1.382 + // current dictionary. 1.383 + 1.384 + if (!mSpellCheckingEngine) { 1.385 + // We didn't have a current dictionary 1.386 + return NS_OK; 1.387 + } 1.388 + 1.389 + nsXPIDLString dictname; 1.390 + mSpellCheckingEngine->GetDictionary(getter_Copies(dictname)); 1.391 + 1.392 + if (!dictname.IsEmpty()) { 1.393 + // We still have a current dictionary 1.394 + return NS_OK; 1.395 + } 1.396 + 1.397 + // We had a current dictionary, but it has gone, so we cannot use it anymore. 1.398 + mSpellCheckingEngine = nullptr; 1.399 + return NS_OK; 1.400 +} 1.401 + 1.402 +nsresult 1.403 +mozSpellChecker::SetupDoc(int32_t *outBlockOffset) 1.404 +{ 1.405 + nsresult rv; 1.406 + 1.407 + nsITextServicesDocument::TSDBlockSelectionStatus blockStatus; 1.408 + int32_t selOffset; 1.409 + int32_t selLength; 1.410 + *outBlockOffset = 0; 1.411 + 1.412 + if (!mFromStart) 1.413 + { 1.414 + rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength); 1.415 + if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound)) 1.416 + { 1.417 + switch (blockStatus) 1.418 + { 1.419 + case nsITextServicesDocument::eBlockOutside: // No TB in S, but found one before/after S. 1.420 + case nsITextServicesDocument::eBlockPartial: // S begins or ends in TB but extends outside of TB. 1.421 + // the TS doc points to the block we want. 1.422 + *outBlockOffset = selOffset + selLength; 1.423 + break; 1.424 + 1.425 + case nsITextServicesDocument::eBlockInside: // S extends beyond the start and end of TB. 1.426 + // we want the block after this one. 1.427 + rv = mTsDoc->NextBlock(); 1.428 + *outBlockOffset = 0; 1.429 + break; 1.430 + 1.431 + case nsITextServicesDocument::eBlockContains: // TB contains entire S. 1.432 + *outBlockOffset = selOffset + selLength; 1.433 + break; 1.434 + 1.435 + case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S). 1.436 + default: 1.437 + NS_NOTREACHED("Shouldn't ever get this status"); 1.438 + } 1.439 + } 1.440 + else //failed to get last sel block. Just start at beginning 1.441 + { 1.442 + rv = mTsDoc->FirstBlock(); 1.443 + *outBlockOffset = 0; 1.444 + } 1.445 + 1.446 + } 1.447 + else // we want the first block 1.448 + { 1.449 + rv = mTsDoc->FirstBlock(); 1.450 + mFromStart = false; 1.451 + } 1.452 + return rv; 1.453 +} 1.454 + 1.455 + 1.456 +// utility method to discover which block we're in. The TSDoc interface doesn't give 1.457 +// us this, because it can't assume a read-only document. 1.458 +// shamelessly stolen from nsTextServicesDocument 1.459 +nsresult 1.460 +mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex) 1.461 +{ 1.462 + int32_t blockIndex = 0; 1.463 + bool isDone = false; 1.464 + nsresult result = NS_OK; 1.465 + 1.466 + do 1.467 + { 1.468 + aDoc->PrevBlock(); 1.469 + 1.470 + result = aDoc->IsDone(&isDone); 1.471 + 1.472 + if (!isDone) 1.473 + blockIndex ++; 1.474 + 1.475 + } while (NS_SUCCEEDED(result) && !isDone); 1.476 + 1.477 + *outBlockIndex = blockIndex; 1.478 + 1.479 + return result; 1.480 +} 1.481 + 1.482 +nsresult 1.483 +mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines) 1.484 +{ 1.485 + nsresult rv; 1.486 + bool hasMoreEngines; 1.487 + 1.488 + nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); 1.489 + if (!catMgr) 1.490 + return NS_ERROR_NULL_POINTER; 1.491 + 1.492 + nsCOMPtr<nsISimpleEnumerator> catEntries; 1.493 + 1.494 + // Get contract IDs of registrated external spell-check engines and 1.495 + // append one of HunSpell at the end. 1.496 + rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries)); 1.497 + if (NS_FAILED(rv)) 1.498 + return rv; 1.499 + 1.500 + while (catEntries->HasMoreElements(&hasMoreEngines), hasMoreEngines){ 1.501 + nsCOMPtr<nsISupports> elem; 1.502 + rv = catEntries->GetNext(getter_AddRefs(elem)); 1.503 + 1.504 + nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv); 1.505 + if (NS_FAILED(rv)) 1.506 + return rv; 1.507 + 1.508 + nsCString contractId; 1.509 + rv = entry->GetData(contractId); 1.510 + if (NS_FAILED(rv)) 1.511 + return rv; 1.512 + 1.513 + // Try to load spellchecker engine. Ignore errors silently 1.514 + // except for the last one (HunSpell). 1.515 + nsCOMPtr<mozISpellCheckingEngine> engine = 1.516 + do_GetService(contractId.get(), &rv); 1.517 + if (NS_SUCCEEDED(rv)) { 1.518 + aSpellCheckingEngines->AppendObject(engine); 1.519 + } 1.520 + } 1.521 + 1.522 + // Try to load HunSpell spellchecker engine. 1.523 + nsCOMPtr<mozISpellCheckingEngine> engine = 1.524 + do_GetService(DEFAULT_SPELL_CHECKER, &rv); 1.525 + if (NS_FAILED(rv)) { 1.526 + // Fail if not succeeded to load HunSpell. Ignore errors 1.527 + // for external spellcheck engines. 1.528 + return rv; 1.529 + } 1.530 + aSpellCheckingEngines->AppendObject(engine); 1.531 + 1.532 + return NS_OK; 1.533 +}