Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / extensions / spellcheck / src / mozSpellChecker.cpp
blobc4544067b408393b81ed501bc75760c877f818ba
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
14 * The Original Code is Mozilla Spellchecker Component.
16 * The Initial Developer of the Original Code is David Einstein.
17 * Portions created by the Initial Developer are Copyright (C) 2001
18 * the Initial Developer. All Rights Reserved.
20 * Contributor(s): David Einstein Deinst@world.std.com
22 * Alternatively, the contents of this file may be used under the terms of
23 * either the GNU General Public License Version 2 or later (the "GPL"), or
24 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
25 * in which case the provisions of the GPL or the LGPL are applicable instead
26 * of those above. If you wish to allow use of your version of this file only
27 * under the terms of either the GPL or the LGPL, and not to allow others to
28 * use your version of this file under the terms of the MPL, indicate your
29 * decision by deleting the provisions above and replace them with the notice
30 * and other provisions required by the GPL or the LGPL. If you do not delete
31 * the provisions above, a recipient may use your version of this file under
32 * the terms of any one of the MPL, the GPL or the LGPL.
34 * ***** END LICENSE BLOCK ***** */
37 #include "mozSpellChecker.h"
38 #include "nsIServiceManager.h"
39 #include "mozISpellI18NManager.h"
40 #include "nsIStringEnumerator.h"
41 #include "nsICategoryManager.h"
42 #include "nsISupportsPrimitives.h"
44 #define UNREASONABLE_WORD_LENGTH 64
46 #define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
48 NS_IMPL_ISUPPORTS1(mozSpellChecker, nsISpellChecker)
50 mozSpellChecker::mozSpellChecker()
54 mozSpellChecker::~mozSpellChecker()
56 if(mPersonalDictionary){
57 // mPersonalDictionary->Save();
58 mPersonalDictionary->EndSession();
60 mSpellCheckingEngine = nsnull;
61 mPersonalDictionary = nsnull;
64 nsresult
65 mozSpellChecker::Init()
67 mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
69 mSpellCheckingEngine = nsnull;
70 mCurrentEngineContractId = nsnull;
71 mDictionariesMap.Init();
72 InitSpellCheckDictionaryMap();
74 return NS_OK;
77 NS_IMETHODIMP
78 mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, PRBool aFromStartofDoc)
80 mTsDoc = aDoc;
81 mFromStart = aFromStartofDoc;
82 return NS_OK;
86 NS_IMETHODIMP
87 mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsStringArray *aSuggestions)
89 if(!aSuggestions||!mConverter)
90 return NS_ERROR_NULL_POINTER;
92 PRUint32 selOffset;
93 PRInt32 begin,end;
94 nsresult result;
95 result = SetupDoc(&selOffset);
96 PRBool isMisspelled,done;
97 if (NS_FAILED(result))
98 return result;
100 while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
102 nsString str;
103 result = mTsDoc->GetCurrentTextBlock(&str);
105 if (NS_FAILED(result))
106 return result;
108 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
109 if(NS_SUCCEEDED(result)&&(begin != -1)){
110 const nsAString &currWord = Substring(str, begin, end - begin);
111 result = CheckWord(currWord, &isMisspelled, aSuggestions);
112 if(isMisspelled){
113 aWord = currWord;
114 mTsDoc->SetSelection(begin, end-begin);
115 // After ScrollSelectionIntoView(), the pending notifications might
116 // be flushed and PresShell/PresContext/Frames may be dead.
117 // See bug 418470.
118 mTsDoc->ScrollSelectionIntoView();
119 return NS_OK;
122 selOffset = end;
123 }while(end != -1);
124 mTsDoc->NextBlock();
125 selOffset=0;
127 return NS_OK;
130 NS_IMETHODIMP
131 mozSpellChecker::CheckWord(const nsAString &aWord, PRBool *aIsMisspelled, nsStringArray *aSuggestions)
133 nsresult result;
134 PRBool correct;
135 if(!mSpellCheckingEngine)
136 return NS_ERROR_NULL_POINTER;
138 // don't bother to check crazy words
139 if (aWord.Length() > UNREASONABLE_WORD_LENGTH) {
140 *aIsMisspelled = PR_TRUE;
141 return NS_OK;
144 *aIsMisspelled = PR_FALSE;
145 result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
146 NS_ENSURE_SUCCESS(result, result);
147 if(!correct){
148 if(aSuggestions){
149 PRUint32 count,i;
150 PRUnichar **words;
152 result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count);
153 NS_ENSURE_SUCCESS(result, result);
154 for(i=0;i<count;i++){
155 aSuggestions->AppendString(nsDependentString(words[i]));
158 if (count)
159 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
161 if(aIsMisspelled){
162 *aIsMisspelled = PR_TRUE;
165 return NS_OK;
168 NS_IMETHODIMP
169 mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, PRBool aAllOccurrences)
171 if(!mConverter)
172 return NS_ERROR_NULL_POINTER;
174 nsAutoString newWord(aNewWord); // sigh
176 if(aAllOccurrences){
177 PRUint32 selOffset;
178 PRInt32 startBlock,currentBlock,currOffset;
179 PRInt32 begin,end;
180 PRBool done;
181 nsresult result;
182 nsAutoString str;
184 // find out where we are
185 result = SetupDoc(&selOffset);
186 if(NS_FAILED(result))
187 return result;
188 result = GetCurrentBlockIndex(mTsDoc,&startBlock);
189 if(NS_FAILED(result))
190 return result;
192 //start at the beginning
193 result = mTsDoc->FirstBlock();
194 currOffset=0;
195 currentBlock = 0;
196 while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
198 result = mTsDoc->GetCurrentTextBlock(&str);
200 result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end);
201 if(NS_SUCCEEDED(result)&&(begin != -1)){
202 if (aOldWord.Equals(Substring(str, begin, end-begin))) {
203 // if we are before the current selection point but in the same block
204 // move the selection point forwards
205 if((currentBlock == startBlock)&&(begin < (PRInt32) selOffset)){
206 selOffset += (aNewWord.Length() - aOldWord.Length());
207 if(selOffset < 0) selOffset=0;
209 mTsDoc->SetSelection(begin, end-begin);
210 mTsDoc->InsertText(&newWord);
211 mTsDoc->GetCurrentTextBlock(&str);
212 end += (aNewWord.Length() - aOldWord.Length()); // recursion was cute in GEB, not here.
215 currOffset = end;
216 }while(currOffset != -1);
217 mTsDoc->NextBlock();
218 currentBlock++;
219 currOffset=0;
222 // We are done replacing. Put the selection point back where we found it (or equivalent);
223 result = mTsDoc->FirstBlock();
224 currentBlock = 0;
225 while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){
226 mTsDoc->NextBlock();
229 //After we have moved to the block where the first occurrence of replace was done, put the
230 //selection to the next word following it. In case there is no word following it i.e if it happens
231 //to be the last word in that block, then move to the next block and put the selection to the
232 //first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock()
233 //and the selection offset of the last occurrence of the replaced word is taken instead of the first
234 //occurrence and things get messed up as reported in the bug 244969
236 if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){
237 nsString str;
238 result = mTsDoc->GetCurrentTextBlock(&str);
239 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
240 if(end == -1)
242 mTsDoc->NextBlock();
243 selOffset=0;
244 result = mTsDoc->GetCurrentTextBlock(&str);
245 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
246 mTsDoc->SetSelection(begin, 0);
248 else
249 mTsDoc->SetSelection(begin, 0);
252 else{
253 mTsDoc->InsertText(&newWord);
255 return NS_OK;
258 NS_IMETHODIMP
259 mozSpellChecker::IgnoreAll(const nsAString &aWord)
261 if(mPersonalDictionary){
262 mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get());
264 return NS_OK;
267 NS_IMETHODIMP
268 mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord)
270 nsresult res;
271 PRUnichar empty=0;
272 if (!mPersonalDictionary)
273 return NS_ERROR_NULL_POINTER;
274 res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty);
275 return res;
278 NS_IMETHODIMP
279 mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord)
281 nsresult res;
282 PRUnichar empty=0;
283 if (!mPersonalDictionary)
284 return NS_ERROR_NULL_POINTER;
285 res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty);
286 return res;
289 NS_IMETHODIMP
290 mozSpellChecker::GetPersonalDictionary(nsStringArray *aWordList)
292 if(!aWordList || !mPersonalDictionary)
293 return NS_ERROR_NULL_POINTER;
295 nsCOMPtr<nsIStringEnumerator> words;
296 mPersonalDictionary->GetWordList(getter_AddRefs(words));
298 PRBool hasMore;
299 nsAutoString word;
300 while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) {
301 words->GetNext(word);
302 aWordList->AppendString(word);
304 return NS_OK;
307 struct AppendNewStruct
309 nsStringArray *dictionaryList;
310 PRBool failed;
313 static PLDHashOperator
314 AppendNewString(const nsAString& aString, nsCString*, void* aClosure)
316 AppendNewStruct *ans = (AppendNewStruct*) aClosure;
318 if (!ans->dictionaryList->AppendString(aString))
320 ans->failed = PR_TRUE;
321 return PL_DHASH_STOP;
324 return PL_DHASH_NEXT;
327 NS_IMETHODIMP
328 mozSpellChecker::GetDictionaryList(nsStringArray *aDictionaryList)
330 AppendNewStruct ans = {aDictionaryList, PR_FALSE};
332 mDictionariesMap.EnumerateRead(AppendNewString, &ans);
334 if (ans.failed)
335 return NS_ERROR_OUT_OF_MEMORY;
337 return NS_OK;
340 NS_IMETHODIMP
341 mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
343 nsXPIDLString dictname;
345 if (!mSpellCheckingEngine)
346 return NS_ERROR_NOT_INITIALIZED;
348 mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
349 aDictionary = dictname;
350 return NS_OK;
353 NS_IMETHODIMP
354 mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
356 nsresult rv;
357 nsCString *contractId;
359 if (!mDictionariesMap.Get(aDictionary, &contractId)){
360 NS_WARNING("Dictionary not found");
361 return NS_ERROR_NOT_AVAILABLE;
364 if (!mCurrentEngineContractId || !mCurrentEngineContractId->Equals(*contractId)){
365 mSpellCheckingEngine = do_GetService(contractId->get(), &rv);
366 if (NS_FAILED(rv))
367 return rv;
369 mCurrentEngineContractId = contractId;
372 nsresult res;
373 res = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get());
374 if(NS_FAILED(res)){
375 NS_WARNING("Dictionary load failed");
376 return res;
379 mSpellCheckingEngine->SetPersonalDictionary(mPersonalDictionary);
381 nsXPIDLString language;
383 nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &res));
384 if(serv && NS_SUCCEEDED(res)){
385 res = serv->GetUtil(language.get(),getter_AddRefs(mConverter));
387 return res;
390 nsresult
391 mozSpellChecker::SetupDoc(PRUint32 *outBlockOffset)
393 nsresult rv;
395 nsITextServicesDocument::TSDBlockSelectionStatus blockStatus;
396 PRInt32 selOffset;
397 PRInt32 selLength;
398 *outBlockOffset = 0;
400 if (!mFromStart)
402 rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength);
403 if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound))
405 switch (blockStatus)
407 case nsITextServicesDocument::eBlockOutside: // No TB in S, but found one before/after S.
408 case nsITextServicesDocument::eBlockPartial: // S begins or ends in TB but extends outside of TB.
409 // the TS doc points to the block we want.
410 *outBlockOffset = selOffset + selLength;
411 break;
413 case nsITextServicesDocument::eBlockInside: // S extends beyond the start and end of TB.
414 // we want the block after this one.
415 rv = mTsDoc->NextBlock();
416 *outBlockOffset = 0;
417 break;
419 case nsITextServicesDocument::eBlockContains: // TB contains entire S.
420 *outBlockOffset = selOffset + selLength;
421 break;
423 case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S).
424 default:
425 NS_NOTREACHED("Shouldn't ever get this status");
428 else //failed to get last sel block. Just start at beginning
430 rv = mTsDoc->FirstBlock();
431 *outBlockOffset = 0;
435 else // we want the first block
437 rv = mTsDoc->FirstBlock();
438 mFromStart = PR_FALSE;
440 return rv;
444 // utility method to discover which block we're in. The TSDoc interface doesn't give
445 // us this, because it can't assume a read-only document.
446 // shamelessly stolen from nsTextServicesDocument
447 nsresult
448 mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, PRInt32 *outBlockIndex)
450 PRInt32 blockIndex = 0;
451 PRBool isDone = PR_FALSE;
452 nsresult result = NS_OK;
456 aDoc->PrevBlock();
458 result = aDoc->IsDone(&isDone);
460 if (!isDone)
461 blockIndex ++;
463 } while (NS_SUCCEEDED(result) && !isDone);
465 *outBlockIndex = blockIndex;
467 return result;
470 nsresult
471 mozSpellChecker::InitSpellCheckDictionaryMap()
473 nsresult rv;
474 PRBool hasMoreEngines;
475 PRInt32 i;
476 nsCStringArray contractIds;
478 nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
479 if (!catMgr)
480 return NS_ERROR_NULL_POINTER;
482 nsCOMPtr<nsISimpleEnumerator> catEntries;
484 // Get contract IDs of registrated external spell-check engines and
485 // append one of HunSpell at the end.
486 rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries));
487 if (NS_FAILED(rv))
488 return rv;
490 while (catEntries->HasMoreElements(&hasMoreEngines), hasMoreEngines){
491 nsCOMPtr<nsISupports> elem;
492 rv = catEntries->GetNext(getter_AddRefs(elem));
494 nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv);
495 if (NS_FAILED(rv))
496 return rv;
498 nsCString contractId;
499 rv = entry->GetData(contractId);
500 if (NS_FAILED(rv))
501 return rv;
503 contractIds.AppendCString(contractId);
506 contractIds.AppendCString(NS_LITERAL_CSTRING(DEFAULT_SPELL_CHECKER));
508 // Retrieve dictionaries from all available spellcheckers and
509 // fill mDictionariesMap hash (only the first dictionary with the
510 // each name is used).
511 for (i=0;i<contractIds.Count();i++){
512 PRUint32 count,k;
513 PRUnichar **words;
515 nsCString *contractId = contractIds[i];
517 // Try to load spellchecker engine. Ignore errors silently
518 // except for the last one (HunSpell).
519 nsCOMPtr<mozISpellCheckingEngine> engine =
520 do_GetService(contractId->get(), &rv);
521 if (NS_FAILED(rv)){
522 // Fail if not succeeded to load HunSpell. Ignore errors
523 // for external spellcheck engines.
524 if (i==contractIds.Count()-1){
525 return rv;
528 continue;
531 engine->GetDictionaryList(&words,&count);
532 for(k=0;k<count;k++){
533 nsAutoString dictName;
535 dictName.Assign(words[k]);
537 nsCString dictCName = NS_ConvertUTF16toUTF8(dictName);
539 // Skip duplicate dictionaries. Only take the first one
540 // for each name.
541 if (mDictionariesMap.Get(dictName, NULL))
542 continue;
544 mDictionariesMap.Put(dictName, new nsCString(*contractId));
547 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
550 return NS_OK;