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