5

Can anyone explain how to add syntax highlighting to a Tkinter Text widget ?

Every time the program finds a matching word, it would color that word to how I want. Such as : Color the word tkinter in pink and in in blue. But when I type in Tkinter, it color Tk--ter in yellow and in in blue.

How can I fix this ? Thanks !

Hung Truong
  • 339
  • 2
  • 6
  • 19
  • 4
    You may want to look at [Pygments](http://pygments.org/docs/quickstart/). It would be a lot easier than rolling your own. If you still want to try to fix your version you may want to describe how you are finding keywords to highlight (i.e. what is your regular expression). – FamousJameous Jul 26 '16 at 16:18

5 Answers5

5

This is an extension of tfpf's answer.

When you call ic.make_pat() it returns the entire regular expression for python formatting. Whereas it may seem convenient to OR in some extra expressions, to one side or the other, it doesn't really give you much control, and it becomes cumbersome quickly. A potentially more useful and definitely more customizable approach would be to print/copy/paste ic.make_pat(), and break it up similar to below. This also has the bonus side-effect that you don't have to worry about how to call ic.make_pat() in regards to python versions because, after you do this you aren't going to use ic.make_pat(), at all.

#syntax highlighter patterns
KEYWORD   = r"\b(?P<KEYWORD>False|None|True|and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b"
EXCEPTION = r"([^.'\"\\#]\b|^)(?P<EXCEPTION>ArithmeticError|AssertionError|AttributeError|BaseException|BlockingIOError|BrokenPipeError|BufferError|BytesWarning|ChildProcessError|ConnectionAbortedError|ConnectionError|ConnectionRefusedError|ConnectionResetError|DeprecationWarning|EOFError|Ellipsis|EnvironmentError|Exception|FileExistsError|FileNotFoundError|FloatingPointError|FutureWarning|GeneratorExit|IOError|ImportError|ImportWarning|IndentationError|IndexError|InterruptedError|IsADirectoryError|KeyError|KeyboardInterrupt|LookupError|MemoryError|ModuleNotFoundError|NameError|NotADirectoryError|NotImplemented|NotImplementedError|OSError|OverflowError|PendingDeprecationWarning|PermissionError|ProcessLookupError|RecursionError|ReferenceError|ResourceWarning|RuntimeError|RuntimeWarning|StopAsyncIteration|StopIteration|SyntaxError|SyntaxWarning|SystemError|SystemExit|TabError|TimeoutError|TypeError|UnboundLocalError|UnicodeDecodeError|UnicodeEncodeError|UnicodeError|UnicodeTranslateError|UnicodeWarning|UserWarning|ValueError|Warning|WindowsError|ZeroDivisionError)\b"
BUILTIN   = r"([^.'\"\\#]\b|^)(?P<BUILTIN>abs|all|any|ascii|bin|breakpoint|callable|chr|classmethod|compile|complex|copyright|credits|delattr|dir|divmod|enumerate|eval|exec|exit|filter|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|isinstance|issubclass|iter|len|license|locals|map|max|memoryview|min|next|oct|open|ord|pow|print|quit|range|repr|reversed|round|set|setattr|slice|sorted|staticmethod|sum|type|vars|zip)\b"
DOCSTRING = r"(?P<DOCSTRING>(?i:r|u|f|fr|rf|b|br|rb)?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?|(?i:r|u|f|fr|rf|b|br|rb)?\"\"\"[^\"\\]*((\\.|\"(?!\"\"))[^\"\\]*)*(\"\"\")?)"
STRING    = r"(?P<STRING>(?i:r|u|f|fr|rf|b|br|rb)?'[^'\\\n]*(\\.[^'\\\n]*)*'?|(?i:r|u|f|fr|rf|b|br|rb)?\"[^\"\\\n]*(\\.[^\"\\\n]*)*\"?)"
TYPES     = r"\b(?P<TYPES>bool|bytearray|bytes|dict|float|int|list|str|tuple|object)\b"
NUMBER    = r"\b(?P<NUMBER>((0x|0b|0o|#)[\da-fA-F]+)|((\d*\.)?\d+))\b"
CLASSDEF  = r"(?<=\bclass)[ \t]+(?P<CLASSDEF>\w+)[ \t]*[:\(]" #recolor of DEFINITION for class definitions
DECORATOR = r"(^[ \t]*(?P<DECORATOR>@[\w\d\.]+))"
INSTANCE  = r"\b(?P<INSTANCE>super|self|cls)\b"
COMMENT   = r"(?P<COMMENT>#[^\n]*)"
SYNC      = r"(?P<SYNC>\n)"

Then you can concat all of those patterns, in whatever order suits you, as below:

PROG   = rf"{KEYWORD}|{BUILTIN}|{EXCEPTION}|{TYPES}|{COMMENT}|{DOCSTRING}|{STRING}|{SYNC}|{INSTANCE}|{DECORATOR}|{NUMBER}|{CLASSDEF}"

You may notice that DEFINITION is not present in any of the above patterns. That's because the above patterns are for .prog, but the DEFINITION pattern is determined by .idprog. Below is mine. I wanted class definitions to be a different color so, my pattern ignores definitions that are preceded with class. If you don't intend to make some exceptions to DEFINITION you don't have to mess with it, at all.

#original - r"\s+(\w+)"
IDPROG = r"(?<!class)\s+(\w+)"

The next thing to consider is tagdefs. Instead of line by line adding/modifying a key, you can just predefine tagdefs. Below is an example. Note that every regex group name used in the first set of patterns above, is represented with a key in the below object. Also note that DEFINITION is included here. Each object below becomes options for tag_configure, and you can use any option that tag_configure accepts. Colors and fonts are my own, and including them is unnecessary to the example.

TAGDEFS   = {   'COMMENT'    : {'foreground': CHARBLUE  , 'background': None},
                'TYPES'      : {'foreground': CLOUD2    , 'background': None},
                'NUMBER'     : {'foreground': LEMON     , 'background': None},
                'BUILTIN'    : {'foreground': OVERCAST  , 'background': None},
                'STRING'     : {'foreground': PUMPKIN   , 'background': None},
                'DOCSTRING'  : {'foreground': STORMY    , 'background': None},
                'EXCEPTION'  : {'foreground': CLOUD2    , 'background': None, 'font':FONTBOLD},
                'DEFINITION' : {'foreground': SAILOR    , 'background': None, 'font':FONTBOLD},
                'DECORATOR'  : {'foreground': CLOUD2    , 'background': None, 'font':FONTITAL},
                'INSTANCE'   : {'foreground': CLOUD     , 'background': None, 'font':FONTITAL},
                'KEYWORD'    : {'foreground': DK_SEAFOAM, 'background': None, 'font':FONTBOLD},
                'CLASSDEF'   : {'foreground': PURPLE    , 'background': None, 'font':FONTBOLD},
            }

'''
#what literally happens to this data when it is applied
for tag, cfg in self.tagdefs.items():
    self.tag_configure(tag, **cfg)
'''

Once you have that setup you can easily plug everything in. If you make a custom text widget you could put the below in __init__ and change YourTextWidget to self. Otherwise, just change YourTextWidget to the instance name of the text widget you want to connect this to (as it is in tfpf's answer).

cd         = ic.ColorDelegator()
cd.prog    = re.compile(PROG, re.S|re.M)
cd.idprog  = re.compile(IDPROG, re.S)
cd.tagdefs = {**cd.tagdefs, **TAGDEFS}
ip.Percolator(YourTextWidget).insertfilter(cd)

cd.tagdefs = {**cd.tagdefs, **TAGDEFS}

Why did I do it this way? We don't omit any values with this method. What if KEYWORD was defined in tagdefs, but not in TAGDEFS? If we didn't first unpack tagdefs into itself we would lose KEYWORD.

To sum up this end of the system: one big regex is run, and whatever regex group name matches becomes the name of the tag to apply. Whatever new regex groups you create should (maybe must) have an identically named key in .tagdefs.

OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
4

This is a follow-up to igwd's answer. idlelib.colorizer.ColorDelegator and idlelib.percolator.Percolator don't seem to be documented well, so I decided to post what I found.

If you're looking to highlight words like 'tkinter' and 'in', you probably want normal Python syntax highlighting and a few additions.

import idlelib.colorizer as ic
import idlelib.percolator as ip
import re
import tkinter as tk

root = tk.Tk()
root.title('Python Syntax Highlighting')

text = tk.Text(root)
text.pack()

cdg = ic.ColorDelegator()
cdg.prog = re.compile(r'\b(?P<MYGROUP>tkinter)\b|' + ic.make_pat(), re.S)
cdg.idprog = re.compile(r'\s+(\w+)', re.S)

cdg.tagdefs['MYGROUP'] = {'foreground': '#7F7F7F', 'background': '#FFFFFF'}

# These five lines are optional. If omitted, default colours are used.
cdg.tagdefs['COMMENT'] = {'foreground': '#FF0000', 'background': '#FFFFFF'}
cdg.tagdefs['KEYWORD'] = {'foreground': '#007F00', 'background': '#FFFFFF'}
cdg.tagdefs['BUILTIN'] = {'foreground': '#7F7F00', 'background': '#FFFFFF'}
cdg.tagdefs['STRING'] = {'foreground': '#7F3F00', 'background': '#FFFFFF'}
cdg.tagdefs['DEFINITION'] = {'foreground': '#007F7F', 'background': '#FFFFFF'}

ip.Percolator(text).insertfilter(cdg)

root.mainloop()

Example Output

The above MWE works with Python 3.8. On Python 3.10, just replace cdg.prog = re.compile(r'\b(?P<MYGROUP>tkinter)\b|' + ic.make_pat(), re.S) with cdg.prog = re.compile(r'\b(?P<MYGROUP>tkinter)\b|' + ic.make_pat().pattern, re.S). I have not tested this with other Python versions.

tfpf
  • 612
  • 1
  • 9
  • 19
2

These codes can achieve syntax highlighting in IDLE. You can copy the source code and modify certain things.

import tkinter as tk
from idlelib.percolator import Percolator
from idlelib.colorizer import ColorDelegator
main = tk.Tk()
text = tk.Text(main)
text.pack()
Percolator(text).insertfilter(ColorDelegator())
main.mainloop()
RemonDrk
  • 3
  • 2
igwd
  • 21
  • 2
  • If you have IDLE installed, this is the best solution. You can customise the colours, too! `cdg = ColorDelegator(); cdg.tagdefs['COMMENT'] = {'foreground': '#007FFF', 'background': '#333333'}; Percolator(text).insertfilter(cdg)` – tfpf Aug 05 '21 at 17:56
  • @tfpf this is a good solution for Windows, probably. On Mac it's slow, and the syntax isn't highlighted. – Daniyal Warraich Aug 20 '21 at 19:37
0

You can use a tag to do this. You can configure the tag to have certain backgrounds, fonts, text sizes, colors etc. And then add these tags to the text you want to configure.

All of this is in the documentation.

Pythonista
  • 11,377
  • 2
  • 31
  • 50
  • The documentation link does not work (anymore?). Any hints about a working link for the documentation? – Claudio Dec 15 '22 at 00:22
0

Use tags. I am going to implement the notions given there.

Example:

import tkinter as tk

root = tk.Tk()
root.title("Begueradj")
text = tk.Text(root)
# Insert some text
text.insert(tk.INSERT, "Security ")
text.insert(tk.END, " Pentesting ")
text.insert(tk.END, "Hacking ")
text.insert(tk.END, "Coding")
text.pack()
# Create some tags
text.tag_add("one", "1.0", "1.8")
text.tag_add("two", "1.10", "1.20")
text.tag_add("three", "1.21", "1.28")
text.tag_add("four", "1.29", "1.36")
#Configure the tags
text.tag_config("one", background="yellow", foreground="blue")
text.tag_config("two", background="black", foreground="green")
text.tag_config("three", background="blue", foreground="yellow")
text.tag_config("four", background="red", foreground="black")
#Start the program
root.mainloop()

Demo:

enter image description here

Billal Begueradj
  • 20,717
  • 43
  • 112
  • 130
  • 4
    But sometimes I type in "print"(which contains the word "int" in it), it highlights "int" in blue and the rest in yellow – Hung Truong Jul 27 '16 at 00:01