summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/editing/TextCheckingHelper.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-13 06:44:40 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2011-05-13 06:44:40 -0700
commit08014c20784f3db5df3a89b73cce46037b77eb59 (patch)
tree47749210d31e19e6e2f64036fa8fae2ad693476f /Source/WebCore/editing/TextCheckingHelper.cpp
parent860220379e56aeb66424861ad602b07ee22b4055 (diff)
parent4c3661f7918f8b3f139f824efb7855bedccb4c94 (diff)
downloadexternal_webkit-08014c20784f3db5df3a89b73cce46037b77eb59.zip
external_webkit-08014c20784f3db5df3a89b73cce46037b77eb59.tar.gz
external_webkit-08014c20784f3db5df3a89b73cce46037b77eb59.tar.bz2
Merge changes Ide388898,Ic49f367c,I1158a808,Iacb6ca5d,I2100dd3a,I5c1abe54,Ib0ef9902,I31dbc523,I570314b3
* changes: Merge WebKit at r75315: Update WebKit version Merge WebKit at r75315: Add FrameLoaderClient PageCache stubs Merge WebKit at r75315: Stub out AXObjectCache::remove() Merge WebKit at r75315: Fix ImageBuffer Merge WebKit at r75315: Fix PluginData::initPlugins() Merge WebKit at r75315: Fix conflicts Merge WebKit at r75315: Fix Makefiles Merge WebKit at r75315: Move Android-specific WebCore files to Source Merge WebKit at r75315: Initial merge by git.
Diffstat (limited to 'Source/WebCore/editing/TextCheckingHelper.cpp')
-rw-r--r--Source/WebCore/editing/TextCheckingHelper.cpp605
1 files changed, 605 insertions, 0 deletions
diff --git a/Source/WebCore/editing/TextCheckingHelper.cpp b/Source/WebCore/editing/TextCheckingHelper.cpp
new file mode 100644
index 0000000..b4429a6
--- /dev/null
+++ b/Source/WebCore/editing/TextCheckingHelper.cpp
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "TextCheckingHelper.h"
+
+#include "DocumentMarkerController.h"
+#include "Range.h"
+#include "TextIterator.h"
+#include "VisiblePosition.h"
+#include "visible_units.h"
+
+namespace WebCore {
+
+static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range)
+{
+ ExceptionCode ec = 0;
+ RefPtr<Range> paragraphRange = range->cloneRange(ec);
+ setStart(paragraphRange.get(), startOfParagraph(range->startPosition()));
+ setEnd(paragraphRange.get(), endOfParagraph(range->endPosition()));
+ return paragraphRange;
+}
+
+TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange)
+ : m_checkingRange(checkingRange)
+ , m_checkingStart(-1)
+ , m_checkingEnd(-1)
+ , m_checkingLength(-1)
+{
+}
+
+TextCheckingParagraph::~TextCheckingParagraph()
+{
+}
+
+void TextCheckingParagraph::expandRangeToNextEnd()
+{
+ ASSERT(m_checkingRange);
+ setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(paragraphRange()->startPosition())));
+ invalidateParagraphRangeValues();
+}
+
+void TextCheckingParagraph::invalidateParagraphRangeValues()
+{
+ m_checkingStart = m_checkingEnd = -1;
+ m_offsetAsRange = 0;
+ m_text = String();
+}
+
+int TextCheckingParagraph::rangeLength() const
+{
+ ASSERT(m_checkingRange);
+ return TextIterator::rangeLength(paragraphRange().get());
+}
+
+PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const
+{
+ ASSERT(m_checkingRange);
+ if (!m_paragraphRange)
+ m_paragraphRange = expandToParagraphBoundary(checkingRange());
+ return m_paragraphRange;
+}
+
+PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
+{
+ ASSERT(m_checkingRange);
+ return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
+}
+
+int TextCheckingParagraph::offsetTo(const Position& position, ExceptionCode& ec) const
+{
+ ASSERT(m_checkingRange);
+ RefPtr<Range> range = offsetAsRange();
+ range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), ec);
+ if (ec)
+ return 0;
+ return TextIterator::rangeLength(range.get());
+}
+
+bool TextCheckingParagraph::isEmpty() const
+{
+ // Both predicates should have same result, but we check both just for sure.
+ // We need to investigate to remove this redundancy.
+ return isRangeEmpty() || isTextEmpty();
+}
+
+PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const
+{
+ ASSERT(m_checkingRange);
+ if (!m_offsetAsRange) {
+ ExceptionCode ec = 0;
+ m_offsetAsRange = Range::create(paragraphRange()->startContainer(ec)->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
+ }
+
+ return m_offsetAsRange;
+}
+
+const String& TextCheckingParagraph::text() const
+{
+ ASSERT(m_checkingRange);
+ if (m_text.isEmpty())
+ m_text = plainText(paragraphRange().get());
+ return m_text;
+}
+
+int TextCheckingParagraph::checkingStart() const
+{
+ ASSERT(m_checkingRange);
+ if (m_checkingStart == -1)
+ m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
+ return m_checkingStart;
+}
+
+int TextCheckingParagraph::checkingEnd() const
+{
+ ASSERT(m_checkingRange);
+ if (m_checkingEnd == -1)
+ m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
+ return m_checkingEnd;
+}
+
+int TextCheckingParagraph::checkingLength() const
+{
+ ASSERT(m_checkingRange);
+ if (-1 == m_checkingLength)
+ m_checkingLength = TextIterator::rangeLength(checkingRange().get());
+ return m_checkingLength;
+}
+
+TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range)
+ : m_client(client)
+ , m_range(range)
+{
+ ASSERT_ARG(m_client, m_client);
+ ASSERT_ARG(m_range, m_range);
+}
+
+TextCheckingHelper::~TextCheckingHelper()
+{
+}
+
+String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
+{
+ WordAwareIterator it(m_range.get());
+ firstMisspellingOffset = 0;
+
+ String firstMisspelling;
+ int currentChunkOffset = 0;
+
+ while (!it.atEnd()) {
+ const UChar* chars = it.characters();
+ int len = it.length();
+
+ // Skip some work for one-space-char hunks
+ if (!(len == 1 && chars[0] == ' ')) {
+
+ int misspellingLocation = -1;
+ int misspellingLength = 0;
+ m_client->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
+
+ // 5490627 shows that there was some code path here where the String constructor below crashes.
+ // We don't know exactly what combination of bad input caused this, so we're making this much
+ // more robust against bad input on release builds.
+ ASSERT(misspellingLength >= 0);
+ ASSERT(misspellingLocation >= -1);
+ ASSERT(!misspellingLength || misspellingLocation >= 0);
+ ASSERT(misspellingLocation < len);
+ ASSERT(misspellingLength <= len);
+ ASSERT(misspellingLocation + misspellingLength <= len);
+
+ if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
+
+ // Compute range of misspelled word
+ RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
+
+ // Remember first-encountered misspelling and its offset.
+ if (!firstMisspelling) {
+ firstMisspellingOffset = currentChunkOffset + misspellingLocation;
+ firstMisspelling = String(chars + misspellingLocation, misspellingLength);
+ firstMisspellingRange = misspellingRange;
+ }
+
+ // Store marker for misspelled word.
+ ExceptionCode ec = 0;
+ misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
+ ASSERT(!ec);
+
+ // Bail out if we're marking only the first misspelling, and not all instances.
+ if (!markAll)
+ break;
+ }
+ }
+
+ currentChunkOffset += len;
+ it.advance();
+ }
+
+ return firstMisspelling;
+}
+
+String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+ String firstFoundItem;
+ String misspelledWord;
+ String badGrammarPhrase;
+ ExceptionCode ec = 0;
+
+ // 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.
+ RefPtr<Range> paragraphRange = m_range->cloneRange(ec);
+ setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
+ int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
+ setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
+
+ RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->startPosition());
+ int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
+ 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(paragraphRange.get());
+ int currentStartOffset = firstIteration ? rangeStartOffset : 0;
+ int currentEndOffset = currentLength;
+ if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
+ // 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.
+ RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->endPosition());
+ currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
+ lastIteration = true;
+ }
+ if (currentStartOffset < currentEndOffset) {
+ String paragraphString = plainText(paragraphRange.get());
+ if (paragraphString.length() > 0) {
+ bool foundGrammar = false;
+ int spellingLocation = 0;
+ int grammarPhraseLocation = 0;
+ int grammarDetailLocation = 0;
+ unsigned grammarDetailIndex = 0;
+
+ Vector<TextCheckingResult> results;
+ uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+ m_client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
+
+ for (unsigned i = 0; i < results.size(); i++) {
+ const TextCheckingResult* result = &results[i];
+ if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
+ ASSERT(result->length > 0 && result->location >= 0);
+ spellingLocation = result->location;
+ misspelledWord = paragraphString.substring(result->location, result->length);
+ ASSERT(misspelledWord.length());
+ break;
+ }
+ if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
+ ASSERT(result->length > 0 && 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];
+ ASSERT(detail->length > 0 && 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);
+ ASSERT(badGrammarPhrase.length());
+ }
+ }
+ }
+
+ if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
+ int spellingOffset = spellingLocation - currentStartOffset;
+ if (!firstIteration) {
+ RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
+ spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
+ }
+ outIsSpelling = true;
+ outFirstFoundOffset = spellingOffset;
+ firstFoundItem = misspelledWord;
+ break;
+ }
+ if (checkGrammar && !badGrammarPhrase.isEmpty()) {
+ int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
+ if (!firstIteration) {
+ RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
+ grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
+ }
+ outIsSpelling = false;
+ outFirstFoundOffset = grammarPhraseOffset;
+ firstFoundItem = badGrammarPhrase;
+ break;
+ }
+ }
+ }
+ if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
+ break;
+ VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
+ setStart(paragraphRange.get(), newParagraphStart);
+ setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
+ firstIteration = false;
+ totalLengthProcessed += currentLength;
+ }
+ return firstFoundItem;
+#else
+ ASSERT_NOT_REACHED();
+ UNUSED_PARAM(checkGrammar);
+ UNUSED_PARAM(outIsSpelling);
+ UNUSED_PARAM(outFirstFoundOffset);
+ UNUSED_PARAM(outGrammarDetail);
+ return "";
+#endif
+}
+
+int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll)
+{
+#ifndef BUILDING_ON_TIGER
+ // 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];
+ ASSERT(detail->length > 0 && 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) {
+ RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
+ ExceptionCode ec = 0;
+ badGrammarRange->startContainer(ec)->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
+ ASSERT(!ec);
+ }
+
+ // 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;
+#else
+ ASSERT_NOT_REACHED();
+ UNUSED_PARAM(grammarDetails);
+ UNUSED_PARAM(badGrammarPhraseLocation);
+ UNUSED_PARAM(startOffset);
+ UNUSED_PARAM(endOffset);
+ UNUSED_PARAM(markAll);
+ return 0;
+#endif
+}
+
+String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
+{
+#ifndef BUILDING_ON_TIGER
+ // 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(m_range);
+
+ // 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->checkGrammarOfString(paragraph.textCharacters() + startOffset, paragraph.textLength() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
+
+ if (!badGrammarPhraseLength) {
+ ASSERT(badGrammarPhraseLocation == -1);
+ return String();
+ }
+
+ ASSERT(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, badGrammarPhraseLength, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
+ if (badGrammarIndex >= 0) {
+ ASSERT(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;
+#else
+ ASSERT_NOT_REACHED();
+ UNUSED_PARAM(outGrammarDetail);
+ UNUSED_PARAM(outGrammarPhraseOffset);
+ UNUSED_PARAM(markAll);
+#endif
+}
+
+
+bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const
+{
+#ifndef BUILDING_ON_TIGER
+ if (!m_client)
+ return false;
+
+ ExceptionCode ec;
+ if (!m_range || m_range->collapsed(ec))
+ return false;
+
+ // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
+ // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
+ // or overlapping the range; the ranges must exactly match.
+ guessesVector.clear();
+ int grammarPhraseOffset;
+
+ GrammarDetail grammarDetail;
+ String badGrammarPhrase = const_cast<TextCheckingHelper*>(this)->findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
+
+ // No bad grammar in these parts at all.
+ if (badGrammarPhrase.isEmpty())
+ return false;
+
+ // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
+ if (grammarPhraseOffset > 0)
+ return false;
+
+ ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
+
+ // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
+ if (grammarDetail.location + grammarPhraseOffset)
+ return false;
+
+ // Bad grammar at start of range, but end of bad grammar is before or after end of range
+ if (grammarDetail.length != TextIterator::rangeLength(m_range.get()))
+ return false;
+
+ // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
+ // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
+ // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
+ // or a grammar error.
+ m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
+
+ return true;
+#else
+ ASSERT_NOT_REACHED();
+ UNUSED_PARAM(guessesVector);
+ return true;
+#endif
+}
+
+Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+ Vector<String> guesses;
+ ExceptionCode ec;
+ misspelled = false;
+ ungrammatical = false;
+
+ if (!m_client || !m_range || m_range->collapsed(ec))
+ return guesses;
+
+ // Expand the range to encompass entire paragraphs, since text checking needs that much context.
+ TextCheckingParagraph paragraph(m_range);
+ if (paragraph.isEmpty())
+ return guesses;
+
+ Vector<TextCheckingResult> results;
+ uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+ m_client->checkTextOfParagraph(paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
+
+ for (unsigned i = 0; i < results.size(); i++) {
+ const TextCheckingResult* result = &results[i];
+ if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) {
+ String misspelledWord = paragraph.checkingSubstring();
+ ASSERT(misspelledWord.length());
+ m_client->getGuessesForWord(misspelledWord, String(), guesses);
+ m_client->updateSpellingUIWithMisspelledWord(misspelledWord);
+ misspelled = true;
+ return guesses;
+ }
+ }
+
+ if (!checkGrammar)
+ return guesses;
+
+ for (unsigned i = 0; i < results.size(); i++) {
+ const TextCheckingResult* result = &results[i];
+ if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) {
+ for (unsigned j = 0; j < result->details.size(); j++) {
+ const GrammarDetail* detail = &result->details[j];
+ ASSERT(detail->length > 0 && detail->location >= 0);
+ if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) {
+ String badGrammarPhrase = paragraph.textSubstring(result->location, result->length);
+ ASSERT(badGrammarPhrase.length());
+ for (unsigned k = 0; k < detail->guesses.size(); k++)
+ guesses.append(detail->guesses[k]);
+ m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
+ ungrammatical = true;
+ return guesses;
+ }
+ }
+ }
+ }
+ return guesses;
+#else
+ ASSERT_NOT_REACHED();
+ UNUSED_PARAM(checkGrammar);
+ UNUSED_PARAM(misspelled);
+ UNUSED_PARAM(ungrammatical);
+ return Vector<String>();
+#endif
+}
+
+
+void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
+{
+ // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
+ // all we need to do is mark every instance.
+ int ignoredOffset;
+ findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
+}
+
+void TextCheckingHelper::markAllBadGrammar()
+{
+#ifndef BUILDING_ON_TIGER
+ // 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);
+#else
+ ASSERT_NOT_REACHED();
+#endif
+}
+
+}