1 /* 2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "core/editing/TypingCommand.h" 28 29 #include "HTMLNames.h" 30 #include "core/dom/Document.h" 31 #include "core/dom/Element.h" 32 #include "core/editing/BreakBlockquoteCommand.h" 33 #include "core/editing/Editor.h" 34 #include "core/editing/FrameSelection.h" 35 #include "core/editing/InsertLineBreakCommand.h" 36 #include "core/editing/InsertParagraphSeparatorCommand.h" 37 #include "core/editing/InsertTextCommand.h" 38 #include "core/editing/SpellChecker.h" 39 #include "core/editing/VisiblePosition.h" 40 #include "core/editing/VisibleUnits.h" 41 #include "core/editing/htmlediting.h" 42 #include "core/frame/Frame.h" 43 #include "core/rendering/RenderObject.h" 44 45 namespace WebCore { 46 47 using namespace HTMLNames; 48 49 class TypingCommandLineOperation 50 { 51 public: 52 TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text) 53 : m_typingCommand(typingCommand) 54 , m_selectInsertedText(selectInsertedText) 55 , m_text(text) 56 { } 57 58 void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const 59 { 60 if (isLastLine) { 61 if (!lineOffset || lineLength > 0) 62 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), m_selectInsertedText); 63 } else { 64 if (lineLength > 0) 65 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), false); 66 m_typingCommand->insertParagraphSeparator(); 67 } 68 } 69 70 private: 71 TypingCommand* m_typingCommand; 72 bool m_selectInsertedText; 73 const String& m_text; 74 }; 75 76 TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType) 77 : TextInsertionBaseCommand(document) 78 , m_commandType(commandType) 79 , m_textToInsert(textToInsert) 80 , m_openForMoreTyping(true) 81 , m_selectInsertedText(options & SelectInsertedText) 82 , m_smartDelete(options & SmartDelete) 83 , m_granularity(granularity) 84 , m_compositionType(compositionType) 85 , m_killRing(options & KillRing) 86 , m_openedByBackwardDelete(false) 87 , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator) 88 , m_shouldPreventSpellChecking(options & PreventSpellChecking) 89 { 90 updatePreservesTypingStyle(m_commandType); 91 } 92 93 void TypingCommand::deleteSelection(Document& document, Options options) 94 { 95 Frame* frame = document.frame(); 96 ASSERT(frame); 97 98 if (!frame->selection().isRange()) 99 return; 100 101 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 102 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 103 lastTypingCommand->deleteSelection(options & SmartDelete); 104 return; 105 } 106 107 TypingCommand::create(document, DeleteSelection, "", options)->apply(); 108 } 109 110 void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity) 111 { 112 if (granularity == CharacterGranularity) { 113 Frame* frame = document.frame(); 114 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 115 // If the last typing command is not Delete, open a new typing command. 116 // We need to group continuous delete commands alone in a single typing command. 117 if (lastTypingCommand->commandTypeOfOpenCommand() == DeleteKey) { 118 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame); 119 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 120 lastTypingCommand->deleteKeyPressed(granularity, options & KillRing); 121 return; 122 } 123 } 124 } 125 126 TypingCommand::create(document, DeleteKey, "", options, granularity)->apply(); 127 } 128 129 void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, TextGranularity granularity) 130 { 131 // FIXME: Forward delete in TextEdit appears to open and close a new typing command. 132 if (granularity == CharacterGranularity) { 133 Frame* frame = document.frame(); 134 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 135 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame); 136 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 137 lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing); 138 return; 139 } 140 } 141 142 TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply(); 143 } 144 145 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame) 146 { 147 ASSERT(frame); 148 VisibleSelection currentSelection = frame->selection().selection(); 149 if (currentSelection == typingCommand->endingSelection()) 150 return; 151 152 typingCommand->setStartingSelection(currentSelection); 153 typingCommand->setEndingSelection(currentSelection); 154 } 155 156 void TypingCommand::insertText(Document& document, const String& text, Options options, TextCompositionType composition) 157 { 158 Frame* frame = document.frame(); 159 ASSERT(frame); 160 161 if (!text.isEmpty()) 162 document.frame()->spellChecker().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0])); 163 164 insertText(document, text, frame->selection().selection(), options, composition); 165 } 166 167 // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to FrameSelection's current selection. 168 void TypingCommand::insertText(Document& document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType) 169 { 170 RefPtr<Frame> frame = document.frame(); 171 ASSERT(frame); 172 173 VisibleSelection currentSelection = frame->selection().selection(); 174 175 String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate); 176 177 // Set the starting and ending selection appropriately if we are using a selection 178 // that is different from the current selection. In the future, we should change EditCommand 179 // to deal with custom selections in a general way that can be used by all of the commands. 180 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame.get())) { 181 if (lastTypingCommand->endingSelection() != selectionForInsertion) { 182 lastTypingCommand->setStartingSelection(selectionForInsertion); 183 lastTypingCommand->setEndingSelection(selectionForInsertion); 184 } 185 186 lastTypingCommand->setCompositionType(compositionType); 187 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 188 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 189 lastTypingCommand->insertText(newText, options & SelectInsertedText); 190 return; 191 } 192 193 RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType); 194 applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection); 195 } 196 197 void TypingCommand::insertLineBreak(Document& document, Options options) 198 { 199 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { 200 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 201 lastTypingCommand->insertLineBreak(); 202 return; 203 } 204 205 TypingCommand::create(document, InsertLineBreak, "", options)->apply(); 206 } 207 208 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document) 209 { 210 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { 211 lastTypingCommand->insertParagraphSeparatorInQuotedContent(); 212 return; 213 } 214 215 TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent)->apply(); 216 } 217 218 void TypingCommand::insertParagraphSeparator(Document& document, Options options) 219 { 220 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { 221 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 222 lastTypingCommand->insertParagraphSeparator(); 223 return; 224 } 225 226 TypingCommand::create(document, InsertParagraphSeparator, "", options)->apply(); 227 } 228 229 PassRefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(Frame* frame) 230 { 231 ASSERT(frame); 232 233 RefPtr<CompositeEditCommand> lastEditCommand = frame->editor().lastEditCommand(); 234 if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping()) 235 return 0; 236 237 return static_cast<TypingCommand*>(lastEditCommand.get()); 238 } 239 240 void TypingCommand::closeTyping(Frame* frame) 241 { 242 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) 243 lastTypingCommand->closeTyping(); 244 } 245 246 void TypingCommand::doApply() 247 { 248 if (!endingSelection().isNonOrphanedCaretOrRange()) 249 return; 250 251 if (m_commandType == DeleteKey) 252 if (m_commands.isEmpty()) 253 m_openedByBackwardDelete = true; 254 255 switch (m_commandType) { 256 case DeleteSelection: 257 deleteSelection(m_smartDelete); 258 return; 259 case DeleteKey: 260 deleteKeyPressed(m_granularity, m_killRing); 261 return; 262 case ForwardDeleteKey: 263 forwardDeleteKeyPressed(m_granularity, m_killRing); 264 return; 265 case InsertLineBreak: 266 insertLineBreak(); 267 return; 268 case InsertParagraphSeparator: 269 insertParagraphSeparator(); 270 return; 271 case InsertParagraphSeparatorInQuotedContent: 272 insertParagraphSeparatorInQuotedContent(); 273 return; 274 case InsertText: 275 insertText(m_textToInsert, m_selectInsertedText); 276 return; 277 } 278 279 ASSERT_NOT_REACHED(); 280 } 281 282 EditAction TypingCommand::editingAction() const 283 { 284 return EditActionTyping; 285 } 286 287 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) 288 { 289 Frame* frame = document().frame(); 290 if (!frame) 291 return; 292 293 if (!frame->spellChecker().isContinuousSpellCheckingEnabled()) 294 return; 295 296 frame->spellChecker().cancelCheck(); 297 298 // Take a look at the selection that results after typing and determine whether we need to spellcheck. 299 // Since the word containing the current selection is never marked, this does a check to 300 // see if typing made a new word that is not in the current selection. Basically, you 301 // get this by being at the end of a word and typing a space. 302 VisiblePosition start(endingSelection().start(), endingSelection().affinity()); 303 VisiblePosition previous = start.previous(); 304 if (previous.isNotNull()) { 305 VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); 306 VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); 307 if (p1 != p2) 308 frame->spellChecker().markMisspellingsAfterTypingToWord(p1, endingSelection()); 309 } 310 } 311 312 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping) 313 { 314 Frame* frame = document().frame(); 315 if (!frame) 316 return; 317 318 updatePreservesTypingStyle(commandTypeForAddedTyping); 319 updateCommandTypeOfOpenCommand(commandTypeForAddedTyping); 320 321 // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled. 322 markMisspellingsAfterTyping(commandTypeForAddedTyping); 323 frame->editor().appliedEditing(this); 324 } 325 326 void TypingCommand::insertText(const String &text, bool selectInsertedText) 327 { 328 // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved. 329 // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending 330 // an existing selection; at the moment they can either put the caret after what's inserted or 331 // select what's inserted, but there's no way to "extend selection" to include both an old selection 332 // that ends just before where we want to insert text and the newly inserted text. 333 TypingCommandLineOperation operation(this, selectInsertedText, text); 334 forEachLineInString(text, operation); 335 } 336 337 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText) 338 { 339 RefPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText, 340 m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces); 341 342 applyCommandToComposite(command, endingSelection()); 343 344 typingAddedToOpenCommand(InsertText); 345 } 346 347 void TypingCommand::insertLineBreak() 348 { 349 if (!canAppendNewLineFeedToSelection(endingSelection())) 350 return; 351 352 applyCommandToComposite(InsertLineBreakCommand::create(document())); 353 typingAddedToOpenCommand(InsertLineBreak); 354 } 355 356 void TypingCommand::insertParagraphSeparator() 357 { 358 if (!canAppendNewLineFeedToSelection(endingSelection())) 359 return; 360 361 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); 362 typingAddedToOpenCommand(InsertParagraphSeparator); 363 } 364 365 void TypingCommand::insertParagraphSeparatorInQuotedContent() 366 { 367 // If the selection starts inside a table, just insert the paragraph separator normally 368 // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline 369 if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) { 370 insertParagraphSeparator(); 371 return; 372 } 373 374 applyCommandToComposite(BreakBlockquoteCommand::create(document())); 375 typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent); 376 } 377 378 bool TypingCommand::makeEditableRootEmpty() 379 { 380 Element* root = endingSelection().rootEditableElement(); 381 if (!root || !root->firstChild()) 382 return false; 383 384 if (root->firstChild() == root->lastChild() && root->firstElementChild() && root->firstElementChild()->hasTagName(brTag)) { 385 // If there is a single child and it could be a placeholder, leave it alone. 386 if (root->renderer() && root->renderer()->isRenderBlockFlow()) 387 return false; 388 } 389 390 while (Node* child = root->firstChild()) 391 removeNode(child); 392 393 addBlockPlaceholderIfNeeded(root); 394 setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional())); 395 396 return true; 397 } 398 399 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) 400 { 401 Frame* frame = document().frame(); 402 if (!frame) 403 return; 404 405 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false); 406 407 VisibleSelection selectionToDelete; 408 VisibleSelection selectionAfterUndo; 409 410 switch (endingSelection().selectionType()) { 411 case RangeSelection: 412 selectionToDelete = endingSelection(); 413 selectionAfterUndo = selectionToDelete; 414 break; 415 case CaretSelection: { 416 // After breaking out of an empty mail blockquote, we still want continue with the deletion 417 // so actual content will get deleted, and not just the quote style. 418 if (breakOutOfEmptyMailBlockquotedParagraph()) 419 typingAddedToOpenCommand(DeleteKey); 420 421 m_smartDelete = false; 422 423 FrameSelection selection; 424 selection.setSelection(endingSelection()); 425 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); 426 if (killRing && selection.isCaret() && granularity != CharacterGranularity) 427 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity); 428 429 if (endingSelection().visibleStart().previous(CannotCrossEditingBoundary).isNull()) { 430 // When the caret is at the start of the editable area in an empty list item, break out of the list item. 431 if (breakOutOfEmptyListItem()) { 432 typingAddedToOpenCommand(DeleteKey); 433 return; 434 } 435 // When there are no visible positions in the editing root, delete its entire contents. 436 if (endingSelection().visibleStart().next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) { 437 typingAddedToOpenCommand(DeleteKey); 438 return; 439 } 440 } 441 442 VisiblePosition visibleStart(endingSelection().visibleStart()); 443 // If we have a caret selection at the beginning of a cell, we have nothing to do. 444 Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell); 445 if (enclosingTableCell && visibleStart == firstPositionInNode(enclosingTableCell)) 446 return; 447 448 // If the caret is at the start of a paragraph after a table, move content into the last table cell. 449 if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) { 450 // Unless the caret is just before a table. We don't want to move a table into the last table cell. 451 if (isLastPositionBeforeTable(visibleStart)) 452 return; 453 // Extend the selection backward into the last cell, then deletion will handle the move. 454 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); 455 // If the caret is just after a table, select the table and don't delete anything. 456 } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { 457 setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional())); 458 typingAddedToOpenCommand(DeleteKey); 459 return; 460 } 461 462 selectionToDelete = selection.selection(); 463 464 if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode() 465 && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) { 466 // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions. 467 selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion)); 468 } 469 470 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) 471 selectionAfterUndo = selectionToDelete; 472 else 473 // It's a little tricky to compute what the starting selection would have been in the original document. 474 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on 475 // the current state of the document and we'll get the wrong result. 476 selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent()); 477 break; 478 } 479 case NoSelection: 480 ASSERT_NOT_REACHED(); 481 break; 482 } 483 484 ASSERT(!selectionToDelete.isNone()); 485 if (selectionToDelete.isNone()) 486 return; 487 488 if (selectionToDelete.isCaret()) 489 return; 490 491 if (killRing) 492 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false); 493 // On Mac, make undo select everything that has been deleted, unless an undo will undo more than just this deletion. 494 // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete 495 // more text than you insert. In that case all of the text that was around originally should be selected. 496 if (frame->editor().behavior().shouldUndoOfDeleteSelectText() && m_openedByBackwardDelete) 497 setStartingSelection(selectionAfterUndo); 498 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); 499 setSmartDelete(false); 500 typingAddedToOpenCommand(DeleteKey); 501 } 502 503 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) 504 { 505 Frame* frame = document().frame(); 506 if (!frame) 507 return; 508 509 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false); 510 511 VisibleSelection selectionToDelete; 512 VisibleSelection selectionAfterUndo; 513 514 switch (endingSelection().selectionType()) { 515 case RangeSelection: 516 selectionToDelete = endingSelection(); 517 selectionAfterUndo = selectionToDelete; 518 break; 519 case CaretSelection: { 520 m_smartDelete = false; 521 522 // Handle delete at beginning-of-block case. 523 // Do nothing in the case that the caret is at the start of a 524 // root editable element or at the start of a document. 525 FrameSelection selection; 526 selection.setSelection(endingSelection()); 527 selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity); 528 if (killRing && selection.isCaret() && granularity != CharacterGranularity) 529 selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); 530 531 Position downstreamEnd = endingSelection().end().downstream(); 532 VisiblePosition visibleEnd = endingSelection().visibleEnd(); 533 Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell); 534 if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell)) 535 return; 536 if (visibleEnd == endOfParagraph(visibleEnd)) 537 downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream(); 538 // When deleting tables: Select the table first, then perform the deletion 539 if (isRenderedTable(downstreamEnd.containerNode()) && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) { 540 setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional())); 541 typingAddedToOpenCommand(ForwardDeleteKey); 542 return; 543 } 544 545 // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any) 546 if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd())) 547 selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); 548 549 selectionToDelete = selection.selection(); 550 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) 551 selectionAfterUndo = selectionToDelete; 552 else { 553 // It's a little tricky to compute what the starting selection would have been in the original document. 554 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on 555 // the current state of the document and we'll get the wrong result. 556 Position extent = startingSelection().end(); 557 if (extent.containerNode() != selectionToDelete.end().containerNode()) 558 extent = selectionToDelete.extent(); 559 else { 560 int extraCharacters; 561 if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode()) 562 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode(); 563 else 564 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode(); 565 extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor); 566 } 567 selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); 568 } 569 break; 570 } 571 case NoSelection: 572 ASSERT_NOT_REACHED(); 573 break; 574 } 575 576 ASSERT(!selectionToDelete.isNone()); 577 if (selectionToDelete.isNone()) 578 return; 579 580 if (selectionToDelete.isCaret()) 581 return; 582 583 if (killRing) 584 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false); 585 // Make undo select what was deleted on Mac alone 586 if (frame->editor().behavior().shouldUndoOfDeleteSelectText()) 587 setStartingSelection(selectionAfterUndo); 588 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); 589 setSmartDelete(false); 590 typingAddedToOpenCommand(ForwardDeleteKey); 591 } 592 593 void TypingCommand::deleteSelection(bool smartDelete) 594 { 595 CompositeEditCommand::deleteSelection(smartDelete); 596 typingAddedToOpenCommand(DeleteSelection); 597 } 598 599 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType) 600 { 601 switch (commandType) { 602 case DeleteSelection: 603 case DeleteKey: 604 case ForwardDeleteKey: 605 case InsertParagraphSeparator: 606 case InsertLineBreak: 607 m_preservesTypingStyle = true; 608 return; 609 case InsertParagraphSeparatorInQuotedContent: 610 case InsertText: 611 m_preservesTypingStyle = false; 612 return; 613 } 614 ASSERT_NOT_REACHED(); 615 m_preservesTypingStyle = false; 616 } 617 618 bool TypingCommand::isTypingCommand() const 619 { 620 return true; 621 } 622 623 } // namespace WebCore 624