blob: 49f98693324b8cb14710f4e0ae9f42e72cdc128f [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001# changes by dscherer@cmu.edu
2# - created format and run menus
3# - added silly advice dialog (apologies to Douglas Adams)
4# - made Python Documentation work on Windows (requires win32api to
5# do a ShellExecute(); other ways of starting a web browser are awkward)
6
7import sys
8import os
9import string
10import re
11import imp
12from Tkinter import *
13import tkSimpleDialog
14import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000015
16import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000017import idlever
18import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000019import SearchDialog
20import GrepDialog
21import ReplaceDialog
Steven M. Gavadc72f482002-01-03 11:51:07 +000022#from IdleConf import idleconf
23from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000024import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000025
26# The default tab setting for a Text widget, in average-width characters.
27TK_TABWIDTH_DEFAULT = 8
28
29# File menu
30
31#$ event <<open-module>>
32#$ win <Alt-m>
33#$ unix <Control-x><Control-m>
34
35#$ event <<open-class-browser>>
36#$ win <Alt-c>
37#$ unix <Control-x><Control-b>
38
39#$ event <<open-path-browser>>
40
41#$ event <<close-window>>
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +000042
David Scherer7aced172000-08-15 01:13:23 +000043#$ unix <Control-x><Control-0>
44#$ unix <Control-x><Key-0>
45#$ win <Alt-F4>
46
47# Edit menu
48
49#$ event <<Copy>>
50#$ win <Control-c>
51#$ unix <Alt-w>
52
53#$ event <<Cut>>
54#$ win <Control-x>
55#$ unix <Control-w>
56
57#$ event <<Paste>>
58#$ win <Control-v>
59#$ unix <Control-y>
60
61#$ event <<select-all>>
62#$ win <Alt-a>
63#$ unix <Alt-a>
64
65# Help menu
66
67#$ event <<help>>
68#$ win <F1>
69#$ unix <F1>
70
71#$ event <<about-idle>>
72
73# Events without menu entries
74
75#$ event <<remove-selection>>
76#$ win <Escape>
77
78#$ event <<center-insert>>
79#$ win <Control-l>
80#$ unix <Control-l>
81
82#$ event <<do-nothing>>
83#$ unix <Control-x>
84
David Scherer7aced172000-08-15 01:13:23 +000085class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000086 from Percolator import Percolator
87 from ColorDelegator import ColorDelegator
88 from UndoDelegator import UndoDelegator
89 from IOBinding import IOBinding
90 import Bindings
91 from Tkinter import Toplevel
92 from MultiStatusBar import MultiStatusBar
93
David Scherer7aced172000-08-15 01:13:23 +000094 vars = {}
95
96 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000097 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000098 self.flist = flist
99 root = root or flist.root
100 self.root = root
101 if flist:
102 self.vars = flist.vars
103 self.menubar = Menu(root)
104 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000105 #self.top.instanceDict makes flist.inversedict avalable to
106 #configDialog.py so it can access all EditorWindow instaces
107 self.top.instanceDict=flist.inversedict
David Scherer7aced172000-08-15 01:13:23 +0000108 self.vbar = vbar = Scrollbar(top, name='vbar')
109 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +0000110 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
111 foreground=idleConf.GetHighlight(currentTheme,
112 'normal',fgBg='fg'),
113 background=idleConf.GetHighlight(currentTheme,
114 'normal',fgBg='bg'),
115 highlightcolor=idleConf.GetHighlight(currentTheme,
116 'hilite',fgBg='fg'),
117 highlightbackground=idleConf.GetHighlight(currentTheme,
118 'hilite',fgBg='bg'),
119 insertbackground=idleConf.GetHighlight(currentTheme,
120 'cursor',fgBg='fg'),
121 width=idleConf.GetOption('main','EditorWindow','width'),
122 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000123
124 self.createmenubar()
125 self.apply_bindings()
126
127 self.top.protocol("WM_DELETE_WINDOW", self.close)
128 self.top.bind("<<close-window>>", self.close_event)
129 text.bind("<<center-insert>>", self.center_insert_event)
130 text.bind("<<help>>", self.help_dialog)
131 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000132 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000133 text.bind("<<python-docs>>", self.python_docs)
134 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000135 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000136 text.bind("<<open-module>>", self.open_module)
137 text.bind("<<do-nothing>>", lambda event: "break")
138 text.bind("<<select-all>>", self.select_all)
139 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000140 text.bind("<<find>>", self.find_event)
141 text.bind("<<find-again>>", self.find_again_event)
142 text.bind("<<find-in-files>>", self.find_in_files_event)
143 text.bind("<<find-selection>>", self.find_selection_event)
144 text.bind("<<replace>>", self.replace_event)
145 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000146 text.bind("<3>", self.right_menu_event)
147 if flist:
148 flist.inversedict[self] = key
149 if key:
150 flist.dict[key] = self
151 text.bind("<<open-new-window>>", self.flist.new_callback)
152 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
153 text.bind("<<open-class-browser>>", self.open_class_browser)
154 text.bind("<<open-path-browser>>", self.open_path_browser)
155
Steven M. Gava898a3652001-10-07 11:10:44 +0000156 self.set_status_bar()
157
David Scherer7aced172000-08-15 01:13:23 +0000158 vbar['command'] = text.yview
159 vbar.pack(side=RIGHT, fill=Y)
160
161 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000162 fontWeight='normal'
163 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
164 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000165 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000166 idleConf.GetOption('main','EditorWindow','font-size'),
167 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000168 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
169 text.pack(side=TOP, fill=BOTH, expand=1)
170 text.focus_set()
171
172 self.per = per = self.Percolator(text)
173 if self.ispythonsource(filename):
174 self.color = color = self.ColorDelegator(); per.insertfilter(color)
175 ##print "Initial colorizer"
176 else:
177 ##print "No initial colorizer"
178 self.color = None
179 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
180 self.io = io = self.IOBinding(self)
181
182 text.undo_block_start = undo.undo_block_start
183 text.undo_block_stop = undo.undo_block_stop
184 undo.set_saved_change_hook(self.saved_change_hook)
185 io.set_filename_change_hook(self.filename_change_hook)
186
187 if filename:
188 if os.path.exists(filename):
189 io.loadfile(filename)
190 else:
191 io.set_filename(filename)
192
193 self.saved_change_hook()
194
195 self.load_extensions()
196
197 menu = self.menudict.get('windows')
198 if menu:
199 end = menu.index("end")
200 if end is None:
201 end = -1
202 if end >= 0:
203 menu.add_separator()
204 end = end + 1
205 self.wmenu_end = end
206 WindowList.register_callback(self.postwindowsmenu)
207
208 # Some abstractions so IDLE extensions are cross-IDE
209 self.askyesno = tkMessageBox.askyesno
210 self.askinteger = tkSimpleDialog.askinteger
211 self.showerror = tkMessageBox.showerror
212
213 if self.extensions.has_key('AutoIndent'):
214 self.extensions['AutoIndent'].set_indentation_params(
215 self.ispythonsource(filename))
David Scherer7aced172000-08-15 01:13:23 +0000216
217 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000218 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000219 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
220 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
221 self.status_bar.pack(side=BOTTOM, fill=X)
222 self.text.bind('<KeyRelease>', self.set_line_and_column)
223 self.text.bind('<ButtonRelease>', self.set_line_and_column)
224 self.text.after_idle(self.set_line_and_column)
225
226 def set_line_and_column(self, event=None):
227 line, column = string.split(self.text.index(INSERT), '.')
228 self.status_bar.set_label('column', 'Col: %s' % column)
229 self.status_bar.set_label('line', 'Ln: %s' % line)
230
231 def wakeup(self):
232 if self.top.wm_state() == "iconic":
233 self.top.wm_deiconify()
234 else:
235 self.top.tkraise()
236 self.text.focus_set()
237
238 menu_specs = [
239 ("file", "_File"),
240 ("edit", "_Edit"),
241 ("format", "F_ormat"),
242 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000243 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000244 ("windows", "_Windows"),
245 ("help", "_Help"),
246 ]
247
248 def createmenubar(self):
249 mbar = self.menubar
250 self.menudict = menudict = {}
251 for name, label in self.menu_specs:
252 underline, label = prepstr(label)
253 menudict[name] = menu = Menu(mbar, name=name)
254 mbar.add_cascade(label=label, menu=menu, underline=underline)
255 self.fill_menus()
256
257 def postwindowsmenu(self):
258 # Only called when Windows menu exists
259 # XXX Actually, this Just-In-Time updating interferes badly
260 # XXX with the tear-off feature. It would be better to update
261 # XXX all Windows menus whenever the list of windows changes.
262 menu = self.menudict['windows']
263 end = menu.index("end")
264 if end is None:
265 end = -1
266 if end > self.wmenu_end:
267 menu.delete(self.wmenu_end+1, end)
268 WindowList.add_windows_to_menu(menu)
269
270 rmenu = None
271
272 def right_menu_event(self, event):
273 self.text.tag_remove("sel", "1.0", "end")
274 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
275 if not self.rmenu:
276 self.make_rmenu()
277 rmenu = self.rmenu
278 self.event = event
279 iswin = sys.platform[:3] == 'win'
280 if iswin:
281 self.text.config(cursor="arrow")
282 rmenu.tk_popup(event.x_root, event.y_root)
283 if iswin:
284 self.text.config(cursor="ibeam")
285
286 rmenu_specs = [
287 # ("Label", "<<virtual-event>>"), ...
288 ("Close", "<<close-window>>"), # Example
289 ]
290
291 def make_rmenu(self):
292 rmenu = Menu(self.text, tearoff=0)
293 for label, eventname in self.rmenu_specs:
294 def command(text=self.text, eventname=eventname):
295 text.event_generate(eventname)
296 rmenu.add_command(label=label, command=command)
297 self.rmenu = rmenu
298
299 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000300 aboutDialog.AboutDialog(self.top,'About IDLEfork')
301
Steven M. Gava3b55a892001-11-21 05:56:26 +0000302 def config_dialog(self, event=None):
303 configDialog.ConfigDialog(self.top,'Settings')
304
David Scherer7aced172000-08-15 01:13:23 +0000305 def good_advice(self, event=None):
306 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
307
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000308 def view_readme(self, event=None):
309 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
310 textView.TextViewer(self.top,'IDLEfork - README',fn)
311
David Scherer7aced172000-08-15 01:13:23 +0000312 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000313 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
314 textView.TextViewer(self.top,'Help',fn)
315
David Scherer7aced172000-08-15 01:13:23 +0000316 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000317 if sys.platform[:3] == "win":
318 fn = os.path.dirname(__file__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000319 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000320 fn = os.path.normpath(fn)
321 if os.path.isfile(fn):
322 help_url = fn
323 del fn
David Scherer7aced172000-08-15 01:13:23 +0000324
325 def python_docs(self, event=None):
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000326 webbrowser.open(self.help_url)
David Scherer7aced172000-08-15 01:13:23 +0000327
328 def select_all(self, event=None):
329 self.text.tag_add("sel", "1.0", "end-1c")
330 self.text.mark_set("insert", "1.0")
331 self.text.see("insert")
332 return "break"
333
334 def remove_selection(self, event=None):
335 self.text.tag_remove("sel", "1.0", "end")
336 self.text.see("insert")
337
Steven M. Gavac5976402002-01-04 03:06:08 +0000338 def find_event(self, event):
339 SearchDialog.find(self.text)
340 return "break"
341
342 def find_again_event(self, event):
343 SearchDialog.find_again(self.text)
344 return "break"
345
346 def find_selection_event(self, event):
347 SearchDialog.find_selection(self.text)
348 return "break"
349
350 def find_in_files_event(self, event):
351 GrepDialog.grep(self.text, self.io, self.flist)
352 return "break"
353
354 def replace_event(self, event):
355 ReplaceDialog.replace(self.text)
356 return "break"
357
358 def goto_line_event(self, event):
359 text = self.text
360 lineno = tkSimpleDialog.askinteger("Goto",
361 "Go to line number:",parent=text)
362 if lineno is None:
363 return "break"
364 if lineno <= 0:
365 text.bell()
366 return "break"
367 text.mark_set("insert", "%d.0" % lineno)
368 text.see("insert")
369
David Scherer7aced172000-08-15 01:13:23 +0000370 def open_module(self, event=None):
371 # XXX Shouldn't this be in IOBinding or in FileList?
372 try:
373 name = self.text.get("sel.first", "sel.last")
374 except TclError:
375 name = ""
376 else:
377 name = string.strip(name)
378 if not name:
379 name = tkSimpleDialog.askstring("Module",
380 "Enter the name of a Python module\n"
381 "to search on sys.path and open:",
382 parent=self.text)
383 if name:
384 name = string.strip(name)
385 if not name:
386 return
387 # XXX Ought to support package syntax
388 # XXX Ought to insert current file's directory in front of path
389 try:
390 (f, file, (suffix, mode, type)) = imp.find_module(name)
391 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:
432 return 1
433 base, ext = os.path.splitext(os.path.basename(filename))
434 if os.path.normcase(ext) in (".py", ".pyw"):
435 return 1
436 try:
437 f = open(filename)
438 line = f.readline()
439 f.close()
440 except IOError:
441 return 0
442 return line[:2] == '#!' and string.find(line, 'python') >= 0
443
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):
479 #this function is called from configDialog.py
480 #to update the colour theme if it is changed
481 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):
486 #this function is called from configDialog.py
487 #to update the text widgets' font if it is changed
488 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):
496 #this function is called from configDialog.py
497 #to update the keybindings if they are changed
498 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
David Scherer7aced172000-08-15 01:13:23 +0000528 def saved_change_hook(self):
529 short = self.short_title()
530 long = self.long_title()
531 if short and long:
532 title = short + " - " + long
533 elif short:
534 title = short
535 elif long:
536 title = long
537 else:
538 title = "Untitled"
539 icon = short or long or title
540 if not self.get_saved():
541 title = "*%s*" % title
542 icon = "*%s" % icon
543 self.top.wm_title(title)
544 self.top.wm_iconname(icon)
545
546 def get_saved(self):
547 return self.undo.get_saved()
548
549 def set_saved(self, flag):
550 self.undo.set_saved(flag)
551
552 def reset_undo(self):
553 self.undo.reset_undo()
554
555 def short_title(self):
556 filename = self.io.filename
557 if filename:
558 filename = os.path.basename(filename)
559 return filename
560
561 def long_title(self):
562 return self.io.filename or ""
563
564 def center_insert_event(self, event):
565 self.center()
566
567 def center(self, mark="insert"):
568 text = self.text
569 top, bot = self.getwindowlines()
570 lineno = self.getlineno(mark)
571 height = bot - top
572 newtop = max(1, lineno - height/2)
573 text.yview(float(newtop))
574
575 def getwindowlines(self):
576 text = self.text
577 top = self.getlineno("@0,0")
578 bot = self.getlineno("@0,65535")
579 if top == bot and text.winfo_height() == 1:
580 # Geometry manager hasn't run yet
581 height = int(text['height'])
582 bot = top + height - 1
583 return top, bot
584
585 def getlineno(self, mark="insert"):
586 text = self.text
587 return int(float(text.index(mark)))
588
589 def close_event(self, event):
590 self.close()
591
592 def maybesave(self):
593 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000594 if not self.get_saved():
595 if self.top.state()!='normal':
596 self.top.deiconify()
597 self.top.lower()
598 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000599 return self.io.maybesave()
600
601 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000602 reply = self.maybesave()
603 if reply != "cancel":
604 self._close()
605 return reply
606
607 def _close(self):
608 WindowList.unregister_callback(self.postwindowsmenu)
609 if self.close_hook:
610 self.close_hook()
611 self.flist = None
612 colorizing = 0
613 self.unload_extensions()
614 self.io.close(); self.io = None
615 self.undo = None # XXX
616 if self.color:
617 colorizing = self.color.colorizing
618 doh = colorizing and self.top
619 self.color.close(doh) # Cancel colorization
620 self.text = None
621 self.vars = None
622 self.per.close(); self.per = None
623 if not colorizing:
624 self.top.destroy()
625
626 def load_extensions(self):
627 self.extensions = {}
628 self.load_standard_extensions()
629
630 def unload_extensions(self):
631 for ins in self.extensions.values():
632 if hasattr(ins, "close"):
633 ins.close()
634 self.extensions = {}
635
636 def load_standard_extensions(self):
637 for name in self.get_standard_extension_names():
638 try:
639 self.load_extension(name)
640 except:
641 print "Failed to load extension", `name`
642 import traceback
643 traceback.print_exc()
644
645 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000646 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000647
648 def load_extension(self, name):
649 mod = __import__(name, globals(), locals(), [])
650 cls = getattr(mod, name)
651 ins = cls(self)
652 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000653 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000654 if keydefs:
655 self.apply_bindings(keydefs)
656 for vevent in keydefs.keys():
657 methodname = string.replace(vevent, "-", "_")
658 while methodname[:1] == '<':
659 methodname = methodname[1:]
660 while methodname[-1:] == '>':
661 methodname = methodname[:-1]
662 methodname = methodname + "_event"
663 if hasattr(ins, methodname):
664 self.text.bind(vevent, getattr(ins, methodname))
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000665
David Scherer7aced172000-08-15 01:13:23 +0000666 if hasattr(ins, "menudefs"):
667 self.fill_menus(ins.menudefs, keydefs)
668 return ins
669
670 def apply_bindings(self, keydefs=None):
671 if keydefs is None:
672 keydefs = self.Bindings.default_keydefs
673 text = self.text
674 text.keydefs = keydefs
675 for event, keylist in keydefs.items():
676 if keylist:
677 apply(text.event_add, (event,) + tuple(keylist))
678
679 def fill_menus(self, defs=None, keydefs=None):
680 # Fill the menus. Menus that are absent or None in
681 # self.menudict are ignored.
682 if defs is None:
683 defs = self.Bindings.menudefs
684 if keydefs is None:
685 keydefs = self.Bindings.default_keydefs
686 menudict = self.menudict
687 text = self.text
688 for mname, itemlist in defs:
689 menu = menudict.get(mname)
690 if not menu:
691 continue
692 for item in itemlist:
693 if not item:
694 menu.add_separator()
695 else:
696 label, event = item
697 checkbutton = (label[:1] == '!')
698 if checkbutton:
699 label = label[1:]
700 underline, label = prepstr(label)
701 accelerator = get_accelerator(keydefs, event)
702 def command(text=text, event=event):
703 text.event_generate(event)
704 if checkbutton:
705 var = self.getrawvar(event, BooleanVar)
706 menu.add_checkbutton(label=label, underline=underline,
707 command=command, accelerator=accelerator,
708 variable=var)
709 else:
710 menu.add_command(label=label, underline=underline,
711 command=command, accelerator=accelerator)
712
713 def getvar(self, name):
714 var = self.getrawvar(name)
715 if var:
716 return var.get()
717
718 def setvar(self, name, value, vartype=None):
719 var = self.getrawvar(name, vartype)
720 if var:
721 var.set(value)
722
723 def getrawvar(self, name, vartype=None):
724 var = self.vars.get(name)
725 if not var and vartype:
726 self.vars[name] = var = vartype(self.text)
727 return var
728
729 # Tk implementations of "virtual text methods" -- each platform
730 # reusing IDLE's support code needs to define these for its GUI's
731 # flavor of widget.
732
733 # Is character at text_index in a Python string? Return 0 for
734 # "guaranteed no", true for anything else. This info is expensive
735 # to compute ab initio, but is probably already known by the
736 # platform's colorizer.
737
738 def is_char_in_string(self, text_index):
739 if self.color:
740 # Return true iff colorizer hasn't (re)gotten this far
741 # yet, or the character is tagged as being in a string
742 return self.text.tag_prevrange("TODO", text_index) or \
743 "STRING" in self.text.tag_names(text_index)
744 else:
745 # The colorizer is missing: assume the worst
746 return 1
747
748 # If a selection is defined in the text widget, return (start,
749 # end) as Tkinter text indices, otherwise return (None, None)
750 def get_selection_indices(self):
751 try:
752 first = self.text.index("sel.first")
753 last = self.text.index("sel.last")
754 return first, last
755 except TclError:
756 return None, None
757
758 # Return the text widget's current view of what a tab stop means
759 # (equivalent width in spaces).
760
761 def get_tabwidth(self):
762 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
763 return int(current)
764
765 # Set the text widget's current view of what a tab stop means.
766
767 def set_tabwidth(self, newtabwidth):
768 text = self.text
769 if self.get_tabwidth() != newtabwidth:
770 pixels = text.tk.call("font", "measure", text["font"],
771 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000772 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000773 text.configure(tabs=pixels)
774
775def prepstr(s):
776 # Helper to extract the underscore from a string, e.g.
777 # prepstr("Co_py") returns (2, "Copy").
778 i = string.find(s, '_')
779 if i >= 0:
780 s = s[:i] + s[i+1:]
781 return i, s
782
783
784keynames = {
785 'bracketleft': '[',
786 'bracketright': ']',
787 'slash': '/',
788}
789
790def get_accelerator(keydefs, event):
791 keylist = keydefs.get(event)
792 if not keylist:
793 return ""
794 s = keylist[0]
795 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
796 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
797 s = re.sub("Key-", "", s)
798 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
799 s = re.sub("Control-", "Ctrl-", s)
800 s = re.sub("-", "+", s)
801 s = re.sub("><", " ", s)
802 s = re.sub("<", "", s)
803 s = re.sub(">", "", s)
804 return s
805
806
807def fixwordbreaks(root):
808 # Make sure that Tk's double-click and next/previous word
809 # operations use our definition of a word (i.e. an identifier)
810 tk = root.tk
811 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
812 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
813 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
814
815
816def test():
817 root = Tk()
818 fixwordbreaks(root)
819 root.withdraw()
820 if sys.argv[1:]:
821 filename = sys.argv[1]
822 else:
823 filename = None
824 edit = EditorWindow(root=root, filename=filename)
825 edit.set_close_hook(root.quit)
826 root.mainloop()
827 root.destroy()
828
829if __name__ == '__main__':
830 test()