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')
113 'height': idleConf
.GetOption('main', 'EditorWindow', 'height')}
115 # Starting with tk 8.5 we have to set the new tabstyle option
116 # to 'wordprocessor' to achieve the same display of tabs as in
118 text_options
['tabstyle'] = 'wordprocessor'
119 self
.text
= text
= MultiCallCreator(Text
)(text_frame
, **text_options
)
120 self
.top
.focused_widget
= self
.text
123 self
.apply_bindings()
125 self
.top
.protocol("WM_DELETE_WINDOW", self
.close
)
126 self
.top
.bind("<<close-window>>", self
.close_event
)
127 if macosxSupport
.runningAsOSXApp():
128 # Command-W on editorwindows doesn't work without this.
129 text
.bind('<<close-window>>', self
.close_event
)
130 text
.bind("<<cut>>", self
.cut
)
131 text
.bind("<<copy>>", self
.copy
)
132 text
.bind("<<paste>>", self
.paste
)
133 text
.bind("<<center-insert>>", self
.center_insert_event
)
134 text
.bind("<<help>>", self
.help_dialog
)
135 text
.bind("<<python-docs>>", self
.python_docs
)
136 text
.bind("<<about-idle>>", self
.about_dialog
)
137 text
.bind("<<open-config-dialog>>", self
.config_dialog
)
138 text
.bind("<<open-module>>", self
.open_module
)
139 text
.bind("<<do-nothing>>", lambda event
: "break")
140 text
.bind("<<select-all>>", self
.select_all
)
141 text
.bind("<<remove-selection>>", self
.remove_selection
)
142 text
.bind("<<find>>", self
.find_event
)
143 text
.bind("<<find-again>>", self
.find_again_event
)
144 text
.bind("<<find-in-files>>", self
.find_in_files_event
)
145 text
.bind("<<find-selection>>", self
.find_selection_event
)
146 text
.bind("<<replace>>", self
.replace_event
)
147 text
.bind("<<goto-line>>", self
.goto_line_event
)
148 text
.bind("<3>", self
.right_menu_event
)
149 text
.bind("<<smart-backspace>>",self
.smart_backspace_event
)
150 text
.bind("<<newline-and-indent>>",self
.newline_and_indent_event
)
151 text
.bind("<<smart-indent>>",self
.smart_indent_event
)
152 text
.bind("<<indent-region>>",self
.indent_region_event
)
153 text
.bind("<<dedent-region>>",self
.dedent_region_event
)
154 text
.bind("<<comment-region>>",self
.comment_region_event
)
155 text
.bind("<<uncomment-region>>",self
.uncomment_region_event
)
156 text
.bind("<<tabify-region>>",self
.tabify_region_event
)
157 text
.bind("<<untabify-region>>",self
.untabify_region_event
)
158 text
.bind("<<toggle-tabs>>",self
.toggle_tabs_event
)
159 text
.bind("<<change-indentwidth>>",self
.change_indentwidth_event
)
160 text
.bind("<Left>", self
.move_at_edge_if_selection(0))
161 text
.bind("<Right>", self
.move_at_edge_if_selection(1))
162 text
.bind("<<del-word-left>>", self
.del_word_left
)
163 text
.bind("<<del-word-right>>", self
.del_word_right
)
164 text
.bind("<<beginning-of-line>>", self
.home_callback
)
167 flist
.inversedict
[self
] = key
169 flist
.dict[key
] = self
170 text
.bind("<<open-new-window>>", self
.new_callback
)
171 text
.bind("<<close-all-windows>>", self
.flist
.close_all_callback
)
172 text
.bind("<<open-class-browser>>", self
.open_class_browser
)
173 text
.bind("<<open-path-browser>>", self
.open_path_browser
)
175 self
.set_status_bar()
176 vbar
['command'] = text
.yview
177 vbar
.pack(side
=RIGHT
, fill
=Y
)
178 text
['yscrollcommand'] = vbar
.set
179 fontWeight
= 'normal'
180 if idleConf
.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
182 text
.config(font
=(idleConf
.GetOption('main', 'EditorWindow', 'font'),
183 idleConf
.GetOption('main', 'EditorWindow', 'font-size'),
185 text_frame
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
186 text
.pack(side
=TOP
, fill
=BOTH
, expand
=1)
189 # usetabs true -> literal tab characters are used by indent and
190 # dedent cmds, possibly mixed with spaces if
191 # indentwidth is not a multiple of tabwidth,
192 # which will cause Tabnanny to nag!
193 # false -> tab characters are converted to spaces by indent
194 # and dedent cmds, and ditto TAB keystrokes
195 # Although use-spaces=0 can be configured manually in config-main.def,
196 # configuration of tabs v. spaces is not supported in the configuration
197 # dialog. IDLE promotes the preferred Python indentation: use spaces!
198 usespaces
= idleConf
.GetOption('main', 'Indent', 'use-spaces', type='bool')
199 self
.usetabs
= not usespaces
201 # tabwidth is the display width of a literal tab character.
202 # CAUTION: telling Tk to use anything other than its default
203 # tab setting causes it to use an entirely different tabbing algorithm,
204 # treating tab stops as fixed distances from the left margin.
205 # Nobody expects this, so for now tabwidth should never be changed.
206 self
.tabwidth
= 8 # must remain 8 until Tk is fixed.
208 # indentwidth is the number of screen characters per indent level.
209 # The recommended Python indentation is four spaces.
210 self
.indentwidth
= self
.tabwidth
211 self
.set_notabs_indentwidth()
213 # If context_use_ps1 is true, parsing searches back for a ps1 line;
214 # else searches for a popular (if, def, ...) Python stmt.
215 self
.context_use_ps1
= False
217 # When searching backwards for a reliable place to begin parsing,
218 # first start num_context_lines[0] lines back, then
219 # num_context_lines[1] lines back if that didn't work, and so on.
220 # The last value should be huge (larger than the # of lines in a
222 # Making the initial values larger slows things down more often.
223 self
.num_context_lines
= 50, 500, 5000000
225 self
.per
= per
= self
.Percolator(text
)
227 self
.undo
= undo
= self
.UndoDelegator()
228 per
.insertfilter(undo
)
229 text
.undo_block_start
= undo
.undo_block_start
230 text
.undo_block_stop
= undo
.undo_block_stop
231 undo
.set_saved_change_hook(self
.saved_change_hook
)
233 # IOBinding implements file I/O and printing functionality
234 self
.io
= io
= self
.IOBinding(self
)
235 io
.set_filename_change_hook(self
.filename_change_hook
)
237 # Create the recent files submenu
238 self
.recent_files_menu
= Menu(self
.menubar
)
239 self
.menudict
['file'].insert_cascade(3, label
='Recent Files',
241 menu
=self
.recent_files_menu
)
242 self
.update_recent_files_list()
244 self
.color
= None # initialized below in self.ResetColorizer
246 if os
.path
.exists(filename
) and not os
.path
.isdir(filename
):
247 io
.loadfile(filename
)
249 io
.set_filename(filename
)
250 self
.ResetColorizer()
251 self
.saved_change_hook()
253 self
.set_indentation_params(self
.ispythonsource(filename
))
255 self
.load_extensions()
257 menu
= self
.menudict
.get('windows')
259 end
= menu
.index("end")
266 WindowList
.register_callback(self
.postwindowsmenu
)
268 # Some abstractions so IDLE extensions are cross-IDE
269 self
.askyesno
= tkMessageBox
.askyesno
270 self
.askinteger
= tkSimpleDialog
.askinteger
271 self
.showerror
= tkMessageBox
.showerror
273 def _filename_to_unicode(self
, filename
):
274 """convert filename to unicode in order to display it in Tk"""
275 if isinstance(filename
, unicode) or not filename
:
279 return filename
.decode(self
.filesystemencoding
)
280 except UnicodeDecodeError:
283 return filename
.decode(self
.encoding
)
284 except UnicodeDecodeError:
285 # byte-to-byte conversion
286 return filename
.decode('iso8859-1')
288 def new_callback(self
, event
):
289 dirname
, basename
= self
.io
.defaultfilename()
290 self
.flist
.new(dirname
)
293 def home_callback(self
, event
):
294 if (event
.state
& 12) != 0 and event
.keysym
== "Home":
295 # state&1==shift, state&4==control, state&8==alt
296 return # <Modifier-Home>; fall back to class binding
298 if self
.text
.index("iomark") and \
299 self
.text
.compare("iomark", "<=", "insert lineend") and \
300 self
.text
.compare("insert linestart", "<=", "iomark"):
301 insertpt
= int(self
.text
.index("iomark").split(".")[1])
303 line
= self
.text
.get("insert linestart", "insert lineend")
304 for insertpt
in xrange(len(line
)):
305 if line
[insertpt
] not in (' ','\t'):
310 lineat
= int(self
.text
.index("insert").split('.')[1])
312 if insertpt
== lineat
:
315 dest
= "insert linestart+"+str(insertpt
)+"c"
317 if (event
.state
&1) == 0:
319 self
.text
.tag_remove("sel", "1.0", "end")
321 if not self
.text
.index("sel.first"):
322 self
.text
.mark_set("anchor","insert")
324 first
= self
.text
.index(dest
)
325 last
= self
.text
.index("anchor")
327 if self
.text
.compare(first
,">",last
):
328 first
,last
= last
,first
330 self
.text
.tag_remove("sel", "1.0", "end")
331 self
.text
.tag_add("sel", first
, last
)
333 self
.text
.mark_set("insert", dest
)
334 self
.text
.see("insert")
337 def set_status_bar(self
):
338 self
.status_bar
= self
.MultiStatusBar(self
.top
)
339 if macosxSupport
.runningAsOSXApp():
340 # Insert some padding to avoid obscuring some of the statusbar
341 # by the resize widget.
342 self
.status_bar
.set_label('_padding1', ' ', side
=RIGHT
)
343 self
.status_bar
.set_label('column', 'Col: ?', side
=RIGHT
)
344 self
.status_bar
.set_label('line', 'Ln: ?', side
=RIGHT
)
345 self
.status_bar
.pack(side
=BOTTOM
, fill
=X
)
346 self
.text
.bind("<<set-line-and-column>>", self
.set_line_and_column
)
347 self
.text
.event_add("<<set-line-and-column>>",
348 "<KeyRelease>", "<ButtonRelease>")
349 self
.text
.after_idle(self
.set_line_and_column
)
351 def set_line_and_column(self
, event
=None):
352 line
, column
= self
.text
.index(INSERT
).split('.')
353 self
.status_bar
.set_label('column', 'Col: %s' % column
)
354 self
.status_bar
.set_label('line', 'Ln: %s' % line
)
359 ("format", "F_ormat"),
361 ("options", "_Options"),
362 ("windows", "_Windows"),
366 if macosxSupport
.runningAsOSXApp():
368 menu_specs
[-2] = ("windows", "_Window")
371 def createmenubar(self
):
373 self
.menudict
= menudict
= {}
374 for name
, label
in self
.menu_specs
:
375 underline
, label
= prepstr(label
)
376 menudict
[name
] = menu
= Menu(mbar
, name
=name
)
377 mbar
.add_cascade(label
=label
, menu
=menu
, underline
=underline
)
379 if macosxSupport
.runningAsOSXApp():
380 # Insert the application menu
381 menudict
['application'] = menu
= Menu(mbar
, name
='apple')
382 mbar
.add_cascade(label
='IDLE', menu
=menu
)
385 self
.base_helpmenu_length
= self
.menudict
['help'].index(END
)
386 self
.reset_help_menu_entries()
388 def postwindowsmenu(self
):
389 # Only called when Windows menu exists
390 menu
= self
.menudict
['windows']
391 end
= menu
.index("end")
394 if end
> self
.wmenu_end
:
395 menu
.delete(self
.wmenu_end
+1, end
)
396 WindowList
.add_windows_to_menu(menu
)
400 def right_menu_event(self
, event
):
401 self
.text
.tag_remove("sel", "1.0", "end")
402 self
.text
.mark_set("insert", "@%d,%d" % (event
.x
, event
.y
))
407 iswin
= sys
.platform
[:3] == 'win'
409 self
.text
.config(cursor
="arrow")
410 rmenu
.tk_popup(event
.x_root
, event
.y_root
)
412 self
.text
.config(cursor
="ibeam")
415 # ("Label", "<<virtual-event>>"), ...
416 ("Close", "<<close-window>>"), # Example
419 def make_rmenu(self
):
420 rmenu
= Menu(self
.text
, tearoff
=0)
421 for label
, eventname
in self
.rmenu_specs
:
422 def command(text
=self
.text
, eventname
=eventname
):
423 text
.event_generate(eventname
)
424 rmenu
.add_command(label
=label
, command
=command
)
427 def about_dialog(self
, event
=None):
428 aboutDialog
.AboutDialog(self
.top
,'About IDLE')
430 def config_dialog(self
, event
=None):
431 configDialog
.ConfigDialog(self
.top
,'Settings')
433 def help_dialog(self
, event
=None):
434 fn
=os
.path
.join(os
.path
.abspath(os
.path
.dirname(__file__
)),'help.txt')
435 textView
.view_file(self
.top
,'Help',fn
)
437 def python_docs(self
, event
=None):
438 if sys
.platform
[:3] == 'win':
439 os
.startfile(self
.help_url
)
441 webbrowser
.open(self
.help_url
)
445 self
.text
.event_generate("<<Cut>>")
448 def copy(self
,event
):
449 if not self
.text
.tag_ranges("sel"):
450 # There is no selection, so do nothing and maybe interrupt.
452 self
.text
.event_generate("<<Copy>>")
455 def paste(self
,event
):
456 self
.text
.event_generate("<<Paste>>")
457 self
.text
.see("insert")
460 def select_all(self
, event
=None):
461 self
.text
.tag_add("sel", "1.0", "end-1c")
462 self
.text
.mark_set("insert", "1.0")
463 self
.text
.see("insert")
466 def remove_selection(self
, event
=None):
467 self
.text
.tag_remove("sel", "1.0", "end")
468 self
.text
.see("insert")
470 def move_at_edge_if_selection(self
, edge_index
):
471 """Cursor move begins at start or end of selection
473 When a left/right cursor key is pressed create and return to Tkinter a
474 function which causes a cursor move from the associated edge of the
478 self_text_index
= self
.text
.index
479 self_text_mark_set
= self
.text
.mark_set
480 edges_table
= ("sel.first+1c", "sel.last-1c")
481 def move_at_edge(event
):
482 if (event
.state
& 5) == 0: # no shift(==1) or control(==4) pressed
484 self_text_index("sel.first")
485 self_text_mark_set("insert", edges_table
[edge_index
])
490 def del_word_left(self
, event
):
491 self
.text
.event_generate('<Meta-Delete>')
494 def del_word_right(self
, event
):
495 self
.text
.event_generate('<Meta-d>')
498 def find_event(self
, event
):
499 SearchDialog
.find(self
.text
)
502 def find_again_event(self
, event
):
503 SearchDialog
.find_again(self
.text
)
506 def find_selection_event(self
, event
):
507 SearchDialog
.find_selection(self
.text
)
510 def find_in_files_event(self
, event
):
511 GrepDialog
.grep(self
.text
, self
.io
, self
.flist
)
514 def replace_event(self
, event
):
515 ReplaceDialog
.replace(self
.text
)
518 def goto_line_event(self
, event
):
520 lineno
= tkSimpleDialog
.askinteger("Goto",
521 "Go to line number:",parent
=text
)
527 text
.mark_set("insert", "%d.0" % lineno
)
530 def open_module(self
, event
=None):
531 # XXX Shouldn't this be in IOBinding or in FileList?
533 name
= self
.text
.get("sel.first", "sel.last")
538 name
= tkSimpleDialog
.askstring("Module",
539 "Enter the name of a Python module\n"
540 "to search on sys.path and open:",
541 parent
=self
.text
, initialvalue
=name
)
546 # XXX Ought to insert current file's directory in front of path
548 (f
, file, (suffix
, mode
, type)) = _find_module(name
)
549 except (NameError, ImportError), msg
:
550 tkMessageBox
.showerror("Import error", str(msg
), parent
=self
.text
)
552 if type != imp
.PY_SOURCE
:
553 tkMessageBox
.showerror("Unsupported type",
554 "%s is not a source module" % name
, parent
=self
.text
)
559 self
.flist
.open(file)
561 self
.io
.loadfile(file)
563 def open_class_browser(self
, event
=None):
564 filename
= self
.io
.filename
566 tkMessageBox
.showerror(
568 "This buffer has no associated filename",
570 self
.text
.focus_set()
572 head
, tail
= os
.path
.split(filename
)
573 base
, ext
= os
.path
.splitext(tail
)
575 ClassBrowser
.ClassBrowser(self
.flist
, base
, [head
])
577 def open_path_browser(self
, event
=None):
579 PathBrowser
.PathBrowser(self
.flist
)
581 def gotoline(self
, lineno
):
582 if lineno
is not None and lineno
> 0:
583 self
.text
.mark_set("insert", "%d.0" % lineno
)
584 self
.text
.tag_remove("sel", "1.0", "end")
585 self
.text
.tag_add("sel", "insert", "insert +1l")
588 def ispythonsource(self
, filename
):
589 if not filename
or os
.path
.isdir(filename
):
591 base
, ext
= os
.path
.splitext(os
.path
.basename(filename
))
592 if os
.path
.normcase(ext
) in (".py", ".pyw"):
600 return line
.startswith('#!') and line
.find('python') >= 0
602 def close_hook(self
):
604 self
.flist
.unregister_maybe_terminate(self
)
607 def set_close_hook(self
, close_hook
):
608 self
.close_hook
= close_hook
610 def filename_change_hook(self
):
612 self
.flist
.filename_changed_edit(self
)
613 self
.saved_change_hook()
614 self
.top
.update_windowlist_registry(self
)
615 self
.ResetColorizer()
617 def _addcolorizer(self
):
620 if self
.ispythonsource(self
.io
.filename
):
621 self
.color
= self
.ColorDelegator()
622 # can add more colorizers here...
624 self
.per
.removefilter(self
.undo
)
625 self
.per
.insertfilter(self
.color
)
626 self
.per
.insertfilter(self
.undo
)
628 def _rmcolorizer(self
):
631 self
.color
.removecolors()
632 self
.per
.removefilter(self
.color
)
635 def ResetColorizer(self
):
636 "Update the colour theme"
637 # Called from self.filename_change_hook and from configDialog.py
640 theme
= idleConf
.GetOption('main','Theme','name')
641 normal_colors
= idleConf
.GetHighlight(theme
, 'normal')
642 cursor_color
= idleConf
.GetHighlight(theme
, 'cursor', fgBg
='fg')
643 select_colors
= idleConf
.GetHighlight(theme
, 'hilite')
645 foreground
=normal_colors
['foreground'],
646 background
=normal_colors
['background'],
647 insertbackground
=cursor_color
,
648 selectforeground
=select_colors
['foreground'],
649 selectbackground
=select_colors
['background'],
653 "Update the text widgets' font if it is changed"
654 # Called from configDialog.py
656 if idleConf
.GetOption('main','EditorWindow','font-bold',type='bool'):
658 self
.text
.config(font
=(idleConf
.GetOption('main','EditorWindow','font'),
659 idleConf
.GetOption('main','EditorWindow','font-size'),
662 def RemoveKeybindings(self
):
663 "Remove the keybindings before they are changed."
664 # Called from configDialog.py
665 self
.Bindings
.default_keydefs
= keydefs
= idleConf
.GetCurrentKeySet()
666 for event
, keylist
in keydefs
.items():
667 self
.text
.event_delete(event
, *keylist
)
668 for extensionName
in self
.get_standard_extension_names():
669 xkeydefs
= idleConf
.GetExtensionBindings(extensionName
)
671 for event
, keylist
in xkeydefs
.items():
672 self
.text
.event_delete(event
, *keylist
)
674 def ApplyKeybindings(self
):
675 "Update the keybindings after they are changed"
676 # Called from configDialog.py
677 self
.Bindings
.default_keydefs
= keydefs
= idleConf
.GetCurrentKeySet()
678 self
.apply_bindings()
679 for extensionName
in self
.get_standard_extension_names():
680 xkeydefs
= idleConf
.GetExtensionBindings(extensionName
)
682 self
.apply_bindings(xkeydefs
)
683 #update menu accelerators
685 for menu
in self
.Bindings
.menudefs
:
686 menuEventDict
[menu
[0]] = {}
689 menuEventDict
[menu
[0]][prepstr(item
[0])[1]] = item
[1]
690 for menubarItem
in self
.menudict
.keys():
691 menu
= self
.menudict
[menubarItem
]
692 end
= menu
.index(END
) + 1
693 for index
in range(0, end
):
694 if menu
.type(index
) == 'command':
695 accel
= menu
.entrycget(index
, 'accelerator')
697 itemName
= menu
.entrycget(index
, 'label')
699 if menuEventDict
.has_key(menubarItem
):
700 if menuEventDict
[menubarItem
].has_key(itemName
):
701 event
= menuEventDict
[menubarItem
][itemName
]
703 accel
= get_accelerator(keydefs
, event
)
704 menu
.entryconfig(index
, accelerator
=accel
)
706 def set_notabs_indentwidth(self
):
707 "Update the indentwidth if changed and not using tabs in this window"
708 # Called from configDialog.py
710 self
.indentwidth
= idleConf
.GetOption('main', 'Indent','num-spaces',
713 def reset_help_menu_entries(self
):
714 "Update the additional help entries on the Help menu"
715 help_list
= idleConf
.GetAllExtraHelpSourcesList()
716 helpmenu
= self
.menudict
['help']
717 # first delete the extra help entries, if any
718 helpmenu_length
= helpmenu
.index(END
)
719 if helpmenu_length
> self
.base_helpmenu_length
:
720 helpmenu
.delete((self
.base_helpmenu_length
+ 1), helpmenu_length
)
723 helpmenu
.add_separator()
724 for entry
in help_list
:
725 cmd
= self
.__extra
_help
_callback
(entry
[1])
726 helpmenu
.add_command(label
=entry
[0], command
=cmd
)
727 # and update the menu dictionary
728 self
.menudict
['help'] = helpmenu
730 def __extra_help_callback(self
, helpfile
):
731 "Create a callback with the helpfile value frozen at definition time"
732 def display_extra_help(helpfile
=helpfile
):
733 if not helpfile
.startswith(('www', 'http')):
734 url
= os
.path
.normpath(helpfile
)
735 if sys
.platform
[:3] == 'win':
736 os
.startfile(helpfile
)
738 webbrowser
.open(helpfile
)
739 return display_extra_help
741 def update_recent_files_list(self
, new_file
=None):
742 "Load and update the recent files list and menus"
744 if os
.path
.exists(self
.recent_files_path
):
745 rf_list_file
= open(self
.recent_files_path
,'r')
747 rf_list
= rf_list_file
.readlines()
751 new_file
= os
.path
.abspath(new_file
) + '\n'
752 if new_file
in rf_list
:
753 rf_list
.remove(new_file
) # move to top
754 rf_list
.insert(0, new_file
)
755 # clean and save the recent files list
758 if '\0' in path
or not os
.path
.exists(path
[0:-1]):
759 bad_paths
.append(path
)
760 rf_list
= [path
for path
in rf_list
if path
not in bad_paths
]
761 ulchars
= "1234567890ABCDEFGHIJK"
762 rf_list
= rf_list
[0:len(ulchars
)]
763 rf_file
= open(self
.recent_files_path
, 'w')
765 rf_file
.writelines(rf_list
)
768 # for each edit window instance, construct the recent files menu
769 for instance
in self
.top
.instance_dict
.keys():
770 menu
= instance
.recent_files_menu
771 menu
.delete(1, END
) # clear, and rebuild:
772 for i
, file in zip(count(), rf_list
):
773 file_name
= file[0:-1] # zap \n
774 # make unicode string to display non-ASCII chars correctly
775 ufile_name
= self
._filename
_to
_unicode
(file_name
)
776 callback
= instance
.__recent
_file
_callback
(file_name
)
777 menu
.add_command(label
=ulchars
[i
] + " " + ufile_name
,
781 def __recent_file_callback(self
, file_name
):
782 def open_recent_file(fn_closure
=file_name
):
783 self
.io
.open(editFile
=fn_closure
)
784 return open_recent_file
786 def saved_change_hook(self
):
787 short
= self
.short_title()
788 long = self
.long_title()
790 title
= short
+ " - " + long
797 icon
= short
or long or title
798 if not self
.get_saved():
799 title
= "*%s*" % title
801 self
.top
.wm_title(title
)
802 self
.top
.wm_iconname(icon
)
805 return self
.undo
.get_saved()
807 def set_saved(self
, flag
):
808 self
.undo
.set_saved(flag
)
810 def reset_undo(self
):
811 self
.undo
.reset_undo()
813 def short_title(self
):
814 filename
= self
.io
.filename
816 filename
= os
.path
.basename(filename
)
817 # return unicode string to display non-ASCII chars correctly
818 return self
._filename
_to
_unicode
(filename
)
820 def long_title(self
):
821 # return unicode string to display non-ASCII chars correctly
822 return self
._filename
_to
_unicode
(self
.io
.filename
or "")
824 def center_insert_event(self
, event
):
827 def center(self
, mark
="insert"):
829 top
, bot
= self
.getwindowlines()
830 lineno
= self
.getlineno(mark
)
832 newtop
= max(1, lineno
- height
//2)
833 text
.yview(float(newtop
))
835 def getwindowlines(self
):
837 top
= self
.getlineno("@0,0")
838 bot
= self
.getlineno("@0,65535")
839 if top
== bot
and text
.winfo_height() == 1:
840 # Geometry manager hasn't run yet
841 height
= int(text
['height'])
842 bot
= top
+ height
- 1
845 def getlineno(self
, mark
="insert"):
847 return int(float(text
.index(mark
)))
849 def get_geometry(self
):
850 "Return (width, height, x, y)"
851 geom
= self
.top
.wm_geometry()
852 m
= re
.match(r
"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom
)
853 tuple = (map(int, m
.groups()))
856 def close_event(self
, event
):
861 if not self
.get_saved():
862 if self
.top
.state()!='normal':
866 return self
.io
.maybesave()
869 reply
= self
.maybesave()
870 if str(reply
) != "cancel":
876 self
.update_recent_files_list(new_file
=self
.io
.filename
)
877 WindowList
.unregister_callback(self
.postwindowsmenu
)
878 self
.unload_extensions()
883 self
.color
.close(False)
886 self
.tkinter_vars
= None
891 # unless override: unregister from flist, terminate if last window
894 def load_extensions(self
):
896 self
.load_standard_extensions()
898 def unload_extensions(self
):
899 for ins
in self
.extensions
.values():
900 if hasattr(ins
, "close"):
904 def load_standard_extensions(self
):
905 for name
in self
.get_standard_extension_names():
907 self
.load_extension(name
)
909 print "Failed to load extension", repr(name
)
911 traceback
.print_exc()
913 def get_standard_extension_names(self
):
914 return idleConf
.GetExtensions(editor_only
=True)
916 def load_extension(self
, name
):
918 mod
= __import__(name
, globals(), locals(), [])
920 print "\nFailed to import extension: ", name
922 cls
= getattr(mod
, name
)
923 keydefs
= idleConf
.GetExtensionBindings(name
)
924 if hasattr(cls
, "menudefs"):
925 self
.fill_menus(cls
.menudefs
, keydefs
)
927 self
.extensions
[name
] = ins
929 self
.apply_bindings(keydefs
)
930 for vevent
in keydefs
.keys():
931 methodname
= vevent
.replace("-", "_")
932 while methodname
[:1] == '<':
933 methodname
= methodname
[1:]
934 while methodname
[-1:] == '>':
935 methodname
= methodname
[:-1]
936 methodname
= methodname
+ "_event"
937 if hasattr(ins
, methodname
):
938 self
.text
.bind(vevent
, getattr(ins
, methodname
))
940 def apply_bindings(self
, keydefs
=None):
942 keydefs
= self
.Bindings
.default_keydefs
944 text
.keydefs
= keydefs
945 for event
, keylist
in keydefs
.items():
947 text
.event_add(event
, *keylist
)
949 def fill_menus(self
, menudefs
=None, keydefs
=None):
950 """Add appropriate entries to the menus and submenus
952 Menus that are absent or None in self.menudict are ignored.
955 menudefs
= self
.Bindings
.menudefs
957 keydefs
= self
.Bindings
.default_keydefs
958 menudict
= self
.menudict
960 for mname
, entrylist
in menudefs
:
961 menu
= menudict
.get(mname
)
964 for entry
in entrylist
:
968 label
, eventname
= entry
969 checkbutton
= (label
[:1] == '!')
972 underline
, label
= prepstr(label
)
973 accelerator
= get_accelerator(keydefs
, eventname
)
974 def command(text
=text
, eventname
=eventname
):
975 text
.event_generate(eventname
)
977 var
= self
.get_var_obj(eventname
, BooleanVar
)
978 menu
.add_checkbutton(label
=label
, underline
=underline
,
979 command
=command
, accelerator
=accelerator
,
982 menu
.add_command(label
=label
, underline
=underline
,
984 accelerator
=accelerator
)
986 def getvar(self
, name
):
987 var
= self
.get_var_obj(name
)
992 raise NameError, name
994 def setvar(self
, name
, value
, vartype
=None):
995 var
= self
.get_var_obj(name
, vartype
)
999 raise NameError, name
1001 def get_var_obj(self
, name
, vartype
=None):
1002 var
= self
.tkinter_vars
.get(name
)
1003 if not var
and vartype
:
1004 # create a Tkinter variable object with self.text as master:
1005 self
.tkinter_vars
[name
] = var
= vartype(self
.text
)
1008 # Tk implementations of "virtual text methods" -- each platform
1009 # reusing IDLE's support code needs to define these for its GUI's
1012 # Is character at text_index in a Python string? Return 0 for
1013 # "guaranteed no", true for anything else. This info is expensive
1014 # to compute ab initio, but is probably already known by the
1015 # platform's colorizer.
1017 def is_char_in_string(self
, text_index
):
1019 # Return true iff colorizer hasn't (re)gotten this far
1020 # yet, or the character is tagged as being in a string
1021 return self
.text
.tag_prevrange("TODO", text_index
) or \
1022 "STRING" in self
.text
.tag_names(text_index
)
1024 # The colorizer is missing: assume the worst
1027 # If a selection is defined in the text widget, return (start,
1028 # end) as Tkinter text indices, otherwise return (None, None)
1029 def get_selection_indices(self
):
1031 first
= self
.text
.index("sel.first")
1032 last
= self
.text
.index("sel.last")
1037 # Return the text widget's current view of what a tab stop means
1038 # (equivalent width in spaces).
1040 def get_tabwidth(self
):
1041 current
= self
.text
['tabs'] or TK_TABWIDTH_DEFAULT
1044 # Set the text widget's current view of what a tab stop means.
1046 def set_tabwidth(self
, newtabwidth
):
1048 if self
.get_tabwidth() != newtabwidth
:
1049 pixels
= text
.tk
.call("font", "measure", text
["font"],
1050 "-displayof", text
.master
,
1052 text
.configure(tabs
=pixels
)
1054 # If ispythonsource and guess are true, guess a good value for
1055 # indentwidth based on file content (if possible), and if
1056 # indentwidth != tabwidth set usetabs false.
1057 # In any case, adjust the Text widget's view of what a tab
1060 def set_indentation_params(self
, ispythonsource
, guess
=True):
1061 if guess
and ispythonsource
:
1062 i
= self
.guess_indent()
1064 self
.indentwidth
= i
1065 if self
.indentwidth
!= self
.tabwidth
:
1066 self
.usetabs
= False
1067 self
.set_tabwidth(self
.tabwidth
)
1069 def smart_backspace_event(self
, event
):
1071 first
, last
= self
.get_selection_indices()
1073 text
.delete(first
, last
)
1074 text
.mark_set("insert", first
)
1076 # Delete whitespace left, until hitting a real char or closest
1077 # preceding virtual tab stop.
1078 chars
= text
.get("insert linestart", "insert")
1080 if text
.compare("insert", ">", "1.0"):
1081 # easy: delete preceding newline
1082 text
.delete("insert-1c")
1084 text
.bell() # at start of buffer
1086 if chars
[-1] not in " \t":
1087 # easy: delete preceding real char
1088 text
.delete("insert-1c")
1090 # Ick. It may require *inserting* spaces if we back up over a
1091 # tab character! This is written to be clear, not fast.
1092 tabwidth
= self
.tabwidth
1093 have
= len(chars
.expandtabs(tabwidth
))
1095 want
= ((have
- 1) // self
.indentwidth
) * self
.indentwidth
1096 # Debug prompt is multilined....
1097 last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
1100 if chars
== last_line_of_prompt
:
1103 ncharsdeleted
= ncharsdeleted
+ 1
1104 have
= len(chars
.expandtabs(tabwidth
))
1105 if have
<= want
or chars
[-1] not in " \t":
1107 text
.undo_block_start()
1108 text
.delete("insert-%dc" % ncharsdeleted
, "insert")
1110 text
.insert("insert", ' ' * (want
- have
))
1111 text
.undo_block_stop()
1114 def smart_indent_event(self
, event
):
1115 # if intraline selection:
1117 # elif multiline selection:
1122 first
, last
= self
.get_selection_indices()
1123 text
.undo_block_start()
1126 if index2line(first
) != index2line(last
):
1127 return self
.indent_region_event(event
)
1128 text
.delete(first
, last
)
1129 text
.mark_set("insert", first
)
1130 prefix
= text
.get("insert linestart", "insert")
1131 raw
, effective
= classifyws(prefix
, self
.tabwidth
)
1132 if raw
== len(prefix
):
1133 # only whitespace to the left
1134 self
.reindent_to(effective
+ self
.indentwidth
)
1136 # tab to the next 'stop' within or to right of line's text:
1140 effective
= len(prefix
.expandtabs(self
.tabwidth
))
1141 n
= self
.indentwidth
1142 pad
= ' ' * (n
- effective
% n
)
1143 text
.insert("insert", pad
)
1147 text
.undo_block_stop()
1149 def newline_and_indent_event(self
, event
):
1151 first
, last
= self
.get_selection_indices()
1152 text
.undo_block_start()
1155 text
.delete(first
, last
)
1156 text
.mark_set("insert", first
)
1157 line
= text
.get("insert linestart", "insert")
1159 while i
< n
and line
[i
] in " \t":
1162 # the cursor is in or at leading indentation in a continuation
1163 # line; just inject an empty line at the start
1164 text
.insert("insert linestart", '\n')
1167 # strip whitespace before insert point unless it's in the prompt
1169 last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
1170 while line
and line
[-1] in " \t" and line
!= last_line_of_prompt
:
1174 text
.delete("insert - %d chars" % i
, "insert")
1175 # strip whitespace after insert point
1176 while text
.get("insert") in " \t":
1177 text
.delete("insert")
1179 text
.insert("insert", '\n')
1181 # adjust indentation for continuations and block
1182 # open/close first need to find the last stmt
1183 lno
= index2line(text
.index('insert'))
1184 y
= PyParse
.Parser(self
.indentwidth
, self
.tabwidth
)
1185 if not self
.context_use_ps1
:
1186 for context
in self
.num_context_lines
:
1187 startat
= max(lno
- context
, 1)
1188 startatindex
= `startat`
+ ".0"
1189 rawtext
= text
.get(startatindex
, "insert")
1191 bod
= y
.find_good_parse_start(
1192 self
.context_use_ps1
,
1193 self
._build
_char
_in
_string
_func
(startatindex
))
1194 if bod
is not None or startat
== 1:
1198 r
= text
.tag_prevrange("console", "insert")
1202 startatindex
= "1.0"
1203 rawtext
= text
.get(startatindex
, "insert")
1207 c
= y
.get_continuation_type()
1208 if c
!= PyParse
.C_NONE
:
1209 # The current stmt hasn't ended yet.
1210 if c
== PyParse
.C_STRING_FIRST_LINE
:
1211 # after the first line of a string; do not indent at all
1213 elif c
== PyParse
.C_STRING_NEXT_LINES
:
1214 # inside a string which started before this line;
1215 # just mimic the current indent
1216 text
.insert("insert", indent
)
1217 elif c
== PyParse
.C_BRACKET
:
1218 # line up with the first (if any) element of the
1219 # last open bracket structure; else indent one
1220 # level beyond the indent of the line with the
1222 self
.reindent_to(y
.compute_bracket_indent())
1223 elif c
== PyParse
.C_BACKSLASH
:
1224 # if more than one line in this stmt already, just
1225 # mimic the current indent; else if initial line
1226 # has a start on an assignment stmt, indent to
1227 # beyond leftmost =; else to beyond first chunk of
1228 # non-whitespace on initial line
1229 if y
.get_num_lines_in_stmt() > 1:
1230 text
.insert("insert", indent
)
1232 self
.reindent_to(y
.compute_backslash_indent())
1234 assert 0, "bogus continuation type %r" % (c
,)
1237 # This line starts a brand new stmt; indent relative to
1238 # indentation of initial line of closest preceding
1240 indent
= y
.get_base_indent_string()
1241 text
.insert("insert", indent
)
1242 if y
.is_block_opener():
1243 self
.smart_indent_event(event
)
1244 elif indent
and y
.is_block_closer():
1245 self
.smart_backspace_event(event
)
1249 text
.undo_block_stop()
1251 # Our editwin provides a is_char_in_string function that works
1252 # with a Tk text index, but PyParse only knows about offsets into
1253 # a string. This builds a function for PyParse that accepts an
1256 def _build_char_in_string_func(self
, startindex
):
1257 def inner(offset
, _startindex
=startindex
,
1258 _icis
=self
.is_char_in_string
):
1259 return _icis(_startindex
+ "+%dc" % offset
)
1262 def indent_region_event(self
, event
):
1263 head
, tail
, chars
, lines
= self
.get_region()
1264 for pos
in range(len(lines
)):
1267 raw
, effective
= classifyws(line
, self
.tabwidth
)
1268 effective
= effective
+ self
.indentwidth
1269 lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
1270 self
.set_region(head
, tail
, chars
, lines
)
1273 def dedent_region_event(self
, event
):
1274 head
, tail
, chars
, lines
= self
.get_region()
1275 for pos
in range(len(lines
)):
1278 raw
, effective
= classifyws(line
, self
.tabwidth
)
1279 effective
= max(effective
- self
.indentwidth
, 0)
1280 lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
1281 self
.set_region(head
, tail
, chars
, lines
)
1284 def comment_region_event(self
, event
):
1285 head
, tail
, chars
, lines
= self
.get_region()
1286 for pos
in range(len(lines
) - 1):
1288 lines
[pos
] = '##' + line
1289 self
.set_region(head
, tail
, chars
, lines
)
1291 def uncomment_region_event(self
, event
):
1292 head
, tail
, chars
, lines
= self
.get_region()
1293 for pos
in range(len(lines
)):
1297 if line
[:2] == '##':
1299 elif line
[:1] == '#':
1302 self
.set_region(head
, tail
, chars
, lines
)
1304 def tabify_region_event(self
, event
):
1305 head
, tail
, chars
, lines
= self
.get_region()
1306 tabwidth
= self
._asktabwidth
()
1307 for pos
in range(len(lines
)):
1310 raw
, effective
= classifyws(line
, tabwidth
)
1311 ntabs
, nspaces
= divmod(effective
, tabwidth
)
1312 lines
[pos
] = '\t' * ntabs
+ ' ' * nspaces
+ line
[raw
:]
1313 self
.set_region(head
, tail
, chars
, lines
)
1315 def untabify_region_event(self
, event
):
1316 head
, tail
, chars
, lines
= self
.get_region()
1317 tabwidth
= self
._asktabwidth
()
1318 for pos
in range(len(lines
)):
1319 lines
[pos
] = lines
[pos
].expandtabs(tabwidth
)
1320 self
.set_region(head
, tail
, chars
, lines
)
1322 def toggle_tabs_event(self
, event
):
1325 "Turn tabs " + ("on", "off")[self
.usetabs
] +
1326 "?\nIndent width " +
1327 ("will be", "remains at")[self
.usetabs
] + " 8." +
1328 "\n Note: a tab is always 8 columns",
1330 self
.usetabs
= not self
.usetabs
1331 # Try to prevent inconsistent indentation.
1332 # User must change indent width manually after using tabs.
1333 self
.indentwidth
= 8
1336 # XXX this isn't bound to anything -- see tabwidth comments
1337 ## def change_tabwidth_event(self, event):
1338 ## new = self._asktabwidth()
1339 ## if new != self.tabwidth:
1340 ## self.tabwidth = new
1341 ## self.set_indentation_params(0, guess=0)
1344 def change_indentwidth_event(self
, event
):
1345 new
= self
.askinteger(
1347 "New indent width (2-16)\n(Always use 8 when using tabs)",
1349 initialvalue
=self
.indentwidth
,
1352 if new
and new
!= self
.indentwidth
and not self
.usetabs
:
1353 self
.indentwidth
= new
1356 def get_region(self
):
1358 first
, last
= self
.get_selection_indices()
1360 head
= text
.index(first
+ " linestart")
1361 tail
= text
.index(last
+ "-1c lineend +1c")
1363 head
= text
.index("insert linestart")
1364 tail
= text
.index("insert lineend +1c")
1365 chars
= text
.get(head
, tail
)
1366 lines
= chars
.split("\n")
1367 return head
, tail
, chars
, lines
1369 def set_region(self
, head
, tail
, chars
, lines
):
1371 newchars
= "\n".join(lines
)
1372 if newchars
== chars
:
1375 text
.tag_remove("sel", "1.0", "end")
1376 text
.mark_set("insert", head
)
1377 text
.undo_block_start()
1378 text
.delete(head
, tail
)
1379 text
.insert(head
, newchars
)
1380 text
.undo_block_stop()
1381 text
.tag_add("sel", head
, "insert")
1383 # Make string that displays as n leading blanks.
1385 def _make_blanks(self
, n
):
1387 ntabs
, nspaces
= divmod(n
, self
.tabwidth
)
1388 return '\t' * ntabs
+ ' ' * nspaces
1392 # Delete from beginning of line to insert point, then reinsert
1393 # column logical (meaning use tabs if appropriate) spaces.
1395 def reindent_to(self
, column
):
1397 text
.undo_block_start()
1398 if text
.compare("insert linestart", "!=", "insert"):
1399 text
.delete("insert linestart", "insert")
1401 text
.insert("insert", self
._make
_blanks
(column
))
1402 text
.undo_block_stop()
1404 def _asktabwidth(self
):
1405 return self
.askinteger(
1407 "Columns per tab? (2-16)",
1409 initialvalue
=self
.indentwidth
,
1411 maxvalue
=16) or self
.tabwidth
1413 # Guess indentwidth from text content.
1414 # Return guessed indentwidth. This should not be believed unless
1415 # it's in a reasonable range (e.g., it will be 0 if no indented
1416 # blocks are found).
1418 def guess_indent(self
):
1419 opener
, indented
= IndentSearcher(self
.text
, self
.tabwidth
).run()
1420 if opener
and indented
:
1421 raw
, indentsmall
= classifyws(opener
, self
.tabwidth
)
1422 raw
, indentlarge
= classifyws(indented
, self
.tabwidth
)
1424 indentsmall
= indentlarge
= 0
1425 return indentlarge
- indentsmall
1427 # "line.col" -> line, as an int
1428 def index2line(index
):
1429 return int(float(index
))
1431 # Look at the leading whitespace in s.
1432 # Return pair (# of leading ws characters,
1433 # effective # of leading blanks after expanding
1434 # tabs to width tabwidth)
1436 def classifyws(s
, tabwidth
):
1441 effective
= effective
+ 1
1444 effective
= (effective
// tabwidth
+ 1) * tabwidth
1447 return raw
, effective
1450 _tokenize
= tokenize
1453 class IndentSearcher(object):
1455 # .run() chews over the Text widget, looking for a block opener
1456 # and the stmt following it. Returns a pair,
1457 # (line containing block opener, line containing stmt)
1458 # Either or both may be None.
1460 def __init__(self
, text
, tabwidth
):
1462 self
.tabwidth
= tabwidth
1463 self
.i
= self
.finished
= 0
1464 self
.blkopenline
= self
.indentedline
= None
1469 i
= self
.i
= self
.i
+ 1
1470 mark
= repr(i
) + ".0"
1471 if self
.text
.compare(mark
, ">=", "end"):
1473 return self
.text
.get(mark
, mark
+ " lineend+1c")
1475 def tokeneater(self
, type, token
, start
, end
, line
,
1476 INDENT
=_tokenize
.INDENT
,
1477 NAME
=_tokenize
.NAME
,
1478 OPENERS
=('class', 'def', 'for', 'if', 'try', 'while')):
1481 elif type == NAME
and token
in OPENERS
:
1482 self
.blkopenline
= line
1483 elif type == INDENT
and self
.blkopenline
:
1484 self
.indentedline
= line
1488 save_tabsize
= _tokenize
.tabsize
1489 _tokenize
.tabsize
= self
.tabwidth
1492 _tokenize
.tokenize(self
.readline
, self
.tokeneater
)
1493 except _tokenize
.TokenError
:
1494 # since we cut off the tokenizer early, we can trigger
1498 _tokenize
.tabsize
= save_tabsize
1499 return self
.blkopenline
, self
.indentedline
1501 ### end autoindent code ###
1504 # Helper to extract the underscore from a string, e.g.
1505 # prepstr("Co_py") returns (2, "Copy").
1514 'bracketright': ']',
1518 def get_accelerator(keydefs
, eventname
):
1519 keylist
= keydefs
.get(eventname
)
1523 s
= re
.sub(r
"-[a-z]\b", lambda m
: m
.group().upper(), s
)
1524 s
= re
.sub(r
"\b\w+\b", lambda m
: keynames
.get(m
.group(), m
.group()), s
)
1525 s
= re
.sub("Key-", "", s
)
1526 s
= re
.sub("Cancel","Ctrl-Break",s
) # dscherer@cmu.edu
1527 s
= re
.sub("Control-", "Ctrl-", s
)
1528 s
= re
.sub("-", "+", s
)
1529 s
= re
.sub("><", " ", s
)
1530 s
= re
.sub("<", "", s
)
1531 s
= re
.sub(">", "", s
)
1535 def fixwordbreaks(root
):
1536 # Make sure that Tk's double-click and next/previous word
1537 # operations use our definition of a word (i.e. an identifier)
1539 tk
.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1540 tk
.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1541 tk
.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1549 filename
= sys
.argv
[1]
1552 edit
= EditorWindow(root
=root
, filename
=filename
)
1553 edit
.set_close_hook(root
.quit
)
1554 edit
.text
.bind("<<close-all-windows>>", edit
.close_event
)
1558 if __name__
== '__main__':