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