bool isRangeTextAllWhitespace(VisiblePosition startPosition, VisiblePosition endPosition) { while (isWhitespace(startPosition.characterAfter())) { startPosition = startPosition.next(); if (startPosition == endPosition) return true; } return false; }
void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) { Frame& frame = this->frame(); #if PLATFORM(MAC) if (!frame.editor().isContinuousSpellCheckingEnabled() && !frame.editor().isAutomaticQuoteSubstitutionEnabled() && !frame.editor().isAutomaticLinkDetectionEnabled() && !frame.editor().isAutomaticDashSubstitutionEnabled() && !frame.editor().isAutomaticTextReplacementEnabled()) return; if (frame.editor().isHandlingAcceptedCandidate()) return; #else if (!frame.editor().isContinuousSpellCheckingEnabled()) return; #endif // Take a look at the selection that results after typing and determine whether we need to spellcheck. // Since the word containing the current selection is never marked, this does a check to // see if typing made a new word that is not in the current selection. Basically, you // get this by being at the end of a word and typing a space. VisiblePosition start(endingSelection().start(), endingSelection().affinity()); VisiblePosition previous = start.previous(); if (previous.isNotNull()) { #if !PLATFORM(IOS) VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) { RefPtr<Range> range = makeRange(p1, p2); String strippedPreviousWord; if (range && (commandType == TypingCommand::InsertText || commandType == TypingCommand::InsertLineBreak || commandType == TypingCommand::InsertParagraphSeparator || commandType == TypingCommand::InsertParagraphSeparatorInQuotedContent)) strippedPreviousWord = plainText(range.get()).stripWhiteSpace(); frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), !strippedPreviousWord.isEmpty()); } else if (commandType == TypingCommand::InsertText) frame.editor().startAlternativeTextUITimer(); #else UNUSED_PARAM(commandType); // If this bug gets fixed, this PLATFORM(IOS) code could be removed: // <rdar://problem/7259611> Word boundary code on iPhone gives different results than desktop EWordSide startWordSide = LeftWordIfOnBoundary; UChar32 c = previous.characterAfter(); // FIXME: VisiblePosition::characterAfter() and characterBefore() do not emit newlines the same // way as TextIterator, so we do an isEndOfParagraph check here. if (isSpaceOrNewline(c) || c == 0xA0 || isEndOfParagraph(previous)) { startWordSide = RightWordIfOnBoundary; } VisiblePosition p1 = startOfWord(previous, startWordSide); VisiblePosition p2 = startOfWord(start, startWordSide); if (p1 != p2) frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), false); #endif // !PLATFORM(IOS) } }
PassRefPtr<Range> trimWhitespaceFromRange(VisiblePosition startPosition, VisiblePosition endPosition) { if (startPosition == endPosition || isRangeTextAllWhitespace(startPosition, endPosition)) return 0; while (isWhitespace(startPosition.characterAfter())) startPosition = startPosition.next(); while (isWhitespace(endPosition.characterBefore())) endPosition = endPosition.previous(); return makeRange(startPosition, endPosition); }
static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) { Position pos = c.deepEquivalent(); Node *n = pos.node(); if (!n) return VisiblePosition(); Document *d = n->document(); Node *de = d->documentElement(); if (!de) return VisiblePosition(); Node *boundary = n->enclosingBlockFlowElement(); if (!boundary) return VisiblePosition(); bool isContentEditable = boundary->isContentEditable(); while (boundary && boundary != de && boundary->parentNode() && isContentEditable == boundary->parentNode()->isContentEditable()) boundary = boundary->parentNode(); RefPtr<Range> searchRange(d->createRange()); Position start(rangeCompliantEquivalent(pos)); Vector<UChar, 1024> string; unsigned prefixLength = 0; ExceptionCode ec = 0; if (requiresContextForWordBoundary(c.characterAfter())) { RefPtr<Range> backwardsScanRange(d->createRange()); backwardsScanRange->setEnd(start.node(), start.deprecatedEditingOffset(), ec); SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange.get()); while (!backwardsIterator.atEnd()) { const UChar* characters = backwardsIterator.characters(); int length = backwardsIterator.length(); int i = startOfLastWordBoundaryContext(characters, length); string.prepend(characters + i, length - i); prefixLength += length - i; if (i > 0) break; backwardsIterator.advance(); } } searchRange->selectNodeContents(boundary, ec); searchRange->setStart(start.node(), start.deprecatedEditingOffset(), ec); TextIterator it(searchRange.get(), true); unsigned next = 0; bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE; bool needMoreContext = false; while (!it.atEnd()) { // Keep asking the iterator for chunks until the search function // returns an end value not equal to the length of the string passed to it. if (!inTextSecurityMode) string.append(it.characters(), it.length()); else { // Treat bullets used in the text security mode as regular characters when looking for boundaries String iteratorString(it.characters(), it.length()); iteratorString = iteratorString.impl()->secure('x'); string.append(iteratorString.characters(), iteratorString.length()); } next = searchFunction(string.data(), string.size(), prefixLength, MayHaveMoreContext, needMoreContext); if (next != string.size()) break; it.advance(); } if (needMoreContext) { // The last search returned the end of the buffer and asked for more context, // but there is no further text. Force a search with what's available. next = searchFunction(string.data(), string.size(), prefixLength, DontHaveMoreContext, needMoreContext); ASSERT(!needMoreContext); } if (it.atEnd() && next == string.size()) { pos = it.range()->startPosition(); } else if (next != prefixLength) { // Use the character iterator to translate the next value into a DOM position. CharacterIterator charIt(searchRange.get(), true); charIt.advance(next - prefixLength - 1); pos = charIt.range()->endPosition(); if (*charIt.characters() == '\n') { // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) VisiblePosition visPos = VisiblePosition(pos); if (visPos == VisiblePosition(charIt.range()->startPosition())) pos = visPos.next(true).deepEquivalent(); } } // generate VisiblePosition, use UPSTREAM affinity if possible return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); }