A Text Widget With Line Numbers
Here is a prototype of a tkinter text widget that displays line numbers and copes well with wrapped lines.
I have taken a lazy way out and update the line numbers using a periodic interrupt. This saves having to monitor every action on the text widget to see if the line numbers have changed.
The line numbers are displayed in a separate Text widget, so normal editing operations on the main widget do not have to take account of line numbers.
Screenshot showing three instances of the widget in a PanedWindow
1 """A wrapper to show line numbers for Tkinter Text widgets.
2
3 Features:
4
5 - Line numbers are displayed in a seperate Text widget.
6
7 - Line number display is entirely automatic.
8
9 - Text in the main Text widget can be manipulated normally,
10 without regard to line numbers.
11
12 - Line numbers are displayed correctly for wrapped lines.
13
14 Drawbacks:
15
16 - The height of each line in the main Text widget must all
17 be the same.
18
19 - The height of the lines in the line number display Text
20 widget must be the same as for the main Text widget.
21
22 - There is a slight delay in line numbers catching up with reality.
23 This is most noticable when fast scrolling is taking place.
24
25 """
26
27 __version__ = 0.2
28 __date__ = "2009-07-25"
29 __author__ = "robert@pytrash.co.uk"
30 __licence__ = "Public Domain"
31
32
33 __changelog__ = (
34
35 ('2009-07-25', '0.2', 'PyTrash',
36
37 """Fixed bugs, improved efficiency, added PanedWindow to demo."""),
38
39 ('2009-07-24', '0.1', 'PyTrash',
40
41 """Initial version."""),
42
43 )
44
45
46 from Tkinter import *
47
48 root = Tk()
49
50 class EditorClass(object):
51
52 UPDATE_PERIOD = 100 #ms
53
54 editors = []
55 updateId = None
56
57 def __init__(self, master):
58
59 self.__class__.editors.append(self)
60
61 self.lineNumbers = ''
62
63 # A frame to hold the three components of the widget.
64 self.frame = Frame(master, bd=2, relief=SUNKEN)
65
66 # The widgets vertical scrollbar
67 self.vScrollbar = Scrollbar(self.frame, orient=VERTICAL)
68 self.vScrollbar.pack(fill='y', side=RIGHT)
69
70 # The Text widget holding the line numbers.
71 self.lnText = Text(self.frame,
72 width = 4,
73 padx = 4,
74 highlightthickness = 0,
75 takefocus = 0,
76 bd = 0,
77 background = 'lightgrey',
78 foreground = 'magenta',
79 state='disabled'
80 )
81 self.lnText.pack(side=LEFT, fill='y')
82
83 # The Main Text Widget
84 self.text = Text(self.frame,
85 width=16,
86 bd=0,
87 padx = 4,
88 undo=True,
89 background = 'white'
90 )
91 self.text.pack(side=LEFT, fill=BOTH, expand=1)
92
93 self.text.config(yscrollcommand=self.vScrollbar.set)
94 self.vScrollbar.config(command=self.text.yview)
95
96 if self.__class__.updateId is None:
97 self.updateAllLineNumbers()
98
99 def getLineNumbers(self):
100
101 x = 0
102 line = '0'
103 col= ''
104 ln = ''
105
106 # assume each line is at least 6 pixels high
107 step = 6
108
109 nl = '\n'
110 lineMask = ' %s\n'
111 indexMask = '@0,%d'
112
113 for i in range(0, self.text.winfo_height(), step):
114
115 ll, cc = self.text.index( indexMask % i).split('.')
116
117 if line == ll:
118 if col != cc:
119 col = cc
120 ln += nl
121 else:
122 line, col = ll, cc
123 ln += (lineMask % line)[-5:]
124
125 return ln
126
127 def updateLineNumbers(self):
128
129 tt = self.lnText
130 ln = self.getLineNumbers()
131 if self.lineNumbers != ln:
132 self.lineNumbers = ln
133 tt.config(state='normal')
134 tt.delete('1.0', END)
135 tt.insert('1.0', self.lineNumbers)
136 tt.config(state='disabled')
137
138 @classmethod
139 def updateAllLineNumbers(cls):
140
141 if len(cls.editors) < 1:
142 cls.updateId = None
143 return
144
145 for ed in cls.editors:
146 ed.updateLineNumbers()
147
148 cls.updateId = ed.text.after(
149 cls.UPDATE_PERIOD,
150 cls.updateAllLineNumbers)
151
152
153 def demo(noOfEditors, noOfLines):
154
155 pane = PanedWindow(root, orient=HORIZONTAL, opaqueresize=True)
156
157 for e in range(noOfEditors):
158 ed = EditorClass(root)
159 pane.add(ed.frame)
160
161 s = 'line ................................... %s'
162 s = '\n'.join( s%i for i in xrange(1, noOfLines+1) )
163
164 for ed in EditorClass.editors:
165 ed.text.insert(END, s)
166
167 pane.pack(fill='both', expand=1)
168
169 root.title("Example - Line Numbers For Text Widgets")
170
171
172 if __name__ == '__main__':
173
174 demo(3, 9999)
175 mainloop()
176