1 /* 2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "config.h" 28 #include "core/editing/TextCheckingHelper.h" 29 30 #include "bindings/v8/ExceptionState.h" 31 #include "bindings/v8/ExceptionStatePlaceholder.h" 32 #include "core/dom/Document.h" 33 #include "core/dom/DocumentMarkerController.h" 34 #include "core/dom/Range.h" 35 #include "core/editing/TextIterator.h" 36 #include "core/editing/VisiblePosition.h" 37 #include "core/editing/VisibleUnits.h" 38 #include "core/page/Frame.h" 39 #include "core/page/Settings.h" 40 #include "core/platform/text/TextBreakIterator.h" 41 #include "core/platform/text/TextCheckerClient.h" 42 43 namespace WebCore { 44 45 static void findBadGrammars(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results) 46 { 47 int checkLocation = start; 48 int checkLength = length; 49 50 while (0 < checkLength) { 51 int badGrammarLocation = -1; 52 int badGrammarLength = 0; 53 Vector<GrammarDetail> badGrammarDetails; 54 client->checkGrammarOfString(String(text + checkLocation, checkLength), badGrammarDetails, &badGrammarLocation, &badGrammarLength); 55 if (!badGrammarLength) 56 break; 57 ASSERT(0 <= badGrammarLocation && badGrammarLocation <= checkLength); 58 ASSERT(0 < badGrammarLength && badGrammarLocation + badGrammarLength <= checkLength); 59 TextCheckingResult badGrammar; 60 badGrammar.type = TextCheckingTypeGrammar; 61 badGrammar.location = checkLocation + badGrammarLocation; 62 badGrammar.length = badGrammarLength; 63 badGrammar.details.swap(badGrammarDetails); 64 results.append(badGrammar); 65 66 checkLocation += (badGrammarLocation + badGrammarLength); 67 checkLength -= (badGrammarLocation + badGrammarLength); 68 } 69 } 70 71 static void findMisspellings(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results) 72 { 73 TextBreakIterator* iterator = wordBreakIterator(text + start, length); 74 if (!iterator) 75 return; 76 int wordStart = textBreakCurrent(iterator); 77 while (0 <= wordStart) { 78 int wordEnd = textBreakNext(iterator); 79 if (wordEnd < 0) 80 break; 81 int wordLength = wordEnd - wordStart; 82 int misspellingLocation = -1; 83 int misspellingLength = 0; 84 client->checkSpellingOfString(String(text + start + wordStart, wordLength), &misspellingLocation, &misspellingLength); 85 if (0 < misspellingLength) { 86 ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength); 87 ASSERT(0 < misspellingLength && misspellingLocation + misspellingLength <= wordLength); 88 TextCheckingResult misspelling; 89 misspelling.type = TextCheckingTypeSpelling; 90 misspelling.location = start + wordStart + misspellingLocation; 91 misspelling.length = misspellingLength; 92 misspelling.replacement = client->getAutoCorrectSuggestionForMisspelledWord(String(text + misspelling.location, misspelling.length)); 93 results.append(misspelling); 94 } 95 96 wordStart = wordEnd; 97 } 98 } 99 100 static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range) 101 { 102 RefPtr<Range> paragraphRange = range->cloneRange(IGNORE_EXCEPTION); 103 setStart(paragraphRange.get(), startOfParagraph(range->startPosition())); 104 setEnd(paragraphRange.get(), endOfParagraph(range->endPosition())); 105 return paragraphRange; 106 } 107 108 TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange) 109 : m_checkingRange(checkingRange) 110 , m_checkingStart(-1) 111 , m_checkingEnd(-1) 112 , m_checkingLength(-1) 113 { 114 } 115 116 TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange) 117 : m_checkingRange(checkingRange) 118 , m_paragraphRange(paragraphRange) 119 , m_checkingStart(-1) 120 , m_checkingEnd(-1) 121 , m_checkingLength(-1) 122 { 123 } 124 125 TextCheckingParagraph::~TextCheckingParagraph() 126 { 127 } 128 129 void TextCheckingParagraph::expandRangeToNextEnd() 130 { 131 ASSERT(m_checkingRange); 132 setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(paragraphRange()->startPosition()))); 133 invalidateParagraphRangeValues(); 134 } 135 136 void TextCheckingParagraph::invalidateParagraphRangeValues() 137 { 138 m_checkingStart = m_checkingEnd = -1; 139 m_offsetAsRange = 0; 140 m_text = String(); 141 } 142 143 int TextCheckingParagraph::rangeLength() const 144 { 145 ASSERT(m_checkingRange); 146 return TextIterator::rangeLength(paragraphRange().get()); 147 } 148 149 PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const 150 { 151 ASSERT(m_checkingRange); 152 if (!m_paragraphRange) 153 m_paragraphRange = expandToParagraphBoundary(checkingRange()); 154 return m_paragraphRange; 155 } 156 157 PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const 158 { 159 ASSERT(m_checkingRange); 160 return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount); 161 } 162 163 int TextCheckingParagraph::offsetTo(const Position& position, ExceptionState& es) const 164 { 165 ASSERT(m_checkingRange); 166 RefPtr<Range> range = offsetAsRange()->cloneRange(ASSERT_NO_EXCEPTION); 167 range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), es); 168 if (es.hadException()) 169 return 0; 170 return TextIterator::rangeLength(range.get()); 171 } 172 173 bool TextCheckingParagraph::isEmpty() const 174 { 175 // Both predicates should have same result, but we check both just for sure. 176 // We need to investigate to remove this redundancy. 177 return isRangeEmpty() || isTextEmpty(); 178 } 179 180 PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const 181 { 182 ASSERT(m_checkingRange); 183 if (!m_offsetAsRange) 184 m_offsetAsRange = Range::create(paragraphRange()->startContainer()->document(), paragraphRange()->startPosition(), checkingRange()->startPosition()); 185 186 return m_offsetAsRange; 187 } 188 189 const String& TextCheckingParagraph::text() const 190 { 191 ASSERT(m_checkingRange); 192 if (m_text.isEmpty()) 193 m_text = plainText(paragraphRange().get()); 194 return m_text; 195 } 196 197 int TextCheckingParagraph::checkingStart() const 198 { 199 ASSERT(m_checkingRange); 200 if (m_checkingStart == -1) 201 m_checkingStart = TextIterator::rangeLength(offsetAsRange().get()); 202 return m_checkingStart; 203 } 204 205 int TextCheckingParagraph::checkingEnd() const 206 { 207 ASSERT(m_checkingRange); 208 if (m_checkingEnd == -1) 209 m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get()); 210 return m_checkingEnd; 211 } 212 213 int TextCheckingParagraph::checkingLength() const 214 { 215 ASSERT(m_checkingRange); 216 if (-1 == m_checkingLength) 217 m_checkingLength = TextIterator::rangeLength(checkingRange().get()); 218 return m_checkingLength; 219 } 220 221 TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range) 222 : m_client(client) 223 , m_range(range) 224 { 225 ASSERT_ARG(m_client, m_client); 226 ASSERT_ARG(m_range, m_range); 227 } 228 229 TextCheckingHelper::~TextCheckingHelper() 230 { 231 } 232 233 String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange) 234 { 235 WordAwareIterator it(m_range.get()); 236 firstMisspellingOffset = 0; 237 238 String firstMisspelling; 239 int currentChunkOffset = 0; 240 241 while (!it.atEnd()) { 242 int length = it.length(); 243 244 // Skip some work for one-space-char hunks 245 if (!(length == 1 && it.characterAt(0) == ' ')) { 246 247 int misspellingLocation = -1; 248 int misspellingLength = 0; 249 m_client->textChecker()->checkSpellingOfString(it.substring(0, length), &misspellingLocation, &misspellingLength); 250 251 // 5490627 shows that there was some code path here where the String constructor below crashes. 252 // We don't know exactly what combination of bad input caused this, so we're making this much 253 // more robust against bad input on release builds. 254 ASSERT(misspellingLength >= 0); 255 ASSERT(misspellingLocation >= -1); 256 ASSERT(!misspellingLength || misspellingLocation >= 0); 257 ASSERT(misspellingLocation < length); 258 ASSERT(misspellingLength <= length); 259 ASSERT(misspellingLocation + misspellingLength <= length); 260 261 if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < length && misspellingLength <= length && misspellingLocation + misspellingLength <= length) { 262 263 // Compute range of misspelled word 264 RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength); 265 266 // Remember first-encountered misspelling and its offset. 267 if (!firstMisspelling) { 268 firstMisspellingOffset = currentChunkOffset + misspellingLocation; 269 firstMisspelling = it.substring(misspellingLocation, misspellingLength); 270 firstMisspellingRange = misspellingRange; 271 } 272 273 // Store marker for misspelled word. 274 misspellingRange->startContainer()->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); 275 276 // Bail out if we're marking only the first misspelling, and not all instances. 277 if (!markAll) 278 break; 279 } 280 } 281 282 currentChunkOffset += length; 283 it.advance(); 284 } 285 286 return firstMisspelling; 287 } 288 289 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) 290 { 291 if (!unifiedTextCheckerEnabled()) 292 return ""; 293 294 String firstFoundItem; 295 String misspelledWord; 296 String badGrammarPhrase; 297 298 // Initialize out parameters; these will be updated if we find something to return. 299 outIsSpelling = true; 300 outFirstFoundOffset = 0; 301 outGrammarDetail.location = -1; 302 outGrammarDetail.length = 0; 303 outGrammarDetail.guesses.clear(); 304 outGrammarDetail.userDescription = ""; 305 306 // Expand the search range to encompass entire paragraphs, since text checking needs that much context. 307 // Determine the character offset from the start of the paragraph to the start of the original search range, 308 // since we will want to ignore results in this area. 309 RefPtr<Range> paragraphRange = m_range->cloneRange(IGNORE_EXCEPTION); 310 setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition())); 311 int totalRangeLength = TextIterator::rangeLength(paragraphRange.get()); 312 setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition())); 313 314 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->startPosition()); 315 int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get()); 316 int totalLengthProcessed = 0; 317 318 bool firstIteration = true; 319 bool lastIteration = false; 320 while (totalLengthProcessed < totalRangeLength) { 321 // Iterate through the search range by paragraphs, checking each one for spelling and grammar. 322 int currentLength = TextIterator::rangeLength(paragraphRange.get()); 323 int currentStartOffset = firstIteration ? rangeStartOffset : 0; 324 int currentEndOffset = currentLength; 325 if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) { 326 // Determine the character offset from the end of the original search range to the end of the paragraph, 327 // since we will want to ignore results in this area. 328 RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->endPosition()); 329 currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get()); 330 lastIteration = true; 331 } 332 if (currentStartOffset < currentEndOffset) { 333 String paragraphString = plainText(paragraphRange.get()); 334 if (paragraphString.length() > 0) { 335 bool foundGrammar = false; 336 int spellingLocation = 0; 337 int grammarPhraseLocation = 0; 338 int grammarDetailLocation = 0; 339 unsigned grammarDetailIndex = 0; 340 341 Vector<TextCheckingResult> results; 342 TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; 343 checkTextOfParagraph(m_client->textChecker(), paragraphString, checkingTypes, results); 344 345 for (unsigned i = 0; i < results.size(); i++) { 346 const TextCheckingResult* result = &results[i]; 347 if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { 348 ASSERT(result->length > 0 && result->location >= 0); 349 spellingLocation = result->location; 350 misspelledWord = paragraphString.substring(result->location, result->length); 351 ASSERT(misspelledWord.length()); 352 break; 353 } 354 if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { 355 ASSERT(result->length > 0 && result->location >= 0); 356 // We can't stop after the first grammar result, since there might still be a spelling result after 357 // it begins but before the first detail in it, but we can stop if we find a second grammar result. 358 if (foundGrammar) 359 break; 360 for (unsigned j = 0; j < result->details.size(); j++) { 361 const GrammarDetail* detail = &result->details[j]; 362 ASSERT(detail->length > 0 && detail->location >= 0); 363 if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { 364 grammarDetailIndex = j; 365 grammarDetailLocation = result->location + detail->location; 366 foundGrammar = true; 367 } 368 } 369 if (foundGrammar) { 370 grammarPhraseLocation = result->location; 371 outGrammarDetail = result->details[grammarDetailIndex]; 372 badGrammarPhrase = paragraphString.substring(result->location, result->length); 373 ASSERT(badGrammarPhrase.length()); 374 } 375 } 376 } 377 378 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { 379 int spellingOffset = spellingLocation - currentStartOffset; 380 if (!firstIteration) { 381 RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition()); 382 spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); 383 } 384 outIsSpelling = true; 385 outFirstFoundOffset = spellingOffset; 386 firstFoundItem = misspelledWord; 387 break; 388 } 389 if (checkGrammar && !badGrammarPhrase.isEmpty()) { 390 int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; 391 if (!firstIteration) { 392 RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition()); 393 grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); 394 } 395 outIsSpelling = false; 396 outFirstFoundOffset = grammarPhraseOffset; 397 firstFoundItem = badGrammarPhrase; 398 break; 399 } 400 } 401 } 402 if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) 403 break; 404 VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition()); 405 setStart(paragraphRange.get(), newParagraphStart); 406 setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart)); 407 firstIteration = false; 408 totalLengthProcessed += currentLength; 409 } 410 return firstFoundItem; 411 } 412 413 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const 414 { 415 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). 416 // Optionally add a DocumentMarker for each detail in the range. 417 int earliestDetailLocationSoFar = -1; 418 int earliestDetailIndex = -1; 419 for (unsigned i = 0; i < grammarDetails.size(); i++) { 420 const GrammarDetail* detail = &grammarDetails[i]; 421 ASSERT(detail->length > 0 && detail->location >= 0); 422 423 int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location; 424 425 // Skip this detail if it starts before the original search range 426 if (detailStartOffsetInParagraph < startOffset) 427 continue; 428 429 // Skip this detail if it starts after the original search range 430 if (detailStartOffsetInParagraph >= endOffset) 431 continue; 432 433 if (markAll) { 434 RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length); 435 badGrammarRange->startContainer()->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription); 436 } 437 438 // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order) 439 if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) { 440 earliestDetailIndex = i; 441 earliestDetailLocationSoFar = detail->location; 442 } 443 } 444 445 return earliestDetailIndex; 446 } 447 448 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll) 449 { 450 // Initialize out parameters; these will be updated if we find something to return. 451 outGrammarDetail.location = -1; 452 outGrammarDetail.length = 0; 453 outGrammarDetail.guesses.clear(); 454 outGrammarDetail.userDescription = ""; 455 outGrammarPhraseOffset = 0; 456 457 String firstBadGrammarPhrase; 458 459 // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context. 460 // Determine the character offset from the start of the paragraph to the start of the original search range, 461 // since we will want to ignore results in this area. 462 TextCheckingParagraph paragraph(m_range); 463 464 // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range. 465 int startOffset = 0; 466 while (startOffset < paragraph.checkingEnd()) { 467 Vector<GrammarDetail> grammarDetails; 468 int badGrammarPhraseLocation = -1; 469 int badGrammarPhraseLength = 0; 470 m_client->textChecker()->checkGrammarOfString(paragraph.textSubstring(startOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength); 471 472 if (!badGrammarPhraseLength) { 473 ASSERT(badGrammarPhraseLocation == -1); 474 return String(); 475 } 476 477 ASSERT(badGrammarPhraseLocation >= 0); 478 badGrammarPhraseLocation += startOffset; 479 480 481 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). 482 int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll); 483 if (badGrammarIndex >= 0) { 484 ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size()); 485 outGrammarDetail = grammarDetails[badGrammarIndex]; 486 } 487 488 // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but 489 // kept going so we could mark all instances). 490 if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) { 491 outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart(); 492 firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength); 493 494 // Found one. We're done now, unless we're marking each instance. 495 if (!markAll) 496 break; 497 } 498 499 // These results were all between the start of the paragraph and the start of the search range; look 500 // beyond this phrase. 501 startOffset = badGrammarPhraseLocation + badGrammarPhraseLength; 502 } 503 504 return firstBadGrammarPhrase; 505 } 506 507 void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange) 508 { 509 // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter"; 510 // all we need to do is mark every instance. 511 int ignoredOffset; 512 findFirstMisspelling(ignoredOffset, true, firstMisspellingRange); 513 } 514 515 void TextCheckingHelper::markAllBadGrammar() 516 { 517 // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to 518 // do is mark every instance. 519 GrammarDetail ignoredGrammarDetail; 520 int ignoredOffset; 521 findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true); 522 } 523 524 bool TextCheckingHelper::unifiedTextCheckerEnabled() const 525 { 526 if (!m_range) 527 return false; 528 529 Document* doc = m_range->ownerDocument(); 530 if (!doc) 531 return false; 532 533 return WebCore::unifiedTextCheckerEnabled(doc->frame()); 534 } 535 536 void checkTextOfParagraph(TextCheckerClient* client, const String& text, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results) 537 { 538 Vector<UChar> characters; 539 text.appendTo(characters); 540 unsigned length = text.length(); 541 542 Vector<TextCheckingResult> spellingResult; 543 if (checkingTypes & TextCheckingTypeSpelling) 544 findMisspellings(client, characters.data(), 0, length, spellingResult); 545 546 Vector<TextCheckingResult> grammarResult; 547 if (checkingTypes & TextCheckingTypeGrammar) { 548 // Only checks grammartical error before the first misspellings 549 int grammarCheckLength = length; 550 for (size_t i = 0; i < spellingResult.size(); ++i) { 551 if (spellingResult[i].location < grammarCheckLength) 552 grammarCheckLength = spellingResult[i].location; 553 } 554 555 findBadGrammars(client, characters.data(), 0, grammarCheckLength, grammarResult); 556 } 557 558 if (grammarResult.size()) 559 results.swap(grammarResult); 560 561 if (spellingResult.size()) { 562 if (results.isEmpty()) 563 results.swap(spellingResult); 564 else 565 results.append(spellingResult); 566 } 567 } 568 569 bool unifiedTextCheckerEnabled(const Frame* frame) 570 { 571 if (!frame) 572 return false; 573 574 const Settings* settings = frame->settings(); 575 if (!settings) 576 return false; 577 578 return settings->unifiedTextCheckerEnabled(); 579 } 580 581 } 582