mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
1013 lines
26 KiB
C++
1013 lines
26 KiB
C++
#include "veditutils.h"
|
|
|
|
#include <QTextDocument>
|
|
#include <QDebug>
|
|
#include <QTextEdit>
|
|
#include <QPlainTextEdit>
|
|
#include <QScrollBar>
|
|
|
|
#include "vutils.h"
|
|
|
|
void VEditUtils::removeBlock(QTextBlock &p_block, QString *p_text)
|
|
{
|
|
QTextCursor cursor(p_block);
|
|
removeBlock(cursor, p_text);
|
|
}
|
|
|
|
void VEditUtils::removeBlock(QTextCursor &p_cursor, QString *p_text)
|
|
{
|
|
const QTextDocument *doc = p_cursor.document();
|
|
int blockCount = doc->blockCount();
|
|
int blockNum = p_cursor.block().blockNumber();
|
|
|
|
p_cursor.select(QTextCursor::BlockUnderCursor);
|
|
if (p_text) {
|
|
*p_text = selectedText(p_cursor) + "\n";
|
|
}
|
|
|
|
p_cursor.deleteChar();
|
|
|
|
// Deleting the first block will leave an empty block.
|
|
// Deleting the last empty block will not work with deleteChar().
|
|
if (blockCount == doc->blockCount()) {
|
|
if (blockNum == blockCount - 1) {
|
|
// The last block.
|
|
p_cursor.deletePreviousChar();
|
|
} else {
|
|
p_cursor.deleteChar();
|
|
}
|
|
}
|
|
|
|
if (p_cursor.block().blockNumber() < blockNum) {
|
|
p_cursor.movePosition(QTextCursor::NextBlock);
|
|
}
|
|
|
|
p_cursor.movePosition(QTextCursor::StartOfBlock);
|
|
}
|
|
|
|
bool VEditUtils::insertBlockWithIndent(QTextCursor &p_cursor)
|
|
{
|
|
V_ASSERT(!p_cursor.hasSelection());
|
|
p_cursor.insertBlock();
|
|
return indentBlockAsBlock(p_cursor, false);
|
|
}
|
|
|
|
bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor)
|
|
{
|
|
bool ret = false;
|
|
QTextBlock block = p_cursor.block();
|
|
QTextBlock preBlock = block.previous();
|
|
if (!preBlock.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
QString text = preBlock.text();
|
|
QRegExp regExp(VUtils::c_listRegExp);
|
|
int regIdx = regExp.indexIn(text);
|
|
if (regIdx != -1) {
|
|
ret = true;
|
|
V_ASSERT(regExp.captureCount() == 1);
|
|
QString markText = regExp.capturedTexts()[1];
|
|
if (isListBullet(markText)) {
|
|
// Insert bullet in front.
|
|
p_cursor.insertText(markText + " ");
|
|
} else {
|
|
// markText is like "123.".
|
|
V_ASSERT(markText.endsWith('.'));
|
|
bool ok = false;
|
|
int num = markText.left(markText.size() - 1).toInt(&ok, 10);
|
|
V_ASSERT(ok);
|
|
num++;
|
|
p_cursor.insertText(QString::number(num, 10) + ". ");
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
bool VEditUtils::indentBlockAsBlock(QTextCursor &p_cursor, bool p_next)
|
|
{
|
|
QTextBlock block = p_cursor.block();
|
|
QTextBlock refBlock = p_next ? block.next() : block.previous();
|
|
return indentBlockAsBlock(p_cursor, refBlock);
|
|
}
|
|
|
|
bool VEditUtils::indentBlockAsBlock(QTextCursor &p_cursor, const QTextBlock &p_refBlock)
|
|
{
|
|
if (!p_refBlock.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
Q_ASSERT(!p_cursor.hasSelection());
|
|
|
|
bool changed = false;
|
|
QString leadingSpaces = fetchIndentSpaces(p_refBlock);
|
|
|
|
moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::MoveAnchor);
|
|
if (!p_cursor.atBlockStart()) {
|
|
p_cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
|
|
p_cursor.removeSelectedText();
|
|
changed = true;
|
|
}
|
|
|
|
if (!leadingSpaces.isEmpty()) {
|
|
p_cursor.insertText(leadingSpaces);
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
// Use another QTextCursor to remain the selection.
|
|
void VEditUtils::indentSelectedBlocksAsBlock(const QTextCursor &p_cursor, bool p_next)
|
|
{
|
|
int nrBlocks = 1;
|
|
int start = p_cursor.selectionStart();
|
|
int end = p_cursor.selectionEnd();
|
|
|
|
QTextDocument *doc = p_cursor.document();
|
|
QTextBlock sBlock = doc->findBlock(start);
|
|
QTextBlock refBlock;
|
|
if (!p_next) {
|
|
refBlock = sBlock.previous();
|
|
}
|
|
|
|
if (start != end) {
|
|
QTextBlock eBlock = doc->findBlock(end);
|
|
nrBlocks = eBlock.blockNumber() - sBlock.blockNumber() + 1;
|
|
|
|
if (p_next) {
|
|
refBlock = eBlock.next();
|
|
}
|
|
} else {
|
|
refBlock = sBlock.next();
|
|
}
|
|
|
|
QTextCursor bCursor(sBlock);
|
|
bCursor.beginEditBlock();
|
|
for (int i = 0; i < nrBlocks; ++i) {
|
|
indentBlockAsBlock(bCursor, refBlock);
|
|
|
|
bCursor.movePosition(QTextCursor::NextBlock);
|
|
}
|
|
|
|
bCursor.endEditBlock();
|
|
}
|
|
|
|
bool VEditUtils::hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb)
|
|
{
|
|
int nonSpaceIdxa = 0;
|
|
int nonSpaceIdxb = 0;
|
|
|
|
QString texta = p_blocka.text();
|
|
for (int i = 0; i < texta.size(); ++i) {
|
|
if (!texta[i].isSpace()) {
|
|
nonSpaceIdxa = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
QString textb = p_blockb.text();
|
|
for (int i = 0; i < textb.size(); ++i) {
|
|
if (!textb[i].isSpace()) {
|
|
nonSpaceIdxb = i;
|
|
break;
|
|
} else if (i >= nonSpaceIdxa || texta[i] != textb[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return nonSpaceIdxa == nonSpaceIdxb;
|
|
}
|
|
|
|
void VEditUtils::moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
|
|
QTextCursor::MoveMode p_mode)
|
|
{
|
|
QTextBlock block = p_cursor.block();
|
|
QString text = block.text();
|
|
int idx = 0;
|
|
for (; idx < text.size(); ++idx) {
|
|
if (text[idx].isSpace()) {
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
p_cursor.setPosition(block.position() + idx, p_mode);
|
|
}
|
|
|
|
void VEditUtils::removeObjectReplacementCharacter(QString &p_text)
|
|
{
|
|
QRegExp orcBlockExp(VUtils::c_previewImageBlockRegExp);
|
|
p_text.remove(orcBlockExp);
|
|
p_text.remove(QChar::ObjectReplacementCharacter);
|
|
}
|
|
|
|
QString VEditUtils::selectedText(const QTextCursor &p_cursor)
|
|
{
|
|
QString text = p_cursor.selectedText();
|
|
text.replace(QChar::ParagraphSeparator, '\n');
|
|
return text;
|
|
}
|
|
|
|
// Use another QTextCursor to remain the selection.
|
|
void VEditUtils::indentSelectedBlocks(const QTextCursor &p_cursor,
|
|
const QString &p_indentationText,
|
|
bool p_isIndent)
|
|
{
|
|
int nrBlocks = 1;
|
|
int start = p_cursor.selectionStart();
|
|
int end = p_cursor.selectionEnd();
|
|
|
|
QTextDocument *doc = p_cursor.document();
|
|
QTextBlock sBlock = doc->findBlock(start);
|
|
if (start != end) {
|
|
QTextBlock eBlock = doc->findBlock(end);
|
|
nrBlocks = eBlock.blockNumber() - sBlock.blockNumber() + 1;
|
|
}
|
|
|
|
QTextCursor bCursor(sBlock);
|
|
bCursor.beginEditBlock();
|
|
for (int i = 0; i < nrBlocks; ++i) {
|
|
if (p_isIndent) {
|
|
indentBlock(bCursor, p_indentationText);
|
|
} else {
|
|
unindentBlock(bCursor, p_indentationText);
|
|
}
|
|
|
|
bCursor.movePosition(QTextCursor::NextBlock);
|
|
}
|
|
|
|
bCursor.endEditBlock();
|
|
}
|
|
|
|
void VEditUtils::indentBlock(QTextCursor &p_cursor,
|
|
const QString &p_indentationText,
|
|
bool p_skipEmpty)
|
|
{
|
|
QTextBlock block = p_cursor.block();
|
|
if (block.length() > 1 || !p_skipEmpty) {
|
|
p_cursor.movePosition(QTextCursor::StartOfBlock);
|
|
p_cursor.insertText(p_indentationText);
|
|
}
|
|
}
|
|
|
|
void VEditUtils::unindentBlock(QTextCursor &p_cursor,
|
|
const QString &p_indentationText)
|
|
{
|
|
QTextBlock block = p_cursor.block();
|
|
QString text = block.text();
|
|
if (text.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
p_cursor.movePosition(QTextCursor::StartOfBlock);
|
|
if (text[0] == '\t') {
|
|
p_cursor.deleteChar();
|
|
} else if (text[0].isSpace()) {
|
|
int width = p_indentationText.size();
|
|
for (int i = 0; i < width; ++i) {
|
|
if (text[i] == ' ') {
|
|
p_cursor.deleteChar();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VEditUtils::findTargetWithinBlock(QTextCursor &p_cursor,
|
|
QTextCursor::MoveMode p_mode,
|
|
QChar p_target,
|
|
bool p_forward,
|
|
bool p_inclusive,
|
|
bool p_leftSideOfCursor,
|
|
int p_repeat)
|
|
{
|
|
if (p_repeat < 1) {
|
|
return false;
|
|
}
|
|
|
|
QTextBlock block = p_cursor.block();
|
|
QString text = block.text();
|
|
int pib = p_cursor.positionInBlock();
|
|
int delta = p_forward ? 1 : -1;
|
|
|
|
// The index to start searching.
|
|
int idx = pib + (p_inclusive ? delta : 2 * delta);
|
|
if (p_leftSideOfCursor) {
|
|
--idx;
|
|
}
|
|
|
|
for (; idx < text.size() && idx >= 0; idx += delta) {
|
|
if (text[idx] == p_target) {
|
|
if (--p_repeat == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (idx < 0 || idx >= text.size() || p_repeat > 0) {
|
|
return false;
|
|
}
|
|
|
|
// text[idx] is the target character.
|
|
if ((p_forward && p_inclusive && p_mode == QTextCursor::KeepAnchor)
|
|
|| (!p_forward && !p_inclusive)) {
|
|
++idx;
|
|
} else if (p_forward && !p_inclusive && p_mode == QTextCursor::MoveAnchor) {
|
|
--idx;
|
|
}
|
|
|
|
p_cursor.setPosition(block.position() + idx, p_mode);
|
|
return true;
|
|
}
|
|
|
|
int VEditUtils::findTargetsWithinBlock(QTextCursor &p_cursor,
|
|
const QList<QChar> &p_targets,
|
|
bool p_forward,
|
|
bool p_leftSideOfCursor,
|
|
bool p_inclusive)
|
|
{
|
|
if (p_targets.isEmpty()) {
|
|
return -1;
|
|
}
|
|
|
|
int targetIdx = -1;
|
|
QTextBlock block = p_cursor.block();
|
|
QString text = block.text();
|
|
int pib = p_cursor.positionInBlock();
|
|
int delta = p_forward ? 1 : -1;
|
|
|
|
// The index to start searching.
|
|
int idx = pib + (p_inclusive ? delta : 2 * delta);
|
|
if (p_leftSideOfCursor) {
|
|
--idx;
|
|
}
|
|
|
|
for (; idx < text.size() && idx >= 0; idx += delta) {
|
|
int index = p_targets.indexOf(text[idx]);
|
|
if (index != -1) {
|
|
targetIdx = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (idx < 0 || idx >= text.size()) {
|
|
return -1;
|
|
}
|
|
|
|
// text[idx] is the target character.
|
|
if (p_forward && !p_inclusive) {
|
|
--idx;
|
|
}
|
|
|
|
p_cursor.setPosition(block.position() + idx);
|
|
return targetIdx;
|
|
}
|
|
|
|
|
|
int VEditUtils::selectedBlockCount(const QTextCursor &p_cursor)
|
|
{
|
|
if (!p_cursor.hasSelection()) {
|
|
return 0;
|
|
}
|
|
|
|
QTextDocument *doc = p_cursor.document();
|
|
int sbNum = doc->findBlock(p_cursor.selectionStart()).blockNumber();
|
|
int ebNum = doc->findBlock(p_cursor.selectionEnd()).blockNumber();
|
|
|
|
return ebNum - sbNum + 1;
|
|
}
|
|
|
|
void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
|
|
int p_blockNum,
|
|
int p_dest)
|
|
{
|
|
QTextDocument *doc = p_edit->document();
|
|
QTextCursor cursor = p_edit->textCursor();
|
|
if (p_blockNum >= doc->blockCount()) {
|
|
p_blockNum = doc->blockCount() - 1;
|
|
}
|
|
|
|
QTextBlock block = doc->findBlockByNumber(p_blockNum);
|
|
|
|
int pib = cursor.positionInBlock();
|
|
if (cursor.block().blockNumber() != p_blockNum) {
|
|
// Move the cursor to the block.
|
|
if (pib >= block.length()) {
|
|
pib = block.length() - 1;
|
|
}
|
|
|
|
cursor.setPosition(block.position() + pib);
|
|
p_edit->setTextCursor(cursor);
|
|
}
|
|
|
|
// Scroll to let current cursor locate in proper position.
|
|
p_edit->ensureCursorVisible();
|
|
QScrollBar *vsbar = p_edit->verticalScrollBar();
|
|
|
|
if (!vsbar || !vsbar->isVisible()) {
|
|
// No vertical scrollbar. No need to scroll.
|
|
return;
|
|
}
|
|
|
|
QRect rect = p_edit->cursorRect();
|
|
int height = p_edit->rect().height();
|
|
QScrollBar *sbar = p_edit->horizontalScrollBar();
|
|
if (sbar && sbar->isVisible()) {
|
|
height -= sbar->height();
|
|
}
|
|
|
|
switch (p_dest) {
|
|
case 0:
|
|
{
|
|
// Top.
|
|
while (rect.y() > 0 && vsbar->value() < vsbar->maximum()) {
|
|
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
// Center.
|
|
height = qMax(height / 2, 1);
|
|
if (rect.y() > height) {
|
|
while (rect.y() > height && vsbar->value() < vsbar->maximum()) {
|
|
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
} else if (rect.y() < height) {
|
|
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
|
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
// Bottom.
|
|
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
|
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
p_edit->ensureCursorVisible();
|
|
}
|
|
|
|
void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
|
|
int p_blockNum,
|
|
int p_dest)
|
|
{
|
|
QTextDocument *doc = p_edit->document();
|
|
QTextCursor cursor = p_edit->textCursor();
|
|
if (p_blockNum >= doc->blockCount()) {
|
|
p_blockNum = doc->blockCount() - 1;
|
|
}
|
|
|
|
QTextBlock block = doc->findBlockByNumber(p_blockNum);
|
|
|
|
int pib = cursor.positionInBlock();
|
|
if (cursor.block().blockNumber() != p_blockNum) {
|
|
// Move the cursor to the block.
|
|
if (pib >= block.length()) {
|
|
pib = block.length() - 1;
|
|
}
|
|
|
|
cursor.setPosition(block.position() + pib);
|
|
p_edit->setTextCursor(cursor);
|
|
}
|
|
|
|
// Scroll to let current cursor locate in proper position.
|
|
p_edit->ensureCursorVisible();
|
|
QScrollBar *vsbar = p_edit->verticalScrollBar();
|
|
|
|
if (!vsbar || !vsbar->isVisible()) {
|
|
// No vertical scrollbar. No need to scroll.
|
|
return;
|
|
}
|
|
|
|
QRect rect = p_edit->cursorRect();
|
|
int height = p_edit->rect().height();
|
|
QScrollBar *sbar = p_edit->horizontalScrollBar();
|
|
if (sbar && sbar->isVisible()) {
|
|
height -= sbar->height();
|
|
}
|
|
|
|
switch (p_dest) {
|
|
case 0:
|
|
{
|
|
// Top.
|
|
while (rect.y() > 0 && vsbar->value() < vsbar->maximum()) {
|
|
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
// Center.
|
|
height = qMax(height / 2, 1);
|
|
if (rect.y() > height) {
|
|
while (rect.y() > height && vsbar->value() < vsbar->maximum()) {
|
|
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
} else if (rect.y() < height) {
|
|
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
|
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
// Bottom.
|
|
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
|
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
|
rect = p_edit->cursorRect();
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
p_edit->ensureCursorVisible();
|
|
}
|
|
|
|
bool VEditUtils::isListBlock(const QTextBlock &p_block, int *p_seq)
|
|
{
|
|
QString text = p_block.text();
|
|
QRegExp regExp(VUtils::c_listRegExp);
|
|
|
|
if (p_seq) {
|
|
*p_seq = -1;
|
|
}
|
|
|
|
int regIdx = regExp.indexIn(text);
|
|
if (regIdx == -1) {
|
|
return false;
|
|
}
|
|
|
|
V_ASSERT(regExp.captureCount() == 1);
|
|
QString markText = regExp.capturedTexts()[1];
|
|
if (!isListBullet(markText)) {
|
|
V_ASSERT(markText.endsWith('.'));
|
|
bool ok = false;
|
|
int num = markText.left(markText.size() - 1).toInt(&ok, 10);
|
|
V_ASSERT(ok);
|
|
if (p_seq) {
|
|
*p_seq = num;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VEditUtils::isListBullet(const QString &p_str)
|
|
{
|
|
return p_str == "-" || p_str == "*";
|
|
}
|
|
|
|
bool VEditUtils::isSpaceToBlockStart(const QTextBlock &p_block, int p_posInBlock)
|
|
{
|
|
if (p_posInBlock <= 0) {
|
|
return true;
|
|
}
|
|
|
|
QString text = p_block.text();
|
|
V_ASSERT(text.size() >= p_posInBlock);
|
|
return text.left(p_posInBlock).trimmed().isEmpty();
|
|
}
|
|
|
|
bool VEditUtils::isSpaceBlock(const QTextBlock &p_block)
|
|
{
|
|
return p_block.text().trimmed().isEmpty();
|
|
}
|
|
|
|
void VEditUtils::deleteIndentAndListMark(QTextCursor &p_cursor)
|
|
{
|
|
V_ASSERT(!p_cursor.hasSelection());
|
|
p_cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
|
|
p_cursor.removeSelectedText();
|
|
}
|
|
|
|
|
|
bool VEditUtils::selectPairTargetAround(QTextCursor &p_cursor,
|
|
QChar p_opening,
|
|
QChar p_closing,
|
|
bool p_inclusive,
|
|
bool p_crossBlock,
|
|
int p_repeat)
|
|
{
|
|
Q_ASSERT(p_repeat >= 1);
|
|
|
|
QTextDocument *doc = p_cursor.document();
|
|
int pos = p_cursor.position();
|
|
|
|
// Search range [start, end].
|
|
int start = 0;
|
|
int end = doc->characterCount() - 1;
|
|
if (!p_crossBlock) {
|
|
QTextBlock block = p_cursor.block();
|
|
start = block.position();
|
|
end = block.position() + block.length() - 1;
|
|
}
|
|
|
|
if (start == end || pos > end) {
|
|
return false;
|
|
}
|
|
|
|
Q_ASSERT(!doc->characterAt(pos).isNull());
|
|
|
|
bool found = false;
|
|
|
|
// The number of un-paired symbols before we meet a target.
|
|
// For example, when we are searching the `(`, nrPair is the number of
|
|
// the un-paired `)` currently.
|
|
int nrPair = 0;
|
|
|
|
// The absolute position of the found target.
|
|
// vnote|(vnote|)vnote
|
|
int opening = pos;
|
|
int closing = pos;
|
|
|
|
round:
|
|
// "abc|"def", after `di"`, becomes "|"def"
|
|
// So we need to try closing first.
|
|
QChar ch = doc->characterAt(closing);
|
|
Q_ASSERT(!ch.isNull());
|
|
if (ch == p_closing) {
|
|
// Try to find the opening.
|
|
nrPair = 1;
|
|
int i = opening;
|
|
if (opening == closing) {
|
|
--i;
|
|
}
|
|
|
|
for (; i >= start; --i) {
|
|
ch = doc->characterAt(i);
|
|
Q_ASSERT(!ch.isNull());
|
|
if (ch == p_opening) {
|
|
if (--nrPair == 0) {
|
|
break;
|
|
}
|
|
} else if (ch == p_closing) {
|
|
++nrPair;
|
|
}
|
|
}
|
|
|
|
if (i >= start) {
|
|
// Found the opening. Done.
|
|
opening = i;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
ch = doc->characterAt(opening);
|
|
Q_ASSERT(!ch.isNull());
|
|
if (!found && ch == p_opening) {
|
|
// Try to find the closing.
|
|
nrPair = 1;
|
|
int j = closing;
|
|
if (opening == closing) {
|
|
++j;
|
|
}
|
|
|
|
for (; j <= end; ++j) {
|
|
ch = doc->characterAt(j);
|
|
Q_ASSERT(!ch.isNull());
|
|
if (ch == p_closing) {
|
|
if (--nrPair == 0) {
|
|
break;
|
|
}
|
|
} else if (ch == p_opening) {
|
|
++nrPair;
|
|
}
|
|
}
|
|
|
|
if (j <= end) {
|
|
// Foudnd the closing. Done.
|
|
closing = j;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found
|
|
&& doc->characterAt(opening) != p_opening
|
|
&& doc->characterAt(closing) != p_closing) {
|
|
// Need to find both the opening and closing.
|
|
int i = opening - 1;
|
|
int j = closing + 1;
|
|
// Pretend that we have found one.
|
|
nrPair = 1;
|
|
for (; i >= start; --i) {
|
|
ch = doc->characterAt(i);
|
|
Q_ASSERT(!ch.isNull());
|
|
if (ch == p_opening) {
|
|
if (--nrPair == 0) {
|
|
break;
|
|
}
|
|
} else if (ch == p_closing) {
|
|
++nrPair;
|
|
}
|
|
}
|
|
|
|
if (i >= start) {
|
|
opening = i;
|
|
// Continue to find the closing.
|
|
nrPair = 1;
|
|
for (; j <= end; ++j) {
|
|
ch = doc->characterAt(j);
|
|
Q_ASSERT(!ch.isNull());
|
|
if (ch == p_closing) {
|
|
if (--nrPair == 0) {
|
|
break;
|
|
}
|
|
} else if (ch == p_opening) {
|
|
++nrPair;
|
|
}
|
|
}
|
|
|
|
if (j <= end) {
|
|
closing = j;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return false;
|
|
} else if (--p_repeat) {
|
|
// Need to find more.
|
|
found = false;
|
|
--opening;
|
|
++closing;
|
|
|
|
if (opening < start && closing > end) {
|
|
return false;
|
|
}
|
|
|
|
goto round;
|
|
}
|
|
|
|
if (p_inclusive) {
|
|
++closing;
|
|
} else {
|
|
++opening;
|
|
}
|
|
|
|
p_cursor.setPosition(opening, QTextCursor::MoveAnchor);
|
|
p_cursor.setPosition(closing, QTextCursor::KeepAnchor);
|
|
return true;
|
|
}
|
|
|
|
int VEditUtils::findNextEmptyBlock(const QTextCursor &p_cursor,
|
|
bool p_forward,
|
|
int p_repeat)
|
|
{
|
|
Q_ASSERT(p_repeat > 0);
|
|
int res = -1;
|
|
QTextBlock block = p_cursor.block();
|
|
if (p_forward) {
|
|
block = block.next();
|
|
} else {
|
|
block = block.previous();
|
|
}
|
|
|
|
while (block.isValid()) {
|
|
if (block.length() == 1) {
|
|
res = block.position();
|
|
if (--p_repeat == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p_forward) {
|
|
block = block.next();
|
|
} else {
|
|
block = block.previous();
|
|
}
|
|
}
|
|
|
|
return p_repeat > 0 ? -1 : res;
|
|
}
|
|
|
|
bool VEditUtils::needToCancelAutoIndent(int p_autoIndentPos, const QTextCursor &p_cursor)
|
|
{
|
|
// Cancel the auto indent/list if the pos is the same and cursor is at
|
|
// the end of a block.
|
|
QTextBlock block = p_cursor.block();
|
|
if (p_cursor.position() == p_autoIndentPos
|
|
&& !p_cursor.hasSelection()
|
|
&& p_cursor.atBlockEnd()) {
|
|
if (isListBlock(block)) {
|
|
return true;
|
|
} else if (isSpaceToBlockStart(block,
|
|
p_cursor.positionInBlock())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void VEditUtils::insertTitleMark(QTextCursor &p_cursor,
|
|
const QTextBlock &p_block,
|
|
int p_level)
|
|
{
|
|
if (!p_block.isValid()) {
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(p_level >= 0 && p_level <= 6);
|
|
|
|
bool needInsert = true;
|
|
|
|
p_cursor.setPosition(p_block.position());
|
|
|
|
// Test if this block contains title marks.
|
|
QRegExp headerReg(VUtils::c_headerRegExp);
|
|
QString text = p_block.text();
|
|
bool matched = headerReg.exactMatch(text);
|
|
if (matched) {
|
|
int level = headerReg.cap(1).length();
|
|
if (level == p_level) {
|
|
needInsert = false;
|
|
} else {
|
|
// Remove the title mark.
|
|
int length = level;
|
|
if (p_level == 0) {
|
|
// Remove all the prefix.
|
|
QRegExp prefixReg(VUtils::c_headerPrefixRegExp);
|
|
bool preMatched = prefixReg.exactMatch(text);
|
|
Q_UNUSED(preMatched);
|
|
Q_ASSERT(preMatched);
|
|
length = prefixReg.cap(1).length();
|
|
}
|
|
|
|
p_cursor.movePosition(QTextCursor::NextCharacter,
|
|
QTextCursor::KeepAnchor,
|
|
length);
|
|
p_cursor.removeSelectedText();
|
|
}
|
|
}
|
|
|
|
// Insert titleMark + " " at the front of the block.
|
|
if (p_level > 0 && needInsert) {
|
|
// Remove the spaces at front.
|
|
// insertText() will remove the selection.
|
|
moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::KeepAnchor);
|
|
|
|
// Insert.
|
|
const QString titleMark(p_level, '#');
|
|
p_cursor.insertText(titleMark + " ");
|
|
}
|
|
|
|
// Go to the end of this block.
|
|
p_cursor.movePosition(QTextCursor::EndOfBlock);
|
|
}
|
|
|
|
void VEditUtils::findCurrentWord(QTextCursor p_cursor,
|
|
int &p_start,
|
|
int &p_end)
|
|
{
|
|
QString text = p_cursor.block().text();
|
|
int pib = p_cursor.positionInBlock();
|
|
|
|
if (pib < text.size() && text[pib].isSpace()) {
|
|
p_start = p_end = p_cursor.position();
|
|
return;
|
|
}
|
|
|
|
p_cursor.movePosition(QTextCursor::StartOfWord);
|
|
p_start = p_cursor.position();
|
|
p_cursor.movePosition(QTextCursor::EndOfWord);
|
|
p_end = p_cursor.position();
|
|
}
|
|
|
|
void VEditUtils::findCurrentWORD(const QTextCursor &p_cursor,
|
|
int &p_start,
|
|
int &p_end)
|
|
{
|
|
QTextBlock block = p_cursor.block();
|
|
QString text = block.text();
|
|
int pib = p_cursor.positionInBlock();
|
|
|
|
if (pib < text.size() && text[pib].isSpace()) {
|
|
p_start = p_end = p_cursor.position();
|
|
return;
|
|
}
|
|
|
|
// Find the start.
|
|
p_start = 0;
|
|
for (int i = pib - 1; i >= 0; --i) {
|
|
if (text[i].isSpace()) {
|
|
p_start = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find the end.
|
|
p_end = block.length() - 1;
|
|
for (int i = pib; i < text.size(); ++i) {
|
|
if (text[i].isSpace()) {
|
|
p_end = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
p_start += block.position();
|
|
p_end += block.position();
|
|
}
|
|
|
|
QString VEditUtils::fetchIndentSpaces(const QTextBlock &p_block)
|
|
{
|
|
QString text = p_block.text();
|
|
QRegExp regExp("(^\\s*)");
|
|
regExp.indexIn(text);
|
|
Q_ASSERT(regExp.captureCount() == 1);
|
|
return regExp.capturedTexts()[1];
|
|
}
|
|
|
|
int VEditUtils::fetchIndentation(const QString &p_text)
|
|
{
|
|
int idx = 0;
|
|
for (; idx < p_text.size(); ++idx) {
|
|
if (!p_text[idx].isSpace()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
int VEditUtils::fetchIndentation(const QTextBlock &p_block)
|
|
{
|
|
return fetchIndentation(p_block.text());
|
|
}
|
|
|
|
void VEditUtils::insertBlock(QTextCursor &p_cursor,
|
|
bool p_above)
|
|
{
|
|
p_cursor.movePosition(p_above ? QTextCursor::StartOfBlock
|
|
: QTextCursor::EndOfBlock,
|
|
QTextCursor::MoveAnchor,
|
|
1);
|
|
|
|
p_cursor.insertBlock();
|
|
|
|
if (p_above) {
|
|
p_cursor.movePosition(QTextCursor::PreviousBlock,
|
|
QTextCursor::MoveAnchor,
|
|
1);
|
|
}
|
|
|
|
p_cursor.movePosition(QTextCursor::EndOfBlock);
|
|
}
|
|
|
|
void VEditUtils::insertBeforeEachLine(QString &p_text, const QString &p_str)
|
|
{
|
|
int pos = 0;
|
|
while (pos < p_text.size()) {
|
|
int idx = p_text.indexOf("\n", pos);
|
|
if (idx == -1) {
|
|
break;
|
|
}
|
|
|
|
++idx;
|
|
if (idx == p_text.size()) {
|
|
break;
|
|
}
|
|
|
|
p_text.insert(idx, p_str);
|
|
pos = idx + p_str.size();
|
|
}
|
|
|
|
p_text.prepend(p_str);
|
|
}
|
|
|
|
bool VEditUtils::isEmptyBlock(const QTextBlock &p_block)
|
|
{
|
|
return p_block.length() == 1;
|
|
}
|