如何在 QScintilla 中实现 SublimeText 的逐级折叠功能
How to implement SublimeText fold-by-level feature in QScintilla
我正在尝试在 QScintilla 组件上实现 fold_by_level
SublimeText3 功能,但我不太清楚如何去做,到目前为止我已经想出了这个代码:
import sys
import re
import math
from PyQt5.Qt import * # noqa
from PyQt5.Qsci import QsciScintilla
from PyQt5 import Qsci
from PyQt5.Qsci import QsciLexerCPP
class Foo(QsciScintilla):
def __init__(self, parent=None):
super().__init__(parent)
# http://www.scintilla.org/ScintillaDoc.html#Folding
self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
# Indentation
self.setIndentationsUseTabs(False)
self.setIndentationWidth(4)
self.setBackspaceUnindents(True)
self.setIndentationGuides(True)
# Set the default font
self.font = QFont()
self.font.setFamily('Consolas')
self.font.setFixedPitch(True)
self.font.setPointSize(10)
self.setFont(self.font)
self.setMarginsFont(self.font)
# Margin 0 is used for line numbers
fontmetrics = QFontMetrics(self.font)
self.setMarginsFont(self.font)
self.setMarginWidth(0, fontmetrics.width("000") + 6)
self.setMarginLineNumbers(0, True)
self.setMarginsBackgroundColor(QColor("#cccccc"))
# Indentation
self.setIndentationsUseTabs(False)
self.setIndentationWidth(4)
self.setBackspaceUnindents(True)
lexer = QsciLexerCPP()
lexer.setFoldAtElse(True)
lexer.setFoldComments(True)
lexer.setFoldCompact(False)
lexer.setFoldPreprocessor(True)
self.setLexer(lexer)
QShortcut(QKeySequence("Ctrl+K, Ctrl+J"), self,
lambda level=-1: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+1"), self,
lambda level=1: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+2"), self,
lambda level=2: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+3"), self,
lambda level=3: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+4"), self,
lambda level=4: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+5"), self,
lambda level=5: self.fold_by_level(level))
def fold_by_level(self, lvl):
if lvl < 0:
self.foldAll(True)
else:
for i in range(self.lines()):
level = self.SendScintilla(
QsciScintilla.SCI_GETFOLDLEVEL, i) & QsciScintilla.SC_FOLDLEVELNUMBERMASK
level -= 0x400
print(f"line={i+1}, level={level}")
if lvl == level:
self.foldLine(i)
def main():
app = QApplication(sys.argv)
ex = Foo()
ex.setText("""\
#include <iostream>
using namespace std;
void Function0() {
cout << "Function0";
}
void Function1() {
cout << "Function1";
}
void Function2() {
cout << "Function2";
}
void Function3() {
cout << "Function3";
}
int main(void) {
if (1) {
if (1) {
if (1) {
if (1) {
int yay;
}
}
}
}
if (1) {
if (1) {
if (1) {
if (1) {
int yay2;
}
}
}
}
return 0;
}\
""")
ex.resize(800, 600)
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
我关注的文档是 https://www.scintilla.org/ScintillaDoc.html#Folding and http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintilla.html。
正如我所说,fold_by_level
功能旨在表现得与 SublimeText 完全一样,但我不确定 ST 的功能实现细节。在任何情况下,让我 post 在 SublimeText 上测试一些基本序列后的一些屏幕截图可以澄清我在这里想要实现的目标:
序列 1: {ctrl+k, ctrl+5}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+4}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+3}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+2}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+1}, {ctrl+k, ctrl+j}
序列 2: {ctrl+k, ctrl+5}, {ctrl+k, ctrl+4}, {ctrl+k, ctrl+3}, {ctrl+k, ctrl+2}, {ctrl+k, ctrl+1}
我确信有更多关于 SublimeText 行为的内部细节,但如果我的示例在测试序列后在这些镜头上的表现与 posted 完全一样,您可以说该功能已经变得非常易于使用。
您的示例中的问题主要是由于 QsciScintilla API 中的命名不当造成的。 foldLine and foldAll 方法实际上应该称为 toggleFoldLine
和 toggleFoldAll
,因为它们实际上 撤消 以前的状态。这意味着,例如,如果两个连续的行具有相同的折叠级别,则调用 foldLine
两次将不会产生任何净变化。
在下面的实现中,我使用了更明确的 Scintilla 消息,这样只有真正需要折叠的行才会受到影响。我还更改了键盘快捷键以匹配 SublimeText 中的默认值:
class Foo(QsciScintilla):
def __init__(self, parent=None):
...
QShortcut(QKeySequence("Ctrl+K, Ctrl+J"), self, self.fold_by_level)
QShortcut(QKeySequence("Ctrl+K, Ctrl+0"), self, self.fold_by_level)
...
def fold_by_level(self, level=0):
SCI = self.SendScintilla
if level:
level += 0x400
MASK = QsciScintilla.SC_FOLDLEVELNUMBERMASK
for line in range(self.lines()):
foldlevel = SCI(QsciScintilla.SCI_GETFOLDLEVEL, line) & MASK
print('line=%i, level=%i' % (line + 1, foldlevel), end='')
if foldlevel == level:
line = SCI(QsciScintilla.SCI_GETFOLDPARENT, line)
if SCI(QsciScintilla.SCI_GETFOLDEXPANDED, line):
print(', foldline:', line + 1, end='')
SCI(QsciScintilla.SCI_FOLDLINE, line,
QsciScintilla.SC_FOLDACTION_CONTRACT)
print()
else:
SCI(QsciScintilla.SCI_FOLDALL, QsciScintilla.SC_FOLDACTION_EXPAND)
我正在尝试在 QScintilla 组件上实现 fold_by_level
SublimeText3 功能,但我不太清楚如何去做,到目前为止我已经想出了这个代码:
import sys
import re
import math
from PyQt5.Qt import * # noqa
from PyQt5.Qsci import QsciScintilla
from PyQt5 import Qsci
from PyQt5.Qsci import QsciLexerCPP
class Foo(QsciScintilla):
def __init__(self, parent=None):
super().__init__(parent)
# http://www.scintilla.org/ScintillaDoc.html#Folding
self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
# Indentation
self.setIndentationsUseTabs(False)
self.setIndentationWidth(4)
self.setBackspaceUnindents(True)
self.setIndentationGuides(True)
# Set the default font
self.font = QFont()
self.font.setFamily('Consolas')
self.font.setFixedPitch(True)
self.font.setPointSize(10)
self.setFont(self.font)
self.setMarginsFont(self.font)
# Margin 0 is used for line numbers
fontmetrics = QFontMetrics(self.font)
self.setMarginsFont(self.font)
self.setMarginWidth(0, fontmetrics.width("000") + 6)
self.setMarginLineNumbers(0, True)
self.setMarginsBackgroundColor(QColor("#cccccc"))
# Indentation
self.setIndentationsUseTabs(False)
self.setIndentationWidth(4)
self.setBackspaceUnindents(True)
lexer = QsciLexerCPP()
lexer.setFoldAtElse(True)
lexer.setFoldComments(True)
lexer.setFoldCompact(False)
lexer.setFoldPreprocessor(True)
self.setLexer(lexer)
QShortcut(QKeySequence("Ctrl+K, Ctrl+J"), self,
lambda level=-1: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+1"), self,
lambda level=1: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+2"), self,
lambda level=2: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+3"), self,
lambda level=3: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+4"), self,
lambda level=4: self.fold_by_level(level))
QShortcut(QKeySequence("Ctrl+K, Ctrl+5"), self,
lambda level=5: self.fold_by_level(level))
def fold_by_level(self, lvl):
if lvl < 0:
self.foldAll(True)
else:
for i in range(self.lines()):
level = self.SendScintilla(
QsciScintilla.SCI_GETFOLDLEVEL, i) & QsciScintilla.SC_FOLDLEVELNUMBERMASK
level -= 0x400
print(f"line={i+1}, level={level}")
if lvl == level:
self.foldLine(i)
def main():
app = QApplication(sys.argv)
ex = Foo()
ex.setText("""\
#include <iostream>
using namespace std;
void Function0() {
cout << "Function0";
}
void Function1() {
cout << "Function1";
}
void Function2() {
cout << "Function2";
}
void Function3() {
cout << "Function3";
}
int main(void) {
if (1) {
if (1) {
if (1) {
if (1) {
int yay;
}
}
}
}
if (1) {
if (1) {
if (1) {
if (1) {
int yay2;
}
}
}
}
return 0;
}\
""")
ex.resize(800, 600)
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
我关注的文档是 https://www.scintilla.org/ScintillaDoc.html#Folding and http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintilla.html。
正如我所说,fold_by_level
功能旨在表现得与 SublimeText 完全一样,但我不确定 ST 的功能实现细节。在任何情况下,让我 post 在 SublimeText 上测试一些基本序列后的一些屏幕截图可以澄清我在这里想要实现的目标:
序列 1: {ctrl+k, ctrl+5}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+4}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+3}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+2}, {ctrl+k, ctrl+j} {ctrl+k, ctrl+1}, {ctrl+k, ctrl+j}
序列 2: {ctrl+k, ctrl+5}, {ctrl+k, ctrl+4}, {ctrl+k, ctrl+3}, {ctrl+k, ctrl+2}, {ctrl+k, ctrl+1}
我确信有更多关于 SublimeText 行为的内部细节,但如果我的示例在测试序列后在这些镜头上的表现与 posted 完全一样,您可以说该功能已经变得非常易于使用。
您的示例中的问题主要是由于 QsciScintilla API 中的命名不当造成的。 foldLine and foldAll 方法实际上应该称为 toggleFoldLine
和 toggleFoldAll
,因为它们实际上 撤消 以前的状态。这意味着,例如,如果两个连续的行具有相同的折叠级别,则调用 foldLine
两次将不会产生任何净变化。
在下面的实现中,我使用了更明确的 Scintilla 消息,这样只有真正需要折叠的行才会受到影响。我还更改了键盘快捷键以匹配 SublimeText 中的默认值:
class Foo(QsciScintilla):
def __init__(self, parent=None):
...
QShortcut(QKeySequence("Ctrl+K, Ctrl+J"), self, self.fold_by_level)
QShortcut(QKeySequence("Ctrl+K, Ctrl+0"), self, self.fold_by_level)
...
def fold_by_level(self, level=0):
SCI = self.SendScintilla
if level:
level += 0x400
MASK = QsciScintilla.SC_FOLDLEVELNUMBERMASK
for line in range(self.lines()):
foldlevel = SCI(QsciScintilla.SCI_GETFOLDLEVEL, line) & MASK
print('line=%i, level=%i' % (line + 1, foldlevel), end='')
if foldlevel == level:
line = SCI(QsciScintilla.SCI_GETFOLDPARENT, line)
if SCI(QsciScintilla.SCI_GETFOLDEXPANDED, line):
print(', foldline:', line + 1, end='')
SCI(QsciScintilla.SCI_FOLDLINE, line,
QsciScintilla.SC_FOLDACTION_CONTRACT)
print()
else:
SCI(QsciScintilla.SCI_FOLDALL, QsciScintilla.SC_FOLDACTION_EXPAND)