Я делаю тестовый редактор на базе, которую я получил из учебника на YouTube. Я пытался сделать выделенными операторы Python, но когда я пишу оператор, он раскрашивает все строки, и я подумал, что проблема заключается в использовании индексов, которые я делаю.
Это код:
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()
Как я могу получить индекс строки, где находится оператор?
Хорошо, извините, ребята, я буду иметь это в виду, спасибо :3
Вы работаете с каждой строкой отдельно, поэтому получаете только X
. Чтобы получить Y
нужно enumerate
строк:
for y, row in enumerate(lines, 1):
В результате find()
вы конвертируете в float
, но вам нужно int
, а затем преобразовать X,Y
в строку "Y.X"
start = '{}.{}'.format(y, x)
end = '{}.{}'.format(y, x+len(tag))
Версия, которая работает для меня
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))
У вашей идеи есть только одна большая проблема — она будет раскрашивать def
в слове define
, или for
в слове forward
и т. д. Так что, возможно, потребуется regex
создать более сложные правила и искать только полные слова.
Другая проблема: find()
дает вам только первый элемент в строке, поэтому, если вы попытаетесь выделить элемент, который может быть два раза в строке, вам придется использовать цикл с find(..., start)
для поиска первого элемента.
Возможно, было бы лучше использовать пример для Как выделить текст в текстовом виджете tkinter
КСТАТИ:
Вы привязали сочетания клавиш к self.text_area
, поэтому мне пришлось щелкнуть в текстовой области, чтобы использовать сочетание клавиш. Если я заменю self.text_area.bind
на self.master.bind
, то ярлыки будут работать даже без щелчка в текстовой области.
Обновлено:
Существует Thonny IDE, которая использует Tkinter
и выделяет код.
Я пытался найти, как это делается, но нашел только регулярное выражение для выделения — thonny_utils.py
Возможно, если вы получите полный код и используете какой-нибудь инструмент для поиска строки в файлах (например, grep
в Linux), вы сможете найти все места, где используются переменные из token_utils.py
(т.е. KEYWORD
)
Обновлено: coloring.py
Обновлено:
Полный код с функцией highlight_text
, которая использует предыдущий метод line.find
и highlight_text_regex
, которая использует text_area.search
с regex
.
Новая версия на основе кода из ответа на вопрос Как выделить текст в текстовом виджете tkinter
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()
Большое спасибо за исчерпывающий ответ! Чтобы придать больше смысла моему методу .find, могу ли я поставить операторы после пробела? (например, "def", чтобы не путать "define" с "def"). Для привязок да, было бы лучше, ахах. Последний совет, я попытался прочитать первую ссылку, но я мало что понял, и во второй ссылке я прочитал код, но я не понял того же:/ может быть, мне нужно немного больше кодировать, чтобы понять все эти вещи. Кстати, я реализую второй цикл для .find() и... вот и все ;P Спасибо за ответ, чувак! :D
возможно, вам следует добавить пробел с обеих сторон `def`, чтобы пропустить define
, а также такие слова, как undef
. Но это создает проблемы, когда def
находится в начале строки или кто-то использует табуляцию вместо пробела для создания отступов. Аналогичная проблема будет, когда кто-то не будет использовать пробел после слова - т.е. while(1)
. Используя regex
со специальным символом \b
, вы можете поймать все эти ситуации - re.search("\bdef\b", ...)
. В регулярном выражении вы также можете использовать один шаблон для всех слов, которые используют один и тот же цвет re.search("from|import", ...)
- и этот метод я вижу в регулярном выражении у Тонни (thonny_utils.py
)
Извините, вы можете опубликовать код с регулярным выражением? Я искал это в Интернете, потому что я никогда не слышал об этом до сегодняшнего дня, но я не могу понять, как это работает. Я попрошу своего профессора объяснить мне этот новый аргумент O.o
Я добавил полный код для ответа - есть предыдущие highlight_text
и highlight_text_regex
о, хорошо, я прочитал методы, которые вы добавили, и я пытаюсь понять их... Да, вы также добавили другие теги и регулярное выражение... вы взяли пары тегов - цвета и поместили теги в шаблоны с "\m " и "\M"... а потом вы создали теги в text_area... Ну, я думаю, я понял более или менее все, большое спасибо, чувак! Теперь я также понял, что такое регулярные выражения ;) Но... последний вопрос... что означает "TODO" в комментариях? и эти комментарии «TODO: перейти к« инициализации »?»
TODO
означает (eventually) TO DO it later
. А TODO: move to __init__
означает, что этот код можно было выполнить один раз, т.е. в __init__
. Нет необходимости выполнять его много раз - нет необходимости определять tags
много раз, потому что они никогда не меняются - поэтому вы можете сделать это только один раз в __init__
. Нет необходимости определять ` self.text_area.tag_config` много раз, потому что они никогда не меняются, поэтому вы можете сделать это только один раз в __init__
а ок ок, понял. Спасибо чувак за все, правда <3
Пожалуйста, сократите код до минимального воспроизводимого примера. Если вопрос касается индексации текстового виджета, нам не нужно ничего, кроме текстового виджета. Все остальные функции не нужны для воспроизведения проблемы.