You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为Gtk.TextView添加斜体标签时Python程序崩溃问题排查

问题:Gtk.TextView Markdown斜体格式化程序崩溃

我正在编写一个基于Gtk.TextView的Markdown格式化工具,目标是读取文本并为Markdown标记分配对应格式标签,但每当添加斜体标记* *对时,程序就会崩溃,且输入任何内容都会触发崩溃。以下是测试代码:

# Imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango

class MarkdownFormatter:
    def __init__(self, textview):
        self.textview = textview
        self.text_buffer = self.textview.get_buffer()
        self.text_buffer.connect('insert-text', self.on_key_insert)
        self.tag_bold = self.text_buffer.create_tag('bold',weight=Pango.Weight.BOLD)
        self.tag_italic = self.text_buffer.create_tag('italic',style=Pango.Style.ITALIC, )
        self.tag_underline = self.text_buffer.create_tag('underline',underline=Pango.Underline.SINGLE)
        self.bold= False
        self.italic = False
        self.italic_start_iter = Gtk.TextIter()
        self.italic_end_iter = Gtk.TextIter()
        self.underline = False
        
    def on_key_insert(self, buffer, inter, text, iteg):
        end= self.text_buffer.get_end_iter()
        start= self.text_buffer.get_start_iter()
        all_text = self.text_buffer.get_text(end, start, True)
        self.text_buffer.remove_all_tags(start, end)
        count = 0
        self.italic =  False
        for d in all_text:
            count+= 1
            if '*' in d:
                if self.italic == False:
                    self.italic = True
                    self.italic_start_iter.set_offset(count)
                elif self.italic:
                    self.italic = False
                    self.italic_end_iter.set_offset(count)
                    self.text_buffer.apply_tag(self.tag_italic, self.italic_start_iter, self.italic_end_iter)

text = '''
*I is good.*
she is smart.
"Hi" he said.

'''

if __name__ == '__main__':

    def quit(*args):
        Gtk.main_quit()

    window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
    window.set_title('Markdown formatter')
    view = Gtk.TextView()
    view.get_buffer().set_text(text)

    formater = MarkdownFormatter(view)

    window.set_default_size(600, 400)
    window.add(view)
    window.show_all()
    window.connect('delete-event', quit)
    Gtk.main()

问题分析与修复

核心错误点

  1. 文本获取顺序错误:get_text(start, end)的参数顺序是起始迭代器在前、结束迭代器在后,原代码写反后获取的是反向文本,后续处理全部失效。
  2. 迭代器使用不当:复用全局迭代器对象,文本内容变化后旧迭代器会失效,直接引发崩溃。
  3. Offset计数错误:TextIter的offset从0开始,但代码中count从1开始,导致标签应用位置偏移。
  4. 无限递归触发:在insert-text信号处理中修改缓冲区,会再次触发该信号,形成无限递归最终导致程序崩溃。

修复后的代码

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango

class MarkdownFormatter:
    def __init__(self, textview):
        self.textview = textview
        self.text_buffer = self.textview.get_buffer()
        self.text_buffer.connect('insert-text', self.on_key_insert)
        self.text_buffer.connect('delete-range', self.on_text_delete)
        self.tag_bold = self.text_buffer.create_tag('bold', weight=Pango.Weight.BOLD)
        self.tag_italic = self.text_buffer.create_tag('italic', style=Pango.Style.ITALIC)
        self.tag_underline = self.text_buffer.create_tag('underline', underline=Pango.Underline.SINGLE)

    def format_text(self):
        # 先断开信号避免递归触发
        self.text_buffer.handler_block_by_func(self.on_key_insert)
        self.text_buffer.handler_block_by_func(self.on_text_delete)

        start = self.text_buffer.get_start_iter()
        end = self.text_buffer.get_end_iter()
        all_text = self.text_buffer.get_text(start, end, True)
        self.text_buffer.remove_all_tags(start, end)

        # 收集所有*的位置,成对处理
        asterisk_positions = [i for i, char in enumerate(all_text) if char == '*']
        
        for i in range(0, len(asterisk_positions), 2):
            if i + 1 >= len(asterisk_positions):
                break  # 忽略未闭合的*
            start_idx = asterisk_positions[i]
            end_idx = asterisk_positions[i+1]

            # 获取当前有效的迭代器
            start_iter = self.text_buffer.get_iter_at_offset(start_idx)
            end_iter = self.text_buffer.get_iter_at_offset(end_idx)
            # 跳过*本身,只格式化中间内容
            start_iter.forward_char()
            self.text_buffer.apply_tag(self.tag_italic, start_iter, end_iter)

        # 重新连接信号
        self.text_buffer.handler_unblock_by_func(self.on_key_insert)
        self.text_buffer.handler_unblock_by_func(self.on_text_delete)

    def on_key_insert(self, buffer, iter, text, length):
        # 延迟格式化,避免插入过程中修改缓冲区
        Gtk.idle_add(self.format_text)

    def on_text_delete(self, buffer, start, end):
        # 删除文本时也触发格式化
        Gtk.idle_add(self.format_text)

text = '''
*I is good.*
she is smart.
"Hi" he said.

'''

if __name__ == '__main__':
    def quit(*args):
        Gtk.main_quit()

    window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
    window.set_title('Markdown formatter')
    view = Gtk.TextView()
    view.get_buffer().set_text(text)

    formatter = MarkdownFormatter(view)
    # 初始化格式化现有文本
    formatter.format_text()

    window.set_default_size(600, 400)
    window.add(view)
    window.show_all()
    window.connect('delete-event', quit)
    Gtk.main()

关键修复说明

  • 使用Gtk.idle_add()延迟格式化操作,避免在信号处理中直接修改缓冲区引发递归。
  • 处理文本时先断开信号,完成后重新连接,彻底杜绝无限递归。
  • 先收集所有*的位置,成对处理并忽略未闭合标记,避免无效操作。
  • 使用get_iter_at_offset()获取最新有效迭代器,不再复用旧迭代器。
  • 修正文本获取顺序,确保获取正向文本。

内容的提问来源于stack exchange,提问作者ShadowOfHassen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 03:15:05