diff options
Diffstat (limited to 'WebCore/editing')
25 files changed, 592 insertions, 222 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp index c4aaac8..c43a574 100644 --- a/WebCore/editing/ApplyStyleCommand.cpp +++ b/WebCore/editing/ApplyStyleCommand.cpp @@ -1242,7 +1242,7 @@ bool ApplyStyleCommand::removeImplicitlyStyledElement(CSSMutableStyleDeclaration // Current implementation does not support stylePushedDown when mode == RemoveNone because of early exit. ASSERT(!extractedStyle || mode != RemoveNone); bool removed = false; - for (size_t i = 0; i < sizeof(HTMLEquivalents) / sizeof(HTMLEquivalent); i++) { + for (size_t i = 0; i < WTF_ARRAY_LENGTH(HTMLEquivalents); ++i) { const HTMLEquivalent& equivalent = HTMLEquivalents[i]; ASSERT(equivalent.element || equivalent.attribute); if ((extractedStyle && equivalent.pushDownType == ShouldNotBePushedDown) diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index 06cfd2b..602ca0f 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -776,8 +776,8 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Positi outerNode = outerNode->parentNode(); topNode = topNode->parentNode(); } - - for (Node* n = start.node()->traverseNextSibling(outerNode); n; n = n->nextSibling()) { + + for (Node* n = start.node()->traverseNextSibling(outerNode); n; n = n->traverseNextSibling(outerNode)) { if (n->parentNode() != start.node()->parentNode()) lastNode = topNode->lastChild(); diff --git a/WebCore/editing/CorrectionPanelInfo.h b/WebCore/editing/CorrectionPanelInfo.h index 2caac17..edc2bf7 100644 --- a/WebCore/editing/CorrectionPanelInfo.h +++ b/WebCore/editing/CorrectionPanelInfo.h @@ -44,7 +44,8 @@ namespace WebCore { struct CorrectionPanelInfo { enum PanelType { PanelTypeCorrection = 0, - PanelTypeReversion + PanelTypeReversion, + PanelTypeSpellingSuggestions }; RefPtr<Range> m_rangeToBeReplaced; diff --git a/WebCore/editing/DeleteSelectionCommand.cpp b/WebCore/editing/DeleteSelectionCommand.cpp index b617a27..594a94f 100644 --- a/WebCore/editing/DeleteSelectionCommand.cpp +++ b/WebCore/editing/DeleteSelectionCommand.cpp @@ -28,6 +28,7 @@ #include "Document.h" #include "DocumentFragment.h" +#include "EditingBoundary.h" #include "Editor.h" #include "EditorClient.h" #include "Element.h" @@ -739,8 +740,8 @@ void DeleteSelectionCommand::doApply() EAffinity affinity = m_selectionToDelete.affinity(); Position downstreamEnd = m_selectionToDelete.end().downstream(); - m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart(), Position::CanCrossEditingBoundary) - && isEndOfParagraph(m_selectionToDelete.visibleEnd(), Position::CanCrossEditingBoundary) + m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart(), CanCrossEditingBoundary) + && isEndOfParagraph(m_selectionToDelete.visibleEnd(), CanCrossEditingBoundary) && !lineBreakExistsAtVisiblePosition(m_selectionToDelete.visibleEnd()); if (m_needPlaceholder) { // Don't need a placeholder when deleting a selection that starts just before a table diff --git a/WebCore/editing/EditingBehavior.h b/WebCore/editing/EditingBehavior.h index 842d3f2..a367c52 100644 --- a/WebCore/editing/EditingBehavior.h +++ b/WebCore/editing/EditingBehavior.h @@ -57,6 +57,9 @@ public: // in place and moving the extent. Matches NSTextView. bool shouldAlwaysGrowSelectionWhenExtendingToBoundary() const { return m_type == EditingMacBehavior; } + // On Mac, when processing a contextual click, the object being clicked upon should be selected. + bool shouldSelectOnContextualMenuClick() const { return m_type == EditingMacBehavior; } + private: EditingBehaviorType m_type; }; diff --git a/WebCore/editing/EditingBoundary.h b/WebCore/editing/EditingBoundary.h new file mode 100644 index 0000000..1cb0849 --- /dev/null +++ b/WebCore/editing/EditingBoundary.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved. + * + * 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. + */ + +#ifndef EditingBoundary_h +#define EditingBoundary_h + +namespace WebCore { + +enum EditingBoundaryCrossingRule { + CanCrossEditingBoundary, + CannotCrossEditingBoundary +}; + +} + +#endif // EditingBoundary_h diff --git a/WebCore/editing/EditingStyle.cpp b/WebCore/editing/EditingStyle.cpp index 9da337f..ad77696 100644 --- a/WebCore/editing/EditingStyle.cpp +++ b/WebCore/editing/EditingStyle.cpp @@ -30,6 +30,7 @@ #include "ApplyStyleCommand.h" #include "CSSComputedStyleDeclaration.h" #include "CSSMutableStyleDeclaration.h" +#include "CSSValueKeywords.h" #include "Frame.h" #include "RenderStyle.h" #include "SelectionController.h" @@ -65,7 +66,7 @@ static const int editingStyleProperties[] = { CSSPropertyWebkitTextStrokeColor, CSSPropertyWebkitTextStrokeWidth, }; -size_t numEditingStyleProperties = sizeof(editingStyleProperties) / sizeof(editingStyleProperties[0]); +size_t numEditingStyleProperties = WTF_ARRAY_LENGTH(editingStyleProperties); static PassRefPtr<CSSMutableStyleDeclaration> copyEditingProperties(CSSStyleDeclaration* style) { @@ -141,6 +142,33 @@ bool EditingStyle::isEmpty() const return !m_mutableStyle || m_mutableStyle->isEmpty(); } +bool EditingStyle::textDirection(WritingDirection& writingDirection) const +{ + RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); + if (!unicodeBidi) + return false; + + ASSERT(unicodeBidi->isPrimitiveValue()); + int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); + if (unicodeBidiValue == CSSValueEmbed) { + RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection); + ASSERT(!direction || direction->isPrimitiveValue()); + if (!direction) + return false; + + writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; + + return true; + } + + if (unicodeBidiValue == CSSValueNormal) { + writingDirection = NaturalWritingDirection; + return true; + } + + return false; +} + void EditingStyle::setStyle(PassRefPtr<CSSMutableStyleDeclaration> style) { m_mutableStyle = style; @@ -175,7 +203,7 @@ void EditingStyle::removeStyleAddedByNode(Node* node) void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node) { - if (!node || !node->parentNode()) + if (!node || !node->parentNode() || !m_mutableStyle) return; RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode())); RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node)); @@ -192,12 +220,23 @@ void EditingStyle::removeNonEditingProperties() m_mutableStyle = copyEditingProperties(m_mutableStyle.get()); } -void EditingStyle::prepareToApplyAt(const Position& position) +void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection) { + if (!m_mutableStyle) + return; + // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style. // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate // which one of editingStyleAtPosition or computedStyle is called. RefPtr<EditingStyle> style = EditingStyle::create(position); + + RefPtr<CSSValue> unicodeBidi; + RefPtr<CSSValue> direction; + if (shouldPreserveWritingDirection == PreserveWritingDirection) { + unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); + direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection); + } + style->m_mutableStyle->diff(m_mutableStyle.get()); // if alpha value is zero, we don't add the background color. @@ -207,14 +246,23 @@ void EditingStyle::prepareToApplyAt(const Position& position) ExceptionCode ec; m_mutableStyle->removeProperty(CSSPropertyBackgroundColor, ec); } + + if (unicodeBidi) { + ASSERT(unicodeBidi->isPrimitiveValue()); + m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent()); + if (direction) { + ASSERT(direction->isPrimitiveValue()); + m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); + } + } } PassRefPtr<EditingStyle> editingStyleIncludingTypingStyle(const Position& position) { RefPtr<EditingStyle> editingStyle = EditingStyle::create(position); - RefPtr<CSSMutableStyleDeclaration> typingStyle = position.node()->document()->frame()->selection()->typingStyle(); - if (typingStyle) - editingStyle->style()->merge(copyEditingProperties(typingStyle.get()).get()); + RefPtr<EditingStyle> typingStyle = position.node()->document()->frame()->selection()->typingStyle(); + if (typingStyle && typingStyle->style()) + editingStyle->style()->merge(copyEditingProperties(typingStyle->style()).get()); return editingStyle; } diff --git a/WebCore/editing/EditingStyle.h b/WebCore/editing/EditingStyle.h index 6b4c60c..b1c370b 100644 --- a/WebCore/editing/EditingStyle.h +++ b/WebCore/editing/EditingStyle.h @@ -40,8 +40,12 @@ namespace WebCore { class CSSStyleDeclaration; class CSSComputedStyleDeclaration; +enum WritingDirection { NaturalWritingDirection, LeftToRightWritingDirection, RightToLeftWritingDirection }; + class EditingStyle : public RefCounted<EditingStyle> { public: + + enum ShouldPreserveWritingDirection { PreserveWritingDirection, DoNotPreserveWritingDirection }; static PassRefPtr<EditingStyle> create() { @@ -64,6 +68,7 @@ public: } CSSMutableStyleDeclaration* style() { return m_mutableStyle.get(); } + bool textDirection(WritingDirection&) const; bool isEmpty() const; void setStyle(PassRefPtr<CSSMutableStyleDeclaration>); void clear(); @@ -71,7 +76,7 @@ public: void removeStyleAddedByNode(Node* node); void removeStyleConflictingWithStyleOfNode(Node* node); void removeNonEditingProperties(); - void prepareToApplyAt(const Position&); + void prepareToApplyAt(const Position&, ShouldPreserveWritingDirection = DoNotPreserveWritingDirection); private: EditingStyle(); diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp index 530ed36..8c85363 100644 --- a/WebCore/editing/Editor.cpp +++ b/WebCore/editing/Editor.cpp @@ -84,6 +84,45 @@ namespace WebCore { using namespace std; using namespace HTMLNames; +static inline bool isAmbiguousBoundaryCharacter(UChar character) +{ + // These are characters that can behave as word boundaries, but can appear within words. + // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed. + // FIXME: this is required until 6853027 is fixed and text checking can do this for us. + return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; +} + +#if SUPPORT_AUTOCORRECTION_PANEL +static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection() +{ + DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ()); + if (markerTypesForAutoCorrection.isEmpty()) { + markerTypesForAutoCorrection.append(DocumentMarker::Replacement); + markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator); + } + return markerTypesForAutoCorrection; +} + +static FloatRect boundingBoxForRange(Range* range) +{ + Vector<FloatQuad> textQuads; + range->getBorderAndTextQuads(textQuads); + FloatRect totalBoundingBox; + size_t size = textQuads.size(); + for (size_t i = 0; i< size; ++i) + totalBoundingBox.unite(textQuads[i].boundingBox()); + return totalBoundingBox; +} +#endif // SUPPORT_AUTOCORRECTION_PANEL + +static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement() +{ + DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ()); + if (markerTypesForReplacement.isEmpty()) + markerTypesForReplacement.append(DocumentMarker::Replacement); + return markerTypesForReplacement; +} + // When an event handler has moved the selection outside of a text control // we should use the target control's selection for this editing operation. VisibleSelection Editor::selectionForCommand(Event* event) @@ -492,13 +531,18 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection) size_t markerCount = markers.size(); for (size_t i = 0; i < markerCount; ++i) { const DocumentMarker& marker = markers[i]; - if (marker.type == DocumentMarker::CorrectionIndicator && static_cast<int>(marker.endOffset) == endOffset) { + if (((marker.type == DocumentMarker::CorrectionIndicator && marker.description.length()) || marker.type == DocumentMarker::Spelling) && static_cast<int>(marker.endOffset) == endOffset) { RefPtr<Range> wordRange = Range::create(frame()->document(), node, marker.startOffset, node, marker.endOffset); String currentWord = plainText(wordRange.get()); - if (currentWord.length() > 0 && marker.description.length() > 0) { + if (currentWord.length()) { m_correctionPanelInfo.m_rangeToBeReplaced = wordRange; - m_correctionPanelInfo.m_replacementString = marker.description; - startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion); + m_correctionPanelInfo.m_replacedString = currentWord; + if (marker.type == DocumentMarker::Spelling) + startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeSpellingSuggestions); + else { + m_correctionPanelInfo.m_replacementString = marker.description; + startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion); + } } break; } @@ -608,24 +652,11 @@ WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbe } if (m_frame->selection()->isCaret()) { - RefPtr<CSSMutableStyleDeclaration> typingStyle = m_frame->selection()->typingStyle(); - if (typingStyle) { - RefPtr<CSSValue> unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (unicodeBidi) { - ASSERT(unicodeBidi->isPrimitiveValue()); - int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); - if (unicodeBidiValue == CSSValueEmbed) { - RefPtr<CSSValue> direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection); - ASSERT(!direction || direction->isPrimitiveValue()); - if (direction) { - hasNestedOrMultipleEmbeddings = false; - return static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; - } - } else if (unicodeBidiValue == CSSValueNormal) { - hasNestedOrMultipleEmbeddings = false; - return NaturalWritingDirection; - } - } + RefPtr<EditingStyle> typingStyle = m_frame->selection()->typingStyle(); + WritingDirection direction; + if (typingStyle && typingStyle->textDirection(direction)) { + hasNestedOrMultipleEmbeddings = false; + return direction; } node = m_frame->selection()->selection().visibleStart().deepEquivalent().node(); } @@ -921,7 +952,7 @@ static TriState triStateOfStyle(CSSStyleDeclaration* desiredStyle, CSSStyleDecla RefPtr<CSSMutableStyleDeclaration> diff = getPropertiesNotIn(desiredStyle, styleToCompare); if (ignoreTextOnlyProperties) - diff->removePropertiesInSet(textOnlyProperties, sizeof(textOnlyProperties) / sizeof(textOnlyProperties[0])); + diff->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties)); if (!diff->length()) return TrueTriState; @@ -1937,7 +1968,7 @@ Vector<String> Editor::guessesForMisspelledSelection() Vector<String> guesses; if (client()) - client()->getGuessesForWord(selectedString, guesses); + client()->getGuessesForWord(selectedString, String(), guesses); return guesses; } @@ -2010,15 +2041,25 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelecti markBadGrammar(movingSelection); } -void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p) +void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping) { #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) #if SUPPORT_AUTOCORRECTION_PANEL // Apply pending autocorrection before next round of spell checking. - applyCorrectionPanelInfo(true); + bool doApplyCorrection = true; + VisiblePosition startOfSelection = selectionAfterTyping.visibleStart(); + VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary)); + if (currentWord.visibleEnd() == startOfSelection) { + String wordText = plainText(currentWord.toNormalizedRange().get()); + if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1])) + doApplyCorrection = false; + } + if (doApplyCorrection) + applyCorrectionPanelInfo(markerTypesForAutocorrection()); m_correctionPanelInfo.m_rangeToBeReplaced.clear(); +#else + UNUSED_PARAM(selectionAfterTyping); #endif - TextCheckingOptions textCheckingOptions = 0; if (isContinuousSpellCheckingEnabled()) textCheckingOptions |= MarkSpelling; @@ -2036,20 +2077,21 @@ void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p) if (isGrammarCheckingEnabled()) textCheckingOptions |= MarkGrammar; - VisibleSelection adjacentWords = VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)); + VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)); if (textCheckingOptions & MarkGrammar) { - VisibleSelection selectedSentence = VisibleSelection(startOfSentence(p), endOfSentence(p)); + VisibleSelection selectedSentence = VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart)); markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), selectedSentence.toNormalizedRange().get()); } else { markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), adjacentWords.toNormalizedRange().get()); } #else + UNUSED_PARAM(selectionAfterTyping); if (!isContinuousSpellCheckingEnabled()) return; - + // Check spelling of one word RefPtr<Range> misspellingRange; - markMisspellings(VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)), misspellingRange); + markMisspellings(VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)), misspellingRange); // Autocorrect the misspelled word. if (!misspellingRange) @@ -2081,7 +2123,7 @@ void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p) return; // Check grammar of entire sentence - markBadGrammar(VisibleSelection(startOfSentence(p), endOfSentence(p))); + markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart))); #endif } @@ -2155,15 +2197,6 @@ void Editor::markBadGrammar(const VisibleSelection& selection) } #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) - -static inline bool isAmbiguousBoundaryCharacter(UChar character) -{ - // These are characters that can behave as word boundaries, but can appear within words. - // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed. - // FIXME: this is required until 6853027 is fixed and text checking can do this for us. - return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; -} - void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCheckingOptions, Range* spellingRange, Range* grammarRange) { bool shouldMarkSpelling = textCheckingOptions & MarkSpelling; @@ -2249,7 +2282,14 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh const TextCheckingResult* result = &results[i]; int resultLocation = result->location + offsetDueToReplacement; int resultLength = result->length; - if (shouldMarkSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingParagraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset) { + bool resultEndsAtAmbiguousBoundary = ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset; + + // Only mark misspelling if: + // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false. + // 2. Result falls within spellingRange. + // 3. The word in question doesn't end at an ambiguous boundary. For instance, we would not mark + // "wouldn'" as misspelled right after apostrophe is typed. + if (shouldMarkSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingParagraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) { ASSERT(resultLength > 0 && resultLocation >= 0); RefPtr<Range> misspellingRange = spellingParagraph.subrange(resultLocation, resultLength); misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); @@ -2276,14 +2316,14 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh continue; int replacementLength = result->replacement.length(); - bool doReplacement = (replacementLength > 0); + + // Apply replacement if: + // 1. The replacement length is non-zero. + // 2. The result doesn't end at an ambiguous boundary. + // (FIXME: this is required until 6853027 is fixed and text checking can do this for us + bool doReplacement = replacementLength > 0 && !resultEndsAtAmbiguousBoundary; RefPtr<Range> rangeToReplace = paragraph.subrange(resultLocation, resultLength); VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM); - - // avoid correcting text after an ambiguous boundary character has been typed - // FIXME: this is required until 6853027 is fixed and text checking can do this for us - if (ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset) - doReplacement = false; // adding links should be done only immediately after they are typed if (result->type == TextCheckingTypeLink && selectionOffset > resultLocation + resultLength + 1) @@ -2337,15 +2377,19 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh m_correctionPanelInfo.m_replacedString = replacedString; m_correctionPanelInfo.m_replacementString = result->replacement; m_correctionPanelInfo.m_isActive = true; - client()->showCorrectionPanel(m_correctionPanelInfo.m_panelType, totalBoundingBox, m_correctionPanelInfo.m_replacedString, result->replacement, this); + client()->showCorrectionPanel(m_correctionPanelInfo.m_panelType, totalBoundingBox, m_correctionPanelInfo.m_replacedString, result->replacement, Vector<String>(), this); doReplacement = false; } #endif if (doReplacement) { replaceSelectionWithText(result->replacement, false, false); offsetDueToReplacement += replacementLength - resultLength; - if (resultLocation < selectionOffset) + if (resultLocation < selectionOffset) { selectionOffset += replacementLength - resultLength; + if (ambiguousBoundaryOffset >= 0) + ambiguousBoundaryOffset = selectionOffset - 1; + } + if (result->type == TextCheckingTypeCorrection) { // Add a marker so that corrections can easily be undone and won't be re-corrected. RefPtr<Range> replacedRange = paragraph.subrange(resultLocation, replacementLength); @@ -2411,39 +2455,66 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec void Editor::correctionPanelTimerFired(Timer<Editor>*) { #if SUPPORT_AUTOCORRECTION_PANEL - if (m_correctionPanelInfo.m_panelType == CorrectionPanelInfo::PanelTypeCorrection) { + switch (m_correctionPanelInfo.m_panelType) { + case CorrectionPanelInfo::PanelTypeCorrection: { VisibleSelection selection(frame()->selection()->selection()); VisiblePosition start(selection.start(), selection.affinity()); VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary); VisibleSelection adjacentWords = VisibleSelection(p, start); markAllMisspellingsAndBadGrammarInRanges(MarkSpelling | ShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0); - } else { - String currentWord = plainText(m_correctionPanelInfo.m_rangeToBeReplaced.get()); - Vector<FloatQuad> textQuads; - m_correctionPanelInfo.m_rangeToBeReplaced->getBorderAndTextQuads(textQuads); - Vector<FloatQuad>::const_iterator end = textQuads.end(); - FloatRect totalBoundingBox; - for (Vector<FloatQuad>::const_iterator it = textQuads.begin(); it < end; ++it) - totalBoundingBox.unite(it->boundingBox()); + } + break; + case CorrectionPanelInfo::PanelTypeReversion: { + m_correctionPanelInfo.m_isActive = true; + m_correctionPanelInfo.m_replacedString = plainText(m_correctionPanelInfo.m_rangeToBeReplaced.get()); + client()->showCorrectionPanel(m_correctionPanelInfo.m_panelType, boundingBoxForRange(m_correctionPanelInfo.m_rangeToBeReplaced.get()), m_correctionPanelInfo.m_replacedString, m_correctionPanelInfo.m_replacementString, Vector<String>(), this); + } + break; + case CorrectionPanelInfo::PanelTypeSpellingSuggestions: { + if (plainText(m_correctionPanelInfo.m_rangeToBeReplaced.get()) != m_correctionPanelInfo.m_replacedString) + break; + String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.m_rangeToBeReplaced).paragraphRange().get()); + Vector<String> suggestions; + client()->getGuessesForWord(m_correctionPanelInfo.m_replacedString, paragraphText, suggestions); + if (suggestions.isEmpty()) + break; + String topSuggestion = suggestions.first(); + suggestions.remove(0); m_correctionPanelInfo.m_isActive = true; - m_correctionPanelInfo.m_replacedString = currentWord; - client()->showCorrectionPanel(m_correctionPanelInfo.m_panelType, totalBoundingBox, m_correctionPanelInfo.m_replacedString, m_correctionPanelInfo.m_replacementString, this); + client()->showCorrectionPanel(m_correctionPanelInfo.m_panelType, boundingBoxForRange(m_correctionPanelInfo.m_rangeToBeReplaced.get()), m_correctionPanelInfo.m_replacedString, topSuggestion, suggestions, this); + } + break; } #endif } -void Editor::handleRejectedCorrection() +void Editor::handleCorrectionPanelResult(const String& correction) { Range* replacedRange = m_correctionPanelInfo.m_rangeToBeReplaced.get(); if (!replacedRange || m_frame->document() != replacedRange->ownerDocument()) return; - if (m_correctionPanelInfo.m_panelType == CorrectionPanelInfo::PanelTypeCorrection) + String currentWord = plainText(m_correctionPanelInfo.m_rangeToBeReplaced.get()); + // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered. + if (currentWord != m_correctionPanelInfo.m_replacedString) + return; + + m_correctionPanelInfo.m_isActive = false; + + switch (m_correctionPanelInfo.m_panelType) { + case CorrectionPanelInfo::PanelTypeCorrection: replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.m_replacedString); - else { - m_correctionPanelInfo.m_isActive = false; - applyCorrectionPanelInfo(false); + break; + case CorrectionPanelInfo::PanelTypeReversion: + applyCorrectionPanelInfo(markerTypesForReplacement()); + case CorrectionPanelInfo::PanelTypeSpellingSuggestions: + if (correction.length()) { + m_correctionPanelInfo.m_replacementString = correction; + applyCorrectionPanelInfo(markerTypesForReplacement()); + } + break; } + m_correctionPanelInfo.m_rangeToBeReplaced.clear(); } @@ -2467,6 +2538,7 @@ void Editor::stopCorrectionPanelTimer() { #if SUPPORT_AUTOCORRECTION_PANEL m_correctionPanelTimer.stop(); + m_correctionPanelInfo.m_rangeToBeReplaced.clear(); #endif } @@ -2496,7 +2568,6 @@ void Editor::dismissCorrectionPanel(CorrectionWasRejectedOrNot correctionWasReje if (!m_correctionPanelInfo.m_isActive) return; m_correctionPanelInfo.m_isActive = false; - m_correctionPanelInfo.m_rangeToBeReplaced.clear(); if (client()) client()->dismissCorrectionPanel(correctionWasRejectedOrNot); #else @@ -2600,7 +2671,7 @@ void Editor::removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemove document->markers()->removeMarkers(pairIterator->first.get(), pairIterator->second); } -void Editor::applyCorrectionPanelInfo(bool addCorrectionIndicatorMarker) +void Editor::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd) { if (!m_correctionPanelInfo.m_rangeToBeReplaced) return; @@ -2641,9 +2712,10 @@ void Editor::applyCorrectionPanelInfo(bool addCorrectionIndicatorMarker) caretPosition.moveToOffset(caretPosition.offsetInContainerNode() + m_correctionPanelInfo.m_replacementString.length() - m_correctionPanelInfo.m_replacedString.length()); setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(caretPosition)); RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionPanelInfo.m_replacementString.length()); - replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::Replacement, m_correctionPanelInfo.m_replacementString); - if (addCorrectionIndicatorMarker) - replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::CorrectionIndicator, m_correctionPanelInfo.m_replacedString); + DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers(); + size_t size = markerTypesToAdd.size(); + for (size_t i = 0; i < size; ++i) + markers->addMarker(replacementRange.get(), markerTypesToAdd[i], m_correctionPanelInfo.m_replacementString); m_frame->selection()->moveTo(caretPosition, false); } } @@ -2826,39 +2898,39 @@ bool Editor::insideVisibleArea(Range* range) const return rectInFrameCoords.contains(resultRect); } -PassRefPtr<Range> Editor::firstVisibleRange(const String& target, bool caseFlag) +PassRefPtr<Range> Editor::firstVisibleRange(const String& target, FindOptions options) { RefPtr<Range> searchRange(rangeOfContents(m_frame->document())); - RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, true, caseFlag); + RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, options & ~Backwards); ExceptionCode ec = 0; while (!insideVisibleArea(resultRange.get())) { searchRange->setStartAfter(resultRange->endContainer(), ec); if (searchRange->startContainer() == searchRange->endContainer()) return Range::create(m_frame->document()); - resultRange = findPlainText(searchRange.get(), target, true, caseFlag); + resultRange = findPlainText(searchRange.get(), target, options & ~Backwards); } return resultRange; } -PassRefPtr<Range> Editor::lastVisibleRange(const String& target, bool caseFlag) +PassRefPtr<Range> Editor::lastVisibleRange(const String& target, FindOptions options) { RefPtr<Range> searchRange(rangeOfContents(m_frame->document())); - RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, false, caseFlag); + RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, options | Backwards); ExceptionCode ec = 0; while (!insideVisibleArea(resultRange.get())) { searchRange->setEndBefore(resultRange->startContainer(), ec); if (searchRange->startContainer() == searchRange->endContainer()) return Range::create(m_frame->document()); - resultRange = findPlainText(searchRange.get(), target, false, caseFlag); + resultRange = findPlainText(searchRange.get(), target, options | Backwards); } return resultRange; } -PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& target, bool forward, bool caseFlag, bool wrapFlag) +PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& target, FindOptions options) { if (m_frame->excludeFromTextSearch()) return Range::create(m_frame->document()); @@ -2866,8 +2938,8 @@ PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& ta RefPtr<Range> resultRange = currentRange; RefPtr<Range> searchRange(rangeOfContents(m_frame->document())); ExceptionCode ec = 0; - - for ( ; !insideVisibleArea(resultRange.get()); resultRange = findPlainText(searchRange.get(), target, forward, caseFlag)) { + bool forward = !(options & Backwards); + for ( ; !insideVisibleArea(resultRange.get()); resultRange = findPlainText(searchRange.get(), target, options)) { if (resultRange->collapsed(ec)) { if (!resultRange->startContainer()->isInShadowTree()) break; @@ -2899,13 +2971,13 @@ PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& ta if (insideVisibleArea(resultRange.get())) return resultRange; - if (!wrapFlag) + if (!(options & WrapAround)) return Range::create(m_frame->document()); - if (forward) - return firstVisibleRange(target, caseFlag); + if (options & Backwards) + return lastVisibleRange(target, options); - return lastVisibleRange(target, caseFlag); + return firstVisibleRange(target, options); } void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle) @@ -3006,9 +3078,10 @@ void Editor::computeAndSetTypingStyle(CSSStyleDeclaration* style, EditAction edi // Calculate the current typing style. RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable(); - if (m_frame->selection()->typingStyle()) { - m_frame->selection()->typingStyle()->merge(mutableStyle.get()); - mutableStyle = m_frame->selection()->typingStyle(); + RefPtr<EditingStyle> typingStyle = m_frame->selection()->typingStyle(); + if (typingStyle && typingStyle->style()) { + typingStyle->style()->merge(mutableStyle.get()); + mutableStyle = typingStyle->style(); } RefPtr<CSSValue> unicodeBidi; @@ -3068,7 +3141,7 @@ PassRefPtr<CSSMutableStyleDeclaration> Editor::selectionComputedStyle(bool& shou if (!m_frame->selection()->typingStyle()) return mutableStyle; - RefPtr<EditingStyle> typingStyle = EditingStyle::create(m_frame->selection()->typingStyle()); + RefPtr<EditingStyle> typingStyle = m_frame->selection()->typingStyle(); typingStyle->removeNonEditingProperties(); typingStyle->prepareToApplyAt(position); mutableStyle->merge(typingStyle->style()); @@ -3152,13 +3225,14 @@ RenderStyle* Editor::styleForSelectionStart(Node *&nodeToRemove) const if (!position.node()) return 0; - if (!m_frame->selection()->typingStyle()) + RefPtr<EditingStyle> typingStyle = m_frame->selection()->typingStyle(); + if (!typingStyle || !typingStyle->style()) return position.node()->renderer()->style(); RefPtr<Element> styleElement = m_frame->document()->createElement(spanTag, false); ExceptionCode ec = 0; - String styleText = m_frame->selection()->typingStyle()->cssText() + " display: inline"; + String styleText = typingStyle->style()->cssText() + " display: inline"; styleElement->setAttribute(styleAttr, styleText.impl(), ec); ASSERT(!ec); @@ -3175,6 +3249,12 @@ RenderStyle* Editor::styleForSelectionStart(Node *&nodeToRemove) const // Searches from the beginning of the document if nothing is selected. bool Editor::findString(const String& target, bool forward, bool caseFlag, bool wrapFlag, bool startInSelection) { + FindOptions options = (forward ? 0 : Backwards) | (caseFlag ? 0 : CaseInsensitive) | (wrapFlag ? WrapAround : 0) | (startInSelection ? StartInSelection : 0); + return findString(target, options); +} + +bool Editor::findString(const String& target, FindOptions options) +{ if (target.isEmpty()) return false; @@ -3186,6 +3266,8 @@ bool Editor::findString(const String& target, bool forward, bool caseFlag, bool RefPtr<Range> searchRange(rangeOfContents(m_frame->document())); VisibleSelection selection = m_frame->selection()->selection(); + bool forward = !(options & Backwards); + bool startInSelection = options & StartInSelection; if (forward) setStart(searchRange.get(), startInSelection ? selection.visibleStart() : selection.visibleEnd()); else @@ -3200,7 +3282,7 @@ bool Editor::findString(const String& target, bool forward, bool caseFlag, bool searchRange->setStart(shadowTreeRoot.get(), 0, ec); } - RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, forward, caseFlag)); + RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, options)); // If we started in the selection and the found range exactly matches the existing selection, find again. // Build a selection with the found range to remove collapsed whitespace. // Compare ranges instead of selection objects to ignore the way that the current selection was made. @@ -3219,7 +3301,7 @@ bool Editor::findString(const String& target, bool forward, bool caseFlag, bool searchRange->setStart(shadowTreeRoot.get(), 0, ec); } - resultRange = findPlainText(searchRange.get(), target, forward, caseFlag); + resultRange = findPlainText(searchRange.get(), target, options); } ExceptionCode exception = 0; @@ -3232,20 +3314,20 @@ bool Editor::findString(const String& target, bool forward, bool caseFlag, bool else searchRange->setEndBefore(shadowTreeRoot->shadowParentNode(), exception); - resultRange = findPlainText(searchRange.get(), target, forward, caseFlag); + resultRange = findPlainText(searchRange.get(), target, options); } if (!insideVisibleArea(resultRange.get())) { - resultRange = nextVisibleRange(resultRange.get(), target, forward, caseFlag, wrapFlag); + resultRange = nextVisibleRange(resultRange.get(), target, options); if (!resultRange) return false; } // If we didn't find anything and we're wrapping, search again in the entire document (this will // redundantly re-search the area already searched in some cases). - if (resultRange->collapsed(exception) && wrapFlag) { + if (resultRange->collapsed(exception) && options & WrapAround) { searchRange = rangeOfContents(m_frame->document()); - resultRange = findPlainText(searchRange.get(), target, forward, caseFlag); + resultRange = findPlainText(searchRange.get(), target, options); // We used to return false here if we ended up with the same range that we started with // (e.g., the selection was already the only instance of this text). But we decided that // this should be a success case instead, so we'll just fall through in that case. @@ -3259,7 +3341,7 @@ bool Editor::findString(const String& target, bool forward, bool caseFlag, bool return true; } -unsigned Editor::countMatchesForText(const String& target, bool caseFlag, unsigned limit, bool markMatches) +unsigned Editor::countMatchesForText(const String& target, FindOptions options, unsigned limit, bool markMatches) { if (target.isEmpty()) return 0; @@ -3269,7 +3351,7 @@ unsigned Editor::countMatchesForText(const String& target, bool caseFlag, unsign ExceptionCode exception = 0; unsigned matchCount = 0; do { - RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, caseFlag)); + RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, options & ~Backwards)); if (resultRange->collapsed(exception)) { if (!resultRange->startContainer()->isInShadowTree()) break; diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h index 24cdaf1..abba4f4 100644 --- a/WebCore/editing/Editor.h +++ b/WebCore/editing/Editor.h @@ -33,6 +33,7 @@ #include "EditingBehavior.h" #include "EditorDeleteAction.h" #include "EditorInsertAction.h" +#include "FindOptions.h" #include "SelectionController.h" #if PLATFORM(MAC) && !defined(__OBJC__) @@ -70,7 +71,6 @@ struct CompositionUnderline { enum TriState { FalseTriState, TrueTriState, MixedTriState }; enum EditorCommandSource { CommandFromMenuOrKeyBinding, CommandFromDOM, CommandFromDOMWithUserInterface }; -enum WritingDirection { NaturalWritingDirection, LeftToRightWritingDirection, RightToLeftWritingDirection }; class Editor { public: @@ -210,7 +210,7 @@ public: Vector<String> guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical); bool isSpellCheckingEnabledInFocusedNode() const; bool isSpellCheckingEnabledFor(Node*) const; - void markMisspellingsAfterTypingToPosition(const VisiblePosition&); + void markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping); void markMisspellings(const VisibleSelection&, RefPtr<Range>& firstMisspellingRange); void markBadGrammar(const VisibleSelection&); void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection); @@ -308,13 +308,13 @@ public: // We should make these functions private when their callers in Frame are moved over here to Editor bool insideVisibleArea(const IntPoint&) const; bool insideVisibleArea(Range*) const; - PassRefPtr<Range> nextVisibleRange(Range*, const String&, bool forward, bool caseFlag, bool wrapFlag); void addToKillRing(Range*, bool prepend); void handleCancelOperation(); void startCorrectionPanelTimer(CorrectionPanelInfo::PanelType); - void handleRejectedCorrection(); + // If user confirmed a correction in the correction panel, correction has non-zero length, otherwise it means that user has dismissed the panel. + void handleCorrectionPanelResult(const String& correction); bool isShowingCorrectionPanel(); void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle); @@ -329,6 +329,8 @@ public: Node* findEventTargetFrom(const VisibleSelection& selection) const; String selectedText() const; + bool findString(const String&, FindOptions); + // FIXME: Switch callers over to the FindOptions version and retire this one. bool findString(const String&, bool forward, bool caseFlag, bool wrapFlag, bool startInSelection); const VisibleSelection& mark() const; // Mark, to be used as emacs uses it. @@ -345,7 +347,7 @@ public: RenderStyle* styleForSelectionStart(Node*& nodeToRemove) const; - unsigned countMatchesForText(const String&, bool caseFlag, unsigned limit, bool markMatches); + unsigned countMatchesForText(const String&, FindOptions, unsigned limit, bool markMatches); bool markedTextMatchesAreHighlighted() const; void setMarkedTextMatchesAreHighlighted(bool); @@ -399,15 +401,16 @@ private: void confirmComposition(const String&, bool preserveSelection); void setIgnoreCompositionSelectionChange(bool ignore); - PassRefPtr<Range> firstVisibleRange(const String&, bool caseFlag); - PassRefPtr<Range> lastVisibleRange(const String&, bool caseFlag); + PassRefPtr<Range> firstVisibleRange(const String&, FindOptions); + PassRefPtr<Range> lastVisibleRange(const String&, FindOptions); + PassRefPtr<Range> nextVisibleRange(Range*, const String&, FindOptions); void changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle); void correctionPanelTimerFired(Timer<Editor>*); Node* findEventTargetFromSelection() const; void stopCorrectionPanelTimer(); void dismissCorrectionPanel(CorrectionWasRejectedOrNot); - void applyCorrectionPanelInfo(bool addCorrectionIndicatorMarker); + void applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd); }; inline void Editor::setStartNewKillRingSequence(bool flag) diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp index e3ea37e..ab83817 100644 --- a/WebCore/editing/EditorCommand.cpp +++ b/WebCore/editing/EditorCommand.cpp @@ -1556,8 +1556,7 @@ static const CommandMap& createCommandMap() CommandMap& commandMap = *new CommandMap; - const unsigned numCommands = sizeof(commands) / sizeof(commands[0]); - for (unsigned i = 0; i < numCommands; i++) { + for (size_t i = 0; i < WTF_ARRAY_LENGTH(commands); ++i) { ASSERT(!commandMap.get(commands[i].name)); commandMap.set(commands[i].name, &commands[i].command); } diff --git a/WebCore/editing/FindOptions.h b/WebCore/editing/FindOptions.h new file mode 100644 index 0000000..ae4aecf --- /dev/null +++ b/WebCore/editing/FindOptions.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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. + */ + +#ifndef FindOptions_h +#define FindOptions_h + +namespace WebCore { + +enum FindOptionFlag { + CaseInsensitive = 1 << 0, + AtWordStarts = 1 << 1, + // When combined with AtWordStarts, accepts a match in the middle of a word if the match begins with + // an uppercase letter followed by a lowercase or non-letter. Accepts several other intra-word matches. + TreatMedialCapitalAsWordStart = 1 << 2, + Backwards = 1 << 3, + WrapAround = 1 << 4, + StartInSelection = 1 << 5 +}; + +typedef unsigned FindOptions; + +} // namespace WebCore + +#endif // FindOptions_h diff --git a/WebCore/editing/InsertLineBreakCommand.cpp b/WebCore/editing/InsertLineBreakCommand.cpp index 5588326..9397000 100644 --- a/WebCore/editing/InsertLineBreakCommand.cpp +++ b/WebCore/editing/InsertLineBreakCommand.cpp @@ -164,15 +164,15 @@ void InsertLineBreakCommand::doApply() } // Handle the case where there is a typing style. - - RefPtr<CSSMutableStyleDeclaration> typingStyle = document()->frame()->selection()->typingStyle(); - - if (typingStyle && typingStyle->length() > 0) { + + RefPtr<EditingStyle> typingStyle = document()->frame()->selection()->typingStyle(); + + if (typingStyle && !typingStyle->isEmpty()) { // Apply the typing style to the inserted line break, so that if the selection // leaves and then comes back, new input will have the right style. // FIXME: We shouldn't always apply the typing style to the line break here, // see <rdar://problem/5794462>. - applyStyle(typingStyle.get(), firstDeepEditingPositionForNode(nodeToInsert.get()), lastDeepEditingPositionForNode(nodeToInsert.get())); + applyStyle(typingStyle->style(), firstDeepEditingPositionForNode(nodeToInsert.get()), lastDeepEditingPositionForNode(nodeToInsert.get())); // Even though this applyStyle operates on a Range, it still sets an endingSelection(). // It tries to set a VisibleSelection around the content it operated on. So, that VisibleSelection // will either (a) select the line break we inserted, or it will (b) be a caret just diff --git a/WebCore/editing/InsertListCommand.cpp b/WebCore/editing/InsertListCommand.cpp index f90d5d3..bb3cd93 100644 --- a/WebCore/editing/InsertListCommand.cpp +++ b/WebCore/editing/InsertListCommand.cpp @@ -156,6 +156,11 @@ void InsertListCommand::doApply() doApplyForSingleParagraph(forceCreateList, listTag, currentSelection.get()); if (endOfSelection.isNull() || endOfSelection.isOrphan() || startOfLastParagraph.isNull() || startOfLastParagraph.isOrphan()) { RefPtr<Range> lastSelectionRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), indexForEndOfSelection, 0, true); + // If lastSelectionRange is null, then some contents have been deleted from the document. + // This should never happen and if it did, exit early immediately because we've lost the loop invariant. + ASSERT(lastSelectionRange); + if (!lastSelectionRange) + return; endOfSelection = lastSelectionRange->startPosition(); startOfLastParagraph = startOfParagraph(endOfSelection); } diff --git a/WebCore/editing/InsertTextCommand.cpp b/WebCore/editing/InsertTextCommand.cpp index 87a695d..9eb8aa7 100644 --- a/WebCore/editing/InsertTextCommand.cpp +++ b/WebCore/editing/InsertTextCommand.cpp @@ -190,26 +190,11 @@ void InsertTextCommand::input(const String& text, bool selectInsertedText) setEndingSelection(forcedEndingSelection); // Handle the case where there is a typing style. - RefPtr<CSSMutableStyleDeclaration> typingStyle = document()->frame()->selection()->typingStyle(); - RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle(); - RefPtr<CSSValue> unicodeBidi; - RefPtr<CSSValue> direction; - if (typingStyle) { - unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); - direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection); + if (RefPtr<EditingStyle> typingStyle = document()->frame()->selection()->typingStyle()) { + typingStyle->prepareToApplyAt(endPosition, EditingStyle::PreserveWritingDirection); + if (!typingStyle->isEmpty()) + applyStyle(typingStyle->style()); } - endingStyle->diff(typingStyle.get()); - if (typingStyle && unicodeBidi) { - ASSERT(unicodeBidi->isPrimitiveValue()); - typingStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent()); - if (direction) { - ASSERT(direction->isPrimitiveValue()); - typingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); - } - } - - if (typingStyle && typingStyle->length()) - applyStyle(typingStyle.get()); if (!selectInsertedText) setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity())); diff --git a/WebCore/editing/MarkupAccumulator.cpp b/WebCore/editing/MarkupAccumulator.cpp index a701189..f6dbd8b 100644 --- a/WebCore/editing/MarkupAccumulator.cpp +++ b/WebCore/editing/MarkupAccumulator.cpp @@ -60,8 +60,8 @@ void appendCharactersReplacingEntities(Vector<UChar>& out, const UChar* content, }; size_t positionAfterLastEntity = 0; - for (size_t i = 0; i < length; i++) { - for (size_t m = 0; m < sizeof(entityMaps) / sizeof(EntityDescription); m++) { + for (size_t i = 0; i < length; ++i) { + for (size_t m = 0; m < WTF_ARRAY_LENGTH(entityMaps); ++m) { if (content[i] == entityMaps[m].entity && entityMaps[m].mask & entityMask) { out.append(content + positionAfterLastEntity, i - positionAfterLastEntity); append(out, entityMaps[m].reference); diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp index 1164c45..bc6ef24 100644 --- a/WebCore/editing/SelectionController.cpp +++ b/WebCore/editing/SelectionController.cpp @@ -1555,6 +1555,7 @@ void SelectionController::setFocusedNodeIfNeeded() // Walk up the render tree to search for a node to focus. // Walking up the DOM tree wouldn't work for shadow trees, like those behind the engine-based text fields. + // FIXME: Combine with the same traversal code in EventHandle::dispatchMouseEvent. while (renderer) { // We don't want to set focus on a subframe when selecting in a parent frame, // so add the !isFrameElement check here. There's probably a better way to make this diff --git a/WebCore/editing/SelectionController.h b/WebCore/editing/SelectionController.h index 2edad0a..8cc89e4 100644 --- a/WebCore/editing/SelectionController.h +++ b/WebCore/editing/SelectionController.h @@ -160,7 +160,8 @@ public: void paintDragCaret(GraphicsContext*, int tx, int ty, const IntRect& clipRect) const; - CSSMutableStyleDeclaration* typingStyle() const; + EditingStyle* typingStyle() const; + PassRefPtr<CSSMutableStyleDeclaration> copyTypingStyle() const; void setTypingStyle(PassRefPtr<EditingStyle>); void clearTypingStyle(); @@ -233,9 +234,16 @@ private: bool m_caretPaint; }; -inline CSSMutableStyleDeclaration* SelectionController::typingStyle() const +inline EditingStyle* SelectionController::typingStyle() const { - return m_typingStyle ? m_typingStyle->style() : 0; + return m_typingStyle.get(); +} + +inline PassRefPtr<CSSMutableStyleDeclaration> SelectionController::copyTypingStyle() const +{ + if (!m_typingStyle || !m_typingStyle->style()) + return 0; + return m_typingStyle->style()->copy(); } inline void SelectionController::clearTypingStyle() diff --git a/WebCore/editing/TextCheckingHelper.cpp b/WebCore/editing/TextCheckingHelper.cpp index d524cf3..2241b85 100644 --- a/WebCore/editing/TextCheckingHelper.cpp +++ b/WebCore/editing/TextCheckingHelper.cpp @@ -541,7 +541,7 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) { String misspelledWord = paragraph.checkingSubstring(); ASSERT(misspelledWord.length()); - m_client->getGuessesForWord(misspelledWord, guesses); + m_client->getGuessesForWord(misspelledWord, String(), guesses); m_client->updateSpellingUIWithMisspelledWord(misspelledWord); misspelled = true; return guesses; diff --git a/WebCore/editing/TextCheckingHelper.h b/WebCore/editing/TextCheckingHelper.h index 4dced05..227530f 100644 --- a/WebCore/editing/TextCheckingHelper.h +++ b/WebCore/editing/TextCheckingHelper.h @@ -55,11 +55,11 @@ public: bool checkingRangeMatches(int location, int length) const { return location == checkingStart() && length == checkingLength(); } bool isCheckingRangeCoveredBy(int location, int length) const { return location <= checkingStart() && location + length >= checkingStart() + checkingLength(); } bool checkingRangeCovers(int location, int length) const { return location < checkingEnd() && location + length > checkingStart(); } + PassRefPtr<Range> paragraphRange() const; private: void invalidateParagraphRangeValues(); PassRefPtr<Range> checkingRange() const { return m_checkingRange; } - PassRefPtr<Range> paragraphRange() const; PassRefPtr<Range> offsetAsRange() const; const String& text() const; diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp index a96268d..a3edd38 100644 --- a/WebCore/editing/TextIterator.cpp +++ b/WebCore/editing/TextIterator.cpp @@ -38,6 +38,8 @@ #include "RenderTableRow.h" #include "RenderTextControl.h" #include "RenderTextFragment.h" +#include "TextBoundaries.h" +#include "TextBreakIterator.h" #include "VisiblePosition.h" #include "visible_units.h" @@ -56,14 +58,18 @@ using namespace HTMLNames; // Buffer that knows how to compare with a search target. // Keeps enough of the previous text to be able to search in the future, but no more. // Non-breaking spaces are always equal to normal spaces. -// Case folding is also done if <isCaseSensitive> is false. +// Case folding is also done if the CaseInsensitive option is specified. +// Matches are further filtered if the AtWordStarts option is specified, although some +// matches inside a word are permitted if TreatMedialCapitalAsWordStart is specified as well. class SearchBuffer : public Noncopyable { public: - SearchBuffer(const String& target, bool isCaseSensitive); + SearchBuffer(const String& target, FindOptions); ~SearchBuffer(); // Returns number of characters appended; guaranteed to be in the range [1, length]. size_t append(const UChar*, size_t length); + bool needsMoreContext() const; + void prependContext(const UChar*, size_t length); void reachedBreak(); // Result is the size in characters of what was found. @@ -75,11 +81,16 @@ public: private: bool isBadMatch(const UChar*, size_t length) const; + bool isWordStartMatch(size_t start, size_t length) const; String m_target; + FindOptions m_options; + Vector<UChar> m_buffer; size_t m_overlap; + size_t m_prefixLength; bool m_atBreak; + bool m_needsMoreContext; bool m_targetRequiresKanaWorkaround; Vector<UChar> m_normalizedTarget; @@ -92,7 +103,7 @@ private: size_t length() const; String m_target; - bool m_isCaseSensitive; + FindOptions m_options; Vector<UChar> m_buffer; Vector<bool> m_isCharacterStartBuffer; @@ -473,7 +484,7 @@ bool TextIterator::handleTextNode() handleTextNodeFirstLetter(static_cast<RenderTextFragment*>(renderer)); if (m_firstLetterText) { String firstLetter = m_firstLetterText->text(); - emitText(m_node, m_firstLetterText, m_offset, firstLetter.length()); + emitText(m_node, m_firstLetterText, m_offset, m_offset + firstLetter.length()); m_firstLetterText = 0; m_textBox = 0; return false; @@ -1828,9 +1839,46 @@ static void normalizeCharacters(const UChar* characters, unsigned length, Vector ASSERT(status == U_STRING_NOT_TERMINATED_WARNING); } -inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive) +static bool isNonLatin1Separator(UChar32 character) +{ + ASSERT_ARG(character, character >= 256); + + return U_GET_GC_MASK(character) & (U_GC_S_MASK | U_GC_P_MASK | U_GC_Z_MASK | U_GC_CF_MASK); +} + +static inline bool isSeparator(UChar32 character) +{ + static const bool latin1SeparatorTable[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // space ! " # $ % & ' ( ) * + , - . / + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, // : ; < = > ? + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // @ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // [ \ ] ^ _ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ` + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // { | } ~ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (character < 256) + return latin1SeparatorTable[character]; + + return isNonLatin1Separator(character); +} + +inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) : m_target(target) + , m_options(options) + , m_prefixLength(0) , m_atBreak(true) + , m_needsMoreContext(options & AtWordStarts) , m_targetRequiresKanaWorkaround(containsKanaLetters(m_target)) { ASSERT(!m_target.isEmpty()); @@ -1844,6 +1892,17 @@ inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive) m_buffer.reserveInitialCapacity(max(targetLength * 8, minimumSearchBufferSize)); m_overlap = m_buffer.capacity() / 4; + if ((m_options & AtWordStarts) && targetLength) { + UChar32 targetFirstCharacter; + U16_GET(m_target.characters(), 0, 0, targetLength, targetFirstCharacter); + // Characters in the separator category never really occur at the beginning of a word, + // so if the target begins with such a character, we just ignore the AtWordStart option. + if (isSeparator(targetFirstCharacter)) { + m_options &= ~AtWordStarts; + m_needsMoreContext = false; + } + } + // Grab the single global searcher. // If we ever have a reason to do more than once search buffer at once, we'll have // to move to multiple searchers. @@ -1852,7 +1911,7 @@ inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive) UStringSearch* searcher = WebCore::searcher(); UCollator* collator = usearch_getCollator(searcher); - UCollationStrength strength = isCaseSensitive ? UCOL_TERTIARY : UCOL_PRIMARY; + UCollationStrength strength = m_options & CaseInsensitive ? UCOL_PRIMARY : UCOL_TERTIARY; if (ucol_getStrength(collator) != strength) { ucol_setStrength(collator, strength); usearch_reset(searcher); @@ -1878,9 +1937,11 @@ inline size_t SearchBuffer::append(const UChar* characters, size_t length) if (m_atBreak) { m_buffer.shrink(0); + m_prefixLength = 0; m_atBreak = false; } else if (m_buffer.size() == m_buffer.capacity()) { memcpy(m_buffer.data(), m_buffer.data() + m_buffer.size() - m_overlap, m_overlap * sizeof(UChar)); + m_prefixLength -= min(m_prefixLength, m_buffer.size() - m_overlap); m_buffer.shrink(m_overlap); } @@ -1892,6 +1953,35 @@ inline size_t SearchBuffer::append(const UChar* characters, size_t length) return usableLength; } +inline bool SearchBuffer::needsMoreContext() const +{ + return m_needsMoreContext; +} + +inline void SearchBuffer::prependContext(const UChar* characters, size_t length) +{ + ASSERT(m_needsMoreContext); + ASSERT(m_prefixLength == m_buffer.size()); + + if (!length) + return; + + m_atBreak = false; + + size_t wordBoundaryContextStart = length; + if (wordBoundaryContextStart) { + U16_BACK_1(characters, 0, wordBoundaryContextStart); + wordBoundaryContextStart = startOfLastWordBoundaryContext(characters, wordBoundaryContextStart); + } + + size_t usableLength = min(m_buffer.capacity() - m_prefixLength, length - wordBoundaryContextStart); + m_buffer.prepend(characters + length - usableLength, usableLength); + m_prefixLength += usableLength; + + if (wordBoundaryContextStart || m_prefixLength == m_buffer.capacity()) + m_needsMoreContext = false; +} + inline bool SearchBuffer::atBreak() const { return m_atBreak; @@ -1962,6 +2052,55 @@ inline bool SearchBuffer::isBadMatch(const UChar* match, size_t matchLength) con } } +inline bool SearchBuffer::isWordStartMatch(size_t start, size_t length) const +{ + ASSERT(m_options & AtWordStarts); + + if (!start) + return true; + + if (m_options & TreatMedialCapitalAsWordStart) { + int size = m_buffer.size(); + int offset = start; + UChar32 firstCharacter; + U16_GET(m_buffer.data(), 0, offset, size, firstCharacter); + UChar32 previousCharacter; + U16_PREV(m_buffer.data(), 0, offset, previousCharacter); + + if (isSeparator(firstCharacter)) { + // The start of a separator run is a word start (".org" in "webkit.org"). + if (!isSeparator(previousCharacter)) + return true; + } else if (isASCIIUpper(firstCharacter)) { + // The start of an uppercase run is a word start ("Kit" in "WebKit"). + if (!isASCIIUpper(previousCharacter)) + return true; + // The last character of an uppercase run followed by a non-separator, non-digit + // is a word start ("Request" in "XMLHTTPRequest"). + offset = start; + U16_FWD_1(m_buffer.data(), offset, size); + UChar32 nextCharacter = 0; + if (offset < size) + U16_GET(m_buffer.data(), 0, offset, size, nextCharacter); + if (!isASCIIUpper(nextCharacter) && !isASCIIDigit(nextCharacter) && !isSeparator(nextCharacter)) + return true; + } else if (isASCIIDigit(firstCharacter)) { + // The start of a digit run is a word start ("2" in "WebKit2"). + if (!isASCIIDigit(previousCharacter)) + return true; + } else if (isSeparator(previousCharacter) || isASCIIDigit(previousCharacter)) { + // The start of a non-separator, non-uppercase, non-digit run is a word start, + // except after an uppercase. ("org" in "webkit.org", but not "ore" in "WebCore"). + return true; + } + } + + size_t wordBreakSearchStart = start + length; + while (wordBreakSearchStart > start) + wordBreakSearchStart = findNextWordFromIndex(m_buffer.data(), m_buffer.size(), wordBreakSearchStart, false /* backwards */); + return wordBreakSearchStart == start; +} + inline size_t SearchBuffer::search(size_t& start) { size_t size = m_buffer.size(); @@ -1979,7 +2118,10 @@ inline size_t SearchBuffer::search(size_t& start) usearch_setText(searcher, m_buffer.data(), size, &status); ASSERT(status == U_ZERO_ERROR); - int matchStart = usearch_first(searcher, &status); + usearch_setOffset(searcher, m_prefixLength, &status); + ASSERT(status == U_ZERO_ERROR); + + int matchStart = usearch_next(searcher, &status); ASSERT(status == U_ZERO_ERROR); nextMatch: @@ -1992,8 +2134,18 @@ nextMatch: // The same match may appear later, matching more characters, // possibly including a combining character that's not yet in the buffer. if (!m_atBreak && static_cast<size_t>(matchStart) >= size - m_overlap) { - memcpy(m_buffer.data(), m_buffer.data() + size - m_overlap, m_overlap * sizeof(UChar)); - m_buffer.shrink(m_overlap); + size_t overlap = m_overlap; + if (m_options & AtWordStarts) { + // Ensure that there is sufficient context before matchStart the next time around for + // determining if it is at a word boundary. + int wordBoundaryContextStart = matchStart; + U16_BACK_1(m_buffer.data(), 0, wordBoundaryContextStart); + wordBoundaryContextStart = startOfLastWordBoundaryContext(m_buffer.data(), wordBoundaryContextStart); + overlap = min(size - 1, max(overlap, size - wordBoundaryContextStart)); + } + memcpy(m_buffer.data(), m_buffer.data() + size - overlap, overlap * sizeof(UChar)); + m_prefixLength -= min(m_prefixLength, size - overlap); + m_buffer.shrink(overlap); return 0; } @@ -2001,7 +2153,7 @@ nextMatch: ASSERT(matchStart + matchedLength <= size); // If this match is "bad", move on to the next match. - if (isBadMatch(m_buffer.data() + matchStart, matchedLength)) { + if (isBadMatch(m_buffer.data() + matchStart, matchedLength) || ((m_options & AtWordStarts) && !isWordStartMatch(matchStart, matchedLength))) { matchStart = usearch_next(searcher, &status); ASSERT(status == U_ZERO_ERROR); goto nextMatch; @@ -2009,6 +2161,7 @@ nextMatch: size_t newSize = size - (matchStart + 1); memmove(m_buffer.data(), m_buffer.data() + matchStart + 1, newSize * sizeof(UChar)); + m_prefixLength -= min<size_t>(m_prefixLength, matchStart + 1); m_buffer.shrink(newSize); start = size - matchStart; @@ -2017,9 +2170,9 @@ nextMatch: #else // !ICU_UNICODE -inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive) - : m_target(isCaseSensitive ? target : target.foldCase()) - , m_isCaseSensitive(isCaseSensitive) +inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) + : m_target(options & CaseInsensitive ? target.foldCase() : target) + , m_options(options) , m_buffer(m_target.length()) , m_isCharacterStartBuffer(m_target.length()) , m_isBufferFull(false) @@ -2058,7 +2211,7 @@ inline void SearchBuffer::append(UChar c, bool isStart) inline size_t SearchBuffer::append(const UChar* characters, size_t length) { ASSERT(length); - if (m_isCaseSensitive) { + if (!(m_options & CaseInsensitive)) { append(characters[0], true); return 1; } @@ -2078,6 +2231,16 @@ inline size_t SearchBuffer::append(const UChar* characters, size_t length) return 1; } +inline bool SearchBuffer::needsMoreContext() const +{ + return false; +} + +void SearchBuffer::prependContext(const UChar*, size_t) +{ + ASSERT_NOT_REACHED(); +} + inline size_t SearchBuffer::search(size_t& start) { if (!m_isBufferFull) @@ -2332,12 +2495,24 @@ static PassRefPtr<Range> collapsedToBoundary(const Range* range, bool forward) return result.release(); } -static size_t findPlainText(CharacterIterator& it, const String& target, bool forward, bool caseSensitive, size_t& matchStart) +static size_t findPlainText(CharacterIterator& it, const String& target, FindOptions options, size_t& matchStart) { matchStart = 0; size_t matchLength = 0; - SearchBuffer buffer(target, caseSensitive); + SearchBuffer buffer(target, options); + + if (buffer.needsMoreContext()) { + RefPtr<Range> startRange = it.range(); + RefPtr<Range> beforeStartRange = startRange->ownerDocument()->createRange(); + ExceptionCode ec = 0; + beforeStartRange->setEnd(startRange->startContainer(), startRange->startOffset(), ec); + for (SimplifiedBackwardsTextIterator backwardsIterator(beforeStartRange.get()); !backwardsIterator.atEnd(); backwardsIterator.advance()) { + buffer.prependContext(backwardsIterator.characters(), backwardsIterator.length()); + if (!buffer.needsMoreContext()) + break; + } + } while (!it.atEnd()) { it.advance(buffer.append(it.characters(), it.length())); @@ -2351,7 +2526,7 @@ tryAgain: matchLength = newMatchLength; // If searching forward, stop on the first match. // If searching backward, don't stop, so we end up with the last match. - if (forward) + if (!(options & Backwards)) break; goto tryAgain; } @@ -2366,14 +2541,19 @@ tryAgain: PassRefPtr<Range> findPlainText(const Range* range, const String& target, bool forward, bool caseSensitive) { + return findPlainText(range, target, (forward ? 0 : Backwards) | (caseSensitive ? 0 : CaseInsensitive)); +} + +PassRefPtr<Range> findPlainText(const Range* range, const String& target, FindOptions options) +{ // First, find the text. size_t matchStart; size_t matchLength; { CharacterIterator findIterator(range, TextIteratorEntersTextControls); - matchLength = findPlainText(findIterator, target, forward, caseSensitive, matchStart); + matchLength = findPlainText(findIterator, target, options, matchStart); if (!matchLength) - return collapsedToBoundary(range, forward); + return collapsedToBoundary(range, !(options & Backwards)); } // Then, find the document position of the start and the end of the text. diff --git a/WebCore/editing/TextIterator.h b/WebCore/editing/TextIterator.h index 1bd8828..8b61afe 100644 --- a/WebCore/editing/TextIterator.h +++ b/WebCore/editing/TextIterator.h @@ -26,6 +26,7 @@ #ifndef TextIterator_h #define TextIterator_h +#include "FindOptions.h" #include "InlineTextBox.h" #include "Range.h" #include <wtf/Vector.h> @@ -58,7 +59,9 @@ inline bool isCollapsibleWhitespace(UChar c) } String plainText(const Range*, TextIteratorBehavior defaultBehavior = TextIteratorDefaultBehavior); -UChar* plainTextToMallocAllocatedBuffer(const Range*, unsigned& bufferLength, bool isDisplayString, TextIteratorBehavior defaultBehavior = TextIteratorDefaultBehavior); +UChar* plainTextToMallocAllocatedBuffer(const Range*, unsigned& bufferLength, bool isDisplayString, TextIteratorBehavior = TextIteratorDefaultBehavior); +PassRefPtr<Range> findPlainText(const Range*, const String&, FindOptions); +// FIXME: Switch callers over to the FindOptions version and retire this one. PassRefPtr<Range> findPlainText(const Range*, const String&, bool forward, bool caseSensitive); class BitStack { diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp index 9165a3e..c596353 100644 --- a/WebCore/editing/TypingCommand.cpp +++ b/WebCore/editing/TypingCommand.cpp @@ -310,7 +310,7 @@ void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) - document()->frame()->editor()->markMisspellingsAfterTypingToPosition(p1); + document()->frame()->editor()->markMisspellingsAfterTypingToWord(p1, endingSelection()); #if SUPPORT_AUTOCORRECTION_PANEL else if (commandType == TypingCommand::InsertText) document()->frame()->editor()->startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeCorrection); diff --git a/WebCore/editing/visible_units.cpp b/WebCore/editing/visible_units.cpp index 7d46374..d7343bf 100644 --- a/WebCore/editing/visible_units.cpp +++ b/WebCore/editing/visible_units.cpp @@ -45,30 +45,6 @@ namespace WebCore { using namespace HTMLNames; using namespace WTF::Unicode; -static int endOfFirstWordBoundaryContext(const UChar* characters, int length) -{ - for (int i = 0; i < length; ) { - int first = i; - UChar32 ch; - U16_NEXT(characters, i, length, ch); - if (!requiresContextForWordBoundary(ch)) - return first; - } - return length; -} - -static int startOfLastWordBoundaryContext(const UChar* characters, int length) -{ - for (int i = length; i > 0; ) { - int last = i; - UChar32 ch; - U16_PREV(characters, 0, i, ch); - if (!requiresContextForWordBoundary(ch)) - return last; - } - return 0; -} - enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext }; typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext); @@ -418,21 +394,6 @@ static VisiblePosition startPositionForLine(const VisiblePosition& c) VisiblePosition startOfLine(const VisiblePosition& c) { VisiblePosition visPos = startPositionForLine(c); - - if (visPos.isNotNull()) { - // Make sure the start of line is not greater than the given input position. Else use the previous position to - // obtain start of line. This condition happens when the input position is before the space character at the end - // of a soft-wrapped non-editable line. In this scenario, startPositionForLine would incorrectly hand back a position - // greater than the input position. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space - // style versus lines without that style, which would break before a space by default. - Position p = visPos.deepEquivalent(); - if (p.deprecatedEditingOffset() > c.deepEquivalent().deprecatedEditingOffset() && p.node()->isSameNode(c.deepEquivalent().node())) { - visPos = c.previous(); - if (visPos.isNull()) - return VisiblePosition(); - visPos = startPositionForLine(visPos); - } - } return c.honorEditableBoundaryAtOrAfter(visPos); } @@ -780,7 +741,7 @@ VisiblePosition nextSentencePosition(const VisiblePosition &c) return c.honorEditableBoundaryAtOrBefore(next); } -VisiblePosition startOfParagraph(const VisiblePosition& c, Position::EditingBoundaryCrossingRule boundaryCrossingRule) +VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) { Position p = c.deepEquivalent(); Node *startNode = p.node(); @@ -798,7 +759,7 @@ VisiblePosition startOfParagraph(const VisiblePosition& c, Position::EditingBoun Node *n = startNode; while (n) { - if (boundaryCrossingRule == Position::CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable()) + if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable()) break; RenderObject *r = n->renderer(); if (!r) { @@ -839,7 +800,7 @@ VisiblePosition startOfParagraph(const VisiblePosition& c, Position::EditingBoun return VisiblePosition(node, offset, DOWNSTREAM); } -VisiblePosition endOfParagraph(const VisiblePosition &c, Position::EditingBoundaryCrossingRule boundaryCrossingRule) +VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossingRule boundaryCrossingRule) { if (c.isNull()) return VisiblePosition(); @@ -858,7 +819,7 @@ VisiblePosition endOfParagraph(const VisiblePosition &c, Position::EditingBounda Node *n = startNode; while (n) { - if (boundaryCrossingRule == Position::CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable()) + if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable()) break; RenderObject *r = n->renderer(); if (!r) { @@ -914,12 +875,12 @@ bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b) return a.isNotNull() && startOfParagraph(a) == startOfParagraph(b); } -bool isStartOfParagraph(const VisiblePosition &pos, Position::EditingBoundaryCrossingRule boundaryCrossingRule) +bool isStartOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) { return pos.isNotNull() && pos == startOfParagraph(pos, boundaryCrossingRule); } -bool isEndOfParagraph(const VisiblePosition &pos, Position::EditingBoundaryCrossingRule boundaryCrossingRule) +bool isEndOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) { return pos.isNotNull() && pos == endOfParagraph(pos, boundaryCrossingRule); } diff --git a/WebCore/editing/visible_units.h b/WebCore/editing/visible_units.h index f78f9d1..98a5332 100644 --- a/WebCore/editing/visible_units.h +++ b/WebCore/editing/visible_units.h @@ -27,6 +27,7 @@ #define visible_units_h #include "Document.h" +#include "EditingBoundary.h" #include "Position.h" #include "TextAffinity.h" @@ -61,13 +62,13 @@ VisiblePosition logicalStartOfLine(const VisiblePosition &); VisiblePosition logicalEndOfLine(const VisiblePosition &); // paragraphs (perhaps a misnomer, can be divided by line break elements) -VisiblePosition startOfParagraph(const VisiblePosition&, Position::EditingBoundaryCrossingRule = Position::CannotCrossEditingBoundary); -VisiblePosition endOfParagraph(const VisiblePosition&, Position::EditingBoundaryCrossingRule = Position::CannotCrossEditingBoundary); +VisiblePosition startOfParagraph(const VisiblePosition&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); +VisiblePosition endOfParagraph(const VisiblePosition&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); VisiblePosition startOfNextParagraph(const VisiblePosition&); VisiblePosition previousParagraphPosition(const VisiblePosition &, int x); VisiblePosition nextParagraphPosition(const VisiblePosition &, int x); -bool isStartOfParagraph(const VisiblePosition &, Position::EditingBoundaryCrossingRule = Position::CannotCrossEditingBoundary); -bool isEndOfParagraph(const VisiblePosition &, Position::EditingBoundaryCrossingRule = Position::CannotCrossEditingBoundary); +bool isStartOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); +bool isEndOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); bool inSameParagraph(const VisiblePosition &, const VisiblePosition &); // blocks (true paragraphs; line break elements don't break blocks) |