ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用C++和Qt构建实时预览的Markdown编辑器

用C++和Qt构建实时预览的Markdown编辑器 用 C 写一个带实时预览的 Markdown 编辑器听起来像是一个不小的工作量其实拆开看只有三件事左侧的文本编辑区、右侧的预览区以及把 Markdown 内容即时转换为 HTML 的解析层。这篇文章用 Qt 和 cmark 库从零搭出一个最小可运行版本并逐步把语法高亮、本地图片加载、常见环境问题排查等内容补上。如果你已经能写完一个简单的 Qt Widgets 窗口程序但对 Markdown 解析和实时刷新还没有完整思路这篇文章会给你一条清晰的落地路径。实时预览不是指用户点一下按钮才渲染而是编辑区内容发生变化后预览区自动更新。要做到这一点依赖一个稳定的信号循环和解析库。读完这篇内容后你可以得到一个带实时预览的桌面 Markdown 编辑器骨架如果要继续扩展成笔记工具、技术写作工具或内部文档客户端也可以在此基础上直接加文件管理、主题切换、导入导出等功能。1. 实时预览编辑器要拆成哪几个模块1.1 输入、解析、渲染三层Markdown 编辑器的核心并不神秘。用户在一个文本控件里输入 Markdown 原文程序把原文交给解析器解析器生成 HTML再把 HTML 交给预览控件渲染。整个流程就是“输入 - 解析 - 渲染”三个环节。输入层QPlainTextEdit负责接收键盘输入、支持基本的文本选择、撤销重做。解析层cmark把 Markdown 文本转换成 HTML 字符串。渲染层QTextBrowser把 HTML 字符串展示为富文本外部链接可以交给系统浏览器处理。把这三个环节拆开之后实时预览就只是一个信号连接问题当编辑区的文本发生变化时重新执行一次“解析 - 渲染”即可。这段链路里最容易被低估的是解析层。很多人会先写一个 replace 函数把#替换成h1把**替换成strong。这种方式处理简单的单行语法没问题一旦遇到嵌套列表、代码块、转义字符和链接就会漏洞百出。所以直接使用成熟的 Markdown 解析库是更值得投入的选择。1.2 为什么选 Qt cmark不自己写解析器Markdown 语法看起来简单真正解析起来非常容易出错。列表嵌套、段落换行、行内代码、转义字符、引用块、任务列表、表格每一类都有边界情况。自己写一个支持常用语法的解析器至少要几百行而且处理不完整会造成预览和源码不一致。直接使用开源解析库是更稳妥的方案。cmark 是 CommonMark 官方参考实现用 C 语言编写提供了非常简洁的 C API。它没有额外依赖编译出来只有一个静态库或动态库很适合嵌入到 C 工程里。Qt 则负责 UI、事件循环、富文本显示和跨平台编译。这两个库搭配可以比较轻松地完成一个可用的桌面编辑器。在选型时还要考虑渲染层。最简单的是QTextBrowser它基于QTextDocument支持 HTML 子集不需要额外安装浏览器内核。若需要完整支持 CSS、JavaScript、代码高亮可以使用QWebEngineView但代价是运行时依赖 WebEngine。对于大多数笔记场景QTextBrowser已经足够。注意预览控件的选型会影响发布包体积和启动速度。QTextBrowser 轻量QWebEngineView 渲染能力强两者不是替代关系而是不同场景的取舍。1.3 最小闭环目标和学习环境差异这篇文章的最小目标很明确打开程序后左侧输入 Markdown右侧实时刷新 HTML 渲染结果。先把这条链路跑通再谈高亮、文件保存、主题切换等功能。在学习环境里建议直接使用 Qt Creator 或 CMake 命令行工程不要一开始就加入复杂的插件系统。生产环境则需要考虑打包、日志、配置外置、崩溃收集等问题。如果你是在公司项目里落地还需要把 Markdown 解析结果做缓存、控制在每次按键时是否全量解析以及兼容不同电脑上的运行时版本。2. 环境准备与依赖配置2.1 编译工具链和 Qt 版本选择文章示例使用 Qt Widgets 模块不依赖 QML。推荐 Qt 5.15 或 Qt 6.5 以上二者 API 在本文用到的范围内基本一致。如果你在 Windows 上使用 MSVC需要同时安装 Visual C Redistributable在 Linux 上使用 GCC则需要安装 base-devel 或 build-essential。很多 C 项目启动时报缺少VCRUNTIME140.dll本质上就是 Visual C Redistributable 版本没有装全。如果你是第一次创建工程建议用 Qt Creator 自带的 “Qt Widgets Application” 模板再手动加入 cmark 依赖。本文的代码结构如下MarkdownEditor/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ ├── MainWindow.h │ ├── MainWindow.cpp │ ├── MarkdownHighlighter.h │ └── MarkdownHighlighter.cpp这个结构足够支撑一个带预览、高亮和文件处理的小型桌面程序。如果你后续要加入测试或插件可以在src旁边继续增加tests、plugins目录。2.2 安装 cmark 的三种方式cmark 的安装方式取决于操作系统和包管理器。在 Ubuntu / Debian 上sudo apt update sudo apt install libcmark-dev在 Windows 上使用 vcpkgvcpkg install cmark如果你不想使用系统包管理器也可以直接源码编译git clone https://github.com/commonmark/cmark.git cd cmark mkdir build cd build cmake .. cmake --build . --config Release sudo cmake --install .源码编译方式是通用兜底方案。需要注意的是不同发行版提供的 cmark 版本可能不同CMake config 文件的 target 名称也可能不同。落地上机前先确认安装版本和 CMake 能找到的 target 名称。2.3 CMake 工程里同时连接 Qt 和 cmark在 CMakeLists.txt 中先查找 Qt再查找 cmark。下面是一个兼容 Qt5 / Qt6 的示例cmake_minimum_required(VERSION 3.16) project(MarkdownEditor LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) find_package(Qt6 COMPONENTS Widgets QUIET) if(NOT Qt6_FOUND) find_package(Qt5 5.15 REQUIRED COMPONENTS Widgets) endif() find_package(cmark CONFIG QUIET) if(NOT cmark_FOUND) find_package(PkgConfig REQUIRED) pkg_check_modules(CMARK IMPORTED_TARGET REQUIRED libcmark) endif() if(TARGET cmark::cmark) set(CMARK_TARGET cmark::cmark) elseif(TARGET PkgConfig::CMARK) set(CMARK_TARGET PkgConfig::CMARK) else() set(CMARK_TARGET cmark) endif() add_executable(markdown_editor src/main.cpp src/MainWindow.cpp src/MainWindow.h src/MarkdownHighlighter.cpp src/MarkdownHighlighter.h ) target_link_libraries(markdown_editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${CMARK_TARGET} )关键点有两处。一是CMAKE_AUTOMOC必须开启因为MainWindow和MarkdownHighlighter都是 QObject 子类需要处理信号槽。二是LANGUAGES CXX C要同时包含 C因为 cmark 本身是 C 项目虽然最终通过库连接但某些构建方式会需要 C 语言编译规则。如果find_package找不到 cmark通常是因为没有安装开发包或没有设置CMAKE_PREFIX_PATH。不要直接手写target_link_libraries指向一个假设的路径优先使用 CMake 的 package 机制这样换机器后更容易复现。3. 实现一个最小可运行版本3.1 创建主窗口与左右分栏布局主窗口使用QSplitter把编辑区和预览区左右排布。QSplitter不仅提供分隔条还允许用户拖动调整左右宽度这对 Markdown 编辑器是基本体验要求。#include QMainWindow #include QSplitter #include QPlainTextEdit #include QTextBrowser class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent nullptr); private slots: void renderMarkdown(); private: QPlainTextEdit *editor_; QTextBrowser *preview_; };构造函数的实现如下#include MainWindow.h MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), editor_(new QPlainTextEdit(this)), preview_(new QTextBrowser(this)) { auto *splitter new QSplitter(Qt::Horizontal, this); splitter-addWidget(editor_); splitter-addWidget(preview_); splitter-setStretchFactor(0, 1); splitter-setStretchFactor(1, 1); setCentralWidget(splitter); setWindowTitle(C Markdown Live Preview); resize(1000, 700); connect(editor_, QPlainTextEdit::textChanged, this, MainWindow::renderMarkdown); }在renderMarkdown()尚未实现之前程序可以编译运行但右侧不会更新。先把界面搭出来是为了验证布局是否正常再继续加入解析逻辑。3.2 用 cmark 把 Markdown 转成 HTML这是整篇文章最核心的一段代码。先写一个工具函数输入QString输出QString#include cmark.h QString markdownToHtml(const QString markdown) { QByteArray utf8 markdown.toUtf8(); char *html cmark_markdown_to_html( utf8.constData(), utf8.size(), CMARK_OPT_DEFAULT ); QString result QString::fromUtf8(html); free(html); return result; }cmark_markdown_to_html接受const char*和字节长度所以必须先把 QString 转成 UTF-8 字节数组。返回的char*是 cmark 内部通过 malloc 分配的内存用完后必须调用free()否则每次预览都会泄漏一块内存。CMARK_OPT_DEFAULT表示使用 CommonMark 默认行为。常用选项如下选项作用使用建议CMARK_OPT_DEFAULT默认解析行为不输出源码位置保留 HTML 块中的潜在危险标签基础场景使用CMARK_OPT_SOURCEPOS在输出 HTML 中加入 sourcepos 属性调试解析问题CMARK_OPT_HARDBREAKS将普通换行渲染为br需要保留换行时使用CMARK_OPT_UNSAFE允许渲染原始 HTML 和危险链接仅信任输入时使用CMARK_OPT_SMART将直引号、省略号等转为弯引号文档排版美化场景默认情况下script等原始 HTML 不会被渲染成可执行内容这是安全设计不是 bug。若编辑器用于公开内容不要随便开启CMARK_OPT_UNSAFE。3.3 用 textChanged 信号驱动实时预览QPlainTextEdit::textChanged在每次文本内容变化时触发。在这里连接renderMarkdown就能实现“敲一个字预览区刷新”的效果。void MainWindow::renderMarkdown() { QString html markdownToHtml(editor_-toPlainText()); preview_-setHtml(html); }这段代码简单直接但每次按键都会对全文重新解析。对于几 KB 的文本没有问题一旦打开几百 KB 的文档连续打字会造成明显卡顿。更稳妥的做法是加一个去抖定时器把连续触发合并为一次解析。auto *deb
返回列表