Tkinter : Syntax highlighting for Text widget
Asked Answered
D

6

6

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 !

Daytoday answered 26/7, 2016 at 16:13 Comment(1)
You may want to look at Pygments. 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).Kneecap
C
6

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.

Conventionality answered 21/6, 2022 at 21:29 Comment(0)
P
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.

Peccable answered 5/8, 2021 at 18:39 Comment(0)
T
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()
Teakettle answered 31/5, 2021 at 14:15 Comment(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)Peccable
@Peccable this is a good solution for Windows, probably. On Mac it's slow, and the syntax isn't highlighted.Redmond
I
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.

Ifill answered 26/7, 2016 at 17:4 Comment(2)
The documentation link does not work (anymore?). Any hints about a working link for the documentation?Bowers
dafarry.github.io/tkinterbook/text.htmIfill
E
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

Ellipsis answered 26/7, 2016 at 18:14 Comment(1)
But sometimes I type in "print"(which contains the word "int" in it), it highlights "int" in blue and the rest in yellowDaytoday
V
0

Options:

  • tokenize example: here

  • pygments example: here where is using get_style_by_name("default").list_styles() for built-in styles; replace lexer with PythonLexer for Python

  • fancycompleter

  • idlelib example:

    from tkinter import Tk
    from tkinter import Text
    from idlelib.colorizer import ColorDelegator, color_config
    from idlelib.percolator import Percolator
    from idlelib.undo import UndoDelegator
    
    
    class CustomEditor:
        def __init__(self):
            self.root = Tk()
            self.text = text = Text(self.root)
            text.pack()
    
            self.perc = perc = Percolator(text)
            self.undo = undo = UndoDelegator()
            perc.insertfilter(undo)
            self.color = None
            self.code_context = None
            self.ResetColorizer()
    
        def _add_colorizer(self):
            if self.color:
                return
            self.color = ColorDelegator()
            self.perc.insertfilterafter(filter=self.color, after=self.undo)
    
        def _rm_colorizer(self):
            if not self.color:
                return
            self.color.removecolors()
            self.perc.removefilter(self.color)
            self.color = None
    
        def ResetColorizer(self):
            self._rm_colorizer()
            self._add_colorizer()
            color_config(self.text)
    
    
    editor = CustomEditor()
    editor.text.insert(1.0, "print('Hello')\n")
    editor.text.focus_set()
    editor.root.mainloop()
    
Villanelle answered 14/12, 2023 at 17:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.