You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python PyQt6实现类Obsidian的Markdown编辑器功能

PyQt6实现Obsidian式Markdown编辑器的问题解决

问题说明

作为Python新手,我用PyQt6开发Markdown编辑器,想要实现类似Obsidian的效果:不需要分栏显示编辑区和渲染区,单个TextEdit组件既能接收输入,同时渲染显示除当前编辑行外的所有内容。但目前的代码在输入字符时会自动切换到渲染视图,必须手动切回编辑模式,求解决方法。

原尝试代码:

from PyQt6 import QtCore, QtGui, QtWidgets

# This isn't live view but the stakcable widgets might be something that we can use 

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.stacked_widget = QtWidgets.QStackedWidget()
        self.setCentralWidget(self.stacked_widget)

        self.markdown_editor = QtWidgets.QTextEdit()
        self.markdown_viewer = QtWidgets.QTextEdit(readOnly=True)

        self.stacked_widget.addWidget(self.markdown_editor)
        self.stacked_widget.addWidget(self.markdown_viewer)

        foo_menu = self.menuBar().addMenu("&FooMenu")
        self.edit_action = foo_menu.addAction("&Edit")
        self.edit_action.setCheckable(True)
        self.edit_action.triggered.connect(self.handle_edit_mode)
        self.text_updated()

    def handle_edit_mode(self):
        self.stacked_widget.setCurrentWidget(
            self.markdown_viewer
            if self.edit_action.isChecked()
            else self.markdown_editor
        )
        if self.stacked_widget.currentWidget() == self.markdown_viewer:
            self.markdown_viewer.setMarkdown(self.markdown_editor.toPlainText())
    def switch(self):
        print("Function switch run")
        self.stacked_widget.setCurrentWidget(self.markdown_viewer)
        self.edit_action.setChecked(False)
        
    def text_updated(self):
        self.markdown_editor.textChanged.connect(self.switch)
        print("Text updated")


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.show()

    sys.exit(app.exec())

问题根源

原代码中,markdown_editor的textChanged信号绑定了switch函数,只要输入任何字符就会触发切换到渲染视图,这就是输入时自动跳转的核心原因。另外,StackedWidget的切换逻辑是完全替换整个组件,无法实现“同一区域内编辑当前行、渲染其他行”的Obsidian式效果。

解决方案

方案1:快速修复输入自动切换问题

如果只是想先解决“输入自动跳转到渲染视图”的问题,只需删除text_updated函数中绑定textChanged到switch的代码,修改后的text_updated函数如下:

def text_updated(self):
    print("Text updated")

但这种方式仅能实现手动切换编辑/渲染视图,无法达到Obsidian的实时混合显示效果。

方案2:实现Obsidian式混合编辑渲染效果

要实现目标效果,需要放弃StackedWidget的切换思路,改为在单个QTextEdit中混合处理编辑和渲染:实时将除当前光标所在行外的内容渲染为Markdown格式,当前行保持纯文本编辑状态。以下是完整代码:

from PyQt6 import QtCore, QtGui, QtWidgets
import markdown

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Obsidian-Style Markdown Editor")
        
        # 单个QTextEdit作为编辑/渲染容器
        self.editor = QtWidgets.QTextEdit()
        self.setCentralWidget(self.editor)
        
        # 监听光标位置变化和文本变更,实时更新显示
        self.editor.cursorPositionChanged.connect(self.update_display)
        self.editor.textChanged.connect(self.update_display)
        
        # 初始化显示
        self.update_display()

    def update_display(self):
        # 获取当前文本和光标位置
        plain_text = self.editor.toPlainText()
        cursor = self.editor.textCursor()
        current_line = cursor.blockNumber()
        
        # 拆分文本为行
        lines = plain_text.split('\n')
        html_content = []
        
        for idx, line in enumerate(lines):
            if idx == current_line:
                # 当前行保持纯文本,用<pre>包裹避免格式影响
                encoded_line = QtCore.QUrl.toPercentEncoding(line).data().decode()
                html_content.append(f'<pre>{encoded_line}</pre>')
            else:
                # 非当前行渲染为Markdown
                rendered_line = markdown.markdown(line)
                html_content.append(rendered_line)
        
        # 拼接所有内容,设置为富文本
        full_html = '\n'.join(html_content)
        # 临时保存光标位置,避免设置内容后光标跳走
        cursor_pos = cursor.position()
        self.editor.setHtml(full_html)
        # 恢复光标位置
        new_cursor = self.editor.textCursor()
        new_cursor.setPosition(cursor_pos)
        self.editor.setTextCursor(new_cursor)

if __name__ == "__main__":
    import sys
    # 提前安装markdown库:pip install markdown
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec())

代码说明

  • 使用单个QTextEdit同时承担编辑和渲染功能,无需分栏或组件切换
  • 监听光标位置和文本变更信号,实时更新显示内容
  • 将文本按行拆分,当前光标所在行保持纯文本格式,其他行通过markdown库渲染为HTML
  • 设置HTML内容时保存并恢复光标位置,避免输入过程中光标乱跑

内容的提问来源于stack exchange,提问作者DigestablePandas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:20:24