如何修改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_FOLDLEVELHEADERFLAGtells 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
QTextBlockuser 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:
- Replace any code that manually tracks fold start/end lines with the
SCI_SETFOLDLEVELcalls we used above. You don't need to define block ranges explicitly—Scintilla handles that based on line fold levels. - Swap QPlainTextEdit's
QTextDocumentblock operations with QScintilla's line-based methods (e.g.,editor.lines()instead ofdocument().blockCount()). - Use your existing
indentation_levelmethod as-is—just make sure it's calculating indent correctly for QScintilla's line text (useeditor.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

