Search code examples
pythontkintertkinter-text

How can I modify my tkinter code to highlight at different moments multiple occurrences of a word in a Text widget?


I've got the following code to highlight word-matches within a tk.Text.

However, this code will highlight all matches it finds all at once (e.g. if the sentence is "my cat is my friend", it would highlight all occurrences of "my" at the first time I type that word).

How can I modify this code in order to make it highlight only the first occurrence of a word at the first time I pass it through the function, and then when I pass that word through the function a second time, it highlights the next occurrence of that word (so, in my example, it would first highlight "my cat is my friend", and then, on a second run, "my cat is my friend".

Thanks in advance!

class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)

    def highlight_pattern(self, pattern, tag, start="1.0", end="end",
                          regexp=False):
        start = self.index(start)
        end = self.index(end)
        self.mark_set("matchStart", start)
        self.mark_set("matchEnd", start)
        self.mark_set("searchLimit", end)

        count = tk.IntVar()
        while True:
            index = self.search(pattern, "matchEnd","searchLimit",
                                count=count, regexp=regexp)
            if index == "": break
            if count.get() == 0: break # degenerate pattern which matches zero-length strings
            self.mark_set("matchStart", index)
            self.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
            self.tag_add(tag, "matchStart", "matchEnd")

Solution

  • Since you only need to find one match at a time you can greatly simplify your search function by removing the while loop so that it always searches exactly once.

    The search function sets two special marks (indexes) named matchStart and matchEnd. If you start all but the first search at matchEnd it will find the next occurrence after the previous search. For the very first search you can use the index "1.0".

    class CustomText(tk.Text):
        def __init__(self, *args, **kwargs):
            tk.Text.__init__(self, *args, **kwargs)
    
        def highlight_pattern(self, pattern, tag, start="1.0", end="end", regexp=False):
            count = tk.IntVar()
            index = self.search(pattern, start, end, count=count, regexp=regexp)
            if index != "":
                self.mark_set("matchStart", index)
                self.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
                self.tag_add(tag, "matchStart", "matchEnd")
    

    You can then call this function from another function triggered by a button or binding. That function can check to see if matchEnd exists, and use the appropriate starting point for the search.

    text = CustomText(root)
    ...
    def search(event=None):
        start = "1.0" if "matchEnd" not in text.mark_names() else "matchEnd"
        pattern = search_entry.get()
        text.highlight_pattern(pattern, "search", start)