多行插入选择 tkinter 文本小部件

Multiline insert selection tkinter Text widget

我想向我的文本小部件添加一项功能,以便我可以 select 多行并将其注释掉。我目前使用它来处理单行,但无法检测到跨越多个 characters/lines 的插入。我如何获得此类信息。我知道可以检测到它,因为您可以使用删除按钮一次删除多个 characters/lines。我想通过在每行的开头添加一个主题标签来注释掉这些块,它还应该能够删除多行注释。我如何获得插入信息来做这样的事情?

当前评论码:

#In __init__:
        self.text.bind("<Command-slash>", self.make_comment)
#Later...
    def make_comment(self, event):
        self_anchor = (str(self.text.index("insert")).split("."))[0]
        if self.text.get(self_anchor + ".0", self_anchor + ".1") != "#":
            self.text.insert(float("{}.0".format(self_anchor)), "#")
        else:
            self.text.delete(self_anchor + ".0", self_anchor + ".1")

Seperate link

的帮助下,我已经解决了这个问题。我将解释代码及其工作原理,但如果您只想要工作代码,它将位于此答案的底部。


为了comment/uncomment用户选择的所有行,我们需要知道每一行的编号。这可以通过使用 Python 的 range() 函数和用户选择的开始和结束行号来完成。

要获取用户选择的起始行,我们使用此代码:

first_line = self.text.index("sel.first").split(".")[0]

sel.first只是表示“用户选择的起始索引”。同样,如果我们想获得用户选择的 end 行,我们做同样的事情但使用 sel.last:

last_line = self.text.index("sel.last").split(".")[0]

然后,我们使用 range() 函数遍历这两行以及它们之间的每一行:

for line in range(first_line, last_line + 1):
    # Comment or uncomment each line
    ...

请注意,我们使用 last_line + 1 来确保包含最后一行,因为 Python 的 range() 函数在 之前停止 它到达第二个数字。

现在,这段代码的唯一问题是,如果用户没有选择某些东西,你会得到一个_tkinter.TclErrortext doesn't contain any characters tagged with "sel"。所以为了能够comment/uncomment一行,需要插入一个try/except块:

try:
    first_line = self.text.index("sel.first").split(".")[0]
    last_line = self.text.index("sel.last").split(".")[0]
    for line in range(first_line, last_line + 1):
        # Comment or uncomment each line
        ...
except tkinter.TclError:
    # Comment or uncomment a single line
    ...

其余代码看起来与您已有的非常相似;您检查该行的第一个字符是否为 #,如果是,则取消注释该行,如果不是,则注释该行。

还有两件事:

  1. self.make_comment()的末尾,您可能需要添加return "break";至少在我的系统上,Ctrl+/ 命令也会选择所有文本。在函数末尾返回 "break" 会阻止它这样做。
  2. 当循环注释多行时,您需要确保当前行号不大于文本中的换行数。例如:
for line in range(first_line, last_line+1):
    if line <= int(self.text.get("1.0", "end").count("\n")):
        # Comment or uncomment the lines
        ...

综上所述,这是一个完整的可重现示例。您可以一次 comment/uncomment 多行,或仅一行,具体取决于选择的方式:

import tkinter

class Window(tkinter.Tk):
    """The main window."""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Our text widget
        self.text = tkinter.Text(self)
        with open(__file__) as f:
            self.text.insert("1.0", f.read())
        self.text.pack(expand=True, fill="both")
        
        # Bind the text widget
        self.text.bind("<Control-slash>", self.make_comment)
        self.text.bind("<Control-a>", self.select_all)

    def select_all(self, *args):
        self.text.tag_add("sel", "1.0", "end")
        return "break"
        
    def make_comment(self, event):
        
        # If this fails with an error, we know that the user only selected one line
        try:
            
            # Get the line number of the start and the end of the selection
            first_line = int(self.text.index("sel.first").split(".")[0])
            last_line = int(self.text.index("sel.last").split(".")[0])
            
            # Loop through all the selected lines and comment or uncomment them
            for line in range(first_line, last_line+1):
                # This is to make sure that a # isn't added to something that isn't a line
                if line <= int(self.text.get("1.0", "end").count("\n")):
                    if self.text.get("{}.0".format(line), "{}.1".format(line)) != "#":
                        self.text.insert("{}.0".format(line), "#")
                    else:
                        self.text.delete("{}.0".format(line), "{}.1".format(line))
        except tkinter.TclError:
            
            # Get the line number of the current cursor position
            insert = self.text.index("insert").split(".")[0]
            
            # Comment or uncomment the current line
            if self.text.get("{}.0".format(insert), "{}.1".format(insert)) != "#":
                self.text.insert("{}.0".format(insert), "#")
            else:
                self.text.delete("{}.0".format(insert), "{}.1".format(insert))
        return "break"

if __name__ == "__main__":
    Window().mainloop()