gpt4 book ai didi

python - 如何在 QScintilla 中实现基于缩进的代码折叠?

转载 作者:IT老高 更新时间:2023-10-28 21:04:34 25 4
gpt4 key购买 nike

这里的最终目标是在 QScintilla 中实现基于缩进的代码折叠,类似于 SublimeText3 的方式。

首先,这里有一个小例子,说明如何使用 QScintilla 机制手动提供折叠:

import sys

from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import *

if __name__ == '__main__':
app = QApplication(sys.argv)
view = QsciScintilla()

# http://www.scintilla.org/ScintillaDoc.html#Folding
view.setFolding(QsciScintilla.BoxedTreeFoldStyle)

lines = [
(0, "def foo():"),
(1, " x = 10"),
(1, " y = 20"),
(1, " return x+y"),
(-1, ""),
(0, "def bar(x):"),
(1, " if x > 0:"),
(2, " print('this is')"),
(2, " print('branch1')"),
(1, " else:"),
(2, " print('and this')"),
(2, " print('is branch2')"),
(-1, ""),
(-1, ""),
(-1, ""),
(-1, "print('end')"),

]

view.setText("\n".join([b for a, b in lines]))
MASK = QsciScintilla.SC_FOLDLEVELNUMBERMASK

for i, tpl in enumerate(lines):
level, line = tpl
if level >= 0:
view.SendScintilla(view.SCI_SETFOLDLEVEL, i, level | QsciScintilla.SC_FOLDLEVELHEADERFLAG)
else:
view.SendScintilla(view.SCI_SETFOLDLEVEL, i, 0)

view.show()
app.exec_()

要深入了解,可以查看官方文档:

文档引用:

正如我所说,我想像 Sublime 那样实现代码折叠,所以我创建了这个小 mcve 作为基础代码来玩弄:

import re
import time
from pathlib import Path

from PyQt5.Qsci import QsciLexerCustom, QsciScintilla
from PyQt5.Qt import *


def lskip_nonewlines(text, pt):
len_text = len(text)

while True:
if pt <= 0 or pt >= len_text:
break
if text[pt - 1] == "\n" or text[pt] == "\n":
break
pt -= 1

return pt


def rskip_nonewlines(text, pt):
len_text = len(text)

while True:
if pt <= 0 or pt >= len_text:
break
if text[pt] == "\n":
break
pt += 1

return pt


class Region():
__slots__ = ['a', 'b']

def __init__(self, x, b=None):
if b is None:
if isinstance(x, int):
self.a = x
self.b = x
elif isinstance(x, tuple):
self.a = x[0]
self.b = x[1]
elif isinstance(x, Region):
self.a = x.a
self.b = x.b
else:
raise TypeError(f"Can't convert {x.__class__} to Region")
else:
self.a = x
self.b = b

def __str__(self):
return "(" + str(self.a) + ", " + str(self.b) + ")"

def __repr__(self):
return "(" + str(self.a) + ", " + str(self.b) + ")"

def __len__(self):
return self.size()

def __eq__(self, rhs):
return isinstance(rhs, Region) and self.a == rhs.a and self.b == rhs.b

def __lt__(self, rhs):
lhs_begin = self.begin()
rhs_begin = rhs.begin()

if lhs_begin == rhs_begin:
return self.end() < rhs.end()
else:
return lhs_begin < rhs_begin

def __sub__(self, rhs):
if self.end() < rhs.begin():
return [self]
elif self.begin() > rhs.end():
return [self]
elif rhs.contains(self):
return []
elif self.contains(rhs):
return [Region(self.begin(), rhs.begin()), Region(rhs.end(), self.end())]
elif rhs.begin() <= self.begin():
return [Region(rhs.end(), self.end())]
elif rhs.begin() > self.begin():
return [Region(self.begin(), rhs.begin())]
else:
raise Exception("Unknown case")

def empty(self):
return self.a == self.b

def begin(self):
if self.a < self.b:
return self.a
else:
return self.b

def end(self):
if self.a < self.b:
return self.b
else:
return self.a

def size(self):
return abs(self.a - self.b)

def contains(self, x):
if isinstance(x, Region):
return self.contains(x.a) and self.contains(x.b)
else:
return x >= self.begin() and x <= self.end()

def cover(self, rhs):
a = min(self.begin(), rhs.begin())
b = max(self.end(), rhs.end())

if self.a < self.b:
return Region(a, b)
else:
return Region(b, a)

def intersection(self, rhs):
if self.end() <= rhs.begin():
return Region(0)
if self.begin() >= rhs.end():
return Region(0)

return Region(max(self.begin(), rhs.begin()), min(self.end(), rhs.end()))

def intersects(self, rhs):
lb = self.begin()
le = self.end()
rb = rhs.begin()
re = rhs.end()

return (
(lb == rb and le == re) or
(rb > lb and rb < le) or (re > lb and re < le) or
(lb > rb and lb < re) or (le > rb and le < re)
)


class View(QsciScintilla):

# -------- MAGIC FUNCTIONS --------
def __init__(self, parent=None):
super().__init__(parent)
self.tab_size = 4

# Set multiselection defaults
self.SendScintilla(QsciScintilla.SCI_SETMULTIPLESELECTION, True)
self.SendScintilla(QsciScintilla.SCI_SETMULTIPASTE, 1)
self.SendScintilla(QsciScintilla.SCI_SETADDITIONALSELECTIONTYPING, True)

def __call__(self, prop, *args, **kwargs):
args = [v.encode("utf-8") if isinstance(v, str) else v for v in args]
kwargs = {
k: (v.encode("utf-8") if isinstance(v, str) else v)
for k, v in kwargs.items()
}
return self.SendScintilla(getattr(self, prop), *args, **kwargs)

# -------- SublimeText API --------
def size(self):
return len(self.text())

def substr(self, x):
# x = point or region
if isinstance(x, Region):
return self.text()[x.begin():x.end()]
else:
s = self.text()[x:x + 1]
if len(s) == 0:
return "\x00"
else:
return s

def line(self, x):
region = Region(x)

text = self.text()

if region.a <= region.b:
region.a = lskip_nonewlines(text, region.a)
region.b = rskip_nonewlines(text, region.b)
else:
region.a = rskip_nonewlines(text, region.a)
region.b = lskip_nonewlines(text, region.b)

return Region(region.begin(), region.end())

def full_line(self, x):
region = Region(x)

text = self.text()

if region.a <= region.b:
region.a = lskip_nonewlines(text, region.a)
region.b = rskip_nonewlines(text, region.b)
region.b = region.b + 1 if region.b < len(text) else region.b
else:
region.a = rskip_nonewlines(text, region.a)
region.b = lskip_nonewlines(text, region.b)
region.a = region.a + 1 if region.a < len(text) else region.a

return Region(region.begin(), region.end())

def indentation_level(self, pt):
view = self
r = view.full_line(pt)
line = view.substr(r)

if line == "\n":
r = view.full_line(pt - 1)
line = view.substr(r)

num_line, index = view.lineIndexFromPosition(pt)

if r.a <= 0 or r.a > view.size():
return 0
else:
i = 0
count = 0
len_line = len(line)
level = 0

while True:
if i >= len_line:
break
if line[i] == " ":
i += 1
count += 1
if count == self.tab_size:
level += 1
count = 0
elif line[i] == "\t":
level += 1
else:
break

if count != 0:
level += 1
return level


if __name__ == '__main__':
import sys
import textwrap

app = QApplication(sys.argv)
view = View()
view.setText(textwrap.dedent("""\
x - 0
x - 3
x - 3
x - 4
x - 3


x - 1
x - 2
x - 2
x - 2
x - 3
x - 3
x - 4
x - 3
x - 1
x - 4



x - 0
a
b
c
d
e
f
"""))

view.show()
app.exec_()

在上面的代码片段中,您可以看到我尝试复制一些 Sublime 函数。如果我的测试没有错,indentation_level 应该提供与 Sublime View 提供的输出相同的输出。 .

问题:您将如何修改上面的代码片段以提供像 Sublime 那样基于缩进的代码折叠?

这里你可以看到一个 Sublime 是如何工作的例子:

enter image description here

当然,在使用多选(在上面的 mcve 中已经启用)时,适当的识别器也应该可以工作,示例如下:

enter image description here

您可以在 Sublime 中看到每个文档的更改如何完美/有效地更新缩进折叠级别

我的盒子的设置:

  • win7
  • Python 3.6.4 (x86)
  • PyQt5==5.12
  • QScintilla==2.10.8

附言。我在互联网上发现了一段非常有趣的代码,效果很好,https://github.com/pyQode/pyqode.core/blob/master/pyqode/core/api/folding.py问题是代码打算在 QPlainTextEditQSyntaxHighlighter 上工作,所以我不太清楚如何调整它以在 QScinScintilla 中工作> 小部件

最佳答案

[删除了上一个答案,因为根据最后一个问题编辑,它可能具有的唯一值可能是历史;如果您仍然好奇,请参阅编辑历史记录]

最后是优化版——捆绑了 80 千行的示例文本以展示其性能。

from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import *


def set_fold(prev, line, fold, full):
if (prev[0] >= 0):
fmax = max(fold, prev[1])
for iter in range(prev[0], line + 1):
view.SendScintilla(view.SCI_SETFOLDLEVEL, iter,
fmax | (0, view.SC_FOLDLEVELHEADERFLAG)[iter + 1 < full])

def line_empty(line):
return view.SendScintilla(view.SCI_GETLINEENDPOSITION, line) \
<= view.SendScintilla(view.SCI_GETLINEINDENTPOSITION, line)

def modify(position, modificationType, text, length, linesAdded,
line, foldLevelNow, foldLevelPrev, token, annotationLinesAdded):
full = view.SC_MOD_INSERTTEXT | view.SC_MOD_DELETETEXT
if (~modificationType & full == full):
return
prev = [-1, 0]
full = view.SendScintilla(view.SCI_GETLINECOUNT)
lbgn = view.SendScintilla(view.SCI_LINEFROMPOSITION, position)
lend = view.SendScintilla(view.SCI_LINEFROMPOSITION, position + length)
for iter in range(max(lbgn - 1, 0), -1, -1):
if ((iter == 0) or not line_empty(iter)):
lbgn = iter
break
for iter in range(min(lend + 1, full), full + 1):
if ((iter == full) or not line_empty(iter)):
lend = min(iter + 1, full)
break
for iter in range(lbgn, lend):
if (line_empty(iter)):
if (prev[0] == -1):
prev[0] = iter
else:
fold = view.SendScintilla(view.SCI_GETLINEINDENTATION, iter)
fold //= view.SendScintilla(view.SCI_GETTABWIDTH)
set_fold(prev, iter - 1, fold, full)
set_fold([iter, fold], iter, fold, full)
prev = [-1, fold]
set_fold(prev, lend - 1, 0, full)


if __name__ == '__main__':
import sys
import textwrap

app = QApplication(sys.argv)
view = QsciScintilla()
view.SendScintilla(view.SCI_SETMULTIPLESELECTION, True)
view.SendScintilla(view.SCI_SETMULTIPASTE, 1)
view.SendScintilla(view.SCI_SETADDITIONALSELECTIONTYPING, True)
view.SendScintilla(view.SCI_SETINDENTATIONGUIDES, view.SC_IV_REAL);
view.SendScintilla(view.SCI_SETTABWIDTH, 4)
view.setFolding(view.BoxedFoldStyle)
view.SCN_MODIFIED.connect(modify)

NUM_CHUNKS = 20000
chunk = textwrap.dedent("""\
x = 1
x = 2
x = 3
""")
view.setText("\n".join([chunk for i in range(NUM_CHUNKS)]))
view.show()
app.exec_()

关于python - 如何在 QScintilla 中实现基于缩进的代码折叠?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55426342/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com