QString QueryPanel::getActiveStatement(int block, int col) { int from = 0, to = -1; State st; QTextBlock b = editor->document()->findBlockByNumber(block); int scol = col; while(b.isValid()) { st.opaque = b.userState(); if(st.s.col != -1 && st.s.col < scol) { from = b.position() + st.s.col + 1; break; } scol = INT_MAX; b = b.previous(); } b = editor->document()->findBlockByNumber(block); scol = col; while(b.isValid()) { st.opaque = b.userState(); if(st.s.col != -1 && st.s.col >= scol) { to = b.position() + st.s.col; break; } scol = 0; b = b.next(); } QString all = editor->document()->toPlainText(); if(to < 0) to = all.length(); return all.mid(from,to);; }
void DevHighlighter::reformatBlocks(int from, int charsRemoved, int charsAdded) { QTextBlock block = doc->findBlock(from); if (!block.isValid()) return; QTextBlock endBlock; if (charsAdded > charsRemoved || charsAdded == charsRemoved) endBlock = doc->findBlock(from + charsAdded); else endBlock = block; bool forceHighlightOfNextBlock = false; while (block.isValid() && (!(endBlock < block) || forceHighlightOfNextBlock) ) { const int stateBeforeHighlight = block.userState(); reformatBlock(block); forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight); block = block.next(); } formatChanges.clear(); }
void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded) { Q_UNUSED(charsRemoved); rehighlightPending = false; QTextBlock block = doc->findBlock(from); if (!block.isValid()) return; int endPosition; QTextBlock lastBlock = doc->findBlock(from + charsAdded); if (lastBlock.isValid()) endPosition = lastBlock.position() + lastBlock.length(); else endPosition = doc->docHandle()->length(); bool forceHighlightOfNextBlock = false; while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) { const int stateBeforeHighlight = block.userState(); reformatBlock(block); forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight); block = block.next(); } formatChanges.clear(); }
void Indenter::indentBlock(const QTextBlock &block, const QChar &, const TextEditor::TabSettings &settings, int) { int indent; QTextBlock previous = block.previous(); // Previous line ends on comma, ignore everything and follow the indent if (previous.text().endsWith(',')) { indent = previous.text().indexOf(QRegularExpression("\\S")) / settings.m_indentSize; } else { // Use the stored indent plus some bizarre heuristics that even myself remember how it works. indent = block.userState() >> 20; if (indent < 0) { while (indent == -1 && previous.isValid()) { indent = previous.userState() >> 20; previous = previous.previous(); } } if (didBlockStart(block) && indent > 0) indent--; } settings.indentLine(block, indent * settings.m_indentSize); }
// Reset the header state of a here-doc to 7 // and the state of its body to 6. void Highlighter::resetHereDocStates (QTextBlock block) { if (block.userState() == hereDocTempState) { block.setUserState (hereDocHeaderState); block = block.next(); while (block.isValid() && block.userState() != 0) { block.setUserState (hereDocBodyState); block = block.next(); } } }
int BaseTextDocumentLayout::braceDepth(const QTextBlock &block) { int state = block.userState(); if (state == -1) return 0; return state >> 8; }
void ScriptFormatter::enforceFormatting( QQuickTextDocument* document ) { //The script doesn't quite look right when loaded from a file, so we call this function. if( document != nullptr && document != NULL ) { for( QTextBlock block = document->textDocument()->firstBlock(); block != document->textDocument()->end(); block = block.next() ) { setParagraphType( document, ( ScriptFormatter::paragraphType ) block.userState(), block.position() ); } } }
void BaseTextDocumentLayout::setBraceDepth(QTextBlock &block, int depth) { int state = block.userState(); if (state == -1) state = 0; state = state & 0xff; block.setUserState((depth << 8) | state); }
int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex) { QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos); const QTextFragmentData * const frag = fragIt.value(); Q_ASSERT(objectIndex == -1 || (frag->size_array[0] == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1)); int charFormatIndex; if (forceCharFormat) charFormatIndex = primaryCharFormatIndex; else charFormatIndex = convertFormatIndex(frag->format, objectIndex); const int inFragmentOffset = qMax(0, pos - fragIt.position()); int charsToCopy = qMin(int(frag->size_array[0] - inFragmentOffset), endPos - pos); QTextBlock nextBlock = src->blocksFind(pos + 1); int blockIdx = -2; if (nextBlock.position() == pos + 1) { blockIdx = convertFormatIndex(nextBlock.blockFormat()); } else if (pos == 0 && insertPos == 0) { dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat()); dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat()); } QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy); if (txtToInsert.length() == 1 && (txtToInsert.at(0) == QChar::ParagraphSeparator || txtToInsert.at(0) == QTextBeginningOfFrame || txtToInsert.at(0) == QTextEndOfFrame ) ) { dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex); ++insertPos; } else { if (nextBlock.textList()) { QTextBlock dstBlock = dst->blocksFind(insertPos); if (!dstBlock.textList()) { // insert a new text block with the block and char format from the // source block to make sure that the following text fragments // end up in a list as they should int listBlockFormatIndex = convertFormatIndex(nextBlock.blockFormat()); int listCharFormatIndex = convertFormatIndex(nextBlock.charFormat()); dst->insertBlock(insertPos, listBlockFormatIndex, listCharFormatIndex); ++insertPos; } } dst->insert(insertPos, txtToInsert, charFormatIndex); const int userState = nextBlock.userState(); if (userState != -1) dst->blocksFind(insertPos).setUserState(userState); insertPos += txtToInsert.length(); } return charsToCopy; }
void AssistInterface::prepareForAsyncUse() { m_text = m_textDocument->toPlainText(); m_userStates.reserve(m_textDocument->blockCount()); for (QTextBlock block = m_textDocument->firstBlock(); block.isValid(); block = block.next()) m_userStates.append(block.userState()); m_textDocument = 0; m_isAsync = true; }
void TextEditor::pcChanged(ParseNode *pc, bool justRolledBack) { ParseNode *old = m_pc; // cerr << "\tpcChanged id:" << QThread::currentThreadId() << "\n"; if (old == pc) return; m_pc = pc; // if (m_program->getStatus() != PAUSED) // return; if (old != NULL) { QTextBlock *b = old->getTextBlock(); int state = b->userState(); if (state > 0) b->setUserState(state & ~B_CURRENT_PC); } if (m_pc != NULL) { QTextBlock *b = m_pc->getTextBlock(); // cerr << "b: " << b->text().toStdString() << endl; int state = b->userState(); if (state < 0) b->setUserState(B_CURRENT_PC); else b->setUserState(state | B_CURRENT_PC); if (!justRolledBack && b != NULL) { QTextCursor c = textCursor(); c.setPosition(b->position()); setTextCursor(c); ensureCursorVisible(); } } m_parent->updateLineNumbers(0); // the 2 can be changed according to preferences later // just be sure to also change the fading factors in paintEvent. m_program->getLastXInstructions(Options::noPreviousXInstructions(), m_lastInstructions); viewport()->update(); }
int ExpressionUnderCursor::previousBlockState(const QTextBlock &block) { const QTextBlock prevBlock = block.previous(); if (prevBlock.isValid()) { int state = prevBlock.userState(); if (state != -1) return state; } return 0; }
/* Returns true if the start of the bottom line of yyProgram (and potentially the whole line) is part of a C-style comment; otherwise returns false. */ bool LineInfo::bottomLineStartsInMultilineComment() { QTextBlock currentLine = yyProgram.lastBlock().previous(); QTextBlock previousLine = currentLine.previous(); int startState = qMax(0, previousLine.userState()) & 0xff; if (startState > 0) return true; return false; }
/** * Parses a text document and builds the navigation tree for it */ void NavigationWidget::parse(QTextDocument *document) { const QSignalBlocker blocker(this); Q_UNUSED(blocker); setDocument(document); clear(); _lastHeadingItemList.clear(); for (int i = 0; i < document->blockCount(); i++) { QTextBlock block = document->findBlockByNumber(i); int elementType = block.userState(); // ignore all non headline types if ((elementType < pmh_H1) || (elementType > pmh_H6)) { continue; } QString text = block.text(); text.remove(QRegularExpression("^#+")) .remove(QRegularExpression("#+$")) .remove(QRegularExpression("^\\s+")) .remove(QRegularExpression("^=+$")) .remove(QRegularExpression("^-+$")); if (text.isEmpty()) { continue; } QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, text); item->setData(0, Qt::UserRole, block.position()); item->setToolTip(0, tr("headline %1").arg(elementType - pmh_H1 + 1)); // attempt to find a suitable parent item for the element type QTreeWidgetItem *lastHigherItem = findSuitableParentItem(elementType); if (lastHigherItem == NULL) { // if there wasn't a last higher level item then add the current // item to the top level addTopLevelItem(item); } else { // if there was a last higher level item then add the current // item as child of that item lastHigherItem->addChild(item); } _lastHeadingItemList[elementType] = item; } expandAll(); }
int BackwardsScanner::previousBlockState(const QTextBlock &block) { const QTextBlock prevBlock = block.previous(); if (prevBlock.isValid()) { int state = prevBlock.userState(); if (state != -1) return state; } return 0; }
void CodeEditor::foldUnfold(QTextBlock& block) { int state = block.userState(); if (state & Error || state & End || !(state & Begin) || // (state & End) для однострочного блока () document()->lastBlock() == block) return; bool unfolding = state & Folded; if (unfolding) block.setUserState(block.userState() & ~Folded); else block.setUserState(block.userState() | Folded); int previousBlockState = block.previous().userState(); int endBraceDepth = previousBlockState & Error ? 0 : previousBlockState >> StateShift; int braceDepth; int skipDepth = 0; while ((block = block.next()).isValid()) { int state = block.userState(); braceDepth = state >> StateShift; if (unfolding) { if (state & Begin && !skipDepth && state & Folded) { skipDepth = block.previous().userState() >> StateShift; } else if (skipDepth) { if (braceDepth == skipDepth) skipDepth = 0; continue; } }
void CodeEditor::paintEvent(QPaintEvent* event) { QPlainTextEdit::paintEvent(event); QPainter painter(viewport()); painter.setPen(Qt::darkGray); QTextBlock block = firstVisibleBlock(); QRectF rect; do { if (!block.isVisible()) continue; rect = blockBoundingGeometry(block).translated(contentOffset()); QTextLine line = block.layout()->lineAt(0); if (config->whitespaces) { QString txt = block.text(); for (int i = 0; i < txt.length(); i++) { // rect.x() <- учитывая горизонтальный скролинг QPoint point(rect.x() + line.cursorToX(i), rect.y() + line.ascent()); if (txt[i] == ' ') painter.drawText(point, QChar(0x00b7)); else if (txt[i] == '\t') painter.drawText(point, QChar(0x21b9)); } } int state = block.userState(); if (!(state & Error) && state & Folded) { QRect collapseRect(rect.x() + line.rect().x() + line.naturalTextWidth() + FONTWIDTH * 2, rect.y() + 2, FONTWIDTH * 6, line.height() - 4); painter.drawText(collapseRect, Qt::AlignCenter, state & Comment ? "...;" : "...)"); painter.drawRoundedRect(collapseRect, 4, 6); } } while ((block = block.next()).isValid() && rect.y() < viewport()->height()); }
void CodeEditor::mouseMoveEvent(QMouseEvent* event) { QTextBlock block = findBlockByY(event->pos().y()); QRect collapseRect; if (block.isValid()) { QRectF rect = blockBoundingGeometry(block).translated(contentOffset()); QTextLine line = block.layout()->lineAt(0); collapseRect = QRect(rect.x() + line.rect().x() + line.naturalTextWidth() + FONTWIDTH * 2, rect.y() + 2, FONTWIDTH * 6, line.height() - 4); } int state = block.userState(); if (!(state & Error) && state & Folded && collapseRect.contains(event->pos())) { pointedBlock = block; viewport()->setCursor(Qt::PointingHandCursor); QString str; while ((block = block.next()).isValid() && !block.isVisible()) { if (str.count() > 1) str += "\n"; if (block.blockNumber() - pointedBlock.blockNumber() > 50) { str += "..."; // "\n..."; break; } str += block.text(); } QToolTip::showText(event->globalPos(), str, this); } else { pointedBlock = QTextBlock(); viewport()->setCursor(Qt::IBeamCursor); } QPlainTextEdit::mouseMoveEvent(event); }
bool QssCompletionAssistProcessor::isInComment() const { QTextBlock block = m_interface->textDocument()->findBlock(m_interface->position()); QTextBlock prevBlock = block.previous(); QList<Token> tokenList; Lexer lexer; lexer.setScanComments(true); if (prevBlock.isValid()) lexer.setState(qssLexerState(prevBlock.userState())); tokenList = lexer.scanMore(block.text()); int index = m_interface->position() - block.position(); Q_FOREACH (const Token &t, tokenList) { if (t.is(Token::Comment) && index >= t.begin() && index < t.end()) { qDebug() << "InComment"; return true; } } if (index == block.length() && (lexer.state() & Lexer::MultiLineComment)) { qDebug() << "InComment"; return true; } return false; }
IAssistProposal *QssCompletionAssistProcessor::perform(const IAssistInterface *interface) { m_interface.reset(interface); if (isInComment()) return 0; if (interface->reason() == IdleEditor && !acceptsIdleEditor()) return 0; if (m_startPosition == -1) m_startPosition = findStartOfName(); QTextBlock block = interface->textDocument()->findBlock(interface->position()); QTextBlock prevBlock = block.previous(); Lexer lexer; Parser parser; if (prevBlock.isValid()) { lexer.setState(qssLexerState(prevBlock.userState())); parser.setState(qssParserState(prevBlock.userState())); } QList<Token> tokenList = lexer.scanMore(block.text()); int index = m_startPosition - block.position(); Q_FOREACH (const Token &t, tokenList) { if (index <= t.begin()) break; parser.parseMore(t); } QStringList keywords; QIcon icon; switch (parser.state()) { case Parser::ObjectState: keywords << Lexer::objects(); icon = QIcon(":/qsseditor/images/class.png"); break; case Parser::PseudoStatesState: keywords << Lexer::pseudoStates(); icon = QIcon(":/qsseditor/images/func.png"); break; case Parser::SubControlState: keywords << Lexer::subControls(); icon = QIcon(":/qsseditor/images/func.png"); break; case Parser::AttributeNameState: keywords << Lexer::attributeNames(); icon = QIcon(":/qsseditor/images/var.png"); break; case Parser::AttributeBodyState: keywords << Lexer::attributeCtors() << Lexer::attributeKeywords(); icon = QIcon(":/qsseditor/images/keyword.png"); break; default: ; } keywords.removeDuplicates(); QList<TextEditor::BasicProposalItem *> items; for (int i = 0; i < keywords.count(); i++) { BasicProposalItem *item = new QssAssistProposalItem; item->setText(keywords[i]); item->setIcon(icon); items.append(item); } return new GenericProposal(m_startPosition, new QssAssistProposalModel(items)); }
// Changing of the start delimiter of a "here documnet" doesn't invoke // highlightBlock() for the rest of it, hence the need for this slot. // I don't like its complex logic but it works. void Highlighter::docChanged (int pos, int, int) { QTextBlock block = qobject_cast< QTextDocument *>(parent())->findBlock (pos); TextBlockData *data = static_cast<TextBlockData *>(block.userData()); /* at start or end (state = 7 or 0 or < 6), or inside but previously at end (state = 6), or outside but previously at start (state = 0 or < 6): these are all states that we need to check */ if (data->isdelimiter()) { /******************************************************************* * It's important to know that this method is always called after * * highlightBlock. There are three possibilities: state = 0, 6, 7. * * Other values are, like 0, related to a block outside here-docs. * *******************************************************************/ /* when we're inside a here-doc, go up to its header */ if (block.userState() == hereDocBodyState) { while (block.isValid()) { block = block.previous(); if (block.userState() == hereDocHeaderState) break; } } /* when we're before, after or at end of a here-doc... */ if (block.userState() != hereDocHeaderState) { /* ... the header state of the next here-doc should be 7; so, go to it and then do as for state 7 below */ block = block.next(); while (block.isValid() && block.userState() != hereDocHeaderState) block = block.next(); } /* when this is a header (state = 7)... */ if (block.isValid()) { /* ... change the header state to 9 for this here-doc to be rehighlighted completely (for the highlighting downward 'wave' to go through it) */ delimState = hereDocTempState; rehighlightBlock (block); /* then reset its block states... */ resetHereDocStates (block); /* ... and also reset the global header state */ delimState = hereDocHeaderState; } /* now, the header states of some here-docs below this one may be 9; so, reset them */ block = block.next(); while (block.isValid()) { while (block.isValid() && block.userState() != hereDocTempState) block = block.next(); if (block.isValid()) { resetHereDocStates (block); block = block.next(); } } } }
void CodeEditor::contentsChange(int pos, int, int) { QTextBlock block = textCursor().block(); // сдвиг блока клавишей Return // состояние предыдущего блока может быть не верным if (block.userState() == Empty) block.setUserState(block.previous().userState() | Rehighligh); block = document()->findBlock(pos); int startBlockNum = block.blockNumber(); bool forceUnfold = false; while (block.isValid()) { int previousState = block.userState(); int state = setBlockState(block); if (!(previousState & Error) && !(state & Error) && previousState & Comment && state & Comment) { QTextBlock next = block.next(); int nextBlockState = next.userState(); setBlockState(next); next.setUserState(nextBlockState); // в начальное состояние // правильное состояние комментария известно // только после обработки последующей строки state = block.userState(); } if (state != previousState) { if (!forceUnfold) { // разворачиваем предыдущие блоки QTextBlock previous = block.previous(); while (previous.isValid()) { int state = previous.userState(); if (!(state & Error) && state & Folded) previous.setUserState(state & ~Folded); if (state & Error || !(state & Nested) || previous.isVisible()) break; previous.setVisible(true); previous = previous.previous(); } } forceUnfold = true; } else if (block.blockNumber() > startBlockNum && // не начальный блок state & Begin && !(state & Nested)) { // (state & Begin ...) пропускаем End главного блока break; // без state & Error, иначе срабатывает между блоками } // при первом обновлении if (forceUnfold) { // разворачиваем последующие блоки if (!(state & Error) && state & Folded) state &= ~Folded; block.setVisible(true); } block.setUserState(state | Rehighligh); block = block.next(); } }
int CodeEditor::setBlockState(QTextBlock& block) { int previousBlockState = block.previous().userState(); if (previousBlockState & Error) previousBlockState = 0; bool inString = previousBlockState & String; int previousBraceDepth = previousBlockState >> StateShift; int braceDepth = previousBraceDepth; QString txt = block.text(); int state = 0; int i = 0; while (txt[i].isSpace()) ++i; if (txt[i] == ';') { if (!previousBraceDepth || previousBlockState & Comment) { state |= Comment; // только за предалами блока кода if (previousBlockState & Comment) { if (braceDepth) { braceDepth--; } else { previousBraceDepth = 1; previousBlockState = (previousBlockState & End ? Nested : Begin) | (previousBlockState & Folded) | (previousBlockState & Rehighligh); previousBlockState |= (previousBraceDepth << StateShift) | Comment; block.previous().setUserState(previousBlockState); } state |= End; } } } else { // коррекция блока после удаления строки комментария if (previousBlockState & Comment && previousBlockState & (Nested | Begin)) { previousBraceDepth = 0; braceDepth = 0; previousBlockState = previousBlockState & Nested ? End : 0; previousBlockState |= (previousBraceDepth << StateShift) | Comment; block.previous().setUserState(previousBlockState); } if (txt[i].isNull() && !previousBraceDepth) { // пустая строка //qDebug() << "*** Error #1 'empty string'"; state = Empty; } else if (txt[i] != '(' && !previousBraceDepth) { // начальный символ вне блока не '(' или ';' //qDebug() << "*** Error #2 'bad begin'"; braceDepth = i; // позиция начала ошибки state |= Error; } else { while (i < txt.length()) { if (txt[i] == '"' && !(i && txt[i - 1] == '\\')) { inString = !inString; } else if (!inString) { if (txt[i] == ';') // комментарий в блоке кода break; if (txt[i] == '(') braceDepth++; else if (txt[i] == ')') braceDepth--; if (braceDepth == -1) { //qDebug() << "*** Error #3 ') mismatch'"; braceDepth = i; state |= Error; break; } } i++; } // while } } if (!(state & (Error | Comment))) { if (inString) state |= String; if (braceDepth > previousBraceDepth || !previousBraceDepth) state |= Begin; if (braceDepth < previousBraceDepth || !braceDepth) state |= End; if (previousBraceDepth && braceDepth) state |= Nested; } state |= (braceDepth << StateShift); int previousState = block.userState(); if (previousState != Empty) state |= (previousState & Folded) | (previousState & Rehighligh); // qDebug("Block[%d]\n" // "\tDepth %d/%d State %d/%d New %d", // block.blockNumber(), // braceDepth, previousBraceDepth, // state, block.userState(), // (bool)(previousState != state)); // if (state & Error) // qDebug("\tError position %d", braceDepth); // else // qDebug("\tEnd:%d Begin:%d String:%d Comment:%d Nested:%d Folded:%d", // (bool)(state & End), (bool)(state & Begin), // (bool)(state & String), (bool)(state & Comment), // (bool)(state & Nested), (bool)(state & Folded)); // if (previousState & Error) // qDebug("\t[P] Error position %d", braceDepth); // else // qDebug("\t[P] End:%d Begin:%d String:%d Comment:%d Nested:%d Folded:%d", // (bool)(previousState & End), (bool)(previousState & Begin), // (bool)(previousState & String), (bool)(previousState & Comment), // (bool)(previousState & Nested), (bool)(previousState & Folded)); block.setUserState(state); return state; }
void CodeEditor::extraAreaPaintEvent() { QTextBlock block = firstVisibleBlock(); QPainter painter(extraArea); QPen pen = painter.pen(); QFont font = painter.font(); bool bold = font.bold(); int y = 0; int cx = lineNumWidth + foldBoxIndent + foldBoxWidth / 2; // центр маркера блока по x do { if (!block.isVisible()) continue; QRectF rect = blockBoundingGeometry(block); y = rect.translated(contentOffset()).y(); if (block == textCursor().block()) { painter.setPen(Qt::yellow); font.setBold(!bold); } else { font.setBold(bold); } painter.setFont(font); painter.drawText(0, y, lineNumWidth, fontMetrics().height(), Qt::AlignRight, QString::number(block.blockNumber() + 1)); // номер строки painter.setPen(pen); int state = block.userState(); int cy = y + fontMetrics().height() / 2; // центр маркера по Y if (!(state & Error) && !(state & Begin && state & End && !(state & Nested))) { if (state & Begin) { if (state & Comment) painter.drawEllipse(FOLDBOXRECT(cy)); else painter.drawRoundedRect(FOLDBOXRECT(cy), 7, 3); if (!(!(state & Nested) && state & Folded)) painter.drawLine(cx, cy + foldBoxWidth / 2, cx, y + rect.height()); // края маркера вниз if (state & Nested) painter.drawLine(cx, y, cx, cy - foldBoxWidth / 2); // края маркера вверх painter.drawLine(cx - foldBoxLength, cy, cx + foldBoxLength, cy); // горизонтальная линия внутри маркера if (state & Folded) painter.drawLine(cx, cy - foldBoxLength, cx, cy + foldBoxLength); // вертикальная линия внутри маркера } else if (state & End) { painter.drawLine(cx, cy, cx + foldBoxLength, cy); // от центра вправо if (state & Nested) painter.drawLine(cx, y, cx, y + rect.height()); // вертикальная линия else painter.drawLine(cx, y, cx, cy); // от центра вверх } else if (state & Nested) { painter.drawLine(cx, y, cx, y + rect.height()); // вертикальная линия } } // в paintEvent не работает if (state != Empty && state & Rehighligh) { highlighter->rehighlightBlock(block); block.setUserState(state & ~Rehighligh); } } while ((block = block.next()).isValid() && y < viewport()->height()); }
void VMdEditor::updateHeaders(const QVector<VElementRegion> &p_headerRegions) { QTextDocument *doc = document(); QVector<VTableOfContentItem> headers; QVector<int> headerBlockNumbers; QVector<QString> headerSequences; if (!p_headerRegions.isEmpty()) { headers.reserve(p_headerRegions.size()); headerBlockNumbers.reserve(p_headerRegions.size()); headerSequences.reserve(p_headerRegions.size()); } // Assume that each block contains only one line // Only support # syntax for now QRegExp headerReg(VUtils::c_headerRegExp); int baseLevel = -1; for (auto const & reg : p_headerRegions) { QTextBlock block = doc->findBlock(reg.m_startPos); if (!block.isValid()) { continue; } if (!block.contains(reg.m_endPos - 1)) { qWarning() << "header accross multiple blocks, starting from block" << block.blockNumber() << block.text(); } if ((block.userState() == HighlightBlockState::Normal) && headerReg.exactMatch(block.text())) { int level = headerReg.cap(1).length(); VTableOfContentItem header(headerReg.cap(2).trimmed(), level, block.blockNumber(), headers.size()); headers.append(header); headerBlockNumbers.append(block.blockNumber()); headerSequences.append(headerReg.cap(3)); if (baseLevel == -1) { baseLevel = level; } else if (baseLevel > level) { baseLevel = level; } } } m_headers.clear(); bool autoSequence = m_config.m_enableHeadingSequence && !isReadOnly() && m_file->isModifiable(); int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel(); if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) { headingSequenceBaseLevel = 1; } QVector<int> seqs(7, 0); QRegExp preReg(VUtils::c_headerPrefixRegExp); int curLevel = baseLevel - 1; for (int i = 0; i < headers.size(); ++i) { VTableOfContentItem &item = headers[i]; while (item.m_level > curLevel + 1) { curLevel += 1; // Insert empty level which is an invalid header. m_headers.append(VTableOfContentItem(c_emptyHeaderName, curLevel, -1, m_headers.size())); if (autoSequence) { addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel); } } item.m_index = m_headers.size(); m_headers.append(item); curLevel = item.m_level; if (autoSequence) { addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel); QString seqStr = headerSequenceStr(seqs); if (headerSequences[i] != seqStr) { // Insert correct sequence. insertSequenceToHeader(doc->findBlockByNumber(headerBlockNumbers[i]), headerReg, preReg, seqStr); } } } emit headersChanged(m_headers); updateCurrentHeader(); }