Как получить индекс текущей строки

Я делаю тестовый редактор на базе, которую я получил из учебника на 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()

Как я могу получить индекс строки, где находится оператор?

Пожалуйста, сократите код до минимального воспроизводимого примера. Если вопрос касается индексации текстового виджета, нам не нужно ничего, кроме текстового виджета. Все остальные функции не нужны для воспроизведения проблемы.

Bryan Oakley 11.12.2020 13:56

Хорошо, извините, ребята, я буду иметь это в виду, спасибо :3

Seintian 11.12.2020 15:01
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
2
476
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы работаете с каждой строкой отдельно, поэтому получаете только 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

Seintian 11.12.2020 15:01

возможно, вам следует добавить пробел с обеих сторон `def`, чтобы пропустить define, а также такие слова, как undef. Но это создает проблемы, когда def находится в начале строки или кто-то использует табуляцию вместо пробела для создания отступов. Аналогичная проблема будет, когда кто-то не будет использовать пробел после слова - т.е. while(1). Используя regex со специальным символом \b, вы можете поймать все эти ситуации - re.search("\bdef\b", ...). В регулярном выражении вы также можете использовать один шаблон для всех слов, которые используют один и тот же цвет re.search("from|import", ...) - и этот метод я вижу в регулярном выражении у Тонни (thonny_utils.py)

furas 11.12.2020 16:12

Извините, вы можете опубликовать код с регулярным выражением? Я искал это в Интернете, потому что я никогда не слышал об этом до сегодняшнего дня, но я не могу понять, как это работает. Я попрошу своего профессора объяснить мне этот новый аргумент O.o

Seintian 12.12.2020 19:13

Я добавил полный код для ответа - есть предыдущие highlight_text и highlight_text_regex

furas 13.12.2020 03:14

о, хорошо, я прочитал методы, которые вы добавили, и я пытаюсь понять их... Да, вы также добавили другие теги и регулярное выражение... вы взяли пары тегов - цвета и поместили теги в шаблоны с "\m " и "\M"... а потом вы создали теги в text_area... Ну, я думаю, я понял более или менее все, большое спасибо, чувак! Теперь я также понял, что такое регулярные выражения ;) Но... последний вопрос... что означает "TODO" в комментариях? и эти комментарии «TODO: перейти к« инициализации »?»

Seintian 13.12.2020 12:37
TODO означает (eventually) TO DO it later. А TODO: move to __init__ означает, что этот код можно было выполнить один раз, т.е. в __init__. Нет необходимости выполнять его много раз - нет необходимости определять tags много раз, потому что они никогда не меняются - поэтому вы можете сделать это только один раз в __init__. Нет необходимости определять ` self.text_area.tag_config` много раз, потому что они никогда не меняются, поэтому вы можете сделать это только один раз в __init__
furas 13.12.2020 18:44

а ок ок, понял. Спасибо чувак за все, правда <3

Seintian 13.12.2020 19:38

Другие вопросы по теме