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
canAppendNewLineFeed(const VisibleSelection & selection)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
TypingCommand(Document * document,ETypingCommand commandType,const String & textToInsert,Options options,TextGranularity granularity,TextCompositionType compositionType)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
deleteSelection(Document * document,Options options)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
deleteKeyPressed(Document * document,Options options,TextGranularity granularity)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
forwardDeleteKeyPressed(Document * document,Options options,TextGranularity granularity)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
updateSelectionIfDifferentFromCurrentSelection(TypingCommand * typingCommand,Frame * frame)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
insertText(Document * document,const String & text,Options options,TextCompositionType composition)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.
insertText(Document * document,const String & text,const VisibleSelection & selectionForInsertion,Options options,TextCompositionType compositionType)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
insertLineBreak(Document * document,Options options)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
insertParagraphSeparatorInQuotedContent(Document * document)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
insertParagraphSeparator(Document * document,Options options)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
isOpenForMoreTypingCommand(const EditCommand * cmd)270 bool TypingCommand::isOpenForMoreTypingCommand(const EditCommand* cmd)
271 {
272 return cmd && cmd->isTypingCommand() && static_cast<const TypingCommand*>(cmd)->isOpenForMoreTyping();
273 }
274
closeTyping(EditCommand * cmd)275 void TypingCommand::closeTyping(EditCommand* cmd)
276 {
277 if (isOpenForMoreTypingCommand(cmd))
278 static_cast<TypingCommand*>(cmd)->closeTyping();
279 }
280
doApply()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
editingAction() const317 EditAction TypingCommand::editingAction() const
318 {
319 return EditActionTyping;
320 }
321
markMisspellingsAfterTyping(ETypingCommand commandType)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
typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)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
insertText(const String & text,bool selectInsertedText)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
insertTextRunWithoutNewlines(const String & text,bool selectInsertedText)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
insertLineBreak()416 void TypingCommand::insertLineBreak()
417 {
418 if (!canAppendNewLineFeed(endingSelection()))
419 return;
420
421 applyCommandToComposite(InsertLineBreakCommand::create(document()));
422 typingAddedToOpenCommand(InsertLineBreak);
423 }
424
insertParagraphSeparator()425 void TypingCommand::insertParagraphSeparator()
426 {
427 if (!canAppendNewLineFeed(endingSelection()))
428 return;
429
430 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
431 typingAddedToOpenCommand(InsertParagraphSeparator);
432 }
433
insertParagraphSeparatorInQuotedContent()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
makeEditableRootEmpty()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
deleteKeyPressed(TextGranularity granularity,bool killRing)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
forwardDeleteKeyPressed(TextGranularity granularity,bool killRing)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
deleteSelection(bool smartDelete)648 void TypingCommand::deleteSelection(bool smartDelete)
649 {
650 CompositeEditCommand::deleteSelection(smartDelete);
651 typingAddedToOpenCommand(DeleteSelection);
652 }
653
updatePreservesTypingStyle(ETypingCommand commandType)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
isTypingCommand() const673 bool TypingCommand::isTypingCommand() const
674 {
675 return true;
676 }
677
678 } // namespace WebCore
679