void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos) { // It is only important to set a style to apply later if we're at the boundaries of // a paragraph. Otherwise, content that is moved as part of the work of the command // will lend their styles to the new paragraph without any extra work needed. VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY); if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos)) return; ASSERT(pos.isNotNull()); m_style = EditingStyle::create(pos); m_style->mergeTypingStyle(pos.document()); }
static bool needInterchangeNewlineAfter(const VisiblePosition& v) { return isEndOfParagraph(v) && isStartOfParagraph(v.next()); }
void InsertTextCommand::input(const String& text, bool selectInsertedText, RebalanceType whitespaceRebalance) { ASSERT(text.find('\n') == notFound); if (!endingSelection().isNonOrphanedCaretOrRange()) return; // Delete the current selection. // FIXME: This delete operation blows away the typing style. if (endingSelection().isRange()) { if (performTrivialReplace(text, selectInsertedText)) return; deleteSelection(false, true, true, false); } Position startPosition(endingSelection().start()); Position placeholder; // We want to remove preserved newlines and brs that will collapse (and thus become unnecessary) when content // is inserted just before them. // FIXME: We shouldn't really have to do this, but removing placeholders is a workaround for 9661. // If the caret is just before a placeholder, downstream will normalize the caret to it. Position downstream(startPosition.downstream()); if (lineBreakExistsAtPosition(downstream)) { // FIXME: This doesn't handle placeholders at the end of anonymous blocks. VisiblePosition caret(startPosition); if (isEndOfBlock(caret) && isStartOfParagraph(caret)) placeholder = downstream; // Don't remove the placeholder yet, otherwise the block we're inserting into would collapse before // we get a chance to insert into it. We check for a placeholder now, though, because doing so requires // the creation of a VisiblePosition, and if we did that post-insertion it would force a layout. } // Insert the character at the leftmost candidate. startPosition = startPosition.upstream(); // It is possible for the node that contains startPosition to contain only unrendered whitespace, // and so deleteInsignificantText could remove it. Save the position before the node in case that happens. Position positionBeforeStartNode(positionInParentBeforeNode(startPosition.containerNode())); deleteInsignificantText(startPosition.upstream(), startPosition.downstream()); if (!startPosition.anchorNode()->inDocument()) startPosition = positionBeforeStartNode; if (!startPosition.isCandidate()) startPosition = startPosition.downstream(); startPosition = positionAvoidingSpecialElementBoundary(startPosition); Position endPosition; if (text == "\t") { endPosition = insertTab(startPosition); startPosition = endPosition.previous(); if (placeholder.isNotNull()) removePlaceholderAt(placeholder); } else { // Make sure the document is set up to receive text startPosition = positionInsideTextNode(startPosition); ASSERT(startPosition.anchorType() == Position::PositionIsOffsetInAnchor); ASSERT(startPosition.containerNode()); ASSERT(startPosition.containerNode()->isTextNode()); if (placeholder.isNotNull()) removePlaceholderAt(placeholder); RefPtr<Text> textNode = static_cast<Text*>(startPosition.containerNode()); const unsigned offset = startPosition.offsetInContainerNode(); insertTextIntoNode(textNode, offset, text); endPosition = Position(textNode, offset + text.length()); if (whitespaceRebalance == RebalanceLeadingAndTrailingWhitespaces) { // The insertion may require adjusting adjacent whitespace, if it is present. rebalanceWhitespaceAt(endPosition); // Rebalancing on both sides isn't necessary if we've inserted only spaces. if (!shouldRebalanceLeadingWhitespaceFor(text)) rebalanceWhitespaceAt(startPosition); } else { ASSERT(whitespaceRebalance == RebalanceAllWhitespaces); if (canRebalance(startPosition) && canRebalance(endPosition)) rebalanceWhitespaceOnTextSubstring(textNode, startPosition.offsetInContainerNode(), endPosition.offsetInContainerNode()); } } // We could have inserted a part of composed character sequence, // so we are basically treating ending selection as a range to avoid validation. // <http://bugs.webkit.org/show_bug.cgi?id=15781> VisibleSelection forcedEndingSelection; forcedEndingSelection.setWithoutValidation(startPosition, endPosition); setEndingSelection(forcedEndingSelection); // Handle the case where there is a typing style. if (RefPtr<EditingStyle> typingStyle = document()->frame()->selection()->typingStyle()) { typingStyle->prepareToApplyAt(endPosition, EditingStyle::PreserveWritingDirection); if (!typingStyle->isEmpty()) applyStyle(typingStyle.get()); } if (!selectInsertedText) setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity())); }
void TypingCommand::deleteKeyPressed(TextGranularity granularity) { Selection selectionToDelete; Selection selectionAfterUndo; switch (endingSelection().state()) { case Selection::RANGE: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case Selection::CARET: { m_smartDelete = false; SelectionController selection; selection.setSelection(endingSelection()); selection.modify(SelectionController::EXTEND, SelectionController::BACKWARD, granularity); // When the caret is at the start of the editable area in an empty list item, break out of the list item. if (endingSelection().visibleStart().previous(true).isNull()) { if (breakOutOfEmptyListItem()) { typingAddedToOpenCommand(); return; } } VisiblePosition visibleStart(endingSelection().visibleStart()); // If the caret is at the start of a paragraph after a table, move content into the last table cell. if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(true))) { // Unless the caret is just before a table. We don't want to move a table into the last table cell. if (isLastPositionBeforeTable(visibleStart)) return; // Extend the selection backward into the last cell, then deletion will handle the move. selection.modify(SelectionController::EXTEND, SelectionController::BACKWARD, granularity); // If the caret is just after a table, select the table and don't delete anything. } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { setEndingSelection(Selection(Position(table, 0), endingSelection().start(), DOWNSTREAM)); typingAddedToOpenCommand(); return; } selectionToDelete = selection.selection(); if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) selectionAfterUndo = selectionToDelete; else // It's a little tricky to compute what the starting selection would have been in the original document. // We can't let the Selection class's validation kick in or it'll adjust for us based on // the current state of the document and we'll get the wrong result. selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent()); break; } case Selection::NONE: ASSERT_NOT_REACHED(); break; } if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(selectionToDelete)) { // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion. // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete // more text than you insert. In that case all of the text that was around originally should be selected. if (m_openedByBackwardDelete) setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); setSmartDelete(false); typingAddedToOpenCommand(); } }
void InsertTextCommand::input(const String& text, bool selectInsertedText) { ASSERT(text.find('\n') == notFound); if (!endingSelection().isNonOrphanedCaretOrRange()) return; // Delete the current selection. // FIXME: This delete operation blows away the typing style. if (endingSelection().isRange()) { if (performTrivialReplace(text, selectInsertedText)) return; deleteSelection(false, true, true, false); } Position startPosition(endingSelection().start()); Position placeholder; // We want to remove preserved newlines and brs that will collapse (and thus become unnecessary) when content // is inserted just before them. // FIXME: We shouldn't really have to do this, but removing placeholders is a workaround for 9661. // If the caret is just before a placeholder, downstream will normalize the caret to it. Position downstream(startPosition.downstream()); if (lineBreakExistsAtPosition(downstream)) { // FIXME: This doesn't handle placeholders at the end of anonymous blocks. VisiblePosition caret(startPosition); if (isEndOfBlock(caret) && isStartOfParagraph(caret)) placeholder = downstream; // Don't remove the placeholder yet, otherwise the block we're inserting into would collapse before // we get a chance to insert into it. We check for a placeholder now, though, because doing so requires // the creation of a VisiblePosition, and if we did that post-insertion it would force a layout. } // Insert the character at the leftmost candidate. startPosition = startPosition.upstream(); // It is possible for the node that contains startPosition to contain only unrendered whitespace, // and so deleteInsignificantText could remove it. Save the position before the node in case that happens. Position positionBeforeStartNode(positionInParentBeforeNode(startPosition.node())); deleteInsignificantText(startPosition.upstream(), startPosition.downstream()); if (!startPosition.node()->inDocument()) startPosition = positionBeforeStartNode; if (!startPosition.isCandidate()) startPosition = startPosition.downstream(); startPosition = positionAvoidingSpecialElementBoundary(startPosition); Position endPosition; if (text == "\t") { endPosition = insertTab(startPosition); startPosition = endPosition.previous(); if (placeholder.isNotNull()) removePlaceholderAt(placeholder); m_charactersAdded += 1; } else { // Make sure the document is set up to receive text startPosition = prepareForTextInsertion(startPosition); if (placeholder.isNotNull()) removePlaceholderAt(placeholder); Text *textNode = static_cast<Text *>(startPosition.node()); int offset = startPosition.deprecatedEditingOffset(); insertTextIntoNode(textNode, offset, text); endPosition = Position(textNode, offset + text.length()); // The insertion may require adjusting adjacent whitespace, if it is present. rebalanceWhitespaceAt(endPosition); // Rebalancing on both sides isn't necessary if we've inserted a space. if (text != " ") rebalanceWhitespaceAt(startPosition); m_charactersAdded += text.length(); } // We could have inserted a part of composed character sequence, // so we are basically treating ending selection as a range to avoid validation. // <http://bugs.webkit.org/show_bug.cgi?id=15781> VisibleSelection forcedEndingSelection; forcedEndingSelection.setWithoutValidation(startPosition, endPosition); setEndingSelection(forcedEndingSelection); // Handle the case where there is a typing style. CSSMutableStyleDeclaration* typingStyle = document()->frame()->selection()->typingStyle(); RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle(); RefPtr<CSSValue> unicodeBidi; RefPtr<CSSValue> direction; if (typingStyle) { unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection); } endingStyle->diff(typingStyle); if (typingStyle && unicodeBidi) { ASSERT(unicodeBidi->isPrimitiveValue()); typingStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent()); if (direction) { ASSERT(direction->isPrimitiveValue()); typingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); } } if (typingStyle && typingStyle->length()) applyStyle(typingStyle); if (!selectInsertedText) setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity())); }
void InsertTextCommand::doApply() { ASSERT(m_text.find('\n') == kNotFound); if (!endingSelection().isNonOrphanedCaretOrRange()) return; // Delete the current selection. // FIXME: This delete operation blows away the typing style. if (endingSelection().isRange()) { if (performTrivialReplace(m_text, m_selectInsertedText)) return; bool endOfSelectionWasAtStartOfBlock = isStartOfBlock(endingSelection().visibleEnd()); deleteSelection(false, true, false, false); // deleteSelection eventually makes a new endingSelection out of a Position. If that Position doesn't have // a renderer (e.g. it is on a <frameset> in the DOM), the VisibleSelection cannot be canonicalized to // anything other than NoSelection. The rest of this function requires a real endingSelection, so bail out. if (endingSelection().isNone()) return; if (endOfSelectionWasAtStartOfBlock) { if (EditingStyle* typingStyle = document().frame()->selection().typingStyle()) typingStyle->removeBlockProperties(); } } else if (document().frame()->editor().isOverwriteModeEnabled()) { if (performOverwrite(m_text, m_selectInsertedText)) return; } Position startPosition(endingSelection().start()); Position placeholder; // We want to remove preserved newlines and brs that will collapse (and thus become unnecessary) when content // is inserted just before them. // FIXME: We shouldn't really have to do this, but removing placeholders is a workaround for 9661. // If the caret is just before a placeholder, downstream will normalize the caret to it. Position downstream(startPosition.downstream()); if (lineBreakExistsAtPosition(downstream)) { // FIXME: This doesn't handle placeholders at the end of anonymous blocks. VisiblePosition caret(startPosition); if (isEndOfBlock(caret) && isStartOfParagraph(caret)) placeholder = downstream; // Don't remove the placeholder yet, otherwise the block we're inserting into would collapse before // we get a chance to insert into it. We check for a placeholder now, though, because doing so requires // the creation of a VisiblePosition, and if we did that post-insertion it would force a layout. } // Insert the character at the leftmost candidate. startPosition = startPosition.upstream(); // It is possible for the node that contains startPosition to contain only unrendered whitespace, // and so deleteInsignificantText could remove it. Save the position before the node in case that happens. ASSERT(startPosition.containerNode()); Position positionBeforeStartNode(positionInParentBeforeNode(*startPosition.containerNode())); deleteInsignificantText(startPosition, startPosition.downstream()); if (!startPosition.inDocument()) startPosition = positionBeforeStartNode; if (!startPosition.isCandidate()) startPosition = startPosition.downstream(); startPosition = positionAvoidingSpecialElementBoundary(startPosition); Position endPosition; if (m_text == "\t") { endPosition = insertTab(startPosition); startPosition = endPosition.previous(); if (placeholder.isNotNull()) removePlaceholderAt(placeholder); } else { // Make sure the document is set up to receive m_text startPosition = positionInsideTextNode(startPosition); ASSERT(startPosition.anchorType() == Position::PositionIsOffsetInAnchor); ASSERT(startPosition.containerNode()); ASSERT(startPosition.containerNode()->isTextNode()); if (placeholder.isNotNull()) removePlaceholderAt(placeholder); RefPtrWillBeRawPtr<Text> textNode = startPosition.containerText(); const unsigned offset = startPosition.offsetInContainerNode(); insertTextIntoNode(textNode, offset, m_text); endPosition = Position(textNode, offset + m_text.length()); if (m_rebalanceType == RebalanceLeadingAndTrailingWhitespaces) { // The insertion may require adjusting adjacent whitespace, if it is present. rebalanceWhitespaceAt(endPosition); // Rebalancing on both sides isn't necessary if we've inserted only spaces. if (!shouldRebalanceLeadingWhitespaceFor(m_text)) rebalanceWhitespaceAt(startPosition); } else { ASSERT(m_rebalanceType == RebalanceAllWhitespaces); if (canRebalance(startPosition) && canRebalance(endPosition)) rebalanceWhitespaceOnTextSubstring(textNode, startPosition.offsetInContainerNode(), endPosition.offsetInContainerNode()); } } setEndingSelectionWithoutValidation(startPosition, endPosition); // Handle the case where there is a typing style. if (RefPtrWillBeRawPtr<EditingStyle> typingStyle = document().frame()->selection().typingStyle()) { typingStyle->prepareToApplyAt(endPosition, EditingStyle::PreserveWritingDirection); if (!typingStyle->isEmpty()) applyStyle(typingStyle.get()); } if (!m_selectInsertedText) setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity(), endingSelection().isDirectional())); }