📄 htmlediting_impl.cpp
字号:
Position downstreamStart(selection.start().equivalentDownstreamPosition()); Position upstreamEnd(selection.end().equivalentUpstreamPosition()); Position downstreamEnd(selection.end().equivalentDownstreamPosition()); if (upstreamStart == downstreamEnd) // after collapsing whitespace, selection is empty...no work to do return; Position endingPosition; bool adjustEndingPositionDownstream = false; bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd); bool startCompletelySelected = !onlyWhitespace && (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() && ((downstreamStart.node() != upstreamEnd.node()) || (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset()))); bool endCompletelySelected = !onlyWhitespace && (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() && ((downstreamStart.node() != upstreamEnd.node()) || (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset()))); unsigned long startRenderedOffset = downstreamStart.renderedOffset(); bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement(); bool startAtStartOfBlock = startAtStartOfRootEditableElement || (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock()); bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock(); NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement(); NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement(); bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode(); debugPosition("upstreamStart: ", upstreamStart); debugPosition("downstreamStart: ", downstreamStart); debugPosition("upstreamEnd: ", upstreamEnd); debugPosition("downstreamEnd: ", downstreamEnd); LOG(Editing, "start selected: %s", startCompletelySelected ? "YES" : "NO"); LOG(Editing, "at start block: %s", startAtStartOfBlock ? "YES" : "NO"); LOG(Editing, "at start root block: %s", startAtStartOfRootEditableElement ? "YES" : "NO"); LOG(Editing, "at end block: %s", endAtEndOfBlock ? "YES" : "NO"); LOG(Editing, "only whitespace: %s", onlyWhitespace ? "YES" : "NO"); // Determine where to put the caret after the deletion if (startAtStartOfBlock) { LOG(Editing, "ending position case 1"); endingPosition = Position(startBlock, 0); adjustEndingPositionDownstream = true; } else if (!startCompletelySelected) { LOG(Editing, "ending position case 2"); endingPosition = upstreamStart; if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) adjustEndingPositionDownstream = true; } else if (upstreamStart != downstreamStart) { LOG(Editing, "ending position case 3"); endingPosition = upstreamStart; if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) adjustEndingPositionDownstream = true; } // // Figure out the whitespace conversions to do // if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) { // convert trailing whitespace Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition()); if (trailing.notEmpty()) { debugPosition("convertTrailingWhitespace: ", trailing); Position collapse = trailing.nextCharacterPosition(); if (collapse != trailing) deleteCollapsibleWhitespace(collapse); TextImpl *textNode = static_cast<TextImpl *>(trailing.node()); replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString()); } } else if (!startAtStartOfBlock && endAtEndOfBlock) { // convert leading whitespace Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition()); if (leading.notEmpty()) { debugPosition("convertLeadingWhitespace: ", leading); TextImpl *textNode = static_cast<TextImpl *>(leading.node()); replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString()); } } else if (!startAtStartOfBlock && !endAtEndOfBlock) { // convert contiguous whitespace Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition()); Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition()); if (leading.notEmpty() && trailing.notEmpty()) { debugPosition("convertLeadingWhitespace [contiguous]: ", leading); TextImpl *textNode = static_cast<TextImpl *>(leading.node()); replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString()); } } // // Do the delete // NodeImpl *n = downstreamStart.node()->traverseNextNode(); // work on start node if (startCompletelySelected) { LOG(Editing, "start node delete case 1"); removeNodeAndPrune(downstreamStart.node(), startBlock); } else if (onlyWhitespace) { // Selection only contains whitespace. This is really a special-case to // handle significant whitespace that is collapsed at the end of a line, // but also handles deleting a space in mid-line. LOG(Editing, "start node delete case 2"); ASSERT(upstreamStart.node()->isTextNode()); TextImpl *text = static_cast<TextImpl *>(upstreamStart.node()); int offset = upstreamStart.offset(); // EDIT FIXME: Signed/unsigned mismatch int length = text->length(); if (length == upstreamStart.offset()) offset--; deleteText(text, offset, 1); } else if (downstreamStart.node()->isTextNode()) { LOG(Editing, "start node delete case 3"); TextImpl *text = static_cast<TextImpl *>(downstreamStart.node()); int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length(); if (endOffset > downstreamStart.offset()) { deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset()); } } else { // we have clipped the end of a non-text element // the offset must be 1 here. if it is, do nothing and move on. LOG(Editing, "start node delete case 4"); ASSERT(downstreamStart.offset() == 1); } if (!onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) { // work on intermediate nodes while (n != upstreamEnd.node()) { NodeImpl *d = n; n = n->traverseNextNode(); if (d->renderer() && d->renderer()->isEditable()) removeNodeAndPrune(d, startBlock); } // work on end node ASSERT(n == upstreamEnd.node()); if (endCompletelySelected) { removeNodeAndPrune(upstreamEnd.node(), startBlock); } else if (upstreamEnd.node()->isTextNode()) { if (upstreamEnd.offset() > 0) { TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node()); deleteText(text, 0, upstreamEnd.offset()); } } else { // we have clipped the beginning of a non-text element // the offset must be 0 here. if it is, do nothing and move on. ASSERT(downstreamStart.offset() == 0); } } // Do block merge if start and end of selection are in different blocks // and the blocks are siblings. This is a first cut at this rule arrived // at by doing a bunch of edits and settling on the behavior that made // the most sense. This could change in the future as we get more // experience with how this should behave. if (startBlock != endBlock && startBlockEndBlockAreSiblings) { LOG(Editing, "merging content to start block"); NodeImpl *node = endBlock->firstChild(); while (node) { NodeImpl *moveNode = node; node = node->nextSibling(); removeNode(moveNode); appendNode(startBlock, moveNode); } } if (adjustEndingPositionDownstream) { LOG(Editing, "adjust ending position downstream"); endingPosition = endingPosition.equivalentDownstreamPosition(); } debugPosition("ending position: ", endingPosition); setEndingSelection(endingPosition); LOG(Editing, "-----------------------------------------------------\n");}//------------------------------------------------------------------------------------------// DeleteTextCommandImplDeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count) : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count){ ASSERT(m_node); ASSERT(m_offset >= 0); ASSERT(m_count >= 0); m_node->ref();}DeleteTextCommandImpl::~DeleteTextCommandImpl(){ if (m_node) m_node->deref();}int DeleteTextCommandImpl::commandID() const{ return DeleteTextCommandID;}void DeleteTextCommandImpl::doApply(){ ASSERT(m_node); int exceptionCode = 0; m_text = m_node->substringData(m_offset, m_count, exceptionCode); ASSERT(exceptionCode == 0); m_node->deleteData(m_offset, m_count, exceptionCode); ASSERT(exceptionCode == 0);}void DeleteTextCommandImpl::doUnapply(){ ASSERT(m_node); ASSERT(!m_text.isEmpty()); int exceptionCode = 0; m_node->insertData(m_offset, m_text, exceptionCode); ASSERT(exceptionCode == 0);}//------------------------------------------------------------------------------------------// InputNewlineCommandImplInputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document) : CompositeEditCommandImpl(document){}InputNewlineCommandImpl::~InputNewlineCommandImpl() {}int InputNewlineCommandImpl::commandID() const{ return InputNewlineCommandID;}void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos){ // Insert the BR after the caret position. In the case the // position is a block, do an append. We don't want to insert // the BR *after* the block. Position upstream(pos.equivalentUpstreamPosition()); NodeImpl *cb = pos.node()->enclosingBlockFlowElement(); if (cb == pos.node()) appendNode(cb, node); else insertNodeAfter(node, pos.node());}void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos){ // Insert the BR after the caret position. In the case the // position is a block, do an append. We don't want to insert // the BR *before* the block. Position upstream(pos.equivalentUpstreamPosition()); NodeImpl *cb = pos.node()->enclosingBlockFlowElement(); if (cb == pos.node()) appendNode(cb, node); else insertNodeBefore(node, pos.node());}void InputNewlineCommandImpl::doApply(){ deleteSelection(); Selection selection = endingSelection(); int exceptionCode = 0; ElementImpl *breakNode = document()->createHTMLElement("BR", exceptionCode); ASSERT(exceptionCode == 0); NodeImpl *nodeToInsert = breakNode; // Handle the case where there is a typing style. if (document()->part()->typingStyle()) { int exceptionCode = 0; ElementImpl *styleElement = createTypingStyleElement(); styleElement->appendChild(breakNode, exceptionCode); ASSERT(exceptionCode == 0); nodeToInsert = styleElement; } Position pos(selection.start().equivalentDownstreamPosition()); bool atStart = pos.offset() <= pos.node()->caretMinOffset(); bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock(); if (atEndOfBlock) { LOG(Editing, "input newline case 1"); // Insert an "extra" BR at the end of the block. This makes the "real" BR we want // to insert appear in the rendering without any significant side effects (and no // real worries either since you can't arrow past this extra one. insertNodeAfterPosition(nodeToInsert, pos); exceptionCode = 0; ElementImpl *extraBreakNode = document()->createHTMLElement("BR", exceptionCode); ASSERT(exceptionCode == 0); insertNodeAfter(extraBreakNode, nodeToInsert); setEndingSelection(Position(extraBreakNode, 0)); } else if (atStart) { LOG(Editing, "input newline case 2"); // Insert node, but place the caret into index 0 of the downstream // position. This will make the caret appear after the break, and as we know // there is content at that location, this is OK. insertNodeBeforePosition(nodeToInsert, pos); setEndingSelection(Position(pos.node(), 0)); } else { // Split a text node LOG(Editing, "input newline case 3"); ASSERT(pos.node()->isTextNode()); TextImpl *textNode = static_cast<TextImpl *>(pos.node()); TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode)); deleteText(textNode, 0, selection.start().offset()); insertNodeBefore(textBeforeNode, textNode); insertNodeBefore(nodeToInsert, textNode); setEndingSelection(Position(textNode, 0)); }}//------------------------------------------------------------------------------------------// InputTextCommandImplInputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document) : CompositeEditCommandImpl(document), m_charactersAdded(0){}InputTextCommandImpl::~InputTextCommandImpl() {}int InputTextCommandImpl::commandID() const{ return InputTextCommandID;}void InputTextCommandImpl::doApply(){}void InputTextCommandImpl::input(const DOMString &text){ execute(text);}void InputTextCommandImpl::deleteCharacter(){ ASSERT(state() == Applied); Selection selection = endingSelection(); if (!selection.start().node()->isTextNode()) return; int exceptionCode = 0; int offset = selection.start().offset() - 1; if (offset >= selection.start().node()->caretMinOffset()) { TextImpl *textNode = static_cast<TextImpl *>(selection.start().node()); textNode->deleteData(offset, 1, exceptionCode); ASSERT(exceptionCode == 0); selection = Selection(Position(textNode, offset)); setEndingSelection(selection); m_charactersAdded--; }}Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream){ // Prepare for text input by looking at the current position. // It may be necessary to insert a text node to receive characters. Selection selection = endingSelection(); ASSERT(selection.state() == Selection::CARET);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -