Tkinter. Как реализовать отображение номеров строк у Text

Здрасте! Делаю небольшой редактор кода на Tkinter. Как я могу реализовать отображение номеров строк у Text?


Ответы (1 шт):

Автор решения: GrAnd

Слева вставляется ещё один узенький редактор, в котором пишутся номера строк. Номера строк обновляются динамически по мере изменения количества строк в основном редакторе, и синхронизируется скролл. Я такое реализовывал ещё в прошлом веке на чистом Tk (который на Tcl.)

Вот, нашёл у себя тот самый проект, вырезал имплементацию редактора и быстренько сконвертировал из Tcl на Python.

import tkinter as tk

class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._orig = str(self) + "_orig"
        self.tk.call("rename", str(self), self._orig)
        self.tk.createcommand(str(self), self._proxy)

    def _proxy(self, command, *args):
        cmd = (self._orig, command) + args
        result = self.tk.call(cmd)

        if command in ("insert", "delete", "replace"):
            self.event_generate("<<TextModified>>")

        return result

class Editor:
    def __init__ (self, parent):
        self._in_sync = False

        tmp = tk.Label()
        fg_color = tmp.cget("foreground")
        bg_color = tmp.cget("background")
        tmp.destroy()

        self.w = tk.Frame(parent)
        self.w.pack(fill=tk.BOTH, expand=True)
        
        self.hs = tk.Scrollbar(self.w, orient=tk.HORIZONTAL)
        self.vs = tk.Scrollbar(self.w)
        self.ruler = tk.Text(self.w, bg=bg_color, fg=fg_color, wrap=tk.NONE, 
                             relief=tk.FLAT, width=5, cursor="arrow", 
                             selectborderwidth=0, exportselection=0)
        self.editor = CustomText(self.w, wrap=tk.NONE, exportselection=1, 
                                 insertwidth=2, xscrollcommand=self.hs.set)
        self.editor.bind("<<TextModified>>", self.rulersync)
                         
        self.hs.configure(command=self.editor.xview)
        self.vs.configure(command=self.scrollsync)
        self.ruler.configure (yscrollcommand=lambda *args: self.edsync(self.editor, self.ruler, *args))
        self.editor.configure(yscrollcommand=lambda *args: self.edsync(self.ruler, self.editor, *args))
                         
        self.ruler.tag_configure("right", justify=tk.RIGHT)
        self.ruler.insert(tk.END, "1", "right")
        self.ruler.configure(state=tk.DISABLED)

        self.w.grid_rowconfigure(0, weight=1, minsize=0)
        self.w.grid_columnconfigure(1, weight=1, minsize=0)

        self.editor.grid(padx=2, pady=0, row=0, column=1, sticky="news")
        self.ruler.grid (padx=2, pady=0, row=0, column=0, sticky="nws" )
        self.vs.grid    (padx=1, pady=0, row=0, column=2, sticky="news")
        self.hs.grid    (padx=0, pady=1, row=1, column=1, sticky="news")


    #-------------------------------------------------------------------------------
    # Description    :  Scroll two widgets by one scrollbar
    #                :  (for scrollbar -command)
    #-------------------------------------------------------------------------------
    def scrollsync(self, *args):
        self.editor.yview(*args)
        self.ruler.yview_moveto(self.editor.yview()[0]) 

    #-------------------------------------------------------------------------------
    # Description    :  Set position of scrollbar
    #                :  (for text -yscrollcommand)
    # Paremeters     :  sw          - source widgets
    #                :  tw          - target widget
    #-------------------------------------------------------------------------------
    def edsync(self, tw, sw, *args):
        if not self._in_sync:
            self._in_sync = True
            tw.yview_moveto(sw.yview()[0]) 
            self.w.update_idletasks()
            self.vs.set(*args)
            self._in_sync = False

    #-------------------------------------------------------------------------------
    # Description    :  Updates line numbers text in ruler
    #                :  to match actual line numbers in the editor
    #-------------------------------------------------------------------------------
    def rulersync(self, *args):
         elines = int(self.editor.index(tk.END).split(".")[0]) - 1
         rlines = int(self.ruler.index(tk.END).split(".")[0]) - 1
         if rlines != elines:
            self.ruler.configure(state=tk.NORMAL)
            if rlines < elines:
                for i in range(rlines+1, elines+1):
                    self.ruler.insert(tk.END, f"\n{i}", "right")
            else:
                self.ruler.delete(f"{elines+1}.0 -1chars", tk.END)
            self.ruler.configure(state=tk.DISABLED)

            self.ruler.yview_moveto(self.editor.yview()[0])
            self.w.update_idletasks()

root = tk.Tk()

ed = Editor(root)
ed.editor.insert(tk.END, "line " + "\nline ".join(map(str, range(1,100))))
            
root.mainloop()

-

→ Ссылка