8 from MultiCall
import MultiCallCreator
17 from configHandler
import idleConf
18 import aboutDialog
, textView
, configDialog
21 # The default tab setting for a Text widget, in average-width characters.
22 TK_TABWIDTH_DEFAULT
= 8
24 def _sphinx_version():
25 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
26 major
, minor
, micro
, level
, serial
= sys
.version_info
27 release
= '%s%s' % (major
, minor
)
29 release
+= '%s' % (micro
,)
30 if level
== 'candidate':
31 release
+= 'rc%s' % (serial
,)
32 elif level
!= 'final':
33 release
+= '%s%s' % (level
[0], serial
)
36 def _find_module(fullname
, path
=None):
37 """Version of imp.find_module() that handles hierarchical module names"""
40 for tgt
in fullname
.split('.'):
42 file.close() # close intermediate files
43 (file, filename
, descr
) = imp
.find_module(tgt
, path
)
44 if descr
[2] == imp
.PY_SOURCE
:
45 break # find but not load the source file
46 module
= imp
.load_module(tgt
, file, filename
, descr
)
48 path
= module
.__path
__
49 except AttributeError:
50 raise ImportError, 'No source for module ' + module
.__name
__
51 return file, filename
, descr
53 class EditorWindow(object):
54 from Percolator
import Percolator
55 from ColorDelegator
import ColorDelegator
56 from UndoDelegator
import UndoDelegator
57 from IOBinding
import IOBinding
, filesystemencoding
, encoding
59 from Tkinter
import Toplevel
60 from MultiStatusBar
import MultiStatusBar
64 def __init__(self
, flist
=None, filename
=None, key
=None, root
=None):
65 if EditorWindow
.help_url
is None:
66 dochome
= os
.path
.join(sys
.prefix
, 'Doc', 'index.html')
67 if sys
.platform
.count('linux'):
68 # look for html docs in a couple of standard places
69 pyver
= 'python-docs-' + '%s.%s.%s' % sys
.version_info
[:3]
70 if os
.path
.isdir('/var/www/html/python/'): # "python2" rpm
71 dochome
= '/var/www/html/python/index.html'
73 basepath
= '/usr/share/doc/' # standard location
74 dochome
= os
.path
.join(basepath
, pyver
,
76 elif sys
.platform
[:3] == 'win':
77 chmfile
= os
.path
.join(sys
.prefix
, 'Doc',
78 'Python%s.chm' % _sphinx_version())
79 if os
.path
.isfile(chmfile
):
81 elif macosxSupport
.runningAsOSXApp():
82 # documentation is stored inside the python framework
83 dochome
= os
.path
.join(sys
.prefix
,
84 'Resources/English.lproj/Documentation/index.html')
85 dochome
= os
.path
.normpath(dochome
)
86 if os
.path
.isfile(dochome
):
87 EditorWindow
.help_url
= dochome
88 if sys
.platform
== 'darwin':
89 # Safari requires real file:-URLs
90 EditorWindow
.help_url
= 'file://' + EditorWindow
.help_url
92 EditorWindow
.help_url
= "http://docs.python.org/%d.%d" % sys
.version_info
[:2]
93 currentTheme
=idleConf
.CurrentTheme()
95 root
= root
or flist
.root
99 except AttributeError:
101 self
.menubar
= Menu(root
)
102 self
.top
= top
= WindowList
.ListedToplevel(root
, menu
=self
.menubar
)
104 self
.tkinter_vars
= flist
.vars
105 #self.top.instance_dict makes flist.inversedict avalable to
106 #configDialog.py so it can access all EditorWindow instaces
107 self
.top
.instance_dict
= flist
.inversedict
109 self
.tkinter_vars
= {} # keys: Tkinter event names
110 # values: Tkinter variable instances
111 self
.top
.instance_dict
= {}
112 self
.recent_files_path
= os
.path
.join(idleConf
.GetUserCfgDir(),
114 self
.text_frame
= text_frame
= Frame(top
)
115 self
.vbar
= vbar
= Scrollbar(text_frame
, name
='vbar')
116 self
.width
= idleConf
.GetOption('main','EditorWindow','width')
122 'height': idleConf
.GetOption('main', 'EditorWindow', 'height')}
124 # Starting with tk 8.5 we have to set the new tabstyle option
125 # to 'wordprocessor' to achieve the same display of tabs as in
127 text_options
['tabstyle'] = 'wordprocessor'
128 self
.text
= text
= MultiCallCreator(Text
)(text_frame
, **text_options
)
129 self
.top
.focused_widget
= self
.text
132 self
.apply_bindings()
134 self
.top
.protocol("WM_DELETE_WINDOW", self
.close
)
135 self
.top
.bind("<<close-window>>", self
.close_event
)
136 if macosxSupport
.runningAsOSXApp():
137 # Command-W on editorwindows doesn't work without this.
138 text
.bind('<<close-window>>', self
.close_event
)
139 text
.bind("<<cut>>", self
.cut
)
140 text
.bind("<<copy>>", self
.copy
)
141 text
.bind("<<paste>>", self
.paste
)
142 text
.bind("<<center-insert>>", self
.center_insert_event
)
143 text
.bind("<<help>>", self
.help_dialog
)
144 text
.bind("<<python-docs>>", self
.python_docs
)
145 text
.bind("<<about-idle>>", self
.about_dialog
)
146 text
.bind("<<open-config-dialog>>", self
.config_dialog
)
147 text
.bind("<<open-module>>", self
.open_module
)
148 text
.bind("<<do-nothing>>", lambda event
: "break")
149 text
.bind("<<select-all>>", self
.select_all
)
150 text
.bind("<<remove-selection>>", self
.remove_selection
)
151 text
.bind("<<find>>", self
.find_event
)
152 text
.bind("<<find-again>>", self
.find_again_event
)
153 text
.bind("<<find-in-files>>", self
.find_in_files_event
)
154 text
.bind("<<find-selection>>", self
.find_selection_event
)
155 text
.bind("<<replace>>", self
.replace_event
)
156 text
.bind("<<goto-line>>", self
.goto_line_event
)
157 text
.bind("<3>", self
.right_menu_event
)
158 text
.bind("<<smart-backspace>>",self
.smart_backspace_event
)
159 text
.bind("<<newline-and-indent>>",self
.newline_and_indent_event
)
160 text
.bind("<<smart-indent>>",self
.smart_indent_event
)
161 text
.bind("<<indent-region>>",self
.indent_region_event
)
162 text
.bind("<<dedent-region>>",self
.dedent_region_event
)
163 text
.bind("<<comment-region>>",self
.comment_region_event
)
164 text
.bind("<<uncomment-region>>",self
.uncomment_region_event
)
165 text
.bind("<<tabify-region>>",self
.tabify_region_event
)
166 text
.bind("<<untabify-region>>",self
.untabify_region_event
)
167 text
.bind("<<toggle-tabs>>",self
.toggle_tabs_event
)
168 text
.bind("<<change-indentwidth>>",self
.change_indentwidth_event
)
169 text
.bind("<Left>", self
.move_at_edge_if_selection(0))
170 text
.bind("<Right>", self
.move_at_edge_if_selection(1))
171 text
.bind("<<del-word-left>>", self
.del_word_left
)
172 text
.bind("<<del-word-right>>", self
.del_word_right
)
173 text
.bind("<<beginning-of-line>>", self
.home_callback
)
176 flist
.inversedict
[self
] = key
178 flist
.dict[key
] = self
179 text
.bind("<<open-new-window>>", self
.new_callback
)
180 text
.bind("<<close-all-windows>>", self
.flist
.close_all_callback
)
181 text
.bind("<<open-class-browser>>", self
.open_class_browser
)
182 text
.bind("<<open-path-browser>>", self
.open_path_browser
)
184 self
.set_status_bar()
185 vbar
['command'] = text
.yview
186 vbar
.pack(side
=RIGHT
, fill
=Y
)
187 text
['yscrollcommand'] = vbar
.set
188 fontWeight
= 'normal'
189 if idleConf
.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
191 text
.config(font
=(idleConf
.GetOption('main', 'EditorWindow', 'font'),
192 idleConf
.GetOption('main', 'EditorWindow', 'font-size'),
194 text_frame
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
195 text
.pack(side
=TOP
, fill
=BOTH
, expand
=1)
198 # usetabs true -> literal tab characters are used by indent and
199 # dedent cmds, possibly mixed with spaces if
200 # indentwidth is not a multiple of tabwidth,
201 # which will cause Tabnanny to nag!
202 # false -> tab characters are converted to spaces by indent
203 # and dedent cmds, and ditto TAB keystrokes
204 # Although use-spaces=0 can be configured manually in config-main.def,
205 # configuration of tabs v. spaces is not supported in the configuration
206 # dialog. IDLE promotes the preferred Python indentation: use spaces!
207 usespaces
= idleConf
.GetOption('main', 'Indent', 'use-spaces', type='bool')
208 self
.usetabs
= not usespaces
210 # tabwidth is the display width of a literal tab character.
211 # CAUTION: telling Tk to use anything other than its default
212 # tab setting causes it to use an entirely different tabbing algorithm,
213 # treating tab stops as fixed distances from the left margin.
214 # Nobody expects this, so for now tabwidth should never be changed.
215 self
.tabwidth
= 8 # must remain 8 until Tk is fixed.
217 # indentwidth is the number of screen characters per indent level.
218 # The recommended Python indentation is four spaces.
219 self
.indentwidth
= self
.tabwidth
220 self
.set_notabs_indentwidth()
222 # If context_use_ps1 is true, parsing searches back for a ps1 line;
223 # else searches for a popular (if, def, ...) Python stmt.
224 self
.context_use_ps1
= False
226 # When searching backwards for a reliable place to begin parsing,
227 # first start num_context_lines[0] lines back, then
228 # num_context_lines[1] lines back if that didn't work, and so on.
229 # The last value should be huge (larger than the # of lines in a
231 # Making the initial values larger slows things down more often.
232 self
.num_context_lines
= 50, 500, 5000000
234 self
.per
= per
= self
.Percolator(text
)
236 self
.undo
= undo
= self
.UndoDelegator()
237 per
.insertfilter(undo
)
238 text
.undo_block_start
= undo
.undo_block_start
239 text
.undo_block_stop
= undo
.undo_block_stop
240 undo
.set_saved_change_hook(self
.saved_change_hook
)
242 # IOBinding implements file I/O and printing functionality
243 self
.io
= io
= self
.IOBinding(self
)
244 io
.set_filename_change_hook(self
.filename_change_hook
)
246 # Create the recent files submenu
247 self
.recent_files_menu
= Menu(self
.menubar
)
248 self
.menudict
['file'].insert_cascade(3, label
='Recent Files',
250 menu
=self
.recent_files_menu
)
251 self
.update_recent_files_list()
253 self
.color
= None # initialized below in self.ResetColorizer
255 if os
.path
.exists(filename
) and not os
.path
.isdir(filename
):
256 io
.loadfile(filename
)
258 io
.set_filename(filename
)
259 self
.ResetColorizer()
260 self
.saved_change_hook()
262 self
.set_indentation_params(self
.ispythonsource(filename
))
264 self
.load_extensions()
266 menu
= self
.menudict
.get('windows')
268 end
= menu
.index("end")
275 WindowList
.register_callback(self
.postwindowsmenu
)
277 # Some abstractions so IDLE extensions are cross-IDE
278 self
.askyesno
= tkMessageBox
.askyesno
279 self
.askinteger
= tkSimpleDialog
.askinteger
280 self
.showerror
= tkMessageBox
.showerror
282 def _filename_to_unicode(self
, filename
):
283 """convert filename to unicode in order to display it in Tk"""
284 if isinstance(filename
, unicode) or not filename
:
288 return filename
.decode(self
.filesystemencoding
)
289 except UnicodeDecodeError:
292 return filename
.decode(self
.encoding
)
293 except UnicodeDecodeError:
294 # byte-to-byte conversion
295 return filename
.decode('iso8859-1')
297 def new_callback(self
, event
):
298 dirname
, basename
= self
.io
.defaultfilename()
299 self
.flist
.new(dirname
)
302 def home_callback(self
, event
):
303 if (event
.state
& 12) != 0 and event
.keysym
== "Home":
304 # state&1==shift, state&4==control, state&8==alt
305 return # <Modifier-Home>; fall back to class binding
307 if self
.text
.index("iomark") and \
308 self
.text
.compare("iomark", "<=", "insert lineend") and \
309 self
.text
.compare("insert linestart", "<=", "iomark"):
310 insertpt
= int(self
.text
.index("iomark").split(".")[1])
312 line
= self
.text
.get("insert linestart", "insert lineend")
313 for insertpt
in xrange(len(line
)):
314 if line
[insertpt
] not in (' ','\t'):
319 lineat
= int(self
.text
.index("insert").split('.')[1])
321 if insertpt
== lineat
:
324 dest
= "insert linestart+"+str(insertpt
)+"c"
326 if (event
.state
&1) == 0:
328 self
.text
.tag_remove("sel", "1.0", "end")
330 if not self
.text
.index("sel.first"):
331 self
.text
.mark_set("anchor","insert")
333 first
= self
.text
.index(dest
)
334 last
= self
.text
.index("anchor")
336 if self
.text
.compare(first
,">",last
):
337 first
,last
= last
,first
339 self
.text
.tag_remove("sel", "1.0", "end")
340 self
.text
.tag_add("sel", first
, last
)
342 self
.text
.mark_set("insert", dest
)
343 self
.text
.see("insert")
346 def set_status_bar(self
):
347 self
.status_bar
= self
.MultiStatusBar(self
.top
)
348 if macosxSupport
.runningAsOSXApp():
349 # Insert some padding to avoid obscuring some of the statusbar
350 # by the resize widget.
351 self
.status_bar
.set_label('_padding1', ' ', side
=RIGHT
)
352 self
.status_bar
.set_label('column', 'Col: ?', side
=RIGHT
)
353 self
.status_bar
.set_label('line', 'Ln: ?', side
=RIGHT
)
354 self
.status_bar
.pack(side
=BOTTOM
, fill
=X
)
355 self
.text
.bind("<<set-line-and-column>>", self
.set_line_and_column
)
356 self
.text
.event_add("<<set-line-and-column>>",
357 "<KeyRelease>", "<ButtonRelease>")
358 self
.text
.after_idle(self
.set_line_and_column
)
360 def set_line_and_column(self
, event
=None):
361 line
, column
= self
.text
.index(INSERT
).split('.')
362 self
.status_bar
.set_label('column', 'Col: %s' % column
)
363 self
.status_bar
.set_label('line', 'Ln: %s' % line
)
368 ("format", "F_ormat"),
370 ("options", "_Options"),
371 ("windows", "_Windows"),
375 if macosxSupport
.runningAsOSXApp():
377 menu_specs
[-2] = ("windows", "_Window")
380 def createmenubar(self
):
382 self
.menudict
= menudict
= {}
383 for name
, label
in self
.menu_specs
:
384 underline
, label
= prepstr(label
)
385 menudict
[name
] = menu
= Menu(mbar
, name
=name
)
386 mbar
.add_cascade(label
=label
, menu
=menu
, underline
=underline
)
388 if macosxSupport
.runningAsOSXApp():
389 # Insert the application menu
390 menudict
['application'] = menu
= Menu(mbar
, name
='apple')
391 mbar
.add_cascade(label
='IDLE', menu
=menu
)
394 self
.base_helpmenu_length
= self
.menudict
['help'].index(END
)
395 self
.reset_help_menu_entries()
397 def postwindowsmenu(self
):
398 # Only called when Windows menu exists
399 menu
= self
.menudict
['windows']
400 end
= menu
.index("end")
403 if end
> self
.wmenu_end
:
404 menu
.delete(self
.wmenu_end
+1, end
)
405 WindowList
.add_windows_to_menu(menu
)
409 def right_menu_event(self
, event
):
410 self
.text
.tag_remove("sel", "1.0", "end")
411 self
.text
.mark_set("insert", "@%d,%d" % (event
.x
, event
.y
))
416 iswin
= sys
.platform
[:3] == 'win'
418 self
.text
.config(cursor
="arrow")
419 rmenu
.tk_popup(event
.x_root
, event
.y_root
)
421 self
.text
.config(cursor
="ibeam")
424 # ("Label", "<<virtual-event>>"), ...
425 ("Close", "<<close-window>>"), # Example
428 def make_rmenu(self
):
429 rmenu
= Menu(self
.text
, tearoff
=0)
430 for label
, eventname
in self
.rmenu_specs
:
431 def command(text
=self
.text
, eventname
=eventname
):
432 text
.event_generate(eventname
)
433 rmenu
.add_command(label
=label
, command
=command
)
436 def about_dialog(self
, event
=None):
437 aboutDialog
.AboutDialog(self
.top
,'About IDLE')
439 def config_dialog(self
, event
=None):
440 configDialog
.ConfigDialog(self
.top
,'Settings')
442 def help_dialog(self
, event
=None):
443 fn
=os
.path
.join(os
.path
.abspath(os
.path
.dirname(__file__
)),'help.txt')
444 textView
.view_file(self
.top
,'Help',fn
)
446 def python_docs(self
, event
=None):
447 if sys
.platform
[:3] == 'win':
448 os
.startfile(self
.help_url
)
450 webbrowser
.open(self
.help_url
)
454 self
.text
.event_generate("<<Cut>>")
457 def copy(self
,event
):
458 if not self
.text
.tag_ranges("sel"):
459 # There is no selection, so do nothing and maybe interrupt.
461 self
.text
.event_generate("<<Copy>>")
464 def paste(self
,event
):
465 self
.text
.event_generate("<<Paste>>")
466 self
.text
.see("insert")
469 def select_all(self
, event
=None):
470 self
.text
.tag_add("sel", "1.0", "end-1c")
471 self
.text
.mark_set("insert", "1.0")
472 self
.text
.see("insert")
475 def remove_selection(self
, event
=None):
476 self
.text
.tag_remove("sel", "1.0", "end")
477 self
.text
.see("insert")
479 def move_at_edge_if_selection(self
, edge_index
):
480 """Cursor move begins at start or end of selection
482 When a left/right cursor key is pressed create and return to Tkinter a
483 function which causes a cursor move from the associated edge of the
487 self_text_index
= self
.text
.index
488 self_text_mark_set
= self
.text
.mark_set
489 edges_table
= ("sel.first+1c", "sel.last-1c")
490 def move_at_edge(event
):
491 if (event
.state
& 5) == 0: # no shift(==1) or control(==4) pressed
493 self_text_index("sel.first")
494 self_text_mark_set("insert", edges_table
[edge_index
])
499 def del_word_left(self
, event
):
500 self
.text
.event_generate('<Meta-Delete>')
503 def del_word_right(self
, event
):
504 self
.text
.event_generate('<Meta-d>')
507 def find_event(self
, event
):
508 SearchDialog
.find(self
.text
)
511 def find_again_event(self
, event
):
512 SearchDialog
.find_again(self
.text
)
515 def find_selection_event(self
, event
):
516 SearchDialog
.find_selection(self
.text
)
519 def find_in_files_event(self
, event
):
520 GrepDialog
.grep(self
.text
, self
.io
, self
.flist
)
523 def replace_event(self
, event
):
524 ReplaceDialog
.replace(self
.text
)
527 def goto_line_event(self
, event
):
529 lineno
= tkSimpleDialog
.askinteger("Goto",
530 "Go to line number:",parent
=text
)
536 text
.mark_set("insert", "%d.0" % lineno
)
539 def open_module(self
, event
=None):
540 # XXX Shouldn't this be in IOBinding or in FileList?
542 name
= self
.text
.get("sel.first", "sel.last")
547 name
= tkSimpleDialog
.askstring("Module",
548 "Enter the name of a Python module\n"
549 "to search on sys.path and open:",
550 parent
=self
.text
, initialvalue
=name
)
555 # XXX Ought to insert current file's directory in front of path
557 (f
, file, (suffix
, mode
, type)) = _find_module(name
)
558 except (NameError, ImportError), msg
:
559 tkMessageBox
.showerror("Import error", str(msg
), parent
=self
.text
)
561 if type != imp
.PY_SOURCE
:
562 tkMessageBox
.showerror("Unsupported type",
563 "%s is not a source module" % name
, parent
=self
.text
)
568 self
.flist
.open(file)
570 self
.io
.loadfile(file)
572 def open_class_browser(self
, event
=None):
573 filename
= self
.io
.filename
575 tkMessageBox
.showerror(
577 "This buffer has no associated filename",
579 self
.text
.focus_set()
581 head
, tail
= os
.path
.split(filename
)
582 base
, ext
= os
.path
.splitext(tail
)
584 ClassBrowser
.ClassBrowser(self
.flist
, base
, [head
])
586 def open_path_browser(self
, event
=None):
588 PathBrowser
.PathBrowser(self
.flist
)
590 def gotoline(self
, lineno
):
591 if lineno
is not None and lineno
> 0:
592 self
.text
.mark_set("insert", "%d.0" % lineno
)
593 self
.text
.tag_remove("sel", "1.0", "end")
594 self
.text
.tag_add("sel", "insert", "insert +1l")
597 def ispythonsource(self
, filename
):
598 if not filename
or os
.path
.isdir(filename
):
600 base
, ext
= os
.path
.splitext(os
.path
.basename(filename
))
601 if os
.path
.normcase(ext
) in (".py", ".pyw"):
609 return line
.startswith('#!') and line
.find('python') >= 0
611 def close_hook(self
):
613 self
.flist
.unregister_maybe_terminate(self
)
616 def set_close_hook(self
, close_hook
):
617 self
.close_hook
= close_hook
619 def filename_change_hook(self
):
621 self
.flist
.filename_changed_edit(self
)
622 self
.saved_change_hook()
623 self
.top
.update_windowlist_registry(self
)
624 self
.ResetColorizer()
626 def _addcolorizer(self
):
629 if self
.ispythonsource(self
.io
.filename
):
630 self
.color
= self
.ColorDelegator()
631 # can add more colorizers here...
633 self
.per
.removefilter(self
.undo
)
634 self
.per
.insertfilter(self
.color
)
635 self
.per
.insertfilter(self
.undo
)
637 def _rmcolorizer(self
):
640 self
.color
.removecolors()
641 self
.per
.removefilter(self
.color
)
644 def ResetColorizer(self
):
645 "Update the colour theme"
646 # Called from self.filename_change_hook and from configDialog.py
649 theme
= idleConf
.GetOption('main','Theme','name')
650 normal_colors
= idleConf
.GetHighlight(theme
, 'normal')
651 cursor_color
= idleConf
.GetHighlight(theme
, 'cursor', fgBg
='fg')
652 select_colors
= idleConf
.GetHighlight(theme
, 'hilite')
654 foreground
=normal_colors
['foreground'],
655 background
=normal_colors
['background'],
656 insertbackground
=cursor_color
,
657 selectforeground
=select_colors
['foreground'],
658 selectbackground
=select_colors
['background'],
662 "Update the text widgets' font if it is changed"
663 # Called from configDialog.py
665 if idleConf
.GetOption('main','EditorWindow','font-bold',type='bool'):
667 self
.text
.config(font
=(idleConf
.GetOption('main','EditorWindow','font'),
668 idleConf
.GetOption('main','EditorWindow','font-size'),
671 def RemoveKeybindings(self
):
672 "Remove the keybindings before they are changed."
673 # Called from configDialog.py
674 self
.Bindings
.default_keydefs
= keydefs
= idleConf
.GetCurrentKeySet()
675 for event
, keylist
in keydefs
.items():
676 self
.text
.event_delete(event
, *keylist
)
677 for extensionName
in self
.get_standard_extension_names():
678 xkeydefs
= idleConf
.GetExtensionBindings(extensionName
)
680 for event
, keylist
in xkeydefs
.items():
681 self
.text
.event_delete(event
, *keylist
)
683 def ApplyKeybindings(self
):
684 "Update the keybindings after they are changed"
685 # Called from configDialog.py
686 self
.Bindings
.default_keydefs
= keydefs
= idleConf
.GetCurrentKeySet()
687 self
.apply_bindings()
688 for extensionName
in self
.get_standard_extension_names():
689 xkeydefs
= idleConf
.GetExtensionBindings(extensionName
)
691 self
.apply_bindings(xkeydefs
)
692 #update menu accelerators
694 for menu
in self
.Bindings
.menudefs
:
695 menuEventDict
[menu
[0]] = {}
698 menuEventDict
[menu
[0]][prepstr(item
[0])[1]] = item
[1]
699 for menubarItem
in self
.menudict
.keys():
700 menu
= self
.menudict
[menubarItem
]
701 end
= menu
.index(END
) + 1
702 for index
in range(0, end
):
703 if menu
.type(index
) == 'command':
704 accel
= menu
.entrycget(index
, 'accelerator')
706 itemName
= menu
.entrycget(index
, 'label')
708 if menuEventDict
.has_key(menubarItem
):
709 if menuEventDict
[menubarItem
].has_key(itemName
):
710 event
= menuEventDict
[menubarItem
][itemName
]
712 accel
= get_accelerator(keydefs
, event
)
713 menu
.entryconfig(index
, accelerator
=accel
)
715 def set_notabs_indentwidth(self
):
716 "Update the indentwidth if changed and not using tabs in this window"
717 # Called from configDialog.py
719 self
.indentwidth
= idleConf
.GetOption('main', 'Indent','num-spaces',
722 def reset_help_menu_entries(self
):
723 "Update the additional help entries on the Help menu"
724 help_list
= idleConf
.GetAllExtraHelpSourcesList()
725 helpmenu
= self
.menudict
['help']
726 # first delete the extra help entries, if any
727 helpmenu_length
= helpmenu
.index(END
)
728 if helpmenu_length
> self
.base_helpmenu_length
:
729 helpmenu
.delete((self
.base_helpmenu_length
+ 1), helpmenu_length
)
732 helpmenu
.add_separator()
733 for entry
in help_list
:
734 cmd
= self
.__extra
_help
_callback
(entry
[1])
735 helpmenu
.add_command(label
=entry
[0], command
=cmd
)
736 # and update the menu dictionary
737 self
.menudict
['help'] = helpmenu
739 def __extra_help_callback(self
, helpfile
):
740 "Create a callback with the helpfile value frozen at definition time"
741 def display_extra_help(helpfile
=helpfile
):
742 if not helpfile
.startswith(('www', 'http')):
743 url
= os
.path
.normpath(helpfile
)
744 if sys
.platform
[:3] == 'win':
745 os
.startfile(helpfile
)
747 webbrowser
.open(helpfile
)
748 return display_extra_help
750 def update_recent_files_list(self
, new_file
=None):
751 "Load and update the recent files list and menus"
753 if os
.path
.exists(self
.recent_files_path
):
754 rf_list_file
= open(self
.recent_files_path
,'r')
756 rf_list
= rf_list_file
.readlines()
760 new_file
= os
.path
.abspath(new_file
) + '\n'
761 if new_file
in rf_list
:
762 rf_list
.remove(new_file
) # move to top
763 rf_list
.insert(0, new_file
)
764 # clean and save the recent files list
767 if '\0' in path
or not os
.path
.exists(path
[0:-1]):
768 bad_paths
.append(path
)
769 rf_list
= [path
for path
in rf_list
if path
not in bad_paths
]
770 ulchars
= "1234567890ABCDEFGHIJK"
771 rf_list
= rf_list
[0:len(ulchars
)]
772 rf_file
= open(self
.recent_files_path
, 'w')
774 rf_file
.writelines(rf_list
)
777 # for each edit window instance, construct the recent files menu
778 for instance
in self
.top
.instance_dict
.keys():
779 menu
= instance
.recent_files_menu
780 menu
.delete(1, END
) # clear, and rebuild:
781 for i
, file_name
in enumerate(rf_list
):
782 file_name
= file_name
.rstrip() # zap \n
783 # make unicode string to display non-ASCII chars correctly
784 ufile_name
= self
._filename
_to
_unicode
(file_name
)
785 callback
= instance
.__recent
_file
_callback
(file_name
)
786 menu
.add_command(label
=ulchars
[i
] + " " + ufile_name
,
790 def __recent_file_callback(self
, file_name
):
791 def open_recent_file(fn_closure
=file_name
):
792 self
.io
.open(editFile
=fn_closure
)
793 return open_recent_file
795 def saved_change_hook(self
):
796 short
= self
.short_title()
797 long = self
.long_title()
799 title
= short
+ " - " + long
806 icon
= short
or long or title
807 if not self
.get_saved():
808 title
= "*%s*" % title
810 self
.top
.wm_title(title
)
811 self
.top
.wm_iconname(icon
)
814 return self
.undo
.get_saved()
816 def set_saved(self
, flag
):
817 self
.undo
.set_saved(flag
)
819 def reset_undo(self
):
820 self
.undo
.reset_undo()
822 def short_title(self
):
823 filename
= self
.io
.filename
825 filename
= os
.path
.basename(filename
)
826 # return unicode string to display non-ASCII chars correctly
827 return self
._filename
_to
_unicode
(filename
)
829 def long_title(self
):
830 # return unicode string to display non-ASCII chars correctly
831 return self
._filename
_to
_unicode
(self
.io
.filename
or "")
833 def center_insert_event(self
, event
):
836 def center(self
, mark
="insert"):
838 top
, bot
= self
.getwindowlines()
839 lineno
= self
.getlineno(mark
)
841 newtop
= max(1, lineno
- height
//2)
842 text
.yview(float(newtop
))
844 def getwindowlines(self
):
846 top
= self
.getlineno("@0,0")
847 bot
= self
.getlineno("@0,65535")
848 if top
== bot
and text
.winfo_height() == 1:
849 # Geometry manager hasn't run yet
850 height
= int(text
['height'])
851 bot
= top
+ height
- 1
854 def getlineno(self
, mark
="insert"):
856 return int(float(text
.index(mark
)))
858 def get_geometry(self
):
859 "Return (width, height, x, y)"
860 geom
= self
.top
.wm_geometry()
861 m
= re
.match(r
"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom
)
862 tuple = (map(int, m
.groups()))
865 def close_event(self
, event
):
870 if not self
.get_saved():
871 if self
.top
.state()!='normal':
875 return self
.io
.maybesave()
878 reply
= self
.maybesave()
879 if str(reply
) != "cancel":
885 self
.update_recent_files_list(new_file
=self
.io
.filename
)
886 WindowList
.unregister_callback(self
.postwindowsmenu
)
887 self
.unload_extensions()
892 self
.color
.close(False)
895 self
.tkinter_vars
= None
900 # unless override: unregister from flist, terminate if last window
903 def load_extensions(self
):
905 self
.load_standard_extensions()
907 def unload_extensions(self
):
908 for ins
in self
.extensions
.values():
909 if hasattr(ins
, "close"):
913 def load_standard_extensions(self
):
914 for name
in self
.get_standard_extension_names():
916 self
.load_extension(name
)
918 print "Failed to load extension", repr(name
)
920 traceback
.print_exc()
922 def get_standard_extension_names(self
):
923 return idleConf
.GetExtensions(editor_only
=True)
925 def load_extension(self
, name
):
927 mod
= __import__(name
, globals(), locals(), [])
929 print "\nFailed to import extension: ", name
931 cls
= getattr(mod
, name
)
932 keydefs
= idleConf
.GetExtensionBindings(name
)
933 if hasattr(cls
, "menudefs"):
934 self
.fill_menus(cls
.menudefs
, keydefs
)
936 self
.extensions
[name
] = ins
938 self
.apply_bindings(keydefs
)
939 for vevent
in keydefs
.keys():
940 methodname
= vevent
.replace("-", "_")
941 while methodname
[:1] == '<':
942 methodname
= methodname
[1:]
943 while methodname
[-1:] == '>':
944 methodname
= methodname
[:-1]
945 methodname
= methodname
+ "_event"
946 if hasattr(ins
, methodname
):
947 self
.text
.bind(vevent
, getattr(ins
, methodname
))
949 def apply_bindings(self
, keydefs
=None):
951 keydefs
= self
.Bindings
.default_keydefs
953 text
.keydefs
= keydefs
954 for event
, keylist
in keydefs
.items():
956 text
.event_add(event
, *keylist
)
958 def fill_menus(self
, menudefs
=None, keydefs
=None):
959 """Add appropriate entries to the menus and submenus
961 Menus that are absent or None in self.menudict are ignored.
964 menudefs
= self
.Bindings
.menudefs
966 keydefs
= self
.Bindings
.default_keydefs
967 menudict
= self
.menudict
969 for mname
, entrylist
in menudefs
:
970 menu
= menudict
.get(mname
)
973 for entry
in entrylist
:
977 label
, eventname
= entry
978 checkbutton
= (label
[:1] == '!')
981 underline
, label
= prepstr(label
)
982 accelerator
= get_accelerator(keydefs
, eventname
)
983 def command(text
=text
, eventname
=eventname
):
984 text
.event_generate(eventname
)
986 var
= self
.get_var_obj(eventname
, BooleanVar
)
987 menu
.add_checkbutton(label
=label
, underline
=underline
,
988 command
=command
, accelerator
=accelerator
,
991 menu
.add_command(label
=label
, underline
=underline
,
993 accelerator
=accelerator
)
995 def getvar(self
, name
):
996 var
= self
.get_var_obj(name
)
1001 raise NameError, name
1003 def setvar(self
, name
, value
, vartype
=None):
1004 var
= self
.get_var_obj(name
, vartype
)
1008 raise NameError, name
1010 def get_var_obj(self
, name
, vartype
=None):
1011 var
= self
.tkinter_vars
.get(name
)
1012 if not var
and vartype
:
1013 # create a Tkinter variable object with self.text as master:
1014 self
.tkinter_vars
[name
] = var
= vartype(self
.text
)
1017 # Tk implementations of "virtual text methods" -- each platform
1018 # reusing IDLE's support code needs to define these for its GUI's
1021 # Is character at text_index in a Python string? Return 0 for
1022 # "guaranteed no", true for anything else. This info is expensive
1023 # to compute ab initio, but is probably already known by the
1024 # platform's colorizer.
1026 def is_char_in_string(self
, text_index
):
1028 # Return true iff colorizer hasn't (re)gotten this far
1029 # yet, or the character is tagged as being in a string
1030 return self
.text
.tag_prevrange("TODO", text_index
) or \
1031 "STRING" in self
.text
.tag_names(text_index
)
1033 # The colorizer is missing: assume the worst
1036 # If a selection is defined in the text widget, return (start,
1037 # end) as Tkinter text indices, otherwise return (None, None)
1038 def get_selection_indices(self
):
1040 first
= self
.text
.index("sel.first")
1041 last
= self
.text
.index("sel.last")
1046 # Return the text widget's current view of what a tab stop means
1047 # (equivalent width in spaces).
1049 def get_tabwidth(self
):
1050 current
= self
.text
['tabs'] or TK_TABWIDTH_DEFAULT
1053 # Set the text widget's current view of what a tab stop means.
1055 def set_tabwidth(self
, newtabwidth
):
1057 if self
.get_tabwidth() != newtabwidth
:
1058 pixels
= text
.tk
.call("font", "measure", text
["font"],
1059 "-displayof", text
.master
,
1061 text
.configure(tabs
=pixels
)
1063 # If ispythonsource and guess are true, guess a good value for
1064 # indentwidth based on file content (if possible), and if
1065 # indentwidth != tabwidth set usetabs false.
1066 # In any case, adjust the Text widget's view of what a tab
1069 def set_indentation_params(self
, ispythonsource
, guess
=True):
1070 if guess
and ispythonsource
:
1071 i
= self
.guess_indent()
1073 self
.indentwidth
= i
1074 if self
.indentwidth
!= self
.tabwidth
:
1075 self
.usetabs
= False
1076 self
.set_tabwidth(self
.tabwidth
)
1078 def smart_backspace_event(self
, event
):
1080 first
, last
= self
.get_selection_indices()
1082 text
.delete(first
, last
)
1083 text
.mark_set("insert", first
)
1085 # Delete whitespace left, until hitting a real char or closest
1086 # preceding virtual tab stop.
1087 chars
= text
.get("insert linestart", "insert")
1089 if text
.compare("insert", ">", "1.0"):
1090 # easy: delete preceding newline
1091 text
.delete("insert-1c")
1093 text
.bell() # at start of buffer
1095 if chars
[-1] not in " \t":
1096 # easy: delete preceding real char
1097 text
.delete("insert-1c")
1099 # Ick. It may require *inserting* spaces if we back up over a
1100 # tab character! This is written to be clear, not fast.
1101 tabwidth
= self
.tabwidth
1102 have
= len(chars
.expandtabs(tabwidth
))
1104 want
= ((have
- 1) // self
.indentwidth
) * self
.indentwidth
1105 # Debug prompt is multilined....
1106 last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
1109 if chars
== last_line_of_prompt
:
1112 ncharsdeleted
= ncharsdeleted
+ 1
1113 have
= len(chars
.expandtabs(tabwidth
))
1114 if have
<= want
or chars
[-1] not in " \t":
1116 text
.undo_block_start()
1117 text
.delete("insert-%dc" % ncharsdeleted
, "insert")
1119 text
.insert("insert", ' ' * (want
- have
))
1120 text
.undo_block_stop()
1123 def smart_indent_event(self
, event
):
1124 # if intraline selection:
1126 # elif multiline selection:
1131 first
, last
= self
.get_selection_indices()
1132 text
.undo_block_start()
1135 if index2line(first
) != index2line(last
):
1136 return self
.indent_region_event(event
)
1137 text
.delete(first
, last
)
1138 text
.mark_set("insert", first
)
1139 prefix
= text
.get("insert linestart", "insert")
1140 raw
, effective
= classifyws(prefix
, self
.tabwidth
)
1141 if raw
== len(prefix
):
1142 # only whitespace to the left
1143 self
.reindent_to(effective
+ self
.indentwidth
)
1145 # tab to the next 'stop' within or to right of line's text:
1149 effective
= len(prefix
.expandtabs(self
.tabwidth
))
1150 n
= self
.indentwidth
1151 pad
= ' ' * (n
- effective
% n
)
1152 text
.insert("insert", pad
)
1156 text
.undo_block_stop()
1158 def newline_and_indent_event(self
, event
):
1160 first
, last
= self
.get_selection_indices()
1161 text
.undo_block_start()
1164 text
.delete(first
, last
)
1165 text
.mark_set("insert", first
)
1166 line
= text
.get("insert linestart", "insert")
1168 while i
< n
and line
[i
] in " \t":
1171 # the cursor is in or at leading indentation in a continuation
1172 # line; just inject an empty line at the start
1173 text
.insert("insert linestart", '\n')
1176 # strip whitespace before insert point unless it's in the prompt
1178 last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
1179 while line
and line
[-1] in " \t" and line
!= last_line_of_prompt
:
1183 text
.delete("insert - %d chars" % i
, "insert")
1184 # strip whitespace after insert point
1185 while text
.get("insert") in " \t":
1186 text
.delete("insert")
1188 text
.insert("insert", '\n')
1190 # adjust indentation for continuations and block
1191 # open/close first need to find the last stmt
1192 lno
= index2line(text
.index('insert'))
1193 y
= PyParse
.Parser(self
.indentwidth
, self
.tabwidth
)
1194 if not self
.context_use_ps1
:
1195 for context
in self
.num_context_lines
:
1196 startat
= max(lno
- context
, 1)
1197 startatindex
= `startat`
+ ".0"
1198 rawtext
= text
.get(startatindex
, "insert")
1200 bod
= y
.find_good_parse_start(
1201 self
.context_use_ps1
,
1202 self
._build
_char
_in
_string
_func
(startatindex
))
1203 if bod
is not None or startat
== 1:
1207 r
= text
.tag_prevrange("console", "insert")
1211 startatindex
= "1.0"
1212 rawtext
= text
.get(startatindex
, "insert")
1216 c
= y
.get_continuation_type()
1217 if c
!= PyParse
.C_NONE
:
1218 # The current stmt hasn't ended yet.
1219 if c
== PyParse
.C_STRING_FIRST_LINE
:
1220 # after the first line of a string; do not indent at all
1222 elif c
== PyParse
.C_STRING_NEXT_LINES
:
1223 # inside a string which started before this line;
1224 # just mimic the current indent
1225 text
.insert("insert", indent
)
1226 elif c
== PyParse
.C_BRACKET
:
1227 # line up with the first (if any) element of the
1228 # last open bracket structure; else indent one
1229 # level beyond the indent of the line with the
1231 self
.reindent_to(y
.compute_bracket_indent())
1232 elif c
== PyParse
.C_BACKSLASH
:
1233 # if more than one line in this stmt already, just
1234 # mimic the current indent; else if initial line
1235 # has a start on an assignment stmt, indent to
1236 # beyond leftmost =; else to beyond first chunk of
1237 # non-whitespace on initial line
1238 if y
.get_num_lines_in_stmt() > 1:
1239 text
.insert("insert", indent
)
1241 self
.reindent_to(y
.compute_backslash_indent())
1243 assert 0, "bogus continuation type %r" % (c
,)
1246 # This line starts a brand new stmt; indent relative to
1247 # indentation of initial line of closest preceding
1249 indent
= y
.get_base_indent_string()
1250 text
.insert("insert", indent
)
1251 if y
.is_block_opener():
1252 self
.smart_indent_event(event
)
1253 elif indent
and y
.is_block_closer():
1254 self
.smart_backspace_event(event
)
1258 text
.undo_block_stop()
1260 # Our editwin provides a is_char_in_string function that works
1261 # with a Tk text index, but PyParse only knows about offsets into
1262 # a string. This builds a function for PyParse that accepts an
1265 def _build_char_in_string_func(self
, startindex
):
1266 def inner(offset
, _startindex
=startindex
,
1267 _icis
=self
.is_char_in_string
):
1268 return _icis(_startindex
+ "+%dc" % offset
)
1271 def indent_region_event(self
, event
):
1272 head
, tail
, chars
, lines
= self
.get_region()
1273 for pos
in range(len(lines
)):
1276 raw
, effective
= classifyws(line
, self
.tabwidth
)
1277 effective
= effective
+ self
.indentwidth
1278 lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
1279 self
.set_region(head
, tail
, chars
, lines
)
1282 def dedent_region_event(self
, event
):
1283 head
, tail
, chars
, lines
= self
.get_region()
1284 for pos
in range(len(lines
)):
1287 raw
, effective
= classifyws(line
, self
.tabwidth
)
1288 effective
= max(effective
- self
.indentwidth
, 0)
1289 lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
1290 self
.set_region(head
, tail
, chars
, lines
)
1293 def comment_region_event(self
, event
):
1294 head
, tail
, chars
, lines
= self
.get_region()
1295 for pos
in range(len(lines
) - 1):
1297 lines
[pos
] = '##' + line
1298 self
.set_region(head
, tail
, chars
, lines
)
1300 def uncomment_region_event(self
, event
):
1301 head
, tail
, chars
, lines
= self
.get_region()
1302 for pos
in range(len(lines
)):
1306 if line
[:2] == '##':
1308 elif line
[:1] == '#':
1311 self
.set_region(head
, tail
, chars
, lines
)
1313 def tabify_region_event(self
, event
):
1314 head
, tail
, chars
, lines
= self
.get_region()
1315 tabwidth
= self
._asktabwidth
()
1316 for pos
in range(len(lines
)):
1319 raw
, effective
= classifyws(line
, tabwidth
)
1320 ntabs
, nspaces
= divmod(effective
, tabwidth
)
1321 lines
[pos
] = '\t' * ntabs
+ ' ' * nspaces
+ line
[raw
:]
1322 self
.set_region(head
, tail
, chars
, lines
)
1324 def untabify_region_event(self
, event
):
1325 head
, tail
, chars
, lines
= self
.get_region()
1326 tabwidth
= self
._asktabwidth
()
1327 for pos
in range(len(lines
)):
1328 lines
[pos
] = lines
[pos
].expandtabs(tabwidth
)
1329 self
.set_region(head
, tail
, chars
, lines
)
1331 def toggle_tabs_event(self
, event
):
1334 "Turn tabs " + ("on", "off")[self
.usetabs
] +
1335 "?\nIndent width " +
1336 ("will be", "remains at")[self
.usetabs
] + " 8." +
1337 "\n Note: a tab is always 8 columns",
1339 self
.usetabs
= not self
.usetabs
1340 # Try to prevent inconsistent indentation.
1341 # User must change indent width manually after using tabs.
1342 self
.indentwidth
= 8
1345 # XXX this isn't bound to anything -- see tabwidth comments
1346 ## def change_tabwidth_event(self, event):
1347 ## new = self._asktabwidth()
1348 ## if new != self.tabwidth:
1349 ## self.tabwidth = new
1350 ## self.set_indentation_params(0, guess=0)
1353 def change_indentwidth_event(self
, event
):
1354 new
= self
.askinteger(
1356 "New indent width (2-16)\n(Always use 8 when using tabs)",
1358 initialvalue
=self
.indentwidth
,
1361 if new
and new
!= self
.indentwidth
and not self
.usetabs
:
1362 self
.indentwidth
= new
1365 def get_region(self
):
1367 first
, last
= self
.get_selection_indices()
1369 head
= text
.index(first
+ " linestart")
1370 tail
= text
.index(last
+ "-1c lineend +1c")
1372 head
= text
.index("insert linestart")
1373 tail
= text
.index("insert lineend +1c")
1374 chars
= text
.get(head
, tail
)
1375 lines
= chars
.split("\n")
1376 return head
, tail
, chars
, lines
1378 def set_region(self
, head
, tail
, chars
, lines
):
1380 newchars
= "\n".join(lines
)
1381 if newchars
== chars
:
1384 text
.tag_remove("sel", "1.0", "end")
1385 text
.mark_set("insert", head
)
1386 text
.undo_block_start()
1387 text
.delete(head
, tail
)
1388 text
.insert(head
, newchars
)
1389 text
.undo_block_stop()
1390 text
.tag_add("sel", head
, "insert")
1392 # Make string that displays as n leading blanks.
1394 def _make_blanks(self
, n
):
1396 ntabs
, nspaces
= divmod(n
, self
.tabwidth
)
1397 return '\t' * ntabs
+ ' ' * nspaces
1401 # Delete from beginning of line to insert point, then reinsert
1402 # column logical (meaning use tabs if appropriate) spaces.
1404 def reindent_to(self
, column
):
1406 text
.undo_block_start()
1407 if text
.compare("insert linestart", "!=", "insert"):
1408 text
.delete("insert linestart", "insert")
1410 text
.insert("insert", self
._make
_blanks
(column
))
1411 text
.undo_block_stop()
1413 def _asktabwidth(self
):
1414 return self
.askinteger(
1416 "Columns per tab? (2-16)",
1418 initialvalue
=self
.indentwidth
,
1420 maxvalue
=16) or self
.tabwidth
1422 # Guess indentwidth from text content.
1423 # Return guessed indentwidth. This should not be believed unless
1424 # it's in a reasonable range (e.g., it will be 0 if no indented
1425 # blocks are found).
1427 def guess_indent(self
):
1428 opener
, indented
= IndentSearcher(self
.text
, self
.tabwidth
).run()
1429 if opener
and indented
:
1430 raw
, indentsmall
= classifyws(opener
, self
.tabwidth
)
1431 raw
, indentlarge
= classifyws(indented
, self
.tabwidth
)
1433 indentsmall
= indentlarge
= 0
1434 return indentlarge
- indentsmall
1436 # "line.col" -> line, as an int
1437 def index2line(index
):
1438 return int(float(index
))
1440 # Look at the leading whitespace in s.
1441 # Return pair (# of leading ws characters,
1442 # effective # of leading blanks after expanding
1443 # tabs to width tabwidth)
1445 def classifyws(s
, tabwidth
):
1450 effective
= effective
+ 1
1453 effective
= (effective
// tabwidth
+ 1) * tabwidth
1456 return raw
, effective
1459 _tokenize
= tokenize
1462 class IndentSearcher(object):
1464 # .run() chews over the Text widget, looking for a block opener
1465 # and the stmt following it. Returns a pair,
1466 # (line containing block opener, line containing stmt)
1467 # Either or both may be None.
1469 def __init__(self
, text
, tabwidth
):
1471 self
.tabwidth
= tabwidth
1472 self
.i
= self
.finished
= 0
1473 self
.blkopenline
= self
.indentedline
= None
1478 i
= self
.i
= self
.i
+ 1
1479 mark
= repr(i
) + ".0"
1480 if self
.text
.compare(mark
, ">=", "end"):
1482 return self
.text
.get(mark
, mark
+ " lineend+1c")
1484 def tokeneater(self
, type, token
, start
, end
, line
,
1485 INDENT
=_tokenize
.INDENT
,
1486 NAME
=_tokenize
.NAME
,
1487 OPENERS
=('class', 'def', 'for', 'if', 'try', 'while')):
1490 elif type == NAME
and token
in OPENERS
:
1491 self
.blkopenline
= line
1492 elif type == INDENT
and self
.blkopenline
:
1493 self
.indentedline
= line
1497 save_tabsize
= _tokenize
.tabsize
1498 _tokenize
.tabsize
= self
.tabwidth
1501 _tokenize
.tokenize(self
.readline
, self
.tokeneater
)
1502 except _tokenize
.TokenError
:
1503 # since we cut off the tokenizer early, we can trigger
1507 _tokenize
.tabsize
= save_tabsize
1508 return self
.blkopenline
, self
.indentedline
1510 ### end autoindent code ###
1513 # Helper to extract the underscore from a string, e.g.
1514 # prepstr("Co_py") returns (2, "Copy").
1523 'bracketright': ']',
1527 def get_accelerator(keydefs
, eventname
):
1528 keylist
= keydefs
.get(eventname
)
1532 s
= re
.sub(r
"-[a-z]\b", lambda m
: m
.group().upper(), s
)
1533 s
= re
.sub(r
"\b\w+\b", lambda m
: keynames
.get(m
.group(), m
.group()), s
)
1534 s
= re
.sub("Key-", "", s
)
1535 s
= re
.sub("Cancel","Ctrl-Break",s
) # dscherer@cmu.edu
1536 s
= re
.sub("Control-", "Ctrl-", s
)
1537 s
= re
.sub("-", "+", s
)
1538 s
= re
.sub("><", " ", s
)
1539 s
= re
.sub("<", "", s
)
1540 s
= re
.sub(">", "", s
)
1544 def fixwordbreaks(root
):
1545 # Make sure that Tk's double-click and next/previous word
1546 # operations use our definition of a word (i.e. an identifier)
1548 tk
.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1549 tk
.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1550 tk
.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1558 filename
= sys
.argv
[1]
1561 edit
= EditorWindow(root
=root
, filename
=filename
)
1562 edit
.set_close_hook(root
.quit
)
1563 edit
.text
.bind("<<close-all-windows>>", edit
.close_event
)
1567 if __name__
== '__main__':