I'm making a Test Editor from a base that I got from a YouTube tutorial. I was trying to make highlighted the python's statements, but when I write a statement, it colorizes all the lines, and I thought that the problem is the use of indexes I make.
This is the code:
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
class Menubar:
def __init__(self, parent):
font_specs = 14
menubar = tk.Menu(parent.master)
parent.master.config(menu = menubar)
file_dropdown = tk.Menu(menubar, font = font_specs, tearoff = 0)
file_dropdown.add_command(label = "Nuovo file",
accelerator = "Ctrl + N",
command = parent.new_file)
file_dropdown.add_command(label = "Apri file",
accelerator = "Ctrl + O",
command = parent.open_file)
file_dropdown.add_command(label = "Salva",
accelerator = "Ctrl + S",
command = parent.save)
file_dropdown.add_command(label = "Salva con nome",
accelerator = "Ctrl + Shit + S",
command = parent.save_as)
file_dropdown.add_separator()
file_dropdown.add_command(label = "Esci",
command = parent.master.destroy)
about_dropdown = tk.Menu(menubar, font = font_specs, tearoff = 0)
about_dropdown.add_command(label = "Note di rilascio",
command = self.show_about_message)
about_dropdown.add_separator()
about_dropdown.add_command(label = "About",
command = self.show_release_notes)
settings_dropdown = tk.Menu(menubar, font = font_specs, tearoff = 0)
settings_dropdown.add_command(label = "Cambia lo sfondo dell'editor",
command = parent.change_background)
menubar.add_cascade(label = "File", menu = file_dropdown)
menubar.add_cascade(label = "About", menu = about_dropdown)
menubar.add_cascade(label = "Settings", menu = settings_dropdown)
def show_about_message(self):
box_title = "Riguardo PyText"
box_message = "Il mio primo editor testuale creato con Python e TkInter!"
messagebox.showinfo(box_title, box_message)
def show_release_notes(self):
box_title = "Note di Rilascio"
box_message = "Versione 0.1 (Beta) Santa"
messagebox.showinfo(box_title, box_message)
class Statusbar:
def __init__(self, parent):
font_specs = 12
self.status = tk.StringVar()
self.status.set("PyText - 0.1 Santa")
label = tk.Label(parent.text_area, textvariable = self.status,
fg = "black", bg = "lightgrey", anchor = "sw")
label.pack(side = tk.BOTTOM, fill = tk.BOTH)
def update_status(self, *args):
if isinstance(args[0], bool):
self.status.set("Il tuo file è stato salvato!")
else:
self.status.set("PyText - 0.1 Santa")
class PyText:
"""
Classe-Madre dell'applicazione
"""
def __init__(self, master):
master.title("Untitled - PyText")
master.geometry("1200x700")
font_specs = 18
self.master = master
self.filename = None
self.text_area = tk.Text(master, font = font_specs, insertbackground = "black")
self.scroll = tk.Scrollbar(master, command = self.text_area.yview)
self.text_area.configure(yscrollcommand = self.scroll.set)
self.text_area.pack(side = tk.LEFT, fill = tk.BOTH, expand = True)
self.scroll.pack(side = tk.RIGHT, fill = tk.Y)
self.menubar = Menubar(self)
self.statusbar = Statusbar(self)
self.bind_shortcuts()
def set_window_title(self, name = None):
if name:
self.master.title(name + " - PyText")
else:
self.master.title("Untitled - PyText")
def new_file(self, *args):
self.text_area.delete(1.0, tk.END)
self.filename = None
self.set_window_title()
def open_file(self, *args):
self.filename = filedialog.askopenfilename(
defaultextension = ".txt",
filetypes = [("Tutti i file", "*.*"),
("File di Testo", "*.txt"),
("Script Python", "*.py"),
("Markdown Text", "*.md"),
("File JavaScript", "*.js"),
("Documenti Html", "*.html"),
("Documenti CSS", "*.css"),
("Programmi Java", "*.java")]
)
if self.filename:
self.text_area.delete(1.0, tk.END)
with open(self.filename, "r") as f:
self.text_area.insert(1.0, f.read())
self.set_window_title(self.filename)
def save(self, *args):
if self.filename:
try:
textarea_content = self.text_area.get(1.0, tk.END)
with open(self.filename, "w") as f:
f.write(textarea_content)
self.statusbar.update_status(True)
except Exception as e:
print(e)
else:
self.save_as()
def save_as(self, *args):
try:
new_file = filedialog.asksaveasfilename(
initialfile = "Untitled.txt",
defaultextension = ".txt",
filetypes = [("Tutti i file", "*.*"),
("File di Testo", "*.txt"),
("Script Python", "*.py"),
("Markdown Text", "*.md"),
("File JavaScript", "*.js"),
("Documenti Html", "*.html"),
("Documenti CSS", "*.css"),
("Programmi Java", "*.java")]
)
textarea_content = self.text_area.get(1.0, tk.END)
with open(new_file, "w") as f:
f.write(textarea_content)
self.filename = new_file
self.set_window_title(self.filename)
self.statusbar.update_status(True)
except Exception as e:
print(e)
def change_background(self):
self.text_area.config(background = "black", foreground = "white",
insertbackground = "white", insertwidth = 2)
def highlight_text(self, *args):
tags = {
"import": "pink",
"from": "pink",
"def": "blue",
"for": "purple",
"while": "purple"
}
textarea_content = self.text_area.get(1.0, tk.END)
lines = textarea_content.split("\n")
for row in lines:
for tag in tags:
index = row.find(tag) + 1.0
if index > 0.0:
self.text_area.tag_add(tag, index, index + len(tag) -1)
self.text_area.tag_config(tag, foreground = tags.get(tag))
print("Nuovo tag aggiunto:", tag)
print("Funzione eseguita:", args, "\n")
def bind_shortcuts(self):
self.text_area.bind("<Control-n>", self.new_file)
self.text_area.bind("<Control-o>", self.open_file)
self.text_area.bind("<Control-s>", self.save)
self.text_area.bind("<Control-S>", self.save_as)
self.text_area.bind("<Key>", self.highlight_text, self.statusbar.update_status)
if __name__ == "__main__":
master = tk.Tk()
pt = PyText(master)
master.mainloop()
How can I get the index of the line where is the statement?
You works with every line separtelly so you get only X
. To get Y
you need to enumerate
lines:
for y, row in enumerate(lines, 1):
Result from find()
you convert to float
but you need int
and later convert X,Y
to string "Y.X"
start = '{}.{}'.format(y, x)
end = '{}.{}'.format(y, x+len(tag))
Version which works for me
for y, row in enumerate(lines, 1):
for tag in tags:
x = row.find(tag)
if x > -1:
print(f"{tag} | x: {x} | y: {y}")
start = '{}.{}'.format(y, x)
end = '{}.{}'.format(y, x+len(tag))
print(f"{tag} | start: {start} | end: {end}")
self.text_area.tag_add(tag, start, end)
self.text_area.tag_config(tag, foreground = tags.get(tag))
Your idea has only one big problem - it will colorize def
in word define
, or for
in word forward
, etc. So maybe it would need regex
to create more complex rules and search only full words.
Other problem: find()
gives you only first item in line - so if you will try to highlight element which can be two times in line then you would have to use loop with find(..., start)
to search after first element.
Maybe it would be better to use example for How to highlight text in a tkinter Text widget
BTW:
You binded key shortcuts to self.text_area
so I had to click in text area to use shortcut. If I change self.text_area.bind
into self.master.bind
then shortcuts works even without clicking in text area.
EDIT:
There is Thonny IDE which uses Tkinter
and it highlights code.
I tried to find how it makes this but I found only regex for highlights - thonny_utils.py
Maybe if you get full code and use some tool to search string in files (like grep
in Linux) then you could find all places where it uses variables from token_utils.py
(ie. KEYWORD
)
EDIT: coloring.py
EDIT:
Full code with function highlight_text
which uses previous method line.find
and highlight_text_regex
which uses text_area.search
with regex
.
New version based on code from answer to question How to highlight text in a tkinter Text widget
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import os
print(os.getcwd())
class Menubar:
def __init__(self, parent):
font_specs = 14
menubar = tk.Menu(parent.master)
parent.master.config(menu = menubar)
file_dropdown = tk.Menu(menubar, font = font_specs, tearoff = 0)
file_dropdown.add_command(label = "Nuovo file",
accelerator = "Ctrl + N",
command = parent.new_file)
file_dropdown.add_command(label = "Apri file",
accelerator = "Ctrl + O",
command = parent.open_file)
file_dropdown.add_command(label = "Salva",
accelerator = "Ctrl + S",
command = parent.save)
file_dropdown.add_command(label = "Salva con nome",
accelerator = "Ctrl + Shift + S",
command = parent.save_as)
file_dropdown.add_separator()
file_dropdown.add_command(label = "Esci",
accelerator = "Ctrl + Q",
command = parent.master.destroy)
about_dropdown = tk.Menu(menubar, font = font_specs, tearoff = 0)
about_dropdown.add_command(label = "Note di rilascio",
command = self.show_about_message)
about_dropdown.add_separator()
about_dropdown.add_command(label = "About",
command = self.show_release_notes)
settings_dropdown = tk.Menu(menubar, font = font_specs, tearoff = 0)
settings_dropdown.add_command(label = "Cambia lo sfondo dell'editor",
command = parent.change_background)
menubar.add_cascade(label = "File", menu = file_dropdown)
menubar.add_cascade(label = "About", menu = about_dropdown)
menubar.add_cascade(label = "Settings", menu = settings_dropdown)
def show_about_message(self):
box_title = "Riguardo PyText"
box_message = "Il mio primo editor testuale creato con Python e TkInter!"
messagebox.showinfo(box_title, box_message)
def show_release_notes(self):
box_title = "Note di Rilascio"
box_message = "Versione 0.1 (Beta) Santa"
messagebox.showinfo(box_title, box_message)
class Statusbar:
def __init__(self, parent):
font_specs = 12
self.status = tk.StringVar()
self.status.set("PyText - 0.1 Santa")
label = tk.Label(parent.text_area, textvariable = self.status,
fg = "black", bg = "lightgrey", anchor = "sw")
label.pack(side = tk.BOTTOM, fill = tk.BOTH)
def update_status(self, *args):
if isinstance(args[0], bool):
self.status.set("Il tuo file è stato salvato!")
else:
self.status.set("PyText - 0.1 Santa")
class PyText:
"""
Classe-Madre dell'applicazione
"""
def __init__(self, master):
master.title("Untitled - PyText")
master.geometry("1200x700")
font_specs = 18
self.master = master
self.filename = None
self.text_area = tk.Text(master, font = font_specs, insertbackground = "black")
self.scroll = tk.Scrollbar(master, command = self.text_area.yview)
self.text_area.configure(yscrollcommand = self.scroll.set)
self.text_area.pack(side = tk.LEFT, fill = tk.BOTH, expand = True)
self.scroll.pack(side = tk.RIGHT, fill = tk.Y)
self.menubar = Menubar(self)
self.statusbar = Statusbar(self)
self.bind_shortcuts()
def set_window_title(self, name = None):
if name:
self.master.title(name + " - PyText")
else:
self.master.title("Untitled - PyText")
def new_file(self, *args):
self.text_area.delete(1.0, tk.END)
self.filename = None
self.set_window_title()
def open_file(self, *args):
self.filename = filedialog.askopenfilename(
initialdir = os.getcwd(),
defaultextension = ".txt",
filetypes = [("Tutti i file", "*.*"),
("File di Testo", "*.txt"),
("Script Python", "*.py"),
("Markdown Text", "*.md"),
("File JavaScript", "*.js"),
("Documenti Html", "*.html"),
("Documenti CSS", "*.css"),
("Programmi Java", "*.java")]
)
if self.filename:
self.text_area.delete(1.0, tk.END)
with open(self.filename, "r") as f:
self.text_area.insert(1.0, f.read())
self.set_window_title(self.filename)
def save(self, *args):
if self.filename:
try:
textarea_content = self.text_area.get(1.0, tk.END)
with open(self.filename, "w") as f:
f.write(textarea_content)
self.statusbar.update_status(True)
except Exception as e:
print(e)
else:
self.save_as()
def save_as(self, *args):
try:
new_file = filedialog.asksaveasfilename(
initialfile = "Untitled.txt",
defaultextension = ".txt",
filetypes = [("Tutti i file", "*.*"),
("File di Testo", "*.txt"),
("Script Python", "*.py"),
("Markdown Text", "*.md"),
("File JavaScript", "*.js"),
("Documenti Html", "*.html"),
("Documenti CSS", "*.css"),
("Programmi Java", "*.java")]
)
textarea_content = self.text_area.get(1.0, tk.END)
with open(new_file, "w") as f:
f.write(textarea_content)
self.filename = new_file
self.set_window_title(self.filename)
self.statusbar.update_status(True)
except Exception as e:
print(e)
def change_background(self):
self.text_area.config(background = "black", foreground = "white",
insertbackground = "white", insertwidth = 2)
def highlight_text_old(self, *args):
tags = {
"import": "pink",
"from": "red",
"def": "blue",
"for": "purple",
"while": "green",
}
textarea_content = self.text_area.get(1.0, tk.END)
lines = textarea_content.split("\n")
for y, row in enumerate(lines, 1):
for tag in tags:
x = row.find(tag)
if x > -1:
print(f"{tag} | x: {x} | y: {y}")
start = '{}.{}'.format(y, x)
end = '{}.{}'.format(y, x+len(tag))
print(f"{tag} | start: {start} | end: {end}")
self.text_area.tag_add(tag, start, end)
self.text_area.tag_config(tag, foreground = tags.get(tag))
#print("Nuovo tag aggiunto:", tag)
#print("Funzione eseguita:", args, "\n")
def highlight_text(self, *args):
# TODO: move to `__init__` ?
tags = {
"import": "pink",
"from": "red",
"def": "blue",
"for": "purple",
"while": "green",
}
# TODO: move to `__init__` ?
# create tags with assigned color - do it only onve (in __init__)
for color in ['pink', 'red', 'blue', 'purple', 'green']:
self.text_area.tag_config(color, foreground=color)
# remove all tags from text
for tag in self.text_area.tag_names():
self.text_area.tag_remove(tag, '1.0', 'end') # not `tag_remove()`
textarea_content = self.text_area.get(1.0, tk.END)
lines = textarea_content.split("\n")
for y, row in enumerate(lines, 1):
for tag in tags:
x = row.find(tag)
if x > -1:
print(f"{tag} | x: {x} | y: {y}")
match_start = '{}.{}'.format(y, x)
match_end = '{}.{}'.format(y, x+len(tag))
print(f"{tag} | start: {match_start} | end: {match_end}")
self.text_area.tag_add(tag, match_start, match_end)
#self.text_area.tag_config(tag, foreground=tags.get(tag)) # create tags only once - at start
#print("Nuovo tag aggiunto:", tag)
#print("Funzione eseguita:", args, "\n")
def highlight_text_regex(self, *args):
# TODO: move to `__init__` ?
tags = {
"import": "red",
"from": "red",
"as": "red",
"def": "blue",
"class": "blue",
"for": "green",
"while": "green",
"if": "brown",
"elif": "brown",
"else": "brown",
"print": "purple",
"True": "blue",
"False": "blue",
"self": "blue",
"\d+": "red", # digits
"__[a-zA-Z][a-zA-Z0-9_]*__": "red", # ie. `__init__`
}
# add `\m \M` to words
tags = {f'\m{word}\M': tag for word, tag in tags.items()}
# tags which doesn't work with `\m \M`
other_tags = {
"\(": "brown", # need `\` because `(` has special meaning
"\)": "brown", # need `\` because `)` has special meaning
">=": "green",
"<=": "green",
"=": "green",
">": "green",
"<": "green",
"#.*$": "brown", # comment - to the end of line `$`
}
# create one dictionary with all tags
tags.update(other_tags)
# TODO: move to `__init__` ?
# create tags with assigned color - do it only onve (in __init__)
for color in ['pink', 'red', 'blue', 'purple', 'green', 'brown', 'yellow']:
self.text_area.tag_config(color, foreground=color)
# remove all tags from text before adding all tags again (to change color when ie. `def` change to `define`)
for tag in self.text_area.tag_names():
self.text_area.tag_remove(tag, '1.0', 'end') # not `tag_remove()`
count_chars = tk.IntVar() # needs to count matched chars - ie. for digits `\d+`
# search `word` and add `tag`
for word, tag in tags.items():
#pattern = f'\m{word}\M' # http://tcl.tk/man/tcl8.5/TclCmd/re_syntax.htm#M72
pattern = word # http://tcl.tk/man/tcl8.5/TclCmd/re_syntax.htm#M72
search_start = '1.0'
search_end = 'end'
while True:
position = self.text_area.search(pattern, search_start, search_end, count=count_chars, regexp=True)
print('search:', word, position)
if position:
print(f"{word} | pos: {position}")
match_start = position
match_end = '{}+{}c'.format(position, count_chars.get()) #len(word)) # use special string `Y.X+Nc` instead values (Y, X+N)
print(f"{word} | start: {match_start} | end: {match_end}")
self.text_area.tag_add(tag, match_start, match_end)
#self.text_area.tag_config(tag, foreground=tags.get(tag)) # create tags only once - at start
search_start = match_end # to search next word
else:
break
def quit(self, *args):
self.master.destroy()
def bind_shortcuts(self):
self.master.bind("<Control-n>", self.new_file)
self.master.bind("<Control-o>", self.open_file)
self.master.bind("<Control-s>", self.save)
self.master.bind("<Control-S>", self.save_as)
self.master.bind("<Control-q>", self.quit)
self.master.bind("<Key>", self.highlight_text_regex, self.statusbar.update_status)
if __name__ == "__main__":
master = tk.Tk()
pt = PyText(master)
master.mainloop()