Reputation: 233
I want to know how to change the style of certain words and expressions based on certain patterns.
I am using the Tkinter.Text
widget and I am not sure how to do such a thing (the same idea of syntax highlighting in text editors). I am not sure even if this is the right widget to use for this purpose.
Upvotes: 19
Views: 41226
Reputation: 1207
Bryan Oakley's answer has helped me a lot on configuring highlights on many text widgets. Thanks to them, I'm able to understand how the highlighting works now.
Only drawback I found was the difference between the regex syntax used by tcl/tk and the python regex syntax. The tcl/tk regex syntax is close to the normal python regular expression syntax, but it's not the same. Due to this issue, many of the regex testing applications available were not usable for writing regex for tkinter search method.
NOTE: This won't work as expected if the text widget has embedded images or widgets since the indexes in the widget won't be the same as the indexes in just the text portion.
I tried to incorporate python's regular expression standard library with the tkinter Text widget.
import re
import tkinter as tk
...
def search_re(self, pattern):
"""
Uses the python re library to match patterns.
pattern - the pattern to match.
"""
matches = []
text = textwidget.get("1.0", tk.END).splitlines()
for i, line in enumerate(text):
for match in re.finditer(pattern, line):
matches.append((f"{i + 1}.{match.start()}", f"{i + 1}.{match.end()}"))
return matches
the return value is a list of tuples containing the start and end indices of the matches. Example:
[('1.1', '1.5'), ('1.6', '1.10'), ('3.1', '3.5')]
Now these values can be used to highlight the pattern in the text widget.
This is a wrapper for tkinter's Text widget with additional methods for highlighting and searching with regular expression library. It's based on bryan's code, thanks to them.
import re
import tkinter as tk
class CustomText(tk.Text):
"""
Wrapper for the tkinter.Text widget with additional methods for
highlighting and matching regular expressions.
highlight_all(pattern, tag) - Highlights all matches of the pattern.
highlight_pattern(pattern, tag) - Cleans all highlights and highlights all matches of the pattern.
clean_highlights(tag) - Removes all highlights of the given tag.
search_re(pattern) - Uses the python re library to match patterns.
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
# sample tag
self.tag_config("match", foreground="red")
def highlight(self, tag, start, end):
self.tag_add(tag, start, end)
def highlight_all(self, pattern, tag):
for match in self.search_re(pattern):
self.highlight(tag, match[0], match[1])
def clean_highlights(self, tag):
self.tag_remove(tag, "1.0", tk.END)
def search_re(self, pattern):
"""
Uses the python re library to match patterns.
Arguments:
pattern - The pattern to match.
Return value:
A list of tuples containing the start and end indices of the matches.
e.g. [("0.4", "5.9"]
"""
matches = []
text = self.get("1.0", tk.END).splitlines()
for i, line in enumerate(text):
for match in re.finditer(pattern, line):
matches.append((f"{i + 1}.{match.start()}", f"{i + 1}.{match.end()}"))
return matches
def highlight_pattern(self, pattern, tag="match"):
"""
Cleans all highlights and highlights all matches of the pattern.
Arguments:
pattern - The pattern to match.
tag - The tag to use for the highlights.
"""
self.clean_highlights(tag)
self.highlight_all(pattern, tag)
Following code uses the above class, and shows an example of how to use it:
import tkinter as tk
root = tk.Tk()
# Example usage
def highlight_text(args):
text.highlight_pattern(r"\bhello\b")
text.highlight_pattern(r"\bworld\b", "match2")
text = CustomText(root)
text.pack()
text.tag_config("match2", foreground="green")
# This is not the best way, but it works.
# instead, see: https://stackoverflow.com/a/40618152/14507110
text.bind("<KeyRelease>", highlight_text)
root.mainloop()
Upvotes: 1
Reputation: 385900
It's the right widget to use for these purposes. The basic concept is, you assign properties to tags, and you apply tags to ranges of text in the widget. You can use the text widget's search
command to find strings that match your pattern, which will return you enough information apply a tag to the range that matched.
For an example of how to apply tags to text, see my answer to the question Advanced Tkinter text box?. It's not exactly what you want to do but it shows the basic concept.
Following is an example of how you can extend the Text class to include a method for highlighting text that matches a pattern.
In this code the pattern must be a string, it cannot be a compiled regular expression. Also, the pattern must adhere to Tcl's syntax rules for regular expressions.
class CustomText(tk.Text):
'''A text widget with a new method, highlight_pattern()
example:
text = CustomText()
text.tag_configure("red", foreground="#ff0000")
text.highlight_pattern("this should be red", "red")
The highlight_pattern method is a simplified python
version of the tcl code at http://wiki.tcl.tk/3246
'''
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
def highlight_pattern(self, pattern, tag, start="1.0", end="end",
regexp=False):
'''Apply the given tag to all text that matches the given pattern
If 'regexp' is set to True, pattern will be treated as a regular
expression according to Tcl's regular expression syntax.
'''
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")
Upvotes: 51