我一直在写故事的文本编辑器。我使用的是Python、GTK+3和GtkSourceView 3。编辑器的要点是折叠某些区域。虽然没有-还没有?-在GTKTextView/SourceView中内置了对折叠的支持,我一直在使用invision=True的标记和SourceView的源标记来实现该功能。
此处提供源代码:https://github.com/mkoskim/mawe
核心编辑器(SourceBuffer和SourceView)位于此处:https://github.com/mkoskim/mawe/blob/master/gui/gtk/SceneView.pyhttps://github.com/mkoskim/mawe/blob/master/gui/gtk/SceneBuffer.py
出于测试目的,您可以克隆repo,并使用运行应用程序
mawe$ ./mawe.py test/test.txt
现在,应用程序经常随机崩溃,出现以下错误:
(mawe.py:10556): Gtk-WARNING **: /build/gtk+3.0-2Ut_nl/gtk+3.0-3.18.9/./gtk/gtktextbtree.c:4034: byte index off the end of the line
(mawe.py:10556): Gtk-ERROR **: Byte index 1362 is off the end of the line
Trace/breakpoint trap
没有其他错误或警告日志。我一直在谷歌上搜索这个错误,但没有成功。
其他症状似乎是:
即使编辑器空闲,也会发生崩溃
今天我发现奇怪的是,我可以通过将鼠标移动到隐藏部分来很快崩溃:o
我不能100%确定,但我认为这与不可见区域有关。
问:有人知道这是不是某个已知的bug吗?
问:有人知道我可以在哪里寻找可能的解决方案吗?有什么想法可以导致崩溃,我可以更深入地探索什么?
更新:我对标签进行了更广泛的测试。似乎没有其他属性对鼠标移动做出反应,但当启用不可见性时,鼠标在整个区域中的移动会使应用程序崩溃。我一直在搜索鼠标事件崩溃gtktextbtree的报告,但到目前为止没有成功。看起来这适用于几个v3.x GTK版本。
UPDATE:我想我几乎找到了一个解决方法:从GtkSource中过滤掉运动通知事件。视图似乎可以工作,如下所示:
def filter_event(widget, event, *args):
# Allow these
if event.type == Gdk.EventType.KEY_PRESS: return False
if event.type == Gdk.EventType.KEY_RELEASE: return False
# Block these
if event.type == Gdk.EventType.LEAVE_NOTIFY: return True
if event.type == Gdk.EventType.MOTION_NOTIFY: return True
# Print & allow the rest
print(event)
return False
self.text.connect("event", filter_event)
如果您在隐藏线附近按下鼠标按钮,应用程序仍然会崩溃,但似乎它不再因鼠标移动而崩溃。
更新:更多调查。虽然阻止鼠标事件可以防止崩溃,但它也会导致怪癖,例如,无法使用鼠标放置光标、选择区域、DnD。。。此外,鼠标光标可能会消失,因为它不是每次都正确更新。我确信在将鼠标/窗口坐标转换为缓冲区位置的算法中存在漏洞(当文本中有较大的隐藏块时),因此任何鼠标事件都可能导致应用程序崩溃。
UPDATE:我一直在尝试为主题创建简单的测试用例。好的方面:隐藏似乎起了作用。糟糕的事情:还不能重现问题。测试脚本可以在这里找到:
https://github.com/mkoskim/mawe/blob/master/gui/gtk/test/hidecrash/hidecrash.py
UPDATE:试图弄清楚它-测试用例有效,编辑器不起作用。与测试用例的区别至少在于编辑器在事件循环(*)中放置了隐藏的标记。正在尝试对此进行测试。。。
(*)对于当前的Gtk SourceView/TextView,肯定有许多不同的解决方案可以实现折叠。我选择了使用"标记"语言并在编辑时应用折叠的方法,因为它可以使用undo/redo。我也尝试过其他解决方案,比如:
剪切折叠场景&在文本缓冲区中插入一个小部件,其中包含文本本身。想法:"text[选择用于折叠的部分]text"->"text[锚+带剪切文本的小部件]text"-遗憾的是,它不适用于撤消/重做。
剪切文本,给它一个ID,并在缓冲区中放置一个包含ID的带有特定标记的部分。Idea"text[选择用于折叠的部分]text"->"text[ID w/hidden+受保护的标签]text"-不起作用,因为剪切、粘贴或撤消/重做都不应用标签,因此用户可以销毁标记。
简单的标记:用折叠指示器来保持标记是非常困难的。你需要像"[char][mark][char]"这样带有受保护标签的东西,以确保你不会在某个地方丢失标记。
无论如何,我会继续调查。
更新:仍然无法在我的测试脚本中重现问题,但发现了一些可能有趣的东西:折叠最后一个场景不会导致崩溃——只有在折叠另一个场景之后的场景时(折叠或未折叠)。
我已经解决了这个问题。我只是不明白为什么它能工作,为什么我不能用测试脚本重现这个问题。以下是执行折叠的片段:
fold_start = at.copy()
fold_start.forward_to_line_end()
fold_start.forward_char() # Comment this line -> crash
fold_end = self.scene_end_iter(end)
self.apply_tag(self.tag_fold_hide, fold_start, fold_end)
at
在插入文本或删除范围回调时派生,并指向行的开头。fold_end
是指向下一个场景标记或文件结尾的TextIter。如果我们查看缓冲区内容,它是这样的:
<mark 1><at>Scene 1 heading<eol>
Line
Line
<mark 2>Scene 2 heading
将隐藏标记从<eol>
应用到<mark 2>
会导致崩溃。将标签从<eol + 1>
应用到<mark 2>
可以按预期工作。如果折叠到文件末尾(<mark 2> == buffer.get_end_iter()
),则折叠有效。在某些情况下,如果只隐藏换行符,它也会起作用,但并非在所有情况下都是这样。
如前所述,我不明白为什么这有效,为什么我不能用更简单的脚本重现这个问题,但我一直在调查更多,尽管现在有了解决方案,就没有那么紧迫了。
好的,所以昨天我这样做了,可见和不可见部分都用类似的字体描述格式化。我仍然无法用简单的测试脚本重现这个问题,但似乎如果可见和隐藏部分离它们太远,偏移量计算就会出错。到目前为止,我知道:
-
错误消息,如
Byte index 1362 is off the end of the line Trace/breakpoint trap
,与隐藏的部分的长度相差约5-7个字节。 -
当可见部分和不可见部分在字体大小、填充和重量方面"足够接近"时,不会出现错误。在我的工作示例中,我使用完全相同的字体、重量、大小等来格式化不可见的部分,并且程序不再崩溃。