Reviewed by Darin.
authormjs <mjs@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 24 Jan 2005 22:06:20 +0000 (22:06 +0000)
committermjs <mjs@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 24 Jan 2005 22:06:20 +0000 (22:06 +0000)
<rdar://problem/3967572> Editing should split elements before removing style

        * khtml/editing/htmlediting.cpp:
        (khtml::CompositeEditCommand::splitElement):
        (khtml::CompositeEditCommand::mergeIdenticalElements):
        (khtml::CompositeEditCommand::wrapContentsInDummySpan):
        (khtml::CompositeEditCommand::splitTextNodeContainingElement):
        (khtml::ApplyStyleCommand::applyInlineStyle):
        (khtml::ApplyStyleCommand::splitTextAtStartIfNeeded):
        (khtml::ApplyStyleCommand::splitTextAtEndIfNeeded):
        (khtml::ApplyStyleCommand::splitTextElementAtStartIfNeeded):
        (khtml::ApplyStyleCommand::splitTextElementAtEndIfNeeded):
        (khtml::areIdenticalElements):
        (khtml::ApplyStyleCommand::mergeStartWithPreviousIfIdentical):
        (khtml::ApplyStyleCommand::mergeEndWithNextIfIdentical):
        (khtml::ApplyStyleCommand::cleanUpEmptyStyleSpans):
        (khtml::ApplyStyleCommand::addInlineStyleIfNeeded):
        (khtml::InsertParagraphSeparatorCommand::doApply):
        (khtml::InsertParagraphSeparatorInQuotedContentCommand::doApply):
        (khtml::InsertTextCommand::prepareForTextInsertion):
        (khtml::SplitTextNodeCommand::doUnapply):
        (khtml::SplitElementCommand::SplitElementCommand):
        (khtml::SplitElementCommand::~SplitElementCommand):
        (khtml::SplitElementCommand::doApply):
        (khtml::SplitElementCommand::doUnapply):
        (khtml::MergeIdenticalElementsCommand::MergeIdenticalElementsCommand):
        (khtml::MergeIdenticalElementsCommand::~MergeIdenticalElementsCommand):
        (khtml::MergeIdenticalElementsCommand::doApply):
        (khtml::MergeIdenticalElementsCommand::doUnapply):
        (khtml::WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand):
        (khtml::WrapContentsInDummySpanCommand::~WrapContentsInDummySpanCommand):
        (khtml::WrapContentsInDummySpanCommand::doApply):
        (khtml::WrapContentsInDummySpanCommand::doUnapply):
        (khtml::SplitTextNodeContainingElementCommand::SplitTextNodeContainingElementCommand):
        (khtml::SplitTextNodeContainingElementCommand::~SplitTextNodeContainingElementCommand):
        (khtml::SplitTextNodeContainingElementCommand::doApply):
        * khtml/editing/htmlediting.h:

New layout test that shows the side benefit of this.

* layout-tests/editing/style/unbold-in-bold-expected.txt: Added.
        * layout-tests/editing/style/unbold-in-bold.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@8439 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/editing/style/unbold-in-bold-expected.txt [new file with mode: 0644]
LayoutTests/editing/style/unbold-in-bold.html [new file with mode: 0644]
WebCore/ChangeLog-2005-08-23
WebCore/khtml/editing/htmlediting.cpp
WebCore/khtml/editing/htmlediting.h

diff --git a/LayoutTests/editing/style/unbold-in-bold-expected.txt b/LayoutTests/editing/style/unbold-in-bold-expected.txt
new file mode 100644 (file)
index 0000000..7b21489
--- /dev/null
@@ -0,0 +1,23 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+        RenderInline {SPAN} at (0,0) size 0x0
+        RenderText {TEXT} at (0,0) size 0x0
+        RenderInline {B} at (0,0) size 78x28
+          RenderText {TEXT} at (14,14) size 78x28
+            text run at (14,14) width 78: "xxxxxx "
+        RenderText {TEXT} at (92,14) size 72x28
+          text run at (92,14) width 72: "xxxxxx"
+        RenderInline {B} at (0,0) size 78x28
+          RenderText {TEXT} at (164,14) size 78x28
+            text run at (164,14) width 78: " xxxxxx"
+selection is RANGE:
+start:      position 0 of child 5 {TEXT} of root {DIV}
+upstream:   position 7 of child 1 {TEXT} of child 4 {B} of root {DIV}
+downstream: position 0 of child 5 {TEXT} of root {DIV}
+end:        position 6 of child 5 {TEXT} of root {DIV}
+upstream:   position 6 of child 5 {TEXT} of root {DIV}
+downstream: position 0 of child 1 {TEXT} of child 6 {B} of root {DIV}
diff --git a/LayoutTests/editing/style/unbold-in-bold.html b/LayoutTests/editing/style/unbold-in-bold.html
new file mode 100644 (file)
index 0000000..4f0000b
--- /dev/null
@@ -0,0 +1,45 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    boldCommand();
+    for (i = 0; i < 6; i++)
+        typeCharacterCommand();
+    typeCharacterCommand(' ');
+    for (i = 0; i < 6; i++)
+        typeCharacterCommand();
+    typeCharacterCommand(' ');
+    for (i = 0; i < 6; i++)
+        typeCharacterCommand();
+    moveSelectionBackwardByWordCommand();
+    moveSelectionBackwardByWordCommand();
+    extendSelectionForwardByWordCommand();
+    boldCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body>
+<div contenteditable id="root" class="editing">
+<span id="test"></span>
+</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
index c2e66d80d339cb8bb8e9fb51687f81bc5fb2b44e..b5d2aa9ebd8f5e9be463de335e526347d5c7c941 100644 (file)
@@ -1,3 +1,50 @@
+2005-01-21  Maciej Stachowiak  <mjs@apple.com>
+
+        Reviewed by Darin.
+
+       <rdar://problem/3967572> Editing should split elements before removing style
+       
+        * khtml/editing/htmlediting.cpp:
+        (khtml::CompositeEditCommand::splitElement):
+        (khtml::CompositeEditCommand::mergeIdenticalElements):
+        (khtml::CompositeEditCommand::wrapContentsInDummySpan):
+        (khtml::CompositeEditCommand::splitTextNodeContainingElement):
+        (khtml::ApplyStyleCommand::applyInlineStyle):
+        (khtml::ApplyStyleCommand::splitTextAtStartIfNeeded):
+        (khtml::ApplyStyleCommand::splitTextAtEndIfNeeded):
+        (khtml::ApplyStyleCommand::splitTextElementAtStartIfNeeded):
+        (khtml::ApplyStyleCommand::splitTextElementAtEndIfNeeded):
+        (khtml::areIdenticalElements):
+        (khtml::ApplyStyleCommand::mergeStartWithPreviousIfIdentical):
+        (khtml::ApplyStyleCommand::mergeEndWithNextIfIdentical):
+        (khtml::ApplyStyleCommand::cleanUpEmptyStyleSpans):
+        (khtml::ApplyStyleCommand::addInlineStyleIfNeeded):
+        (khtml::InsertParagraphSeparatorCommand::doApply):
+        (khtml::InsertParagraphSeparatorInQuotedContentCommand::doApply):
+        (khtml::InsertTextCommand::prepareForTextInsertion):
+        (khtml::SplitTextNodeCommand::doUnapply):
+        (khtml::SplitElementCommand::SplitElementCommand):
+        (khtml::SplitElementCommand::~SplitElementCommand):
+        (khtml::SplitElementCommand::doApply):
+        (khtml::SplitElementCommand::doUnapply):
+        (khtml::MergeIdenticalElementsCommand::MergeIdenticalElementsCommand):
+        (khtml::MergeIdenticalElementsCommand::~MergeIdenticalElementsCommand):
+        (khtml::MergeIdenticalElementsCommand::doApply):
+        (khtml::MergeIdenticalElementsCommand::doUnapply):
+        (khtml::WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand):
+        (khtml::WrapContentsInDummySpanCommand::~WrapContentsInDummySpanCommand):
+        (khtml::WrapContentsInDummySpanCommand::doApply):
+        (khtml::WrapContentsInDummySpanCommand::doUnapply):
+        (khtml::SplitTextNodeContainingElementCommand::SplitTextNodeContainingElementCommand):
+        (khtml::SplitTextNodeContainingElementCommand::~SplitTextNodeContainingElementCommand):
+        (khtml::SplitTextNodeContainingElementCommand::doApply):
+        * khtml/editing/htmlediting.h:
+
+       New layout test that shows the side benefit of this.
+        
+       * layout-tests/editing/style/unbold-in-bold-expected.txt: Added.
+        * layout-tests/editing/style/unbold-in-bold.html: Added.
+
 2005-01-24  Darin Adler  <darin@apple.com>
 
         Reviewed by John.
index d58092adcf2800afbef0254580ece5c73305730a..aecad4966609a119ce475a70c66c8a7a9ca479e1 100644 (file)
@@ -728,6 +728,30 @@ void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
     applyCommandToComposite(cmd);
 }
 
+void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
+{
+    EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
+    applyCommandToComposite(cmd);
+}
+
+void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
+{
+    EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
+    applyCommandToComposite(cmd);
+}
+
+void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
+{
+    EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
+    applyCommandToComposite(cmd);
+}
+
+void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset)
+{
+    EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
+    applyCommandToComposite(cmd);
+}
+
 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
 {
     EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
@@ -1316,25 +1340,43 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
     // to check a computed style
     document()->updateLayout();
 
+    // split the start node and containingelement if the selection starts inside of it
+    bool splitStart = splitTextElementAtStartIfNeeded(start, end); 
+    if (splitStart) {
+        start = endingSelection().start();
+        end = endingSelection().end();
+    }
+
+    // split the end node and containing element if the selection ends inside of it
+    bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
+    start = endingSelection().start();
+    end = endingSelection().end();
+
     // Remove style from the selection.
     // Use the upstream position of the start for removing style.
     // This will ensure we remove all traces of the relevant styles from the selection
     // and prevent us from adding redundant ones, as described in:
     // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
     removeInlineStyle(style, start.upstream(), end);
-    
-    // split the start node if the selection starts inside of it
-    bool splitStart = splitTextAtStartIfNeeded(start, end); 
+
+    if (splitStart || splitEnd) {
+        cleanUpEmptyStyleSpans(start, end);
+    }
+
     if (splitStart) {
+        bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
+        if (mergedStart) {
+            start = endingSelection().start();
+            end = endingSelection().end();
+        }
+    }
+
+    if (splitEnd) {
+        mergeEndWithNextIfIdentical(start, end);
         start = endingSelection().start();
         end = endingSelection().end();
     }
 
-    // split the end node if the selection ends inside of it
-    splitTextAtEndIfNeeded(start, end);
-    start = endingSelection().start();
-    end = endingSelection().end();
-
     // update document layout once before running the rest of the function
     // so that we avoid the expense of updating before each and every call
     // to check a computed style
@@ -1481,14 +1523,12 @@ bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start,
 //------------------------------------------------------------------------------------------
 // ApplyStyleCommand: style-application helpers
 
-
 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
 {
     if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
         long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
         TextImpl *text = static_cast<TextImpl *>(start.node());
-        EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, start.offset()));
-        applyCommandToComposite(cmd);
+        splitTextNode(text, start.offset());
         setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)));
         return true;
     }
@@ -1499,10 +1539,9 @@ bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Posi
 {
     if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
         TextImpl *text = static_cast<TextImpl *>(end.node());
-        SplitTextNodeCommand *impl = new SplitTextNodeCommand(document(), text, end.offset());
-        EditCommandPtr cmd(impl);
-        applyCommandToComposite(cmd);
-        NodeImpl *prevNode = impl->node()->previousSibling();
+        splitTextNode(text, end.offset());
+        
+        NodeImpl *prevNode = text->previousSibling();
         ASSERT(prevNode);
         NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
         ASSERT(startNode);
@@ -1512,6 +1551,202 @@ bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Posi
     return false;
 }
 
+bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
+{
+    if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
+        long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
+        TextImpl *text = static_cast<TextImpl *>(start.node());
+        splitTextNodeContainingElement(text, start.offset());
+
+        setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.offset() - endOffsetAdjustment)));
+        return true;
+    }
+    return false;
+}
+
+bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
+{
+    if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
+        TextImpl *text = static_cast<TextImpl *>(end.node());
+        splitTextNodeContainingElement(text, end.offset());
+
+        NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
+        ASSERT(prevNode);
+        NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
+        ASSERT(startNode);
+        setEndingSelection(Selection(Position(startNode, start.offset()), Position(prevNode->parent(), prevNode->nodeIndex() + 1)));
+        return true;
+    }
+    return false;
+}
+
+static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
+{
+    // check that tag name and all attribute names and values are identical
+
+    if (!first->isElementNode())
+        return false;
+    
+    if (!second->isElementNode())
+        return false;
+
+    ElementImpl *firstElement = static_cast<ElementImpl *>(first);
+    ElementImpl *secondElement = static_cast<ElementImpl *>(second);
+    
+    if (firstElement->id() != secondElement->id())
+        return false;
+
+    NamedAttrMapImpl *firstMap = firstElement->attributes();
+    NamedAttrMapImpl *secondMap = secondElement->attributes();
+
+    unsigned firstLength = firstMap->length();
+
+    if (firstLength != secondMap->length())
+        return false;
+
+    for (unsigned i = 0; i < firstLength; i++) {
+        DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
+        DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
+
+        if (!secondAttribute || attribute->value() != secondAttribute->value())
+            return false;
+    }
+    
+    return true;
+}
+
+bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
+{
+    NodeImpl *startNode = start.node();
+    long startOffset = start.offset();
+
+    if (start.node()->isAtomicNode()) {
+        if (start.offset() != 0)
+            return false;
+
+        if (start.node()->previousSibling())
+            return false;
+
+        startNode = start.node()->parent();
+        startOffset = 0;
+    }
+
+    if (!startNode->isElementNode())
+        return false;
+
+    if (startOffset != 0)
+        return false;
+
+    NodeImpl *previousSibling = startNode->previousSibling();
+
+    if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
+        ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
+        ElementImpl *element = static_cast<ElementImpl *>(startNode);
+        NodeImpl *startChild = element->firstChild();
+        ASSERT(startChild);
+        mergeIdenticalElements(previousElement, element);
+
+        long startOffsetAdjustment = startChild->nodeIndex();
+        long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
+
+        setEndingSelection(Selection(Position(startNode, startOffsetAdjustment),
+                                     Position(end.node(), end.offset() + endOffsetAdjustment))); 
+
+        return true;
+    }
+
+    return false;
+}
+
+bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
+{
+    NodeImpl *endNode = end.node();
+    int endOffset = end.offset();
+
+    if (endNode->isAtomicNode()) {
+        if (endOffset < endNode->caretMaxOffset())
+            return false;
+
+        unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
+        if (end.node()->nextSibling())
+            return false;
+
+        endNode = end.node()->parent();
+        endOffset = parentLastOffset;
+    }
+
+    if (!endNode->isElementNode() || endNode->id() == ID_BR)
+        return false;
+
+    NodeImpl *nextSibling = endNode->nextSibling();
+
+    if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
+        ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
+        ElementImpl *element = static_cast<ElementImpl *>(endNode);
+        NodeImpl *nextChild = nextElement->firstChild();
+
+        mergeIdenticalElements(element, nextElement);
+
+        NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
+        ASSERT(startNode);
+
+        int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
+
+        setEndingSelection(Selection(Position(startNode, start.offset()), 
+                                     Position(nextElement, endOffset)));
+        return true;
+    }
+
+    return false;
+}
+
+void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
+{
+    NodeImpl *node;
+    for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
+    }
+
+    if (isEmptyStyleSpan(node->previousSibling())) {
+        removeNodePreservingChildren(node->previousSibling());
+    }
+
+    if (start.node() == end.node()) {
+        if (start.node()->isTextNode()) {
+            for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
+                if (isEmptyStyleSpan(cur)) {
+                    removeNodePreservingChildren(cur);
+                    break;
+                }
+            }
+
+        }
+    } else {
+        if (start.node()->isTextNode()) {
+            for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
+                if (isEmptyStyleSpan(cur)) {
+                    removeNodePreservingChildren(cur);
+                    break;
+                }
+            }
+        }
+
+        if (end.node()->isTextNode()) {
+            for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
+                if (isEmptyStyleSpan(cur)) {
+                    removeNodePreservingChildren(cur);
+                    break;
+                }
+            }
+        }
+    }
+    
+    for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
+    }
+    if (isEmptyStyleSpan(node->nextSibling())) {
+        removeNodePreservingChildren(node->nextSibling());
+    }
+}
+
 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
 {
     ASSERT(startNode);
@@ -1560,8 +1795,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *s
     int exceptionCode = 0;
     
     if (styleChange.cssStyle().length() > 0) {
-        ElementImpl *styleElement = document()->createHTMLElement("SPAN", exceptionCode);
-        ASSERT(exceptionCode == 0);
+        ElementImpl *styleElement = createStyleSpanElement(document());
         styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
         styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
         insertNodeBefore(styleElement, startNode);
@@ -1583,48 +1817,6 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *s
     }
 }
 
-Position ApplyStyleCommand::positionInsertionPoint(Position pos)
-{
-    if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) {
-        SplitTextNodeCommand *impl = new SplitTextNodeCommand(document(), static_cast<TextImpl *>(pos.node()), pos.offset());
-        EditCommandPtr split(impl);
-        split.apply();
-        pos = Position(impl->node(), 0);
-    }
-
-#if 0
-    // EDIT FIXME: If modified to work with the internals of applying style,
-    // this code can work to optimize cases where a style change is taking place on
-    // a boundary between nodes where one of the nodes has the desired style. In other
-    // words, it is possible for content to be merged into existing nodes rather than adding
-    // additional markup.
-    if (currentlyHasStyle(pos))
-        return pos;
-        
-    // try next node
-    if (pos.offset() >= pos.node()->caretMaxOffset()) {
-        NodeImpl *nextNode = pos.node()->traverseNextNode();
-        if (nextNode) {
-            Position next = Position(nextNode, 0);
-            if (currentlyHasStyle(next))
-                return next;
-        }
-    }
-
-    // try previous node
-    if (pos.offset() <= pos.node()->caretMinOffset()) {
-        NodeImpl *prevNode = pos.node()->traversePreviousNode();
-        if (prevNode) {
-            Position prev = Position(prevNode, prevNode->maxOffset());
-            if (currentlyHasStyle(prev))
-                return prev;
-        }
-    }
-#endif
-    
-    return pos;
-}
-
 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
 {
     float size = 0.0f;
@@ -2715,10 +2907,7 @@ void InsertParagraphSeparatorCommand::doApply()
         TextImpl *textNode = static_cast<TextImpl *>(startNode);
         bool atEnd = (unsigned long)pos.offset() >= textNode->length();
         if (pos.offset() > 0 && !atEnd) {
-            SplitTextNodeCommand *splitCommand = new SplitTextNodeCommand(document(), textNode, pos.offset());
-            EditCommandPtr cmd(splitCommand);
-            applyCommandToComposite(cmd);
-            startNode = splitCommand->node();
+            splitTextNode(textNode, pos.offset());
             pos = Position(startNode, 0);
             splitText = true;
         }
@@ -2842,10 +3031,7 @@ void InsertParagraphSeparatorInQuotedContentCommand::doApply()
             TextImpl *textNode = static_cast<TextImpl *>(startNode);
             bool atEnd = (unsigned long)pos.offset() >= textNode->length();
             if (pos.offset() > 0 && !atEnd) {
-                SplitTextNodeCommand *splitCommand = new SplitTextNodeCommand(document(), textNode, pos.offset());
-                EditCommandPtr cmd(splitCommand);
-                applyCommandToComposite(cmd);
-                startNode = splitCommand->node();
+                splitTextNode(textNode, pos.offset());
                 pos = Position(startNode, 0);
             }
             else if (atEnd) {
@@ -2993,30 +3179,7 @@ Position InsertTextCommand::prepareForTextInsertion(bool adjustDownstream)
         
         pos = Position(textNode, 0);
     }
-    else {
-        // Handle the case where there is a typing style.
-        // FIXME: Improve typing style.
-        // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-        CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
-        if (typingStyle && typingStyle->length() > 0) {
-            if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) {
-                // Need to split current text node in order to insert a span.
-                TextImpl *text = static_cast<TextImpl *>(pos.node());
-                SplitTextNodeCommand *impl = new SplitTextNodeCommand(document(), text, pos.offset());
-                EditCommandPtr cmd(impl);
-                applyCommandToComposite(cmd);
-                setEndingSelection(Position(impl->node(), 0));
-            }
-            
-            TextImpl *editingTextNode = document()->createEditingTextNode("");
-            NodeImpl *node = endingSelection().start().upstream(StayInBlock).node();
-            if (node->isBlockFlow())
-                insertNodeAt(editingTextNode, node, 0);
-            else
-                insertNodeAfter(editingTextNode, node);
-            pos = Position(editingTextNode, 0);
-        }
-    }
+
     return pos;
 }
 
@@ -4147,6 +4310,231 @@ void SplitTextNodeCommand::doUnapply()
     m_offset = m_text1->length();
 }
 
+//------------------------------------------------------------------------------------------
+// SplitElementCommand
+
+SplitElementCommand::SplitElementCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element, DOM::NodeImpl *atChild)
+    : EditCommand(document), m_element1(0), m_element2(element), m_atChild(atChild)
+{
+    ASSERT(m_element2);
+    ASSERT(m_atChild);
+
+    m_element2->ref();
+    m_atChild->ref();
+}
+
+SplitElementCommand::~SplitElementCommand()
+{
+    if (m_element1)
+        m_element1->deref();
+
+    ASSERT(m_element2);
+    m_element2->deref();
+    ASSERT(m_atChild);
+    m_atChild->deref();
+}
+
+void SplitElementCommand::doApply()
+{
+    ASSERT(m_element2);
+    ASSERT(m_atChild);
+
+    int exceptionCode = 0;
+
+    if (!m_element1) {
+        // create only if needed.
+        // if reapplying, this object will already exist.
+        m_element1 = static_cast<ElementImpl *>(m_element2->cloneNode(false));
+        ASSERT(m_element1);
+        m_element1->ref();
+    }
+
+    m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
+    ASSERT(exceptionCode == 0);
+    
+    while (m_element2->firstChild() != m_atChild) {
+        ASSERT(m_element2->firstChild());
+        m_element1->appendChild(m_element2->firstChild(), exceptionCode);
+        ASSERT(exceptionCode == 0);
+    }
+}
+
+void SplitElementCommand::doUnapply()
+{
+    ASSERT(m_element1);
+    ASSERT(m_element2);
+    ASSERT(m_atChild);
+
+    ASSERT(m_element1->nextSibling() == m_element2);
+    ASSERT(m_element2->firstChild() && m_element2->firstChild() == m_atChild);
+
+    int exceptionCode = 0;
+
+    while (m_element1->lastChild()) {
+        m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
+        ASSERT(exceptionCode == 0);
+    }
+
+    m_element2->parentNode()->removeChild(m_element1, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+//------------------------------------------------------------------------------------------
+// MergeIdenticalElementsCommand
+
+MergeIdenticalElementsCommand::MergeIdenticalElementsCommand(DOM::DocumentImpl *document, DOM::ElementImpl *first, DOM::ElementImpl *second)
+    : EditCommand(document), m_element1(first), m_element2(second), m_atChild(0)
+{
+    ASSERT(m_element1);
+    ASSERT(m_element2);
+
+    m_element1->ref();
+    m_element2->ref();
+}
+
+MergeIdenticalElementsCommand::~MergeIdenticalElementsCommand()
+{
+    if (m_atChild)
+        m_atChild->deref();
+
+    ASSERT(m_element1);
+    m_element1->deref();
+    ASSERT(m_element2);
+    m_element2->deref();
+}
+
+void MergeIdenticalElementsCommand::doApply()
+{
+    ASSERT(m_element1);
+    ASSERT(m_element2);
+    ASSERT(m_element1->nextSibling() == m_element2);
+
+    int exceptionCode = 0;
+
+    if (!m_atChild) {
+        m_atChild = m_element2->firstChild();
+        m_atChild->ref();
+    }
+
+    while (m_element1->lastChild()) {
+        m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
+        ASSERT(exceptionCode == 0);
+    }
+
+    m_element2->parentNode()->removeChild(m_element1, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+void MergeIdenticalElementsCommand::doUnapply()
+{
+    ASSERT(m_element1);
+    ASSERT(m_element2);
+
+    int exceptionCode = 0;
+
+    m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
+    ASSERT(exceptionCode == 0);
+
+    while (m_element2->firstChild() != m_atChild) {
+        ASSERT(m_element2->firstChild());
+        m_element1->appendChild(m_element2->firstChild(), exceptionCode);
+        ASSERT(exceptionCode == 0);
+    }
+}
+
+//------------------------------------------------------------------------------------------
+// WrapContentsInDummySpanCommand
+
+WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element)
+    : EditCommand(document), m_element(element), m_dummySpan(0)
+{
+    ASSERT(m_element);
+
+    m_element->ref();
+}
+
+WrapContentsInDummySpanCommand::~WrapContentsInDummySpanCommand()
+{
+    if (m_dummySpan)
+        m_dummySpan->deref();
+
+    ASSERT(m_element);
+    m_element->deref();
+}
+
+void WrapContentsInDummySpanCommand::doApply()
+{
+    ASSERT(m_element);
+
+    int exceptionCode = 0;
+
+    if (!m_dummySpan) {
+        m_dummySpan = createStyleSpanElement(document());
+        m_dummySpan->ref();
+    }
+
+    while (m_element->firstChild()) {
+        m_dummySpan->appendChild(m_element->firstChild(), exceptionCode);
+        ASSERT(exceptionCode == 0);
+    }
+
+    m_element->appendChild(m_dummySpan, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+void WrapContentsInDummySpanCommand::doUnapply()
+{
+    ASSERT(m_element);
+    ASSERT(m_dummySpan);
+
+    ASSERT(m_element->firstChild() == m_dummySpan);
+    ASSERT(!m_element->firstChild()->nextSibling());
+
+    int exceptionCode = 0;
+
+    while (m_dummySpan->firstChild()) {
+        m_element->appendChild(m_dummySpan->firstChild(), exceptionCode);
+        ASSERT(exceptionCode == 0);
+    }
+
+    m_element->removeChild(m_dummySpan, exceptionCode);
+    ASSERT(exceptionCode == 0);
+}
+
+//------------------------------------------------------------------------------------------
+// SplitTextNodeContainingElementCommand
+
+SplitTextNodeContainingElementCommand::SplitTextNodeContainingElementCommand(DocumentImpl *document, TextImpl *text, long offset)
+    : CompositeEditCommand(document), m_text(text), m_offset(offset)
+{
+    ASSERT(m_text);
+    ASSERT(m_text->length() > 0);
+
+    m_text->ref();
+}
+
+SplitTextNodeContainingElementCommand::~SplitTextNodeContainingElementCommand()
+{
+    ASSERT(m_text);
+    m_text->deref();
+}
+
+void SplitTextNodeContainingElementCommand::doApply()
+{
+    ASSERT(m_text);
+    ASSERT(m_offset > 0);
+
+    splitTextNode(m_text, m_offset);
+    
+    NodeImpl *parentNode = m_text->parentNode();
+    if (!parentNode->renderer() || !parentNode->renderer()->isInline()) {
+        wrapContentsInDummySpan(static_cast<ElementImpl *>(parentNode));
+        parentNode = parentNode->firstChild();
+    }
+
+    splitElement(static_cast<ElementImpl *>(parentNode), m_text);
+}
+
 //------------------------------------------------------------------------------------------
 // TypingCommand
 
index 83f1fcc900593550895863f38133a40e39fd4eaf..d471bd35c32658d4ca6b2ca1f2e14b9894e211dc 100644 (file)
@@ -211,6 +211,10 @@ protected:
     void replaceTextInNode(DOM::TextImpl *node, long offset, long count, const DOM::DOMString &replacementText);
     void setNodeAttribute(DOM::ElementImpl *, int attribute, const DOM::DOMString &);
     void splitTextNode(DOM::TextImpl *text, long offset);
+    void splitElement(DOM::ElementImpl *element, DOM::NodeImpl *atChild);
+    void mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second);
+    void wrapContentsInDummySpan(DOM::ElementImpl *element);
+    void splitTextNodeContainingElement(DOM::TextImpl *text, long offset);
 
     void deleteInsignificantText(DOM::TextImpl *, int start, int end);
     void deleteInsignificantText(const DOM::Position &start, const DOM::Position &end);
@@ -280,8 +284,13 @@ private:
     void addInlineStyleIfNeeded(DOM::CSSMutableStyleDeclarationImpl *, DOM::NodeImpl *start, DOM::NodeImpl *end);
     bool splitTextAtStartIfNeeded(const DOM::Position &start, const DOM::Position &end);
     bool splitTextAtEndIfNeeded(const DOM::Position &start, const DOM::Position &end);
+    bool splitTextElementAtStartIfNeeded(const DOM::Position &start, const DOM::Position &end);
+    bool splitTextElementAtEndIfNeeded(const DOM::Position &start, const DOM::Position &end);
+    bool mergeStartWithPreviousIfIdentical(const DOM::Position &start, const DOM::Position &end);
+    bool mergeEndWithNextIfIdentical(const DOM::Position &start, const DOM::Position &end);
+    void cleanUpEmptyStyleSpans(const DOM::Position &start, const DOM::Position &end);
+
     void surroundNodeRangeWithElement(DOM::NodeImpl *start, DOM::NodeImpl *end, DOM::ElementImpl *element);
-    DOM::Position positionInsertionPoint(DOM::Position);
     float computedFontSize(const DOM::NodeImpl *);
     void joinChildTextNodes(DOM::NodeImpl *, const DOM::Position &start, const DOM::Position &end);
     
@@ -738,6 +747,76 @@ private:
     unsigned long m_offset;
 };
 
+//------------------------------------------------------------------------------------------
+// WrapContentsInDummySpanCommand
+
+class WrapContentsInDummySpanCommand : public EditCommand
+{
+public:
+    WrapContentsInDummySpanCommand(DOM::DocumentImpl *, DOM::ElementImpl *);
+    virtual ~WrapContentsInDummySpanCommand();
+       
+    virtual void doApply();
+    virtual void doUnapply();
+
+private:
+    DOM::ElementImpl *m_element;
+    DOM::ElementImpl *m_dummySpan;
+};
+
+//------------------------------------------------------------------------------------------
+// SplitElementCommand
+
+class SplitElementCommand : public EditCommand
+{
+public:
+    SplitElementCommand(DOM::DocumentImpl *, DOM::ElementImpl *element, DOM::NodeImpl *atChild);
+    virtual ~SplitElementCommand();
+       
+    virtual void doApply();
+    virtual void doUnapply();
+
+private:
+    DOM::ElementImpl *m_element1;
+    DOM::ElementImpl *m_element2;
+    DOM::NodeImpl *m_atChild;
+};
+
+//------------------------------------------------------------------------------------------
+// MergeIdenticalElementsCommand
+
+class MergeIdenticalElementsCommand : public EditCommand
+{
+public:
+    MergeIdenticalElementsCommand(DOM::DocumentImpl *, DOM::ElementImpl *first, DOM::ElementImpl *second);
+    virtual ~MergeIdenticalElementsCommand();
+       
+    virtual void doApply();
+    virtual void doUnapply();
+
+private:
+    DOM::ElementImpl *m_element1;
+    DOM::ElementImpl *m_element2;
+    DOM::NodeImpl *m_atChild;
+};
+
+//------------------------------------------------------------------------------------------
+// SplitTextNodeContainingElementCommand
+
+class SplitTextNodeContainingElementCommand : public CompositeEditCommand
+{
+public:
+    SplitTextNodeContainingElementCommand(DOM::DocumentImpl *, DOM::TextImpl *, long);
+    virtual ~SplitTextNodeContainingElementCommand();
+       
+    virtual void doApply();
+
+private:
+    DOM::TextImpl *m_text;
+    long m_offset;
+};
+
+
 //------------------------------------------------------------------------------------------
 // TypingCommand