blob: f5e3add0e99fba62e8f4fb3b089008c03f0ff3f4 [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
5from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008
9import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000010import idlever
11import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000012import SearchDialog
13import GrepDialog
14import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000015import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000016from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000017import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000018
19# The default tab setting for a Text widget, in average-width characters.
20TK_TABWIDTH_DEFAULT = 8
21
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000022def _find_module(fullname, path=None):
23 """Version of imp.find_module() that handles hierarchical module names"""
24
25 file = None
26 for tgt in fullname.split('.'):
27 if file is not None:
28 file.close() # close intermediate files
29 (file, filename, descr) = imp.find_module(tgt, path)
30 if descr[2] == imp.PY_SOURCE:
31 break # find but not load the source file
32 module = imp.load_module(tgt, file, filename, descr)
33 path = module.__path__
34 return file, filename, descr
35
David Scherer7aced172000-08-15 01:13:23 +000036class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000037 from Percolator import Percolator
38 from ColorDelegator import ColorDelegator
39 from UndoDelegator import UndoDelegator
40 from IOBinding import IOBinding
41 import Bindings
42 from Tkinter import Toplevel
43 from MultiStatusBar import MultiStatusBar
44
David Scherer7aced172000-08-15 01:13:23 +000045 vars = {}
46
47 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000048 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000049 self.flist = flist
50 root = root or flist.root
51 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000052 self.menubar = Menu(root)
53 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000054 if flist:
55 self.vars = flist.vars
56 #self.top.instanceDict makes flist.inversedict avalable to
57 #configDialog.py so it can access all EditorWindow instaces
58 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000059 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
60 'recent-files.lst')
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +000061 self.break_set = False
David Scherer7aced172000-08-15 01:13:23 +000062 self.vbar = vbar = Scrollbar(top, name='vbar')
63 self.text_frame = text_frame = Frame(top)
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000064 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000065 foreground=idleConf.GetHighlight(currentTheme,
66 'normal',fgBg='fg'),
67 background=idleConf.GetHighlight(currentTheme,
68 'normal',fgBg='bg'),
69 highlightcolor=idleConf.GetHighlight(currentTheme,
70 'hilite',fgBg='fg'),
71 highlightbackground=idleConf.GetHighlight(currentTheme,
72 'hilite',fgBg='bg'),
73 insertbackground=idleConf.GetHighlight(currentTheme,
74 'cursor',fgBg='fg'),
75 width=idleConf.GetOption('main','EditorWindow','width'),
76 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000077
78 self.createmenubar()
79 self.apply_bindings()
80
81 self.top.protocol("WM_DELETE_WINDOW", self.close)
82 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +000083 text.bind("<<cut>>", self.cut)
84 text.bind("<<copy>>", self.copy)
85 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +000086 text.bind("<<center-insert>>", self.center_insert_event)
87 text.bind("<<help>>", self.help_dialog)
88 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +000089 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +000090 text.bind("<<python-docs>>", self.python_docs)
91 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +000092 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +000093 text.bind("<<open-module>>", self.open_module)
94 text.bind("<<do-nothing>>", lambda event: "break")
95 text.bind("<<select-all>>", self.select_all)
96 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +000097 text.bind("<<find>>", self.find_event)
98 text.bind("<<find-again>>", self.find_again_event)
99 text.bind("<<find-in-files>>", self.find_in_files_event)
100 text.bind("<<find-selection>>", self.find_selection_event)
101 text.bind("<<replace>>", self.replace_event)
102 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000103 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000104 text.bind("<<smart-backspace>>",self.smart_backspace_event)
105 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
106 text.bind("<<smart-indent>>",self.smart_indent_event)
107 text.bind("<<indent-region>>",self.indent_region_event)
108 text.bind("<<dedent-region>>",self.dedent_region_event)
109 text.bind("<<comment-region>>",self.comment_region_event)
110 text.bind("<<uncomment-region>>",self.uncomment_region_event)
111 text.bind("<<tabify-region>>",self.tabify_region_event)
112 text.bind("<<untabify-region>>",self.untabify_region_event)
113 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
114 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
115
David Scherer7aced172000-08-15 01:13:23 +0000116 if flist:
117 flist.inversedict[self] = key
118 if key:
119 flist.dict[key] = self
120 text.bind("<<open-new-window>>", self.flist.new_callback)
121 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
122 text.bind("<<open-class-browser>>", self.open_class_browser)
123 text.bind("<<open-path-browser>>", self.open_path_browser)
124
Steven M. Gava898a3652001-10-07 11:10:44 +0000125 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000126 vbar['command'] = text.yview
127 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000128 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000129 fontWeight='normal'
130 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
131 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000132 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000133 idleConf.GetOption('main','EditorWindow','font-size'),
134 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000135 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
136 text.pack(side=TOP, fill=BOTH, expand=1)
137 text.focus_set()
138
139 self.per = per = self.Percolator(text)
140 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000141 self.color = color = self.ColorDelegator()
142 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000143 ##print "Initial colorizer"
144 else:
145 ##print "No initial colorizer"
146 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000147
148 self.undo = undo = self.UndoDelegator()
149 per.insertfilter(undo)
150 text.undo_block_start = undo.undo_block_start
151 text.undo_block_stop = undo.undo_block_stop
152 undo.set_saved_change_hook(self.saved_change_hook)
153
154 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000155 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000156 io.set_filename_change_hook(self.filename_change_hook)
157
Steven M. Gava1d46e402002-03-27 08:40:46 +0000158 #create the Recent Files submenu
159 self.menuRecentFiles=Menu(self.menubar)
160 self.menudict['file'].insert_cascade(3,label='Recent Files',
161 underline=0,menu=self.menuRecentFiles)
162 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000163
David Scherer7aced172000-08-15 01:13:23 +0000164 if filename:
165 if os.path.exists(filename):
166 io.loadfile(filename)
167 else:
168 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000169 self.saved_change_hook()
170
171 self.load_extensions()
172
173 menu = self.menudict.get('windows')
174 if menu:
175 end = menu.index("end")
176 if end is None:
177 end = -1
178 if end >= 0:
179 menu.add_separator()
180 end = end + 1
181 self.wmenu_end = end
182 WindowList.register_callback(self.postwindowsmenu)
183
184 # Some abstractions so IDLE extensions are cross-IDE
185 self.askyesno = tkMessageBox.askyesno
186 self.askinteger = tkSimpleDialog.askinteger
187 self.showerror = tkMessageBox.showerror
188
189 if self.extensions.has_key('AutoIndent'):
190 self.extensions['AutoIndent'].set_indentation_params(
191 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000192
David Scherer7aced172000-08-15 01:13:23 +0000193 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000194 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000195 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
196 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
197 self.status_bar.pack(side=BOTTOM, fill=X)
198 self.text.bind('<KeyRelease>', self.set_line_and_column)
199 self.text.bind('<ButtonRelease>', self.set_line_and_column)
200 self.text.after_idle(self.set_line_and_column)
201
202 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000203 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000204 self.status_bar.set_label('column', 'Col: %s' % column)
205 self.status_bar.set_label('line', 'Ln: %s' % line)
206
207 def wakeup(self):
208 if self.top.wm_state() == "iconic":
209 self.top.wm_deiconify()
210 else:
211 self.top.tkraise()
212 self.text.focus_set()
213
214 menu_specs = [
215 ("file", "_File"),
216 ("edit", "_Edit"),
217 ("format", "F_ormat"),
218 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000219 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000220 ("windows", "_Windows"),
221 ("help", "_Help"),
222 ]
223
224 def createmenubar(self):
225 mbar = self.menubar
226 self.menudict = menudict = {}
227 for name, label in self.menu_specs:
228 underline, label = prepstr(label)
229 menudict[name] = menu = Menu(mbar, name=name)
230 mbar.add_cascade(label=label, menu=menu, underline=underline)
231 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000232 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000233 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000234
235 def postwindowsmenu(self):
236 # Only called when Windows menu exists
237 # XXX Actually, this Just-In-Time updating interferes badly
238 # XXX with the tear-off feature. It would be better to update
239 # XXX all Windows menus whenever the list of windows changes.
240 menu = self.menudict['windows']
241 end = menu.index("end")
242 if end is None:
243 end = -1
244 if end > self.wmenu_end:
245 menu.delete(self.wmenu_end+1, end)
246 WindowList.add_windows_to_menu(menu)
247
248 rmenu = None
249
250 def right_menu_event(self, event):
251 self.text.tag_remove("sel", "1.0", "end")
252 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
253 if not self.rmenu:
254 self.make_rmenu()
255 rmenu = self.rmenu
256 self.event = event
257 iswin = sys.platform[:3] == 'win'
258 if iswin:
259 self.text.config(cursor="arrow")
260 rmenu.tk_popup(event.x_root, event.y_root)
261 if iswin:
262 self.text.config(cursor="ibeam")
263
264 rmenu_specs = [
265 # ("Label", "<<virtual-event>>"), ...
266 ("Close", "<<close-window>>"), # Example
267 ]
268
269 def make_rmenu(self):
270 rmenu = Menu(self.text, tearoff=0)
271 for label, eventname in self.rmenu_specs:
272 def command(text=self.text, eventname=eventname):
273 text.event_generate(eventname)
274 rmenu.add_command(label=label, command=command)
275 self.rmenu = rmenu
276
277 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000278 aboutDialog.AboutDialog(self.top,'About IDLEfork')
279
Steven M. Gava3b55a892001-11-21 05:56:26 +0000280 def config_dialog(self, event=None):
281 configDialog.ConfigDialog(self.top,'Settings')
282
David Scherer7aced172000-08-15 01:13:23 +0000283 def good_advice(self, event=None):
284 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
285
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000286 def view_readme(self, event=None):
287 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
288 textView.TextViewer(self.top,'IDLEfork - README',fn)
289
David Scherer7aced172000-08-15 01:13:23 +0000290 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000291 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
292 textView.TextViewer(self.top,'Help',fn)
293
David Scherer7aced172000-08-15 01:13:23 +0000294 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000295 if sys.platform[:3] == "win":
296 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000297 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000298 fn = os.path.normpath(fn)
299 if os.path.isfile(fn):
300 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000301 else:
302 fn = os.path.dirname(__file__)
303 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
304 fn = os.path.normpath(fn)
305 if os.path.isfile(fn):
306 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000307 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000308 def python_docs(self, event=None):
309 os.startfile(self.help_url)
310 else:
311 def python_docs(self, event=None):
312 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000313
314 def display_docs(self, url):
315 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000316
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000317 def cut(self,event):
318 self.text.event_generate("<<Cut>>")
319 return "break"
320
321 def copy(self,event):
322 self.text.event_generate("<<Copy>>")
323 return "break"
324
325 def paste(self,event):
326 self.text.event_generate("<<Paste>>")
327 return "break"
328
David Scherer7aced172000-08-15 01:13:23 +0000329 def select_all(self, event=None):
330 self.text.tag_add("sel", "1.0", "end-1c")
331 self.text.mark_set("insert", "1.0")
332 self.text.see("insert")
333 return "break"
334
335 def remove_selection(self, event=None):
336 self.text.tag_remove("sel", "1.0", "end")
337 self.text.see("insert")
338
Steven M. Gavac5976402002-01-04 03:06:08 +0000339 def find_event(self, event):
340 SearchDialog.find(self.text)
341 return "break"
342
343 def find_again_event(self, event):
344 SearchDialog.find_again(self.text)
345 return "break"
346
347 def find_selection_event(self, event):
348 SearchDialog.find_selection(self.text)
349 return "break"
350
351 def find_in_files_event(self, event):
352 GrepDialog.grep(self.text, self.io, self.flist)
353 return "break"
354
355 def replace_event(self, event):
356 ReplaceDialog.replace(self.text)
357 return "break"
358
359 def goto_line_event(self, event):
360 text = self.text
361 lineno = tkSimpleDialog.askinteger("Goto",
362 "Go to line number:",parent=text)
363 if lineno is None:
364 return "break"
365 if lineno <= 0:
366 text.bell()
367 return "break"
368 text.mark_set("insert", "%d.0" % lineno)
369 text.see("insert")
370
David Scherer7aced172000-08-15 01:13:23 +0000371 def open_module(self, event=None):
372 # XXX Shouldn't this be in IOBinding or in FileList?
373 try:
374 name = self.text.get("sel.first", "sel.last")
375 except TclError:
376 name = ""
377 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000378 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000379 if not name:
380 name = tkSimpleDialog.askstring("Module",
381 "Enter the name of a Python module\n"
382 "to search on sys.path and open:",
383 parent=self.text)
384 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000385 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000386 if not name:
387 return
David Scherer7aced172000-08-15 01:13:23 +0000388 # XXX Ought to insert current file's directory in front of path
389 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000390 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000391 except (NameError, ImportError), msg:
392 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
393 return
394 if type != imp.PY_SOURCE:
395 tkMessageBox.showerror("Unsupported type",
396 "%s is not a source module" % name, parent=self.text)
397 return
398 if f:
399 f.close()
400 if self.flist:
401 self.flist.open(file)
402 else:
403 self.io.loadfile(file)
404
405 def open_class_browser(self, event=None):
406 filename = self.io.filename
407 if not filename:
408 tkMessageBox.showerror(
409 "No filename",
410 "This buffer has no associated filename",
411 master=self.text)
412 self.text.focus_set()
413 return None
414 head, tail = os.path.split(filename)
415 base, ext = os.path.splitext(tail)
416 import ClassBrowser
417 ClassBrowser.ClassBrowser(self.flist, base, [head])
418
419 def open_path_browser(self, event=None):
420 import PathBrowser
421 PathBrowser.PathBrowser(self.flist)
422
423 def gotoline(self, lineno):
424 if lineno is not None and lineno > 0:
425 self.text.mark_set("insert", "%d.0" % lineno)
426 self.text.tag_remove("sel", "1.0", "end")
427 self.text.tag_add("sel", "insert", "insert +1l")
428 self.center()
429
430 def ispythonsource(self, filename):
431 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000432 return True
David Scherer7aced172000-08-15 01:13:23 +0000433 base, ext = os.path.splitext(os.path.basename(filename))
434 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000435 return True
David Scherer7aced172000-08-15 01:13:23 +0000436 try:
437 f = open(filename)
438 line = f.readline()
439 f.close()
440 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000441 return False
442 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000443
444 def close_hook(self):
445 if self.flist:
446 self.flist.close_edit(self)
447
448 def set_close_hook(self, close_hook):
449 self.close_hook = close_hook
450
451 def filename_change_hook(self):
452 if self.flist:
453 self.flist.filename_changed_edit(self)
454 self.saved_change_hook()
455 if self.ispythonsource(self.io.filename):
456 self.addcolorizer()
457 else:
458 self.rmcolorizer()
459
460 def addcolorizer(self):
461 if self.color:
462 return
463 ##print "Add colorizer"
464 self.per.removefilter(self.undo)
465 self.color = self.ColorDelegator()
466 self.per.insertfilter(self.color)
467 self.per.insertfilter(self.undo)
468
469 def rmcolorizer(self):
470 if not self.color:
471 return
472 ##print "Remove colorizer"
473 self.per.removefilter(self.undo)
474 self.per.removefilter(self.color)
475 self.color = None
476 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000477
478 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000479 "Update the colour theme if it is changed"
480 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000481 if self.color:
482 self.color = self.ColorDelegator()
483 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000484
Steven M. Gavab1585412002-03-12 00:21:56 +0000485 def ResetFont(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000486 "Update the text widgets' font if it is changed"
487 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000488 fontWeight='normal'
489 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
490 fontWeight='bold'
491 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
492 idleConf.GetOption('main','EditorWindow','font-size'),
493 fontWeight))
494
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000495 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000496 "Update the keybindings if they are changed"
497 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000498 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
499 keydefs = self.Bindings.default_keydefs
500 for event, keylist in keydefs.items():
501 self.text.event_delete(event)
502 self.apply_bindings()
503 #update menu accelerators
504 menuEventDict={}
505 for menu in self.Bindings.menudefs:
506 menuEventDict[menu[0]]={}
507 for item in menu[1]:
508 if item:
509 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
510 for menubarItem in self.menudict.keys():
511 menu=self.menudict[menubarItem]
512 end=menu.index(END)+1
513 for index in range(0,end):
514 if menu.type(index)=='command':
515 accel=menu.entrycget(index,'accelerator')
516 if accel:
517 itemName=menu.entrycget(index,'label')
518 event=''
519 if menuEventDict.has_key(menubarItem):
520 if menuEventDict[menubarItem].has_key(itemName):
521 event=menuEventDict[menubarItem][itemName]
522 if event:
523 #print 'accel was:',accel
524 accel=get_accelerator(keydefs, event)
525 menu.entryconfig(index,accelerator=accel)
526 #print 'accel now:',accel,'\n'
527
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000528 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000529 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000530 menuList=idleConf.GetAllExtraHelpSourcesList()
531 helpMenu=self.menudict['help']
532 cascadeIndex=helpMenu.index(END)-1
533 if menuList:
534 if not hasattr(self,'menuExtraHelp'):
535 self.menuExtraHelp=Menu(self.menubar)
536 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
537 underline=1,menu=self.menuExtraHelp)
538 self.menuExtraHelp.delete(1,END)
539 for menuItem in menuList:
540 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000541 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000542 else: #no extra help items
543 if hasattr(self,'menuExtraHelp'):
544 helpMenu.delete(cascadeIndex-1)
545 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000546
547 def __DisplayExtraHelpCallback(self,helpFile):
548 def DisplayExtraHelp(helpFile=helpFile):
549 self.display_docs(helpFile)
550 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000551
Steven M. Gava1d46e402002-03-27 08:40:46 +0000552 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000553 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000554 rfList=[]
555 if os.path.exists(self.recentFilesPath):
556 RFfile=open(self.recentFilesPath,'r')
557 try:
558 rfList=RFfile.readlines()
559 finally:
560 RFfile.close()
561 if newFile:
562 newFile=os.path.abspath(newFile)+'\n'
563 if newFile in rfList:
564 rfList.remove(newFile)
565 rfList.insert(0,newFile)
566 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000567 #print self.flist.inversedict
568 #print self.top.instanceDict
569 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000570 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000571 if rfList:
572 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000573 menu = instance.menuRecentFiles
574 menu.delete(1,END)
575 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000576 for file in rfList:
577 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000578 callback = instance.__RecentFileCallback(fileName)
579 if i > ullen: # don't underline menuitems
580 ul=None
581 menu.add_command(label=ullist[i] + " " + fileName,
582 command=callback,
583 underline=ul)
584 i += 1
Steven M. Gava1d46e402002-03-27 08:40:46 +0000585
586 def __CleanRecentFiles(self,rfList):
587 origRfList=rfList[:]
588 count=0
589 nonFiles=[]
590 for path in rfList:
591 if not os.path.exists(path[0:-1]):
592 nonFiles.append(count)
593 count=count+1
594 if nonFiles:
595 nonFiles.reverse()
596 for index in nonFiles:
597 del(rfList[index])
598 if len(rfList)>19:
599 rfList=rfList[0:19]
600 #if rfList != origRfList:
601 RFfile=open(self.recentFilesPath,'w')
602 try:
603 RFfile.writelines(rfList)
604 finally:
605 RFfile.close()
606 return rfList
607
608 def __RecentFileCallback(self,fileName):
609 def OpenRecentFile(fileName=fileName):
610 self.io.open(editFile=fileName)
611 return OpenRecentFile
612
David Scherer7aced172000-08-15 01:13:23 +0000613 def saved_change_hook(self):
614 short = self.short_title()
615 long = self.long_title()
616 if short and long:
617 title = short + " - " + long
618 elif short:
619 title = short
620 elif long:
621 title = long
622 else:
623 title = "Untitled"
624 icon = short or long or title
625 if not self.get_saved():
626 title = "*%s*" % title
627 icon = "*%s" % icon
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000628 if self.break_set:
629 shell = self.flist.pyshell
630 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000631 self.top.wm_title(title)
632 self.top.wm_iconname(icon)
633
634 def get_saved(self):
635 return self.undo.get_saved()
636
637 def set_saved(self, flag):
638 self.undo.set_saved(flag)
639
640 def reset_undo(self):
641 self.undo.reset_undo()
642
643 def short_title(self):
644 filename = self.io.filename
645 if filename:
646 filename = os.path.basename(filename)
647 return filename
648
649 def long_title(self):
650 return self.io.filename or ""
651
652 def center_insert_event(self, event):
653 self.center()
654
655 def center(self, mark="insert"):
656 text = self.text
657 top, bot = self.getwindowlines()
658 lineno = self.getlineno(mark)
659 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000660 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000661 text.yview(float(newtop))
662
663 def getwindowlines(self):
664 text = self.text
665 top = self.getlineno("@0,0")
666 bot = self.getlineno("@0,65535")
667 if top == bot and text.winfo_height() == 1:
668 # Geometry manager hasn't run yet
669 height = int(text['height'])
670 bot = top + height - 1
671 return top, bot
672
673 def getlineno(self, mark="insert"):
674 text = self.text
675 return int(float(text.index(mark)))
676
677 def close_event(self, event):
678 self.close()
679
680 def maybesave(self):
681 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000682 if not self.get_saved():
683 if self.top.state()!='normal':
684 self.top.deiconify()
685 self.top.lower()
686 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000687 return self.io.maybesave()
688
689 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000690 reply = self.maybesave()
691 if reply != "cancel":
692 self._close()
693 return reply
694
695 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000696 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000697 if self.io.filename:
698 self.UpdateRecentFilesList(newFile=self.io.filename)
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000699 if self.break_set:
700 shell = self.flist.pyshell
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000701 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000702 WindowList.unregister_callback(self.postwindowsmenu)
703 if self.close_hook:
704 self.close_hook()
705 self.flist = None
706 colorizing = 0
707 self.unload_extensions()
708 self.io.close(); self.io = None
709 self.undo = None # XXX
710 if self.color:
711 colorizing = self.color.colorizing
712 doh = colorizing and self.top
713 self.color.close(doh) # Cancel colorization
714 self.text = None
715 self.vars = None
716 self.per.close(); self.per = None
717 if not colorizing:
718 self.top.destroy()
719
720 def load_extensions(self):
721 self.extensions = {}
722 self.load_standard_extensions()
723
724 def unload_extensions(self):
725 for ins in self.extensions.values():
726 if hasattr(ins, "close"):
727 ins.close()
728 self.extensions = {}
729
730 def load_standard_extensions(self):
731 for name in self.get_standard_extension_names():
732 try:
733 self.load_extension(name)
734 except:
735 print "Failed to load extension", `name`
736 import traceback
737 traceback.print_exc()
738
739 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000740 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000741
742 def load_extension(self, name):
743 mod = __import__(name, globals(), locals(), [])
744 cls = getattr(mod, name)
745 ins = cls(self)
746 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000747 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000748 if keydefs:
749 self.apply_bindings(keydefs)
750 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000751 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000752 while methodname[:1] == '<':
753 methodname = methodname[1:]
754 while methodname[-1:] == '>':
755 methodname = methodname[:-1]
756 methodname = methodname + "_event"
757 if hasattr(ins, methodname):
758 self.text.bind(vevent, getattr(ins, methodname))
759 if hasattr(ins, "menudefs"):
760 self.fill_menus(ins.menudefs, keydefs)
761 return ins
762
763 def apply_bindings(self, keydefs=None):
764 if keydefs is None:
765 keydefs = self.Bindings.default_keydefs
766 text = self.text
767 text.keydefs = keydefs
768 for event, keylist in keydefs.items():
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000769 ##print>>sys.__stderr__, "event, list: ", event, keylist
David Scherer7aced172000-08-15 01:13:23 +0000770 if keylist:
771 apply(text.event_add, (event,) + tuple(keylist))
772
773 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000774 """Add appropriate entries to the menus and submenus
775
776 Menus that are absent or None in self.menudict are ignored.
777 """
David Scherer7aced172000-08-15 01:13:23 +0000778 if defs is None:
779 defs = self.Bindings.menudefs
780 if keydefs is None:
781 keydefs = self.Bindings.default_keydefs
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000782 ##print>>sys.__stderr__, "*keydefs: " , keydefs
David Scherer7aced172000-08-15 01:13:23 +0000783 menudict = self.menudict
784 text = self.text
785 for mname, itemlist in defs:
786 menu = menudict.get(mname)
787 if not menu:
788 continue
789 for item in itemlist:
790 if not item:
791 menu.add_separator()
792 else:
793 label, event = item
794 checkbutton = (label[:1] == '!')
795 if checkbutton:
796 label = label[1:]
797 underline, label = prepstr(label)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000798 ##print>>sys.__stderr__, "*Event: " , event
David Scherer7aced172000-08-15 01:13:23 +0000799 accelerator = get_accelerator(keydefs, event)
800 def command(text=text, event=event):
801 text.event_generate(event)
802 if checkbutton:
803 var = self.getrawvar(event, BooleanVar)
804 menu.add_checkbutton(label=label, underline=underline,
805 command=command, accelerator=accelerator,
806 variable=var)
807 else:
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000808 ##print>>sys.__stderr__, "label, ul, cmd, accel: ",
809 ## label, underline, command,
810 ## accelerator
David Scherer7aced172000-08-15 01:13:23 +0000811 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000812 command=command,
813 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000814
815 def getvar(self, name):
816 var = self.getrawvar(name)
817 if var:
818 return var.get()
819
820 def setvar(self, name, value, vartype=None):
821 var = self.getrawvar(name, vartype)
822 if var:
823 var.set(value)
824
825 def getrawvar(self, name, vartype=None):
826 var = self.vars.get(name)
827 if not var and vartype:
828 self.vars[name] = var = vartype(self.text)
829 return var
830
831 # Tk implementations of "virtual text methods" -- each platform
832 # reusing IDLE's support code needs to define these for its GUI's
833 # flavor of widget.
834
835 # Is character at text_index in a Python string? Return 0 for
836 # "guaranteed no", true for anything else. This info is expensive
837 # to compute ab initio, but is probably already known by the
838 # platform's colorizer.
839
840 def is_char_in_string(self, text_index):
841 if self.color:
842 # Return true iff colorizer hasn't (re)gotten this far
843 # yet, or the character is tagged as being in a string
844 return self.text.tag_prevrange("TODO", text_index) or \
845 "STRING" in self.text.tag_names(text_index)
846 else:
847 # The colorizer is missing: assume the worst
848 return 1
849
850 # If a selection is defined in the text widget, return (start,
851 # end) as Tkinter text indices, otherwise return (None, None)
852 def get_selection_indices(self):
853 try:
854 first = self.text.index("sel.first")
855 last = self.text.index("sel.last")
856 return first, last
857 except TclError:
858 return None, None
859
860 # Return the text widget's current view of what a tab stop means
861 # (equivalent width in spaces).
862
863 def get_tabwidth(self):
864 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
865 return int(current)
866
867 # Set the text widget's current view of what a tab stop means.
868
869 def set_tabwidth(self, newtabwidth):
870 text = self.text
871 if self.get_tabwidth() != newtabwidth:
872 pixels = text.tk.call("font", "measure", text["font"],
873 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000874 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000875 text.configure(tabs=pixels)
876
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000877### begin autoindent code ###
878
879 # usetabs true -> literal tab characters are used by indent and
880 # dedent cmds, possibly mixed with spaces if
881 # indentwidth is not a multiple of tabwidth
882 # false -> tab characters are converted to spaces by indent
883 # and dedent cmds, and ditto TAB keystrokes
884 # indentwidth is the number of characters per logical indent level.
885 # tabwidth is the display width of a literal tab character.
886 # CAUTION: telling Tk to use anything other than its default
887 # tab setting causes it to use an entirely different tabbing algorithm,
888 # treating tab stops as fixed distances from the left margin.
889 # Nobody expects this, so for now tabwidth should never be changed.
890 usetabs = 0
891 indentwidth = 4
892 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
893
894 # If context_use_ps1 is true, parsing searches back for a ps1 line;
895 # else searches for a popular (if, def, ...) Python stmt.
896 context_use_ps1 = 0
897
898 # When searching backwards for a reliable place to begin parsing,
899 # first start num_context_lines[0] lines back, then
900 # num_context_lines[1] lines back if that didn't work, and so on.
901 # The last value should be huge (larger than the # of lines in a
902 # conceivable file).
903 # Making the initial values larger slows things down more often.
904 num_context_lines = 50, 500, 5000000
905
906 def config(self, **options):
907 for key, value in options.items():
908 if key == 'usetabs':
909 self.usetabs = value
910 elif key == 'indentwidth':
911 self.indentwidth = value
912 elif key == 'tabwidth':
913 self.tabwidth = value
914 elif key == 'context_use_ps1':
915 self.context_use_ps1 = value
916 else:
917 raise KeyError, "bad option name: %s" % `key`
918
919 # If ispythonsource and guess are true, guess a good value for
920 # indentwidth based on file content (if possible), and if
921 # indentwidth != tabwidth set usetabs false.
922 # In any case, adjust the Text widget's view of what a tab
923 # character means.
924
925 def set_indentation_params(self, ispythonsource, guess=1):
926 if guess and ispythonsource:
927 i = self.guess_indent()
928 if 2 <= i <= 8:
929 self.indentwidth = i
930 if self.indentwidth != self.tabwidth:
931 self.usetabs = 0
932
933 self.set_tabwidth(self.tabwidth)
934
935 def smart_backspace_event(self, event):
936 text = self.text
937 first, last = self.get_selection_indices()
938 if first and last:
939 text.delete(first, last)
940 text.mark_set("insert", first)
941 return "break"
942 # Delete whitespace left, until hitting a real char or closest
943 # preceding virtual tab stop.
944 chars = text.get("insert linestart", "insert")
945 if chars == '':
946 if text.compare("insert", ">", "1.0"):
947 # easy: delete preceding newline
948 text.delete("insert-1c")
949 else:
950 text.bell() # at start of buffer
951 return "break"
952 if chars[-1] not in " \t":
953 # easy: delete preceding real char
954 text.delete("insert-1c")
955 return "break"
956 # Ick. It may require *inserting* spaces if we back up over a
957 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000958 tabwidth = self.tabwidth
959 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000960 assert have > 0
961 want = ((have - 1) // self.indentwidth) * self.indentwidth
962 ncharsdeleted = 0
963 while 1:
964 chars = chars[:-1]
965 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000966 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000967 if have <= want or chars[-1] not in " \t":
968 break
969 text.undo_block_start()
970 text.delete("insert-%dc" % ncharsdeleted, "insert")
971 if have < want:
972 text.insert("insert", ' ' * (want - have))
973 text.undo_block_stop()
974 return "break"
975
976 def smart_indent_event(self, event):
977 # if intraline selection:
978 # delete it
979 # elif multiline selection:
980 # do indent-region & return
981 # indent one level
982 text = self.text
983 first, last = self.get_selection_indices()
984 text.undo_block_start()
985 try:
986 if first and last:
987 if index2line(first) != index2line(last):
988 return self.indent_region_event(event)
989 text.delete(first, last)
990 text.mark_set("insert", first)
991 prefix = text.get("insert linestart", "insert")
992 raw, effective = classifyws(prefix, self.tabwidth)
993 if raw == len(prefix):
994 # only whitespace to the left
995 self.reindent_to(effective + self.indentwidth)
996 else:
997 if self.usetabs:
998 pad = '\t'
999 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001000 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001001 n = self.indentwidth
1002 pad = ' ' * (n - effective % n)
1003 text.insert("insert", pad)
1004 text.see("insert")
1005 return "break"
1006 finally:
1007 text.undo_block_stop()
1008
1009 def newline_and_indent_event(self, event):
1010 text = self.text
1011 first, last = self.get_selection_indices()
1012 text.undo_block_start()
1013 try:
1014 if first and last:
1015 text.delete(first, last)
1016 text.mark_set("insert", first)
1017 line = text.get("insert linestart", "insert")
1018 i, n = 0, len(line)
1019 while i < n and line[i] in " \t":
1020 i = i+1
1021 if i == n:
1022 # the cursor is in or at leading indentation; just inject
1023 # an empty line at the start
1024 text.insert("insert linestart", '\n')
1025 return "break"
1026 indent = line[:i]
1027 # strip whitespace before insert point
1028 i = 0
1029 while line and line[-1] in " \t":
1030 line = line[:-1]
1031 i = i+1
1032 if i:
1033 text.delete("insert - %d chars" % i, "insert")
1034 # strip whitespace after insert point
1035 while text.get("insert") in " \t":
1036 text.delete("insert")
1037 # start new line
1038 text.insert("insert", '\n')
1039
1040 # adjust indentation for continuations and block
1041 # open/close first need to find the last stmt
1042 lno = index2line(text.index('insert'))
1043 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1044 for context in self.num_context_lines:
1045 startat = max(lno - context, 1)
1046 startatindex = `startat` + ".0"
1047 rawtext = text.get(startatindex, "insert")
1048 y.set_str(rawtext)
1049 bod = y.find_good_parse_start(
1050 self.context_use_ps1,
1051 self._build_char_in_string_func(startatindex))
1052 if bod is not None or startat == 1:
1053 break
1054 y.set_lo(bod or 0)
1055 c = y.get_continuation_type()
1056 if c != PyParse.C_NONE:
1057 # The current stmt hasn't ended yet.
1058 if c == PyParse.C_STRING:
1059 # inside a string; just mimic the current indent
1060 text.insert("insert", indent)
1061 elif c == PyParse.C_BRACKET:
1062 # line up with the first (if any) element of the
1063 # last open bracket structure; else indent one
1064 # level beyond the indent of the line with the
1065 # last open bracket
1066 self.reindent_to(y.compute_bracket_indent())
1067 elif c == PyParse.C_BACKSLASH:
1068 # if more than one line in this stmt already, just
1069 # mimic the current indent; else if initial line
1070 # has a start on an assignment stmt, indent to
1071 # beyond leftmost =; else to beyond first chunk of
1072 # non-whitespace on initial line
1073 if y.get_num_lines_in_stmt() > 1:
1074 text.insert("insert", indent)
1075 else:
1076 self.reindent_to(y.compute_backslash_indent())
1077 else:
1078 assert 0, "bogus continuation type " + `c`
1079 return "break"
1080
1081 # This line starts a brand new stmt; indent relative to
1082 # indentation of initial line of closest preceding
1083 # interesting stmt.
1084 indent = y.get_base_indent_string()
1085 text.insert("insert", indent)
1086 if y.is_block_opener():
1087 self.smart_indent_event(event)
1088 elif indent and y.is_block_closer():
1089 self.smart_backspace_event(event)
1090 return "break"
1091 finally:
1092 text.see("insert")
1093 text.undo_block_stop()
1094
1095 auto_indent = newline_and_indent_event
1096
1097 # Our editwin provides a is_char_in_string function that works
1098 # with a Tk text index, but PyParse only knows about offsets into
1099 # a string. This builds a function for PyParse that accepts an
1100 # offset.
1101
1102 def _build_char_in_string_func(self, startindex):
1103 def inner(offset, _startindex=startindex,
1104 _icis=self.is_char_in_string):
1105 return _icis(_startindex + "+%dc" % offset)
1106 return inner
1107
1108 def indent_region_event(self, event):
1109 head, tail, chars, lines = self.get_region()
1110 for pos in range(len(lines)):
1111 line = lines[pos]
1112 if line:
1113 raw, effective = classifyws(line, self.tabwidth)
1114 effective = effective + self.indentwidth
1115 lines[pos] = self._make_blanks(effective) + line[raw:]
1116 self.set_region(head, tail, chars, lines)
1117 return "break"
1118
1119 def dedent_region_event(self, event):
1120 head, tail, chars, lines = self.get_region()
1121 for pos in range(len(lines)):
1122 line = lines[pos]
1123 if line:
1124 raw, effective = classifyws(line, self.tabwidth)
1125 effective = max(effective - self.indentwidth, 0)
1126 lines[pos] = self._make_blanks(effective) + line[raw:]
1127 self.set_region(head, tail, chars, lines)
1128 return "break"
1129
1130 def comment_region_event(self, event):
1131 head, tail, chars, lines = self.get_region()
1132 for pos in range(len(lines) - 1):
1133 line = lines[pos]
1134 lines[pos] = '##' + line
1135 self.set_region(head, tail, chars, lines)
1136
1137 def uncomment_region_event(self, event):
1138 head, tail, chars, lines = self.get_region()
1139 for pos in range(len(lines)):
1140 line = lines[pos]
1141 if not line:
1142 continue
1143 if line[:2] == '##':
1144 line = line[2:]
1145 elif line[:1] == '#':
1146 line = line[1:]
1147 lines[pos] = line
1148 self.set_region(head, tail, chars, lines)
1149
1150 def tabify_region_event(self, event):
1151 head, tail, chars, lines = self.get_region()
1152 tabwidth = self._asktabwidth()
1153 for pos in range(len(lines)):
1154 line = lines[pos]
1155 if line:
1156 raw, effective = classifyws(line, tabwidth)
1157 ntabs, nspaces = divmod(effective, tabwidth)
1158 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1159 self.set_region(head, tail, chars, lines)
1160
1161 def untabify_region_event(self, event):
1162 head, tail, chars, lines = self.get_region()
1163 tabwidth = self._asktabwidth()
1164 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001165 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001166 self.set_region(head, tail, chars, lines)
1167
1168 def toggle_tabs_event(self, event):
1169 if self.askyesno(
1170 "Toggle tabs",
1171 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1172 parent=self.text):
1173 self.usetabs = not self.usetabs
1174 return "break"
1175
1176 # XXX this isn't bound to anything -- see class tabwidth comments
1177 def change_tabwidth_event(self, event):
1178 new = self._asktabwidth()
1179 if new != self.tabwidth:
1180 self.tabwidth = new
1181 self.set_indentation_params(0, guess=0)
1182 return "break"
1183
1184 def change_indentwidth_event(self, event):
1185 new = self.askinteger(
1186 "Indent width",
1187 "New indent width (2-16)",
1188 parent=self.text,
1189 initialvalue=self.indentwidth,
1190 minvalue=2,
1191 maxvalue=16)
1192 if new and new != self.indentwidth:
1193 self.indentwidth = new
1194 return "break"
1195
1196 def get_region(self):
1197 text = self.text
1198 first, last = self.get_selection_indices()
1199 if first and last:
1200 head = text.index(first + " linestart")
1201 tail = text.index(last + "-1c lineend +1c")
1202 else:
1203 head = text.index("insert linestart")
1204 tail = text.index("insert lineend +1c")
1205 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001206 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001207 return head, tail, chars, lines
1208
1209 def set_region(self, head, tail, chars, lines):
1210 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001211 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 if newchars == chars:
1213 text.bell()
1214 return
1215 text.tag_remove("sel", "1.0", "end")
1216 text.mark_set("insert", head)
1217 text.undo_block_start()
1218 text.delete(head, tail)
1219 text.insert(head, newchars)
1220 text.undo_block_stop()
1221 text.tag_add("sel", head, "insert")
1222
1223 # Make string that displays as n leading blanks.
1224
1225 def _make_blanks(self, n):
1226 if self.usetabs:
1227 ntabs, nspaces = divmod(n, self.tabwidth)
1228 return '\t' * ntabs + ' ' * nspaces
1229 else:
1230 return ' ' * n
1231
1232 # Delete from beginning of line to insert point, then reinsert
1233 # column logical (meaning use tabs if appropriate) spaces.
1234
1235 def reindent_to(self, column):
1236 text = self.text
1237 text.undo_block_start()
1238 if text.compare("insert linestart", "!=", "insert"):
1239 text.delete("insert linestart", "insert")
1240 if column:
1241 text.insert("insert", self._make_blanks(column))
1242 text.undo_block_stop()
1243
1244 def _asktabwidth(self):
1245 return self.askinteger(
1246 "Tab width",
1247 "Spaces per tab? (2-16)",
1248 parent=self.text,
1249 initialvalue=self.indentwidth,
1250 minvalue=2,
1251 maxvalue=16) or self.tabwidth
1252
1253 # Guess indentwidth from text content.
1254 # Return guessed indentwidth. This should not be believed unless
1255 # it's in a reasonable range (e.g., it will be 0 if no indented
1256 # blocks are found).
1257
1258 def guess_indent(self):
1259 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1260 if opener and indented:
1261 raw, indentsmall = classifyws(opener, self.tabwidth)
1262 raw, indentlarge = classifyws(indented, self.tabwidth)
1263 else:
1264 indentsmall = indentlarge = 0
1265 return indentlarge - indentsmall
1266
1267# "line.col" -> line, as an int
1268def index2line(index):
1269 return int(float(index))
1270
1271# Look at the leading whitespace in s.
1272# Return pair (# of leading ws characters,
1273# effective # of leading blanks after expanding
1274# tabs to width tabwidth)
1275
1276def classifyws(s, tabwidth):
1277 raw = effective = 0
1278 for ch in s:
1279 if ch == ' ':
1280 raw = raw + 1
1281 effective = effective + 1
1282 elif ch == '\t':
1283 raw = raw + 1
1284 effective = (effective // tabwidth + 1) * tabwidth
1285 else:
1286 break
1287 return raw, effective
1288
1289import tokenize
1290_tokenize = tokenize
1291del tokenize
1292
1293class IndentSearcher:
1294
1295 # .run() chews over the Text widget, looking for a block opener
1296 # and the stmt following it. Returns a pair,
1297 # (line containing block opener, line containing stmt)
1298 # Either or both may be None.
1299
1300 def __init__(self, text, tabwidth):
1301 self.text = text
1302 self.tabwidth = tabwidth
1303 self.i = self.finished = 0
1304 self.blkopenline = self.indentedline = None
1305
1306 def readline(self):
1307 if self.finished:
1308 return ""
1309 i = self.i = self.i + 1
1310 mark = `i` + ".0"
1311 if self.text.compare(mark, ">=", "end"):
1312 return ""
1313 return self.text.get(mark, mark + " lineend+1c")
1314
1315 def tokeneater(self, type, token, start, end, line,
1316 INDENT=_tokenize.INDENT,
1317 NAME=_tokenize.NAME,
1318 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1319 if self.finished:
1320 pass
1321 elif type == NAME and token in OPENERS:
1322 self.blkopenline = line
1323 elif type == INDENT and self.blkopenline:
1324 self.indentedline = line
1325 self.finished = 1
1326
1327 def run(self):
1328 save_tabsize = _tokenize.tabsize
1329 _tokenize.tabsize = self.tabwidth
1330 try:
1331 try:
1332 _tokenize.tokenize(self.readline, self.tokeneater)
1333 except _tokenize.TokenError:
1334 # since we cut off the tokenizer early, we can trigger
1335 # spurious errors
1336 pass
1337 finally:
1338 _tokenize.tabsize = save_tabsize
1339 return self.blkopenline, self.indentedline
1340
1341### end autoindent code ###
1342
David Scherer7aced172000-08-15 01:13:23 +00001343def prepstr(s):
1344 # Helper to extract the underscore from a string, e.g.
1345 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001346 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001347 if i >= 0:
1348 s = s[:i] + s[i+1:]
1349 return i, s
1350
1351
1352keynames = {
1353 'bracketleft': '[',
1354 'bracketright': ']',
1355 'slash': '/',
1356}
1357
1358def get_accelerator(keydefs, event):
1359 keylist = keydefs.get(event)
1360 if not keylist:
1361 return ""
1362 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001363 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001364 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1365 s = re.sub("Key-", "", s)
1366 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1367 s = re.sub("Control-", "Ctrl-", s)
1368 s = re.sub("-", "+", s)
1369 s = re.sub("><", " ", s)
1370 s = re.sub("<", "", s)
1371 s = re.sub(">", "", s)
1372 return s
1373
1374
1375def fixwordbreaks(root):
1376 # Make sure that Tk's double-click and next/previous word
1377 # operations use our definition of a word (i.e. an identifier)
1378 tk = root.tk
1379 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1380 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1381 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1382
1383
1384def test():
1385 root = Tk()
1386 fixwordbreaks(root)
1387 root.withdraw()
1388 if sys.argv[1:]:
1389 filename = sys.argv[1]
1390 else:
1391 filename = None
1392 edit = EditorWindow(root=root, filename=filename)
1393 edit.set_close_hook(root.quit)
1394 root.mainloop()
1395 root.destroy()
1396
1397if __name__ == '__main__':
1398 test()