Hi, I have integrated line numbering into IDLE. All the code related to it, are in idlelib/LineNumber.py for the moment, for easier debugging.
I'd like to say, that there is a memory leak. The leak is, events like insert, scroll etc cause the memory to increase continuously. AFA I can tell, the number of objects after every re-render is constant. Objects are first deleted before new ones are created. I have tried reusing existing objects and moving them to the required screen coordinate, instead of deleting and creating anew. This did not work. I can almost certainly say that it is related MultiCallCreator's MultiCall class and not with the way LineNumberCanvas rerender's itself. In the past two days, I have been trying to understand the MultiCall module and how values related to <<Changed>> virtual event have to be added to it. I have not made a breakthrough yet. The current changes to MultiCall.py goes some way to reduce the sluggishness. This make me feel more strongly that some optmization in MultiCall, will fix the memory leak. I have also added a file text-without-multicall.py to show the above. In this, using tkinter.Text, the memory usage does not change. I have attached the patch in this email. -- Regards Saimadhav Heblikar
diff -r 601a08fcb507 Lib/idlelib/EditorWindow.py --- a/Lib/idlelib/EditorWindow.py Sat Jun 14 18:51:34 2014 -0700 +++ b/Lib/idlelib/EditorWindow.py Tue Jun 17 18:28:18 2014 +0530 @@ -13,6 +13,7 @@ import webbrowser from idlelib.MultiCall import MultiCallCreator +from idlelib.LineNumber import LineNumberCanvas, Text from idlelib import idlever from idlelib import WindowList from idlelib import SearchDialog @@ -227,6 +228,10 @@ vbar['command'] = text.yview vbar.pack(side=RIGHT, fill=Y) text['yscrollcommand'] = vbar.set + if self.__class__.__name__ != 'PyShell': + self.linenumber_canvas = LineNumberCanvas(text_frame) + self.linenumber_canvas.connect(text=self.text, side=LEFT, fill=Y) + fontWeight = 'normal' if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'): fontWeight='bold' diff -r 601a08fcb507 Lib/idlelib/LineNumber.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/idlelib/LineNumber.py Tue Jun 17 18:28:18 2014 +0530 @@ -0,0 +1,65 @@ +import tkinter as tk + +class LineNumberCanvas(tk.Canvas): + def __init__(self,*args, **kwargs): + tk.Canvas.__init__(self, *args, **kwargs) + + def connect(self, text, *arg, **kwargs): + self.text = text + self.text.bind("<<Changed>>", self.re_render) + self.text.bind("<Configure>", self.re_render) + self.number_of_digits = 0 + self.pack(*arg, **kwargs) + + def disconnect(self, text_widget): + pass + + def re_render(self, event): + """Re-render the line canvas""" + self.delete('all') + if self.number_of_digits != len(self.text.index('end')): + self.number_of_digits = len(self.text.index('end')) + self['width'] = self.number_of_digits * 8 + + temp = self.text.index("@0,0") + while True : + dline= self.text.dlineinfo(temp) + if not dline: + break + y, height = dline[1], dline[4] + linenum = str(temp).split(".")[0] + linenum_text = ' '*(self.number_of_digits - len(linenum)) \ + + linenum + self.create_text(5, y + height/4, anchor="nw", text=linenum_text) + temp = self.text.index("%s+1line" % temp) + +class Text(tk.Text): + def __init__(self, *args, **kwargs): + tk.Text.__init__(self, *args, **kwargs) + + self.tk.eval(''' + proc widget_interceptor {widget command args} { + + set orig_call [uplevel [linsert $args 0 $command]] + + if { + ([lindex $args 0] == "insert") || + ([lindex $args 0] == "delete") || + ([lindex $args 0] == "replace") || + ([lrange $args 0 2] == {mark set insert}) || + ([lrange $args 0 1] == {xview moveto}) || + ([lrange $args 0 1] == {xview scroll}) || + ([lrange $args 0 1] == {yview moveto}) || + ([lrange $args 0 1] == {yview scroll})} { + + event generate $widget <<Changed>> -when tail + } + + #return original command + return $orig_call + } + ''') + self.tk.eval(''' + rename {widget} new_{widget} + interp alias {{}} ::{widget} {{}} widget_interceptor {widget} new_{widget} + '''.format(widget=str(self))) diff -r 601a08fcb507 Lib/idlelib/MultiCall.py --- a/Lib/idlelib/MultiCall.py Sat Jun 14 18:51:34 2014 -0700 +++ b/Lib/idlelib/MultiCall.py Tue Jun 17 18:28:18 2014 +0530 @@ -34,8 +34,8 @@ import tkinter # the event type constants, which define the meaning of mc_type -MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3; -MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7; +MC_CHANGED=0; MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3; +MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7; MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12; MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17; MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22; @@ -251,7 +251,7 @@ # define the list of event types to be handled by MultiEvent. the order is # compatible with the definition of event type constants. _types = ( - ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"), + ("KeyPress", "Key", "Changed"), ("KeyRelease",), ("ButtonPress", "Button"), ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",), ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",), ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",), diff -r 601a08fcb507 text-without-multicall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/text-without-multicall.py Tue Jun 17 18:28:18 2014 +0530 @@ -0,0 +1,27 @@ +from idlelib.LineNumber import LineNumberCanvas, Text +import tkinter as tk + +class EditorWindow(tk.Frame): + def __init__(self, *args, **kwargs): + tk.Frame.__init__(self, *args, **kwargs) + self.text = Text(self) + self.scrollbar = tk.Scrollbar(orient="vertical", command=self.text.yview) + + self.text.configure(yscrollcommand=self.scrollbar.set) + + self.linenumbers = LineNumberCanvas(self, width=40) + self.linenumbers.connect(self.text) + + self.scrollbar.pack(side="right", fill="y") + self.linenumbers.pack(side="left", fill="y") + + self.text.bind("<<Changed>>", self.linenumbers.re_render) + self.text.bind("<Configure>", self.linenumbers.re_render) + self.text.pack(side="right", fill="both", expand=True) + for i in range(1000): + self.text.insert('end','sample text'+'\n') + +root = tk.Tk() +editwin = EditorWindow() +editwin.pack() +root.mainloop()
_______________________________________________ IDLE-dev mailing list IDLE-dev@python.org https://mail.python.org/mailman/listinfo/idle-dev