5 from itertools
import count
9 from MultiCall
import MultiCallCreator
18 from configHandler
import idleConf
19 import aboutDialog
, textView
, configDialog
22 # The default tab setting for a Text widget, in average-width characters.
23 TK_TABWIDTH_DEFAULT
= 8
25 def _find_module(fullname
, path
=None):
26 """Version of imp.find_module() that handles hierarchical module names"""
29 for tgt
in fullname
.split('.'):
31 file.close() # close intermediate files
32 (file, filename
, descr
) = imp
.find_module(tgt
, path
)
33 if descr
[2] == imp
.PY_SOURCE
:
34 break # find but not load the source file
35 module
= imp
.load_module(tgt
, file, filename
, descr
)
37 path
= module
.__path
__
38 except AttributeError:
39 raise ImportError, 'No source for module ' + module
.__name
__
40 return file, filename
, descr
42 class EditorWindow(object):
43 from Percolator
import Percolator
44 from ColorDelegator
import ColorDelegator
45 from UndoDelegator
import UndoDelegator
46 from IOBinding
import IOBinding
, filesystemencoding
, encoding
48 from Tkinter
import Toplevel
49 from MultiStatusBar
import MultiStatusBar
53 def __init__(self
, flist
=None, filename
=None, key
=None, root
=None):
54 if EditorWindow
.help_url
is None:
55 dochome
= os
.path
.join(sys
.prefix
, 'Doc', 'index.html')
56 if sys
.platform
.count('linux'):
57 # look for html docs in a couple of standard places
58 pyver
= 'python-docs-' + '%s.%s.%s' % sys
.version_info
[:3]
59 if os
.path
.isdir('/var/www/html/python/'): # "python2" rpm
60 dochome
= '/var/www/html/python/index.html'
62 basepath
= '/usr/share/doc/' # standard location
63 dochome
= os
.path
.join(basepath
, pyver
,
65 elif sys
.platform
[:3] == 'win':
66 chmfile
= os
.path
.join(sys
.prefix
, 'Doc',
67 'Python%d%d.chm' % sys
.version_info
[:2])
68 if os
.path
.isfile(chmfile
):
71 elif macosxSupport
.runningAsOSXApp():
72 # documentation is stored inside the python framework
73 dochome
= os
.path
.join(sys
.prefix
,
74 'Resources/English.lproj/Documentation/index.html')
76 dochome
= os
.path
.normpath(dochome
)
77 if os
.path
.isfile(dochome
):
78 EditorWindow
.help_url
= dochome
79 if sys
.platform
== 'darwin':
80 # Safari requires real file:-URLs
81 EditorWindow
.help_url
= 'file://' + EditorWindow
.help_url
83 EditorWindow
.help_url
= "http://docs.python.org/%d.%d" % sys
.version_info
[:2]
84 currentTheme
=idleConf
.CurrentTheme()
86 root
= root
or flist
.root
90 except AttributeError:
92 self
.menubar
= Menu(root
)
93 self
.top
= top
= WindowList
.ListedToplevel(root
, menu
=self
.menubar
)
95 self
.tkinter_vars
= flist
.vars
96 #self.top.instance_dict makes flist.inversedict avalable to
97 #configDialog.py so it can access all EditorWindow instaces
98 self
.top
.instance_dict
= flist
.inversedict
100 self
.tkinter_vars
= {} # keys: Tkinter event names
101 # values: Tkinter variable instances
102 self
.top
.instance_dict
= {}
103 self
.recent_files_path
= os
.path
.join(idleConf
.GetUserCfgDir(),
105 self
.text_frame
= text_frame
= Frame(top
)
106 self
.vbar
= vbar
= Scrollbar(text_frame
, name
='vbar')
107 self
.width
= idleConf
.GetOption('main','EditorWindow','width')
108 self
.text
= text
= MultiCallCreator(Text
)(
109 text_frame
, name
='text', padx
=5, wrap
='none',
111 height
=idleConf
.GetOption('main','EditorWindow','height') )
112 self
.top
.focused_widget
= self
.text
115 self
.apply_bindings()
117 self
.top
.protocol("WM_DELETE_WINDOW", self
.close
)
118 self
.top
.bind("<<close-window>>", self
.close_event
)
119 if macosxSupport
.runningAsOSXApp():
120 # Command-W on editorwindows doesn't work without this.
121 text
.bind('<<close-window>>', self
.close_event
)
122 text
.bind("<<cut>>", self
.cut
)
123 text
.bind("<<copy>>", self
.copy
)
124 text
.bind("<<paste>>", self
.paste
)
125 text
.bind("<<center-insert>>", self
.center_insert_event
)
126 text
.bind("<<help>>", self
.help_dialog
)
127 text
.bind("<<python-docs>>", self
.python_docs
)
128 text
.bind("<<about-idle>>", self
.about_dialog
)
129 text
.bind("<<open-config-dialog>>", self
.config_dialog
)
130 text
.bind("<<open-module>>", self
.open_module
)
131 text
.bind("<<do-nothing>>", lambda event
: "break")
132 text
.bind("<<select-all>>", self
.select_all
)
133 text
.bind("<<remove-selection>>", self
.remove_selection
)
134 text
.bind("<<find>>", self
.find_event
)
135 text
.bind("<<find-again>>", self
.find_again_event
)
136 text
.bind("<<find-in-files>>", self
.find_in_files_event
)
137 text
.bind("<<find-selection>>", self
.find_selection_event
)
138 text
.bind("<<replace>>", self
.replace_event
)
139 text
.bind("<<goto-line>>", self
.goto_line_event
)
140 text
.bind("<3>", self
.right_menu_event
)
141 text
.bind("<<smart-backspace>>",self
.smart_backspace_event
)
142 text
.bind("<<newline-and-indent>>",self
.newline_and_indent_event
)
143 text
.bind("<<smart-indent>>",self
.smart_indent_event
)
144 text
.bind("<<indent-region>>",self
.indent_region_event
)
145 text
.bind("<<dedent-region>>",self
.dedent_region_event
)
146 text
.bind("<<comment-region>>",self
.comment_region_event
)
147 text
.bind("<<uncomment-region>>",self
.uncomment_region_event
)
148 text
.bind("<<tabify-region>>",self
.tabify_region_event
)
149 text
.bind("<<untabify-region>>",self
.untabify_region_event
)
150 text
.bind("<<toggle-tabs>>",self
.toggle_tabs_event
)
151 text
.bind("<<change-indentwidth>>",self
.change_indentwidth_event
)
152 text
.bind("<Left>", self
.move_at_edge_if_selection(0))
153 text
.bind("<Right>", self
.move_at_edge_if_selection(1))
154 text
.bind("<<del-word-left>>", self
.del_word_left
)
155 text
.bind("<<del-word-right>>", self
.del_word_right
)
156 text
.bind("<<beginning-of-line>>", self
.home_callback
)
159 flist
.inversedict
[self
] = key
161 flist
.dict[key
] = self
162 text
.bind("<<open-new-window>>", self
.new_callback
)
163 text
.bind("<<close-all-windows>>", self
.flist
.close_all_callback
)
164 text
.bind("<<open-class-browser>>", self
.open_class_browser
)
165 text
.bind("<<open-path-browser>>", self
.open_path_browser
)
167 self
.set_status_bar()
168 vbar
['command'] = text
.yview
169 vbar
.pack(side
=RIGHT
, fill
=Y
)
170 text
['yscrollcommand'] = vbar
.set
171 fontWeight
= 'normal'
172 if idleConf
.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
174 text
.config(font
=(idleConf
.GetOption('main', 'EditorWindow', 'font'),
175 idleConf
.GetOption('main', 'EditorWindow', 'font-size'),
177 text_frame
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
178 text
.pack(side
=TOP
, fill
=BOTH
, expand
=1)
181 # usetabs true -> literal tab characters are used by indent and
182 # dedent cmds, possibly mixed with spaces if
183 # indentwidth is not a multiple of tabwidth,
184 # which will cause Tabnanny to nag!
185 # false -> tab characters are converted to spaces by indent
186 # and dedent cmds, and ditto TAB keystrokes
187 # Although use-spaces=0 can be configured manually in config-main.def,
188 # configuration of tabs v. spaces is not supported in the configuration
189 # dialog. IDLE promotes the preferred Python indentation: use spaces!
190 usespaces
= idleConf
.GetOption('main', 'Indent', 'use-spaces', type='bool')
191 self
.usetabs
= not usespaces
193 # tabwidth is the display width of a literal tab character.
194 # CAUTION: telling Tk to use anything other than its default
195 # tab setting causes it to use an entirely different tabbing algorithm,
196 # treating tab stops as fixed distances from the left margin.
197 # Nobody expects this, so for now tabwidth should never be changed.
198 self
.tabwidth
= 8 # must remain 8 until Tk is fixed.
200 # indentwidth is the number of screen characters per indent level.
201 # The recommended Python indentation is four spaces.
202 self
.indentwidth
= self
.tabwidth
203 self
.set_notabs_indentwidth()
205 # If context_use_ps1 is true, parsing searches back for a ps1 line;
206 # else searches for a popular (if, def, ...) Python stmt.
207 self
.context_use_ps1
= False
209 # When searching backwards for a reliable place to begin parsing,
210 # first start num_context_lines[0] lines back, then
211 # num_context_lines[1] lines back if that didn't work, and so on.
212 # The last value should be huge (larger than the # of lines in a
214 # Making the initial values larger slows things down more often.
215 self
.num_context_lines
= 50, 500, 5000000
217 self
.per
= per
= self
.Percolator(text
)
219 self
.undo
= undo
= self
.UndoDelegator()
220 per
.insertfilter(undo
)
221 text
.undo_block_start
= undo
.undo_block_start
222 text
.undo_block_stop
= undo
.undo_block_stop
223 undo
.set_saved_change_hook(self
.saved_change_hook
)
225 # IOBinding implements file I/O and printing functionality
226 self
.io
= io
= self
.IOBinding(self
)
227 io
.set_filename_change_hook(self
.filename_change_hook
)
229 # Create the recent files submenu
230 self
.recent_files_menu
= Menu(self
.menubar
)
231 self
.menudict
['file'].insert_cascade(3, label
='Recent Files',
233 menu
=self
.recent_files_menu
)
234 self
.update_recent_files_list()
236 self
.color
= None # initialized below in self.ResetColorizer
238 if os
.path
.exists(filename
) and not os
.path
.isdir(filename
):
239 io
.loadfile(filename
)
241 io
.set_filename(filename
)
242 self
.ResetColorizer()
243 self
.saved_change_hook()
245 self
.set_indentation_params(self
.ispythonsource(filename
))
247 self
.load_extensions()
249 menu
= self
.menudict
.get('windows')
251 end
= menu
.index("end")
258 WindowList
.register_callback(self
.postwindowsmenu
)
260 # Some abstractions so IDLE extensions are cross-IDE
261 self
.askyesno
= tkMessageBox
.askyesno
262 self
.askinteger
= tkSimpleDialog
.askinteger
263 self
.showerror
= tkMessageBox
.showerror
265 def _filename_to_unicode(self
, filename
):
266 """convert filename to unicode in order to display it in Tk"""
267 if isinstance(filename
, unicode) or not filename
:
271 return filename
.decode(self
.filesystemencoding
)
272 except UnicodeDecodeError:
275 return filename
.decode(self
.encoding
)
276 except UnicodeDecodeError:
277 # byte-to-byte conversion
278 return filename
.decode('iso8859-1')
280 def new_callback(self
, event
):
281 dirname
, basename
= self
.io
.defaultfilename()
282 self
.flist
.new(dirname
)
285 def home_callback(self
, event
):
286 if (event
.state
& 12) != 0 and event
.keysym
== "Home":
287 # state&1==shift, state&4==control, state&8==alt
288 return # <Modifier-Home>; fall back to class binding
290 if self
.text
.index("iomark") and \
291 self
.text
.compare("iomark", "<=", "insert lineend") and \
292 self
.text
.compare("insert linestart", "<=", "iomark"):
293 insertpt
= int(self
.text
.index("iomark").split(".")[1])
295 line
= self
.text
.get("insert linestart", "insert lineend")
296 for insertpt
in xrange(len(line
)):
297 if line
[insertpt
] not in (' ','\t'):
302 lineat
= int(self
.text
.index("insert").split('.')[1])
304 if insertpt
== lineat
:
307 dest
= "insert linestart+"+str(insertpt
)+"c"
309 if (event
.state
&1) == 0:
311 self
.text
.tag_remove("sel", "1.0", "end")
313 if not self
.text
.index("sel.first"):
314 self
.text
.mark_set("anchor","insert")
316 first
= self
.text
.index(dest
)
317 last
= self
.text
.index("anchor")
319 if self
.text
.compare(first
,">",last
):
320 first
,last
= last
,first
322 self
.text
.tag_remove("sel", "1.0", "end")
323 self
.text
.tag_add("sel", first
, last
)
325 self
.text
.mark_set("insert", dest
)
326 self
.text
.see("insert")
329 def set_status_bar(self
):
330 self
.status_bar
= self
.MultiStatusBar(self
.top
)
331 if macosxSupport
.runningAsOSXApp():
332 # Insert some padding to avoid obscuring some of the statusbar
333 # by the resize widget.
334 self
.status_bar
.set_label('_padding1', ' ', side
=RIGHT
)
335 self
.status_bar
.set_label('column', 'Col: ?', side
=RIGHT
)
336 self
.status_bar
.set_label('line', 'Ln: ?', side
=RIGHT
)
337 self
.status_bar
.pack(side
=BOTTOM
, fill
=X
)
338 self
.text
.bind("<<set-line-and-column>>", self
.set_line_and_column
)
339 self
.text
.event_add("<<set-line-and-column>>",
340 "<KeyRelease>", "<ButtonRelease>")
341 self
.text
.after_idle(self
.set_line_and_column
)
343 def set_line_and_column(self
, event
=None):
344 line
, column
= self
.text
.index(INSERT
).split('.')
345 self
.status_bar
.set_label('column', 'Col: %s' % column
)
346 self
.status_bar
.set_label('line', 'Ln: %s' % line
)
351 ("format", "F_ormat"),
353 ("options", "_Options"),
354 ("windows", "_Windows"),
358 if macosxSupport
.runningAsOSXApp():
360 menu_specs
[-2] = ("windows", "_Window")
363 def createmenubar(self
):
365 self
.menudict
= menudict
= {}
366 for name
, label
in self
.menu_specs
:
367 underline
, label
= prepstr(label
)
368 menudict
[name
] = menu
= Menu(mbar
, name
=name
)
369 mbar
.add_cascade(label
=label
, menu
=menu
, underline
=underline
)
371 if sys
.platform
== 'darwin' and '.framework' in sys
.executable
:
372 # Insert the application menu
373 menudict
['application'] = menu
= Menu(mbar
, name
='apple')
374 mbar
.add_cascade(label
='IDLE', menu
=menu
)
377 self
.base_helpmenu_length
= self
.menudict
['help'].index(END
)
378 self
.reset_help_menu_entries()
380 def postwindowsmenu(self
):
381 # Only called when Windows menu exists
382 menu
= self
.menudict
['windows']
383 end
= menu
.index("end")
386 if end
> self
.wmenu_end
:
387 menu
.delete(self
.wmenu_end
+1, end
)
388 WindowList
.add_windows_to_menu(menu
)
392 def right_menu_event(self
, event
):
393 self
.text
.tag_remove("sel", "1.0", "end")
394 self
.text
.mark_set("insert", "@%d,%d" % (event
.x
, event
.y
))
399 iswin
= sys
.platform
[:3] == 'win'
401 self
.text
.config(cursor
="arrow")
402 rmenu
.tk_popup(event
.x_root
, event
.y_root
)
404 self
.text
.config(cursor
="ibeam")
407 # ("Label", "<<virtual-event>>"), ...
408 ("Close", "<<close-window>>"), # Example
411 def make_rmenu(self
):
412 rmenu
= Menu(self
.text
, tearoff
=0)
413 for label
, eventname
in self
.rmenu_specs
:
414 def command(text
=self
.text
, eventname
=eventname
):
415 text
.event_generate(eventname
)
416 rmenu
.add_command(label
=label
, command
=command
)
419 def about_dialog(self
, event
=None):
420 aboutDialog
.AboutDialog(self
.top
,'About IDLE')
422 def config_dialog(self
, event
=None):
423 configDialog
.ConfigDialog(self
.top
,'Settings')
425 def help_dialog(self
, event
=None):
426 fn
=os
.path
.join(os
.path
.abspath(os
.path
.dirname(__file__
)),'help.txt')
427 textView
.view_file(self
.top
,'Help',fn
)
429 def python_docs(self
, event
=None):
430 if sys
.platform
[:3] == 'win':
431 os
.startfile(self
.help_url
)
433 webbrowser
.open(self
.help_url
)
437 self
.text
.event_generate("<<Cut>>")
440 def copy(self
,event
):
441 if not self
.text
.tag_ranges("sel"):
442 # There is no selection, so do nothing and maybe interrupt.
444 self
.text
.event_generate("<<Copy>>")
447 def paste(self
,event
):
448 self
.text
.event_generate("<<Paste>>")
449 self
.text
.see("insert")
452 def select_all(self
, event
=None):
453 self
.text
.tag_add("sel", "1.0", "end-1c")
454 self
.text
.mark_set("insert", "1.0")
455 self
.text
.see("insert")
458 def remove_selection(self
, event
=None):
459 self
.text
.tag_remove("sel", "1.0", "end")
460 self
.text
.see("insert")
462 def move_at_edge_if_selection(self
, edge_index
):
463 """Cursor move begins at start or end of selection
465 When a left/right cursor key is pressed create and return to Tkinter a
466 function which causes a cursor move from the associated edge of the
470 self_text_index
= self
.text
.index
471 self_text_mark_set
= self
.text
.mark_set
472 edges_table
= ("sel.first+1c", "sel.last-1c")
473 def move_at_edge(event
):
474 if (event
.state
& 5) == 0: # no shift(==1) or control(==4) pressed
476 self_text_index("sel.first")
477 self_text_mark_set("insert", edges_table
[edge_index
])
482 def del_word_left(self
, event
):
483 self
.text
.event_generate('<Meta-Delete>')
486 def del_word_right(self
, event
):
487 self
.text
.event_generate('<Meta-d>')
490 def find_event(self
, event
):
491 SearchDialog
.find(self
.text
)
494 def find_again_event(self
, event
):
495 SearchDialog
.find_again(self
.text
)
498 def find_selection_event(self
, event
):
499 SearchDialog
.find_selection(self
.text
)
502 def find_in_files_event(self
, event
):
503 GrepDialog
.grep(self
.text
, self
.io
, self
.flist
)
506 def replace_event(self
, event
):
507 ReplaceDialog
.replace(self
.text
)
510 def goto_line_event(self
, event
):
512 lineno
= tkSimpleDialog
.askinteger("Goto",
513 "Go to line number:",parent
=text
)
519 text
.mark_set("insert", "%d.0" % lineno
)
522 def open_module(self
, event
=None):
523 # XXX Shouldn't this be in IOBinding or in FileList?
525 name
= self
.text
.get("sel.first", "sel.last")
530 name
= tkSimpleDialog
.askstring("Module",
531 "Enter the name of a Python module\n"
532 "to search on sys.path and open:",
533 parent
=self
.text
, initialvalue
=name
)
538 # XXX Ought to insert current file's directory in front of path
540 (f
, file, (suffix
, mode
, type)) = _find_module(name
)
541 except (NameError, ImportError), msg
:
542 tkMessageBox
.showerror("Import error", str(msg
), parent
=self
.text
)
544 if type != imp
.PY_SOURCE
:
545 tkMessageBox
.showerror("Unsupported type",
546 "%s is not a source module" % name
, parent
=self
.text
)
551 self
.flist
.open(file)
553 self
.io
.loadfile(file)
555 def open_class_browser(self
, event
=None):
556 filename
= self
.io
.filename
558 tkMessageBox
.showerror(
560 "This buffer has no associated filename",
562 self
.text
.focus_set()
564 head
, tail
= os
.path
.split(filename
)
565 base
, ext
= os
.path
.splitext(tail
)
567 ClassBrowser
.ClassBrowser(self
.flist
, base
, [head
])
569 def open_path_browser(self
, event
=None):
571 PathBrowser
.PathBrowser(self
.flist
)
573 def gotoline(self
, lineno
):
574 if lineno
is not None and lineno
> 0:
575 self
.text
.mark_set("insert", "%d.0" % lineno
)
576 self
.text
.tag_remove("sel", "1.0", "end")
577 self
.text
.tag_add("sel", "insert", "insert +1l")
580 def ispythonsource(self
, filename
):
581 if not filename
or os
.path
.isdir(filename
):
583 base
, ext
= os
.path
.splitext(os
.path
.basename(filename
))
584 if os
.path
.normcase(ext
) in (".py", ".pyw"):
592 return line
.startswith('#!') and line
.find('python') >= 0
594 def close_hook(self
):
596 self
.flist
.unregister_maybe_terminate(self
)
599 def set_close_hook(self
, close_hook
):
600 self
.close_hook
= close_hook
602 def filename_change_hook(self
):
604 self
.flist
.filename_changed_edit(self
)
605 self
.saved_change_hook()
606 self
.top
.update_windowlist_registry(self
)
607 self
.ResetColorizer()
609 def _addcolorizer(self
):
612 if self
.ispythonsource(self
.io
.filename
):
613 self
.color
= self
.ColorDelegator()
614 # can add more colorizers here...
616 self
.per
.removefilter(self
.undo
)
617 self
.per
.insertfilter(self
.color
)
618 self
.per
.insertfilter(self
.undo
)
620 def _rmcolorizer(self
):
623 self
.color
.removecolors()
624 self
.per
.removefilter(self
.color
)
627 def ResetColorizer(self
):
628 "Update the colour theme"
629 # Called from self.filename_change_hook and from configDialog.py
632 theme
= idleConf
.GetOption('main','Theme','name')
633 normal_colors
= idleConf
.GetHighlight(theme
, 'normal')
634 cursor_color
= idleConf
.GetHighlight(theme
, 'cursor', fgBg
='fg')
635 select_colors
= idleConf
.GetHighlight(theme
, 'hilite')
637 foreground
=normal_colors
['foreground'],
638 background
=normal_colors
['background'],
639 insertbackground
=cursor_color
,
640 selectforeground
=select_colors
['foreground'],
641 selectbackground
=select_colors
['background'],
645 "Update the text widgets' font if it is changed"
646 # Called from configDialog.py
648 if idleConf
.GetOption('main','EditorWindow','font-bold',type='bool'):
650 self
.text
.config(font
=(idleConf
.GetOption('main','EditorWindow','font'),
651 idleConf
.GetOption('main','EditorWindow','font-size'),
654 def RemoveKeybindings(self
):
655 "Remove the keybindings before they are changed."
656 # Called from configDialog.py
657 self
.Bindings
.default_keydefs
= keydefs
= idleConf
.GetCurrentKeySet()
658 for event
, keylist
in keydefs
.items():
659 self
.text
.event_delete(event
, *keylist
)
660 for extensionName
in self
.get_standard_extension_names():
661 xkeydefs
= idleConf
.GetExtensionBindings(extensionName
)
663 for event
, keylist
in xkeydefs
.items():
664 self
.text
.event_delete(event
, *keylist
)
666 def ApplyKeybindings(self
):
667 "Update the keybindings after they are changed"
668 # Called from configDialog.py
669 self
.Bindings
.default_keydefs
= keydefs
= idleConf
.GetCurrentKeySet()
670 self
.apply_bindings()
671 for extensionName
in self
.get_standard_extension_names():
672 xkeydefs
= idleConf
.GetExtensionBindings(extensionName
)
674 self
.apply_bindings(xkeydefs
)
675 #update menu accelerators
677 for menu
in self
.Bindings
.menudefs
:
678 menuEventDict
[menu
[0]] = {}
681 menuEventDict
[menu
[0]][prepstr(item
[0])[1]] = item
[1]
682 for menubarItem
in self
.menudict
.keys():
683 menu
= self
.menudict
[menubarItem
]
684 end
= menu
.index(END
) + 1
685 for index
in range(0, end
):
686 if menu
.type(index
) == 'command':
687 accel
= menu
.entrycget(index
, 'accelerator')
689 itemName
= menu
.entrycget(index
, 'label')
691 if menuEventDict
.has_key(menubarItem
):
692 if menuEventDict
[menubarItem
].has_key(itemName
):
693 event
= menuEventDict
[menubarItem
][itemName
]
695 accel
= get_accelerator(keydefs
, event
)
696 menu
.entryconfig(index
, accelerator
=accel
)
698 def set_notabs_indentwidth(self
):
699 "Update the indentwidth if changed and not using tabs in this window"
700 # Called from configDialog.py
702 self
.indentwidth
= idleConf
.GetOption('main', 'Indent','num-spaces',
705 def reset_help_menu_entries(self
):
706 "Update the additional help entries on the Help menu"
707 help_list
= idleConf
.GetAllExtraHelpSourcesList()
708 helpmenu
= self
.menudict
['help']
709 # first delete the extra help entries, if any
710 helpmenu_length
= helpmenu
.index(END
)
711 if helpmenu_length
> self
.base_helpmenu_length
:
712 helpmenu
.delete((self
.base_helpmenu_length
+ 1), helpmenu_length
)
715 helpmenu
.add_separator()
716 for entry
in help_list
:
717 cmd
= self
.__extra
_help
_callback
(entry
[1])
718 helpmenu
.add_command(label
=entry
[0], command
=cmd
)
719 # and update the menu dictionary
720 self
.menudict
['help'] = helpmenu
722 def __extra_help_callback(self
, helpfile
):
723 "Create a callback with the helpfile value frozen at definition time"
724 def display_extra_help(helpfile
=helpfile
):
725 if not helpfile
.startswith(('www', 'http')):
726 url
= os
.path
.normpath(helpfile
)
727 if sys
.platform
[:3] == 'win':
728 os
.startfile(helpfile
)
730 webbrowser
.open(helpfile
)
731 return display_extra_help
733 def update_recent_files_list(self
, new_file
=None):
734 "Load and update the recent files list and menus"
736 if os
.path
.exists(self
.recent_files_path
):
737 rf_list_file
= open(self
.recent_files_path
,'r')
739 rf_list
= rf_list_file
.readlines()
743 new_file
= os
.path
.abspath(new_file
) + '\n'
744 if new_file
in rf_list
:
745 rf_list
.remove(new_file
) # move to top
746 rf_list
.insert(0, new_file
)
747 # clean and save the recent files list
750 if '\0' in path
or not os
.path
.exists(path
[0:-1]):
751 bad_paths
.append(path
)
752 rf_list
= [path
for path
in rf_list
if path
not in bad_paths
]
753 ulchars
= "1234567890ABCDEFGHIJK"
754 rf_list
= rf_list
[0:len(ulchars
)]
755 rf_file
= open(self
.recent_files_path
, 'w')
757 rf_file
.writelines(rf_list
)
760 # for each edit window instance, construct the recent files menu
761 for instance
in self
.top
.instance_dict
.keys():
762 menu
= instance
.recent_files_menu
763 menu
.delete(1, END
) # clear, and rebuild:
764 for i
, file in zip(count(), rf_list
):
765 file_name
= file[0:-1] # zap \n
766 # make unicode string to display non-ASCII chars correctly
767 ufile_name
= self
._filename
_to
_unicode
(file_name
)
768 callback
= instance
.__recent
_file
_callback
(file_name
)
769 menu
.add_command(label
=ulchars
[i
] + " " + ufile_name
,
773 def __recent_file_callback(self
, file_name
):
774 def open_recent_file(fn_closure
=file_name
):
775 self
.io
.open(editFile
=fn_closure
)
776 return open_recent_file
778 def saved_change_hook(self
):
779 short
= self
.short_title()
780 long = self
.long_title()
782 title
= short
+ " - " + long
789 icon
= short
or long or title
790 if not self
.get_saved():
791 title
= "*%s*" % title
793 self
.top
.wm_title(title
)
794 self
.top
.wm_iconname(icon
)
797 return self
.undo
.get_saved()
799 def set_saved(self
, flag
):
800 self
.undo
.set_saved(flag
)
802 def reset_undo(self
):
803 self
.undo
.reset_undo()
805 def short_title(self
):
806 filename
= self
.io
.filename
808 filename
= os
.path
.basename(filename
)
809 # return unicode string to display non-ASCII chars correctly
810 return self
._filename
_to
_unicode
(filename
)
812 def long_title(self
):
813 # return unicode string to display non-ASCII chars correctly
814 return self
._filename
_to
_unicode
(self
.io
.filename
or "")
816 def center_insert_event(self
, event
):
819 def center(self
, mark
="insert"):
821 top
, bot
= self
.getwindowlines()
822 lineno
= self
.getlineno(mark
)
824 newtop
= max(1, lineno
- height
//2)
825 text
.yview(float(newtop
))
827 def getwindowlines(self
):
829 top
= self
.getlineno("@0,0")
830 bot
= self
.getlineno("@0,65535")
831 if top
== bot
and text
.winfo_height() == 1:
832 # Geometry manager hasn't run yet
833 height
= int(text
['height'])
834 bot
= top
+ height
- 1
837 def getlineno(self
, mark
="insert"):
839 return int(float(text
.index(mark
)))
841 def get_geometry(self
):
842 "Return (width, height, x, y)"
843 geom
= self
.top
.wm_geometry()
844 m
= re
.match(r
"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom
)
845 tuple = (map(int, m
.groups()))
848 def close_event(self
, event
):
853 if not self
.get_saved():
854 if self
.top
.state()!='normal':
858 return self
.io
.maybesave()
861 reply
= self
.maybesave()
862 if str(reply
) != "cancel":
868 self
.update_recent_files_list(new_file
=self
.io
.filename
)
869 WindowList
.unregister_callback(self
.postwindowsmenu
)
870 self
.unload_extensions()
875 self
.color
.close(False)
878 self
.tkinter_vars
= None
883 # unless override: unregister from flist, terminate if last window
886 def load_extensions(self
):
888 self
.load_standard_extensions()
890 def unload_extensions(self
):
891 for ins
in self
.extensions
.values():
892 if hasattr(ins
, "close"):
896 def load_standard_extensions(self
):
897 for name
in self
.get_standard_extension_names():
899 self
.load_extension(name
)
901 print "Failed to load extension", repr(name
)
903 traceback
.print_exc()
905 def get_standard_extension_names(self
):
906 return idleConf
.GetExtensions(editor_only
=True)
908 def load_extension(self
, name
):
910 mod
= __import__(name
, globals(), locals(), [])
912 print "\nFailed to import extension: ", name
914 cls
= getattr(mod
, name
)
915 keydefs
= idleConf
.GetExtensionBindings(name
)
916 if hasattr(cls
, "menudefs"):
917 self
.fill_menus(cls
.menudefs
, keydefs
)
919 self
.extensions
[name
] = ins
921 self
.apply_bindings(keydefs
)
922 for vevent
in keydefs
.keys():
923 methodname
= vevent
.replace("-", "_")
924 while methodname
[:1] == '<':
925 methodname
= methodname
[1:]
926 while methodname
[-1:] == '>':
927 methodname
= methodname
[:-1]
928 methodname
= methodname
+ "_event"
929 if hasattr(ins
, methodname
):
930 self
.text
.bind(vevent
, getattr(ins
, methodname
))
932 def apply_bindings(self
, keydefs
=None):
934 keydefs
= self
.Bindings
.default_keydefs
936 text
.keydefs
= keydefs
937 for event
, keylist
in keydefs
.items():
939 text
.event_add(event
, *keylist
)
941 def fill_menus(self
, menudefs
=None, keydefs
=None):
942 """Add appropriate entries to the menus and submenus
944 Menus that are absent or None in self.menudict are ignored.
947 menudefs
= self
.Bindings
.menudefs
949 keydefs
= self
.Bindings
.default_keydefs
950 menudict
= self
.menudict
952 for mname
, entrylist
in menudefs
:
953 menu
= menudict
.get(mname
)
956 for entry
in entrylist
:
960 label
, eventname
= entry
961 checkbutton
= (label
[:1] == '!')
964 underline
, label
= prepstr(label
)
965 accelerator
= get_accelerator(keydefs
, eventname
)
966 def command(text
=text
, eventname
=eventname
):
967 text
.event_generate(eventname
)
969 var
= self
.get_var_obj(eventname
, BooleanVar
)
970 menu
.add_checkbutton(label
=label
, underline
=underline
,
971 command
=command
, accelerator
=accelerator
,
974 menu
.add_command(label
=label
, underline
=underline
,
976 accelerator
=accelerator
)
978 def getvar(self
, name
):
979 var
= self
.get_var_obj(name
)
984 raise NameError, name
986 def setvar(self
, name
, value
, vartype
=None):
987 var
= self
.get_var_obj(name
, vartype
)
991 raise NameError, name
993 def get_var_obj(self
, name
, vartype
=None):
994 var
= self
.tkinter_vars
.get(name
)
995 if not var
and vartype
:
996 # create a Tkinter variable object with self.text as master:
997 self
.tkinter_vars
[name
] = var
= vartype(self
.text
)
1000 # Tk implementations of "virtual text methods" -- each platform
1001 # reusing IDLE's support code needs to define these for its GUI's
1004 # Is character at text_index in a Python string? Return 0 for
1005 # "guaranteed no", true for anything else. This info is expensive
1006 # to compute ab initio, but is probably already known by the
1007 # platform's colorizer.
1009 def is_char_in_string(self
, text_index
):
1011 # Return true iff colorizer hasn't (re)gotten this far
1012 # yet, or the character is tagged as being in a string
1013 return self
.text
.tag_prevrange("TODO", text_index
) or \
1014 "STRING" in self
.text
.tag_names(text_index
)
1016 # The colorizer is missing: assume the worst
1019 # If a selection is defined in the text widget, return (start,
1020 # end) as Tkinter text indices, otherwise return (None, None)
1021 def get_selection_indices(self
):
1023 first
= self
.text
.index("sel.first")
1024 last
= self
.text
.index("sel.last")
1029 # Return the text widget's current view of what a tab stop means
1030 # (equivalent width in spaces).
1032 def get_tabwidth(self
):
1033 current
= self
.text
['tabs'] or TK_TABWIDTH_DEFAULT
1036 # Set the text widget's current view of what a tab stop means.
1038 def set_tabwidth(self
, newtabwidth
):
1040 if self
.get_tabwidth() != newtabwidth
:
1041 pixels
= text
.tk
.call("font", "measure", text
["font"],
1042 "-displayof", text
.master
,
1044 text
.configure(tabs
=pixels
)
1046 # If ispythonsource and guess are true, guess a good value for
1047 # indentwidth based on file content (if possible), and if
1048 # indentwidth != tabwidth set usetabs false.
1049 # In any case, adjust the Text widget's view of what a tab
1052 def set_indentation_params(self
, ispythonsource
, guess
=True):
1053 if guess
and ispythonsource
:
1054 i
= self
.guess_indent()
1056 self
.indentwidth
= i
1057 if self
.indentwidth
!= self
.tabwidth
:
1058 self
.usetabs
= False
1059 self
.set_tabwidth(self
.tabwidth
)
1061 def smart_backspace_event(self
, event
):
1063 first
, last
= self
.get_selection_indices()
1065 text
.delete(first
, last
)
1066 text
.mark_set("insert", first
)
1068 # Delete whitespace left, until hitting a real char or closest
1069 # preceding virtual tab stop.
1070 chars
= text
.get("insert linestart", "insert")
1072 if text
.compare("insert", ">", "1.0"):
1073 # easy: delete preceding newline
1074 text
.delete("insert-1c")
1076 text
.bell() # at start of buffer
1078 if chars
[-1] not in " \t":
1079 # easy: delete preceding real char
1080 text
.delete("insert-1c")
1082 # Ick. It may require *inserting* spaces if we back up over a
1083 # tab character! This is written to be clear, not fast.
1084 tabwidth
= self
.tabwidth
1085 have
= len(chars
.expandtabs(tabwidth
))
1087 want
= ((have
- 1) // self
.indentwidth
) * self
.indentwidth
1088 # Debug prompt is multilined....
1089 last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
1092 if chars
== last_line_of_prompt
:
1095 ncharsdeleted
= ncharsdeleted
+ 1
1096 have
= len(chars
.expandtabs(tabwidth
))
1097 if have
<= want
or chars
[-1] not in " \t":
1099 text
.undo_block_start()
1100 text
.delete("insert-%dc" % ncharsdeleted
, "insert")
1102 text
.insert("insert", ' ' * (want
- have
))
1103 text
.undo_block_stop()
1106 def smart_indent_event(self
, event
):
1107 # if intraline selection:
1109 # elif multiline selection:
1114 first
, last
= self
.get_selection_indices()
1115 text
.undo_block_start()
1118 if index2line(first
) != index2line(last
):
1119 return self
.indent_region_event(event
)
1120 text
.delete(first
, last
)
1121 text
.mark_set("insert", first
)
1122 prefix
= text
.get("insert linestart", "insert")
1123 raw
, effective
= classifyws(prefix
, self
.tabwidth
)
1124 if raw
== len(prefix
):
1125 # only whitespace to the left
1126 self
.reindent_to(effective
+ self
.indentwidth
)
1128 # tab to the next 'stop' within or to right of line's text:
1132 effective
= len(prefix
.expandtabs(self
.tabwidth
))
1133 n
= self
.indentwidth
1134 pad
= ' ' * (n
- effective
% n
)
1135 text
.insert("insert", pad
)
1139 text
.undo_block_stop()
1141 def newline_and_indent_event(self
, event
):
1143 first
, last
= self
.get_selection_indices()
1144 text
.undo_block_start()
1147 text
.delete(first
, last
)
1148 text
.mark_set("insert", first
)
1149 line
= text
.get("insert linestart", "insert")
1151 while i
< n
and line
[i
] in " \t":
1154 # the cursor is in or at leading indentation in a continuation
1155 # line; just inject an empty line at the start
1156 text
.insert("insert linestart", '\n')
1159 # strip whitespace before insert point unless it's in the prompt
1161 last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
1162 while line
and line
[-1] in " \t" and line
!= last_line_of_prompt
:
1166 text
.delete("insert - %d chars" % i
, "insert")
1167 # strip whitespace after insert point
1168 while text
.get("insert") in " \t":
1169 text
.delete("insert")
1171 text
.insert("insert", '\n')
1173 # adjust indentation for continuations and block
1174 # open/close first need to find the last stmt
1175 lno
= index2line(text
.index('insert'))
1176 y
= PyParse
.Parser(self
.indentwidth
, self
.tabwidth
)
1177 if not self
.context_use_ps1
:
1178 for context
in self
.num_context_lines
:
1179 startat
= max(lno
- context
, 1)
1180 startatindex
= `startat`
+ ".0"
1181 rawtext
= text
.get(startatindex
, "insert")
1183 bod
= y
.find_good_parse_start(
1184 self
.context_use_ps1
,
1185 self
._build
_char
_in
_string
_func
(startatindex
))
1186 if bod
is not None or startat
== 1:
1190 r
= text
.tag_prevrange("console", "insert")
1194 startatindex
= "1.0"
1195 rawtext
= text
.get(startatindex
, "insert")
1199 c
= y
.get_continuation_type()
1200 if c
!= PyParse
.C_NONE
:
1201 # The current stmt hasn't ended yet.
1202 if c
== PyParse
.C_STRING_FIRST_LINE
:
1203 # after the first line of a string; do not indent at all
1205 elif c
== PyParse
.C_STRING_NEXT_LINES
:
1206 # inside a string which started before this line;
1207 # just mimic the current indent
1208 text
.insert("insert", indent
)
1209 elif c
== PyParse
.C_BRACKET
:
1210 # line up with the first (if any) element of the
1211 # last open bracket structure; else indent one
1212 # level beyond the indent of the line with the
1214 self
.reindent_to(y
.compute_bracket_indent())
1215 elif c
== PyParse
.C_BACKSLASH
:
1216 # if more than one line in this stmt already, just
1217 # mimic the current indent; else if initial line
1218 # has a start on an assignment stmt, indent to
1219 # beyond leftmost =; else to beyond first chunk of
1220 # non-whitespace on initial line
1221 if y
.get_num_lines_in_stmt() > 1:
1222 text
.insert("insert", indent
)
1224 self
.reindent_to(y
.compute_backslash_indent())
1226 assert 0, "bogus continuation type %r" % (c
,)
1229 # This line starts a brand new stmt; indent relative to
1230 # indentation of initial line of closest preceding
1232 indent
= y
.get_base_indent_string()
1233 text
.insert("insert", indent
)
1234 if y
.is_block_opener():
1235 self
.smart_indent_event(event
)
1236 elif indent
and y
.is_block_closer():
1237 self
.smart_backspace_event(event
)
1241 text
.undo_block_stop()
1243 # Our editwin provides a is_char_in_string function that works
1244 # with a Tk text index, but PyParse only knows about offsets into
1245 # a string. This builds a function for PyParse that accepts an
1248 def _build_char_in_string_func(self
, startindex
):
1249 def inner(offset
, _startindex
=startindex
,
1250 _icis
=self
.is_char_in_string
):
1251 return _icis(_startindex
+ "+%dc" % offset
)
1254 def indent_region_event(self
, event
):
1255 head
, tail
, chars
, lines
= self
.get_region()
1256 for pos
in range(len(lines
)):
1259 raw
, effective
= classifyws(line
, self
.tabwidth
)
1260 effective
= effective
+ self
.indentwidth
1261 lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
1262 self
.set_region(head
, tail
, chars
, lines
)
1265 def dedent_region_event(self
, event
):
1266 head
, tail
, chars
, lines
= self
.get_region()
1267 for pos
in range(len(lines
)):
1270 raw
, effective
= classifyws(line
, self
.tabwidth
)
1271 effective
= max(effective
- self
.indentwidth
, 0)
1272 lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
1273 self
.set_region(head
, tail
, chars
, lines
)
1276 def comment_region_event(self
, event
):
1277 head
, tail
, chars
, lines
= self
.get_region()
1278 for pos
in range(len(lines
) - 1):
1280 lines
[pos
] = '##' + line
1281 self
.set_region(head
, tail
, chars
, lines
)
1283 def uncomment_region_event(self
, event
):
1284 head
, tail
, chars
, lines
= self
.get_region()
1285 for pos
in range(len(lines
)):
1289 if line
[:2] == '##':
1291 elif line
[:1] == '#':
1294 self
.set_region(head
, tail
, chars
, lines
)
1296 def tabify_region_event(self
, event
):
1297 head
, tail
, chars
, lines
= self
.get_region()
1298 tabwidth
= self
._asktabwidth
()
1299 for pos
in range(len(lines
)):
1302 raw
, effective
= classifyws(line
, tabwidth
)
1303 ntabs
, nspaces
= divmod(effective
, tabwidth
)
1304 lines
[pos
] = '\t' * ntabs
+ ' ' * nspaces
+ line
[raw
:]
1305 self
.set_region(head
, tail
, chars
, lines
)
1307 def untabify_region_event(self
, event
):
1308 head
, tail
, chars
, lines
= self
.get_region()
1309 tabwidth
= self
._asktabwidth
()
1310 for pos
in range(len(lines
)):
1311 lines
[pos
] = lines
[pos
].expandtabs(tabwidth
)
1312 self
.set_region(head
, tail
, chars
, lines
)
1314 def toggle_tabs_event(self
, event
):
1317 "Turn tabs " + ("on", "off")[self
.usetabs
] +
1318 "?\nIndent width " +
1319 ("will be", "remains at")[self
.usetabs
] + " 8." +
1320 "\n Note: a tab is always 8 columns",
1322 self
.usetabs
= not self
.usetabs
1323 # Try to prevent inconsistent indentation.
1324 # User must change indent width manually after using tabs.
1325 self
.indentwidth
= 8
1328 # XXX this isn't bound to anything -- see tabwidth comments
1329 ## def change_tabwidth_event(self, event):
1330 ## new = self._asktabwidth()
1331 ## if new != self.tabwidth:
1332 ## self.tabwidth = new
1333 ## self.set_indentation_params(0, guess=0)
1336 def change_indentwidth_event(self
, event
):
1337 new
= self
.askinteger(
1339 "New indent width (2-16)\n(Always use 8 when using tabs)",
1341 initialvalue
=self
.indentwidth
,
1344 if new
and new
!= self
.indentwidth
and not self
.usetabs
:
1345 self
.indentwidth
= new
1348 def get_region(self
):
1350 first
, last
= self
.get_selection_indices()
1352 head
= text
.index(first
+ " linestart")
1353 tail
= text
.index(last
+ "-1c lineend +1c")
1355 head
= text
.index("insert linestart")
1356 tail
= text
.index("insert lineend +1c")
1357 chars
= text
.get(head
, tail
)
1358 lines
= chars
.split("\n")
1359 return head
, tail
, chars
, lines
1361 def set_region(self
, head
, tail
, chars
, lines
):
1363 newchars
= "\n".join(lines
)
1364 if newchars
== chars
:
1367 text
.tag_remove("sel", "1.0", "end")
1368 text
.mark_set("insert", head
)
1369 text
.undo_block_start()
1370 text
.delete(head
, tail
)
1371 text
.insert(head
, newchars
)
1372 text
.undo_block_stop()
1373 text
.tag_add("sel", head
, "insert")
1375 # Make string that displays as n leading blanks.
1377 def _make_blanks(self
, n
):
1379 ntabs
, nspaces
= divmod(n
, self
.tabwidth
)
1380 return '\t' * ntabs
+ ' ' * nspaces
1384 # Delete from beginning of line to insert point, then reinsert
1385 # column logical (meaning use tabs if appropriate) spaces.
1387 def reindent_to(self
, column
):
1389 text
.undo_block_start()
1390 if text
.compare("insert linestart", "!=", "insert"):
1391 text
.delete("insert linestart", "insert")
1393 text
.insert("insert", self
._make
_blanks
(column
))
1394 text
.undo_block_stop()
1396 def _asktabwidth(self
):
1397 return self
.askinteger(
1399 "Columns per tab? (2-16)",
1401 initialvalue
=self
.indentwidth
,
1403 maxvalue
=16) or self
.tabwidth
1405 # Guess indentwidth from text content.
1406 # Return guessed indentwidth. This should not be believed unless
1407 # it's in a reasonable range (e.g., it will be 0 if no indented
1408 # blocks are found).
1410 def guess_indent(self
):
1411 opener
, indented
= IndentSearcher(self
.text
, self
.tabwidth
).run()
1412 if opener
and indented
:
1413 raw
, indentsmall
= classifyws(opener
, self
.tabwidth
)
1414 raw
, indentlarge
= classifyws(indented
, self
.tabwidth
)
1416 indentsmall
= indentlarge
= 0
1417 return indentlarge
- indentsmall
1419 # "line.col" -> line, as an int
1420 def index2line(index
):
1421 return int(float(index
))
1423 # Look at the leading whitespace in s.
1424 # Return pair (# of leading ws characters,
1425 # effective # of leading blanks after expanding
1426 # tabs to width tabwidth)
1428 def classifyws(s
, tabwidth
):
1433 effective
= effective
+ 1
1436 effective
= (effective
// tabwidth
+ 1) * tabwidth
1439 return raw
, effective
1442 _tokenize
= tokenize
1445 class IndentSearcher(object):
1447 # .run() chews over the Text widget, looking for a block opener
1448 # and the stmt following it. Returns a pair,
1449 # (line containing block opener, line containing stmt)
1450 # Either or both may be None.
1452 def __init__(self
, text
, tabwidth
):
1454 self
.tabwidth
= tabwidth
1455 self
.i
= self
.finished
= 0
1456 self
.blkopenline
= self
.indentedline
= None
1461 i
= self
.i
= self
.i
+ 1
1462 mark
= repr(i
) + ".0"
1463 if self
.text
.compare(mark
, ">=", "end"):
1465 return self
.text
.get(mark
, mark
+ " lineend+1c")
1467 def tokeneater(self
, type, token
, start
, end
, line
,
1468 INDENT
=_tokenize
.INDENT
,
1469 NAME
=_tokenize
.NAME
,
1470 OPENERS
=('class', 'def', 'for', 'if', 'try', 'while')):
1473 elif type == NAME
and token
in OPENERS
:
1474 self
.blkopenline
= line
1475 elif type == INDENT
and self
.blkopenline
:
1476 self
.indentedline
= line
1480 save_tabsize
= _tokenize
.tabsize
1481 _tokenize
.tabsize
= self
.tabwidth
1484 _tokenize
.tokenize(self
.readline
, self
.tokeneater
)
1485 except _tokenize
.TokenError
:
1486 # since we cut off the tokenizer early, we can trigger
1490 _tokenize
.tabsize
= save_tabsize
1491 return self
.blkopenline
, self
.indentedline
1493 ### end autoindent code ###
1496 # Helper to extract the underscore from a string, e.g.
1497 # prepstr("Co_py") returns (2, "Copy").
1506 'bracketright': ']',
1510 def get_accelerator(keydefs
, eventname
):
1511 keylist
= keydefs
.get(eventname
)
1515 s
= re
.sub(r
"-[a-z]\b", lambda m
: m
.group().upper(), s
)
1516 s
= re
.sub(r
"\b\w+\b", lambda m
: keynames
.get(m
.group(), m
.group()), s
)
1517 s
= re
.sub("Key-", "", s
)
1518 s
= re
.sub("Cancel","Ctrl-Break",s
) # dscherer@cmu.edu
1519 s
= re
.sub("Control-", "Ctrl-", s
)
1520 s
= re
.sub("-", "+", s
)
1521 s
= re
.sub("><", " ", s
)
1522 s
= re
.sub("<", "", s
)
1523 s
= re
.sub(">", "", s
)
1527 def fixwordbreaks(root
):
1528 # Make sure that Tk's double-click and next/previous word
1529 # operations use our definition of a word (i.e. an identifier)
1531 tk
.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1532 tk
.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1533 tk
.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1541 filename
= sys
.argv
[1]
1544 edit
= EditorWindow(root
=root
, filename
=filename
)
1545 edit
.set_close_hook(root
.quit
)
1546 edit
.text
.bind("<<close-all-windows>>", edit
.close_event
)
1550 if __name__
== '__main__':