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

如何修改QScintilla代码实现类SublimeText3的缩进式代码折叠?

Hey there! Let's break down how to implement Sublime Text 3-style indent-based folding in QScintilla, plus how to adapt your existing QPlainTextEdit folding logic to fit. I'll walk you through this step by step using your existing setup (Python 3.6.4, PyQt5 5.12, QScintilla 2.10.8).


1. First: Configure QScintilla's Core Folding Setup

Before adding custom logic, make sure QScintilla has folding enabled and styled to match Sublime's look. Add this to your editor initialization:

from PyQt5 import Qsci

# Initialize your QScintilla editor
self.editor = Qsci.QsciScintilla()

# Enable folding (1 = on, 0 = off)
self.editor.SendScintilla(Qsci.QsciScintilla.SCI_SETFOLDING, 1)
# Set fold markers to look like Sublime's arrows
self.editor.SendScintilla(
    Qsci.QsciScintilla.SCI_SETFOLDFLAGS,
    Qsci.QsciScintilla.SC_FOLDFLAG_LINEBEFORE_CONTRACTED | 
    Qsci.QsciScintilla.SC_FOLDFLAG_LINEAFTER_CONTRACTED
)
# Optional: Add indent guides to match Sublime's visual style
self.editor.SendScintilla(Qsci.QsciScintilla.SCI_SETINDENTATIONGUIDES, 1)

2. Implement Sublime-Style Indent Folding Logic

Sublime's folding works by tracking indent levels: a new fold starts when a line's indent is deeper than the previous line, and ends when indent drops back to the parent level. We'll use your existing indentation_level method to build this logic.

Add this method to your code (either in your View wrapper class or directly with the editor):

def update_indent_folding(self):
    editor = self.editor
    total_lines = editor.lines()
    if total_lines == 0:
        return

    # Clear existing fold levels first
    editor.SendScintilla(Qsci.QsciScintilla.SCI_SETFOLDLEVEL, 0, 0)

    # Track indent hierarchy with a stack
    indent_stack = []
    first_line_indent = self.indentation_level(0)
    indent_stack.append(first_line_indent)

    for line_num in range(1, total_lines):
        current_indent = self.indentation_level(line_num)
        prev_indent = self.indentation_level(line_num - 1)

        # Case 1: Current line is more indented than previous → start a new fold
        if current_indent > prev_indent:
            indent_stack.append(current_indent)
            # Mark this line as a fold header (clickable to collapse/expand)
            fold_level = len(indent_stack) | Qsci.QsciScintilla.SC_FOLDLEVELHEADERFLAG
            editor.SendScintilla(Qsci.QsciScintilla.SCI_SETFOLDLEVEL, line_num, fold_level)
        
        # Case 2: Same indent as previous → part of the same fold block
        elif current_indent == prev_indent:
            fold_level = len(indent_stack)
            editor.SendScintilla(Qsci.QsciScintilla.SCI_SETFOLDLEVEL, line_num, fold_level)
        
        # Case 3: Less indented than previous → pop the stack to match current level
        else:
            # Remove indent levels from the stack that are deeper than current
            while indent_stack and indent_stack[-1] > current_indent:
                indent_stack.pop()
            # Set fold level to match the current stack depth
            if indent_stack and indent_stack[-1] == current_indent:
                fold_level = len(indent_stack)
            else:
                indent_stack.append(current_indent)
                fold_level = len(indent_stack)
            editor.SendScintilla(Qsci.QsciScintilla.SCI_SETFOLDLEVEL, line_num, fold_level)

    # Optional: Auto-collapse all folds on load
    # editor.SendScintilla(Qsci.QsciScintilla.SCI_FOLDALL, 1)

Key Notes Here:

  • SC_FOLDLEVELHEADERFLAG tells QScintilla this line is a foldable header (clickable to toggle the fold)
  • The indent stack tracks the current hierarchy of indents, so we can correctly map lines to their parent fold blocks

3. Trigger Folding Updates Automatically

Make sure the folding logic runs whenever the text or indent changes. Bind these signals to your update method:

# Update folding when text is modified
self.editor.textChanged.connect(self.update_indent_folding)
# Update folding when indent is adjusted (Tab/Shift+Tab)
self.editor.modificationChanged.connect(self.update_indent_folding)
# Optional: Update when the editor gains focus (in case external changes happened)
self.editor.focusInEvent = lambda e: (super(type(self.editor), self.editor).focusInEvent(e), self.update_indent_folding())

4. Adapting Your QPlainTextEdit Folding Code to QScintilla

The core difference between QPlainTextEdit and QScintilla folding is how they track fold blocks:

  • QPlainTextEdit uses QTextBlock user data to manually mark fold start/end positions
  • QScintilla uses fold levels per line, handled by the underlying Scintilla engine

To adapt your existing QPlainTextEdit code:

  1. Replace any code that manually tracks fold start/end lines with the SCI_SETFOLDLEVEL calls we used above. You don't need to define block ranges explicitly—Scintilla handles that based on line fold levels.
  2. Swap QPlainTextEdit's QTextDocument block operations with QScintilla's line-based methods (e.g., editor.lines() instead of document().blockCount()).
  3. Use your existing indentation_level method as-is—just make sure it's calculating indent correctly for QScintilla's line text (use editor.text(line_num) to get a line's content).

5. Add Multi-Select Folding Support (Optional)

Since you have multi-select enabled, add a shortcut to fold all selected indent blocks:

from PyQt5.QtWidgets import QShortcut
from PyQt5.QtGui import QKeySequence

def fold_selected_indent_blocks(self):
    editor = self.editor
    # Get all active selections (start/end line pairs)
    selections = [
        (editor.selectionStartLine(), editor.selectionEndLine())
        for _ in range(editor.selections())
    ]
    for start_line, end_line in selections:
        base_indent = self.indentation_level(start_line)
        # Collapse all lines in the selection with deeper indent
        for line_num in range(start_line, end_line + 1):
            if self.indentation_level(line_num) > base_indent:
                editor.SendScintilla(Qsci.QsciScintilla.SCI_FOLD, line_num, 1)

# Bind to Sublime's default fold shortcut (Ctrl+K, Ctrl+F)
fold_shortcut = QShortcut(QKeySequence("Ctrl+K,Ctrl+F"), self.editor)
fold_shortcut.activated.connect(self.fold_selected_indent_blocks)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:54:02