// FIXME: We should merge this function with // ApplyBlockElementCommand::formatSelection void IndentOutdentCommand::outdentRegion( const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection, EditingState* editingState) { VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); if (endOfCurrentParagraph.deepEquivalent() == endOfLastParagraph.deepEquivalent()) { outdentParagraph(editingState); return; } Position originalSelectionEnd = endingSelection().end(); Position endAfterSelection = endOfParagraph(nextPositionOf(endOfLastParagraph)).deepEquivalent(); while (endOfCurrentParagraph.deepEquivalent() != endAfterSelection) { PositionWithAffinity endOfNextParagraph = endOfParagraph(nextPositionOf(endOfCurrentParagraph)) .toPositionWithAffinity(); if (endOfCurrentParagraph.deepEquivalent() == endOfLastParagraph.deepEquivalent()) { SelectionInDOMTree::Builder builder; if (originalSelectionEnd.isNotNull()) builder.collapse(originalSelectionEnd); setEndingSelection(builder.build()); } else { setEndingSelection(SelectionInDOMTree::Builder() .collapse(endOfCurrentParagraph.deepEquivalent()) .build()); } outdentParagraph(editingState); if (editingState->isAborted()) return; // outdentParagraph could move more than one paragraph if the paragraph // is in a list item. As a result, endAfterSelection and endOfNextParagraph // could refer to positions no longer in the document. if (endAfterSelection.isNotNull() && !endAfterSelection.isConnected()) break; document().updateStyleAndLayoutIgnorePendingStylesheets(); if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.isConnected()) { endOfCurrentParagraph = createVisiblePosition(endingSelection().end()); endOfNextParagraph = endOfParagraph(nextPositionOf(endOfCurrentParagraph)) .toPositionWithAffinity(); } endOfCurrentParagraph = createVisiblePosition(endOfNextParagraph); } }
void InsertListCommand::doApply(EditingState* editingState) { // Only entry points are Editor::Command::execute and // IndentOutdentCommand::outdentParagraph, both of which ensure clean layout. DCHECK(!document().needsLayoutTreeUpdate()); if (!endingSelection().isNonOrphanedCaretOrRange()) return; if (!endingSelection().rootEditableElement()) return; VisiblePosition visibleEnd = endingSelection().visibleEnd(); VisiblePosition visibleStart = endingSelection().visibleStart(); // When a selection ends at the start of a paragraph, we rarely paint // the selection gap before that paragraph, because there often is no gap. // In a case like this, it's not obvious to the user that the selection // ends "inside" that paragraph, so it would be confusing if // InsertUn{Ordered}List operated on that paragraph. // FIXME: We paint the gap before some paragraphs that are indented with left // margin/padding, but not others. We should make the gap painting more // consistent and then use a left margin/padding rule here. if (visibleEnd.deepEquivalent() != visibleStart.deepEquivalent() && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) { setEndingSelection(createVisibleSelection( visibleStart, previousPositionOf(visibleEnd, CannotCrossEditingBoundary), endingSelection().isDirectional())); if (!endingSelection().rootEditableElement()) return; } const HTMLQualifiedName& listTag = (m_type == OrderedList) ? olTag : ulTag; if (endingSelection().isRange()) { bool forceListCreation = false; VisibleSelection selection = selectionForParagraphIteration(endingSelection()); DCHECK(selection.isRange()); VisiblePosition visibleStartOfSelection = selection.visibleStart(); VisiblePosition visibleEndOfSelection = selection.visibleEnd(); PositionWithAffinity startOfSelection = visibleStartOfSelection.toPositionWithAffinity(); PositionWithAffinity endOfSelection = visibleEndOfSelection.toPositionWithAffinity(); Position startOfLastParagraph = startOfParagraph(visibleEndOfSelection, CanSkipOverEditingBoundary) .deepEquivalent(); Range* currentSelection = firstRangeOf(endingSelection()); ContainerNode* scopeForStartOfSelection = nullptr; ContainerNode* scopeForEndOfSelection = nullptr; // FIXME: This is an inefficient way to keep selection alive because // indexForVisiblePosition walks from the beginning of the document to the // visibleEndOfSelection everytime this code is executed. But not using // index is hard because there are so many ways we can lose selection inside // doApplyForSingleParagraph. int indexForStartOfSelection = indexForVisiblePosition( visibleStartOfSelection, scopeForStartOfSelection); int indexForEndOfSelection = indexForVisiblePosition(visibleEndOfSelection, scopeForEndOfSelection); if (startOfParagraph(visibleStartOfSelection, CanSkipOverEditingBoundary) .deepEquivalent() != startOfLastParagraph) { forceListCreation = !selectionHasListOfType(selection, listTag); VisiblePosition startOfCurrentParagraph = visibleStartOfSelection; while (inSameTreeAndOrdered(startOfCurrentParagraph.deepEquivalent(), startOfLastParagraph) && !inSameParagraph(startOfCurrentParagraph, createVisiblePosition(startOfLastParagraph), CanCrossEditingBoundary)) { // doApply() may operate on and remove the last paragraph of the // selection from the document if it's in the same list item as // startOfCurrentParagraph. Return early to avoid an infinite loop and // because there is no more work to be done. // FIXME(<rdar://problem/5983974>): The endingSelection() may be // incorrect here. Compute the new location of visibleEndOfSelection // and use it as the end of the new selection. if (!startOfLastParagraph.isConnected()) return; setEndingSelection(startOfCurrentParagraph); // Save and restore visibleEndOfSelection and startOfLastParagraph when // necessary since moveParagraph and movePragraphWithClones can remove // nodes. bool singleParagraphResult = doApplyForSingleParagraph( forceListCreation, listTag, *currentSelection, editingState); if (editingState->isAborted()) return; if (!singleParagraphResult) break; document().updateStyleAndLayoutIgnorePendingStylesheets(); // Make |visibleEndOfSelection| valid again. if (!endOfSelection.isConnected() || !startOfLastParagraph.isConnected()) { visibleEndOfSelection = visiblePositionForIndex( indexForEndOfSelection, scopeForEndOfSelection); endOfSelection = visibleEndOfSelection.toPositionWithAffinity(); // If visibleEndOfSelection is null, then some contents have been // deleted from the document. This should never happen and if it did, // exit early immediately because we've lost the loop invariant. DCHECK(visibleEndOfSelection.isNotNull()); if (visibleEndOfSelection.isNull() || !rootEditableElementOf(visibleEndOfSelection)) return; startOfLastParagraph = startOfParagraph(visibleEndOfSelection, CanSkipOverEditingBoundary) .deepEquivalent(); } else { visibleEndOfSelection = createVisiblePosition(endOfSelection); } startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart()); } setEndingSelection(visibleEndOfSelection); } doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection, editingState); if (editingState->isAborted()) return; document().updateStyleAndLayoutIgnorePendingStylesheets(); // Fetch the end of the selection, for the reason mentioned above. if (!endOfSelection.isConnected()) { visibleEndOfSelection = visiblePositionForIndex(indexForEndOfSelection, scopeForEndOfSelection); if (visibleEndOfSelection.isNull()) return; } else { visibleEndOfSelection = createVisiblePosition(endOfSelection); } if (!startOfSelection.isConnected()) { visibleStartOfSelection = visiblePositionForIndex( indexForStartOfSelection, scopeForStartOfSelection); if (visibleStartOfSelection.isNull()) return; } else { visibleStartOfSelection = createVisiblePosition(startOfSelection); } setEndingSelection( createVisibleSelection(visibleStartOfSelection, visibleEndOfSelection, endingSelection().isDirectional())); return; } DCHECK(firstRangeOf(endingSelection())); doApplyForSingleParagraph(false, listTag, *firstRangeOf(endingSelection()), editingState); }