vnote/src/vmdeditoperations.cpp
2017-11-24 19:56:41 +08:00

1024 lines
30 KiB
C++

#include <QtDebug>
#include <QImage>
#include <QVariant>
#include <QMimeData>
#include <QWidget>
#include <QImageReader>
#include <QDir>
#include <QMessageBox>
#include <QKeyEvent>
#include <QTextCursor>
#include <QTimer>
#include <QGuiApplication>
#include <QApplication>
#include <QClipboard>
#include "vmdeditoperations.h"
#include "dialog/vinsertimagedialog.h"
#include "dialog/vselectdialog.h"
#include "utils/vutils.h"
#include "veditor.h"
#include "vdownloader.h"
#include "vfile.h"
#include "vmdeditor.h"
#include "vconfigmanager.h"
#include "utils/vvim.h"
#include "utils/veditutils.h"
extern VConfigManager *g_config;
const QString VMdEditOperations::c_defaultImageTitle = "";
VMdEditOperations::VMdEditOperations(VEditor *p_editor, VFile *p_file)
: VEditOperations(p_editor, p_file), m_autoIndentPos(-1)
{
}
bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
{
QImage image = qvariant_cast<QImage>(source->imageData());
if (image.isNull()) {
return false;
}
VInsertImageDialog dialog(tr("Insert Image From Clipboard"),
c_defaultImageTitle,
"",
m_editor->getEditor());
dialog.setBrowseable(false);
dialog.setImage(image);
if (dialog.exec() == QDialog::Accepted) {
insertImageFromQImage(dialog.getImageTitleInput(),
m_file->fetchImageFolderPath(),
m_file->getImageFolderInLink(),
image);
}
return true;
}
void VMdEditOperations::insertImageFromQImage(const QString &title, const QString &path,
const QString &folderInLink, const QImage &image)
{
QString fileName = VUtils::generateImageFileName(path, title);
QString filePath = QDir(path).filePath(fileName);
V_ASSERT(!QFile(filePath).exists());
QString errStr;
bool ret = VUtils::makePath(path);
if (!ret) {
errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle).arg(path);
} else {
ret = image.save(filePath);
if (!ret) {
errStr = tr("Fail to save image <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle).arg(filePath);
}
}
if (!ret) {
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
tr("Fail to insert image <span style=\"%1\">%2</span>.").arg(g_config->c_dataTextStyle).arg(title),
errStr,
QMessageBox::Ok,
QMessageBox::Ok,
m_editor->getEditor());
return;
}
QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
insertTextAtCurPos(md);
qDebug() << "insert image" << title << filePath;
VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
Q_ASSERT(mdEditor);
mdEditor->imageInserted(filePath);
}
void VMdEditOperations::insertImageFromPath(const QString &title, const QString &path,
const QString &folderInLink, const QString &oriImagePath)
{
QString fileName = VUtils::generateImageFileName(path, title, QFileInfo(oriImagePath).suffix());
QString filePath = QDir(path).filePath(fileName);
V_ASSERT(!QFile(filePath).exists());
QString errStr;
bool ret = VUtils::makePath(path);
if (!ret) {
errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle).arg(path);
} else {
ret = QFile::copy(oriImagePath, filePath);
if (!ret) {
errStr = tr("Fail to copy image <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle).arg(filePath);
}
}
if (!ret) {
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
tr("Fail to insert image <span style=\"%1\">%2</span>.").arg(g_config->c_dataTextStyle).arg(title),
errStr,
QMessageBox::Ok,
QMessageBox::Ok,
m_editor->getEditor());
return;
}
QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
insertTextAtCurPos(md);
qDebug() << "insert image" << title << filePath;
VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
Q_ASSERT(mdEditor);
mdEditor->imageInserted(filePath);
}
bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
{
QString imagePath;
QImage image;
bool isLocal = imageUrl.isLocalFile();
QString title;
// Whether it is a local file or web URL
if (isLocal) {
imagePath = imageUrl.toLocalFile();
image = QImage(imagePath);
if (image.isNull()) {
qWarning() << "image is null";
return false;
}
title = "Insert Image From File";
} else {
imagePath = imageUrl.toString();
title = "Insert Image From Network";
}
VInsertImageDialog dialog(title, c_defaultImageTitle,
imagePath, m_editor->getEditor());
dialog.setBrowseable(false, true);
if (isLocal) {
dialog.setImage(image);
} else {
// Download it to a QImage
VDownloader *downloader = new VDownloader(&dialog);
connect(downloader, &VDownloader::downloadFinished,
&dialog, &VInsertImageDialog::imageDownloaded);
downloader->download(imageUrl.toString());
}
if (dialog.exec() == QDialog::Accepted) {
if (isLocal) {
insertImageFromPath(dialog.getImageTitleInput(),
m_file->fetchImageFolderPath(),
m_file->getImageFolderInLink(),
imagePath);
} else {
insertImageFromQImage(dialog.getImageTitleInput(),
m_file->fetchImageFolderPath(),
m_file->getImageFolderInLink(),
dialog.getImage());
}
}
return true;
}
bool VMdEditOperations::insertImage()
{
VInsertImageDialog dialog(tr("Insert Image From File"),
c_defaultImageTitle, "", m_editor->getEditor());
if (dialog.exec() == QDialog::Accepted) {
QString title = dialog.getImageTitleInput();
QString imagePath = dialog.getPathInput();
qDebug() << "insert image from" << imagePath << "as" << title;
insertImageFromPath(title,
m_file->fetchImageFolderPath(),
m_file->getImageFolderInLink(),
imagePath);
}
return true;
}
bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
{
if (m_editConfig->m_enableVimMode
&& m_vim->handleKeyPressEvent(p_event, &m_autoIndentPos)) {
return true;
}
bool ret = false;
int key = p_event->key();
int modifiers = p_event->modifiers();
switch (key) {
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
{
if (modifiers == Qt::ControlModifier) {
// Ctrl + <N>: insert title at level <N>.
if (decorateHeading(key == Qt::Key_7 ? 0 : key - Qt::Key_0)) {
p_event->accept();
ret = true;
goto exit;
}
}
break;
}
case Qt::Key_Tab:
{
if (handleKeyTab(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_Backtab:
{
if (handleKeyBackTab(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_B:
{
if (modifiers == Qt::ControlModifier) {
decorateBold();
p_event->accept();
ret = true;
}
break;
}
case Qt::Key_H:
{
if (handleKeyH(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_I:
{
if (modifiers == Qt::ControlModifier) {
decorateItalic();
p_event->accept();
ret = true;
}
break;
}
case Qt::Key_L:
{
if (modifiers == Qt::ControlModifier) {
m_editor->insertLink();
p_event->accept();
ret = true;
}
break;
}
case Qt::Key_M:
{
if (modifiers == Qt::ControlModifier) {
decorateCodeBlock();
p_event->accept();
ret = true;
}
break;
}
case Qt::Key_K:
{
if (modifiers == Qt::ControlModifier) {
decorateInlineCode();
p_event->accept();
ret = true;
}
break;
}
case Qt::Key_U:
{
if (handleKeyU(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_W:
{
if (handleKeyW(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_BracketLeft:
{
if (handleKeyBracketLeft(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_Escape:
{
if (handleKeyEsc(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_Enter:
// Fall through.
case Qt::Key_Return:
{
if (handleKeyReturn(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_D:
{
if (modifiers == Qt::ControlModifier) {
decorateStrikethrough();
p_event->accept();
ret = true;
}
break;
}
default:
break;
}
exit:
// Qt::Key_Return, Qt::Key_Tab and Qt::Key_Backtab will handle m_autoIndentPos.
if (key != Qt::Key_Return
&& key != Qt::Key_Enter
&& key != Qt::Key_Tab
&& key != Qt::Key_Backtab
&& key != Qt::Key_Shift
&& key != Qt::Key_Control) {
m_autoIndentPos = -1;
}
return ret;
}
// Let Ctrl+[ behave exactly like ESC.
bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
{
// 1. If there is any selection, clear it.
// 2. Otherwise, ignore this event and let parent handles it.
if (p_event->modifiers() == Qt::ControlModifier) {
QTextCursor cursor = m_editor->textCursorW();
if (cursor.hasSelection()) {
cursor.clearSelection();
m_editor->setTextCursorW(cursor);
p_event->accept();
return true;
}
}
return false;
}
bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
{
QTextDocument *doc = m_editor->documentW();
QString text(m_editConfig->m_tabSpaces);
if (p_event->modifiers() == Qt::NoModifier) {
QTextCursor cursor = m_editor->textCursorW();
if (cursor.hasSelection()) {
m_autoIndentPos = -1;
cursor.beginEditBlock();
// Indent each selected line.
VEditUtils::indentSelectedBlocks(doc, cursor, text, true);
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
} else {
// If it is a Tab key following auto list, increase the indent level.
QTextBlock block = cursor.block();
int seq = -1;
if (m_autoIndentPos == cursor.position()
&& VEditUtils::isListBlock(block, &seq)) {
QTextCursor blockCursor(block);
blockCursor.beginEditBlock();
blockCursor.insertText(text);
if (seq != -1) {
changeListBlockSeqNumber(block, 1);
}
blockCursor.endEditBlock();
// Change m_autoIndentPos to let it can be repeated.
m_autoIndentPos = m_editor->textCursorW().position();
} else {
// Just insert "tab".
insertTextAtCurPos(text);
m_autoIndentPos = -1;
}
}
} else {
m_autoIndentPos = -1;
return false;
}
p_event->accept();
return true;
}
bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
{
if (p_event->modifiers() != Qt::ShiftModifier) {
m_autoIndentPos = -1;
return false;
}
QTextDocument *doc = m_editor->documentW();
QTextCursor cursor = m_editor->textCursorW();
QTextBlock block = doc->findBlock(cursor.selectionStart());
bool continueAutoIndent = false;
int seq = -1;
if (cursor.position() == m_autoIndentPos
&& VEditUtils::isListBlock(block, &seq) &&
!cursor.hasSelection()) {
continueAutoIndent = true;
}
cursor.beginEditBlock();
if (continueAutoIndent && seq != -1) {
changeListBlockSeqNumber(block, 1);
}
VEditUtils::indentSelectedBlocks(doc, cursor, m_editConfig->m_tabSpaces, false);
cursor.endEditBlock();
if (continueAutoIndent) {
m_autoIndentPos = m_editor->textCursorW().position();
} else {
m_autoIndentPos = -1;
}
p_event->accept();
return true;
}
bool VMdEditOperations::handleKeyH(QKeyEvent *p_event)
{
if (p_event->modifiers() == Qt::ControlModifier) {
// Ctrl+H, equal to backspace.
QTextCursor cursor = m_editor->textCursorW();
cursor.deletePreviousChar();
p_event->accept();
return true;
}
return false;
}
bool VMdEditOperations::handleKeyU(QKeyEvent *p_event)
{
if (p_event->modifiers() == Qt::ControlModifier) {
// Ctrl+U, delete till the start of line.
QTextCursor cursor = m_editor->textCursorW();
bool ret;
if (cursor.atBlockStart()) {
ret = cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
} else {
ret = cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
}
if (ret) {
cursor.removeSelectedText();
}
p_event->accept();
return true;
}
return false;
}
bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
{
if (p_event->modifiers() == Qt::ControlModifier) {
// Ctrl+W, delete till the start of previous word.
QTextCursor cursor = m_editor->textCursorW();
if (cursor.hasSelection()) {
cursor.removeSelectedText();
} else {
bool ret = cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
if (ret) {
cursor.removeSelectedText();
}
}
p_event->accept();
return true;
}
return false;
}
bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
{
// 1. If there is any selection, clear it.
// 2. Otherwise, ignore this event and let parent handles it.
QTextCursor cursor = m_editor->textCursorW();
if (cursor.hasSelection()) {
cursor.clearSelection();
m_editor->setTextCursorW(cursor);
p_event->accept();
return true;
}
return false;
}
bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
{
bool autolist = true;
if (p_event->modifiers() & Qt::ControlModifier) {
m_autoIndentPos = -1;
return false;
} else if (p_event->modifiers() & Qt::ShiftModifier) {
// Insert two spaces and a new line.
m_autoIndentPos = -1;
QTextCursor cursor = m_editor->textCursorW();
cursor.beginEditBlock();
cursor.removeSelectedText();
cursor.insertText(" ");
cursor.endEditBlock();
// Let remaining logics handle inserting the new block except that we
// do not need to insert auto list.
autolist = false;
}
// See if we need to cancel auto indent.
if (m_autoIndentPos > -1) {
// Cancel the auto indent/list if the pos is the same and cursor is at
// the end of a block.
QTextCursor cursor = m_editor->textCursorW();
if (VEditUtils::needToCancelAutoIndent(m_autoIndentPos, cursor)) {
m_autoIndentPos = -1;
VEditUtils::deleteIndentAndListMark(cursor);
m_editor->setTextCursorW(cursor);
return true;
}
}
bool handled = false;
m_autoIndentPos = -1;
if (g_config->getAutoIndent()) {
handled = true;
QTextCursor cursor = m_editor->textCursorW();
bool textInserted = false;
cursor.beginEditBlock();
cursor.removeSelectedText();
// Indent the new line as previous line.
textInserted = VEditUtils::insertBlockWithIndent(cursor);
// Continue the list from previous line.
if (g_config->getAutoList() && autolist) {
textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor) || textInserted;
}
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
if (textInserted) {
m_autoIndentPos = m_editor->textCursorW().position();
}
}
return handled;
}
void VMdEditOperations::changeListBlockSeqNumber(QTextBlock &p_block, int p_seq)
{
QString text = p_block.text();
QRegExp regExp("^(\\s*)(\\d+)\\.\\s");
int idx = regExp.indexIn(text);
if (idx == -1 || regExp.captureCount() != 2) {
return;
}
int oriSeq = -1;
bool ok = false;
oriSeq = regExp.capturedTexts()[2].toInt(&ok);
if (ok && oriSeq == p_seq) {
return;
}
QTextCursor cursor(p_block);
bool ret = cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor,
regExp.capturedTexts()[1].size());
if (!ret) {
return;
}
ret = cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,
regExp.capturedTexts()[2].size());
if (!ret) {
return;
}
cursor.removeSelectedText();
cursor.insertText(QString::number(p_seq));
}
bool VMdEditOperations::decorateHeading(int p_level)
{
QTextDocument *doc = m_editor->documentW();
QTextCursor cursor = m_editor->textCursorW();
int firstBlock = cursor.block().blockNumber();
int lastBlock = firstBlock;
if (cursor.hasSelection()) {
// Insert title # in front of the selected blocks.
int start = cursor.selectionStart();
int end = cursor.selectionEnd();
firstBlock = doc->findBlock(start).blockNumber();
lastBlock = doc->findBlock(end).blockNumber();
}
cursor.beginEditBlock();
for (int i = firstBlock; i <= lastBlock; ++i) {
VEditUtils::insertTitleMark(cursor, doc->findBlockByNumber(i), p_level);
}
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
return true;
}
void VMdEditOperations::decorateText(TextDecoration p_decoration, int p_level)
{
if (p_decoration == TextDecoration::None) {
return;
}
bool validDecoration = true;
switch (p_decoration) {
case TextDecoration::Bold:
decorateBold();
break;
case TextDecoration::Italic:
decorateItalic();
break;
case TextDecoration::Strikethrough:
decorateStrikethrough();
break;
case TextDecoration::InlineCode:
decorateInlineCode();
break;
case TextDecoration::CodeBlock:
decorateCodeBlock();
break;
case TextDecoration::Heading:
decorateHeading(p_level);
break;
default:
validDecoration = false;
qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
break;
}
if (validDecoration && m_editConfig->m_enableVimMode) {
Q_ASSERT(m_vim);
m_vim->setMode(VimMode::Insert, false);
}
}
void VMdEditOperations::decorateBold()
{
QTextCursor cursor = m_editor->textCursorW();
cursor.beginEditBlock();
if (cursor.hasSelection()) {
// Insert ** around the selected text.
int start = cursor.selectionStart();
int end = cursor.selectionEnd();
cursor.clearSelection();
cursor.setPosition(start, QTextCursor::MoveAnchor);
cursor.insertText("**");
cursor.setPosition(end + 2, QTextCursor::MoveAnchor);
cursor.insertText("**");
} else {
// Insert **** and place cursor in the middle.
// Or if there are two * after current cursor, just skip them or delete
// them if four * appear.
int pos = cursor.positionInBlock();
bool hasStars = false;
bool emptyMarkers = false;
QString text = cursor.block().text();
if (pos <= text.size() - 2) {
if (text[pos] == '*' && text[pos + 1] == '*') {
hasStars = true;
if (pos >= 2
&& text[pos - 1] == '*'
&& text[pos - 2] == '*') {
emptyMarkers = true;
}
}
}
if (hasStars) {
if (emptyMarkers) {
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 2);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
cursor.removeSelectedText();
} else {
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2);
}
} else {
cursor.insertText("****");
cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
}
}
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
}
void VMdEditOperations::decorateItalic()
{
QTextCursor cursor = m_editor->textCursorW();
cursor.beginEditBlock();
if (cursor.hasSelection()) {
// Insert * around the selected text.
int start = cursor.selectionStart();
int end = cursor.selectionEnd();
cursor.clearSelection();
cursor.setPosition(start, QTextCursor::MoveAnchor);
cursor.insertText("*");
cursor.setPosition(end + 1, QTextCursor::MoveAnchor);
cursor.insertText("*");
} else {
// Insert ** and place cursor in the middle.
// Or if there are one * after current cursor, just skip them or delete
// them if two * appear.
int pos = cursor.positionInBlock();
bool hasStar = false;
bool emptyMarkers = false;
QString text = cursor.block().text();
if (pos <= text.size() - 1) {
if (text[pos] == '*'
&& (pos == text.size() - 1 || text[pos + 1] != '*')) {
hasStar = true;
if (pos >= 1 && text[pos - 1] == '*') {
emptyMarkers = true;
}
}
}
if (hasStar) {
if (emptyMarkers) {
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 1);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
cursor.removeSelectedText();
} else {
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
}
} else {
cursor.insertText("**");
cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1);
}
}
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
}
void VMdEditOperations::decorateInlineCode()
{
QTextCursor cursor = m_editor->textCursorW();
cursor.beginEditBlock();
if (cursor.hasSelection()) {
// Insert ` around the selected text.
int start = cursor.selectionStart();
int end = cursor.selectionEnd();
cursor.clearSelection();
cursor.setPosition(start, QTextCursor::MoveAnchor);
cursor.insertText("`");
cursor.setPosition(end + 1, QTextCursor::MoveAnchor);
cursor.insertText("`");
} else {
// Insert `` and place cursor in the middle.
// Or if there are one ` after current cursor, just skip them or delete
// them if two ` appear.
int pos = cursor.positionInBlock();
bool hasBackquote = false;
bool emptyMarkers = false;
QString text = cursor.block().text();
if (pos <= text.size() - 1) {
if (text[pos] == '`') {
hasBackquote = true;
if (pos >= 1 && text[pos - 1] == '`') {
emptyMarkers = true;
}
}
}
if (hasBackquote) {
if (emptyMarkers) {
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 1);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
cursor.removeSelectedText();
} else {
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
}
} else {
cursor.insertText("``");
cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1);
}
}
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
}
void VMdEditOperations::decorateCodeBlock()
{
const QString marker("```");
QTextCursor cursor = m_editor->textCursorW();
cursor.beginEditBlock();
if (cursor.hasSelection()) {
// Insert ``` around the selected text.
int start = cursor.selectionStart();
int end = cursor.selectionEnd();
QString indentation = VEditUtils::fetchIndentSpaces(cursor.block());
// Insert the end marker first.
cursor.setPosition(end, QTextCursor::MoveAnchor);
VEditUtils::insertBlock(cursor, false);
VEditUtils::indentBlock(cursor, indentation);
cursor.insertText(marker);
// Insert the start marker.
cursor.setPosition(start, QTextCursor::MoveAnchor);
VEditUtils::insertBlock(cursor, true);
VEditUtils::indentBlock(cursor, indentation);
cursor.insertText(marker);
} else {
// Insert ``` ``` and place cursor after the first marker.
// Or if current block or next block is ```, we will skip it.
QTextBlock block = cursor.block();
int state = block.userState();
if (state == HighlightBlockState::CodeBlock
|| state == HighlightBlockState::CodeBlockStart
|| state == HighlightBlockState::CodeBlockEnd) {
// Find the block end.
QTextBlock endBlock = block;
while (endBlock.isValid()) {
if (endBlock.userState() == HighlightBlockState::CodeBlockEnd) {
break;
}
endBlock = endBlock.next();
}
if (endBlock.isValid()) {
// It is CodeBlockEnd.
if (endBlock.previous().isValid()
&& endBlock.previous().userState() == HighlightBlockState::CodeBlockStart) {
// Delete empty code blocks.
cursor.setPosition(endBlock.previous().position());
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
} else {
cursor.setPosition(endBlock.position());
if (endBlock.next().isValid()) {
cursor.movePosition(QTextCursor::NextBlock);
cursor.movePosition(QTextCursor::StartOfBlock);
} else {
cursor.movePosition(QTextCursor::EndOfBlock);
}
}
} else {
// Reach the end of the document.
cursor.movePosition(QTextCursor::End);
}
} else {
bool insertInline = false;
if (!cursor.atBlockEnd()) {
cursor.insertBlock();
cursor.movePosition(QTextCursor::PreviousBlock);
} else if (cursor.atBlockStart() || VEditUtils::isSpaceBlock(block)) {
insertInline = true;
}
if (!insertInline) {
VEditUtils::insertBlock(cursor, false);
VEditUtils::indentBlockAsBlock(cursor, false);
}
cursor.insertText(marker);
VEditUtils::insertBlock(cursor, true);
VEditUtils::indentBlockAsBlock(cursor, true);
cursor.insertText(marker);
}
}
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
}
void VMdEditOperations::decorateStrikethrough()
{
QTextCursor cursor = m_editor->textCursorW();
cursor.beginEditBlock();
if (cursor.hasSelection()) {
// Insert ~~ around the selected text.
int start = cursor.selectionStart();
int end = cursor.selectionEnd();
cursor.clearSelection();
cursor.setPosition(start, QTextCursor::MoveAnchor);
cursor.insertText("~~");
cursor.setPosition(end + 2, QTextCursor::MoveAnchor);
cursor.insertText("~~");
} else {
// Insert ~~~~ and place cursor in the middle.
// Or if there are one ~~ after current cursor, just skip it or delete
// it if for ~ appear.
int pos = cursor.positionInBlock();
bool hasStrikethrough = false;
bool emptyMarkers = false;
QString text = cursor.block().text();
if (pos <= text.size() - 2) {
if (text[pos] == '~' && text[pos + 1] == '~') {
hasStrikethrough = true;
if (pos >= 2
&& text[pos - 1] == '~'
&& text[pos - 2] == '~') {
emptyMarkers = true;
}
}
}
if (hasStrikethrough) {
if (emptyMarkers) {
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 2);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
cursor.removeSelectedText();
} else {
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2);
}
} else {
cursor.insertText("~~~~");
cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
}
}
cursor.endEditBlock();
m_editor->setTextCursorW(cursor);
}
bool VMdEditOperations::insertLink(const QString &p_linkText,
const QString &p_linkUrl)
{
QString link = QString("[%1](%2)").arg(p_linkText).arg(p_linkUrl);
QTextCursor cursor = m_editor->textCursorW();
cursor.insertText(link);
m_editor->setTextCursorW(cursor);
setVimMode(VimMode::Insert);
return true;
}