| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220 |
- #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 <QScrollBar>
- #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,
- "",
- false,
- m_editor->getEditor());
- dialog.setImage(image);
- if (dialog.exec() == QDialog::Accepted) {
- insertImageFromQImage(dialog.getImageTitleInput(),
- m_file->fetchImageFolderPath(),
- m_file->getImageFolderInLink(),
- image,
- dialog.getOverridenWidth());
- }
- return true;
- }
- // @p_width, @p_height: 0 if no override.
- static QString imageLink(const QString &p_title,
- const QString &p_url,
- int p_width = 0,
- int p_height = 0)
- {
- QString scale;
- if (p_width > 0) {
- if (p_height > 0) {
- scale = QString(" =%1x%2").arg(p_width).arg(p_height);
- } else {
- scale = QString(" =%1x").arg(p_width);
- }
- } else if (p_height > 0) {
- scale = QString(" =x%1").arg(p_height);
- }
- return QString("").arg(p_title).arg(p_url).arg(scale);
- }
- void VMdEditOperations::insertImageFromQImage(const QString &p_title,
- const QString &p_folderPath,
- const QString &p_folderInLink,
- const QImage &p_image,
- int p_width,
- int p_height)
- {
- QString fileName = VUtils::generateImageFileName(p_folderPath, p_title);
- QString filePath = QDir(p_folderPath).filePath(fileName);
- V_ASSERT(!QFile(filePath).exists());
- QString errStr;
- bool ret = VUtils::makePath(p_folderPath);
- if (!ret) {
- errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
- .arg(g_config->c_dataTextStyle).arg(p_folderPath);
- } else {
- ret = p_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(p_title),
- errStr,
- QMessageBox::Ok,
- QMessageBox::Ok,
- m_editor->getEditor());
- return;
- }
- QString url = QString("%1/%2").arg(p_folderInLink).arg(fileName);
- insertText(imageLink(p_title, url, p_width, p_height));
- qDebug() << "insert image" << p_title << filePath;
- VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
- Q_ASSERT(mdEditor);
- mdEditor->imageInserted(filePath, url);
- }
- void VMdEditOperations::insertImageFromPath(const QString &p_title,
- const QString &p_folderPath,
- const QString &p_folderInLink,
- const QString &p_srcImagePath,
- int p_width,
- int p_height)
- {
- QString destImagePath;
- QString urlInLink;
- insertImageFromPath(p_title,
- p_folderPath,
- p_folderInLink,
- p_srcImagePath,
- true,
- destImagePath,
- urlInLink,
- p_width,
- p_height);
- }
- void VMdEditOperations::insertImageFromPath(const QString &p_title,
- const QString &p_folderPath,
- const QString &p_folderInLink,
- const QString &p_srcImagePath,
- bool p_insertText,
- QString &p_destImagePath,
- QString &p_urlInLink,
- int p_width,
- int p_height)
- {
- p_destImagePath.clear();
- p_urlInLink.clear();
- // Make sure src image is valid.
- if (QImage(p_srcImagePath).isNull()) {
- qWarning() << "fail to insert invalid source image" << p_srcImagePath;
- return;
- }
- QString fileName = VUtils::generateImageFileName(p_folderPath,
- p_title,
- QFileInfo(p_srcImagePath).suffix());
- QString filePath = QDir(p_folderPath).filePath(fileName);
- V_ASSERT(!QFile(filePath).exists());
- QString errStr;
- bool ret = VUtils::makePath(p_folderPath);
- if (!ret) {
- errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
- .arg(g_config->c_dataTextStyle).arg(p_folderPath);
- } else {
- ret = QFile::copy(p_srcImagePath, 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(p_title),
- errStr,
- QMessageBox::Ok,
- QMessageBox::Ok,
- m_editor->getEditor());
- return;
- }
- p_urlInLink = QString("%1/%2").arg(p_folderInLink).arg(fileName);
- p_destImagePath = filePath;
- if (p_insertText) {
- insertText(imageLink(p_title, p_urlInLink, p_width, p_height));
- }
- qDebug() << "insert image" << p_title << filePath;
- VMdEditor *mdEditor = static_cast<VMdEditor *>(m_editor);
- Q_ASSERT(mdEditor);
- mdEditor->imageInserted(filePath, p_urlInLink);
- }
- 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 = VUtils::imageFromFile(imagePath);
- if (image.isNull()) {
- qWarning() << "inserted image is null" << imagePath;
- return false;
- }
- title = tr("Insert Image From File");
- } else {
- imagePath = imageUrl.toString();
- title = tr("Insert Image From Network");
- }
- VInsertImageDialog dialog(title,
- c_defaultImageTitle,
- imagePath,
- false,
- m_editor->getEditor());
- 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);
- }
- if (dialog.exec() == QDialog::Accepted) {
- if (isLocal) {
- insertImageFromPath(dialog.getImageTitleInput(),
- m_file->fetchImageFolderPath(),
- m_file->getImageFolderInLink(),
- imagePath,
- dialog.getOverridenWidth());
- } else {
- if (dialog.getImageType() == VInsertImageDialog::ImageType::LocalFile) {
- insertImageFromPath(dialog.getImageTitleInput(),
- m_file->fetchImageFolderPath(),
- m_file->getImageFolderInLink(),
- dialog.getPathInput(),
- dialog.getOverridenWidth());
- } else {
- insertImageFromQImage(dialog.getImageTitleInput(),
- m_file->fetchImageFolderPath(),
- m_file->getImageFolderInLink(),
- dialog.getImage(),
- dialog.getOverridenWidth());
- }
- }
- }
- return true;
- }
- bool VMdEditOperations::insertImage()
- {
- // Use empty title and path to let the dialog auto complete.
- VInsertImageDialog dialog(tr("Insert Image"),
- "",
- "",
- true,
- m_editor->getEditor());
- if (dialog.exec() == QDialog::Accepted) {
- VInsertImageDialog::ImageType type = dialog.getImageType();
- if (type == VInsertImageDialog::ImageType::LocalFile) {
- insertImageFromPath(dialog.getImageTitleInput(),
- m_file->fetchImageFolderPath(),
- m_file->getImageFolderInLink(),
- dialog.getPathInput(),
- dialog.getOverridenWidth());
- } else {
- QImage img = dialog.getImage();
- if (!img.isNull()) {
- insertImageFromQImage(dialog.getImageTitleInput(),
- m_file->fetchImageFolderPath(),
- m_file->getImageFolderInLink(),
- img,
- dialog.getOverridenWidth());
- }
- }
- }
- 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_Apostrophe:
- {
- if (modifiers == Qt::ControlModifier) {
- m_editor->insertImage();
- p_event->accept();
- ret = true;
- }
- break;
- }
- case Qt::Key_M:
- {
- if (modifiers == Qt::ControlModifier) {
- decorateCodeBlock();
- p_event->accept();
- ret = true;
- }
- break;
- }
- case Qt::Key_Semicolon:
- {
- 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.
- V_FALLTHROUGH;
- 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;
- }
- case Qt::Key_J:
- {
- if (VUtils::isControlModifierForVim(modifiers)) {
- // Scroll down without changing cursor.
- QScrollBar *vbar = m_editor->verticalScrollBarW();
- if (vbar && (vbar->minimum() != vbar->maximum())) {
- vbar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
- }
- ret = true;
- }
- break;
- }
- case Qt::Key_K:
- {
- if (VUtils::isControlModifierForVim(modifiers)) {
- // Scroll up without changing cursor.
- QScrollBar *vbar = m_editor->verticalScrollBarW();
- if (vbar && (vbar->minimum() != vbar->maximum())) {
- vbar->triggerAction(QAbstractSlider::SliderSingleStepSub);
- }
- ret = true;
- }
- break;
- }
- case Qt::Key_N:
- {
- if (VUtils::isControlModifierForVim(modifiers)) {
- // Completion.
- if (!m_editor->isCompletionActivated()) {
- m_editor->requestCompletion(false);
- }
- ret = true;
- }
- break;
- }
- case Qt::Key_P:
- {
- if (VUtils::isControlModifierForVim(modifiers)) {
- // Completion.
- if (!m_editor->isCompletionActivated()) {
- m_editor->requestCompletion(true);
- }
- 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
- // For mapping Caps as Ctrl in KDE.
- && key != Qt::Key_CapsLock) {
- 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)
- {
- 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(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".
- insertText(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;
- }
- QTextCursor cursor = m_editor->textCursorW();
- QTextBlock block = m_editor->documentW()->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(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 {
- int indent = VEditUtils::fetchIndentation(cursor.block());
- int pib = cursor.positionInBlock();
- if (pib <= indent) {
- ret = cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
- } else {
- ret = cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, pib - indent);
- }
- }
- 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;
- bool autoQuote = 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.
- // But we still need auto quote.
- 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();
- cursor.beginEditBlock();
- cursor.removeSelectedText();
- // Indent the new line as previous line.
- bool textInserted = VEditUtils::insertBlockWithIndent(cursor);
- // Continue the list from previous line.
- bool listInserted = false;
- if (autolist && g_config->getAutoList()) {
- listInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor);
- textInserted = listInserted || textInserted;
- }
- if (autoQuote && !listInserted && g_config->getAutoQuote()) {
- textInserted = VEditUtils::insertQuoteMarkAsPreviousBlock(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);
- insertText(link);
- setVimMode(VimMode::Insert);
- return true;
- }
- bool VMdEditOperations::insertImageLink(const QString &p_linkText,
- const QString &p_linkUrl)
- {
- QTextCursor cursor = m_editor->textCursorW();
- cursor.insertText(imageLink(p_linkText, p_linkUrl));
- m_editor->setTextCursorW(cursor);
- setVimMode(VimMode::Insert);
- return true;
- }
|