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) |