bool isLastVisiblePositionInBlock(const VisiblePosition &pos) { if (pos.isNull()) return false; VisiblePosition next = pos.next(); if (next.isNull()) return true; switch (blockRelationship(pos, next)) { case NoBlockRelationship: case SameBlockRelationship: case AncestorBlockRelationship: return false; case OtherBlockRelationship: case PeerBlockRelationship: case DescendantBlockRelationship: return true; } ASSERT_NOT_REACHED(); return false; }
void IndentOutdentCommand::indentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) { // Special case empty unsplittable elements because there's nothing to split // and there's nothing to move. Position start = startOfSelection.deepEquivalent().downstream(); if (isAtUnsplittableElement(start)) { RefPtr<Element> blockquote = createIndentBlockquoteElement(document()); insertNodeAt(blockquote, start); RefPtr<Element> placeholder = createBreakElement(document()); appendNode(placeholder, blockquote); setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM)); return; } RefPtr<Element> blockquoteForNextIndent; VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); while (endOfCurrentParagraph != endAfterSelection) { // Iterate across the selected paragraphs... VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); if (tryIndentingAsListItem(endOfCurrentParagraph)) blockquoteForNextIndent = 0; else indentIntoBlockquote(endOfCurrentParagraph, endOfNextParagraph, blockquoteForNextIndent); // indentIntoBlockquote could move more than one paragraph if the paragraph // is in a list item or a table. As a result, endAfterSelection could refer to a position // no longer in the document. if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().node()->inDocument()) break; // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node() // If somehow we did, return to prevent crashes. if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) { ASSERT_NOT_REACHED(); return; } endOfCurrentParagraph = endOfNextParagraph; } }
String DeleteSelectionCommand::originalStringForAutocorrectionAtBeginningOfSelection() { if (!m_selectionToDelete.isRange()) return String(); VisiblePosition startOfSelection = m_selectionToDelete.start(); if (!isStartOfWord(startOfSelection)) return String(); VisiblePosition nextPosition = startOfSelection.next(); if (nextPosition.isNull()) return String(); RefPtr<Range> rangeOfFirstCharacter = Range::create(document(), startOfSelection.deepEquivalent(), nextPosition.deepEquivalent()); Vector<DocumentMarker*> markers = document()->markers()->markersInRange(rangeOfFirstCharacter.get(), DocumentMarker::Autocorrected); for (size_t i = 0; i < markers.size(); ++i) { const DocumentMarker* marker = markers[i]; int startOffset = marker->startOffset(); if (startOffset == startOfSelection.deepEquivalent().offsetInContainerNode()) return marker->description(); } return String(); }
PassRefPtr<Range> Frame::rangeForPoint(const LayoutPoint& framePoint) { VisiblePosition position = visiblePositionForPoint(framePoint); if (position.isNull()) return 0; VisiblePosition previous = position.previous(); if (previous.isNotNull()) { RefPtr<Range> previousCharacterRange = makeRange(previous, position); LayoutRect rect = editor()->firstRectForRange(previousCharacterRange.get()); if (rect.contains(framePoint)) return previousCharacterRange.release(); } VisiblePosition next = position.next(); if (RefPtr<Range> nextCharacterRange = makeRange(position, next)) { LayoutRect rect = editor()->firstRectForRange(nextCharacterRange.get()); if (rect.contains(framePoint)) return nextCharacterRange.release(); } return 0; }
VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const { // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) // Related? <rdar://problem/3927736> Text selection broken in 8A336 if (visiblePos.isNull()) return VisiblePosition(); // make sure we move off of a sentence end VisiblePosition nextVisiblePos = visiblePos.next(); if (nextVisiblePos.isNull()) return VisiblePosition(); // an empty line is considered a sentence. If it's skipped, then the sentence parser will not // see this empty line. Instead, return the end position of the empty line. VisiblePosition endPosition; String lineString = plainText(makeRange(startOfLine(visiblePos), endOfLine(visiblePos)).get()); if (lineString.isEmpty()) endPosition = nextVisiblePos; else endPosition = endOfSentence(nextVisiblePos); return endPosition; }
VisiblePosition ApplyBlockElementCommand::endOfNextParagrahSplittingTextNodesIfNeeded(VisiblePosition& endOfCurrentParagraph, Position& start, Position& end) { VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); Position position = endOfNextParagraph.deepEquivalent(); RenderStyle* style = renderStyleOfEnclosingTextNode(position); if (!style) return endOfNextParagraph; RefPtrWillBeRawPtr<Text> text = position.containerText(); if (!style->preserveNewline() || !position.offsetInContainerNode() || !isNewLineAtPosition(firstPositionInNode(text.get()))) return endOfNextParagraph; // \n at the beginning of the text node immediately following the current paragraph is trimmed by moveParagraphWithClones. // If endOfNextParagraph was pointing at this same text node, endOfNextParagraph will be shifted by one paragraph. // Avoid this by splitting "\n" splitTextNode(text, 1); if (text == start.containerNode() && text->previousSibling() && text->previousSibling()->isTextNode()) { ASSERT(start.offsetInContainerNode() < position.offsetInContainerNode()); start = Position(toText(text->previousSibling()), start.offsetInContainerNode()); } if (text == end.containerNode() && text->previousSibling() && text->previousSibling()->isTextNode()) { ASSERT(end.offsetInContainerNode() < position.offsetInContainerNode()); end = Position(toText(text->previousSibling()), end.offsetInContainerNode()); } if (text == m_endOfLastParagraph.containerNode()) { if (m_endOfLastParagraph.offsetInContainerNode() < position.offsetInContainerNode()) { // We can only fix endOfLastParagraph if the previous node was still text and hasn't been modified by script. if (text->previousSibling()->isTextNode() && static_cast<unsigned>(m_endOfLastParagraph.offsetInContainerNode()) <= toText(text->previousSibling())->length()) m_endOfLastParagraph = Position(toText(text->previousSibling()), m_endOfLastParagraph.offsetInContainerNode()); } else m_endOfLastParagraph = Position(text.get(), m_endOfLastParagraph.offsetInContainerNode() - 1); } return VisiblePosition(Position(text.get(), position.offsetInContainerNode() - 1)); }
void Editor::transpose() { if (!canEdit()) return; VisibleSelection selection = m_frame.selection().selection(); if (!selection.isCaret()) return; // Make a selection that goes back one character and forward two characters. VisiblePosition caret = selection.visibleStart(); VisiblePosition next = isEndOfParagraph(caret) ? caret : caret.next(); VisiblePosition previous = next.previous(); if (next == previous) return; previous = previous.previous(); if (!inSameParagraph(next, previous)) return; RefPtr<Range> range = makeRange(previous, next); if (!range) return; VisibleSelection newSelection(range.get(), DOWNSTREAM); // Transpose the two characters. String text = plainText(range.get()); if (text.length() != 2) return; String transposed = text.right(1) + text.left(1); // Select the two characters. if (newSelection != m_frame.selection().selection()) m_frame.selection().setSelection(newSelection); // Insert the transposed characters. replaceSelectionWithText(transposed, false, false); }
Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selection) { // This function is used by range style computations to avoid bugs like: // <rdar://problem/4017641> REGRESSION (Mail): you can only bold/unbold a selection starting from end of line once // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up // with a spurious "mixed" style. VisiblePosition visiblePosition = selection.start(); if (visiblePosition.isNull()) return Position(); // if the selection is a caret, just return the position, since the style // behind us is relevant if (selection.isCaret()) return visiblePosition.deepEquivalent(); // if the selection starts just before a paragraph break, skip over it if (isEndOfParagraph(visiblePosition)) return visiblePosition.next().deepEquivalent().downstream(); // otherwise, make sure to be at the start of the first selected node, // instead of possibly at the end of the last node before the selection return visiblePosition.deepEquivalent().downstream(); }
void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity) { if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } switch (granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; //SAMSUNG - Text Selection >> Document *doc = NULL; if (m_start.anchorNode()) doc = m_start.anchorNode()->document(); if (doc && doc->settings() && doc->settings()->advancedSelectionEnabled()) { bool endOfDocument; if ( (endOfDocument = isEndOfDocument(start)) || ( (isEndOfLine(start) || isStartOfSpace(start)) && !isStartOfLine(start))) { side = LeftWordIfOnBoundary; if (!endOfDocument && isEndOfParagraph(start)) { originalEnd = start; } } m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isStartOfSpace(start)) { side = LeftWordIfOnBoundary; } if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); m_end = end.deepEquivalent(); } else { //SAMSUNG - Text Selection << if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.deprecatedNode())) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); //SAMSUNG - Text Selection >> } //SAMSUNG - Text Selection << break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfDocument(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. //SAMSUNG - Text Selection >> // Here it tries to extend the end point from current paragraph to beginning of next paragraph. So somehow it is // selecting the next paragraph also and it looks wrong when we do backward paragraph selection. // Even though user is trying to extend selection in backward direction, because of this condition it extends selection // in both directions. So commented the below code to avoid wrong backward text selection // WAS: VisiblePosition end(visibleParagraphEnd.next()); VisiblePosition end(visibleParagraphEnd); //SAMSUNG - Text Selection << if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case WebKitVisualWordGranularity: break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; }
static bool needInterchangeNewlineAfter(const VisiblePosition& v) { return isEndOfParagraph(v) && isStartOfParagraph(v.next()); }
static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) { Position pos = c.deepEquivalent(); Node *n = pos.node(); if (!n) return VisiblePosition(); Document *d = n->document(); Node *de = d->documentElement(); if (!de) return VisiblePosition(); Node *boundary = n->enclosingBlockFlowElement(); if (!boundary) return VisiblePosition(); bool isContentEditable = boundary->isContentEditable(); while (boundary && boundary != de && boundary->parentNode() && isContentEditable == boundary->parentNode()->isContentEditable()) boundary = boundary->parentNode(); RefPtr<Range> searchRange(d->createRange()); Position start(rangeCompliantEquivalent(pos)); Vector<UChar, 1024> string; unsigned prefixLength = 0; ExceptionCode ec = 0; if (requiresContextForWordBoundary(c.characterAfter())) { RefPtr<Range> backwardsScanRange(d->createRange()); backwardsScanRange->setEnd(start.node(), start.deprecatedEditingOffset(), ec); SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange.get()); while (!backwardsIterator.atEnd()) { const UChar* characters = backwardsIterator.characters(); int length = backwardsIterator.length(); int i = startOfLastWordBoundaryContext(characters, length); string.prepend(characters + i, length - i); prefixLength += length - i; if (i > 0) break; backwardsIterator.advance(); } } searchRange->selectNodeContents(boundary, ec); searchRange->setStart(start.node(), start.deprecatedEditingOffset(), ec); TextIterator it(searchRange.get(), true); unsigned next = 0; bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE; bool needMoreContext = false; while (!it.atEnd()) { // Keep asking the iterator for chunks until the search function // returns an end value not equal to the length of the string passed to it. if (!inTextSecurityMode) string.append(it.characters(), it.length()); else { // Treat bullets used in the text security mode as regular characters when looking for boundaries String iteratorString(it.characters(), it.length()); iteratorString = iteratorString.impl()->secure('x'); string.append(iteratorString.characters(), iteratorString.length()); } next = searchFunction(string.data(), string.size(), prefixLength, MayHaveMoreContext, needMoreContext); if (next != string.size()) break; it.advance(); } if (needMoreContext) { // The last search returned the end of the buffer and asked for more context, // but there is no further text. Force a search with what's available. next = searchFunction(string.data(), string.size(), prefixLength, DontHaveMoreContext, needMoreContext); ASSERT(!needMoreContext); } if (it.atEnd() && next == string.size()) { pos = it.range()->startPosition(); } else if (next != prefixLength) { // Use the character iterator to translate the next value into a DOM position. CharacterIterator charIt(searchRange.get(), true); charIt.advance(next - prefixLength - 1); pos = charIt.range()->endPosition(); if (*charIt.characters() == '\n') { // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) VisiblePosition visPos = VisiblePosition(pos); if (visPos == VisiblePosition(charIt.range()->startPosition())) pos = visPos.next(true).deepEquivalent(); } } // generate VisiblePosition, use UPSTREAM affinity if possible return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); }
bool isEndOfDocument(const VisiblePosition &p) { return p.isNotNull() && p.next().isNull(); }
PassRefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag) { VisiblePosition start = startOfParagraph(originalStart); VisiblePosition end = endOfParagraph(start); // Check for adjoining lists. VisiblePosition previousPosition = start.previous(true); VisiblePosition nextPosition = end.next(true); RefPtr<HTMLElement> listItemElement = createListItemElement(document()); RefPtr<HTMLElement> placeholder = createBreakElement(document()); appendNode(placeholder, listItemElement); Element* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node()); Element* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node()); Node* startNode = start.deepEquivalent().node(); Node* previousCell = enclosingTableCell(previousPosition.deepEquivalent()); Node* nextCell = enclosingTableCell(nextPosition.deepEquivalent()); Node* currentCell = enclosingTableCell(start.deepEquivalent()); if (previousList && (!previousList->hasTagName(listTag) || startNode->isDescendantOf(previousList) || previousCell != currentCell)) previousList = 0; if (nextList && (!nextList->hasTagName(listTag) || startNode->isDescendantOf(nextList) || nextCell != currentCell)) nextList = 0; // Place list item into adjoining lists. RefPtr<HTMLElement> listElement; if (previousList) appendNode(listItemElement, previousList); else if (nextList) insertNodeAt(listItemElement, Position(nextList, 0)); else { // Create the list. listElement = createHTMLElement(document(), listTag); appendNode(listItemElement, listElement); if (start == end && isBlock(start.deepEquivalent().node())) { // Inserting the list into an empty paragraph that isn't held open // by a br or a '\n', will invalidate start and end. Insert // a placeholder and then recompute start and end. RefPtr<Node> placeholder = insertBlockPlaceholder(start.deepEquivalent()); start = VisiblePosition(Position(placeholder.get(), 0)); end = start; } // Insert the list at a position visually equivalent to start of the // paragraph that is being moved into the list. // Try to avoid inserting it somewhere where it will be surrounded by // inline ancestors of start, since it is easier for editing to produce // clean markup when inline elements are pushed down as far as possible. Position insertionPos(start.deepEquivalent().upstream()); // Also avoid the containing list item. Node* listChild = enclosingListChild(insertionPos.node()); if (listChild && listChild->hasTagName(liTag)) insertionPos = positionInParentBeforeNode(listChild); insertNodeAt(listElement, insertionPos); // We inserted the list at the start of the content we're about to move // Update the start of content, so we don't try to move the list into itself. bug 19066 if (insertionPos == start.deepEquivalent()) start = startOfParagraph(originalStart); previousList = outermostEnclosingList(previousPosition.deepEquivalent().node(), enclosingList(listElement.get())); nextList = outermostEnclosingList(nextPosition.deepEquivalent().node(), enclosingList(listElement.get())); } moveParagraph(start, end, VisiblePosition(Position(placeholder.get(), 0)), true); // FIXME: listifyParagraph should not depend on a member variable. // Since fixOrphanedListChild is the only other method that updates m_listElement, // we should fix unlistifyParagraph to support orphaned list child to get rid of this assignment. if (!listElement && m_listElement) listElement = m_listElement; if (listElement) { if (canMergeLists(previousList, listElement.get())) mergeIdenticalElements(previousList, listElement.get()); if (canMergeLists(listElement.get(), nextList)) mergeIdenticalElements(listElement.get(), nextList); } else if (canMergeLists(nextList, previousList)) mergeIdenticalElements(previousList, nextList); return listElement; }
void IndentOutdentCommand::indentRegion() { Selection selection = selectionForParagraphIteration(endingSelection()); VisiblePosition startOfSelection = selection.visibleStart(); VisiblePosition endOfSelection = selection.visibleEnd(); int startIndex = indexForVisiblePosition(startOfSelection); int endIndex = indexForVisiblePosition(endOfSelection); ASSERT(!startOfSelection.isNull()); ASSERT(!endOfSelection.isNull()); // Special case empty root editable elements because there's nothing to split // and there's nothing to move. Position start = startOfSelection.deepEquivalent().downstream(); if (start.node() == editableRootForPosition(start)) { RefPtr<Element> blockquote = createIndentBlockquoteElement(document()); insertNodeAt(blockquote, start); RefPtr<Element> placeholder = createBreakElement(document()); appendNode(placeholder, blockquote); setEndingSelection(Selection(Position(placeholder.get(), 0), DOWNSTREAM)); return; } RefPtr<Element> previousListNode; RefPtr<Element> newListNode; RefPtr<Element> newBlockquote; VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); while (endOfCurrentParagraph != endAfterSelection) { // Iterate across the selected paragraphs... VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); RefPtr<Element> listNode = enclosingList(endOfCurrentParagraph.deepEquivalent().node()); RefPtr<Element> insertionPoint; if (listNode) { RefPtr<Element> placeholder = createBreakElement(document()); insertionPoint = placeholder; newBlockquote = 0; RefPtr<Element> listItem = createListItemElement(document()); if (listNode == previousListNode) { // The previous paragraph was inside the same list, so add this list item to the list we already created appendNode(listItem, newListNode); appendNode(placeholder, listItem); } else { // Clone the list element, insert it before the current paragraph, and move the paragraph into it. RefPtr<Element> clonedList = listNode->cloneElement(); insertNodeBefore(clonedList, enclosingListChild(endOfCurrentParagraph.deepEquivalent().node())); appendNode(listItem, clonedList); appendNode(placeholder, listItem); newListNode = clonedList; previousListNode = listNode; } } else if (newBlockquote) // The previous paragraph was put into a new blockquote, so move this paragraph there as well insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, newBlockquote); else { // Create a new blockquote and insert it as a child of the root editable element. We accomplish // this by splitting all parents of the current paragraph up to that point. RefPtr<Element> blockquote = createIndentBlockquoteElement(document()); Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent(); Node* enclosingCell = enclosingNodeOfType(start, &isTableCell); Node* nodeToSplitTo = enclosingCell ? enclosingCell : editableRootForPosition(start); RefPtr<Node> startOfNewBlock = splitTreeToNode(start.node(), nodeToSplitTo); insertNodeBefore(blockquote, startOfNewBlock); newBlockquote = blockquote; insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, newBlockquote); // Don't put the next paragraph in the blockquote we just created for this paragraph unless // the next paragraph is in the same cell. if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell)) newBlockquote = 0; } moveParagraph(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, VisiblePosition(Position(insertionPoint, 0)), true); // moveParagraph should not destroy content that contains endOfNextParagraph, but if it does, return here // to avoid a crash. if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) { ASSERT_NOT_REACHED(); return; } endOfCurrentParagraph = endOfNextParagraph; } RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true); RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true); if (startRange && endRange) setEndingSelection(Selection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM)); }
void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity) { if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } switch (granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; if (isEndOfEditableOrNonEditableContent(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfEditableOrNonEditableContent(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.deprecatedNode())) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); // End must not be before start. if (m_start.deprecatedNode() == m_end.deprecatedNode() && m_start.deprecatedEditingOffset() > m_end.deprecatedEditingOffset()) { Position swap(m_start); m_start = m_end; m_end = swap; } break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfEditableOrNonEditableContent(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. VisiblePosition end(visibleParagraphEnd.next()); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case DocumentGranularity: ASSERT_NOT_REACHED(); break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; }
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) { Frame* frame = document()->frame(); if (!frame) return; frame->editor()->updateMarkersForWordsAffectedByEditing(false); VisibleSelection selectionToDelete; VisibleSelection selectionAfterUndo; switch (endingSelection().selectionType()) { case VisibleSelection::RangeSelection: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case VisibleSelection::CaretSelection: { m_smartDelete = false; // Handle delete at beginning-of-block case. // Do nothing in the case that the caret is at the start of a // root editable element or at the start of a document. FrameSelection selection; selection.setSelection(endingSelection()); selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity); if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); Position downstreamEnd = endingSelection().end().downstream(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell); if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell)) return; if (visibleEnd == endOfParagraph(visibleEnd)) downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream(); // When deleting tables: Select the table first, then perform the deletion if (downstreamEnd.containerNode() && downstreamEnd.containerNode()->renderer() && downstreamEnd.containerNode()->renderer()->isTable() && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) { setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional())); typingAddedToOpenCommand(ForwardDeleteKey); return; } // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any) if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd())) selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); 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 VisibleSelection 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. Position extent = startingSelection().end(); if (extent.containerNode() != selectionToDelete.end().containerNode()) extent = selectionToDelete.extent(); else { int extraCharacters; if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode()) extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode(); else extraCharacters = selectionToDelete.end().computeOffsetInContainerNode(); extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor); } selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); } break; } case VisibleSelection::NoSelection: ASSERT_NOT_REACHED(); break; } ASSERT(!selectionToDelete.isNone()); if (selectionToDelete.isNone()) return; if (selectionToDelete.isCaret() || !frame->selection()->shouldDeleteSelection(selectionToDelete)) return; if (killRing) frame->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); // make undo select what was deleted setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); setSmartDelete(false); typingAddedToOpenCommand(ForwardDeleteKey); }
void InsertListCommand::doApply() { if (endingSelection().isNone()) 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 != visibleStart && isStartOfParagraph(visibleEnd)) setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); if (endingSelection().isRange() && modifyRange()) return; // FIXME: This will produce unexpected results for a selection that starts just before a // table and ends inside the first cell, selectionForParagraphIteration should probably // be renamed and deployed inside setEndingSelection(). Node* selectionNode = endingSelection().start().node(); const QualifiedName listTag = (m_type == OrderedList) ? olTag : ulTag; Node* listChildNode = enclosingListChild(selectionNode); bool switchListType = false; if (listChildNode) { // Remove the list chlild. HTMLElement* listNode = enclosingList(listChildNode); if (!listNode) listNode = fixOrphanedListChild(listChildNode); if (!listNode->hasTagName(listTag)) // listChildNode will be removed from the list and a list of type m_type will be created. switchListType = true; Node* nextListChild; Node* previousListChild; VisiblePosition start; VisiblePosition end; if (listChildNode->hasTagName(liTag)) { start = firstDeepEditingPositionForNode(listChildNode); end = lastDeepEditingPositionForNode(listChildNode); nextListChild = listChildNode->nextSibling(); previousListChild = listChildNode->previousSibling(); } else { // A paragraph is visually a list item minus a list marker. The paragraph will be moved. start = startOfParagraph(endingSelection().visibleStart()); end = endOfParagraph(endingSelection().visibleEnd()); nextListChild = enclosingListChild(end.next().deepEquivalent().node()); ASSERT(nextListChild != listChildNode); if (enclosingList(nextListChild) != listNode) nextListChild = 0; previousListChild = enclosingListChild(start.previous().deepEquivalent().node()); ASSERT(previousListChild != listChildNode); if (enclosingList(previousListChild) != listNode) previousListChild = 0; } // When removing a list, we must always create a placeholder to act as a point of insertion // for the list content being removed. RefPtr<Element> placeholder = createBreakElement(document()); RefPtr<Element> nodeToInsert = placeholder; // If the content of the list item will be moved into another list, put it in a list item // so that we don't create an orphaned list child. if (enclosingList(listNode)) { nodeToInsert = createListItemElement(document()); appendNode(placeholder, nodeToInsert); } if (nextListChild && previousListChild) { // We want to pull listChildNode out of listNode, and place it before nextListChild // and after previousListChild, so we split listNode and insert it between the two lists. // But to split listNode, we must first split ancestors of listChildNode between it and listNode, // if any exist. // FIXME: We appear to split at nextListChild as opposed to listChildNode so that when we remove // listChildNode below in moveParagraphs, previousListChild will be removed along with it if it is // unrendered. But we ought to remove nextListChild too, if it is unrendered. splitElement(listNode, splitTreeToNode(nextListChild, listNode)); insertNodeBefore(nodeToInsert, listNode); } else if (nextListChild || listChildNode->parentNode() != listNode) { // Just because listChildNode has no previousListChild doesn't mean there isn't any content // in listNode that comes before listChildNode, as listChildNode could have ancestors // between it and listNode. So, we split up to listNode before inserting the placeholder // where we're about to move listChildNode to. if (listChildNode->parentNode() != listNode) splitElement(listNode, splitTreeToNode(listChildNode, listNode).get()); insertNodeBefore(nodeToInsert, listNode); } else insertNodeAfter(nodeToInsert, listNode); VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0)); moveParagraphs(start, end, insertionPoint, true); } if (!listChildNode || switchListType || m_forceCreateList) { // Create list. VisiblePosition originalStart = endingSelection().visibleStart(); VisiblePosition start = startOfParagraph(originalStart); VisiblePosition end = endOfParagraph(endingSelection().visibleEnd()); // Check for adjoining lists. VisiblePosition previousPosition = start.previous(true); VisiblePosition nextPosition = end.next(true); RefPtr<HTMLElement> listItemElement = createListItemElement(document()); RefPtr<HTMLElement> placeholder = createBreakElement(document()); appendNode(placeholder, listItemElement); Element* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node()); Element* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node()); Node* startNode = start.deepEquivalent().node(); Node* previousCell = enclosingTableCell(previousPosition.deepEquivalent()); Node* nextCell = enclosingTableCell(nextPosition.deepEquivalent()); Node* currentCell = enclosingTableCell(start.deepEquivalent()); if (previousList && (!previousList->hasTagName(listTag) || startNode->isDescendantOf(previousList) || previousCell != currentCell)) previousList = 0; if (nextList && (!nextList->hasTagName(listTag) || startNode->isDescendantOf(nextList) || nextCell != currentCell)) nextList = 0; // Place list item into adjoining lists. if (previousList) appendNode(listItemElement, previousList); else if (nextList) insertNodeAt(listItemElement, Position(nextList, 0)); else { // Create the list. RefPtr<HTMLElement> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document()); m_listElement = listElement; appendNode(listItemElement, listElement); if (start == end && isBlock(start.deepEquivalent().node())) { // Inserting the list into an empty paragraph that isn't held open // by a br or a '\n', will invalidate start and end. Insert // a placeholder and then recompute start and end. RefPtr<Node> placeholder = insertBlockPlaceholder(start.deepEquivalent()); start = VisiblePosition(Position(placeholder.get(), 0)); end = start; } // Insert the list at a position visually equivalent to start of the // paragraph that is being moved into the list. // Try to avoid inserting it somewhere where it will be surrounded by // inline ancestors of start, since it is easier for editing to produce // clean markup when inline elements are pushed down as far as possible. Position insertionPos(start.deepEquivalent().upstream()); // Also avoid the containing list item. Node* listChild = enclosingListChild(insertionPos.node()); if (listChild && listChild->hasTagName(liTag)) insertionPos = positionInParentBeforeNode(listChild); insertNodeAt(listElement, insertionPos); // We inserted the list at the start of the content we're about to move // Update the start of content, so we don't try to move the list into itself. bug 19066 if (insertionPos == start.deepEquivalent()) start = startOfParagraph(originalStart); } moveParagraph(start, end, VisiblePosition(Position(placeholder.get(), 0)), true); if (nextList && previousList) mergeIdenticalElements(previousList, nextList); } }
static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const UChar *, unsigned)) { Position pos = c.deepEquivalent(); Node *n = pos.node(); if (!n) return VisiblePosition(); Document *d = n->document(); Node *de = d->documentElement(); if (!de) return VisiblePosition(); Node *boundary = n->enclosingBlockFlowElement(); if (!boundary) return VisiblePosition(); bool isContentEditable = boundary->isContentEditable(); while (boundary && boundary != de && boundary->parentNode() && isContentEditable == boundary->parentNode()->isContentEditable()) boundary = boundary->parentNode(); RefPtr<Range> searchRange(d->createRange()); Position start(rangeCompliantEquivalent(pos)); ExceptionCode ec = 0; searchRange->selectNodeContents(boundary, ec); searchRange->setStart(start.node(), start.offset(), ec); TextIterator it(searchRange.get(), true); Vector<UChar, 1024> string; unsigned next = 0; bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE; while (!it.atEnd()) { // Keep asking the iterator for chunks until the search function // returns an end value not equal to the length of the string passed to it. if (!inTextSecurityMode) string.append(it.characters(), it.length()); else { // Treat bullets used in the text security mode as regular characters when looking for boundaries String iteratorString(it.characters(), it.length()); iteratorString = iteratorString.impl()->secure('x'); string.append(iteratorString.characters(), iteratorString.length()); } next = searchFunction(string.data(), string.size()); if (next != string.size()) break; it.advance(); } if (it.atEnd() && next == string.size()) { pos = it.range()->startPosition(); } else if (next != 0) { // Use the character iterator to translate the next value into a DOM position. CharacterIterator charIt(searchRange.get(), true); charIt.advance(next - 1); pos = charIt.range()->endPosition(); // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) VisiblePosition visPos = VisiblePosition(pos); if (visPos == VisiblePosition(charIt.range()->startPosition())) pos = visPos.next(true).deepEquivalent(); } // generate VisiblePosition, use UPSTREAM affinity if possible return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); }
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) { VisibleSelection selectionToDelete; VisibleSelection selectionAfterUndo; switch (endingSelection().selectionType()) { case VisibleSelection::RangeSelection: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; case VisibleSelection::CaretSelection: { m_smartDelete = false; // Handle delete at beginning-of-block case. // Do nothing in the case that the caret is at the start of a // root editable element or at the start of a document. SelectionController selection; selection.setSelection(endingSelection()); selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, granularity); if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, CharacterGranularity); Position downstreamEnd = endingSelection().end().downstream(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); if (visibleEnd == endOfParagraph(visibleEnd)) downstreamEnd = visibleEnd.next(true).deepEquivalent().downstream(); // When deleting tables: Select the table first, then perform the deletion if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.deprecatedEditingOffset() == 0) { setEndingSelection(VisibleSelection(endingSelection().end(), lastDeepEditingPositionForNode(downstreamEnd.node()), DOWNSTREAM)); typingAddedToOpenCommand(ForwardDeleteKey); return; } // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any) if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd())) selection.modify(SelectionController::EXTEND, SelectionController::FORWARD, CharacterGranularity); 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 VisibleSelection 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. Position extent = startingSelection().end(); if (extent.node() != selectionToDelete.end().node()) extent = selectionToDelete.extent(); else { int extraCharacters; if (selectionToDelete.start().node() == selectionToDelete.end().node()) extraCharacters = selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset(); else extraCharacters = selectionToDelete.end().deprecatedEditingOffset(); extent = Position(extent.node(), extent.deprecatedEditingOffset() + extraCharacters); } selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); } break; } case VisibleSelection::NoSelection: ASSERT_NOT_REACHED(); break; } ASSERT(!selectionToDelete.isNone()); if (selectionToDelete.isNone()) return; if (selectionToDelete.isCaret() || !document()->frame()->shouldDeleteSelection(selectionToDelete)) return; if (killRing) document()->frame()->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); // make undo select what was deleted setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); setSmartDelete(false); typingAddedToOpenCommand(ForwardDeleteKey); }
void Selection::validate() { // Move the selection to rendered positions, if possible. bool baseAndExtentEqual = m_base == m_extent; if (m_base.isNotNull()) { m_base = VisiblePosition(m_base, m_affinity).deepEquivalent(); if (baseAndExtentEqual) m_extent = m_base; } if (m_extent.isNotNull() && !baseAndExtentEqual) m_extent = VisiblePosition(m_extent, m_affinity).deepEquivalent(); // Make sure we do not have a dangling base or extent. if (m_base.isNull() && m_extent.isNull()) m_baseIsFirst = true; else if (m_base.isNull()) { m_base = m_extent; m_baseIsFirst = true; } else if (m_extent.isNull()) { m_extent = m_base; m_baseIsFirst = true; } else { m_baseIsFirst = comparePositions(m_base, m_extent) <= 0; } if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } // Expand the selection if requested. switch (m_granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd)) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(true); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfDocument(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. VisiblePosition end(visibleParagraphEnd.next()); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(true); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; adjustForEditableContent(); // adjust the state if (m_start.isNull()) { ASSERT(m_end.isNull()); m_state = NONE; // enforce downstream affinity if not caret, as affinity only // makes sense for caret m_affinity = DOWNSTREAM; } else if (m_start == m_end || m_start.upstream() == m_end.upstream()) { m_state = CARET; } else { m_state = RANGE; // enforce downstream affinity if not caret, as affinity only // makes sense for caret m_affinity = DOWNSTREAM; // "Constrain" the selection to be the smallest equivalent range of nodes. // This is a somewhat arbitrary choice, but experience shows that it is // useful to make to make the selection "canonical" (if only for // purposes of comparing selections). This is an ideal point of the code // to do this operation, since all selection changes that result in a RANGE // come through here before anyone uses it. m_start = m_start.downstream(); m_end = m_end.upstream(); } }
PassRefPtrWillBeRawPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const HTMLQualifiedName& listTag) { VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBoundary); VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary); if (start.isNull() || end.isNull()) return nullptr; // Check for adjoining lists. RefPtrWillBeRawPtr<HTMLElement> listItemElement = createListItemElement(document()); RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document()); appendNode(placeholder, listItemElement); // Place list item into adjoining lists. HTMLElement* previousList = adjacentEnclosingList(start, start.previous(CannotCrossEditingBoundary), listTag); HTMLElement* nextList = adjacentEnclosingList(start, end.next(CannotCrossEditingBoundary), listTag); RefPtrWillBeRawPtr<HTMLElement> listElement = nullptr; if (previousList) appendNode(listItemElement, previousList); else if (nextList) insertNodeAt(listItemElement, positionBeforeNode(nextList)); else { // Create the list. listElement = createHTMLElement(document(), listTag); appendNode(listItemElement, listElement); if (start == end && isBlock(start.deepEquivalent().deprecatedNode())) { // Inserting the list into an empty paragraph that isn't held open // by a br or a '\n', will invalidate start and end. Insert // a placeholder and then recompute start and end. RefPtrWillBeRawPtr<HTMLBRElement> placeholder = insertBlockPlaceholder(start.deepEquivalent()); start = VisiblePosition(positionBeforeNode(placeholder.get())); end = start; } // Insert the list at a position visually equivalent to start of the // paragraph that is being moved into the list. // Try to avoid inserting it somewhere where it will be surrounded by // inline ancestors of start, since it is easier for editing to produce // clean markup when inline elements are pushed down as far as possible. Position insertionPos(start.deepEquivalent().upstream()); while (isInline(insertionPos.anchorNode()) && !insertionPos.anchorNode()->hasTagName(brTag)) { insertionPos = positionInParentBeforeNode(*insertionPos.anchorNode()); } // Also avoid the containing list item. Node* listChild = enclosingListChild(insertionPos.deprecatedNode()); if (isHTMLLIElement(listChild)) insertionPos = positionInParentBeforeNode(*listChild); insertNodeAt(listElement, insertionPos); // We inserted the list at the start of the content we're about to move // Update the start of content, so we don't try to move the list into itself. bug 19066 // Layout is necessary since start's node's inline layoutObjects may have been destroyed by the insertion // The end of the content may have changed after the insertion and layout so update it as well. if (insertionPos == start.deepEquivalent()) start = originalStart; } // Inserting list element and list item list may change start of pargraph // to move. We calculate start of paragraph again. document().updateLayoutIgnorePendingStylesheets(); start = startOfParagraph(start, CanSkipOverEditingBoundary); end = endOfParagraph(start, CanSkipOverEditingBoundary); moveParagraph(start, end, VisiblePosition(positionBeforeNode(placeholder.get())), true); if (listElement) return mergeWithNeighboringLists(listElement); if (canMergeLists(previousList, nextList)) mergeIdenticalElements(previousList, nextList); return listElement; }
void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity) { if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } switch (granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.deprecatedNode())) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); //added this to select only letters for dictionary viewing if( m_selectOnlyLetters && m_start.anchorNode()->isTextNode() && m_end.anchorNode()->isTextNode() ) { CharacterData* startText = static_cast<CharacterData*>(m_start.anchorNode()); String startStr = startText->data(); CharacterData* endText = static_cast<CharacterData*>(m_end.anchorNode()); String endStr = endText->data(); int n1 = m_start.offsetInContainerNode(); int n2 = m_end.offsetInContainerNode() - 1; //unhandled corner case: at beginning of sentence, m_start may refer //to the Node of the last sentence, this causes the space at the beginning //of a sentence to be part of the selection. Assigning m_end to m_start and //adjusting the offset does not fix this. QChar c1 = startStr[n1]; QChar c2 = endStr[n2]; while(!c1.isLetter() && n1 <= n2) { c1 = startStr[++n1]; m_start = m_start.next(Character); } while(!c2.isLetter() && n2 >= n1) { c2 = endStr[--n2]; m_end = m_end.previous(Character); } } break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity)); // If the end of this line is at the end of a paragraph, include the space // after the end of the line in the selection. if (isEndOfParagraph(end)) { VisiblePosition next = end.next(); if (next.isNotNull()) end = next; } m_end = end.deepEquivalent(); break; } case LineBoundary: m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphGranularity: { VisiblePosition pos(m_start, m_affinity); if (isStartOfLine(pos) && isEndOfDocument(pos)) pos = pos.previous(); m_start = startOfParagraph(pos).deepEquivalent(); VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity)); // Include the "paragraph break" (the space from the end of this paragraph to the start // of the next one) in the selection. VisiblePosition end(visibleParagraphEnd.next()); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) end = end.next(CannotCrossEditingBoundary); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; } if (end.isNull()) end = visibleParagraphEnd; m_end = end.deepEquivalent(); break; } case DocumentBoundary: m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case ParagraphBoundary: m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case SentenceBoundary: m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; case WebKitVisualWordGranularity: break; } // Make sure we do not have a dangling start or end. if (m_start.isNull()) m_start = m_end; if (m_end.isNull()) m_end = m_start; }
VisiblePosition AccessibilityObject::nextVisiblePosition(const VisiblePosition& visiblePos) const { return visiblePos.next(); }
void InsertListCommand::doApply() { if (endingSelection().isNone()) return; if (endingSelection().isRange() && modifyRange()) return; if (!endingSelection().rootEditableElement()) return; Node* selectionNode = endingSelection().start().node(); const QualifiedName listTag = (m_type == OrderedListType) ? olTag : ulTag; Node* listChildNode = enclosingListChild(selectionNode); bool switchListType = false; if (listChildNode) { // Remove the list chlild. Node* listNode = enclosingList(listChildNode); if (!listNode) listNode = fixOrphanedListChild(listChildNode); if (!listNode->hasTagName(listTag)) // listChildNode will be removed from the list and a list of type m_type will be created. switchListType = true; Node* nextListChild; Node* previousListChild; VisiblePosition start; VisiblePosition end; if (listChildNode->hasTagName(liTag)) { start = VisiblePosition(Position(listChildNode, 0)); end = VisiblePosition(Position(listChildNode, maxDeepOffset(listChildNode))); nextListChild = listChildNode->nextSibling(); previousListChild = listChildNode->previousSibling(); } else { // A paragraph is visually a list item minus a list marker. The paragraph will be moved. start = startOfParagraph(endingSelection().visibleStart()); end = endOfParagraph(endingSelection().visibleEnd()); nextListChild = enclosingListChild(end.next().deepEquivalent().node()); ASSERT(nextListChild != listChildNode); if (enclosingList(nextListChild) != listNode) nextListChild = 0; previousListChild = enclosingListChild(start.previous().deepEquivalent().node()); ASSERT(previousListChild != listChildNode); if (enclosingList(previousListChild) != listNode) previousListChild = 0; } // When removing a list, we must always create a placeholder to act as a point of insertion // for the list content being removed. RefPtr<Element> placeholder = createBreakElement(document()); if (nextListChild && previousListChild) { splitElement(static_cast<Element *>(listNode), nextListChild); insertNodeBefore(placeholder.get(), listNode); } else if (nextListChild) insertNodeBefore(placeholder.get(), listNode); else insertNodeAfter(placeholder.get(), listNode); VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0)); moveParagraphs(start, end, insertionPoint, true); } if (!listChildNode || switchListType || m_forceCreateList) { // Create list. VisiblePosition start = startOfParagraph(endingSelection().visibleStart()); VisiblePosition end = endOfParagraph(endingSelection().visibleEnd()); // Check for adjoining lists. VisiblePosition previousPosition = start.previous(true); VisiblePosition nextPosition = end.next(true); RefPtr<Element> listItemElement = createListItemElement(document()); Node* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node()); Node* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node()); if (previousList && !previousList->hasTagName(listTag)) previousList = 0; if (nextList && !nextList->hasTagName(listTag)) nextList = 0; // Stitch matching adjoining lists together. if (previousList) appendNode(listItemElement.get(), previousList); else if (nextList) appendNode(listItemElement.get(), nextList); else { // Create the list. RefPtr<Element> listElement = m_type == OrderedListType ? createOrderedListElement(document()) : createUnorderedListElement(document()); static_cast<HTMLElement*>(listElement.get())->setId(m_id); appendNode(listItemElement.get(), listElement.get()); insertNodeAt(listElement.get(), start.deepEquivalent().node(), start.deepEquivalent().offset()); } moveParagraph(start, end, VisiblePosition(Position(listItemElement.get(), 0)), true); if (nextList && previousList) mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList)); } }