使用qt编写简单的markdown编辑器
- 最近想用qt模仿anki来编写一套适合自己的复习软件,首先需要解决的是markdown,anki不支持markdown,而我的笔记都是以markdown形式保存的,所以复习软件必须要支持markdown。
- qt6.3之后就内置了markdown解析器,所以先研究下qt内置的markdown解析器能不能满足我的需求
- 先上效果图
- 从以上图片来看,解析的效果还可以,暂且使用qt内置的markdown解析器,如果以后满足不了需求的话再考虑第三方库。
布局与初始实现
-
从效果图来看,布局十分简单,就是一个水平均分的布局,左边的原始文本编辑器(
QPlainTextEdit控件),右边是markdown的渲染效果(QTextEdit控件) -
左边原始文本编辑器文本变化的时候,右边markdown的渲染会同步更新,使用qt内置的markdown解析器十分简单,如下代码所示
void ReviewApp::on_plainTextEdit_textChanged() { QString text = ui.plainTextEdit->toPlainText(); ui.markdownTextEdit->setMarkdown(text); } -
设置字体大小、字体、Tab的宽度
// 假设你的 QPlainTextEdit 名为 plainTextEdit QFont font; font.setFamily("Consolas"); // 推荐等宽字体,如 "Consolas"、"Courier New"、"Fira Mono" font.setPointSize(14); // 设置字号 ui.plainTextEdit->setFont(font); ui.markdownTextEdit->setFont(font); // 设置 Tab 宽度(以空格数为单位,常见为4或2) const int tabStop = 4; // 4个空格 QFontMetrics metrics(font); ui.plainTextEdit->setTabStopDistance(tabStop * metrics.horizontalAdvance(' '));
实现粘贴图片的功能
- 左侧的文本编辑器需要支持粘贴图片功能,原始的控件无法支持这个功能,我们需要定义一个类继承
QPlainTextEdit控件并重写insertFromMimeData方法
class MarkdownPlainTextEdit : public QPlainTextEdit
{
Q_OBJECT
public:
explicit MarkdownPlainTextEdit(QWidget* parent = nullptr) : QPlainTextEdit(parent) {}
protected:
void insertFromMimeData(const QMimeData* source) override
{
if (source->hasImage()) {
QImage image = qvariant_cast<QImage>(source->imageData());
QString fileName = QDir::temp().filePath(
QString("pasted_%1.png").arg(QDateTime::currentMSecsSinceEpoch()));
image.save(fileName, "PNG");
// 插入 Markdown 图片语法
this->insertPlainText(QString("\n").arg(fileName));
}
else {
QPlainTextEdit::insertFromMimeData(source);
}
}
};
改变tab的默认行为
-
如果在编辑器中选中一段文本,并按下Tab键,选中的文本将被替换为
Tab,这个在markdown中十分的ugly,所以我们改变这一默认行为 -
还是在
MarkdownPlainTextEdit重写keyPressEvent方法void keyPressEvent(QKeyEvent* event) override { if (event->key() == Qt::Key_Tab) { QTextCursor cursor = textCursor(); if (cursor.hasSelection()) { int start = cursor.selectionStart(); int end = cursor.selectionEnd(); cursor.setPosition(start); int firstBlock = cursor.blockNumber(); cursor.setPosition(end, QTextCursor::KeepAnchor); int lastBlock = cursor.blockNumber(); // 记录原始光标 int selStart = cursor.selectionStart(); int selEnd = cursor.selectionEnd(); cursor.beginEditBlock(); for (int i = firstBlock; i <= lastBlock; ++i) { QTextBlock block = document()->findBlockByNumber(i); QTextCursor blockCursor(block); blockCursor.insertText("\t"); // 或用空格替代 } cursor.endEditBlock(); // 重新选中原区域(向右偏移) QTextCursor newCursor = textCursor(); newCursor.setPosition(selStart); newCursor.setPosition(selEnd + (lastBlock - firstBlock + 1), QTextCursor::KeepAnchor); setTextCursor(newCursor); return; } } QPlainTextEdit::keyPressEvent(event); }
简单实现代码高亮
- qt内置的解析器无法高亮显示代码,我们需要简单的实现一下
#pragma once
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
#include <QRegularExpression>
class CodeHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
explicit CodeHighlighter(QTextDocument* parent)
: QSyntaxHighlighter(parent)
{
// 关键字格式
keywordFormat.setForeground(Qt::blue);
keywordFormat.setFontWeight(QFont::Bold);
QStringList keywordPatternsList = {
"\\bclass\\b", "\\bconst\\b", "\\bvoid\\b", "\\bint\\b", "\\bfloat\\b",
"\\bdouble\\b", "\\bQString\\b", "\\bpublic\\b", "\\bprivate\\b", "\\bprotected\\b",
"\\bif\\b", "\\belse\\b", "\\bfor\\b", "\\bwhile\\b", "\\breturn\\b"
};
for (const QString& pattern : keywordPatternsList) {
keywordPatterns.append(QRegularExpression(pattern));
}
// 注释格式
commentFormat.setForeground(Qt::darkGreen);
commentFormat.setFontItalic(true);
// 字符串格式
stringFormat.setForeground(Qt::darkRed);
}
protected:
void highlightBlock(const QString& text) override
{
// 高亮关键字
for (const QRegularExpression& pattern : keywordPatterns) {
QRegularExpressionMatchIterator i = pattern.globalMatch(text);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
setFormat(match.capturedStart(), match.capturedLength(), keywordFormat);
}
}
// 高亮字符串
QRegularExpression stringPattern("\".*?\"");
QRegularExpressionMatchIterator stringIt = stringPattern.globalMatch(text);
while (stringIt.hasNext()) {
QRegularExpressionMatch match = stringIt.next();
setFormat(match.capturedStart(), match.capturedLength(), stringFormat);
}
// 高亮注释
QRegularExpression commentPattern("//[^\n]*");
QRegularExpressionMatchIterator commentIt = commentPattern.globalMatch(text);
while (commentIt.hasNext()) {
QRegularExpressionMatch match = commentIt.next();
setFormat(match.capturedStart(), match.capturedLength(), commentFormat);
}
}
private:
QTextCharFormat keywordFormat;
QTextCharFormat commentFormat;
QTextCharFormat stringFormat;
QList<QRegularExpression> keywordPatterns;
};
- 在app的构造函数中只需要调用以下代码即可
new CodeHighlighter(ui.markdownTextEdit->document());