如何在QScintilla中实现SublimeText的按层级折叠功能
实现QScintilla类似Sublime Text 3的
fold_by_level功能 我帮你梳理下实现思路,结合你已经写的代码,我们可以通过QScintilla提供的折叠API来完成这个功能。核心是利用foldLevel()获取每行的折叠层级,再对符合目标层级的折叠块进行折叠/展开操作。
完整实现代码
首先补全你的Foo类,添加fold_by_level方法,并确保lexer启用了折叠功能:
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QAction from PyQt5.Qsci import QsciScintilla, QsciLexerCPP class Foo(QsciScintilla): def __init__(self, parent=None): super().__init__(parent) # 设置折叠样式为BoxedTree(你原来的代码写了BoxedTre,应该是这个完整名称) self.setFolding(QsciScintilla.BoxedTreeFoldStyle) # 初始化CPP语法分析器并启用折叠(必须开启,否则不会生成折叠层级) self.lexer = QsciLexerCPP(self) self.lexer.setFolding(True) self.setLexer(self.lexer) # 设置折叠边栏(可选,方便可视化折叠状态) self.setMarginWidth(2, 15) self.setMarginMarkerMask(2, QsciScintilla.FoldMarker) def fold_by_level(self, target_level): """ 折叠所有层级 >= target_level 的代码块,展开层级 < target_level 的块 完全模拟Sublime Text的fold_by_level功能 """ total_lines = self.lines() for line_num in range(total_lines): # 提取折叠层级数值(去掉标志位) fold_level = self.foldLevel(line_num) & QsciScintilla.FoldLevelNumberMask # 判断当前行是否是折叠块的起始行(只有这类行能执行折叠/展开操作) is_fold_header = (self.foldLevel(line_num) & QsciScintilla.FoldLevelHeaderFlag) != 0 if is_fold_header: if fold_level >= target_level: # 如果未折叠,执行折叠 if not self.isFolded(line_num): self.foldLine(line_num) else: # 如果已折叠,执行展开 if self.isFolded(line_num): self.unfoldLine(line_num) def unfold_all(self): """快速展开所有折叠块""" total_lines = self.lines() for line_num in range(total_lines): if self.isFolded(line_num): self.unfoldLine(line_num) # 测试代码 if __name__ == "__main__": app = QApplication(sys.argv) win = QMainWindow() editor = Foo(win) win.setCentralWidget(editor) # 测试用的C++代码 test_code = """ #include <iostream> namespace TestNamespace { void testFunction1() { if (true) { std::cout << "Inside if block" << std::endl; for (int i = 0; i < 5; i++) { std::cout << "Loop iteration: " << i << std::endl; } } } void testFunction2() { std::cout << "Simple function" << std::endl; } } int main() { TestNamespace::testFunction1(); TestNamespace::testFunction2(); return 0; } """ editor.setText(test_code) # 添加工具栏按钮测试折叠功能 toolbar = win.addToolBar("Fold Controls") fold_level2 = QAction("Fold Level 2", win) fold_level2.triggered.connect(lambda: editor.fold_by_level(2)) toolbar.addAction(fold_level2) fold_level3 = QAction("Fold Level 3", win) fold_level3.triggered.connect(lambda: editor.fold_by_level(3)) toolbar.addAction(fold_level3) unfold_all = QAction("Unfold All", win) unfold_all.triggered.connect(editor.unfold_all) toolbar.addAction(unfold_all) win.resize(800, 600) win.show() sys.exit(app.exec_())
关键逻辑解释
折叠层级的获取:
QScintilla的foldLevel()返回的是一个整数,其中高8位是实际的层级数值,低8位是折叠标志位。我们用FoldLevelNumberMask提取层级,用FoldLevelHeaderFlag判断该行是否是折叠块的起始行(只有这类行可以执行折叠/展开操作)。fold_by_level的行为:
遍历所有行,对每个折叠起始行:- 如果层级大于等于目标层级,且未折叠,则折叠该行
- 如果层级小于目标层级,且已折叠,则展开该行
这和Sublime Text的fold_by_level行为完全一致:折叠所有深度达到或超过目标层级的块,同时展开更外层的块。
Lexer的折叠启用:
必须调用lexer.setFolding(True),否则语法分析器不会生成折叠层级,foldLevel()会一直返回0,无法实现折叠。
测试效果
- 点击「Fold Level 2」:会折叠所有函数(层级2)及其内部的块(层级3),只保留namespace和main函数的展开状态
- 点击「Fold Level 3」:只会折叠函数内部的if、for等嵌套块(层级3),函数本身保持展开
- 点击「Unfold All」:一键展开所有折叠块
内容的提问来源于stack exchange,提问作者BPL
相关产品推荐
相关产品推荐

