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/page/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 ASSERT(document); 96 97 Frame* frame = document->frame(); 98 ASSERT(frame); 99 100 if (!frame->selection()->isRange()) 101 return; 102 103 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 104 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 105 lastTypingCommand->deleteSelection(options & SmartDelete); 106 return; 107 } 108 109 TypingCommand::create(document, DeleteSelection, "", options)->apply(); 110 } 111 112 void TypingCommand::deleteKeyPressed(Document *document, Options options, TextGranularity granularity) 113 { 114 ASSERT(document); 115 if (granularity == CharacterGranularity) { 116 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document->frame())) { 117 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), document->frame()); 118 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 119 lastTypingCommand->deleteKeyPressed(granularity, options & KillRing); 120 return; 121 } 122 } 123 124 TypingCommand::create(document, DeleteKey, "", options, granularity)->apply(); 125 } 126 127 void TypingCommand::forwardDeleteKeyPressed(Document *document, Options options, TextGranularity granularity) 128 { 129 // FIXME: Forward delete in TextEdit appears to open and close a new typing command. 130 ASSERT(document); 131 Frame* frame = document->frame(); 132 if (granularity == CharacterGranularity) { 133 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 134 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame); 135 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 136 lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing); 137 return; 138 } 139 } 140 141 TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply(); 142 } 143 144 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame) 145 { 146 ASSERT(frame); 147 VisibleSelection currentSelection = frame->selection()->selection(); 148 if (currentSelection == typingCommand->endingSelection()) 149 return; 150 151 typingCommand->setStartingSelection(currentSelection); 152 typingCommand->setEndingSelection(currentSelection); 153 } 154 155 void TypingCommand::insertText(Document* document, const String& text, Options options, TextCompositionType composition) 156 { 157 ASSERT(document); 158 159 Frame* frame = document->frame(); 160 ASSERT(frame); 161 162 if (!text.isEmpty()) 163 document->frame()->editor()->updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0])); 164 165 insertText(document, text, frame->selection()->selection(), options, composition); 166 } 167 168 // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to FrameSelection's current selection. 169 void TypingCommand::insertText(Document* document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType) 170 { 171 ASSERT(document); 172 173 RefPtr<Frame> frame = document->frame(); 174 ASSERT(frame); 175 176 VisibleSelection currentSelection = frame->selection()->selection(); 177 178 String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate); 179 180 // Set the starting and ending selection appropriately if we are using a selection 181 // that is different from the current selection. In the future, we should change EditCommand 182 // to deal with custom selections in a general way that can be used by all of the commands. 183 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame.get())) { 184 if (lastTypingCommand->endingSelection() != selectionForInsertion) { 185 lastTypingCommand->setStartingSelection(selectionForInsertion); 186 lastTypingCommand->setEndingSelection(selectionForInsertion); 187 } 188 189 lastTypingCommand->setCompositionType(compositionType); 190 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 191 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 192 lastTypingCommand->insertText(newText, options & SelectInsertedText); 193 return; 194 } 195 196 RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType); 197 applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection); 198 } 199 200 void TypingCommand::insertLineBreak(Document *document, Options options) 201 { 202 ASSERT(document); 203 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document->frame())) { 204 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 205 lastTypingCommand->insertLineBreak(); 206 return; 207 } 208 209 applyCommand(TypingCommand::create(document, InsertLineBreak, "", options)); 210 } 211 212 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document *document) 213 { 214 ASSERT(document); 215 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document->frame())) { 216 lastTypingCommand->insertParagraphSeparatorInQuotedContent(); 217 return; 218 } 219 220 applyCommand(TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent)); 221 } 222 223 void TypingCommand::insertParagraphSeparator(Document *document, Options options) 224 { 225 ASSERT(document); 226 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document->frame())) { 227 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 228 lastTypingCommand->insertParagraphSeparator(); 229 return; 230 } 231 232 applyCommand(TypingCommand::create(document, InsertParagraphSeparator, "", options)); 233 } 234 235 PassRefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(Frame* frame) 236 { 237 ASSERT(frame); 238 239 RefPtr<CompositeEditCommand> lastEditCommand = frame->editor()->lastEditCommand(); 240 if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping()) 241 return 0; 242 243 return static_cast<TypingCommand*>(lastEditCommand.get()); 244 } 245 246 void TypingCommand::closeTyping(Frame* frame) 247 { 248 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) 249 lastTypingCommand->closeTyping(); 250 } 251 252 void TypingCommand::doApply() 253 { 254 if (!endingSelection().isNonOrphanedCaretOrRange()) 255 return; 256 257 if (m_commandType == DeleteKey) 258 if (m_commands.isEmpty()) 259 m_openedByBackwardDelete = true; 260 261 switch (m_commandType) { 262 case DeleteSelection: 263 deleteSelection(m_smartDelete); 264 return; 265 case DeleteKey: 266 deleteKeyPressed(m_granularity, m_killRing); 267 return; 268 case ForwardDeleteKey: 269 forwardDeleteKeyPressed(m_granularity, m_killRing); 270 return; 271 case InsertLineBreak: 272 insertLineBreak(); 273 return; 274 case InsertParagraphSeparator: 275 insertParagraphSeparator(); 276 return; 277 case InsertParagraphSeparatorInQuotedContent: 278 insertParagraphSeparatorInQuotedContent(); 279 return; 280 case InsertText: 281 insertText(m_textToInsert, m_selectInsertedText); 282 return; 283 } 284 285 ASSERT_NOT_REACHED(); 286 } 287 288 EditAction TypingCommand::editingAction() const 289 { 290 return EditActionTyping; 291 } 292 293 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) 294 { 295 Frame* frame = document()->frame(); 296 if (!frame) 297 return; 298 299 if (!frame->editor()->isContinuousSpellCheckingEnabled()) 300 return; 301 302 frame->editor()->spellChecker()->cancelCheck(); 303 304 // Take a look at the selection that results after typing and determine whether we need to spellcheck. 305 // Since the word containing the current selection is never marked, this does a check to 306 // see if typing made a new word that is not in the current selection. Basically, you 307 // get this by being at the end of a word and typing a space. 308 VisiblePosition start(endingSelection().start(), endingSelection().affinity()); 309 VisiblePosition previous = start.previous(); 310 if (previous.isNotNull()) { 311 VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); 312 VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); 313 if (p1 != p2) 314 frame->editor()->markMisspellingsAfterTypingToWord(p1, endingSelection()); 315 } 316 } 317 318 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping) 319 { 320 Frame* frame = document()->frame(); 321 if (!frame) 322 return; 323 324 updatePreservesTypingStyle(commandTypeForAddedTyping); 325 326 // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled. 327 markMisspellingsAfterTyping(commandTypeForAddedTyping); 328 frame->editor()->appliedEditing(this); 329 } 330 331 void TypingCommand::insertText(const String &text, bool selectInsertedText) 332 { 333 // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved. 334 // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending 335 // an existing selection; at the moment they can either put the caret after what's inserted or 336 // select what's inserted, but there's no way to "extend selection" to include both an old selection 337 // that ends just before where we want to insert text and the newly inserted text. 338 TypingCommandLineOperation operation(this, selectInsertedText, text); 339 forEachLineInString(text, operation); 340 } 341 342 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText) 343 { 344 RefPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText, 345 m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces); 346 347 applyCommandToComposite(command, endingSelection()); 348 349 typingAddedToOpenCommand(InsertText); 350 } 351 352 void TypingCommand::insertLineBreak() 353 { 354 if (!canAppendNewLineFeedToSelection(endingSelection())) 355 return; 356 357 applyCommandToComposite(InsertLineBreakCommand::create(document())); 358 typingAddedToOpenCommand(InsertLineBreak); 359 } 360 361 void TypingCommand::insertParagraphSeparator() 362 { 363 if (!canAppendNewLineFeedToSelection(endingSelection())) 364 return; 365 366 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); 367 typingAddedToOpenCommand(InsertParagraphSeparator); 368 } 369 370 void TypingCommand::insertParagraphSeparatorInQuotedContent() 371 { 372 // If the selection starts inside a table, just insert the paragraph separator normally 373 // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline 374 if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) { 375 insertParagraphSeparator(); 376 return; 377 } 378 379 applyCommandToComposite(BreakBlockquoteCommand::create(document())); 380 typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent); 381 } 382 383 bool TypingCommand::makeEditableRootEmpty() 384 { 385 Element* root = endingSelection().rootEditableElement(); 386 if (!root || !root->firstChild()) 387 return false; 388 389 if (root->firstChild() == root->lastChild() && root->firstElementChild() && root->firstElementChild()->hasTagName(brTag)) { 390 // If there is a single child and it could be a placeholder, leave it alone. 391 if (root->renderer() && root->renderer()->isBlockFlow()) 392 return false; 393 } 394 395 while (Node* child = root->firstChild()) 396 removeNode(child); 397 398 addBlockPlaceholderIfNeeded(root); 399 setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional())); 400 401 return true; 402 } 403 404 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) 405 { 406 Frame* frame = document()->frame(); 407 if (!frame) 408 return; 409 410 frame->editor()->updateMarkersForWordsAffectedByEditing(false); 411 412 VisibleSelection selectionToDelete; 413 VisibleSelection selectionAfterUndo; 414 415 switch (endingSelection().selectionType()) { 416 case VisibleSelection::RangeSelection: 417 selectionToDelete = endingSelection(); 418 selectionAfterUndo = selectionToDelete; 419 break; 420 case VisibleSelection::CaretSelection: { 421 // After breaking out of an empty mail blockquote, we still want continue with the deletion 422 // so actual content will get deleted, and not just the quote style. 423 if (breakOutOfEmptyMailBlockquotedParagraph()) 424 typingAddedToOpenCommand(DeleteKey); 425 426 m_smartDelete = false; 427 428 FrameSelection selection; 429 selection.setSelection(endingSelection()); 430 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); 431 if (killRing && selection.isCaret() && granularity != CharacterGranularity) 432 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity); 433 434 if (endingSelection().visibleStart().previous(CannotCrossEditingBoundary).isNull()) { 435 // When the caret is at the start of the editable area in an empty list item, break out of the list item. 436 if (breakOutOfEmptyListItem()) { 437 typingAddedToOpenCommand(DeleteKey); 438 return; 439 } 440 // When there are no visible positions in the editing root, delete its entire contents. 441 if (endingSelection().visibleStart().next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) { 442 typingAddedToOpenCommand(DeleteKey); 443 return; 444 } 445 } 446 447 VisiblePosition visibleStart(endingSelection().visibleStart()); 448 // If we have a caret selection at the beginning of a cell, we have nothing to do. 449 Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell); 450 if (enclosingTableCell && visibleStart == firstPositionInNode(enclosingTableCell)) 451 return; 452 453 // If the caret is at the start of a paragraph after a table, move content into the last table cell. 454 if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) { 455 // Unless the caret is just before a table. We don't want to move a table into the last table cell. 456 if (isLastPositionBeforeTable(visibleStart)) 457 return; 458 // Extend the selection backward into the last cell, then deletion will handle the move. 459 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); 460 // If the caret is just after a table, select the table and don't delete anything. 461 } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { 462 setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional())); 463 typingAddedToOpenCommand(DeleteKey); 464 return; 465 } 466 467 selectionToDelete = selection.selection(); 468 469 if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode() 470 && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) { 471 // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions. 472 selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion)); 473 } 474 475 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) 476 selectionAfterUndo = selectionToDelete; 477 else 478 // It's a little tricky to compute what the starting selection would have been in the original document. 479 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on 480 // the current state of the document and we'll get the wrong result. 481 selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent()); 482 break; 483 } 484 case VisibleSelection::NoSelection: 485 ASSERT_NOT_REACHED(); 486 break; 487 } 488 489 ASSERT(!selectionToDelete.isNone()); 490 if (selectionToDelete.isNone()) 491 return; 492 493 if (selectionToDelete.isCaret() || !frame->selection()->shouldDeleteSelection(selectionToDelete)) 494 return; 495 496 if (killRing) 497 frame->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); 498 // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion. 499 // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete 500 // more text than you insert. In that case all of the text that was around originally should be selected. 501 if (m_openedByBackwardDelete) 502 setStartingSelection(selectionAfterUndo); 503 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); 504 setSmartDelete(false); 505 typingAddedToOpenCommand(DeleteKey); 506 } 507 508 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) 509 { 510 Frame* frame = document()->frame(); 511 if (!frame) 512 return; 513 514 frame->editor()->updateMarkersForWordsAffectedByEditing(false); 515 516 VisibleSelection selectionToDelete; 517 VisibleSelection selectionAfterUndo; 518 519 switch (endingSelection().selectionType()) { 520 case VisibleSelection::RangeSelection: 521 selectionToDelete = endingSelection(); 522 selectionAfterUndo = selectionToDelete; 523 break; 524 case VisibleSelection::CaretSelection: { 525 m_smartDelete = false; 526 527 // Handle delete at beginning-of-block case. 528 // Do nothing in the case that the caret is at the start of a 529 // root editable element or at the start of a document. 530 FrameSelection selection; 531 selection.setSelection(endingSelection()); 532 selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity); 533 if (killRing && selection.isCaret() && granularity != CharacterGranularity) 534 selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); 535 536 Position downstreamEnd = endingSelection().end().downstream(); 537 VisiblePosition visibleEnd = endingSelection().visibleEnd(); 538 Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell); 539 if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell)) 540 return; 541 if (visibleEnd == endOfParagraph(visibleEnd)) 542 downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream(); 543 // When deleting tables: Select the table first, then perform the deletion 544 if (downstreamEnd.containerNode() && downstreamEnd.containerNode()->renderer() && downstreamEnd.containerNode()->renderer()->isTable() 545 && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) { 546 setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional())); 547 typingAddedToOpenCommand(ForwardDeleteKey); 548 return; 549 } 550 551 // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any) 552 if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd())) 553 selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); 554 555 selectionToDelete = selection.selection(); 556 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) 557 selectionAfterUndo = selectionToDelete; 558 else { 559 // It's a little tricky to compute what the starting selection would have been in the original document. 560 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on 561 // the current state of the document and we'll get the wrong result. 562 Position extent = startingSelection().end(); 563 if (extent.containerNode() != selectionToDelete.end().containerNode()) 564 extent = selectionToDelete.extent(); 565 else { 566 int extraCharacters; 567 if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode()) 568 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode(); 569 else 570 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode(); 571 extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor); 572 } 573 selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); 574 } 575 break; 576 } 577 case VisibleSelection::NoSelection: 578 ASSERT_NOT_REACHED(); 579 break; 580 } 581 582 ASSERT(!selectionToDelete.isNone()); 583 if (selectionToDelete.isNone()) 584 return; 585 586 if (selectionToDelete.isCaret() || !frame->selection()->shouldDeleteSelection(selectionToDelete)) 587 return; 588 589 if (killRing) 590 frame->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); 591 // make undo select what was deleted 592 setStartingSelection(selectionAfterUndo); 593 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); 594 setSmartDelete(false); 595 typingAddedToOpenCommand(ForwardDeleteKey); 596 } 597 598 void TypingCommand::deleteSelection(bool smartDelete) 599 { 600 CompositeEditCommand::deleteSelection(smartDelete); 601 typingAddedToOpenCommand(DeleteSelection); 602 } 603 604 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType) 605 { 606 switch (commandType) { 607 case DeleteSelection: 608 case DeleteKey: 609 case ForwardDeleteKey: 610 case InsertParagraphSeparator: 611 case InsertLineBreak: 612 m_preservesTypingStyle = true; 613 return; 614 case InsertParagraphSeparatorInQuotedContent: 615 case InsertText: 616 m_preservesTypingStyle = false; 617 return; 618 } 619 ASSERT_NOT_REACHED(); 620 m_preservesTypingStyle = false; 621 } 622 623 bool TypingCommand::isTypingCommand() const 624 { 625 return true; 626 } 627 628 } // namespace WebCore 629