blob: bba1702e4d120b5961af0b6cc1f25bbb219b0750 [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)
105 self.vbar = vbar = Scrollbar(top, name='vbar')
106 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +0000107 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
108 foreground=idleConf.GetHighlight(currentTheme,
109 'normal',fgBg='fg'),
110 background=idleConf.GetHighlight(currentTheme,
111 'normal',fgBg='bg'),
112 highlightcolor=idleConf.GetHighlight(currentTheme,
113 'hilite',fgBg='fg'),
114 highlightbackground=idleConf.GetHighlight(currentTheme,
115 'hilite',fgBg='bg'),
116 insertbackground=idleConf.GetHighlight(currentTheme,
117 'cursor',fgBg='fg'),
118 width=idleConf.GetOption('main','EditorWindow','width'),
119 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000120
121 self.createmenubar()
122 self.apply_bindings()
123
124 self.top.protocol("WM_DELETE_WINDOW", self.close)
125 self.top.bind("<<close-window>>", self.close_event)
126 text.bind("<<center-insert>>", self.center_insert_event)
127 text.bind("<<help>>", self.help_dialog)
128 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000129 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000130 text.bind("<<python-docs>>", self.python_docs)
131 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000132 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000133 text.bind("<<open-module>>", self.open_module)
134 text.bind("<<do-nothing>>", lambda event: "break")
135 text.bind("<<select-all>>", self.select_all)
136 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000137 text.bind("<<find>>", self.find_event)
138 text.bind("<<find-again>>", self.find_again_event)
139 text.bind("<<find-in-files>>", self.find_in_files_event)
140 text.bind("<<find-selection>>", self.find_selection_event)
141 text.bind("<<replace>>", self.replace_event)
142 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000143 text.bind("<3>", self.right_menu_event)
144 if flist:
145 flist.inversedict[self] = key
146 if key:
147 flist.dict[key] = self
148 text.bind("<<open-new-window>>", self.flist.new_callback)
149 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
150 text.bind("<<open-class-browser>>", self.open_class_browser)
151 text.bind("<<open-path-browser>>", self.open_path_browser)
152
Steven M. Gava898a3652001-10-07 11:10:44 +0000153 self.set_status_bar()
154
David Scherer7aced172000-08-15 01:13:23 +0000155 vbar['command'] = text.yview
156 vbar.pack(side=RIGHT, fill=Y)
157
158 text['yscrollcommand'] = vbar.set
Steven M. Gavadc72f482002-01-03 11:51:07 +0000159 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
160 idleConf.GetOption('main','EditorWindow','font-size')))
David Scherer7aced172000-08-15 01:13:23 +0000161 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
162 text.pack(side=TOP, fill=BOTH, expand=1)
163 text.focus_set()
164
165 self.per = per = self.Percolator(text)
166 if self.ispythonsource(filename):
167 self.color = color = self.ColorDelegator(); per.insertfilter(color)
168 ##print "Initial colorizer"
169 else:
170 ##print "No initial colorizer"
171 self.color = None
172 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
173 self.io = io = self.IOBinding(self)
174
175 text.undo_block_start = undo.undo_block_start
176 text.undo_block_stop = undo.undo_block_stop
177 undo.set_saved_change_hook(self.saved_change_hook)
178 io.set_filename_change_hook(self.filename_change_hook)
179
180 if filename:
181 if os.path.exists(filename):
182 io.loadfile(filename)
183 else:
184 io.set_filename(filename)
185
186 self.saved_change_hook()
187
188 self.load_extensions()
189
190 menu = self.menudict.get('windows')
191 if menu:
192 end = menu.index("end")
193 if end is None:
194 end = -1
195 if end >= 0:
196 menu.add_separator()
197 end = end + 1
198 self.wmenu_end = end
199 WindowList.register_callback(self.postwindowsmenu)
200
201 # Some abstractions so IDLE extensions are cross-IDE
202 self.askyesno = tkMessageBox.askyesno
203 self.askinteger = tkSimpleDialog.askinteger
204 self.showerror = tkMessageBox.showerror
205
206 if self.extensions.has_key('AutoIndent'):
207 self.extensions['AutoIndent'].set_indentation_params(
208 self.ispythonsource(filename))
David Scherer7aced172000-08-15 01:13:23 +0000209
210 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000211 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000212 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
213 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
214 self.status_bar.pack(side=BOTTOM, fill=X)
215 self.text.bind('<KeyRelease>', self.set_line_and_column)
216 self.text.bind('<ButtonRelease>', self.set_line_and_column)
217 self.text.after_idle(self.set_line_and_column)
218
219 def set_line_and_column(self, event=None):
220 line, column = string.split(self.text.index(INSERT), '.')
221 self.status_bar.set_label('column', 'Col: %s' % column)
222 self.status_bar.set_label('line', 'Ln: %s' % line)
223
224 def wakeup(self):
225 if self.top.wm_state() == "iconic":
226 self.top.wm_deiconify()
227 else:
228 self.top.tkraise()
229 self.text.focus_set()
230
231 menu_specs = [
232 ("file", "_File"),
233 ("edit", "_Edit"),
234 ("format", "F_ormat"),
235 ("run", "_Run"),
Steven M. Gava3b55a892001-11-21 05:56:26 +0000236 #("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000237 ("windows", "_Windows"),
238 ("help", "_Help"),
239 ]
240
241 def createmenubar(self):
242 mbar = self.menubar
243 self.menudict = menudict = {}
244 for name, label in self.menu_specs:
245 underline, label = prepstr(label)
246 menudict[name] = menu = Menu(mbar, name=name)
247 mbar.add_cascade(label=label, menu=menu, underline=underline)
248 self.fill_menus()
249
250 def postwindowsmenu(self):
251 # Only called when Windows menu exists
252 # XXX Actually, this Just-In-Time updating interferes badly
253 # XXX with the tear-off feature. It would be better to update
254 # XXX all Windows menus whenever the list of windows changes.
255 menu = self.menudict['windows']
256 end = menu.index("end")
257 if end is None:
258 end = -1
259 if end > self.wmenu_end:
260 menu.delete(self.wmenu_end+1, end)
261 WindowList.add_windows_to_menu(menu)
262
263 rmenu = None
264
265 def right_menu_event(self, event):
266 self.text.tag_remove("sel", "1.0", "end")
267 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
268 if not self.rmenu:
269 self.make_rmenu()
270 rmenu = self.rmenu
271 self.event = event
272 iswin = sys.platform[:3] == 'win'
273 if iswin:
274 self.text.config(cursor="arrow")
275 rmenu.tk_popup(event.x_root, event.y_root)
276 if iswin:
277 self.text.config(cursor="ibeam")
278
279 rmenu_specs = [
280 # ("Label", "<<virtual-event>>"), ...
281 ("Close", "<<close-window>>"), # Example
282 ]
283
284 def make_rmenu(self):
285 rmenu = Menu(self.text, tearoff=0)
286 for label, eventname in self.rmenu_specs:
287 def command(text=self.text, eventname=eventname):
288 text.event_generate(eventname)
289 rmenu.add_command(label=label, command=command)
290 self.rmenu = rmenu
291
292 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000293 aboutDialog.AboutDialog(self.top,'About IDLEfork')
294
Steven M. Gava3b55a892001-11-21 05:56:26 +0000295 def config_dialog(self, event=None):
296 configDialog.ConfigDialog(self.top,'Settings')
297
David Scherer7aced172000-08-15 01:13:23 +0000298 def good_advice(self, event=None):
299 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
300
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000301 def view_readme(self, event=None):
302 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
303 textView.TextViewer(self.top,'IDLEfork - README',fn)
304
David Scherer7aced172000-08-15 01:13:23 +0000305 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000306 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
307 textView.TextViewer(self.top,'Help',fn)
308
David Scherer7aced172000-08-15 01:13:23 +0000309 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000310 if sys.platform[:3] == "win":
311 fn = os.path.dirname(__file__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000312 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000313 fn = os.path.normpath(fn)
314 if os.path.isfile(fn):
315 help_url = fn
316 del fn
David Scherer7aced172000-08-15 01:13:23 +0000317
318 def python_docs(self, event=None):
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000319 webbrowser.open(self.help_url)
David Scherer7aced172000-08-15 01:13:23 +0000320
321 def select_all(self, event=None):
322 self.text.tag_add("sel", "1.0", "end-1c")
323 self.text.mark_set("insert", "1.0")
324 self.text.see("insert")
325 return "break"
326
327 def remove_selection(self, event=None):
328 self.text.tag_remove("sel", "1.0", "end")
329 self.text.see("insert")
330
Steven M. Gavac5976402002-01-04 03:06:08 +0000331 def find_event(self, event):
332 SearchDialog.find(self.text)
333 return "break"
334
335 def find_again_event(self, event):
336 SearchDialog.find_again(self.text)
337 return "break"
338
339 def find_selection_event(self, event):
340 SearchDialog.find_selection(self.text)
341 return "break"
342
343 def find_in_files_event(self, event):
344 GrepDialog.grep(self.text, self.io, self.flist)
345 return "break"
346
347 def replace_event(self, event):
348 ReplaceDialog.replace(self.text)
349 return "break"
350
351 def goto_line_event(self, event):
352 text = self.text
353 lineno = tkSimpleDialog.askinteger("Goto",
354 "Go to line number:",parent=text)
355 if lineno is None:
356 return "break"
357 if lineno <= 0:
358 text.bell()
359 return "break"
360 text.mark_set("insert", "%d.0" % lineno)
361 text.see("insert")
362
David Scherer7aced172000-08-15 01:13:23 +0000363 def open_module(self, event=None):
364 # XXX Shouldn't this be in IOBinding or in FileList?
365 try:
366 name = self.text.get("sel.first", "sel.last")
367 except TclError:
368 name = ""
369 else:
370 name = string.strip(name)
371 if not name:
372 name = tkSimpleDialog.askstring("Module",
373 "Enter the name of a Python module\n"
374 "to search on sys.path and open:",
375 parent=self.text)
376 if name:
377 name = string.strip(name)
378 if not name:
379 return
380 # XXX Ought to support package syntax
381 # XXX Ought to insert current file's directory in front of path
382 try:
383 (f, file, (suffix, mode, type)) = imp.find_module(name)
384 except (NameError, ImportError), msg:
385 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
386 return
387 if type != imp.PY_SOURCE:
388 tkMessageBox.showerror("Unsupported type",
389 "%s is not a source module" % name, parent=self.text)
390 return
391 if f:
392 f.close()
393 if self.flist:
394 self.flist.open(file)
395 else:
396 self.io.loadfile(file)
397
398 def open_class_browser(self, event=None):
399 filename = self.io.filename
400 if not filename:
401 tkMessageBox.showerror(
402 "No filename",
403 "This buffer has no associated filename",
404 master=self.text)
405 self.text.focus_set()
406 return None
407 head, tail = os.path.split(filename)
408 base, ext = os.path.splitext(tail)
409 import ClassBrowser
410 ClassBrowser.ClassBrowser(self.flist, base, [head])
411
412 def open_path_browser(self, event=None):
413 import PathBrowser
414 PathBrowser.PathBrowser(self.flist)
415
416 def gotoline(self, lineno):
417 if lineno is not None and lineno > 0:
418 self.text.mark_set("insert", "%d.0" % lineno)
419 self.text.tag_remove("sel", "1.0", "end")
420 self.text.tag_add("sel", "insert", "insert +1l")
421 self.center()
422
423 def ispythonsource(self, filename):
424 if not filename:
425 return 1
426 base, ext = os.path.splitext(os.path.basename(filename))
427 if os.path.normcase(ext) in (".py", ".pyw"):
428 return 1
429 try:
430 f = open(filename)
431 line = f.readline()
432 f.close()
433 except IOError:
434 return 0
435 return line[:2] == '#!' and string.find(line, 'python') >= 0
436
437 def close_hook(self):
438 if self.flist:
439 self.flist.close_edit(self)
440
441 def set_close_hook(self, close_hook):
442 self.close_hook = close_hook
443
444 def filename_change_hook(self):
445 if self.flist:
446 self.flist.filename_changed_edit(self)
447 self.saved_change_hook()
448 if self.ispythonsource(self.io.filename):
449 self.addcolorizer()
450 else:
451 self.rmcolorizer()
452
453 def addcolorizer(self):
454 if self.color:
455 return
456 ##print "Add colorizer"
457 self.per.removefilter(self.undo)
458 self.color = self.ColorDelegator()
459 self.per.insertfilter(self.color)
460 self.per.insertfilter(self.undo)
461
462 def rmcolorizer(self):
463 if not self.color:
464 return
465 ##print "Remove colorizer"
466 self.per.removefilter(self.undo)
467 self.per.removefilter(self.color)
468 self.color = None
469 self.per.insertfilter(self.undo)
470
471 def saved_change_hook(self):
472 short = self.short_title()
473 long = self.long_title()
474 if short and long:
475 title = short + " - " + long
476 elif short:
477 title = short
478 elif long:
479 title = long
480 else:
481 title = "Untitled"
482 icon = short or long or title
483 if not self.get_saved():
484 title = "*%s*" % title
485 icon = "*%s" % icon
486 self.top.wm_title(title)
487 self.top.wm_iconname(icon)
488
489 def get_saved(self):
490 return self.undo.get_saved()
491
492 def set_saved(self, flag):
493 self.undo.set_saved(flag)
494
495 def reset_undo(self):
496 self.undo.reset_undo()
497
498 def short_title(self):
499 filename = self.io.filename
500 if filename:
501 filename = os.path.basename(filename)
502 return filename
503
504 def long_title(self):
505 return self.io.filename or ""
506
507 def center_insert_event(self, event):
508 self.center()
509
510 def center(self, mark="insert"):
511 text = self.text
512 top, bot = self.getwindowlines()
513 lineno = self.getlineno(mark)
514 height = bot - top
515 newtop = max(1, lineno - height/2)
516 text.yview(float(newtop))
517
518 def getwindowlines(self):
519 text = self.text
520 top = self.getlineno("@0,0")
521 bot = self.getlineno("@0,65535")
522 if top == bot and text.winfo_height() == 1:
523 # Geometry manager hasn't run yet
524 height = int(text['height'])
525 bot = top + height - 1
526 return top, bot
527
528 def getlineno(self, mark="insert"):
529 text = self.text
530 return int(float(text.index(mark)))
531
532 def close_event(self, event):
533 self.close()
534
535 def maybesave(self):
536 if self.io:
537 return self.io.maybesave()
538
539 def close(self):
540 self.top.wm_deiconify()
541 self.top.tkraise()
542 reply = self.maybesave()
543 if reply != "cancel":
544 self._close()
545 return reply
546
547 def _close(self):
548 WindowList.unregister_callback(self.postwindowsmenu)
549 if self.close_hook:
550 self.close_hook()
551 self.flist = None
552 colorizing = 0
553 self.unload_extensions()
554 self.io.close(); self.io = None
555 self.undo = None # XXX
556 if self.color:
557 colorizing = self.color.colorizing
558 doh = colorizing and self.top
559 self.color.close(doh) # Cancel colorization
560 self.text = None
561 self.vars = None
562 self.per.close(); self.per = None
563 if not colorizing:
564 self.top.destroy()
565
566 def load_extensions(self):
567 self.extensions = {}
568 self.load_standard_extensions()
569
570 def unload_extensions(self):
571 for ins in self.extensions.values():
572 if hasattr(ins, "close"):
573 ins.close()
574 self.extensions = {}
575
576 def load_standard_extensions(self):
577 for name in self.get_standard_extension_names():
578 try:
579 self.load_extension(name)
580 except:
581 print "Failed to load extension", `name`
582 import traceback
583 traceback.print_exc()
584
585 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000586 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000587
588 def load_extension(self, name):
589 mod = __import__(name, globals(), locals(), [])
590 cls = getattr(mod, name)
591 ins = cls(self)
592 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000593 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000594 if keydefs:
595 self.apply_bindings(keydefs)
596 for vevent in keydefs.keys():
597 methodname = string.replace(vevent, "-", "_")
598 while methodname[:1] == '<':
599 methodname = methodname[1:]
600 while methodname[-1:] == '>':
601 methodname = methodname[:-1]
602 methodname = methodname + "_event"
603 if hasattr(ins, methodname):
604 self.text.bind(vevent, getattr(ins, methodname))
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000605
David Scherer7aced172000-08-15 01:13:23 +0000606 if hasattr(ins, "menudefs"):
607 self.fill_menus(ins.menudefs, keydefs)
608 return ins
609
610 def apply_bindings(self, keydefs=None):
611 if keydefs is None:
612 keydefs = self.Bindings.default_keydefs
613 text = self.text
614 text.keydefs = keydefs
615 for event, keylist in keydefs.items():
616 if keylist:
617 apply(text.event_add, (event,) + tuple(keylist))
618
619 def fill_menus(self, defs=None, keydefs=None):
620 # Fill the menus. Menus that are absent or None in
621 # self.menudict are ignored.
622 if defs is None:
623 defs = self.Bindings.menudefs
624 if keydefs is None:
625 keydefs = self.Bindings.default_keydefs
626 menudict = self.menudict
627 text = self.text
628 for mname, itemlist in defs:
629 menu = menudict.get(mname)
630 if not menu:
631 continue
632 for item in itemlist:
633 if not item:
634 menu.add_separator()
635 else:
636 label, event = item
637 checkbutton = (label[:1] == '!')
638 if checkbutton:
639 label = label[1:]
640 underline, label = prepstr(label)
641 accelerator = get_accelerator(keydefs, event)
642 def command(text=text, event=event):
643 text.event_generate(event)
644 if checkbutton:
645 var = self.getrawvar(event, BooleanVar)
646 menu.add_checkbutton(label=label, underline=underline,
647 command=command, accelerator=accelerator,
648 variable=var)
649 else:
650 menu.add_command(label=label, underline=underline,
651 command=command, accelerator=accelerator)
652
653 def getvar(self, name):
654 var = self.getrawvar(name)
655 if var:
656 return var.get()
657
658 def setvar(self, name, value, vartype=None):
659 var = self.getrawvar(name, vartype)
660 if var:
661 var.set(value)
662
663 def getrawvar(self, name, vartype=None):
664 var = self.vars.get(name)
665 if not var and vartype:
666 self.vars[name] = var = vartype(self.text)
667 return var
668
669 # Tk implementations of "virtual text methods" -- each platform
670 # reusing IDLE's support code needs to define these for its GUI's
671 # flavor of widget.
672
673 # Is character at text_index in a Python string? Return 0 for
674 # "guaranteed no", true for anything else. This info is expensive
675 # to compute ab initio, but is probably already known by the
676 # platform's colorizer.
677
678 def is_char_in_string(self, text_index):
679 if self.color:
680 # Return true iff colorizer hasn't (re)gotten this far
681 # yet, or the character is tagged as being in a string
682 return self.text.tag_prevrange("TODO", text_index) or \
683 "STRING" in self.text.tag_names(text_index)
684 else:
685 # The colorizer is missing: assume the worst
686 return 1
687
688 # If a selection is defined in the text widget, return (start,
689 # end) as Tkinter text indices, otherwise return (None, None)
690 def get_selection_indices(self):
691 try:
692 first = self.text.index("sel.first")
693 last = self.text.index("sel.last")
694 return first, last
695 except TclError:
696 return None, None
697
698 # Return the text widget's current view of what a tab stop means
699 # (equivalent width in spaces).
700
701 def get_tabwidth(self):
702 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
703 return int(current)
704
705 # Set the text widget's current view of what a tab stop means.
706
707 def set_tabwidth(self, newtabwidth):
708 text = self.text
709 if self.get_tabwidth() != newtabwidth:
710 pixels = text.tk.call("font", "measure", text["font"],
711 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000712 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000713 text.configure(tabs=pixels)
714
715def prepstr(s):
716 # Helper to extract the underscore from a string, e.g.
717 # prepstr("Co_py") returns (2, "Copy").
718 i = string.find(s, '_')
719 if i >= 0:
720 s = s[:i] + s[i+1:]
721 return i, s
722
723
724keynames = {
725 'bracketleft': '[',
726 'bracketright': ']',
727 'slash': '/',
728}
729
730def get_accelerator(keydefs, event):
731 keylist = keydefs.get(event)
732 if not keylist:
733 return ""
734 s = keylist[0]
735 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
736 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
737 s = re.sub("Key-", "", s)
738 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
739 s = re.sub("Control-", "Ctrl-", s)
740 s = re.sub("-", "+", s)
741 s = re.sub("><", " ", s)
742 s = re.sub("<", "", s)
743 s = re.sub(">", "", s)
744 return s
745
746
747def fixwordbreaks(root):
748 # Make sure that Tk's double-click and next/previous word
749 # operations use our definition of a word (i.e. an identifier)
750 tk = root.tk
751 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
752 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
753 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
754
755
756def test():
757 root = Tk()
758 fixwordbreaks(root)
759 root.withdraw()
760 if sys.argv[1:]:
761 filename = sys.argv[1]
762 else:
763 filename = None
764 edit = EditorWindow(root=root, filename=filename)
765 edit.set_close_hook(root.quit)
766 root.mainloop()
767 root.destroy()
768
769if __name__ == '__main__':
770 test()