| Index: third_party/WebKit/Source/core/editing/spellcheck/TextCheckingHelper.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingHelper.cpp b/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingHelper.cpp
|
| index b0e55b7e7e62695cf380d3db75fb31947120cd34..7ba54bf4aacd1a37851e81d99304929ca4abce93 100644
|
| --- a/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingHelper.cpp
|
| +++ b/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingHelper.cpp
|
| @@ -41,6 +41,34 @@
|
|
|
| namespace blink {
|
|
|
| +static void findBadGrammars(TextCheckerClient& client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
|
| +{
|
| + int checkLocation = start;
|
| + int checkLength = length;
|
| +
|
| + while (0 < checkLength) {
|
| + int badGrammarLocation = -1;
|
| + int badGrammarLength = 0;
|
| + Vector<GrammarDetail> badGrammarDetails;
|
| + client.checkGrammarOfString(String(text + checkLocation, checkLength), badGrammarDetails, &badGrammarLocation, &badGrammarLength);
|
| + if (!badGrammarLength)
|
| + break;
|
| + DCHECK_LE(0, badGrammarLocation);
|
| + DCHECK_LE(badGrammarLocation, checkLength);
|
| + DCHECK_LT(0, badGrammarLength);
|
| + DCHECK_LE(badGrammarLocation + badGrammarLength, checkLength);
|
| + TextCheckingResult badGrammar;
|
| + badGrammar.decoration = TextDecorationTypeGrammar;
|
| + badGrammar.location = checkLocation + badGrammarLocation;
|
| + badGrammar.length = badGrammarLength;
|
| + badGrammar.details.swap(badGrammarDetails);
|
| + results.append(badGrammar);
|
| +
|
| + checkLocation += (badGrammarLocation + badGrammarLength);
|
| + checkLength -= (badGrammarLocation + badGrammarLength);
|
| + }
|
| +}
|
| +
|
| static void findMisspellings(TextCheckerClient& client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
|
| {
|
| TextBreakIterator* iterator = wordBreakIterator(text + start, length);
|
| @@ -286,6 +314,222 @@ String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, boo
|
| return firstMisspelling;
|
| }
|
|
|
| +String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
|
| +{
|
| + if (!unifiedTextCheckerEnabled())
|
| + return "";
|
| +
|
| + String firstFoundItem;
|
| + String misspelledWord;
|
| + String badGrammarPhrase;
|
| +
|
| + // Initialize out parameters; these will be updated if we find something to return.
|
| + outIsSpelling = true;
|
| + outFirstFoundOffset = 0;
|
| + outGrammarDetail.location = -1;
|
| + outGrammarDetail.length = 0;
|
| + outGrammarDetail.guesses.clear();
|
| + outGrammarDetail.userDescription = "";
|
| +
|
| + // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
|
| + // Determine the character offset from the start of the paragraph to the start of the original search range,
|
| + // since we will want to ignore results in this area.
|
| + Position paragraphStart = startOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition();
|
| + Position paragraphEnd = m_end;
|
| + int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEnd);
|
| + paragraphEnd = endOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition();
|
| +
|
| + int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start);
|
| + int totalLengthProcessed = 0;
|
| +
|
| + bool firstIteration = true;
|
| + bool lastIteration = false;
|
| + while (totalLengthProcessed < totalRangeLength) {
|
| + // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
|
| + int currentLength = TextIterator::rangeLength(paragraphStart, paragraphEnd);
|
| + int currentStartOffset = firstIteration ? rangeStartOffset : 0;
|
| + int currentEndOffset = currentLength;
|
| + if (inSameParagraph(createVisiblePosition(paragraphStart), createVisiblePosition(m_end))) {
|
| + // Determine the character offset from the end of the original search range to the end of the paragraph,
|
| + // since we will want to ignore results in this area.
|
| + currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end);
|
| + lastIteration = true;
|
| + }
|
| + if (currentStartOffset < currentEndOffset) {
|
| + String paragraphString = plainText(EphemeralRange(paragraphStart, paragraphEnd));
|
| + if (paragraphString.length() > 0) {
|
| + bool foundGrammar = false;
|
| + int spellingLocation = 0;
|
| + int grammarPhraseLocation = 0;
|
| + int grammarDetailLocation = 0;
|
| + unsigned grammarDetailIndex = 0;
|
| +
|
| + Vector<TextCheckingResult> results;
|
| + TextCheckingTypeMask checkingTypes = TextCheckingTypeSpelling | TextCheckingTypeGrammar;
|
| + checkTextOfParagraph(m_client->textChecker(), paragraphString, checkingTypes, results);
|
| +
|
| + for (unsigned i = 0; i < results.size(); i++) {
|
| + const TextCheckingResult* result = &results[i];
|
| + if (result->decoration == TextDecorationTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
|
| + DCHECK_GT(result->length, 0);
|
| + DCHECK_GE(result->location, 0);
|
| + spellingLocation = result->location;
|
| + misspelledWord = paragraphString.substring(result->location, result->length);
|
| + DCHECK(misspelledWord.length());
|
| + break;
|
| + }
|
| + if (result->decoration == TextDecorationTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
|
| + DCHECK_GT(result->length, 0);
|
| + DCHECK_GE(result->location, 0);
|
| + // We can't stop after the first grammar result, since there might still be a spelling result after
|
| + // it begins but before the first detail in it, but we can stop if we find a second grammar result.
|
| + if (foundGrammar)
|
| + break;
|
| + for (unsigned j = 0; j < result->details.size(); j++) {
|
| + const GrammarDetail* detail = &result->details[j];
|
| + DCHECK_GT(detail->length, 0);
|
| + DCHECK_GE(detail->location, 0);
|
| + if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
|
| + grammarDetailIndex = j;
|
| + grammarDetailLocation = result->location + detail->location;
|
| + foundGrammar = true;
|
| + }
|
| + }
|
| + if (foundGrammar) {
|
| + grammarPhraseLocation = result->location;
|
| + outGrammarDetail = result->details[grammarDetailIndex];
|
| + badGrammarPhrase = paragraphString.substring(result->location, result->length);
|
| + DCHECK(badGrammarPhrase.length());
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (!misspelledWord.isEmpty() && (badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
|
| + int spellingOffset = spellingLocation - currentStartOffset;
|
| + if (!firstIteration)
|
| + spellingOffset += TextIterator::rangeLength(m_start, paragraphStart);
|
| + outIsSpelling = true;
|
| + outFirstFoundOffset = spellingOffset;
|
| + firstFoundItem = misspelledWord;
|
| + break;
|
| + }
|
| + if (!badGrammarPhrase.isEmpty()) {
|
| + int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
|
| + if (!firstIteration)
|
| + grammarPhraseOffset += TextIterator::rangeLength(m_start, paragraphStart);
|
| + outIsSpelling = false;
|
| + outFirstFoundOffset = grammarPhraseOffset;
|
| + firstFoundItem = badGrammarPhrase;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
|
| + break;
|
| + VisiblePosition newParagraphStart = startOfNextParagraph(createVisiblePosition(paragraphEnd));
|
| + paragraphStart = newParagraphStart.toParentAnchoredPosition();
|
| + paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPosition();
|
| + firstIteration = false;
|
| + totalLengthProcessed += currentLength;
|
| + }
|
| + return firstFoundItem;
|
| +}
|
| +
|
| +int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const
|
| +{
|
| + // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
|
| + // Optionally add a DocumentMarker for each detail in the range.
|
| + int earliestDetailLocationSoFar = -1;
|
| + int earliestDetailIndex = -1;
|
| + for (unsigned i = 0; i < grammarDetails.size(); i++) {
|
| + const GrammarDetail* detail = &grammarDetails[i];
|
| + DCHECK_GT(detail->length, 0);
|
| + DCHECK_GE(detail->location, 0);
|
| +
|
| + int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
|
| +
|
| + // Skip this detail if it starts before the original search range
|
| + if (detailStartOffsetInParagraph < startOffset)
|
| + continue;
|
| +
|
| + // Skip this detail if it starts after the original search range
|
| + if (detailStartOffsetInParagraph >= endOffset)
|
| + continue;
|
| +
|
| + if (markAll) {
|
| + const EphemeralRange badGrammarRange = calculateCharacterSubrange(EphemeralRange(m_start, m_end), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
|
| + badGrammarRange.document().markers().addMarker(badGrammarRange.startPosition(), badGrammarRange.endPosition(), DocumentMarker::Grammar, detail->userDescription);
|
| + }
|
| +
|
| + // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
|
| + if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
|
| + earliestDetailIndex = i;
|
| + earliestDetailLocationSoFar = detail->location;
|
| + }
|
| + }
|
| +
|
| + return earliestDetailIndex;
|
| +}
|
| +
|
| +String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
|
| +{
|
| + // Initialize out parameters; these will be updated if we find something to return.
|
| + outGrammarDetail.location = -1;
|
| + outGrammarDetail.length = 0;
|
| + outGrammarDetail.guesses.clear();
|
| + outGrammarDetail.userDescription = "";
|
| + outGrammarPhraseOffset = 0;
|
| +
|
| + String firstBadGrammarPhrase;
|
| +
|
| + // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
|
| + // Determine the character offset from the start of the paragraph to the start of the original search range,
|
| + // since we will want to ignore results in this area.
|
| + TextCheckingParagraph paragraph(EphemeralRange(m_start, m_end));
|
| +
|
| + // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
|
| + int startOffset = 0;
|
| + while (startOffset < paragraph.checkingEnd()) {
|
| + Vector<GrammarDetail> grammarDetails;
|
| + int badGrammarPhraseLocation = -1;
|
| + int badGrammarPhraseLength = 0;
|
| + m_client->textChecker().checkGrammarOfString(paragraph.textSubstring(startOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
|
| +
|
| + if (!badGrammarPhraseLength) {
|
| + DCHECK_EQ(badGrammarPhraseLocation, -1);
|
| + return String();
|
| + }
|
| +
|
| + DCHECK_GE(badGrammarPhraseLocation, 0);
|
| + badGrammarPhraseLocation += startOffset;
|
| +
|
| +
|
| + // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
|
| + int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
|
| + if (badGrammarIndex >= 0) {
|
| + DCHECK_LT(static_cast<unsigned>(badGrammarIndex), grammarDetails.size());
|
| + outGrammarDetail = grammarDetails[badGrammarIndex];
|
| + }
|
| +
|
| + // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
|
| + // kept going so we could mark all instances).
|
| + if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
|
| + outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
|
| + firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
|
| +
|
| + // Found one. We're done now, unless we're marking each instance.
|
| + if (!markAll)
|
| + break;
|
| + }
|
| +
|
| + // These results were all between the start of the paragraph and the start of the search range; look
|
| + // beyond this phrase.
|
| + startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
|
| + }
|
| +
|
| + return firstBadGrammarPhrase;
|
| +}
|
| +
|
| bool TextCheckingHelper::markAllMisspellings()
|
| {
|
| // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
|
| @@ -294,6 +538,15 @@ bool TextCheckingHelper::markAllMisspellings()
|
| return findFirstMisspelling(ignoredOffset, true).isEmpty();
|
| }
|
|
|
| +void TextCheckingHelper::markAllBadGrammar()
|
| +{
|
| + // Use the "markAll" feature of findFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
|
| + // do is mark every instance.
|
| + GrammarDetail ignoredGrammarDetail;
|
| + int ignoredOffset;
|
| + findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
|
| +}
|
| +
|
| bool TextCheckingHelper::unifiedTextCheckerEnabled() const
|
| {
|
| DCHECK(m_start.isNotNull());
|
| @@ -311,8 +564,27 @@ void checkTextOfParagraph(TextCheckerClient& client, const String& text, TextChe
|
| if (checkingTypes & TextCheckingTypeSpelling)
|
| findMisspellings(client, characters.data(), 0, length, spellingResult);
|
|
|
| - if (spellingResult.size())
|
| - results.swap(spellingResult);
|
| + Vector<TextCheckingResult> grammarResult;
|
| + if (checkingTypes & TextCheckingTypeGrammar) {
|
| + // Only checks grammartical error before the first misspellings
|
| + int grammarCheckLength = length;
|
| + for (const auto& spelling : spellingResult) {
|
| + if (spelling.location < grammarCheckLength)
|
| + grammarCheckLength = spelling.location;
|
| + }
|
| +
|
| + findBadGrammars(client, characters.data(), 0, grammarCheckLength, grammarResult);
|
| + }
|
| +
|
| + if (grammarResult.size())
|
| + results.swap(grammarResult);
|
| +
|
| + if (spellingResult.size()) {
|
| + if (results.isEmpty())
|
| + results.swap(spellingResult);
|
| + else
|
| + results.appendVector(spellingResult);
|
| + }
|
| }
|
|
|
| bool unifiedTextCheckerEnabled(const LocalFrame* frame)
|
|
|