Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/controls/settingsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@
auto shortcuts_window_jumptolineName = QObject::tr("Go to line");
auto shortcuts_window_savepositionName = QObject::tr("Save cursor position");
auto shortcuts_window_restorepositionName = QObject::tr("Reset cursor position");
auto shortcuts_window_escapeName = QObject::tr("Exit");

Check warning on line 41 in src/controls/settingsdialog.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'shortcuts_window_escapeName' is assigned a value that is never used.
auto shortcuts_window_displayshortcutsName = QObject::tr("Display shortcuts");

Check warning on line 42 in src/controls/settingsdialog.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'shortcuts_window_displayshortcutsName' is assigned a value that is never used.
auto shortcuts_window_printName = QObject::tr("Print");

Check warning on line 43 in src/controls/settingsdialog.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'shortcuts_window_printName' is assigned a value that is never used.
auto shortcuts_window_togglemarkdownpreviewName = QObject::tr("Toggle markdown preview");

Check warning on line 44 in src/controls/settingsdialog.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'shortcuts_window_togglemarkdownpreviewName' is assigned a value that is never used.
auto group_editorName = QObject::tr("Editor");

Check warning on line 45 in src/controls/settingsdialog.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'group_editorName' is assigned a value that is never used.
auto shortcuts_editor_indentlineName = QObject::tr("Increase indent");

Check warning on line 46 in src/controls/settingsdialog.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'shortcuts_editor_indentlineName' is assigned a value that is never used.
auto shortcuts_editor_backindentlineName = QObject::tr("Decrease indent");

Check warning on line 47 in src/controls/settingsdialog.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'shortcuts_editor_backindentlineName' is assigned a value that is never used.
auto shortcuts_editor_forwardcharName = QObject::tr("Forward character");
auto shortcuts_editor_backwardcharName = QObject::tr("Backward character");
auto shortcuts_editor_forwardwordName = QObject::tr("Forward word");
Expand Down
64 changes: 58 additions & 6 deletions src/editor/editwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

#include "editwrapper.h"
#include "leftareaoftextedit.h"
#include "markdownpreview.h"
#include "drecentmanager.h"

Check warning on line 8 in src/editor/editwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "drecentmanager.h" not found.

#include <unistd.h>

Check warning on line 10 in src/editor/editwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <unistd.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <QCoreApplication>
#include <QApplication>
Expand All @@ -15,9 +16,10 @@
#include <QScroller>
#include <QDebug>
#include <QTimer>
#include <QDir>

Check warning on line 19 in src/editor/editwrapper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QFileInfo>
#include <QEvent>
#include <QSplitter>

#include <DSettings>
#include <DSettingsOption>
Expand Down Expand Up @@ -103,16 +105,41 @@
m_pTextEdit->setAccessibleName("TextEditor");
m_pBottomBar->setAccessibleName("EditorBottomBar");
// Init layout and widgets.
QHBoxLayout *m_layout = new QHBoxLayout;
m_pLeftAreaTextEdit = m_pTextEdit->getLeftAreaWidget();
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
m_layout->addWidget(m_pLeftAreaTextEdit);
m_layout->addWidget(m_pTextEdit);
m_pTextEdit->setWrapper(this);

// 编辑器容器:行号/标记区 + 文本编辑区,保持原有布局
QWidget *editorContainer = new QWidget(this);
QHBoxLayout *editorLayout = new QHBoxLayout(editorContainer);
editorLayout->setContentsMargins(0, 0, 0, 0);
editorLayout->setSpacing(0);
editorLayout->addWidget(m_pLeftAreaTextEdit);
editorLayout->addWidget(m_pTextEdit);

// 编辑区与 Markdown 预览分栏,默认只显示编辑区
m_pEditSplitter = new QSplitter(Qt::Horizontal, this);
m_pEditSplitter->setObjectName("EditSplitter");
m_pEditSplitter->setChildrenCollapsible(false);
m_pEditSplitter->setHandleWidth(1);
m_pEditSplitter->addWidget(editorContainer);
m_pEditSplitter->setStretchFactor(0, 1);

// Qt5 构建下预览不可用(QTextDocument::setMarkdown 需要 Qt >= 6.5)
if (MarkdownPreview::isSupported()) {
m_pMarkdownPreview = new MarkdownPreview(this);
m_pMarkdownPreview->setVisible(false);
m_pEditSplitter->addWidget(m_pMarkdownPreview);
m_pEditSplitter->setStretchFactor(1, 1);
// 编辑内容变化时实时刷新预览
connect(m_pTextEdit, &QPlainTextEdit::textChanged, this, [this]() {
if (m_pMarkdownPreview != nullptr && m_pMarkdownPreview->isVisible()) {
m_pMarkdownPreview->updatePreview(m_pTextEdit->toPlainText());
}
});
}

QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(m_layout);
mainLayout->addWidget(m_pEditSplitter);
mainLayout->addWidget(m_pBottomBar);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
Expand Down Expand Up @@ -780,6 +807,10 @@
qDebug() << "EditWrapper updatePath, m_pTextEdit->setFilePath(file)";
m_pTextEdit->setTruePath(qstrTruePath);
qDebug() << "EditWrapper updatePath, m_pTextEdit->setTruePath(qstrTruePath)";
// Markdown 文件打开时自动显示可视化预览
if (m_pMarkdownPreview != nullptr) {
setMarkdownPreviewVisible(MarkdownPreview::isMarkdownFile(file));
}
}

bool EditWrapper::isModified()
Expand Down Expand Up @@ -1482,6 +1513,27 @@
return m_pTextEdit->getFilePath();
}

void EditWrapper::setMarkdownPreviewVisible(bool visible)
{
if (m_pMarkdownPreview == nullptr) {
return;
}
m_pMarkdownPreview->setVisible(visible);
if (visible) {
m_pMarkdownPreview->updatePreview(m_pTextEdit->toPlainText());
// 保证初次显示时左右分栏比例合理(编辑区 55% / 预览 45%)
if (m_pEditSplitter != nullptr && m_pEditSplitter->width() > 0) {
const int total = m_pEditSplitter->width();
m_pEditSplitter->setSizes({total * 55 / 100, total * 45 / 100});
}
}
}

bool EditWrapper::isMarkdownPreviewVisible() const
{
return m_pMarkdownPreview != nullptr && m_pMarkdownPreview->isVisible();
}

TextEdit *EditWrapper::textEditor()
{
qDebug() << "EditWrapper textEditor";
Expand Down
10 changes: 10 additions & 0 deletions src/editor/editwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <KSyntaxHighlighting/Theme>

class Window;
class MarkdownPreview;
class QSplitter;
class EditWrapper : public QWidget
{
Q_OBJECT
Expand Down Expand Up @@ -108,6 +110,10 @@ class EditWrapper : public QWidget
Window *window();
void updateHighlighterAll();

// Markdown 可视化预览开关(Qt5 构建下预览不可用,调用无效果)
void setMarkdownPreviewVisible(bool visible);
bool isMarkdownPreviewVisible() const;

//get and set m_tModifiedDateTime
QDateTime getLastModifiedTime() const;
void setLastModifiedTime(const QString &time);
Expand Down Expand Up @@ -175,6 +181,10 @@ public slots:
BottomBar *m_pBottomBar = nullptr;
//
WarningNotices *m_pWaringNotices = nullptr;
//
MarkdownPreview *m_pMarkdownPreview = nullptr;
// 编辑区与 Markdown 预览的分栏容器
QSplitter *m_pEditSplitter = nullptr;

QDateTime m_tModifiedDateTime;
//退出
Expand Down
65 changes: 65 additions & 0 deletions src/editor/markdownpreview.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "markdownpreview.h"

#include <QFileInfo>
#include <QTextDocument>
#include <QUrl>

MarkdownPreview::MarkdownPreview(QWidget *parent)
: QTextBrowser(parent)
{
setAccessibleName("MarkdownPreviewView");
setFrameShape(QFrame::NoFrame);
setOpenLinks(false);
setOpenExternalLinks(false);
setReadOnly(true);
}

bool MarkdownPreview::isSupported()
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
return true;
#else
return false;
#endif
}

bool MarkdownPreview::isMarkdownFile(const QString &filePath)
{
const QString suffix = QFileInfo(filePath).suffix().toLower();
return suffix == QStringLiteral("md") || suffix == QStringLiteral("markdown");
}

void MarkdownPreview::updatePreview(const QString &markdown)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
QTextDocument *newDoc = new QTextDocument(this);
// 主题无关的基础样式:标题分级 + 等宽代码,颜色随应用调色板自适应
newDoc->setDefaultStyleSheet(QStringLiteral(
"h1 { font-size: 26px; font-weight: 600; }"
"h2 { font-size: 22px; font-weight: 600; }"
"h3 { font-size: 19px; font-weight: 600; }"
"h4 { font-size: 16px; font-weight: 600; }"
"h5 { font-size: 14px; font-weight: 600; }"
"h6 { font-size: 13px; font-weight: 600; }"
"code, pre { font-family: 'monospace'; }"
"blockquote { margin-left: 16px; }"));
newDoc->setMarkdown(markdown);

// setDocument 会接管新文档(父对象为 this)并释放上一份文档,无需手动 delete
setDocument(newDoc);
#else
Q_UNUSED(markdown)
setPlainText(markdown);
#endif
}

void MarkdownPreview::doSetSource(const QUrl &url, QTextDocument::ResourceType type)
{
Q_UNUSED(url)
Q_UNUSED(type)
// 预览只读,不进行链接跳转
}
37 changes: 37 additions & 0 deletions src/editor/markdownpreview.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef MARKDOWNPREVIEW_H
#define MARKDOWNPREVIEW_H

#include <QTextBrowser>

/**
* @brief Markdown 可视化预览控件
*
* 将 markdown 文本实时渲染为富文本展示,基于 QTextDocument::setMarkdown(Qt >= 6.5)。
* Qt5 构建下预览功能整体禁用(isSupported() 返回 false)。
*/
class MarkdownPreview : public QTextBrowser
{
Q_OBJECT

public:
explicit MarkdownPreview(QWidget *parent = nullptr);

// 当前构建是否支持 markdown 预览(需要 Qt >= 6.5)
static bool isSupported();

// 是否为可预览的 markdown 文件(.md / .markdown)
static bool isMarkdownFile(const QString &filePath);

// 以 markdown 内容刷新预览
void updatePreview(const QString &markdown);

protected:
// 预览为只读展示,忽略超链接跳转
void doSetSource(const QUrl &url, QTextDocument::ResourceType type = QTextDocument::UnknownResource) override;
};

#endif // MARKDOWNPREVIEW_H
6 changes: 6 additions & 0 deletions src/resources/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@
"name": "Print",
"type": "keySequenceEdit",
"default": "Ctrl+P"
},
{
"key": "togglemarkdownpreview",
"name": "Toggle markdown preview",
"type": "keySequenceEdit",
"default": "Ctrl+Shift+M"
}
]
},
Expand Down
6 changes: 6 additions & 0 deletions src/resources/settings.json.in
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@
"name": "Print",
"type": "keySequenceEdit",
"default": "Ctrl+P"
},
{
"key": "togglemarkdownpreview",
"name": "Toggle markdown preview",
"type": "keySequenceEdit",
"default": "Ctrl+Shift+M"
}
]
},
Expand Down
39 changes: 39 additions & 0 deletions src/widgets/window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "window.h"
#include "pathsettintwgt.h"
#include "../editor/markdownpreview.h"
#include <DTitlebar>
#include <DAnchors>
#include <DSettingsWidgetFactory>
Expand Down Expand Up @@ -477,6 +478,8 @@ void Window::updateSaveAsFileName(QString strOldFilePath, QString strNewFilePath

m_wrappers.remove(strOldFilePath);
m_wrappers.insert(strNewFilePath, wrapper);
// 另存为可能改变文件类型,刷新 Markdown 预览开关状态
updateMarkdownPreviewActionState();
qDebug() << "updateSaveAsFileName end";
}

Expand Down Expand Up @@ -539,6 +542,11 @@ void Window::initTitlebar()
saveAsAction->setObjectName("SaveAs");
QAction *printAction(new QAction(tr("Print"), this));
printAction->setObjectName("Print");
QAction *markdownPreviewAction(new QAction(tr("Markdown preview"), this));
markdownPreviewAction->setObjectName("MarkdownPreview");
markdownPreviewAction->setCheckable(true);
markdownPreviewAction->setEnabled(false);
m_markdownPreviewAction = markdownPreviewAction;
QAction *switchThemeAction(new QAction(tr("Switch theme"), this));
switchThemeAction->setObjectName("SwitchTheme");
QAction *settingAction(new QAction(tr("Settings"), this));
Expand All @@ -557,6 +565,7 @@ void Window::initTitlebar()
m_menu->addAction(saveAction);
m_menu->addAction(saveAsAction);
m_menu->addAction(printAction);
m_menu->addAction(markdownPreviewAction);
//此接口不可删除,预留的编辑器内部主题选择接口
//m_menu->addAction(switchThemeAction);
m_menu->addSeparator();
Expand Down Expand Up @@ -627,6 +636,7 @@ void Window::initTitlebar()
connect(saveAction, &QAction::triggered, this, &Window::saveFile);
connect(saveAsAction, &QAction::triggered, this, &Window::saveAsFile);
connect(printAction, &QAction::triggered, this, &Window::popupPrintDialog);
connect(markdownPreviewAction, &QAction::triggered, this, &Window::toggleMarkdownPreview);
connect(settingAction, &QAction::triggered, this, &Window::popupSettingsDialog);
connect(switchThemeAction, &QAction::triggered, this, &Window::popupThemePanel);
qDebug() << "initTitlebar end";
Expand Down Expand Up @@ -3553,6 +3563,9 @@ void Window::handleCurrentChanged(const int &index)
currentWrapper()->bottomBar()->show();
currentWrapper()->bottomBar()->updateSize(BottomBar::defaultHeight(), false);
}

// 同步 Markdown 预览菜单项状态(可用性 + 勾选状态)
updateMarkdownPreviewActionState();
qDebug() << "handleCurrentChanged end";
}

Expand Down Expand Up @@ -4515,6 +4528,9 @@ void Window::keyPressEvent(QKeyEvent *e)
} else if (key == Utils::getKeyshortcutFromKeymap(m_settings, "window", "print")) {
qDebug() << "key press event popup print dialog";
popupPrintDialog();
} else if (key == Utils::getKeyshortcutFromKeymap(m_settings, "window", "togglemarkdownpreview")) {
qDebug() << "key press event toggle markdown preview";
toggleMarkdownPreview();
} else {
qDebug() << "key press event post event to window widget";
// Post event to window widget if match Alt+0 ~ Alt+9
Expand Down Expand Up @@ -4654,6 +4670,29 @@ void Window::setPrintEnabled(bool enabled)
}
}

void Window::toggleMarkdownPreview()
{
EditWrapper *wrapper = currentWrapper();
if (wrapper == nullptr || !MarkdownPreview::isSupported()
|| !MarkdownPreview::isMarkdownFile(wrapper->filePath())) {
return;
}
wrapper->setMarkdownPreviewVisible(!wrapper->isMarkdownPreviewVisible());
updateMarkdownPreviewActionState();
}

void Window::updateMarkdownPreviewActionState()
{
if (m_markdownPreviewAction == nullptr) {
return;
}
EditWrapper *wrapper = currentWrapper();
const bool isMarkdown = (wrapper != nullptr) && MarkdownPreview::isSupported()
&& MarkdownPreview::isMarkdownFile(wrapper->filePath());
m_markdownPreviewAction->setEnabled(isMarkdown);
m_markdownPreviewAction->setChecked(isMarkdown && wrapper->isMarkdownPreviewVisible());
}

QStackedWidget *Window::getStackedWgt()
{
qDebug() << "get stacked wgt";
Expand Down
7 changes: 7 additions & 0 deletions src/widgets/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class Window : public DMainWindow
void popupThemePanel();

void toggleFullscreen();
// Markdown 可视化预览开关(仅当前文件为 markdown 时可用)
void toggleMarkdownPreview();

void remberPositionSave();
void remberPositionRestore();
Expand Down Expand Up @@ -287,6 +289,11 @@ public Q_SLOTS:
bool m_bBatchAddingPendingTabs = false; // 批量添加 pending 标签页期间,阻止 handleCurrentChanged 触发加载

DMenu *m_menu {nullptr};
// Markdown 预览开关菜单项,状态随当前标签页同步
QAction *m_markdownPreviewAction {nullptr};

// 刷新 Markdown 预览开关的可用性与勾选状态
void updateMarkdownPreviewActionState();

QStringList m_closeFileHistory;

Expand Down
Loading
Loading