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 47namespace WebCore { 48 49using namespace HTMLNames; 50 51static 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 63TypingCommand::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 80void 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 101void 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 120void 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 140void 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 151void 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. 165void 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 218void 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 236void 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 252void 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 270bool TypingCommand::isOpenForMoreTypingCommand(const EditCommand* cmd) 271{ 272 return cmd && cmd->isTypingCommand() && static_cast<const TypingCommand*>(cmd)->isOpenForMoreTyping(); 273} 274 275void TypingCommand::closeTyping(EditCommand* cmd) 276{ 277 if (isOpenForMoreTypingCommand(cmd)) 278 static_cast<TypingCommand*>(cmd)->closeTyping(); 279} 280 281void 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 317EditAction TypingCommand::editingAction() const 318{ 319 return EditActionTyping; 320} 321 322void 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 355void 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 371void 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 395void 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 416void TypingCommand::insertLineBreak() 417{ 418 if (!canAppendNewLineFeed(endingSelection())) 419 return; 420 421 applyCommandToComposite(InsertLineBreakCommand::create(document())); 422 typingAddedToOpenCommand(InsertLineBreak); 423} 424 425void TypingCommand::insertParagraphSeparator() 426{ 427 if (!canAppendNewLineFeed(endingSelection())) 428 return; 429 430 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); 431 typingAddedToOpenCommand(InsertParagraphSeparator); 432} 433 434void 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 447bool 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 468void 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 566void 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 648void TypingCommand::deleteSelection(bool smartDelete) 649{ 650 CompositeEditCommand::deleteSelection(smartDelete); 651 typingAddedToOpenCommand(DeleteSelection); 652} 653 654void 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 673bool TypingCommand::isTypingCommand() const 674{ 675 return true; 676} 677 678} // namespace WebCore 679