Re: IDLE Line numbering initial plan

Saimadhav Heblikar <[email protected]>
Newsgroups gmane.comp.python.idle
Message-ID <CAO3PiBjUdjPFDa6NXVto=iSEL2jHAQkk9nNB-hFvjOQxEvD+9w@mail.gmail.com>
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

_______________________________________________
IDLE-dev mailing list
[email protected]
https://mail.python.org/mailman/listinfo/idle-dev
linenumber-v1.diff (text/plain, 5.8 KB)
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()
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.