I'm working on a Tkinter application where I display text in a Text widget. The incoming text sometimes contains markdown-style links in the following format:
[CNN Travel](.html)
Here, the source name ("CNN Travel") is immediately followed by a URL in brackets. I want to make the source name clickable so that clicking it opens the corresponding link in the default web browser.
I already have code in place that processes text for bold formatting using ** markers. Below is a simplified version of my current code that processes the text stream and formats bold text:
stream = some_function()
full_response = ""
buffer = ""
bold = False
for chunk in stream:
if chunk != "None":
full_response += chunk
buffer += chunk
if "**" in buffer:
while "**" in buffer:
print("exist bold")
pre, _, post = buffer.partition("**")
append_to_chat_log(message=pre, bold=bold)
bold = not bold
buffer = post
else:
append_to_chat_log(message=buffer, bold=bold)
buffer = ""
def insert_with_formatting(message):
chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
bold_pattern = repile(r'\*\*(.*?)\*\*')
cursor = 0
for match in bold_pattern.finditer(message):
start, end = match.span()
# Insert text before bold part
chat_log.insert("end", message[cursor:start])
# Insert bold text
chat_log.insert("end", match.group(1), "bold")
cursor = end
# Insert any remaining text after the last bold part
chat_log.insert("end", message[cursor:])
def append_to_chat_log(sender=None, message=None, bold=False):
chat_log.config(state=tk.NORMAL)
if sender:
chat_log.insert("end", f"{sender}\n\n", "sender")
if message and bold:
print("append bold")
chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
chat_log.insert("end", message, "bold")
if not bold and message:
if "**" in message:
print("if message and not bold")
insert_with_formatting(message)
else:
chat_log.insert("end", message)
chat_log.tag_config("sender", font=('Helvetica', font_size, 'bold'), foreground=TEXT_COLOR)
chat_log.config(state=tk.DISABLED)
chat_log.see("end")
chat_log.update()
My Question:
How can I modify this (or add additional code) to detect markdown-style links (e.g., [CNN Travel](.html)
), and make the source name clickable? When clicked, it should open the associated URL in the browser.
I've looked into ways to create hyperlinks in Tkinter's Text widget but haven't been able to integrate it with my current text formatting logic. Any help on how to implement clickable hyperlinks in this context would be greatly appreciated.
I'm working on a Tkinter application where I display text in a Text widget. The incoming text sometimes contains markdown-style links in the following format:
[CNN Travel](https://www.cnn/travel/best-destinations-to-visit-2025/index.html)
Here, the source name ("CNN Travel") is immediately followed by a URL in brackets. I want to make the source name clickable so that clicking it opens the corresponding link in the default web browser.
I already have code in place that processes text for bold formatting using ** markers. Below is a simplified version of my current code that processes the text stream and formats bold text:
stream = some_function()
full_response = ""
buffer = ""
bold = False
for chunk in stream:
if chunk != "None":
full_response += chunk
buffer += chunk
if "**" in buffer:
while "**" in buffer:
print("exist bold")
pre, _, post = buffer.partition("**")
append_to_chat_log(message=pre, bold=bold)
bold = not bold
buffer = post
else:
append_to_chat_log(message=buffer, bold=bold)
buffer = ""
def insert_with_formatting(message):
chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
bold_pattern = repile(r'\*\*(.*?)\*\*')
cursor = 0
for match in bold_pattern.finditer(message):
start, end = match.span()
# Insert text before bold part
chat_log.insert("end", message[cursor:start])
# Insert bold text
chat_log.insert("end", match.group(1), "bold")
cursor = end
# Insert any remaining text after the last bold part
chat_log.insert("end", message[cursor:])
def append_to_chat_log(sender=None, message=None, bold=False):
chat_log.config(state=tk.NORMAL)
if sender:
chat_log.insert("end", f"{sender}\n\n", "sender")
if message and bold:
print("append bold")
chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
chat_log.insert("end", message, "bold")
if not bold and message:
if "**" in message:
print("if message and not bold")
insert_with_formatting(message)
else:
chat_log.insert("end", message)
chat_log.tag_config("sender", font=('Helvetica', font_size, 'bold'), foreground=TEXT_COLOR)
chat_log.config(state=tk.DISABLED)
chat_log.see("end")
chat_log.update()
My Question:
How can I modify this (or add additional code) to detect markdown-style links (e.g., [CNN Travel](https://www.cnn/travel/best-destinations-to-visit-2025/index.html)
), and make the source name clickable? When clicked, it should open the associated URL in the browser.
I've looked into ways to create hyperlinks in Tkinter's Text widget but haven't been able to integrate it with my current text formatting logic. Any help on how to implement clickable hyperlinks in this context would be greatly appreciated.
Share Improve this question edited Mar 31 at 5:18 user29255210 asked Mar 31 at 4:35 user29255210user29255210 133 bronze badges 3 |1 Answer
Reset to default 0I love regular expressions and they are very useful, for sure. But in quite a lot of situations, I think that using a proper parser is a better choice. You'll probably have new input cases and finally have problems with regular expressions.
I'll explain why with a solution using a regular expression.
Cases of markdown links
- The usual one:
[Visit example](https://www.example)
- A link with a title:
[⬆️top](#top "Go back to the top of the page")
- Not really valid, but you could accept it:
[](https://www.google)
The regular expression (first try)
To make it more readable, use the x
modifier so that you can use spaces
and comments and write the regular expression on several lines:
regex = r"""
\[ # Opening bracket.
(?P<text>[^\]]*) # Capture the link text.
\]\( # Closing bracket and opening parenthesis.
(?P<url>[^"\s)]+) # Capture the URL.
(?: # Optional group for the title attribute.
\s+ # Spaces between the URL and the title.
"(?P<title>[^"]*)" # Capture the optional title value.
)?
\) # Closing parenthesis.
"""
I used named capturing groups with (?P<group_name>)
instead of indexed
capturing groups that one usually create with ()
.
I made the text group accept an empty value. You could then add some logic in your code to use the url group instead.
Test it here: https://regex101/r/HbwBhy/1
Minor problem: don't match images
In Markdown, an image is very similar, as it has an exclamation point before the brackets, like this:


This can be solved by adding a negative lookbehind just before the opening bracket, to not match the "!" char:
(?<!!)
regex = r""" (?<!!) # Negative lookbehind to avoid matching images. \[ # Opening bracket. (?P<text>[^\]]*) # Capture the link text. \]\( # Closing bracket and opening parenthesis. (?P<url>[^"\s)]+) # Capture the URL. (?: # Optional group for the title attribute. \s+ # Spaces between the URL and the title. "(?P<title>[^"]*)" # Capture the optional title value. )? \) # Closing parenthesis. """
See it working here: https://regex101/r/HbwBhy/2
More complicated problems
As you can see in my regex101 example above, you may have a link inside a block of code:
[This link is ok to match](#ok) But not in this block of RAW text: Here, **nothing** should be matched, not even the bold or the following [link](https://www.example/should-not-be-matched)
This will also be the case for fenced code blocks
The same problem occurs for inline code (between backticks):
Image syntax is `[link text](https://www.example/path/to/page)`
Just fet solving it with negative lookbehinds and lookaheads. This is why a parser is the best choice.
Regex or Markdown parser?
If you think that your "Markdown-like" input will never have blocks of code, ok, well, try using the regular expression.
You can generate the Python code directly from regex101.
But if you want to be sure, use a proper Markdown parser and you could traverse its AST, if it has it available.
I'm not a Python developer, but maybe this library could help you:
https://github/miyuchina/mistletoe
[...](...)
. And you could keep it in dictionary as{name: url}
, To make it clickable you would have to assigntag
toCNN Travel
and use it to get url from dictionary. – furas Commented Mar 31 at 9:18