diff -r 000000000000 -r 6474c204b198 extensions/spellcheck/src/mozSpellChecker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extensions/spellcheck/src/mozSpellChecker.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,530 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "mozSpellChecker.h" +#include "nsIServiceManager.h" +#include "mozISpellI18NManager.h" +#include "nsIStringEnumerator.h" +#include "nsICategoryManager.h" +#include "nsISupportsPrimitives.h" +#include "nsISimpleEnumerator.h" + +#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1" + +NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker) +NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker) + +NS_INTERFACE_MAP_BEGIN(mozSpellChecker) + NS_INTERFACE_MAP_ENTRY(nsISpellChecker) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(mozSpellChecker, + mTsDoc, + mPersonalDictionary) + +mozSpellChecker::mozSpellChecker() +{ +} + +mozSpellChecker::~mozSpellChecker() +{ + if(mPersonalDictionary){ + // mPersonalDictionary->Save(); + mPersonalDictionary->EndSession(); + } + mSpellCheckingEngine = nullptr; + mPersonalDictionary = nullptr; +} + +nsresult +mozSpellChecker::Init() +{ + mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); + + mSpellCheckingEngine = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc) +{ + mTsDoc = aDoc; + mFromStart = aFromStartofDoc; + return NS_OK; +} + + +NS_IMETHODIMP +mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsTArray *aSuggestions) +{ + if(!aSuggestions||!mConverter) + return NS_ERROR_NULL_POINTER; + + int32_t selOffset; + int32_t begin,end; + nsresult result; + result = SetupDoc(&selOffset); + bool isMisspelled,done; + if (NS_FAILED(result)) + return result; + + while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) + { + nsString str; + result = mTsDoc->GetCurrentTextBlock(&str); + + if (NS_FAILED(result)) + return result; + do{ + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); + if(NS_SUCCEEDED(result)&&(begin != -1)){ + const nsAString &currWord = Substring(str, begin, end - begin); + result = CheckWord(currWord, &isMisspelled, aSuggestions); + if(isMisspelled){ + aWord = currWord; + mTsDoc->SetSelection(begin, end-begin); + // After ScrollSelectionIntoView(), the pending notifications might + // be flushed and PresShell/PresContext/Frames may be dead. + // See bug 418470. + mTsDoc->ScrollSelectionIntoView(); + return NS_OK; + } + } + selOffset = end; + }while(end != -1); + mTsDoc->NextBlock(); + selOffset=0; + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray *aSuggestions) +{ + nsresult result; + bool correct; + if(!mSpellCheckingEngine) + return NS_ERROR_NULL_POINTER; + + *aIsMisspelled = false; + result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct); + NS_ENSURE_SUCCESS(result, result); + if(!correct){ + if(aSuggestions){ + uint32_t count,i; + char16_t **words; + + result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count); + NS_ENSURE_SUCCESS(result, result); + for(i=0;iAppendElement(nsDependentString(words[i])); + } + + if (count) + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); + } + *aIsMisspelled = true; + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences) +{ + if(!mConverter) + return NS_ERROR_NULL_POINTER; + + nsAutoString newWord(aNewWord); // sigh + + if(aAllOccurrences){ + int32_t selOffset; + int32_t startBlock,currentBlock,currOffset; + int32_t begin,end; + bool done; + nsresult result; + nsAutoString str; + + // find out where we are + result = SetupDoc(&selOffset); + if(NS_FAILED(result)) + return result; + result = GetCurrentBlockIndex(mTsDoc,&startBlock); + if(NS_FAILED(result)) + return result; + + //start at the beginning + result = mTsDoc->FirstBlock(); + currOffset=0; + currentBlock = 0; + while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) + { + result = mTsDoc->GetCurrentTextBlock(&str); + do{ + result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end); + if(NS_SUCCEEDED(result)&&(begin != -1)){ + if (aOldWord.Equals(Substring(str, begin, end-begin))) { + // if we are before the current selection point but in the same block + // move the selection point forwards + if((currentBlock == startBlock)&&(begin < selOffset)){ + selOffset += + int32_t(aNewWord.Length()) - int32_t(aOldWord.Length()); + if(selOffset < begin) selOffset=begin; + } + mTsDoc->SetSelection(begin, end-begin); + mTsDoc->InsertText(&newWord); + mTsDoc->GetCurrentTextBlock(&str); + end += (aNewWord.Length() - aOldWord.Length()); // recursion was cute in GEB, not here. + } + } + currOffset = end; + }while(currOffset != -1); + mTsDoc->NextBlock(); + currentBlock++; + currOffset=0; + } + + // We are done replacing. Put the selection point back where we found it (or equivalent); + result = mTsDoc->FirstBlock(); + currentBlock = 0; + while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){ + mTsDoc->NextBlock(); + } + +//After we have moved to the block where the first occurrence of replace was done, put the +//selection to the next word following it. In case there is no word following it i.e if it happens +//to be the last word in that block, then move to the next block and put the selection to the +//first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock() +//and the selection offset of the last occurrence of the replaced word is taken instead of the first +//occurrence and things get messed up as reported in the bug 244969 + + if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){ + nsString str; + result = mTsDoc->GetCurrentTextBlock(&str); + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); + if(end == -1) + { + mTsDoc->NextBlock(); + selOffset=0; + result = mTsDoc->GetCurrentTextBlock(&str); + result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end); + mTsDoc->SetSelection(begin, 0); + } + else + mTsDoc->SetSelection(begin, 0); + } + } + else{ + mTsDoc->InsertText(&newWord); + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::IgnoreAll(const nsAString &aWord) +{ + if(mPersonalDictionary){ + mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get()); + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord) +{ + nsresult res; + char16_t empty=0; + if (!mPersonalDictionary) + return NS_ERROR_NULL_POINTER; + res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty); + return res; +} + +NS_IMETHODIMP +mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord) +{ + nsresult res; + char16_t empty=0; + if (!mPersonalDictionary) + return NS_ERROR_NULL_POINTER; + res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty); + return res; +} + +NS_IMETHODIMP +mozSpellChecker::GetPersonalDictionary(nsTArray *aWordList) +{ + if(!aWordList || !mPersonalDictionary) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr words; + mPersonalDictionary->GetWordList(getter_AddRefs(words)); + + bool hasMore; + nsAutoString word; + while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) { + words->GetNext(word); + aWordList->AppendElement(word); + } + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::GetDictionaryList(nsTArray *aDictionaryList) +{ + nsresult rv; + + // For catching duplicates + nsClassHashtable dictionaries; + + nsCOMArray spellCheckingEngines; + rv = GetEngineList(&spellCheckingEngines); + NS_ENSURE_SUCCESS(rv, rv); + + for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { + nsCOMPtr engine = spellCheckingEngines[i]; + + uint32_t count = 0; + char16_t **words = nullptr; + engine->GetDictionaryList(&words, &count); + for (uint32_t k = 0; k < count; k++) { + nsAutoString dictName; + + dictName.Assign(words[k]); + + // Skip duplicate dictionaries. Only take the first one + // for each name. + if (dictionaries.Get(dictName, nullptr)) + continue; + + dictionaries.Put(dictName, nullptr); + + if (!aDictionaryList->AppendElement(dictName)) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words); + } + + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary) +{ + if (!mSpellCheckingEngine) { + aDictionary.AssignLiteral(""); + return NS_OK; + } + + nsXPIDLString dictname; + mSpellCheckingEngine->GetDictionary(getter_Copies(dictname)); + aDictionary = dictname; + return NS_OK; +} + +NS_IMETHODIMP +mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary) +{ + // Calls to mozISpellCheckingEngine::SetDictionary might destroy us + nsRefPtr kungFuDeathGrip = this; + + mSpellCheckingEngine = nullptr; + + if (aDictionary.IsEmpty()) { + return NS_OK; + } + + nsresult rv; + nsCOMArray spellCheckingEngines; + rv = GetEngineList(&spellCheckingEngines); + NS_ENSURE_SUCCESS(rv, rv); + + for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) { + // We must set mSpellCheckingEngine before we call SetDictionary, since + // SetDictionary calls back to this spell checker to check if the + // dictionary was set + mSpellCheckingEngine = spellCheckingEngines[i]; + + rv = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get()); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr personalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1"); + mSpellCheckingEngine->SetPersonalDictionary(personalDictionary.get()); + + nsXPIDLString language; + nsCOMPtr serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return serv->GetUtil(language.get(),getter_AddRefs(mConverter)); + } + } + + mSpellCheckingEngine = nullptr; + + // We could not find any engine with the requested dictionary + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +mozSpellChecker::CheckCurrentDictionary() +{ + // If the current dictionary has been uninstalled, we need to stop using it. + // This happens when there is a current engine, but that engine has no + // current dictionary. + + if (!mSpellCheckingEngine) { + // We didn't have a current dictionary + return NS_OK; + } + + nsXPIDLString dictname; + mSpellCheckingEngine->GetDictionary(getter_Copies(dictname)); + + if (!dictname.IsEmpty()) { + // We still have a current dictionary + return NS_OK; + } + + // We had a current dictionary, but it has gone, so we cannot use it anymore. + mSpellCheckingEngine = nullptr; + return NS_OK; +} + +nsresult +mozSpellChecker::SetupDoc(int32_t *outBlockOffset) +{ + nsresult rv; + + nsITextServicesDocument::TSDBlockSelectionStatus blockStatus; + int32_t selOffset; + int32_t selLength; + *outBlockOffset = 0; + + if (!mFromStart) + { + rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength); + if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound)) + { + switch (blockStatus) + { + case nsITextServicesDocument::eBlockOutside: // No TB in S, but found one before/after S. + case nsITextServicesDocument::eBlockPartial: // S begins or ends in TB but extends outside of TB. + // the TS doc points to the block we want. + *outBlockOffset = selOffset + selLength; + break; + + case nsITextServicesDocument::eBlockInside: // S extends beyond the start and end of TB. + // we want the block after this one. + rv = mTsDoc->NextBlock(); + *outBlockOffset = 0; + break; + + case nsITextServicesDocument::eBlockContains: // TB contains entire S. + *outBlockOffset = selOffset + selLength; + break; + + case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S). + default: + NS_NOTREACHED("Shouldn't ever get this status"); + } + } + else //failed to get last sel block. Just start at beginning + { + rv = mTsDoc->FirstBlock(); + *outBlockOffset = 0; + } + + } + else // we want the first block + { + rv = mTsDoc->FirstBlock(); + mFromStart = false; + } + return rv; +} + + +// utility method to discover which block we're in. The TSDoc interface doesn't give +// us this, because it can't assume a read-only document. +// shamelessly stolen from nsTextServicesDocument +nsresult +mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex) +{ + int32_t blockIndex = 0; + bool isDone = false; + nsresult result = NS_OK; + + do + { + aDoc->PrevBlock(); + + result = aDoc->IsDone(&isDone); + + if (!isDone) + blockIndex ++; + + } while (NS_SUCCEEDED(result) && !isDone); + + *outBlockIndex = blockIndex; + + return result; +} + +nsresult +mozSpellChecker::GetEngineList(nsCOMArray* aSpellCheckingEngines) +{ + nsresult rv; + bool hasMoreEngines; + + nsCOMPtr catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMgr) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr catEntries; + + // Get contract IDs of registrated external spell-check engines and + // append one of HunSpell at the end. + rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries)); + if (NS_FAILED(rv)) + return rv; + + while (catEntries->HasMoreElements(&hasMoreEngines), hasMoreEngines){ + nsCOMPtr elem; + rv = catEntries->GetNext(getter_AddRefs(elem)); + + nsCOMPtr entry = do_QueryInterface(elem, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCString contractId; + rv = entry->GetData(contractId); + if (NS_FAILED(rv)) + return rv; + + // Try to load spellchecker engine. Ignore errors silently + // except for the last one (HunSpell). + nsCOMPtr engine = + do_GetService(contractId.get(), &rv); + if (NS_SUCCEEDED(rv)) { + aSpellCheckingEngines->AppendObject(engine); + } + } + + // Try to load HunSpell spellchecker engine. + nsCOMPtr engine = + do_GetService(DEFAULT_SPELL_CHECKER, &rv); + if (NS_FAILED(rv)) { + // Fail if not succeeded to load HunSpell. Ignore errors + // for external spellcheck engines. + return rv; + } + aSpellCheckingEngines->AppendObject(engine); + + return NS_OK; +}