summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/editing')
-rw-r--r--WebCore/editing/ApplyStyleCommand.cpp2
-rw-r--r--WebCore/editing/CompositeEditCommand.cpp4
-rw-r--r--WebCore/editing/CorrectionPanelInfo.h3
-rw-r--r--WebCore/editing/DeleteSelectionCommand.cpp5
-rw-r--r--WebCore/editing/EditingBehavior.h3
-rw-r--r--WebCore/editing/EditingBoundary.h38
-rw-r--r--WebCore/editing/EditingStyle.cpp60
-rw-r--r--WebCore/editing/EditingStyle.h7
-rw-r--r--WebCore/editing/Editor.cpp278
-rw-r--r--WebCore/editing/Editor.h19
-rw-r--r--WebCore/editing/EditorCommand.cpp3
-rw-r--r--WebCore/editing/FindOptions.h46
-rw-r--r--WebCore/editing/InsertLineBreakCommand.cpp10
-rw-r--r--WebCore/editing/InsertListCommand.cpp5
-rw-r--r--WebCore/editing/InsertTextCommand.cpp23
-rw-r--r--WebCore/editing/MarkupAccumulator.cpp4
-rw-r--r--WebCore/editing/SelectionController.cpp1
-rw-r--r--WebCore/editing/SelectionController.h14
-rw-r--r--WebCore/editing/TextCheckingHelper.cpp2
-rw-r--r--WebCore/editing/TextCheckingHelper.h2
-rw-r--r--WebCore/editing/TextIterator.cpp218
-rw-r--r--WebCore/editing/TextIterator.h5
-rw-r--r--WebCore/editing/TypingCommand.cpp2
-rw-r--r--WebCore/editing/visible_units.cpp51
-rw-r--r--WebCore/editing/visible_units.h9
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)