如何用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
相关产品推荐
相关产品推荐

