这里的最终目标是在 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 是如何工作的例子:
当然,在使用多选(在上面的 mcve 中已经启用)时,适当的识别器也应该可以工作,示例如下:
您可以在 Sublime 中看到每个文档的更改如何完美/有效地更新缩进折叠级别
我的盒子的设置:
附言。我在互联网上发现了一段非常有趣的代码,效果很好,https://github.com/pyQode/pyqode.core/blob/master/pyqode/core/api/folding.py问题是代码打算在 QPlainTextEdit 和 QSyntaxHighlighter 上工作,所以我不太清楚如何调整它以在 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/
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我怎样才能完成http://php.net/manual/en/function.call-user-func-array.php在ruby中?所以我可以这样做:classAppdeffoo(a,b)putsa+benddefbarargs=[1,2]App.send(:foo,args)#doesn'tworkApp.send(:foo,args[0],args[1])#doeswork,butdoesnotscaleendend 最佳答案 尝试分解数组App.send(:foo,*args)
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"
我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121