// FIXME: We should merge this function with ApplyBlockElementCommand::formatSelection void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) { VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); if (endOfParagraph(startOfSelection) == endOfLastParagraph) { outdentParagraph(); return; } Position originalSelectionEnd = endingSelection().end(); VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); while (endOfCurrentParagraph != endAfterSelection) { VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); if (endOfCurrentParagraph == endOfLastParagraph) setEndingSelection(VisibleSelection(originalSelectionEnd, DOWNSTREAM)); else setEndingSelection(endOfCurrentParagraph); outdentParagraph(); // 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.deepEquivalent().anchorNode()->inDocument()) break; if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->inDocument()) { endOfCurrentParagraph = endingSelection().end(); endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); } endOfCurrentParagraph = endOfNextParagraph; } }
void RenderTextControl::setSelectionRange(int start, int end) { end = max(end, 0); start = min(max(start, 0), end); ASSERT(!document()->childNeedsAndNotInStyleRecalc()); if (style()->visibility() == HIDDEN || !m_innerText || !m_innerText->renderer() || !m_innerText->renderBox()->height()) { cacheSelection(start, end); return; } VisiblePosition startPosition = visiblePositionForIndex(start); VisiblePosition endPosition; if (start == end) endPosition = startPosition; else endPosition = visiblePositionForIndex(end); // startPosition and endPosition can be null position for example when // "-webkit-user-select: none" style attribute is specified. if (startPosition.isNotNull() && endPosition.isNotNull()) { ASSERT(startPosition.deepEquivalent().node()->shadowAncestorNode() == node() && endPosition.deepEquivalent().node()->shadowAncestorNode() == node()); } VisibleSelection newSelection = VisibleSelection(startPosition, endPosition); if (Frame* frame = document()->frame()) frame->selection()->setSelection(newSelection); // FIXME: Granularity is stored separately on the frame, but also in the selection controller. // The granularity in the selection controller should be used, and then this line of code would not be needed. if (Frame* frame = document()->frame()) frame->setSelectionGranularity(CharacterGranularity); }
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()); int endOfCurrentParagraphIndex = indexForVisiblePosition(endOfCurrentParagraph); int endAfterSelectionIndex = indexForVisiblePosition(endAfterSelection); // When indenting within a <pre> tag, we need to split each paragraph into a separate node for moveParagraphWithClones to work. // However, splitting text nodes can cause endOfCurrentParagraph and endAfterSelection to point to an invalid position if we // changed the text node it was pointing at. So we have to reset these positions. int numParagraphs = countParagraphs(endOfCurrentParagraph, endAfterSelection); if (splitTextNodes(startOfParagraph(startOfSelection), numParagraphs + 1)) { RefPtr<Range> endOfCurrentParagraphRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endOfCurrentParagraphIndex, 0, true); RefPtr<Range> endAfterSelectionRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endAfterSelectionIndex, 0, true); if (!endOfCurrentParagraphRange.get() || !endAfterSelectionRange.get()) { ASSERT_NOT_REACHED(); return; } endOfCurrentParagraph = VisiblePosition(endOfCurrentParagraphRange->startPosition(), DOWNSTREAM); endAfterSelection = VisiblePosition(endAfterSelectionRange->startPosition(), DOWNSTREAM); } 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; } }
void ApplyBlockElementCommand::formatSelection(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 = createBlockElement(); insertNodeAt(blockquote, start); RefPtr<Element> placeholder = createBreakElement(document()); appendNode(placeholder, blockquote); setEndingSelection(VisibleSelection(positionBeforeNode(placeholder.get()), DOWNSTREAM, endingSelection().isDirectional())); return; } RefPtr<Element> blockquoteForNextIndent; VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); m_endOfLastParagraph = endOfParagraph(endOfSelection).deepEquivalent(); bool atEnd = false; Position end; while (endOfCurrentParagraph != endAfterSelection && !atEnd) { if (endOfCurrentParagraph.deepEquivalent() == m_endOfLastParagraph) atEnd = true; rangeForParagraphSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end); endOfCurrentParagraph = end; Position afterEnd = end.next(); Node* enclosingCell = enclosingNodeOfType(start, &isTableCell); VisiblePosition endOfNextParagraph = endOfNextParagrahSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end); formatRange(start, end, m_endOfLastParagraph, blockquoteForNextIndent); // 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)) blockquoteForNextIndent = 0; // 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().anchorNode()->inDocument()) break; // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().deprecatedNode() // If somehow we did, return to prevent crashes. if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->inDocument()) { ASSERT_NOT_REACHED(); return; } endOfCurrentParagraph = endOfNextParagraph; } }
VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const { if (visiblePos.isNull()) return VisiblePositionRange(); // make a caret selection for the position before marker position (to make sure // we move off of a line start) VisiblePosition prevVisiblePos = visiblePos.previous(); if (prevVisiblePos.isNull()) return VisiblePositionRange(); VisiblePosition startPosition = startOfLine(prevVisiblePos); // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should // always be a valid line range. However, startOfLine will return null for position next to a floating object, // since floating object doesn't really belong to any line. // This check will reposition the marker before the floating object, to ensure we get a line start. if (startPosition.isNull()) { while (startPosition.isNull() && prevVisiblePos.isNotNull()) { prevVisiblePos = prevVisiblePos.previous(); startPosition = startOfLine(prevVisiblePos); } } else startPosition = updateAXLineStartForVisiblePosition(startPosition); VisiblePosition endPosition = endOfLine(prevVisiblePos); return VisiblePositionRange(startPosition, endPosition); }
int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const { if (visiblePos.isNull() || !node()) return -1; // If the position is not in the same editable region as this AX object, return -1. Node* containerNode = visiblePos.deepEquivalent().containerNode(); if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode)) return -1; int lineCount = -1; VisiblePosition currentVisiblePos = visiblePos; VisiblePosition savedVisiblePos; // move up until we get to the top // FIXME: This only takes us to the top of the rootEditableElement, not the top of the // top document. do { savedVisiblePos = currentVisiblePos; VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole); currentVisiblePos = prevVisiblePos; ++lineCount; } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))); return lineCount; }
bool FormatBlockCommand::modifyRange() { ASSERT(endingSelection().isRange()); VisiblePosition visibleStart = endingSelection().visibleStart(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd); if (startOfParagraph(visibleStart) == startOfLastParagraph) return false; setEndingSelection(visibleStart); doApply(); visibleStart = endingSelection().visibleStart(); VisiblePosition nextParagraph = endOfParagraph(visibleStart).next(); while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) { setEndingSelection(nextParagraph); doApply(); nextParagraph = endOfParagraph(endingSelection().visibleStart()).next(); } setEndingSelection(visibleEnd); doApply(); visibleEnd = endingSelection().visibleEnd(); setEndingSelection(Selection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM)); return true; }
RefPtr<Range> Frame::rangeForPoint(const IntPoint& framePoint) { VisiblePosition position = visiblePositionForPoint(framePoint); if (position.isNull()) return nullptr; Position deepPosition = position.deepEquivalent(); Text* containerText = deepPosition.containerText(); if (!containerText || !containerText->renderer() || containerText->renderer()->style().userSelect() == SELECT_NONE) return nullptr; 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; } VisiblePosition next = position.next(); if (RefPtr<Range> nextCharacterRange = makeRange(position, next)) { LayoutRect rect = editor().firstRectForRange(nextCharacterRange.get()); if (rect.contains(framePoint)) return nextCharacterRange; } return nullptr; }
VisiblePosition VisiblePosition::previous(bool stayInEditableContent) const { // find first previous DOM position that is visible Position pos = previousVisiblePosition(m_deepPosition); // return null visible position if there is no previous visible position if (pos.atStart()) return VisiblePosition(); VisiblePosition prev = VisiblePosition(pos, DOWNSTREAM); ASSERT(prev != *this); #ifndef NDEBUG // we should always be able to make the affinity DOWNSTREAM, because going previous from an // UPSTREAM position can never yield another UPSTREAM position (unless line wrap length is 0!). if (prev.isNotNull() && m_affinity == UPSTREAM) { VisiblePosition temp = prev; temp.setAffinity(UPSTREAM); ASSERT(inSameLine(temp, prev)); } #endif if (!stayInEditableContent || prev.isNull()) return prev; Node* highestRoot = highestEditableRoot(deepEquivalent()); if (!prev.deepEquivalent().node()->isAncestor(highestRoot)) return VisiblePosition(); if (highestEditableRoot(prev.deepEquivalent()) == highestRoot) return prev; return lastEditablePositionBeforePositionInRoot(prev.deepEquivalent(), highestRoot); }
VisiblePosition VisiblePosition::previous(EditingBoundaryCrossingRule rule) const { // FIXME: Support CanSkipEditingBoundary ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); // find first previous DOM position that is visible Position pos = previousVisuallyDistinctCandidate(m_deepPosition); // return null visible position if there is no previous visible position if (pos.atStartOfTree()) return VisiblePosition(); VisiblePosition prev = VisiblePosition(pos, DOWNSTREAM); ASSERT(prev != *this); #ifndef NDEBUG // we should always be able to make the affinity DOWNSTREAM, because going previous from an // UPSTREAM position can never yield another UPSTREAM position (unless line wrap length is 0!). if (prev.isNotNull() && m_affinity == UPSTREAM) { VisiblePosition temp = prev; temp.setAffinity(UPSTREAM); ASSERT(inSameLine(temp, prev)); } #endif if (rule == CanCrossEditingBoundary) return prev; return honorEditingBoundaryAtOrBefore(prev); }
void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) { LocalFrame* frame = document().frame(); if (!frame) return; if (!frame->spellChecker().isContinuousSpellCheckingEnabled()) return; frame->spellChecker().cancelCheck(); // Take a look at the selection that results after typing and determine whether we need to spellcheck. // Since the word containing the current selection is never marked, this does a check to // see if typing made a new word that is not in the current selection. Basically, you // get this by being at the end of a word and typing a space. VisiblePosition start(endingSelection().start(), endingSelection().affinity()); VisiblePosition previous = start.previous(); VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); if (commandType == InsertParagraphSeparator) { VisiblePosition p2 = nextWordPosition(start); VisibleSelection words(p1, endOfWord(p2)); frame->spellChecker().markMisspellingsAfterLineBreak(words); } else if (previous.isNotNull()) { VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) frame->spellChecker().markMisspellingsAfterTypingToWord(p1, endingSelection()); } }
VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const { if (visiblePos.isNull()) return VisiblePositionRange(); // make sure we move off of a line end VisiblePosition nextVisiblePos = visiblePos.next(); if (nextVisiblePos.isNull()) return VisiblePositionRange(); VisiblePosition startPosition = startOfLine(nextVisiblePos); // fetch for a valid line start position if (startPosition.isNull() ) { startPosition = visiblePos; nextVisiblePos = nextVisiblePos.next(); } else startPosition = updateAXLineStartForVisiblePosition(startPosition); VisiblePosition endPosition = endOfLine(nextVisiblePos); // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will // return null for position by a floating object, since floating object doesn't really belong to any line. // This check will reposition the marker after the floating object, to ensure we get a line end. while (endPosition.isNull() && nextVisiblePos.isNotNull()) { nextVisiblePos = nextVisiblePos.next(); endPosition = endOfLine(nextVisiblePos); } return VisiblePositionRange(startPosition, endPosition); }
VisiblePosition VisiblePosition::previous(EditingBoundaryCrossingRule rule) const { Position pos = previousVisuallyDistinctCandidate(m_deepPosition); // return null visible position if there is no previous visible position if (pos.atStartOfTree()) return VisiblePosition(); VisiblePosition prev = VisiblePosition(pos, DOWNSTREAM); ASSERT(prev != *this); #if ENABLE(ASSERT) // we should always be able to make the affinity DOWNSTREAM, because going previous from an // UPSTREAM position can never yield another UPSTREAM position (unless line wrap length is 0!). if (prev.isNotNull() && m_affinity == UPSTREAM) { VisiblePosition temp = prev; temp.setAffinity(UPSTREAM); ASSERT(inSameLine(temp, prev)); } #endif switch (rule) { case CanCrossEditingBoundary: return prev; case CannotCrossEditingBoundary: return honorEditingBoundaryAtOrBefore(prev); case CanSkipOverEditingBoundary: return skipToStartOfEditingBoundary(prev); } ASSERT_NOT_REACHED(); return honorEditingBoundaryAtOrBefore(prev); }
TextDirection SelectionModifier::directionOfSelection() const { InlineBox* startBox = nullptr; InlineBox* endBox = nullptr; // Cache the VisiblePositions because visibleStart() and visibleEnd() // can cause layout, which has the potential to invalidate lineboxes. VisiblePosition startPosition = m_selection.visibleStart(); VisiblePosition endPosition = m_selection.visibleEnd(); if (startPosition.isNotNull()) startBox = computeInlineBoxPosition(startPosition).inlineBox; if (endPosition.isNotNull()) endBox = computeInlineBoxPosition(endPosition).inlineBox; if (startBox && endBox && startBox->direction() == endBox->direction()) return startBox->direction(); return directionOfEnclosingBlock(); }
VisiblePosition SelectionModifier::nextWordPositionForPlatform( const VisiblePosition& originalPosition) { VisiblePosition positionAfterCurrentWord = nextWordPosition(originalPosition); if (frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight()) { // In order to skip spaces when moving right, we advance one // word further and then move one word back. Given the // semantics of previousWordPosition() this will put us at the // beginning of the word following. VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord); if (positionAfterSpacingAndFollowingWord.isNotNull() && positionAfterSpacingAndFollowingWord.deepEquivalent() != positionAfterCurrentWord.deepEquivalent()) positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord); bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord.deepEquivalent() == previousWordPosition(nextWordPosition(originalPosition)) .deepEquivalent(); if (movingBackwardsMovedPositionToStartOfCurrentWord) positionAfterCurrentWord = positionAfterSpacingAndFollowingWord; } return positionAfterCurrentWord; }
EphemeralRange LocalFrame::rangeForPoint(const IntPoint& framePoint) { const PositionWithAffinity positionWithAffinity = positionForPoint(framePoint); if (positionWithAffinity.isNull()) return EphemeralRange(); VisiblePosition position = createVisiblePosition(positionWithAffinity); VisiblePosition previous = previousPositionOf(position); if (previous.isNotNull()) { const EphemeralRange previousCharacterRange = makeRange(previous, position); IntRect rect = editor().firstRectForRange(previousCharacterRange); if (rect.contains(framePoint)) return EphemeralRange(previousCharacterRange); } VisiblePosition next = nextPositionOf(position); const EphemeralRange nextCharacterRange = makeRange(position, next); if (nextCharacterRange.isNotNull()) { IntRect rect = editor().firstRectForRange(nextCharacterRange); if (rect.contains(framePoint)) return EphemeralRange(nextCharacterRange); } return EphemeralRange(); }
bool InsertListCommand::modifyRange() { ASSERT(endingSelection().isRange()); VisiblePosition visibleStart = endingSelection().visibleStart(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd); if (startOfParagraph(visibleStart) == startOfLastParagraph) return false; Node* startList = enclosingList(visibleStart.deepEquivalent().node()); Node* endList = enclosingList(visibleEnd.deepEquivalent().node()); if (!startList || startList != endList) m_forceCreateList = true; setEndingSelection(visibleStart); doApply(); visibleStart = endingSelection().visibleStart(); VisiblePosition nextParagraph = endOfParagraph(visibleStart).next(); while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) { setEndingSelection(nextParagraph); doApply(); nextParagraph = endOfParagraph(endingSelection().visibleStart()).next(); } setEndingSelection(visibleEnd); doApply(); visibleEnd = endingSelection().visibleEnd(); setEndingSelection(Selection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM)); m_forceCreateList = false; return true; }
void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) { #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled() && !document()->frame()->editor()->isAutomaticQuoteSubstitutionEnabled() && !document()->frame()->editor()->isAutomaticLinkDetectionEnabled() && !document()->frame()->editor()->isAutomaticDashSubstitutionEnabled() && !document()->frame()->editor()->isAutomaticTextReplacementEnabled()) return; #else if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled()) return; #endif // Take a look at the selection that results after typing and determine whether we need to spellcheck. // Since the word containing the current selection is never marked, this does a check to // see if typing made a new word that is not in the current selection. Basically, you // get this by being at the end of a word and typing a space. VisiblePosition start(endingSelection().start(), endingSelection().affinity()); VisiblePosition previous = start.previous(); if (previous.isNotNull()) { VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) { RefPtr<Range> range = makeRange(p1, p2); String strippedPreviousWord; if (range && (commandType == TypingCommand::InsertText || commandType == TypingCommand::InsertLineBreak || commandType == TypingCommand::InsertParagraphSeparator || commandType == TypingCommand::InsertParagraphSeparatorInQuotedContent)) strippedPreviousWord = plainText(range.get()).stripWhiteSpace(); document()->frame()->lspellcheck();//FIX ME: Opensource issue. } else if (commandType == TypingCommand::InsertText) document()->frame()->editor()->startCorrectionPanelTimer(); } }
void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) { #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled() && !document()->frame()->editor()->isAutomaticQuoteSubstitutionEnabled() && !document()->frame()->editor()->isAutomaticLinkDetectionEnabled() && !document()->frame()->editor()->isAutomaticDashSubstitutionEnabled() && !document()->frame()->editor()->isAutomaticTextReplacementEnabled()) return; #else if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled()) return; #endif // Take a look at the selection that results after typing and determine whether we need to spellcheck. // Since the word containing the current selection is never marked, this does a check to // see if typing made a new word that is not in the current selection. Basically, you // get this by being at the end of a word and typing a space. VisiblePosition start(endingSelection().start(), endingSelection().affinity()); VisiblePosition previous = start.previous(); if (previous.isNotNull()) { VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) document()->frame()->editor()->markMisspellingsAfterTypingToWord(p1, endingSelection()); #if SUPPORT_AUTOCORRECTION_PANEL else if (commandType == TypingCommand::InsertText) document()->frame()->editor()->startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeCorrection); #else UNUSED_PARAM(commandType); #endif } }
VisiblePosition VisiblePosition::previous(EditingBoundaryCrossingRule rule) const { Position pos = previousVisuallyDistinctCandidate(m_deepPosition); // return null visible position if there is no previous visible position if (pos.atStartOfTree()) return VisiblePosition(); VisiblePosition prev = VisiblePosition(pos); ASSERT(prev.deepEquivalent() != m_deepPosition); #if ENABLE(ASSERT) // we should always be able to make the affinity |TextAffinity::Downstream|, // because going previous from an |TextAffinity::Upstream| position can // never yield another |TextAffinity::Upstream position| (unless line wrap // length is 0!). if (prev.isNotNull() && m_affinity == TextAffinity::Upstream) { ASSERT(inSameLine(PositionWithAffinity(prev.deepEquivalent()), PositionWithAffinity(prev.deepEquivalent(), TextAffinity::Upstream))); } #endif switch (rule) { case CanCrossEditingBoundary: return prev; case CannotCrossEditingBoundary: return honorEditingBoundaryAtOrBefore(prev); case CanSkipOverEditingBoundary: return skipToStartOfEditingBoundary(prev); } ASSERT_NOT_REACHED(); return honorEditingBoundaryAtOrBefore(prev); }
void IndentOutdentCommand::outdentParagraph() { VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart()); VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph); Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote); if (!enclosingNode) return; // Use InsertListCommand to remove the selection from the list if (enclosingNode->hasTagName(olTag)) { applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList, "")); return; } if (enclosingNode->hasTagName(ulTag)) { applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList, "")); return; } // The selection is inside a blockquote VisiblePosition positionInEnclosingBlock = VisiblePosition(Position(enclosingNode, 0)); VisiblePosition startOfEnclosingBlock = startOfBlock(positionInEnclosingBlock); VisiblePosition endOfEnclosingBlock = endOfBlock(positionInEnclosingBlock); if (visibleStartOfParagraph == startOfEnclosingBlock && visibleEndOfParagraph == endOfEnclosingBlock) { // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed. removeNodePreservingChildren(enclosingNode); updateLayout(); visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent()); visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent()); if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph)) insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent()); if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph)) insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent()); return; } Node* enclosingBlockFlow = enclosingBlockFlowElement(visibleStartOfParagraph); RefPtr<Node> splitBlockquoteNode = enclosingNode; if (enclosingBlockFlow != enclosingNode) splitBlockquoteNode = splitTreeToNode(enclosingBlockFlowElement(visibleStartOfParagraph), enclosingNode, true); RefPtr<Node> placeholder = createBreakElement(document()); insertNodeBefore(placeholder, splitBlockquoteNode); moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), VisiblePosition(Position(placeholder.get(), 0)), true); }
void setAffinityUsingLinePosition(VisiblePosition &pos) { // When not moving across line wrap, make sure to end up with DOWNSTREAM affinity. if (pos.isNotNull() && pos.affinity() == UPSTREAM) { VisiblePosition temp(pos); temp.setAffinity(DOWNSTREAM); if (!visiblePositionsOnDifferentLines(temp, pos)) pos.setAffinity(DOWNSTREAM); } }
void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) { Frame& frame = this->frame(); #if PLATFORM(MAC) if (!frame.editor().isContinuousSpellCheckingEnabled() && !frame.editor().isAutomaticQuoteSubstitutionEnabled() && !frame.editor().isAutomaticLinkDetectionEnabled() && !frame.editor().isAutomaticDashSubstitutionEnabled() && !frame.editor().isAutomaticTextReplacementEnabled()) return; if (frame.editor().isHandlingAcceptedCandidate()) return; #else if (!frame.editor().isContinuousSpellCheckingEnabled()) return; #endif // Take a look at the selection that results after typing and determine whether we need to spellcheck. // Since the word containing the current selection is never marked, this does a check to // see if typing made a new word that is not in the current selection. Basically, you // get this by being at the end of a word and typing a space. VisiblePosition start(endingSelection().start(), endingSelection().affinity()); VisiblePosition previous = start.previous(); if (previous.isNotNull()) { #if !PLATFORM(IOS) VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) { RefPtr<Range> range = makeRange(p1, p2); String strippedPreviousWord; if (range && (commandType == TypingCommand::InsertText || commandType == TypingCommand::InsertLineBreak || commandType == TypingCommand::InsertParagraphSeparator || commandType == TypingCommand::InsertParagraphSeparatorInQuotedContent)) strippedPreviousWord = plainText(range.get()).stripWhiteSpace(); frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), !strippedPreviousWord.isEmpty()); } else if (commandType == TypingCommand::InsertText) frame.editor().startAlternativeTextUITimer(); #else UNUSED_PARAM(commandType); // If this bug gets fixed, this PLATFORM(IOS) code could be removed: // <rdar://problem/7259611> Word boundary code on iPhone gives different results than desktop EWordSide startWordSide = LeftWordIfOnBoundary; UChar32 c = previous.characterAfter(); // FIXME: VisiblePosition::characterAfter() and characterBefore() do not emit newlines the same // way as TextIterator, so we do an isEndOfParagraph check here. if (isSpaceOrNewline(c) || c == 0xA0 || isEndOfParagraph(previous)) { startWordSide = RightWordIfOnBoundary; } VisiblePosition p1 = startOfWord(previous, startWordSide); VisiblePosition p2 = startOfWord(start, startWordSide); if (p1 != p2) frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), false); #endif // !PLATFORM(IOS) } }
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; } }
void HTMLTextFormControlElement::setSelectionRange(int start, int end, TextFieldSelectionDirection direction) { document().updateLayoutIgnorePendingStylesheets(); if (!renderer() || !renderer()->isTextControl()) return; end = std::max(end, 0); start = std::min(std::max(start, 0), end); if (!hasVisibleTextArea(*renderer(), innerTextElement())) { cacheSelection(start, end, direction); return; } VisiblePosition startPosition = visiblePositionForIndex(start); VisiblePosition endPosition; if (start == end) endPosition = startPosition; else endPosition = visiblePositionForIndex(end); #if !PLATFORM(IOS) // startPosition and endPosition can be null position for example when // "-webkit-user-select: none" style attribute is specified. if (startPosition.isNotNull() && endPosition.isNotNull()) { ASSERT(startPosition.deepEquivalent().deprecatedNode()->shadowHost() == this && endPosition.deepEquivalent().deprecatedNode()->shadowHost() == this); } #endif VisibleSelection newSelection; if (direction == SelectionHasBackwardDirection) newSelection = VisibleSelection(endPosition, startPosition); else newSelection = VisibleSelection(startPosition, endPosition); newSelection.setIsDirectional(direction != SelectionHasNoDirection); if (Frame* frame = document().frame()) frame->selection().setSelection(newSelection); }
// 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(); VisiblePosition endAfterSelection = endOfParagraph(nextPositionOf(endOfLastParagraph)); while (endOfCurrentParagraph.deepEquivalent() != endAfterSelection.deepEquivalent()) { VisiblePosition endOfNextParagraph = endOfParagraph(nextPositionOf(endOfCurrentParagraph)); if (endOfCurrentParagraph.deepEquivalent() == endOfLastParagraph.deepEquivalent()) setEndingSelection(VisibleSelection(originalSelectionEnd, TextAffinity::Downstream)); else setEndingSelection(endOfCurrentParagraph); 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.deepEquivalent().inShadowIncludingDocument()) break; if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().inShadowIncludingDocument()) { endOfCurrentParagraph = createVisiblePosition(endingSelection().end()); endOfNextParagraph = endOfParagraph(nextPositionOf(endOfCurrentParagraph)); } endOfCurrentParagraph = endOfNextParagraph; } }
void IndentOutdentCommand::indentRegion() { VisibleSelection 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 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); // blockquoteForNextIndent maybe updated // this is due to the way prepareBlockquoteLevelForInsertion was designed. // 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; } RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true); RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true); if (startRange && endRange) setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM)); }
VisiblePosition VisiblePosition::previous() const { VisiblePosition result = VisiblePosition(previousVisiblePosition(m_deepPosition), DOWNSTREAM); #ifndef NDEBUG // we should always be able to make the affinity DOWNSTREAM, because going previous from an // UPSTREAM position can never yield another UPSTREAM position (unless line wrap length is 0!). if (result.isNotNull() && m_affinity == UPSTREAM) { VisiblePosition temp = result; temp.setAffinity(UPSTREAM); ASSERT(!visiblePositionsOnDifferentLines(temp, result)); } #endif return result; }
bool InsertListCommand::selectionHasListOfType(const VisibleSelection& selection, const HTMLQualifiedName& listTag) { VisiblePosition start = selection.visibleStart(); if (!enclosingList(start.deepEquivalent().deprecatedNode())) return false; VisiblePosition end = startOfParagraph(selection.visibleEnd()); while (start.isNotNull() && start != end) { HTMLElement* listElement = enclosingList(start.deepEquivalent().deprecatedNode()); if (!listElement || !listElement->hasTagName(listTag)) return false; start = startOfNextParagraph(start); } return true; }
void TypingCommand::markMisspellingsAfterTyping() { if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled()) return; // Take a look at the selection that results after typing and determine whether we need to spellcheck. // Since the word containing the current selection is never marked, this does a check to // see if typing made a new word that is not in the current selection. Basically, you // get this by being at the end of a word and typing a space. VisiblePosition start(endingSelection().start(), endingSelection().affinity()); VisiblePosition previous = start.previous(); if (previous.isNotNull()) { VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) document()->frame()->editor()->markMisspellingsAfterTypingToPosition(p1); } }