blob: 6e1d6f6ef944ebefb54ea16e79c91c1ac16609ef [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00005from itertools import count
Georg Brandl6634bf22008-05-20 07:13:37 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00009from MultiCall import MultiCallCreator
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000010
11import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000012import idlever
13import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000014import SearchDialog
15import GrepDialog
16import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000017import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000018from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000019import aboutDialog, textView, configDialog
Ronald Oussoren19302d92006-06-11 14:33:36 +000020import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000021
22# The default tab setting for a Text widget, in average-width characters.
23TK_TABWIDTH_DEFAULT = 8
24
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000025def _find_module(fullname, path=None):
26 """Version of imp.find_module() that handles hierarchical module names"""
27
28 file = None
29 for tgt in fullname.split('.'):
30 if file is not None:
31 file.close() # close intermediate files
32 (file, filename, descr) = imp.find_module(tgt, path)
33 if descr[2] == imp.PY_SOURCE:
34 break # find but not load the source file
35 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000036 try:
37 path = module.__path__
38 except AttributeError:
39 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000040 return file, filename, descr
41
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000042class EditorWindow(object):
David Scherer7aced172000-08-15 01:13:23 +000043 from Percolator import Percolator
44 from ColorDelegator import ColorDelegator
45 from UndoDelegator import UndoDelegator
Martin v. Löwis307021f2005-11-27 16:59:04 +000046 from IOBinding import IOBinding, filesystemencoding, encoding
David Scherer7aced172000-08-15 01:13:23 +000047 import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +000048 from Tkinter import Toplevel
David Scherer7aced172000-08-15 01:13:23 +000049 from MultiStatusBar import MultiStatusBar
50
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000051 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000052
53 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000054 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000055 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000056 if sys.platform.count('linux'):
57 # look for html docs in a couple of standard places
58 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
59 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
60 dochome = '/var/www/html/python/index.html'
61 else:
62 basepath = '/usr/share/doc/' # standard location
63 dochome = os.path.join(basepath, pyver,
64 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000065 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000066 chmfile = os.path.join(sys.prefix, 'Doc',
67 'Python%d%d.chm' % sys.version_info[:2])
Thomas Heller84ef1532003-09-23 20:53:10 +000068 if os.path.isfile(chmfile):
69 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +000070
71 elif macosxSupport.runningAsOSXApp():
72 # documentation is stored inside the python framework
73 dochome = os.path.join(sys.prefix,
74 'Resources/English.lproj/Documentation/index.html')
75
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000076 dochome = os.path.normpath(dochome)
77 if os.path.isfile(dochome):
78 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +000079 if sys.platform == 'darwin':
80 # Safari requires real file:-URLs
81 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000082 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +000083 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000084 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000085 self.flist = flist
86 root = root or flist.root
87 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +000088 try:
89 sys.ps1
90 except AttributeError:
91 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +000092 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +000093 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000094 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000095 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000096 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000097 #configDialog.py so it can access all EditorWindow instaces
Kurt B. Kaisera2946a42006-07-24 18:05:51 +000098 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000099 else:
100 self.tkinter_vars = {} # keys: Tkinter event names
101 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000102 self.top.instance_dict = {}
103 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000104 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000105 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000106 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000107 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000108 text_options = {
109 'name': 'text',
110 'padx': 5,
111 'wrap': 'none',
112 'width': self.width,
113 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
114 if TkVersion >= 8.5:
115 # Starting with tk 8.5 we have to set the new tabstyle option
116 # to 'wordprocessor' to achieve the same display of tabs as in
117 # older tk versions.
118 text_options['tabstyle'] = 'wordprocessor'
119 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000120 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000121
122 self.createmenubar()
123 self.apply_bindings()
124
125 self.top.protocol("WM_DELETE_WINDOW", self.close)
126 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000127 if macosxSupport.runningAsOSXApp():
128 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000129 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000130 text.bind("<<cut>>", self.cut)
131 text.bind("<<copy>>", self.copy)
132 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000133 text.bind("<<center-insert>>", self.center_insert_event)
134 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000135 text.bind("<<python-docs>>", self.python_docs)
136 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000137 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000138 text.bind("<<open-module>>", self.open_module)
139 text.bind("<<do-nothing>>", lambda event: "break")
140 text.bind("<<select-all>>", self.select_all)
141 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000142 text.bind("<<find>>", self.find_event)
143 text.bind("<<find-again>>", self.find_again_event)
144 text.bind("<<find-in-files>>", self.find_in_files_event)
145 text.bind("<<find-selection>>", self.find_selection_event)
146 text.bind("<<replace>>", self.replace_event)
147 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000148 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000149 text.bind("<<smart-backspace>>",self.smart_backspace_event)
150 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
151 text.bind("<<smart-indent>>",self.smart_indent_event)
152 text.bind("<<indent-region>>",self.indent_region_event)
153 text.bind("<<dedent-region>>",self.dedent_region_event)
154 text.bind("<<comment-region>>",self.comment_region_event)
155 text.bind("<<uncomment-region>>",self.uncomment_region_event)
156 text.bind("<<tabify-region>>",self.tabify_region_event)
157 text.bind("<<untabify-region>>",self.untabify_region_event)
158 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
159 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000160 text.bind("<Left>", self.move_at_edge_if_selection(0))
161 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000162 text.bind("<<del-word-left>>", self.del_word_left)
163 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000164 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000165
David Scherer7aced172000-08-15 01:13:23 +0000166 if flist:
167 flist.inversedict[self] = key
168 if key:
169 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000170 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000171 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
172 text.bind("<<open-class-browser>>", self.open_class_browser)
173 text.bind("<<open-path-browser>>", self.open_path_browser)
174
Steven M. Gava898a3652001-10-07 11:10:44 +0000175 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000176 vbar['command'] = text.yview
177 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000178 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000179 fontWeight = 'normal'
180 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000181 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000182 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
183 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
184 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000185 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
186 text.pack(side=TOP, fill=BOTH, expand=1)
187 text.focus_set()
188
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000189 # usetabs true -> literal tab characters are used by indent and
190 # dedent cmds, possibly mixed with spaces if
191 # indentwidth is not a multiple of tabwidth,
192 # which will cause Tabnanny to nag!
193 # false -> tab characters are converted to spaces by indent
194 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000195 # Although use-spaces=0 can be configured manually in config-main.def,
196 # configuration of tabs v. spaces is not supported in the configuration
197 # dialog. IDLE promotes the preferred Python indentation: use spaces!
198 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
199 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000200
201 # tabwidth is the display width of a literal tab character.
202 # CAUTION: telling Tk to use anything other than its default
203 # tab setting causes it to use an entirely different tabbing algorithm,
204 # treating tab stops as fixed distances from the left margin.
205 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000206 self.tabwidth = 8 # must remain 8 until Tk is fixed.
207
208 # indentwidth is the number of screen characters per indent level.
209 # The recommended Python indentation is four spaces.
210 self.indentwidth = self.tabwidth
211 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000212
213 # If context_use_ps1 is true, parsing searches back for a ps1 line;
214 # else searches for a popular (if, def, ...) Python stmt.
215 self.context_use_ps1 = False
216
217 # When searching backwards for a reliable place to begin parsing,
218 # first start num_context_lines[0] lines back, then
219 # num_context_lines[1] lines back if that didn't work, and so on.
220 # The last value should be huge (larger than the # of lines in a
221 # conceivable file).
222 # Making the initial values larger slows things down more often.
223 self.num_context_lines = 50, 500, 5000000
224
David Scherer7aced172000-08-15 01:13:23 +0000225 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000226
227 self.undo = undo = self.UndoDelegator()
228 per.insertfilter(undo)
229 text.undo_block_start = undo.undo_block_start
230 text.undo_block_stop = undo.undo_block_stop
231 undo.set_saved_change_hook(self.saved_change_hook)
232
233 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000234 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000235 io.set_filename_change_hook(self.filename_change_hook)
236
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000237 # Create the recent files submenu
238 self.recent_files_menu = Menu(self.menubar)
239 self.menudict['file'].insert_cascade(3, label='Recent Files',
240 underline=0,
241 menu=self.recent_files_menu)
242 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000243
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000244 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000245 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000246 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000247 io.loadfile(filename)
248 else:
249 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000250 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000251 self.saved_change_hook()
252
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000253 self.set_indentation_params(self.ispythonsource(filename))
254
David Scherer7aced172000-08-15 01:13:23 +0000255 self.load_extensions()
256
257 menu = self.menudict.get('windows')
258 if menu:
259 end = menu.index("end")
260 if end is None:
261 end = -1
262 if end >= 0:
263 menu.add_separator()
264 end = end + 1
265 self.wmenu_end = end
266 WindowList.register_callback(self.postwindowsmenu)
267
268 # Some abstractions so IDLE extensions are cross-IDE
269 self.askyesno = tkMessageBox.askyesno
270 self.askinteger = tkSimpleDialog.askinteger
271 self.showerror = tkMessageBox.showerror
272
Martin v. Löwis307021f2005-11-27 16:59:04 +0000273 def _filename_to_unicode(self, filename):
274 """convert filename to unicode in order to display it in Tk"""
275 if isinstance(filename, unicode) or not filename:
276 return filename
277 else:
278 try:
279 return filename.decode(self.filesystemencoding)
280 except UnicodeDecodeError:
281 # XXX
282 try:
283 return filename.decode(self.encoding)
284 except UnicodeDecodeError:
285 # byte-to-byte conversion
286 return filename.decode('iso8859-1')
287
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000288 def new_callback(self, event):
289 dirname, basename = self.io.defaultfilename()
290 self.flist.new(dirname)
291 return "break"
292
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000293 def home_callback(self, event):
294 if (event.state & 12) != 0 and event.keysym == "Home":
295 # state&1==shift, state&4==control, state&8==alt
296 return # <Modifier-Home>; fall back to class binding
297
298 if self.text.index("iomark") and \
299 self.text.compare("iomark", "<=", "insert lineend") and \
300 self.text.compare("insert linestart", "<=", "iomark"):
301 insertpt = int(self.text.index("iomark").split(".")[1])
302 else:
303 line = self.text.get("insert linestart", "insert lineend")
304 for insertpt in xrange(len(line)):
305 if line[insertpt] not in (' ','\t'):
306 break
307 else:
308 insertpt=len(line)
309
310 lineat = int(self.text.index("insert").split('.')[1])
311
312 if insertpt == lineat:
313 insertpt = 0
314
315 dest = "insert linestart+"+str(insertpt)+"c"
316
317 if (event.state&1) == 0:
318 # shift not pressed
319 self.text.tag_remove("sel", "1.0", "end")
320 else:
321 if not self.text.index("sel.first"):
322 self.text.mark_set("anchor","insert")
323
324 first = self.text.index(dest)
325 last = self.text.index("anchor")
326
327 if self.text.compare(first,">",last):
328 first,last = last,first
329
330 self.text.tag_remove("sel", "1.0", "end")
331 self.text.tag_add("sel", first, last)
332
333 self.text.mark_set("insert", dest)
334 self.text.see("insert")
335 return "break"
336
David Scherer7aced172000-08-15 01:13:23 +0000337 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000338 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000339 if macosxSupport.runningAsOSXApp():
340 # Insert some padding to avoid obscuring some of the statusbar
341 # by the resize widget.
342 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000343 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
344 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
345 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000346 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
347 self.text.event_add("<<set-line-and-column>>",
348 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000349 self.text.after_idle(self.set_line_and_column)
350
351 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000352 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000353 self.status_bar.set_label('column', 'Col: %s' % column)
354 self.status_bar.set_label('line', 'Ln: %s' % line)
355
David Scherer7aced172000-08-15 01:13:23 +0000356 menu_specs = [
357 ("file", "_File"),
358 ("edit", "_Edit"),
359 ("format", "F_ormat"),
360 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000361 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000362 ("windows", "_Windows"),
363 ("help", "_Help"),
364 ]
365
Ronald Oussoren19302d92006-06-11 14:33:36 +0000366 if macosxSupport.runningAsOSXApp():
367 del menu_specs[-3]
368 menu_specs[-2] = ("windows", "_Window")
369
370
David Scherer7aced172000-08-15 01:13:23 +0000371 def createmenubar(self):
372 mbar = self.menubar
373 self.menudict = menudict = {}
374 for name, label in self.menu_specs:
375 underline, label = prepstr(label)
376 menudict[name] = menu = Menu(mbar, name=name)
377 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000378
Ronald Oussorena97063a2009-03-04 21:35:05 +0000379 if macosxSupport.runningAsOSXApp():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000380 # Insert the application menu
381 menudict['application'] = menu = Menu(mbar, name='apple')
382 mbar.add_cascade(label='IDLE', menu=menu)
383
David Scherer7aced172000-08-15 01:13:23 +0000384 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000385 self.base_helpmenu_length = self.menudict['help'].index(END)
386 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000387
388 def postwindowsmenu(self):
389 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000390 menu = self.menudict['windows']
391 end = menu.index("end")
392 if end is None:
393 end = -1
394 if end > self.wmenu_end:
395 menu.delete(self.wmenu_end+1, end)
396 WindowList.add_windows_to_menu(menu)
397
398 rmenu = None
399
400 def right_menu_event(self, event):
401 self.text.tag_remove("sel", "1.0", "end")
402 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
403 if not self.rmenu:
404 self.make_rmenu()
405 rmenu = self.rmenu
406 self.event = event
407 iswin = sys.platform[:3] == 'win'
408 if iswin:
409 self.text.config(cursor="arrow")
410 rmenu.tk_popup(event.x_root, event.y_root)
411 if iswin:
412 self.text.config(cursor="ibeam")
413
414 rmenu_specs = [
415 # ("Label", "<<virtual-event>>"), ...
416 ("Close", "<<close-window>>"), # Example
417 ]
418
419 def make_rmenu(self):
420 rmenu = Menu(self.text, tearoff=0)
421 for label, eventname in self.rmenu_specs:
422 def command(text=self.text, eventname=eventname):
423 text.event_generate(eventname)
424 rmenu.add_command(label=label, command=command)
425 self.rmenu = rmenu
426
427 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000428 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000429
Steven M. Gava3b55a892001-11-21 05:56:26 +0000430 def config_dialog(self, event=None):
431 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000432
David Scherer7aced172000-08-15 01:13:23 +0000433 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000434 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiserd5f49102007-10-04 02:53:07 +0000435 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000436
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000437 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000438 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000439 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000440 else:
441 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000442 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000443
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000444 def cut(self,event):
445 self.text.event_generate("<<Cut>>")
446 return "break"
447
448 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000449 if not self.text.tag_ranges("sel"):
450 # There is no selection, so do nothing and maybe interrupt.
451 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000452 self.text.event_generate("<<Copy>>")
453 return "break"
454
455 def paste(self,event):
456 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000457 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000458 return "break"
459
David Scherer7aced172000-08-15 01:13:23 +0000460 def select_all(self, event=None):
461 self.text.tag_add("sel", "1.0", "end-1c")
462 self.text.mark_set("insert", "1.0")
463 self.text.see("insert")
464 return "break"
465
466 def remove_selection(self, event=None):
467 self.text.tag_remove("sel", "1.0", "end")
468 self.text.see("insert")
469
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000470 def move_at_edge_if_selection(self, edge_index):
471 """Cursor move begins at start or end of selection
472
473 When a left/right cursor key is pressed create and return to Tkinter a
474 function which causes a cursor move from the associated edge of the
475 selection.
476
477 """
478 self_text_index = self.text.index
479 self_text_mark_set = self.text.mark_set
480 edges_table = ("sel.first+1c", "sel.last-1c")
481 def move_at_edge(event):
482 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
483 try:
484 self_text_index("sel.first")
485 self_text_mark_set("insert", edges_table[edge_index])
486 except TclError:
487 pass
488 return move_at_edge
489
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000490 def del_word_left(self, event):
491 self.text.event_generate('<Meta-Delete>')
492 return "break"
493
494 def del_word_right(self, event):
495 self.text.event_generate('<Meta-d>')
496 return "break"
497
Steven M. Gavac5976402002-01-04 03:06:08 +0000498 def find_event(self, event):
499 SearchDialog.find(self.text)
500 return "break"
501
502 def find_again_event(self, event):
503 SearchDialog.find_again(self.text)
504 return "break"
505
506 def find_selection_event(self, event):
507 SearchDialog.find_selection(self.text)
508 return "break"
509
510 def find_in_files_event(self, event):
511 GrepDialog.grep(self.text, self.io, self.flist)
512 return "break"
513
514 def replace_event(self, event):
515 ReplaceDialog.replace(self.text)
516 return "break"
517
518 def goto_line_event(self, event):
519 text = self.text
520 lineno = tkSimpleDialog.askinteger("Goto",
521 "Go to line number:",parent=text)
522 if lineno is None:
523 return "break"
524 if lineno <= 0:
525 text.bell()
526 return "break"
527 text.mark_set("insert", "%d.0" % lineno)
528 text.see("insert")
529
David Scherer7aced172000-08-15 01:13:23 +0000530 def open_module(self, event=None):
531 # XXX Shouldn't this be in IOBinding or in FileList?
532 try:
533 name = self.text.get("sel.first", "sel.last")
534 except TclError:
535 name = ""
536 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000537 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000538 name = tkSimpleDialog.askstring("Module",
539 "Enter the name of a Python module\n"
540 "to search on sys.path and open:",
541 parent=self.text, initialvalue=name)
542 if name:
543 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000544 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000545 return
David Scherer7aced172000-08-15 01:13:23 +0000546 # XXX Ought to insert current file's directory in front of path
547 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000548 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000549 except (NameError, ImportError), msg:
550 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
551 return
552 if type != imp.PY_SOURCE:
553 tkMessageBox.showerror("Unsupported type",
554 "%s is not a source module" % name, parent=self.text)
555 return
556 if f:
557 f.close()
558 if self.flist:
559 self.flist.open(file)
560 else:
561 self.io.loadfile(file)
562
563 def open_class_browser(self, event=None):
564 filename = self.io.filename
565 if not filename:
566 tkMessageBox.showerror(
567 "No filename",
568 "This buffer has no associated filename",
569 master=self.text)
570 self.text.focus_set()
571 return None
572 head, tail = os.path.split(filename)
573 base, ext = os.path.splitext(tail)
574 import ClassBrowser
575 ClassBrowser.ClassBrowser(self.flist, base, [head])
576
577 def open_path_browser(self, event=None):
578 import PathBrowser
579 PathBrowser.PathBrowser(self.flist)
580
581 def gotoline(self, lineno):
582 if lineno is not None and lineno > 0:
583 self.text.mark_set("insert", "%d.0" % lineno)
584 self.text.tag_remove("sel", "1.0", "end")
585 self.text.tag_add("sel", "insert", "insert +1l")
586 self.center()
587
588 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000589 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000590 return True
David Scherer7aced172000-08-15 01:13:23 +0000591 base, ext = os.path.splitext(os.path.basename(filename))
592 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000593 return True
David Scherer7aced172000-08-15 01:13:23 +0000594 try:
595 f = open(filename)
596 line = f.readline()
597 f.close()
598 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000599 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000600 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000601
602 def close_hook(self):
603 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000604 self.flist.unregister_maybe_terminate(self)
605 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000606
607 def set_close_hook(self, close_hook):
608 self.close_hook = close_hook
609
610 def filename_change_hook(self):
611 if self.flist:
612 self.flist.filename_changed_edit(self)
613 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000614 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000615 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000616
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000617 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000618 if self.color:
619 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000620 if self.ispythonsource(self.io.filename):
621 self.color = self.ColorDelegator()
622 # can add more colorizers here...
623 if self.color:
624 self.per.removefilter(self.undo)
625 self.per.insertfilter(self.color)
626 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000627
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000628 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000629 if not self.color:
630 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000631 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000632 self.per.removefilter(self.color)
633 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000634
Steven M. Gavab77d3432002-03-02 07:16:21 +0000635 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000636 "Update the colour theme"
637 # Called from self.filename_change_hook and from configDialog.py
638 self._rmcolorizer()
639 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000640 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000641 normal_colors = idleConf.GetHighlight(theme, 'normal')
642 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
643 select_colors = idleConf.GetHighlight(theme, 'hilite')
644 self.text.config(
645 foreground=normal_colors['foreground'],
646 background=normal_colors['background'],
647 insertbackground=cursor_color,
648 selectforeground=select_colors['foreground'],
649 selectbackground=select_colors['background'],
650 )
David Scherer7aced172000-08-15 01:13:23 +0000651
Steven M. Gavab1585412002-03-12 00:21:56 +0000652 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000653 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000654 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000655 fontWeight='normal'
656 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
657 fontWeight='bold'
658 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
659 idleConf.GetOption('main','EditorWindow','font-size'),
660 fontWeight))
661
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000662 def RemoveKeybindings(self):
663 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000664 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000665 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000666 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000667 self.text.event_delete(event, *keylist)
668 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000669 xkeydefs = idleConf.GetExtensionBindings(extensionName)
670 if xkeydefs:
671 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000672 self.text.event_delete(event, *keylist)
673
674 def ApplyKeybindings(self):
675 "Update the keybindings after they are changed"
676 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000677 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000678 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000679 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000680 xkeydefs = idleConf.GetExtensionBindings(extensionName)
681 if xkeydefs:
682 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000683 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000684 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000685 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000686 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000687 for item in menu[1]:
688 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000689 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000690 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000691 menu = self.menudict[menubarItem]
692 end = menu.index(END) + 1
693 for index in range(0, end):
694 if menu.type(index) == 'command':
695 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000696 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000697 itemName = menu.entrycget(index, 'label')
698 event = ''
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000699 if menuEventDict.has_key(menubarItem):
700 if menuEventDict[menubarItem].has_key(itemName):
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000701 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000702 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 accel = get_accelerator(keydefs, event)
704 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000705
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000706 def set_notabs_indentwidth(self):
707 "Update the indentwidth if changed and not using tabs in this window"
708 # Called from configDialog.py
709 if not self.usetabs:
710 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
711 type='int')
712
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000713 def reset_help_menu_entries(self):
714 "Update the additional help entries on the Help menu"
715 help_list = idleConf.GetAllExtraHelpSourcesList()
716 helpmenu = self.menudict['help']
717 # first delete the extra help entries, if any
718 helpmenu_length = helpmenu.index(END)
719 if helpmenu_length > self.base_helpmenu_length:
720 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
721 # then rebuild them
722 if help_list:
723 helpmenu.add_separator()
724 for entry in help_list:
725 cmd = self.__extra_help_callback(entry[1])
726 helpmenu.add_command(label=entry[0], command=cmd)
727 # and update the menu dictionary
728 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000729
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000730 def __extra_help_callback(self, helpfile):
731 "Create a callback with the helpfile value frozen at definition time"
732 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000733 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000734 url = os.path.normpath(helpfile)
735 if sys.platform[:3] == 'win':
736 os.startfile(helpfile)
737 else:
738 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000739 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000740
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000741 def update_recent_files_list(self, new_file=None):
742 "Load and update the recent files list and menus"
743 rf_list = []
744 if os.path.exists(self.recent_files_path):
745 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000746 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000747 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000748 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000749 rf_list_file.close()
750 if new_file:
751 new_file = os.path.abspath(new_file) + '\n'
752 if new_file in rf_list:
753 rf_list.remove(new_file) # move to top
754 rf_list.insert(0, new_file)
755 # clean and save the recent files list
756 bad_paths = []
757 for path in rf_list:
758 if '\0' in path or not os.path.exists(path[0:-1]):
759 bad_paths.append(path)
760 rf_list = [path for path in rf_list if path not in bad_paths]
761 ulchars = "1234567890ABCDEFGHIJK"
762 rf_list = rf_list[0:len(ulchars)]
763 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000764 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000765 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000766 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000767 rf_file.close()
768 # for each edit window instance, construct the recent files menu
769 for instance in self.top.instance_dict.keys():
770 menu = instance.recent_files_menu
771 menu.delete(1, END) # clear, and rebuild:
772 for i, file in zip(count(), rf_list):
773 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000774 # make unicode string to display non-ASCII chars correctly
775 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000776 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000777 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000778 command=callback,
779 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000780
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000781 def __recent_file_callback(self, file_name):
782 def open_recent_file(fn_closure=file_name):
783 self.io.open(editFile=fn_closure)
784 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000785
David Scherer7aced172000-08-15 01:13:23 +0000786 def saved_change_hook(self):
787 short = self.short_title()
788 long = self.long_title()
789 if short and long:
790 title = short + " - " + long
791 elif short:
792 title = short
793 elif long:
794 title = long
795 else:
796 title = "Untitled"
797 icon = short or long or title
798 if not self.get_saved():
799 title = "*%s*" % title
800 icon = "*%s" % icon
801 self.top.wm_title(title)
802 self.top.wm_iconname(icon)
803
804 def get_saved(self):
805 return self.undo.get_saved()
806
807 def set_saved(self, flag):
808 self.undo.set_saved(flag)
809
810 def reset_undo(self):
811 self.undo.reset_undo()
812
813 def short_title(self):
814 filename = self.io.filename
815 if filename:
816 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000817 # return unicode string to display non-ASCII chars correctly
818 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000819
820 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000821 # return unicode string to display non-ASCII chars correctly
822 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000823
824 def center_insert_event(self, event):
825 self.center()
826
827 def center(self, mark="insert"):
828 text = self.text
829 top, bot = self.getwindowlines()
830 lineno = self.getlineno(mark)
831 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000832 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000833 text.yview(float(newtop))
834
835 def getwindowlines(self):
836 text = self.text
837 top = self.getlineno("@0,0")
838 bot = self.getlineno("@0,65535")
839 if top == bot and text.winfo_height() == 1:
840 # Geometry manager hasn't run yet
841 height = int(text['height'])
842 bot = top + height - 1
843 return top, bot
844
845 def getlineno(self, mark="insert"):
846 text = self.text
847 return int(float(text.index(mark)))
848
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000849 def get_geometry(self):
850 "Return (width, height, x, y)"
851 geom = self.top.wm_geometry()
852 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
853 tuple = (map(int, m.groups()))
854 return tuple
855
David Scherer7aced172000-08-15 01:13:23 +0000856 def close_event(self, event):
857 self.close()
858
859 def maybesave(self):
860 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000861 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000862 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000863 self.top.deiconify()
864 self.top.lower()
865 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000866 return self.io.maybesave()
867
868 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000869 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000870 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000871 self._close()
872 return reply
873
874 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000875 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000876 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000877 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000878 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000879 self.io.close()
880 self.io = None
881 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000882 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000883 self.color.close(False)
884 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000885 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000886 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000887 self.per.close()
888 self.per = None
889 self.top.destroy()
890 if self.close_hook:
891 # unless override: unregister from flist, terminate if last window
892 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000893
894 def load_extensions(self):
895 self.extensions = {}
896 self.load_standard_extensions()
897
898 def unload_extensions(self):
899 for ins in self.extensions.values():
900 if hasattr(ins, "close"):
901 ins.close()
902 self.extensions = {}
903
904 def load_standard_extensions(self):
905 for name in self.get_standard_extension_names():
906 try:
907 self.load_extension(name)
908 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000909 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000910 import traceback
911 traceback.print_exc()
912
913 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000914 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000915
916 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000917 try:
918 mod = __import__(name, globals(), locals(), [])
919 except ImportError:
920 print "\nFailed to import extension: ", name
921 return
David Scherer7aced172000-08-15 01:13:23 +0000922 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000923 keydefs = idleConf.GetExtensionBindings(name)
924 if hasattr(cls, "menudefs"):
925 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000926 ins = cls(self)
927 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000928 if keydefs:
929 self.apply_bindings(keydefs)
930 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000931 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000932 while methodname[:1] == '<':
933 methodname = methodname[1:]
934 while methodname[-1:] == '>':
935 methodname = methodname[:-1]
936 methodname = methodname + "_event"
937 if hasattr(ins, methodname):
938 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000939
940 def apply_bindings(self, keydefs=None):
941 if keydefs is None:
942 keydefs = self.Bindings.default_keydefs
943 text = self.text
944 text.keydefs = keydefs
945 for event, keylist in keydefs.items():
946 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000947 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000948
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000949 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000950 """Add appropriate entries to the menus and submenus
951
952 Menus that are absent or None in self.menudict are ignored.
953 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000954 if menudefs is None:
955 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000956 if keydefs is None:
957 keydefs = self.Bindings.default_keydefs
958 menudict = self.menudict
959 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000960 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000961 menu = menudict.get(mname)
962 if not menu:
963 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000964 for entry in entrylist:
965 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000966 menu.add_separator()
967 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000968 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000969 checkbutton = (label[:1] == '!')
970 if checkbutton:
971 label = label[1:]
972 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000973 accelerator = get_accelerator(keydefs, eventname)
974 def command(text=text, eventname=eventname):
975 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000976 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000977 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000978 menu.add_checkbutton(label=label, underline=underline,
979 command=command, accelerator=accelerator,
980 variable=var)
981 else:
982 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000983 command=command,
984 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000985
986 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000987 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000988 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000989 value = var.get()
990 return value
991 else:
992 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +0000993
994 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000995 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000996 if var:
997 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000998 else:
999 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001000
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001001 def get_var_obj(self, name, vartype=None):
1002 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001003 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001004 # create a Tkinter variable object with self.text as master:
1005 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001006 return var
1007
1008 # Tk implementations of "virtual text methods" -- each platform
1009 # reusing IDLE's support code needs to define these for its GUI's
1010 # flavor of widget.
1011
1012 # Is character at text_index in a Python string? Return 0 for
1013 # "guaranteed no", true for anything else. This info is expensive
1014 # to compute ab initio, but is probably already known by the
1015 # platform's colorizer.
1016
1017 def is_char_in_string(self, text_index):
1018 if self.color:
1019 # Return true iff colorizer hasn't (re)gotten this far
1020 # yet, or the character is tagged as being in a string
1021 return self.text.tag_prevrange("TODO", text_index) or \
1022 "STRING" in self.text.tag_names(text_index)
1023 else:
1024 # The colorizer is missing: assume the worst
1025 return 1
1026
1027 # If a selection is defined in the text widget, return (start,
1028 # end) as Tkinter text indices, otherwise return (None, None)
1029 def get_selection_indices(self):
1030 try:
1031 first = self.text.index("sel.first")
1032 last = self.text.index("sel.last")
1033 return first, last
1034 except TclError:
1035 return None, None
1036
1037 # Return the text widget's current view of what a tab stop means
1038 # (equivalent width in spaces).
1039
1040 def get_tabwidth(self):
1041 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1042 return int(current)
1043
1044 # Set the text widget's current view of what a tab stop means.
1045
1046 def set_tabwidth(self, newtabwidth):
1047 text = self.text
1048 if self.get_tabwidth() != newtabwidth:
1049 pixels = text.tk.call("font", "measure", text["font"],
1050 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001051 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001052 text.configure(tabs=pixels)
1053
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001054 # If ispythonsource and guess are true, guess a good value for
1055 # indentwidth based on file content (if possible), and if
1056 # indentwidth != tabwidth set usetabs false.
1057 # In any case, adjust the Text widget's view of what a tab
1058 # character means.
1059
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001060 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001061 if guess and ispythonsource:
1062 i = self.guess_indent()
1063 if 2 <= i <= 8:
1064 self.indentwidth = i
1065 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001066 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001067 self.set_tabwidth(self.tabwidth)
1068
1069 def smart_backspace_event(self, event):
1070 text = self.text
1071 first, last = self.get_selection_indices()
1072 if first and last:
1073 text.delete(first, last)
1074 text.mark_set("insert", first)
1075 return "break"
1076 # Delete whitespace left, until hitting a real char or closest
1077 # preceding virtual tab stop.
1078 chars = text.get("insert linestart", "insert")
1079 if chars == '':
1080 if text.compare("insert", ">", "1.0"):
1081 # easy: delete preceding newline
1082 text.delete("insert-1c")
1083 else:
1084 text.bell() # at start of buffer
1085 return "break"
1086 if chars[-1] not in " \t":
1087 # easy: delete preceding real char
1088 text.delete("insert-1c")
1089 return "break"
1090 # Ick. It may require *inserting* spaces if we back up over a
1091 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001092 tabwidth = self.tabwidth
1093 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001094 assert have > 0
1095 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001096 # Debug prompt is multilined....
1097 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001098 ncharsdeleted = 0
1099 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001100 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001101 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001102 chars = chars[:-1]
1103 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001104 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001105 if have <= want or chars[-1] not in " \t":
1106 break
1107 text.undo_block_start()
1108 text.delete("insert-%dc" % ncharsdeleted, "insert")
1109 if have < want:
1110 text.insert("insert", ' ' * (want - have))
1111 text.undo_block_stop()
1112 return "break"
1113
1114 def smart_indent_event(self, event):
1115 # if intraline selection:
1116 # delete it
1117 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001118 # do indent-region
1119 # else:
1120 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001121 text = self.text
1122 first, last = self.get_selection_indices()
1123 text.undo_block_start()
1124 try:
1125 if first and last:
1126 if index2line(first) != index2line(last):
1127 return self.indent_region_event(event)
1128 text.delete(first, last)
1129 text.mark_set("insert", first)
1130 prefix = text.get("insert linestart", "insert")
1131 raw, effective = classifyws(prefix, self.tabwidth)
1132 if raw == len(prefix):
1133 # only whitespace to the left
1134 self.reindent_to(effective + self.indentwidth)
1135 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001136 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001137 if self.usetabs:
1138 pad = '\t'
1139 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001140 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001141 n = self.indentwidth
1142 pad = ' ' * (n - effective % n)
1143 text.insert("insert", pad)
1144 text.see("insert")
1145 return "break"
1146 finally:
1147 text.undo_block_stop()
1148
1149 def newline_and_indent_event(self, event):
1150 text = self.text
1151 first, last = self.get_selection_indices()
1152 text.undo_block_start()
1153 try:
1154 if first and last:
1155 text.delete(first, last)
1156 text.mark_set("insert", first)
1157 line = text.get("insert linestart", "insert")
1158 i, n = 0, len(line)
1159 while i < n and line[i] in " \t":
1160 i = i+1
1161 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001162 # the cursor is in or at leading indentation in a continuation
1163 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001164 text.insert("insert linestart", '\n')
1165 return "break"
1166 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001167 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001168 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001169 last_line_of_prompt = sys.ps1.split('\n')[-1]
1170 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001171 line = line[:-1]
1172 i = i+1
1173 if i:
1174 text.delete("insert - %d chars" % i, "insert")
1175 # strip whitespace after insert point
1176 while text.get("insert") in " \t":
1177 text.delete("insert")
1178 # start new line
1179 text.insert("insert", '\n')
1180
1181 # adjust indentation for continuations and block
1182 # open/close first need to find the last stmt
1183 lno = index2line(text.index('insert'))
1184 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001185 if not self.context_use_ps1:
1186 for context in self.num_context_lines:
1187 startat = max(lno - context, 1)
1188 startatindex = `startat` + ".0"
1189 rawtext = text.get(startatindex, "insert")
1190 y.set_str(rawtext)
1191 bod = y.find_good_parse_start(
1192 self.context_use_ps1,
1193 self._build_char_in_string_func(startatindex))
1194 if bod is not None or startat == 1:
1195 break
1196 y.set_lo(bod or 0)
1197 else:
1198 r = text.tag_prevrange("console", "insert")
1199 if r:
1200 startatindex = r[1]
1201 else:
1202 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001203 rawtext = text.get(startatindex, "insert")
1204 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001205 y.set_lo(0)
1206
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001207 c = y.get_continuation_type()
1208 if c != PyParse.C_NONE:
1209 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001210 if c == PyParse.C_STRING_FIRST_LINE:
1211 # after the first line of a string; do not indent at all
1212 pass
1213 elif c == PyParse.C_STRING_NEXT_LINES:
1214 # inside a string which started before this line;
1215 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001216 text.insert("insert", indent)
1217 elif c == PyParse.C_BRACKET:
1218 # line up with the first (if any) element of the
1219 # last open bracket structure; else indent one
1220 # level beyond the indent of the line with the
1221 # last open bracket
1222 self.reindent_to(y.compute_bracket_indent())
1223 elif c == PyParse.C_BACKSLASH:
1224 # if more than one line in this stmt already, just
1225 # mimic the current indent; else if initial line
1226 # has a start on an assignment stmt, indent to
1227 # beyond leftmost =; else to beyond first chunk of
1228 # non-whitespace on initial line
1229 if y.get_num_lines_in_stmt() > 1:
1230 text.insert("insert", indent)
1231 else:
1232 self.reindent_to(y.compute_backslash_indent())
1233 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001234 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 return "break"
1236
1237 # This line starts a brand new stmt; indent relative to
1238 # indentation of initial line of closest preceding
1239 # interesting stmt.
1240 indent = y.get_base_indent_string()
1241 text.insert("insert", indent)
1242 if y.is_block_opener():
1243 self.smart_indent_event(event)
1244 elif indent and y.is_block_closer():
1245 self.smart_backspace_event(event)
1246 return "break"
1247 finally:
1248 text.see("insert")
1249 text.undo_block_stop()
1250
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 # Our editwin provides a is_char_in_string function that works
1252 # with a Tk text index, but PyParse only knows about offsets into
1253 # a string. This builds a function for PyParse that accepts an
1254 # offset.
1255
1256 def _build_char_in_string_func(self, startindex):
1257 def inner(offset, _startindex=startindex,
1258 _icis=self.is_char_in_string):
1259 return _icis(_startindex + "+%dc" % offset)
1260 return inner
1261
1262 def indent_region_event(self, event):
1263 head, tail, chars, lines = self.get_region()
1264 for pos in range(len(lines)):
1265 line = lines[pos]
1266 if line:
1267 raw, effective = classifyws(line, self.tabwidth)
1268 effective = effective + self.indentwidth
1269 lines[pos] = self._make_blanks(effective) + line[raw:]
1270 self.set_region(head, tail, chars, lines)
1271 return "break"
1272
1273 def dedent_region_event(self, event):
1274 head, tail, chars, lines = self.get_region()
1275 for pos in range(len(lines)):
1276 line = lines[pos]
1277 if line:
1278 raw, effective = classifyws(line, self.tabwidth)
1279 effective = max(effective - self.indentwidth, 0)
1280 lines[pos] = self._make_blanks(effective) + line[raw:]
1281 self.set_region(head, tail, chars, lines)
1282 return "break"
1283
1284 def comment_region_event(self, event):
1285 head, tail, chars, lines = self.get_region()
1286 for pos in range(len(lines) - 1):
1287 line = lines[pos]
1288 lines[pos] = '##' + line
1289 self.set_region(head, tail, chars, lines)
1290
1291 def uncomment_region_event(self, event):
1292 head, tail, chars, lines = self.get_region()
1293 for pos in range(len(lines)):
1294 line = lines[pos]
1295 if not line:
1296 continue
1297 if line[:2] == '##':
1298 line = line[2:]
1299 elif line[:1] == '#':
1300 line = line[1:]
1301 lines[pos] = line
1302 self.set_region(head, tail, chars, lines)
1303
1304 def tabify_region_event(self, event):
1305 head, tail, chars, lines = self.get_region()
1306 tabwidth = self._asktabwidth()
1307 for pos in range(len(lines)):
1308 line = lines[pos]
1309 if line:
1310 raw, effective = classifyws(line, tabwidth)
1311 ntabs, nspaces = divmod(effective, tabwidth)
1312 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1313 self.set_region(head, tail, chars, lines)
1314
1315 def untabify_region_event(self, event):
1316 head, tail, chars, lines = self.get_region()
1317 tabwidth = self._asktabwidth()
1318 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001319 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320 self.set_region(head, tail, chars, lines)
1321
1322 def toggle_tabs_event(self, event):
1323 if self.askyesno(
1324 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001325 "Turn tabs " + ("on", "off")[self.usetabs] +
1326 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001327 ("will be", "remains at")[self.usetabs] + " 8." +
1328 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 parent=self.text):
1330 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001331 # Try to prevent inconsistent indentation.
1332 # User must change indent width manually after using tabs.
1333 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 return "break"
1335
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001336 # XXX this isn't bound to anything -- see tabwidth comments
1337## def change_tabwidth_event(self, event):
1338## new = self._asktabwidth()
1339## if new != self.tabwidth:
1340## self.tabwidth = new
1341## self.set_indentation_params(0, guess=0)
1342## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001343
1344 def change_indentwidth_event(self, event):
1345 new = self.askinteger(
1346 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001347 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001348 parent=self.text,
1349 initialvalue=self.indentwidth,
1350 minvalue=2,
1351 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001352 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 self.indentwidth = new
1354 return "break"
1355
1356 def get_region(self):
1357 text = self.text
1358 first, last = self.get_selection_indices()
1359 if first and last:
1360 head = text.index(first + " linestart")
1361 tail = text.index(last + "-1c lineend +1c")
1362 else:
1363 head = text.index("insert linestart")
1364 tail = text.index("insert lineend +1c")
1365 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001366 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367 return head, tail, chars, lines
1368
1369 def set_region(self, head, tail, chars, lines):
1370 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001371 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 if newchars == chars:
1373 text.bell()
1374 return
1375 text.tag_remove("sel", "1.0", "end")
1376 text.mark_set("insert", head)
1377 text.undo_block_start()
1378 text.delete(head, tail)
1379 text.insert(head, newchars)
1380 text.undo_block_stop()
1381 text.tag_add("sel", head, "insert")
1382
1383 # Make string that displays as n leading blanks.
1384
1385 def _make_blanks(self, n):
1386 if self.usetabs:
1387 ntabs, nspaces = divmod(n, self.tabwidth)
1388 return '\t' * ntabs + ' ' * nspaces
1389 else:
1390 return ' ' * n
1391
1392 # Delete from beginning of line to insert point, then reinsert
1393 # column logical (meaning use tabs if appropriate) spaces.
1394
1395 def reindent_to(self, column):
1396 text = self.text
1397 text.undo_block_start()
1398 if text.compare("insert linestart", "!=", "insert"):
1399 text.delete("insert linestart", "insert")
1400 if column:
1401 text.insert("insert", self._make_blanks(column))
1402 text.undo_block_stop()
1403
1404 def _asktabwidth(self):
1405 return self.askinteger(
1406 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001407 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001408 parent=self.text,
1409 initialvalue=self.indentwidth,
1410 minvalue=2,
1411 maxvalue=16) or self.tabwidth
1412
1413 # Guess indentwidth from text content.
1414 # Return guessed indentwidth. This should not be believed unless
1415 # it's in a reasonable range (e.g., it will be 0 if no indented
1416 # blocks are found).
1417
1418 def guess_indent(self):
1419 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1420 if opener and indented:
1421 raw, indentsmall = classifyws(opener, self.tabwidth)
1422 raw, indentlarge = classifyws(indented, self.tabwidth)
1423 else:
1424 indentsmall = indentlarge = 0
1425 return indentlarge - indentsmall
1426
1427# "line.col" -> line, as an int
1428def index2line(index):
1429 return int(float(index))
1430
1431# Look at the leading whitespace in s.
1432# Return pair (# of leading ws characters,
1433# effective # of leading blanks after expanding
1434# tabs to width tabwidth)
1435
1436def classifyws(s, tabwidth):
1437 raw = effective = 0
1438 for ch in s:
1439 if ch == ' ':
1440 raw = raw + 1
1441 effective = effective + 1
1442 elif ch == '\t':
1443 raw = raw + 1
1444 effective = (effective // tabwidth + 1) * tabwidth
1445 else:
1446 break
1447 return raw, effective
1448
1449import tokenize
1450_tokenize = tokenize
1451del tokenize
1452
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001453class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001454
1455 # .run() chews over the Text widget, looking for a block opener
1456 # and the stmt following it. Returns a pair,
1457 # (line containing block opener, line containing stmt)
1458 # Either or both may be None.
1459
1460 def __init__(self, text, tabwidth):
1461 self.text = text
1462 self.tabwidth = tabwidth
1463 self.i = self.finished = 0
1464 self.blkopenline = self.indentedline = None
1465
1466 def readline(self):
1467 if self.finished:
1468 return ""
1469 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001470 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 if self.text.compare(mark, ">=", "end"):
1472 return ""
1473 return self.text.get(mark, mark + " lineend+1c")
1474
1475 def tokeneater(self, type, token, start, end, line,
1476 INDENT=_tokenize.INDENT,
1477 NAME=_tokenize.NAME,
1478 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1479 if self.finished:
1480 pass
1481 elif type == NAME and token in OPENERS:
1482 self.blkopenline = line
1483 elif type == INDENT and self.blkopenline:
1484 self.indentedline = line
1485 self.finished = 1
1486
1487 def run(self):
1488 save_tabsize = _tokenize.tabsize
1489 _tokenize.tabsize = self.tabwidth
1490 try:
1491 try:
1492 _tokenize.tokenize(self.readline, self.tokeneater)
1493 except _tokenize.TokenError:
1494 # since we cut off the tokenizer early, we can trigger
1495 # spurious errors
1496 pass
1497 finally:
1498 _tokenize.tabsize = save_tabsize
1499 return self.blkopenline, self.indentedline
1500
1501### end autoindent code ###
1502
David Scherer7aced172000-08-15 01:13:23 +00001503def prepstr(s):
1504 # Helper to extract the underscore from a string, e.g.
1505 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001506 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001507 if i >= 0:
1508 s = s[:i] + s[i+1:]
1509 return i, s
1510
1511
1512keynames = {
1513 'bracketleft': '[',
1514 'bracketright': ']',
1515 'slash': '/',
1516}
1517
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001518def get_accelerator(keydefs, eventname):
1519 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001520 if not keylist:
1521 return ""
1522 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001523 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001524 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1525 s = re.sub("Key-", "", s)
1526 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1527 s = re.sub("Control-", "Ctrl-", s)
1528 s = re.sub("-", "+", s)
1529 s = re.sub("><", " ", s)
1530 s = re.sub("<", "", s)
1531 s = re.sub(">", "", s)
1532 return s
1533
1534
1535def fixwordbreaks(root):
1536 # Make sure that Tk's double-click and next/previous word
1537 # operations use our definition of a word (i.e. an identifier)
1538 tk = root.tk
1539 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1540 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1541 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1542
1543
1544def test():
1545 root = Tk()
1546 fixwordbreaks(root)
1547 root.withdraw()
1548 if sys.argv[1:]:
1549 filename = sys.argv[1]
1550 else:
1551 filename = None
1552 edit = EditorWindow(root=root, filename=filename)
1553 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001554 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001555 root.mainloop()
1556 root.destroy()
1557
1558if __name__ == '__main__':
1559 test()