[Idle-dev] IDLE Line numbering initial plan

Saimadhav Heblikar saimadhavheblikar at gmail.com
Tue Jun 17 15:01:49 CEST 2014


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
-------------- next part --------------
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()


More information about the IDLE-dev mailing list