Initial commit of OpenSPARC T2 design and verification files.
[OpenSPARC-T2-DV] / tools / src / nas,5.n2.os.2 / lib / python / lib / python2.4 / idlelib / EditorWindow.py
CommitLineData
86530b38
AT
1import sys
2import os
3import re
4import imp
5from itertools import count
6from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
9
10import webbrowser
11import idlever
12import WindowList
13import SearchDialog
14import GrepDialog
15import ReplaceDialog
16import PyParse
17from configHandler import idleConf
18import aboutDialog, textView, configDialog
19
20# The default tab setting for a Text widget, in average-width characters.
21TK_TABWIDTH_DEFAULT = 8
22
23def _find_module(fullname, path=None):
24 """Version of imp.find_module() that handles hierarchical module names"""
25
26 file = None
27 for tgt in fullname.split('.'):
28 if file is not None:
29 file.close() # close intermediate files
30 (file, filename, descr) = imp.find_module(tgt, path)
31 if descr[2] == imp.PY_SOURCE:
32 break # find but not load the source file
33 module = imp.load_module(tgt, file, filename, descr)
34 try:
35 path = module.__path__
36 except AttributeError:
37 raise ImportError, 'No source for module ' + module.__name__
38 return file, filename, descr
39
40class EditorWindow:
41 from Percolator import Percolator
42 from ColorDelegator import ColorDelegator
43 from UndoDelegator import UndoDelegator
44 from IOBinding import IOBinding
45 import Bindings
46 from Tkinter import Toplevel
47 from MultiStatusBar import MultiStatusBar
48
49 help_url = None
50
51 def __init__(self, flist=None, filename=None, key=None, root=None):
52 if EditorWindow.help_url is None:
53 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
54 if sys.platform.count('linux'):
55 # look for html docs in a couple of standard places
56 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
57 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
58 dochome = '/var/www/html/python/index.html'
59 else:
60 basepath = '/usr/share/doc/' # standard location
61 dochome = os.path.join(basepath, pyver,
62 'Doc', 'index.html')
63 elif sys.platform[:3] == 'win':
64 chmfile = os.path.join(sys.prefix, 'Doc',
65 'Python%d%d.chm' % sys.version_info[:2])
66 if os.path.isfile(chmfile):
67 dochome = chmfile
68 dochome = os.path.normpath(dochome)
69 if os.path.isfile(dochome):
70 EditorWindow.help_url = dochome
71 else:
72 EditorWindow.help_url = "http://www.python.org/doc/current"
73 currentTheme=idleConf.CurrentTheme()
74 self.flist = flist
75 root = root or flist.root
76 self.root = root
77 self.menubar = Menu(root)
78 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
79 if flist:
80 self.tkinter_vars = flist.vars
81 #self.top.instance_dict makes flist.inversedict avalable to
82 #configDialog.py so it can access all EditorWindow instaces
83 self.top.instance_dict=flist.inversedict
84 else:
85 self.tkinter_vars = {} # keys: Tkinter event names
86 # values: Tkinter variable instances
87 self.recent_files_path=os.path.join(idleConf.GetUserCfgDir(),
88 'recent-files.lst')
89 self.vbar = vbar = Scrollbar(top, name='vbar')
90 self.text_frame = text_frame = Frame(top)
91 self.width = idleConf.GetOption('main','EditorWindow','width')
92 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
93 foreground=idleConf.GetHighlight(currentTheme,
94 'normal',fgBg='fg'),
95 background=idleConf.GetHighlight(currentTheme,
96 'normal',fgBg='bg'),
97 highlightcolor=idleConf.GetHighlight(currentTheme,
98 'hilite',fgBg='fg'),
99 highlightbackground=idleConf.GetHighlight(currentTheme,
100 'hilite',fgBg='bg'),
101 insertbackground=idleConf.GetHighlight(currentTheme,
102 'cursor',fgBg='fg'),
103 width=self.width,
104 height=idleConf.GetOption('main','EditorWindow','height') )
105 self.top.focused_widget = self.text
106
107 self.createmenubar()
108 self.apply_bindings()
109
110 self.top.protocol("WM_DELETE_WINDOW", self.close)
111 self.top.bind("<<close-window>>", self.close_event)
112 text.bind("<<cut>>", self.cut)
113 text.bind("<<copy>>", self.copy)
114 text.bind("<<paste>>", self.paste)
115 text.bind("<<center-insert>>", self.center_insert_event)
116 text.bind("<<help>>", self.help_dialog)
117 text.bind("<<python-docs>>", self.python_docs)
118 text.bind("<<about-idle>>", self.about_dialog)
119 text.bind("<<open-config-dialog>>", self.config_dialog)
120 text.bind("<<open-module>>", self.open_module)
121 text.bind("<<do-nothing>>", lambda event: "break")
122 text.bind("<<select-all>>", self.select_all)
123 text.bind("<<remove-selection>>", self.remove_selection)
124 text.bind("<<find>>", self.find_event)
125 text.bind("<<find-again>>", self.find_again_event)
126 text.bind("<<find-in-files>>", self.find_in_files_event)
127 text.bind("<<find-selection>>", self.find_selection_event)
128 text.bind("<<replace>>", self.replace_event)
129 text.bind("<<goto-line>>", self.goto_line_event)
130 text.bind("<3>", self.right_menu_event)
131 text.bind("<<smart-backspace>>",self.smart_backspace_event)
132 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
133 text.bind("<<smart-indent>>",self.smart_indent_event)
134 text.bind("<<indent-region>>",self.indent_region_event)
135 text.bind("<<dedent-region>>",self.dedent_region_event)
136 text.bind("<<comment-region>>",self.comment_region_event)
137 text.bind("<<uncomment-region>>",self.uncomment_region_event)
138 text.bind("<<tabify-region>>",self.tabify_region_event)
139 text.bind("<<untabify-region>>",self.untabify_region_event)
140 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
141 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
142 text.bind("<Left>", self.move_at_edge_if_selection(0))
143 text.bind("<Right>", self.move_at_edge_if_selection(1))
144
145 if flist:
146 flist.inversedict[self] = key
147 if key:
148 flist.dict[key] = self
149 text.bind("<<open-new-window>>", self.new_callback)
150 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
151 text.bind("<<open-class-browser>>", self.open_class_browser)
152 text.bind("<<open-path-browser>>", self.open_path_browser)
153
154 self.set_status_bar()
155 vbar['command'] = text.yview
156 vbar.pack(side=RIGHT, fill=Y)
157 text['yscrollcommand'] = vbar.set
158 fontWeight='normal'
159 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
160 fontWeight='bold'
161 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
162 idleConf.GetOption('main','EditorWindow','font-size'),
163 fontWeight))
164 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
165 text.pack(side=TOP, fill=BOTH, expand=1)
166 text.focus_set()
167
168 self.per = per = self.Percolator(text)
169 if self.ispythonsource(filename):
170 self.color = color = self.ColorDelegator()
171 per.insertfilter(color)
172 else:
173 self.color = None
174
175 self.undo = undo = self.UndoDelegator()
176 per.insertfilter(undo)
177 text.undo_block_start = undo.undo_block_start
178 text.undo_block_stop = undo.undo_block_stop
179 undo.set_saved_change_hook(self.saved_change_hook)
180
181 # IOBinding implements file I/O and printing functionality
182 self.io = io = self.IOBinding(self)
183 io.set_filename_change_hook(self.filename_change_hook)
184
185 # Create the recent files submenu
186 self.recent_files_menu = Menu(self.menubar)
187 self.menudict['file'].insert_cascade(3, label='Recent Files',
188 underline=0,
189 menu=self.recent_files_menu)
190 self.update_recent_files_list()
191
192 if filename:
193 if os.path.exists(filename) and not os.path.isdir(filename):
194 io.loadfile(filename)
195 else:
196 io.set_filename(filename)
197 self.saved_change_hook()
198
199 self.load_extensions()
200
201 menu = self.menudict.get('windows')
202 if menu:
203 end = menu.index("end")
204 if end is None:
205 end = -1
206 if end >= 0:
207 menu.add_separator()
208 end = end + 1
209 self.wmenu_end = end
210 WindowList.register_callback(self.postwindowsmenu)
211
212 # Some abstractions so IDLE extensions are cross-IDE
213 self.askyesno = tkMessageBox.askyesno
214 self.askinteger = tkSimpleDialog.askinteger
215 self.showerror = tkMessageBox.showerror
216
217 if self.extensions.has_key('AutoIndent'):
218 self.extensions['AutoIndent'].set_indentation_params(
219 self.ispythonsource(filename))
220
221 def new_callback(self, event):
222 dirname, basename = self.io.defaultfilename()
223 self.flist.new(dirname)
224 return "break"
225
226 def set_status_bar(self):
227 self.status_bar = self.MultiStatusBar(self.top)
228 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
229 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
230 self.status_bar.pack(side=BOTTOM, fill=X)
231 self.text.bind('<KeyRelease>', self.set_line_and_column)
232 self.text.bind('<ButtonRelease>', self.set_line_and_column)
233 self.text.after_idle(self.set_line_and_column)
234
235 def set_line_and_column(self, event=None):
236 line, column = self.text.index(INSERT).split('.')
237 self.status_bar.set_label('column', 'Col: %s' % column)
238 self.status_bar.set_label('line', 'Ln: %s' % line)
239
240 menu_specs = [
241 ("file", "_File"),
242 ("edit", "_Edit"),
243 ("format", "F_ormat"),
244 ("run", "_Run"),
245 ("options", "_Options"),
246 ("windows", "_Windows"),
247 ("help", "_Help"),
248 ]
249
250 def createmenubar(self):
251 mbar = self.menubar
252 self.menudict = menudict = {}
253 for name, label in self.menu_specs:
254 underline, label = prepstr(label)
255 menudict[name] = menu = Menu(mbar, name=name)
256 mbar.add_cascade(label=label, menu=menu, underline=underline)
257 self.fill_menus()
258 self.base_helpmenu_length = self.menudict['help'].index(END)
259 self.reset_help_menu_entries()
260
261 def postwindowsmenu(self):
262 # Only called when Windows menu exists
263 menu = self.menudict['windows']
264 end = menu.index("end")
265 if end is None:
266 end = -1
267 if end > self.wmenu_end:
268 menu.delete(self.wmenu_end+1, end)
269 WindowList.add_windows_to_menu(menu)
270
271 rmenu = None
272
273 def right_menu_event(self, event):
274 self.text.tag_remove("sel", "1.0", "end")
275 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
276 if not self.rmenu:
277 self.make_rmenu()
278 rmenu = self.rmenu
279 self.event = event
280 iswin = sys.platform[:3] == 'win'
281 if iswin:
282 self.text.config(cursor="arrow")
283 rmenu.tk_popup(event.x_root, event.y_root)
284 if iswin:
285 self.text.config(cursor="ibeam")
286
287 rmenu_specs = [
288 # ("Label", "<<virtual-event>>"), ...
289 ("Close", "<<close-window>>"), # Example
290 ]
291
292 def make_rmenu(self):
293 rmenu = Menu(self.text, tearoff=0)
294 for label, eventname in self.rmenu_specs:
295 def command(text=self.text, eventname=eventname):
296 text.event_generate(eventname)
297 rmenu.add_command(label=label, command=command)
298 self.rmenu = rmenu
299
300 def about_dialog(self, event=None):
301 aboutDialog.AboutDialog(self.top,'About IDLE')
302
303 def config_dialog(self, event=None):
304 configDialog.ConfigDialog(self.top,'Settings')
305
306 def help_dialog(self, event=None):
307 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
308 textView.TextViewer(self.top,'Help',fn)
309
310 def python_docs(self, event=None):
311 if sys.platform[:3] == 'win':
312 os.startfile(self.help_url)
313 else:
314 webbrowser.open(self.help_url)
315 return "break"
316
317 def cut(self,event):
318 self.text.event_generate("<<Cut>>")
319 return "break"
320
321 def copy(self,event):
322 self.text.event_generate("<<Copy>>")
323 return "break"
324
325 def paste(self,event):
326 self.text.event_generate("<<Paste>>")
327 return "break"
328
329 def select_all(self, event=None):
330 self.text.tag_add("sel", "1.0", "end-1c")
331 self.text.mark_set("insert", "1.0")
332 self.text.see("insert")
333 return "break"
334
335 def remove_selection(self, event=None):
336 self.text.tag_remove("sel", "1.0", "end")
337 self.text.see("insert")
338
339 def move_at_edge_if_selection(self, edge_index):
340 """Cursor move begins at start or end of selection
341
342 When a left/right cursor key is pressed create and return to Tkinter a
343 function which causes a cursor move from the associated edge of the
344 selection.
345
346 """
347 self_text_index = self.text.index
348 self_text_mark_set = self.text.mark_set
349 edges_table = ("sel.first+1c", "sel.last-1c")
350 def move_at_edge(event):
351 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
352 try:
353 self_text_index("sel.first")
354 self_text_mark_set("insert", edges_table[edge_index])
355 except TclError:
356 pass
357 return move_at_edge
358
359 def find_event(self, event):
360 SearchDialog.find(self.text)
361 return "break"
362
363 def find_again_event(self, event):
364 SearchDialog.find_again(self.text)
365 return "break"
366
367 def find_selection_event(self, event):
368 SearchDialog.find_selection(self.text)
369 return "break"
370
371 def find_in_files_event(self, event):
372 GrepDialog.grep(self.text, self.io, self.flist)
373 return "break"
374
375 def replace_event(self, event):
376 ReplaceDialog.replace(self.text)
377 return "break"
378
379 def goto_line_event(self, event):
380 text = self.text
381 lineno = tkSimpleDialog.askinteger("Goto",
382 "Go to line number:",parent=text)
383 if lineno is None:
384 return "break"
385 if lineno <= 0:
386 text.bell()
387 return "break"
388 text.mark_set("insert", "%d.0" % lineno)
389 text.see("insert")
390
391 def open_module(self, event=None):
392 # XXX Shouldn't this be in IOBinding or in FileList?
393 try:
394 name = self.text.get("sel.first", "sel.last")
395 except TclError:
396 name = ""
397 else:
398 name = name.strip()
399 name = tkSimpleDialog.askstring("Module",
400 "Enter the name of a Python module\n"
401 "to search on sys.path and open:",
402 parent=self.text, initialvalue=name)
403 if name:
404 name = name.strip()
405 if not name:
406 return
407 # XXX Ought to insert current file's directory in front of path
408 try:
409 (f, file, (suffix, mode, type)) = _find_module(name)
410 except (NameError, ImportError), msg:
411 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
412 return
413 if type != imp.PY_SOURCE:
414 tkMessageBox.showerror("Unsupported type",
415 "%s is not a source module" % name, parent=self.text)
416 return
417 if f:
418 f.close()
419 if self.flist:
420 self.flist.open(file)
421 else:
422 self.io.loadfile(file)
423
424 def open_class_browser(self, event=None):
425 filename = self.io.filename
426 if not filename:
427 tkMessageBox.showerror(
428 "No filename",
429 "This buffer has no associated filename",
430 master=self.text)
431 self.text.focus_set()
432 return None
433 head, tail = os.path.split(filename)
434 base, ext = os.path.splitext(tail)
435 import ClassBrowser
436 ClassBrowser.ClassBrowser(self.flist, base, [head])
437
438 def open_path_browser(self, event=None):
439 import PathBrowser
440 PathBrowser.PathBrowser(self.flist)
441
442 def gotoline(self, lineno):
443 if lineno is not None and lineno > 0:
444 self.text.mark_set("insert", "%d.0" % lineno)
445 self.text.tag_remove("sel", "1.0", "end")
446 self.text.tag_add("sel", "insert", "insert +1l")
447 self.center()
448
449 def ispythonsource(self, filename):
450 if not filename:
451 return True
452 base, ext = os.path.splitext(os.path.basename(filename))
453 if os.path.normcase(ext) in (".py", ".pyw"):
454 return True
455 try:
456 f = open(filename)
457 line = f.readline()
458 f.close()
459 except IOError:
460 return False
461 return line.startswith('#!') and line.find('python') >= 0
462
463 def close_hook(self):
464 if self.flist:
465 self.flist.close_edit(self)
466
467 def set_close_hook(self, close_hook):
468 self.close_hook = close_hook
469
470 def filename_change_hook(self):
471 if self.flist:
472 self.flist.filename_changed_edit(self)
473 self.saved_change_hook()
474 self.top.update_windowlist_registry(self)
475 if self.ispythonsource(self.io.filename):
476 self.addcolorizer()
477 else:
478 self.rmcolorizer()
479
480 def addcolorizer(self):
481 if self.color:
482 return
483 self.per.removefilter(self.undo)
484 self.color = self.ColorDelegator()
485 self.per.insertfilter(self.color)
486 self.per.insertfilter(self.undo)
487
488 def rmcolorizer(self):
489 if not self.color:
490 return
491 self.per.removefilter(self.undo)
492 self.per.removefilter(self.color)
493 self.color = None
494 self.per.insertfilter(self.undo)
495
496 def ResetColorizer(self):
497 "Update the colour theme if it is changed"
498 # Called from configDialog.py
499 if self.color:
500 self.color = self.ColorDelegator()
501 self.per.insertfilter(self.color)
502 theme = idleConf.GetOption('main','Theme','name')
503 self.text.config(idleConf.GetHighlight(theme, "normal"))
504
505 def ResetFont(self):
506 "Update the text widgets' font if it is changed"
507 # Called from configDialog.py
508 fontWeight='normal'
509 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
510 fontWeight='bold'
511 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
512 idleConf.GetOption('main','EditorWindow','font-size'),
513 fontWeight))
514
515 def ResetKeybindings(self):
516 "Update the keybindings if they are changed"
517 # Called from configDialog.py
518 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
519 keydefs = self.Bindings.default_keydefs
520 for event, keylist in keydefs.items():
521 self.text.event_delete(event)
522 self.apply_bindings()
523 #update menu accelerators
524 menuEventDict={}
525 for menu in self.Bindings.menudefs:
526 menuEventDict[menu[0]]={}
527 for item in menu[1]:
528 if item:
529 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
530 for menubarItem in self.menudict.keys():
531 menu=self.menudict[menubarItem]
532 end=menu.index(END)+1
533 for index in range(0,end):
534 if menu.type(index)=='command':
535 accel=menu.entrycget(index,'accelerator')
536 if accel:
537 itemName=menu.entrycget(index,'label')
538 event=''
539 if menuEventDict.has_key(menubarItem):
540 if menuEventDict[menubarItem].has_key(itemName):
541 event=menuEventDict[menubarItem][itemName]
542 if event:
543 accel=get_accelerator(keydefs, event)
544 menu.entryconfig(index,accelerator=accel)
545
546 def reset_help_menu_entries(self):
547 "Update the additional help entries on the Help menu"
548 help_list = idleConf.GetAllExtraHelpSourcesList()
549 helpmenu = self.menudict['help']
550 # first delete the extra help entries, if any
551 helpmenu_length = helpmenu.index(END)
552 if helpmenu_length > self.base_helpmenu_length:
553 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
554 # then rebuild them
555 if help_list:
556 helpmenu.add_separator()
557 for entry in help_list:
558 cmd = self.__extra_help_callback(entry[1])
559 helpmenu.add_command(label=entry[0], command=cmd)
560 # and update the menu dictionary
561 self.menudict['help'] = helpmenu
562
563 def __extra_help_callback(self, helpfile):
564 "Create a callback with the helpfile value frozen at definition time"
565 def display_extra_help(helpfile=helpfile):
566 if not (helpfile.startswith('www') or helpfile.startswith('http')):
567 url = os.path.normpath(helpfile)
568 if sys.platform[:3] == 'win':
569 os.startfile(helpfile)
570 else:
571 webbrowser.open(helpfile)
572 return display_extra_help
573
574 def update_recent_files_list(self, new_file=None):
575 "Load and update the recent files list and menus"
576 rf_list = []
577 if os.path.exists(self.recent_files_path):
578 rf_list_file = open(self.recent_files_path,'r')
579 try:
580 rf_list = rf_list_file.readlines()
581 finally:
582 rf_list_file.close()
583 if new_file:
584 new_file = os.path.abspath(new_file) + '\n'
585 if new_file in rf_list:
586 rf_list.remove(new_file) # move to top
587 rf_list.insert(0, new_file)
588 # clean and save the recent files list
589 bad_paths = []
590 for path in rf_list:
591 if '\0' in path or not os.path.exists(path[0:-1]):
592 bad_paths.append(path)
593 rf_list = [path for path in rf_list if path not in bad_paths]
594 ulchars = "1234567890ABCDEFGHIJK"
595 rf_list = rf_list[0:len(ulchars)]
596 rf_file = open(self.recent_files_path, 'w')
597 try:
598 rf_file.writelines(rf_list)
599 finally:
600 rf_file.close()
601 # for each edit window instance, construct the recent files menu
602 for instance in self.top.instance_dict.keys():
603 menu = instance.recent_files_menu
604 menu.delete(1, END) # clear, and rebuild:
605 for i, file in zip(count(), rf_list):
606 file_name = file[0:-1] # zap \n
607 callback = instance.__recent_file_callback(file_name)
608 menu.add_command(label=ulchars[i] + " " + file_name,
609 command=callback,
610 underline=0)
611
612 def __recent_file_callback(self, file_name):
613 def open_recent_file(fn_closure=file_name):
614 self.io.open(editFile=fn_closure)
615 return open_recent_file
616
617 def saved_change_hook(self):
618 short = self.short_title()
619 long = self.long_title()
620 if short and long:
621 title = short + " - " + long
622 elif short:
623 title = short
624 elif long:
625 title = long
626 else:
627 title = "Untitled"
628 icon = short or long or title
629 if not self.get_saved():
630 title = "*%s*" % title
631 icon = "*%s" % icon
632 self.top.wm_title(title)
633 self.top.wm_iconname(icon)
634
635 def get_saved(self):
636 return self.undo.get_saved()
637
638 def set_saved(self, flag):
639 self.undo.set_saved(flag)
640
641 def reset_undo(self):
642 self.undo.reset_undo()
643
644 def short_title(self):
645 filename = self.io.filename
646 if filename:
647 filename = os.path.basename(filename)
648 return filename
649
650 def long_title(self):
651 return self.io.filename or ""
652
653 def center_insert_event(self, event):
654 self.center()
655
656 def center(self, mark="insert"):
657 text = self.text
658 top, bot = self.getwindowlines()
659 lineno = self.getlineno(mark)
660 height = bot - top
661 newtop = max(1, lineno - height//2)
662 text.yview(float(newtop))
663
664 def getwindowlines(self):
665 text = self.text
666 top = self.getlineno("@0,0")
667 bot = self.getlineno("@0,65535")
668 if top == bot and text.winfo_height() == 1:
669 # Geometry manager hasn't run yet
670 height = int(text['height'])
671 bot = top + height - 1
672 return top, bot
673
674 def getlineno(self, mark="insert"):
675 text = self.text
676 return int(float(text.index(mark)))
677
678 def get_geometry(self):
679 "Return (width, height, x, y)"
680 geom = self.top.wm_geometry()
681 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
682 tuple = (map(int, m.groups()))
683 return tuple
684
685 def close_event(self, event):
686 self.close()
687
688 def maybesave(self):
689 if self.io:
690 if not self.get_saved():
691 if self.top.state()!='normal':
692 self.top.deiconify()
693 self.top.lower()
694 self.top.lift()
695 return self.io.maybesave()
696
697 def close(self):
698 reply = self.maybesave()
699 if reply != "cancel":
700 self._close()
701 return reply
702
703 def _close(self):
704 if self.io.filename:
705 self.update_recent_files_list(new_file=self.io.filename)
706 WindowList.unregister_callback(self.postwindowsmenu)
707 if self.close_hook:
708 self.close_hook()
709 self.flist = None
710 colorizing = 0
711 self.unload_extensions()
712 self.io.close(); self.io = None
713 self.undo = None # XXX
714 if self.color:
715 colorizing = self.color.colorizing
716 doh = colorizing and self.top
717 self.color.close(doh) # Cancel colorization
718 self.text = None
719 self.tkinter_vars = None
720 self.per.close(); self.per = None
721 if not colorizing:
722 self.top.destroy()
723
724 def load_extensions(self):
725 self.extensions = {}
726 self.load_standard_extensions()
727
728 def unload_extensions(self):
729 for ins in self.extensions.values():
730 if hasattr(ins, "close"):
731 ins.close()
732 self.extensions = {}
733
734 def load_standard_extensions(self):
735 for name in self.get_standard_extension_names():
736 try:
737 self.load_extension(name)
738 except:
739 print "Failed to load extension", repr(name)
740 import traceback
741 traceback.print_exc()
742
743 def get_standard_extension_names(self):
744 return idleConf.GetExtensions(editor_only=True)
745
746 def load_extension(self, name):
747 try:
748 mod = __import__(name, globals(), locals(), [])
749 except ImportError:
750 print "\nFailed to import extension: ", name
751 return None
752 cls = getattr(mod, name)
753 keydefs = idleConf.GetExtensionBindings(name)
754 if hasattr(cls, "menudefs"):
755 self.fill_menus(cls.menudefs, keydefs)
756 ins = cls(self)
757 self.extensions[name] = ins
758 if keydefs:
759 self.apply_bindings(keydefs)
760 for vevent in keydefs.keys():
761 methodname = vevent.replace("-", "_")
762 while methodname[:1] == '<':
763 methodname = methodname[1:]
764 while methodname[-1:] == '>':
765 methodname = methodname[:-1]
766 methodname = methodname + "_event"
767 if hasattr(ins, methodname):
768 self.text.bind(vevent, getattr(ins, methodname))
769 return ins
770
771 def apply_bindings(self, keydefs=None):
772 if keydefs is None:
773 keydefs = self.Bindings.default_keydefs
774 text = self.text
775 text.keydefs = keydefs
776 for event, keylist in keydefs.items():
777 if keylist:
778 text.event_add(event, *keylist)
779
780 def fill_menus(self, menudefs=None, keydefs=None):
781 """Add appropriate entries to the menus and submenus
782
783 Menus that are absent or None in self.menudict are ignored.
784 """
785 if menudefs is None:
786 menudefs = self.Bindings.menudefs
787 if keydefs is None:
788 keydefs = self.Bindings.default_keydefs
789 menudict = self.menudict
790 text = self.text
791 for mname, entrylist in menudefs:
792 menu = menudict.get(mname)
793 if not menu:
794 continue
795 for entry in entrylist:
796 if not entry:
797 menu.add_separator()
798 else:
799 label, eventname = entry
800 checkbutton = (label[:1] == '!')
801 if checkbutton:
802 label = label[1:]
803 underline, label = prepstr(label)
804 accelerator = get_accelerator(keydefs, eventname)
805 def command(text=text, eventname=eventname):
806 text.event_generate(eventname)
807 if checkbutton:
808 var = self.get_var_obj(eventname, BooleanVar)
809 menu.add_checkbutton(label=label, underline=underline,
810 command=command, accelerator=accelerator,
811 variable=var)
812 else:
813 menu.add_command(label=label, underline=underline,
814 command=command,
815 accelerator=accelerator)
816
817 def getvar(self, name):
818 var = self.get_var_obj(name)
819 if var:
820 value = var.get()
821 return value
822 else:
823 raise NameError, name
824
825 def setvar(self, name, value, vartype=None):
826 var = self.get_var_obj(name, vartype)
827 if var:
828 var.set(value)
829 else:
830 raise NameError, name
831
832 def get_var_obj(self, name, vartype=None):
833 var = self.tkinter_vars.get(name)
834 if not var and vartype:
835 # create a Tkinter variable object with self.text as master:
836 self.tkinter_vars[name] = var = vartype(self.text)
837 return var
838
839 # Tk implementations of "virtual text methods" -- each platform
840 # reusing IDLE's support code needs to define these for its GUI's
841 # flavor of widget.
842
843 # Is character at text_index in a Python string? Return 0 for
844 # "guaranteed no", true for anything else. This info is expensive
845 # to compute ab initio, but is probably already known by the
846 # platform's colorizer.
847
848 def is_char_in_string(self, text_index):
849 if self.color:
850 # Return true iff colorizer hasn't (re)gotten this far
851 # yet, or the character is tagged as being in a string
852 return self.text.tag_prevrange("TODO", text_index) or \
853 "STRING" in self.text.tag_names(text_index)
854 else:
855 # The colorizer is missing: assume the worst
856 return 1
857
858 # If a selection is defined in the text widget, return (start,
859 # end) as Tkinter text indices, otherwise return (None, None)
860 def get_selection_indices(self):
861 try:
862 first = self.text.index("sel.first")
863 last = self.text.index("sel.last")
864 return first, last
865 except TclError:
866 return None, None
867
868 # Return the text widget's current view of what a tab stop means
869 # (equivalent width in spaces).
870
871 def get_tabwidth(self):
872 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
873 return int(current)
874
875 # Set the text widget's current view of what a tab stop means.
876
877 def set_tabwidth(self, newtabwidth):
878 text = self.text
879 if self.get_tabwidth() != newtabwidth:
880 pixels = text.tk.call("font", "measure", text["font"],
881 "-displayof", text.master,
882 "n" * newtabwidth)
883 text.configure(tabs=pixels)
884
885### begin autoindent code ###
886
887 # usetabs true -> literal tab characters are used by indent and
888 # dedent cmds, possibly mixed with spaces if
889 # indentwidth is not a multiple of tabwidth
890 # false -> tab characters are converted to spaces by indent
891 # and dedent cmds, and ditto TAB keystrokes
892 # indentwidth is the number of characters per logical indent level.
893 # tabwidth is the display width of a literal tab character.
894 # CAUTION: telling Tk to use anything other than its default
895 # tab setting causes it to use an entirely different tabbing algorithm,
896 # treating tab stops as fixed distances from the left margin.
897 # Nobody expects this, so for now tabwidth should never be changed.
898 usetabs = 0
899 indentwidth = 4
900 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
901
902 # If context_use_ps1 is true, parsing searches back for a ps1 line;
903 # else searches for a popular (if, def, ...) Python stmt.
904 context_use_ps1 = 0
905
906 # When searching backwards for a reliable place to begin parsing,
907 # first start num_context_lines[0] lines back, then
908 # num_context_lines[1] lines back if that didn't work, and so on.
909 # The last value should be huge (larger than the # of lines in a
910 # conceivable file).
911 # Making the initial values larger slows things down more often.
912 num_context_lines = 50, 500, 5000000
913
914 def config(self, **options):
915 for key, value in options.items():
916 if key == 'usetabs':
917 self.usetabs = value
918 elif key == 'indentwidth':
919 self.indentwidth = value
920 elif key == 'tabwidth':
921 self.tabwidth = value
922 elif key == 'context_use_ps1':
923 self.context_use_ps1 = value
924 else:
925 raise KeyError, "bad option name: %r" % (key,)
926
927 # If ispythonsource and guess are true, guess a good value for
928 # indentwidth based on file content (if possible), and if
929 # indentwidth != tabwidth set usetabs false.
930 # In any case, adjust the Text widget's view of what a tab
931 # character means.
932
933 def set_indentation_params(self, ispythonsource, guess=1):
934 if guess and ispythonsource:
935 i = self.guess_indent()
936 if 2 <= i <= 8:
937 self.indentwidth = i
938 if self.indentwidth != self.tabwidth:
939 self.usetabs = 0
940
941 self.set_tabwidth(self.tabwidth)
942
943 def smart_backspace_event(self, event):
944 text = self.text
945 first, last = self.get_selection_indices()
946 if first and last:
947 text.delete(first, last)
948 text.mark_set("insert", first)
949 return "break"
950 # Delete whitespace left, until hitting a real char or closest
951 # preceding virtual tab stop.
952 chars = text.get("insert linestart", "insert")
953 if chars == '':
954 if text.compare("insert", ">", "1.0"):
955 # easy: delete preceding newline
956 text.delete("insert-1c")
957 else:
958 text.bell() # at start of buffer
959 return "break"
960 if chars[-1] not in " \t":
961 # easy: delete preceding real char
962 text.delete("insert-1c")
963 return "break"
964 # Ick. It may require *inserting* spaces if we back up over a
965 # tab character! This is written to be clear, not fast.
966 tabwidth = self.tabwidth
967 have = len(chars.expandtabs(tabwidth))
968 assert have > 0
969 want = ((have - 1) // self.indentwidth) * self.indentwidth
970 # Debug prompt is multilined....
971 last_line_of_prompt = sys.ps1.split('\n')[-1]
972 ncharsdeleted = 0
973 while 1:
974 if chars == last_line_of_prompt:
975 break
976 chars = chars[:-1]
977 ncharsdeleted = ncharsdeleted + 1
978 have = len(chars.expandtabs(tabwidth))
979 if have <= want or chars[-1] not in " \t":
980 break
981 text.undo_block_start()
982 text.delete("insert-%dc" % ncharsdeleted, "insert")
983 if have < want:
984 text.insert("insert", ' ' * (want - have))
985 text.undo_block_stop()
986 return "break"
987
988 def smart_indent_event(self, event):
989 # if intraline selection:
990 # delete it
991 # elif multiline selection:
992 # do indent-region & return
993 # indent one level
994 text = self.text
995 first, last = self.get_selection_indices()
996 text.undo_block_start()
997 try:
998 if first and last:
999 if index2line(first) != index2line(last):
1000 return self.indent_region_event(event)
1001 text.delete(first, last)
1002 text.mark_set("insert", first)
1003 prefix = text.get("insert linestart", "insert")
1004 raw, effective = classifyws(prefix, self.tabwidth)
1005 if raw == len(prefix):
1006 # only whitespace to the left
1007 self.reindent_to(effective + self.indentwidth)
1008 else:
1009 if self.usetabs:
1010 pad = '\t'
1011 else:
1012 effective = len(prefix.expandtabs(self.tabwidth))
1013 n = self.indentwidth
1014 pad = ' ' * (n - effective % n)
1015 text.insert("insert", pad)
1016 text.see("insert")
1017 return "break"
1018 finally:
1019 text.undo_block_stop()
1020
1021 def newline_and_indent_event(self, event):
1022 text = self.text
1023 first, last = self.get_selection_indices()
1024 text.undo_block_start()
1025 try:
1026 if first and last:
1027 text.delete(first, last)
1028 text.mark_set("insert", first)
1029 line = text.get("insert linestart", "insert")
1030 i, n = 0, len(line)
1031 while i < n and line[i] in " \t":
1032 i = i+1
1033 if i == n:
1034 # the cursor is in or at leading indentation in a continuation
1035 # line; just inject an empty line at the start
1036 text.insert("insert linestart", '\n')
1037 return "break"
1038 indent = line[:i]
1039 # strip whitespace before insert point unless it's in the prompt
1040 i = 0
1041 last_line_of_prompt = sys.ps1.split('\n')[-1]
1042 while line and line[-1] in " \t" and line != last_line_of_prompt:
1043 line = line[:-1]
1044 i = i+1
1045 if i:
1046 text.delete("insert - %d chars" % i, "insert")
1047 # strip whitespace after insert point
1048 while text.get("insert") in " \t":
1049 text.delete("insert")
1050 # start new line
1051 text.insert("insert", '\n')
1052
1053 # adjust indentation for continuations and block
1054 # open/close first need to find the last stmt
1055 lno = index2line(text.index('insert'))
1056 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1057 for context in self.num_context_lines:
1058 startat = max(lno - context, 1)
1059 startatindex = repr(startat) + ".0"
1060 rawtext = text.get(startatindex, "insert")
1061 y.set_str(rawtext)
1062 bod = y.find_good_parse_start(
1063 self.context_use_ps1,
1064 self._build_char_in_string_func(startatindex))
1065 if bod is not None or startat == 1:
1066 break
1067 y.set_lo(bod or 0)
1068 c = y.get_continuation_type()
1069 if c != PyParse.C_NONE:
1070 # The current stmt hasn't ended yet.
1071 if c == PyParse.C_STRING:
1072 # inside a string; just mimic the current indent
1073 text.insert("insert", indent)
1074 elif c == PyParse.C_BRACKET:
1075 # line up with the first (if any) element of the
1076 # last open bracket structure; else indent one
1077 # level beyond the indent of the line with the
1078 # last open bracket
1079 self.reindent_to(y.compute_bracket_indent())
1080 elif c == PyParse.C_BACKSLASH:
1081 # if more than one line in this stmt already, just
1082 # mimic the current indent; else if initial line
1083 # has a start on an assignment stmt, indent to
1084 # beyond leftmost =; else to beyond first chunk of
1085 # non-whitespace on initial line
1086 if y.get_num_lines_in_stmt() > 1:
1087 text.insert("insert", indent)
1088 else:
1089 self.reindent_to(y.compute_backslash_indent())
1090 else:
1091 assert 0, "bogus continuation type %r" % (c,)
1092 return "break"
1093
1094 # This line starts a brand new stmt; indent relative to
1095 # indentation of initial line of closest preceding
1096 # interesting stmt.
1097 indent = y.get_base_indent_string()
1098 text.insert("insert", indent)
1099 if y.is_block_opener():
1100 self.smart_indent_event(event)
1101 elif indent and y.is_block_closer():
1102 self.smart_backspace_event(event)
1103 return "break"
1104 finally:
1105 text.see("insert")
1106 text.undo_block_stop()
1107
1108 # Our editwin provides a is_char_in_string function that works
1109 # with a Tk text index, but PyParse only knows about offsets into
1110 # a string. This builds a function for PyParse that accepts an
1111 # offset.
1112
1113 def _build_char_in_string_func(self, startindex):
1114 def inner(offset, _startindex=startindex,
1115 _icis=self.is_char_in_string):
1116 return _icis(_startindex + "+%dc" % offset)
1117 return inner
1118
1119 def indent_region_event(self, event):
1120 head, tail, chars, lines = self.get_region()
1121 for pos in range(len(lines)):
1122 line = lines[pos]
1123 if line:
1124 raw, effective = classifyws(line, self.tabwidth)
1125 effective = effective + self.indentwidth
1126 lines[pos] = self._make_blanks(effective) + line[raw:]
1127 self.set_region(head, tail, chars, lines)
1128 return "break"
1129
1130 def dedent_region_event(self, event):
1131 head, tail, chars, lines = self.get_region()
1132 for pos in range(len(lines)):
1133 line = lines[pos]
1134 if line:
1135 raw, effective = classifyws(line, self.tabwidth)
1136 effective = max(effective - self.indentwidth, 0)
1137 lines[pos] = self._make_blanks(effective) + line[raw:]
1138 self.set_region(head, tail, chars, lines)
1139 return "break"
1140
1141 def comment_region_event(self, event):
1142 head, tail, chars, lines = self.get_region()
1143 for pos in range(len(lines) - 1):
1144 line = lines[pos]
1145 lines[pos] = '##' + line
1146 self.set_region(head, tail, chars, lines)
1147
1148 def uncomment_region_event(self, event):
1149 head, tail, chars, lines = self.get_region()
1150 for pos in range(len(lines)):
1151 line = lines[pos]
1152 if not line:
1153 continue
1154 if line[:2] == '##':
1155 line = line[2:]
1156 elif line[:1] == '#':
1157 line = line[1:]
1158 lines[pos] = line
1159 self.set_region(head, tail, chars, lines)
1160
1161 def tabify_region_event(self, event):
1162 head, tail, chars, lines = self.get_region()
1163 tabwidth = self._asktabwidth()
1164 for pos in range(len(lines)):
1165 line = lines[pos]
1166 if line:
1167 raw, effective = classifyws(line, tabwidth)
1168 ntabs, nspaces = divmod(effective, tabwidth)
1169 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1170 self.set_region(head, tail, chars, lines)
1171
1172 def untabify_region_event(self, event):
1173 head, tail, chars, lines = self.get_region()
1174 tabwidth = self._asktabwidth()
1175 for pos in range(len(lines)):
1176 lines[pos] = lines[pos].expandtabs(tabwidth)
1177 self.set_region(head, tail, chars, lines)
1178
1179 def toggle_tabs_event(self, event):
1180 if self.askyesno(
1181 "Toggle tabs",
1182 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1183 parent=self.text):
1184 self.usetabs = not self.usetabs
1185 return "break"
1186
1187 # XXX this isn't bound to anything -- see class tabwidth comments
1188 def change_tabwidth_event(self, event):
1189 new = self._asktabwidth()
1190 if new != self.tabwidth:
1191 self.tabwidth = new
1192 self.set_indentation_params(0, guess=0)
1193 return "break"
1194
1195 def change_indentwidth_event(self, event):
1196 new = self.askinteger(
1197 "Indent width",
1198 "New indent width (2-16)",
1199 parent=self.text,
1200 initialvalue=self.indentwidth,
1201 minvalue=2,
1202 maxvalue=16)
1203 if new and new != self.indentwidth:
1204 self.indentwidth = new
1205 return "break"
1206
1207 def get_region(self):
1208 text = self.text
1209 first, last = self.get_selection_indices()
1210 if first and last:
1211 head = text.index(first + " linestart")
1212 tail = text.index(last + "-1c lineend +1c")
1213 else:
1214 head = text.index("insert linestart")
1215 tail = text.index("insert lineend +1c")
1216 chars = text.get(head, tail)
1217 lines = chars.split("\n")
1218 return head, tail, chars, lines
1219
1220 def set_region(self, head, tail, chars, lines):
1221 text = self.text
1222 newchars = "\n".join(lines)
1223 if newchars == chars:
1224 text.bell()
1225 return
1226 text.tag_remove("sel", "1.0", "end")
1227 text.mark_set("insert", head)
1228 text.undo_block_start()
1229 text.delete(head, tail)
1230 text.insert(head, newchars)
1231 text.undo_block_stop()
1232 text.tag_add("sel", head, "insert")
1233
1234 # Make string that displays as n leading blanks.
1235
1236 def _make_blanks(self, n):
1237 if self.usetabs:
1238 ntabs, nspaces = divmod(n, self.tabwidth)
1239 return '\t' * ntabs + ' ' * nspaces
1240 else:
1241 return ' ' * n
1242
1243 # Delete from beginning of line to insert point, then reinsert
1244 # column logical (meaning use tabs if appropriate) spaces.
1245
1246 def reindent_to(self, column):
1247 text = self.text
1248 text.undo_block_start()
1249 if text.compare("insert linestart", "!=", "insert"):
1250 text.delete("insert linestart", "insert")
1251 if column:
1252 text.insert("insert", self._make_blanks(column))
1253 text.undo_block_stop()
1254
1255 def _asktabwidth(self):
1256 return self.askinteger(
1257 "Tab width",
1258 "Spaces per tab? (2-16)",
1259 parent=self.text,
1260 initialvalue=self.indentwidth,
1261 minvalue=2,
1262 maxvalue=16) or self.tabwidth
1263
1264 # Guess indentwidth from text content.
1265 # Return guessed indentwidth. This should not be believed unless
1266 # it's in a reasonable range (e.g., it will be 0 if no indented
1267 # blocks are found).
1268
1269 def guess_indent(self):
1270 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1271 if opener and indented:
1272 raw, indentsmall = classifyws(opener, self.tabwidth)
1273 raw, indentlarge = classifyws(indented, self.tabwidth)
1274 else:
1275 indentsmall = indentlarge = 0
1276 return indentlarge - indentsmall
1277
1278# "line.col" -> line, as an int
1279def index2line(index):
1280 return int(float(index))
1281
1282# Look at the leading whitespace in s.
1283# Return pair (# of leading ws characters,
1284# effective # of leading blanks after expanding
1285# tabs to width tabwidth)
1286
1287def classifyws(s, tabwidth):
1288 raw = effective = 0
1289 for ch in s:
1290 if ch == ' ':
1291 raw = raw + 1
1292 effective = effective + 1
1293 elif ch == '\t':
1294 raw = raw + 1
1295 effective = (effective // tabwidth + 1) * tabwidth
1296 else:
1297 break
1298 return raw, effective
1299
1300import tokenize
1301_tokenize = tokenize
1302del tokenize
1303
1304class IndentSearcher:
1305
1306 # .run() chews over the Text widget, looking for a block opener
1307 # and the stmt following it. Returns a pair,
1308 # (line containing block opener, line containing stmt)
1309 # Either or both may be None.
1310
1311 def __init__(self, text, tabwidth):
1312 self.text = text
1313 self.tabwidth = tabwidth
1314 self.i = self.finished = 0
1315 self.blkopenline = self.indentedline = None
1316
1317 def readline(self):
1318 if self.finished:
1319 return ""
1320 i = self.i = self.i + 1
1321 mark = repr(i) + ".0"
1322 if self.text.compare(mark, ">=", "end"):
1323 return ""
1324 return self.text.get(mark, mark + " lineend+1c")
1325
1326 def tokeneater(self, type, token, start, end, line,
1327 INDENT=_tokenize.INDENT,
1328 NAME=_tokenize.NAME,
1329 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1330 if self.finished:
1331 pass
1332 elif type == NAME and token in OPENERS:
1333 self.blkopenline = line
1334 elif type == INDENT and self.blkopenline:
1335 self.indentedline = line
1336 self.finished = 1
1337
1338 def run(self):
1339 save_tabsize = _tokenize.tabsize
1340 _tokenize.tabsize = self.tabwidth
1341 try:
1342 try:
1343 _tokenize.tokenize(self.readline, self.tokeneater)
1344 except _tokenize.TokenError:
1345 # since we cut off the tokenizer early, we can trigger
1346 # spurious errors
1347 pass
1348 finally:
1349 _tokenize.tabsize = save_tabsize
1350 return self.blkopenline, self.indentedline
1351
1352### end autoindent code ###
1353
1354def prepstr(s):
1355 # Helper to extract the underscore from a string, e.g.
1356 # prepstr("Co_py") returns (2, "Copy").
1357 i = s.find('_')
1358 if i >= 0:
1359 s = s[:i] + s[i+1:]
1360 return i, s
1361
1362
1363keynames = {
1364 'bracketleft': '[',
1365 'bracketright': ']',
1366 'slash': '/',
1367}
1368
1369def get_accelerator(keydefs, eventname):
1370 keylist = keydefs.get(eventname)
1371 if not keylist:
1372 return ""
1373 s = keylist[0]
1374 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
1375 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1376 s = re.sub("Key-", "", s)
1377 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1378 s = re.sub("Control-", "Ctrl-", s)
1379 s = re.sub("-", "+", s)
1380 s = re.sub("><", " ", s)
1381 s = re.sub("<", "", s)
1382 s = re.sub(">", "", s)
1383 return s
1384
1385
1386def fixwordbreaks(root):
1387 # Make sure that Tk's double-click and next/previous word
1388 # operations use our definition of a word (i.e. an identifier)
1389 tk = root.tk
1390 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1391 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1392 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1393
1394
1395def test():
1396 root = Tk()
1397 fixwordbreaks(root)
1398 root.withdraw()
1399 if sys.argv[1:]:
1400 filename = sys.argv[1]
1401 else:
1402 filename = None
1403 edit = EditorWindow(root=root, filename=filename)
1404 edit.set_close_hook(root.quit)
1405 root.mainloop()
1406 root.destroy()
1407
1408if __name__ == '__main__':
1409 test()