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

如何在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_())

关键逻辑解释

  1. 折叠层级的获取:
    QScintilla的foldLevel()返回的是一个整数,其中高8位是实际的层级数值,低8位是折叠标志位。我们用FoldLevelNumberMask提取层级,用FoldLevelHeaderFlag判断该行是否是折叠块的起始行(只有这类行可以执行折叠/展开操作)。

  2. fold_by_level的行为:
    遍历所有行,对每个折叠起始行:

    • 如果层级大于等于目标层级,且未折叠,则折叠该行
    • 如果层级小于目标层级,且已折叠,则展开该行
      这和Sublime Text的fold_by_level行为完全一致:折叠所有深度达到或超过目标层级的块,同时展开更外层的块。
  3. Lexer的折叠启用:
    必须调用lexer.setFolding(True),否则语法分析器不会生成折叠层级,foldLevel()会一直返回0,无法实现折叠。

测试效果

  • 点击「Fold Level 2」:会折叠所有函数(层级2)及其内部的块(层级3),只保留namespace和main函数的展开状态
  • 点击「Fold Level 3」:只会折叠函数内部的if、for等嵌套块(层级3),函数本身保持展开
  • 点击「Unfold All」:一键展开所有折叠块

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:13:09