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