extensions/spellcheck/src/mozSpellChecker.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:b035978b3cb2
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
6 #include "mozSpellChecker.h"
7 #include "nsIServiceManager.h"
8 #include "mozISpellI18NManager.h"
9 #include "nsIStringEnumerator.h"
10 #include "nsICategoryManager.h"
11 #include "nsISupportsPrimitives.h"
12 #include "nsISimpleEnumerator.h"
13
14 #define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
15
16 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker)
17 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker)
18
19 NS_INTERFACE_MAP_BEGIN(mozSpellChecker)
20 NS_INTERFACE_MAP_ENTRY(nsISpellChecker)
21 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpellChecker)
22 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozSpellChecker)
23 NS_INTERFACE_MAP_END
24
25 NS_IMPL_CYCLE_COLLECTION(mozSpellChecker,
26 mTsDoc,
27 mPersonalDictionary)
28
29 mozSpellChecker::mozSpellChecker()
30 {
31 }
32
33 mozSpellChecker::~mozSpellChecker()
34 {
35 if(mPersonalDictionary){
36 // mPersonalDictionary->Save();
37 mPersonalDictionary->EndSession();
38 }
39 mSpellCheckingEngine = nullptr;
40 mPersonalDictionary = nullptr;
41 }
42
43 nsresult
44 mozSpellChecker::Init()
45 {
46 mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
47
48 mSpellCheckingEngine = nullptr;
49
50 return NS_OK;
51 }
52
53 NS_IMETHODIMP
54 mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc)
55 {
56 mTsDoc = aDoc;
57 mFromStart = aFromStartofDoc;
58 return NS_OK;
59 }
60
61
62 NS_IMETHODIMP
63 mozSpellChecker::NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions)
64 {
65 if(!aSuggestions||!mConverter)
66 return NS_ERROR_NULL_POINTER;
67
68 int32_t selOffset;
69 int32_t begin,end;
70 nsresult result;
71 result = SetupDoc(&selOffset);
72 bool isMisspelled,done;
73 if (NS_FAILED(result))
74 return result;
75
76 while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
77 {
78 nsString str;
79 result = mTsDoc->GetCurrentTextBlock(&str);
80
81 if (NS_FAILED(result))
82 return result;
83 do{
84 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
85 if(NS_SUCCEEDED(result)&&(begin != -1)){
86 const nsAString &currWord = Substring(str, begin, end - begin);
87 result = CheckWord(currWord, &isMisspelled, aSuggestions);
88 if(isMisspelled){
89 aWord = currWord;
90 mTsDoc->SetSelection(begin, end-begin);
91 // After ScrollSelectionIntoView(), the pending notifications might
92 // be flushed and PresShell/PresContext/Frames may be dead.
93 // See bug 418470.
94 mTsDoc->ScrollSelectionIntoView();
95 return NS_OK;
96 }
97 }
98 selOffset = end;
99 }while(end != -1);
100 mTsDoc->NextBlock();
101 selOffset=0;
102 }
103 return NS_OK;
104 }
105
106 NS_IMETHODIMP
107 mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions)
108 {
109 nsresult result;
110 bool correct;
111 if(!mSpellCheckingEngine)
112 return NS_ERROR_NULL_POINTER;
113
114 *aIsMisspelled = false;
115 result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
116 NS_ENSURE_SUCCESS(result, result);
117 if(!correct){
118 if(aSuggestions){
119 uint32_t count,i;
120 char16_t **words;
121
122 result = mSpellCheckingEngine->Suggest(PromiseFlatString(aWord).get(), &words, &count);
123 NS_ENSURE_SUCCESS(result, result);
124 for(i=0;i<count;i++){
125 aSuggestions->AppendElement(nsDependentString(words[i]));
126 }
127
128 if (count)
129 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
130 }
131 *aIsMisspelled = true;
132 }
133 return NS_OK;
134 }
135
136 NS_IMETHODIMP
137 mozSpellChecker::Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences)
138 {
139 if(!mConverter)
140 return NS_ERROR_NULL_POINTER;
141
142 nsAutoString newWord(aNewWord); // sigh
143
144 if(aAllOccurrences){
145 int32_t selOffset;
146 int32_t startBlock,currentBlock,currOffset;
147 int32_t begin,end;
148 bool done;
149 nsresult result;
150 nsAutoString str;
151
152 // find out where we are
153 result = SetupDoc(&selOffset);
154 if(NS_FAILED(result))
155 return result;
156 result = GetCurrentBlockIndex(mTsDoc,&startBlock);
157 if(NS_FAILED(result))
158 return result;
159
160 //start at the beginning
161 result = mTsDoc->FirstBlock();
162 currOffset=0;
163 currentBlock = 0;
164 while( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done )
165 {
166 result = mTsDoc->GetCurrentTextBlock(&str);
167 do{
168 result = mConverter->FindNextWord(str.get(),str.Length(),currOffset,&begin,&end);
169 if(NS_SUCCEEDED(result)&&(begin != -1)){
170 if (aOldWord.Equals(Substring(str, begin, end-begin))) {
171 // if we are before the current selection point but in the same block
172 // move the selection point forwards
173 if((currentBlock == startBlock)&&(begin < selOffset)){
174 selOffset +=
175 int32_t(aNewWord.Length()) - int32_t(aOldWord.Length());
176 if(selOffset < begin) selOffset=begin;
177 }
178 mTsDoc->SetSelection(begin, end-begin);
179 mTsDoc->InsertText(&newWord);
180 mTsDoc->GetCurrentTextBlock(&str);
181 end += (aNewWord.Length() - aOldWord.Length()); // recursion was cute in GEB, not here.
182 }
183 }
184 currOffset = end;
185 }while(currOffset != -1);
186 mTsDoc->NextBlock();
187 currentBlock++;
188 currOffset=0;
189 }
190
191 // We are done replacing. Put the selection point back where we found it (or equivalent);
192 result = mTsDoc->FirstBlock();
193 currentBlock = 0;
194 while(( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ) &&(currentBlock < startBlock)){
195 mTsDoc->NextBlock();
196 }
197
198 //After we have moved to the block where the first occurrence of replace was done, put the
199 //selection to the next word following it. In case there is no word following it i.e if it happens
200 //to be the last word in that block, then move to the next block and put the selection to the
201 //first word in that block, otherwise when the Setupdoc() is called, it queries the LastSelectedBlock()
202 //and the selection offset of the last occurrence of the replaced word is taken instead of the first
203 //occurrence and things get messed up as reported in the bug 244969
204
205 if( NS_SUCCEEDED(mTsDoc->IsDone(&done)) && !done ){
206 nsString str;
207 result = mTsDoc->GetCurrentTextBlock(&str);
208 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
209 if(end == -1)
210 {
211 mTsDoc->NextBlock();
212 selOffset=0;
213 result = mTsDoc->GetCurrentTextBlock(&str);
214 result = mConverter->FindNextWord(str.get(),str.Length(),selOffset,&begin,&end);
215 mTsDoc->SetSelection(begin, 0);
216 }
217 else
218 mTsDoc->SetSelection(begin, 0);
219 }
220 }
221 else{
222 mTsDoc->InsertText(&newWord);
223 }
224 return NS_OK;
225 }
226
227 NS_IMETHODIMP
228 mozSpellChecker::IgnoreAll(const nsAString &aWord)
229 {
230 if(mPersonalDictionary){
231 mPersonalDictionary->IgnoreWord(PromiseFlatString(aWord).get());
232 }
233 return NS_OK;
234 }
235
236 NS_IMETHODIMP
237 mozSpellChecker::AddWordToPersonalDictionary(const nsAString &aWord)
238 {
239 nsresult res;
240 char16_t empty=0;
241 if (!mPersonalDictionary)
242 return NS_ERROR_NULL_POINTER;
243 res = mPersonalDictionary->AddWord(PromiseFlatString(aWord).get(),&empty);
244 return res;
245 }
246
247 NS_IMETHODIMP
248 mozSpellChecker::RemoveWordFromPersonalDictionary(const nsAString &aWord)
249 {
250 nsresult res;
251 char16_t empty=0;
252 if (!mPersonalDictionary)
253 return NS_ERROR_NULL_POINTER;
254 res = mPersonalDictionary->RemoveWord(PromiseFlatString(aWord).get(),&empty);
255 return res;
256 }
257
258 NS_IMETHODIMP
259 mozSpellChecker::GetPersonalDictionary(nsTArray<nsString> *aWordList)
260 {
261 if(!aWordList || !mPersonalDictionary)
262 return NS_ERROR_NULL_POINTER;
263
264 nsCOMPtr<nsIStringEnumerator> words;
265 mPersonalDictionary->GetWordList(getter_AddRefs(words));
266
267 bool hasMore;
268 nsAutoString word;
269 while (NS_SUCCEEDED(words->HasMore(&hasMore)) && hasMore) {
270 words->GetNext(word);
271 aWordList->AppendElement(word);
272 }
273 return NS_OK;
274 }
275
276 NS_IMETHODIMP
277 mozSpellChecker::GetDictionaryList(nsTArray<nsString> *aDictionaryList)
278 {
279 nsresult rv;
280
281 // For catching duplicates
282 nsClassHashtable<nsStringHashKey, nsCString> dictionaries;
283
284 nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
285 rv = GetEngineList(&spellCheckingEngines);
286 NS_ENSURE_SUCCESS(rv, rv);
287
288 for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
289 nsCOMPtr<mozISpellCheckingEngine> engine = spellCheckingEngines[i];
290
291 uint32_t count = 0;
292 char16_t **words = nullptr;
293 engine->GetDictionaryList(&words, &count);
294 for (uint32_t k = 0; k < count; k++) {
295 nsAutoString dictName;
296
297 dictName.Assign(words[k]);
298
299 // Skip duplicate dictionaries. Only take the first one
300 // for each name.
301 if (dictionaries.Get(dictName, nullptr))
302 continue;
303
304 dictionaries.Put(dictName, nullptr);
305
306 if (!aDictionaryList->AppendElement(dictName)) {
307 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
308 return NS_ERROR_OUT_OF_MEMORY;
309 }
310 }
311
312 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, words);
313 }
314
315 return NS_OK;
316 }
317
318 NS_IMETHODIMP
319 mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
320 {
321 if (!mSpellCheckingEngine) {
322 aDictionary.AssignLiteral("");
323 return NS_OK;
324 }
325
326 nsXPIDLString dictname;
327 mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
328 aDictionary = dictname;
329 return NS_OK;
330 }
331
332 NS_IMETHODIMP
333 mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
334 {
335 // Calls to mozISpellCheckingEngine::SetDictionary might destroy us
336 nsRefPtr<mozSpellChecker> kungFuDeathGrip = this;
337
338 mSpellCheckingEngine = nullptr;
339
340 if (aDictionary.IsEmpty()) {
341 return NS_OK;
342 }
343
344 nsresult rv;
345 nsCOMArray<mozISpellCheckingEngine> spellCheckingEngines;
346 rv = GetEngineList(&spellCheckingEngines);
347 NS_ENSURE_SUCCESS(rv, rv);
348
349 for (int32_t i = 0; i < spellCheckingEngines.Count(); i++) {
350 // We must set mSpellCheckingEngine before we call SetDictionary, since
351 // SetDictionary calls back to this spell checker to check if the
352 // dictionary was set
353 mSpellCheckingEngine = spellCheckingEngines[i];
354
355 rv = mSpellCheckingEngine->SetDictionary(PromiseFlatString(aDictionary).get());
356
357 if (NS_SUCCEEDED(rv)) {
358 nsCOMPtr<mozIPersonalDictionary> personalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
359 mSpellCheckingEngine->SetPersonalDictionary(personalDictionary.get());
360
361 nsXPIDLString language;
362 nsCOMPtr<mozISpellI18NManager> serv(do_GetService("@mozilla.org/spellchecker/i18nmanager;1", &rv));
363 NS_ENSURE_SUCCESS(rv, rv);
364 return serv->GetUtil(language.get(),getter_AddRefs(mConverter));
365 }
366 }
367
368 mSpellCheckingEngine = nullptr;
369
370 // We could not find any engine with the requested dictionary
371 return NS_ERROR_NOT_AVAILABLE;
372 }
373
374 NS_IMETHODIMP
375 mozSpellChecker::CheckCurrentDictionary()
376 {
377 // If the current dictionary has been uninstalled, we need to stop using it.
378 // This happens when there is a current engine, but that engine has no
379 // current dictionary.
380
381 if (!mSpellCheckingEngine) {
382 // We didn't have a current dictionary
383 return NS_OK;
384 }
385
386 nsXPIDLString dictname;
387 mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
388
389 if (!dictname.IsEmpty()) {
390 // We still have a current dictionary
391 return NS_OK;
392 }
393
394 // We had a current dictionary, but it has gone, so we cannot use it anymore.
395 mSpellCheckingEngine = nullptr;
396 return NS_OK;
397 }
398
399 nsresult
400 mozSpellChecker::SetupDoc(int32_t *outBlockOffset)
401 {
402 nsresult rv;
403
404 nsITextServicesDocument::TSDBlockSelectionStatus blockStatus;
405 int32_t selOffset;
406 int32_t selLength;
407 *outBlockOffset = 0;
408
409 if (!mFromStart)
410 {
411 rv = mTsDoc->LastSelectedBlock(&blockStatus, &selOffset, &selLength);
412 if (NS_SUCCEEDED(rv) && (blockStatus != nsITextServicesDocument::eBlockNotFound))
413 {
414 switch (blockStatus)
415 {
416 case nsITextServicesDocument::eBlockOutside: // No TB in S, but found one before/after S.
417 case nsITextServicesDocument::eBlockPartial: // S begins or ends in TB but extends outside of TB.
418 // the TS doc points to the block we want.
419 *outBlockOffset = selOffset + selLength;
420 break;
421
422 case nsITextServicesDocument::eBlockInside: // S extends beyond the start and end of TB.
423 // we want the block after this one.
424 rv = mTsDoc->NextBlock();
425 *outBlockOffset = 0;
426 break;
427
428 case nsITextServicesDocument::eBlockContains: // TB contains entire S.
429 *outBlockOffset = selOffset + selLength;
430 break;
431
432 case nsITextServicesDocument::eBlockNotFound: // There is no text block (TB) in or before the selection (S).
433 default:
434 NS_NOTREACHED("Shouldn't ever get this status");
435 }
436 }
437 else //failed to get last sel block. Just start at beginning
438 {
439 rv = mTsDoc->FirstBlock();
440 *outBlockOffset = 0;
441 }
442
443 }
444 else // we want the first block
445 {
446 rv = mTsDoc->FirstBlock();
447 mFromStart = false;
448 }
449 return rv;
450 }
451
452
453 // utility method to discover which block we're in. The TSDoc interface doesn't give
454 // us this, because it can't assume a read-only document.
455 // shamelessly stolen from nsTextServicesDocument
456 nsresult
457 mozSpellChecker::GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex)
458 {
459 int32_t blockIndex = 0;
460 bool isDone = false;
461 nsresult result = NS_OK;
462
463 do
464 {
465 aDoc->PrevBlock();
466
467 result = aDoc->IsDone(&isDone);
468
469 if (!isDone)
470 blockIndex ++;
471
472 } while (NS_SUCCEEDED(result) && !isDone);
473
474 *outBlockIndex = blockIndex;
475
476 return result;
477 }
478
479 nsresult
480 mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellCheckingEngines)
481 {
482 nsresult rv;
483 bool hasMoreEngines;
484
485 nsCOMPtr<nsICategoryManager> catMgr = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
486 if (!catMgr)
487 return NS_ERROR_NULL_POINTER;
488
489 nsCOMPtr<nsISimpleEnumerator> catEntries;
490
491 // Get contract IDs of registrated external spell-check engines and
492 // append one of HunSpell at the end.
493 rv = catMgr->EnumerateCategory("spell-check-engine", getter_AddRefs(catEntries));
494 if (NS_FAILED(rv))
495 return rv;
496
497 while (catEntries->HasMoreElements(&hasMoreEngines), hasMoreEngines){
498 nsCOMPtr<nsISupports> elem;
499 rv = catEntries->GetNext(getter_AddRefs(elem));
500
501 nsCOMPtr<nsISupportsCString> entry = do_QueryInterface(elem, &rv);
502 if (NS_FAILED(rv))
503 return rv;
504
505 nsCString contractId;
506 rv = entry->GetData(contractId);
507 if (NS_FAILED(rv))
508 return rv;
509
510 // Try to load spellchecker engine. Ignore errors silently
511 // except for the last one (HunSpell).
512 nsCOMPtr<mozISpellCheckingEngine> engine =
513 do_GetService(contractId.get(), &rv);
514 if (NS_SUCCEEDED(rv)) {
515 aSpellCheckingEngines->AppendObject(engine);
516 }
517 }
518
519 // Try to load HunSpell spellchecker engine.
520 nsCOMPtr<mozISpellCheckingEngine> engine =
521 do_GetService(DEFAULT_SPELL_CHECKER, &rv);
522 if (NS_FAILED(rv)) {
523 // Fail if not succeeded to load HunSpell. Ignore errors
524 // for external spellcheck engines.
525 return rv;
526 }
527 aSpellCheckingEngines->AppendObject(engine);
528
529 return NS_OK;
530 }

mercurial