0001 import sys 0002 import os 0003 import re 0004 import imp 0005 from itertools import count 0006 from Tkinter import * 0007 import tkSimpleDialog 0008 import tkMessageBox 0009 0010 import webbrowser 0011 import idlever 0012 import WindowList 0013 import SearchDialog 0014 import GrepDialog 0015 import ReplaceDialog 0016 import PyParse 0017 from configHandler import idleConf 0018 import aboutDialog, textView, configDialog 0019 0020 # The default tab setting for a Text widget, in average-width characters. 0021 TK_TABWIDTH_DEFAULT = 8 0022 0023 def _find_module(fullname, path=None): 0024 """Version of imp.find_module() that handles hierarchical module names""" 0025 0026 file = None 0027 for tgt in fullname.split('.'): 0028 if file is not None: 0029 file.close() # close intermediate files 0030 (file, filename, descr) = imp.find_module(tgt, path) 0031 if descr[2] == imp.PY_SOURCE: 0032 break # find but not load the source file 0033 module = imp.load_module(tgt, file, filename, descr) 0034 try: 0035 path = module.__path__ 0036 except AttributeError: 0037 raise ImportError, 'No source for module ' + module.__name__ 0038 return file, filename, descr 0039 0040 class EditorWindow: 0041 from Percolator import Percolator 0042 from ColorDelegator import ColorDelegator 0043 from UndoDelegator import UndoDelegator 0044 from IOBinding import IOBinding 0045 import Bindings 0046 from Tkinter import Toplevel 0047 from MultiStatusBar import MultiStatusBar 0048 0049 help_url = None 0050 0051 def __init__(self, flist=None, filename=None, key=None, root=None): 0052 if EditorWindow.help_url is None: 0053 dochome = os.path.join(sys.prefix, 'Doc', 'index.html') 0054 if sys.platform.count('linux'): 0055 # look for html docs in a couple of standard places 0056 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3] 0057 if os.path.isdir('/var/www/html/python/'): # "python2" rpm 0058 dochome = '/var/www/html/python/index.html' 0059 else: 0060 basepath = '/usr/share/doc/' # standard location 0061 dochome = os.path.join(basepath, pyver, 0062 'Doc', 'index.html') 0063 elif sys.platform[:3] == 'win': 0064 chmfile = os.path.join(sys.prefix, 'Doc', 0065 'Python%d%d.chm' % sys.version_info[:2]) 0066 if os.path.isfile(chmfile): 0067 dochome = chmfile 0068 dochome = os.path.normpath(dochome) 0069 if os.path.isfile(dochome): 0070 EditorWindow.help_url = dochome 0071 else: 0072 EditorWindow.help_url = "http://www.python.org/doc/current" 0073 currentTheme=idleConf.CurrentTheme() 0074 self.flist = flist 0075 root = root or flist.root 0076 self.root = root 0077 self.menubar = Menu(root) 0078 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar) 0079 if flist: 0080 self.tkinter_vars = flist.vars 0081 #self.top.instance_dict makes flist.inversedict avalable to 0082 #configDialog.py so it can access all EditorWindow instaces 0083 self.top.instance_dict=flist.inversedict 0084 else: 0085 self.tkinter_vars = {} # keys: Tkinter event names 0086 # values: Tkinter variable instances 0087 self.recent_files_path=os.path.join(idleConf.GetUserCfgDir(), 0088 'recent-files.lst') 0089 self.vbar = vbar = Scrollbar(top, name='vbar') 0090 self.text_frame = text_frame = Frame(top) 0091 self.width = idleConf.GetOption('main','EditorWindow','width') 0092 self.text = text = Text(text_frame, name='text', padx=5, wrap='none', 0093 foreground=idleConf.GetHighlight(currentTheme, 0094 'normal',fgBg='fg'), 0095 background=idleConf.GetHighlight(currentTheme, 0096 'normal',fgBg='bg'), 0097 highlightcolor=idleConf.GetHighlight(currentTheme, 0098 'hilite',fgBg='fg'), 0099 highlightbackground=idleConf.GetHighlight(currentTheme, 0100 'hilite',fgBg='bg'), 0101 insertbackground=idleConf.GetHighlight(currentTheme, 0102 'cursor',fgBg='fg'), 0103 width=self.width, 0104 height=idleConf.GetOption('main','EditorWindow','height') ) 0105 self.top.focused_widget = self.text 0106 0107 self.createmenubar() 0108 self.apply_bindings() 0109 0110 self.top.protocol("WM_DELETE_WINDOW", self.close) 0111 self.top.bind("<<close-window>>", self.close_event) 0112 text.bind("<<cut>>", self.cut) 0113 text.bind("<<copy>>", self.copy) 0114 text.bind("<<paste>>", self.paste) 0115 text.bind("<<center-insert>>", self.center_insert_event) 0116 text.bind("<<help>>", self.help_dialog) 0117 text.bind("<<python-docs>>", self.python_docs) 0118 text.bind("<<about-idle>>", self.about_dialog) 0119 text.bind("<<open-config-dialog>>", self.config_dialog) 0120 text.bind("<<open-module>>", self.open_module) 0121 text.bind("<<do-nothing>>", lambda event: "break") 0122 text.bind("<<select-all>>", self.select_all) 0123 text.bind("<<remove-selection>>", self.remove_selection) 0124 text.bind("<<find>>", self.find_event) 0125 text.bind("<<find-again>>", self.find_again_event) 0126 text.bind("<<find-in-files>>", self.find_in_files_event) 0127 text.bind("<<find-selection>>", self.find_selection_event) 0128 text.bind("<<replace>>", self.replace_event) 0129 text.bind("<<goto-line>>", self.goto_line_event) 0130 text.bind("<3>", self.right_menu_event) 0131 text.bind("<<smart-backspace>>",self.smart_backspace_event) 0132 text.bind("<<newline-and-indent>>",self.newline_and_indent_event) 0133 text.bind("<<smart-indent>>",self.smart_indent_event) 0134 text.bind("<<indent-region>>",self.indent_region_event) 0135 text.bind("<<dedent-region>>",self.dedent_region_event) 0136 text.bind("<<comment-region>>",self.comment_region_event) 0137 text.bind("<<uncomment-region>>",self.uncomment_region_event) 0138 text.bind("<<tabify-region>>",self.tabify_region_event) 0139 text.bind("<<untabify-region>>",self.untabify_region_event) 0140 text.bind("<<toggle-tabs>>",self.toggle_tabs_event) 0141 text.bind("<<change-indentwidth>>",self.change_indentwidth_event) 0142 text.bind("<Left>", self.move_at_edge_if_selection(0)) 0143 text.bind("<Right>", self.move_at_edge_if_selection(1)) 0144 0145 if flist: 0146 flist.inversedict[self] = key 0147 if key: 0148 flist.dict[key] = self 0149 text.bind("<<open-new-window>>", self.new_callback) 0150 text.bind("<<close-all-windows>>", self.flist.close_all_callback) 0151 text.bind("<<open-class-browser>>", self.open_class_browser) 0152 text.bind("<<open-path-browser>>", self.open_path_browser) 0153 0154 self.set_status_bar() 0155 vbar['command'] = text.yview 0156 vbar.pack(side=RIGHT, fill=Y) 0157 text['yscrollcommand'] = vbar.set 0158 fontWeight='normal' 0159 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'): 0160 fontWeight='bold' 0161 text.config(font=(idleConf.GetOption('main','EditorWindow','font'), 0162 idleConf.GetOption('main','EditorWindow','font-size'), 0163 fontWeight)) 0164 text_frame.pack(side=LEFT, fill=BOTH, expand=1) 0165 text.pack(side=TOP, fill=BOTH, expand=1) 0166 text.focus_set() 0167 0168 self.per = per = self.Percolator(text) 0169 if self.ispythonsource(filename): 0170 self.color = color = self.ColorDelegator() 0171 per.insertfilter(color) 0172 else: 0173 self.color = None 0174 0175 self.undo = undo = self.UndoDelegator() 0176 per.insertfilter(undo) 0177 text.undo_block_start = undo.undo_block_start 0178 text.undo_block_stop = undo.undo_block_stop 0179 undo.set_saved_change_hook(self.saved_change_hook) 0180 0181 # IOBinding implements file I/O and printing functionality 0182 self.io = io = self.IOBinding(self) 0183 io.set_filename_change_hook(self.filename_change_hook) 0184 0185 # Create the recent files submenu 0186 self.recent_files_menu = Menu(self.menubar) 0187 self.menudict['file'].insert_cascade(3, label='Recent Files', 0188 underline=0, 0189 menu=self.recent_files_menu) 0190 self.update_recent_files_list() 0191 0192 if filename: 0193 if os.path.exists(filename) and not os.path.isdir(filename): 0194 io.loadfile(filename) 0195 else: 0196 io.set_filename(filename) 0197 self.saved_change_hook() 0198 0199 self.load_extensions() 0200 0201 menu = self.menudict.get('windows') 0202 if menu: 0203 end = menu.index("end") 0204 if end is None: 0205 end = -1 0206 if end >= 0: 0207 menu.add_separator() 0208 end = end + 1 0209 self.wmenu_end = end 0210 WindowList.register_callback(self.postwindowsmenu) 0211 0212 # Some abstractions so IDLE extensions are cross-IDE 0213 self.askyesno = tkMessageBox.askyesno 0214 self.askinteger = tkSimpleDialog.askinteger 0215 self.showerror = tkMessageBox.showerror 0216 0217 if self.extensions.has_key('AutoIndent'): 0218 self.extensions['AutoIndent'].set_indentation_params( 0219 self.ispythonsource(filename)) 0220 0221 def new_callback(self, event): 0222 dirname, basename = self.io.defaultfilename() 0223 self.flist.new(dirname) 0224 return "break" 0225 0226 def set_status_bar(self): 0227 self.status_bar = self.MultiStatusBar(self.top) 0228 self.status_bar.set_label('column', 'Col: ?', side=RIGHT) 0229 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT) 0230 self.status_bar.pack(side=BOTTOM, fill=X) 0231 self.text.bind('<KeyRelease>', self.set_line_and_column) 0232 self.text.bind('<ButtonRelease>', self.set_line_and_column) 0233 self.text.after_idle(self.set_line_and_column) 0234 0235 def set_line_and_column(self, event=None): 0236 line, column = self.text.index(INSERT).split('.') 0237 self.status_bar.set_label('column', 'Col: %s' % column) 0238 self.status_bar.set_label('line', 'Ln: %s' % line) 0239 0240 menu_specs = [ 0241 ("file", "_File"), 0242 ("edit", "_Edit"), 0243 ("format", "F_ormat"), 0244 ("run", "_Run"), 0245 ("options", "_Options"), 0246 ("windows", "_Windows"), 0247 ("help", "_Help"), 0248 ] 0249 0250 def createmenubar(self): 0251 mbar = self.menubar 0252 self.menudict = menudict = {} 0253 for name, label in self.menu_specs: 0254 underline, label = prepstr(label) 0255 menudict[name] = menu = Menu(mbar, name=name) 0256 mbar.add_cascade(label=label, menu=menu, underline=underline) 0257 self.fill_menus() 0258 self.base_helpmenu_length = self.menudict['help'].index(END) 0259 self.reset_help_menu_entries() 0260 0261 def postwindowsmenu(self): 0262 # Only called when Windows menu exists 0263 menu = self.menudict['windows'] 0264 end = menu.index("end") 0265 if end is None: 0266 end = -1 0267 if end > self.wmenu_end: 0268 menu.delete(self.wmenu_end+1, end) 0269 WindowList.add_windows_to_menu(menu) 0270 0271 rmenu = None 0272 0273 def right_menu_event(self, event): 0274 self.text.tag_remove("sel", "1.0", "end") 0275 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) 0276 if not self.rmenu: 0277 self.make_rmenu() 0278 rmenu = self.rmenu 0279 self.event = event 0280 iswin = sys.platform[:3] == 'win' 0281 if iswin: 0282 self.text.config(cursor="arrow") 0283 rmenu.tk_popup(event.x_root, event.y_root) 0284 if iswin: 0285 self.text.config(cursor="ibeam") 0286 0287 rmenu_specs = [ 0288 # ("Label", "<<virtual-event>>"), ... 0289 ("Close", "<<close-window>>"), # Example 0290 ] 0291 0292 def make_rmenu(self): 0293 rmenu = Menu(self.text, tearoff=0) 0294 for label, eventname in self.rmenu_specs: 0295 def command(text=self.text, eventname=eventname): 0296 text.event_generate(eventname) 0297 rmenu.add_command(label=label, command=command) 0298 self.rmenu = rmenu 0299 0300 def about_dialog(self, event=None): 0301 aboutDialog.AboutDialog(self.top,'About IDLE') 0302 0303 def config_dialog(self, event=None): 0304 configDialog.ConfigDialog(self.top,'Settings') 0305 0306 def help_dialog(self, event=None): 0307 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt') 0308 textView.TextViewer(self.top,'Help',fn) 0309 0310 def python_docs(self, event=None): 0311 if sys.platform[:3] == 'win': 0312 os.startfile(self.help_url) 0313 else: 0314 webbrowser.open(self.help_url) 0315 return "break" 0316 0317 def cut(self,event): 0318 self.text.event_generate("<<Cut>>") 0319 return "break" 0320 0321 def copy(self,event): 0322 self.text.event_generate("<<Copy>>") 0323 return "break" 0324 0325 def paste(self,event): 0326 self.text.event_generate("<<Paste>>") 0327 return "break" 0328 0329 def select_all(self, event=None): 0330 self.text.tag_add("sel", "1.0", "end-1c") 0331 self.text.mark_set("insert", "1.0") 0332 self.text.see("insert") 0333 return "break" 0334 0335 def remove_selection(self, event=None): 0336 self.text.tag_remove("sel", "1.0", "end") 0337 self.text.see("insert") 0338 0339 def move_at_edge_if_selection(self, edge_index): 0340 """Cursor move begins at start or end of selection 0341 0342 When a left/right cursor key is pressed create and return to Tkinter a 0343 function which causes a cursor move from the associated edge of the 0344 selection. 0345 0346 """ 0347 self_text_index = self.text.index 0348 self_text_mark_set = self.text.mark_set 0349 edges_table = ("sel.first+1c", "sel.last-1c") 0350 def move_at_edge(event): 0351 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed 0352 try: 0353 self_text_index("sel.first") 0354 self_text_mark_set("insert", edges_table[edge_index]) 0355 except TclError: 0356 pass 0357 return move_at_edge 0358 0359 def find_event(self, event): 0360 SearchDialog.find(self.text) 0361 return "break" 0362 0363 def find_again_event(self, event): 0364 SearchDialog.find_again(self.text) 0365 return "break" 0366 0367 def find_selection_event(self, event): 0368 SearchDialog.find_selection(self.text) 0369 return "break" 0370 0371 def find_in_files_event(self, event): 0372 GrepDialog.grep(self.text, self.io, self.flist) 0373 return "break" 0374 0375 def replace_event(self, event): 0376 ReplaceDialog.replace(self.text) 0377 return "break" 0378 0379 def goto_line_event(self, event): 0380 text = self.text 0381 lineno = tkSimpleDialog.askinteger("Goto", 0382 "Go to line number:",parent=text) 0383 if lineno is None: 0384 return "break" 0385 if lineno <= 0: 0386 text.bell() 0387 return "break" 0388 text.mark_set("insert", "%d.0" % lineno) 0389 text.see("insert") 0390 0391 def open_module(self, event=None): 0392 # XXX Shouldn't this be in IOBinding or in FileList? 0393 try: 0394 name = self.text.get("sel.first", "sel.last") 0395 except TclError: 0396 name = "" 0397 else: 0398 name = name.strip() 0399 name = tkSimpleDialog.askstring("Module", 0400 "Enter the name of a Python module\n" 0401 "to search on sys.path and open:", 0402 parent=self.text, initialvalue=name) 0403 if name: 0404 name = name.strip() 0405 if not name: 0406 return 0407 # XXX Ought to insert current file's directory in front of path 0408 try: 0409 (f, file, (suffix, mode, type)) = _find_module(name) 0410 except (NameError, ImportError), msg: 0411 tkMessageBox.showerror("Import error", str(msg), parent=self.text) 0412 return 0413 if type != imp.PY_SOURCE: 0414 tkMessageBox.showerror("Unsupported type", 0415 "%s is not a source module" % name, parent=self.text) 0416 return 0417 if f: 0418 f.close() 0419 if self.flist: 0420 self.flist.open(file) 0421 else: 0422 self.io.loadfile(file) 0423 0424 def open_class_browser(self, event=None): 0425 filename = self.io.filename 0426 if not filename: 0427 tkMessageBox.showerror( 0428 "No filename", 0429 "This buffer has no associated filename", 0430 master=self.text) 0431 self.text.focus_set() 0432 return None 0433 head, tail = os.path.split(filename) 0434 base, ext = os.path.splitext(tail) 0435 import ClassBrowser 0436 ClassBrowser.ClassBrowser(self.flist, base, [head]) 0437 0438 def open_path_browser(self, event=None): 0439 import PathBrowser 0440 PathBrowser.PathBrowser(self.flist) 0441 0442 def gotoline(self, lineno): 0443 if lineno is not None and lineno > 0: 0444 self.text.mark_set("insert", "%d.0" % lineno) 0445 self.text.tag_remove("sel", "1.0", "end") 0446 self.text.tag_add("sel", "insert", "insert +1l") 0447 self.center() 0448 0449 def ispythonsource(self, filename): 0450 if not filename: 0451 return True 0452 base, ext = os.path.splitext(os.path.basename(filename)) 0453 if os.path.normcase(ext) in (".py", ".pyw"): 0454 return True 0455 try: 0456 f = open(filename) 0457 line = f.readline() 0458 f.close() 0459 except IOError: 0460 return False 0461 return line.startswith('#!') and line.find('python') >= 0 0462 0463 def close_hook(self): 0464 if self.flist: 0465 self.flist.close_edit(self) 0466 0467 def set_close_hook(self, close_hook): 0468 self.close_hook = close_hook 0469 0470 def filename_change_hook(self): 0471 if self.flist: 0472 self.flist.filename_changed_edit(self) 0473 self.saved_change_hook() 0474 self.top.update_windowlist_registry(self) 0475 if self.ispythonsource(self.io.filename): 0476 self.addcolorizer() 0477 else: 0478 self.rmcolorizer() 0479 0480 def addcolorizer(self): 0481 if self.color: 0482 return 0483 self.per.removefilter(self.undo) 0484 self.color = self.ColorDelegator() 0485 self.per.insertfilter(self.color) 0486 self.per.insertfilter(self.undo) 0487 0488 def rmcolorizer(self): 0489 if not self.color: 0490 return 0491 self.per.removefilter(self.undo) 0492 self.per.removefilter(self.color) 0493 self.color = None 0494 self.per.insertfilter(self.undo) 0495 0496 def ResetColorizer(self): 0497 "Update the colour theme if it is changed" 0498 # Called from configDialog.py 0499 if self.color: 0500 self.color = self.ColorDelegator() 0501 self.per.insertfilter(self.color) 0502 theme = idleConf.GetOption('main','Theme','name') 0503 self.text.config(idleConf.GetHighlight(theme, "normal")) 0504 0505 def ResetFont(self): 0506 "Update the text widgets' font if it is changed" 0507 # Called from configDialog.py 0508 fontWeight='normal' 0509 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'): 0510 fontWeight='bold' 0511 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'), 0512 idleConf.GetOption('main','EditorWindow','font-size'), 0513 fontWeight)) 0514 0515 def ResetKeybindings(self): 0516 "Update the keybindings if they are changed" 0517 # Called from configDialog.py 0518 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet() 0519 keydefs = self.Bindings.default_keydefs 0520 for event, keylist in keydefs.items(): 0521 self.text.event_delete(event) 0522 self.apply_bindings() 0523 #update menu accelerators 0524 menuEventDict={} 0525 for menu in self.Bindings.menudefs: 0526 menuEventDict[menu[0]]={} 0527 for item in menu[1]: 0528 if item: 0529 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1] 0530 for menubarItem in self.menudict.keys(): 0531 menu=self.menudict[menubarItem] 0532 end=menu.index(END)+1 0533 for index in range(0,end): 0534 if menu.type(index)=='command': 0535 accel=menu.entrycget(index,'accelerator') 0536 if accel: 0537 itemName=menu.entrycget(index,'label') 0538 event='' 0539 if menuEventDict.has_key(menubarItem): 0540 if menuEventDict[menubarItem].has_key(itemName): 0541 event=menuEventDict[menubarItem][itemName] 0542 if event: 0543 accel=get_accelerator(keydefs, event) 0544 menu.entryconfig(index,accelerator=accel) 0545 0546 def reset_help_menu_entries(self): 0547 "Update the additional help entries on the Help menu" 0548 help_list = idleConf.GetAllExtraHelpSourcesList() 0549 helpmenu = self.menudict['help'] 0550 # first delete the extra help entries, if any 0551 helpmenu_length = helpmenu.index(END) 0552 if helpmenu_length > self.base_helpmenu_length: 0553 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length) 0554 # then rebuild them 0555 if help_list: 0556 helpmenu.add_separator() 0557 for entry in help_list: 0558 cmd = self.__extra_help_callback(entry[1]) 0559 helpmenu.add_command(label=entry[0], command=cmd) 0560 # and update the menu dictionary 0561 self.menudict['help'] = helpmenu 0562 0563 def __extra_help_callback(self, helpfile): 0564 "Create a callback with the helpfile value frozen at definition time" 0565 def display_extra_help(helpfile=helpfile): 0566 if not (helpfile.startswith('www') or helpfile.startswith('http')): 0567 url = os.path.normpath(helpfile) 0568 if sys.platform[:3] == 'win': 0569 os.startfile(helpfile) 0570 else: 0571 webbrowser.open(helpfile) 0572 return display_extra_help 0573 0574 def update_recent_files_list(self, new_file=None): 0575 "Load and update the recent files list and menus" 0576 rf_list = [] 0577 if os.path.exists(self.recent_files_path): 0578 rf_list_file = open(self.recent_files_path,'r') 0579 try: 0580 rf_list = rf_list_file.readlines() 0581 finally: 0582 rf_list_file.close() 0583 if new_file: 0584 new_file = os.path.abspath(new_file) + '\n' 0585 if new_file in rf_list: 0586 rf_list.remove(new_file) # move to top 0587 rf_list.insert(0, new_file) 0588 # clean and save the recent files list 0589 bad_paths = [] 0590 for path in rf_list: 0591 if '\0' in path or not os.path.exists(path[0:-1]): 0592 bad_paths.append(path) 0593 rf_list = [path for path in rf_list if path not in bad_paths] 0594 ulchars = "1234567890ABCDEFGHIJK" 0595 rf_list = rf_list[0:len(ulchars)] 0596 rf_file = open(self.recent_files_path, 'w') 0597 try: 0598 rf_file.writelines(rf_list) 0599 finally: 0600 rf_file.close() 0601 # for each edit window instance, construct the recent files menu 0602 for instance in self.top.instance_dict.keys(): 0603 menu = instance.recent_files_menu 0604 menu.delete(1, END) # clear, and rebuild: 0605 for i, file in zip(count(), rf_list): 0606 file_name = file[0:-1] # zap \n 0607 callback = instance.__recent_file_callback(file_name) 0608 menu.add_command(label=ulchars[i] + " " + file_name, 0609 command=callback, 0610 underline=0) 0611 0612 def __recent_file_callback(self, file_name): 0613 def open_recent_file(fn_closure=file_name): 0614 self.io.open(editFile=fn_closure) 0615 return open_recent_file 0616 0617 def saved_change_hook(self): 0618 short = self.short_title() 0619 long = self.long_title() 0620 if short and long: 0621 title = short + " - " + long 0622 elif short: 0623 title = short 0624 elif long: 0625 title = long 0626 else: 0627 title = "Untitled" 0628 icon = short or long or title 0629 if not self.get_saved(): 0630 title = "*%s*" % title 0631 icon = "*%s" % icon 0632 self.top.wm_title(title) 0633 self.top.wm_iconname(icon) 0634 0635 def get_saved(self): 0636 return self.undo.get_saved() 0637 0638 def set_saved(self, flag): 0639 self.undo.set_saved(flag) 0640 0641 def reset_undo(self): 0642 self.undo.reset_undo() 0643 0644 def short_title(self): 0645 filename = self.io.filename 0646 if filename: 0647 filename = os.path.basename(filename) 0648 return filename 0649 0650 def long_title(self): 0651 return self.io.filename or "" 0652 0653 def center_insert_event(self, event): 0654 self.center() 0655 0656 def center(self, mark="insert"): 0657 text = self.text 0658 top, bot = self.getwindowlines() 0659 lineno = self.getlineno(mark) 0660 height = bot - top 0661 newtop = max(1, lineno - height//2) 0662 text.yview(float(newtop)) 0663 0664 def getwindowlines(self): 0665 text = self.text 0666 top = self.getlineno("@0,0") 0667 bot = self.getlineno("@0,65535") 0668 if top == bot and text.winfo_height() == 1: 0669 # Geometry manager hasn't run yet 0670 height = int(text['height']) 0671 bot = top + height - 1 0672 return top, bot 0673 0674 def getlineno(self, mark="insert"): 0675 text = self.text 0676 return int(float(text.index(mark))) 0677 0678 def get_geometry(self): 0679 "Return (width, height, x, y)" 0680 geom = self.top.wm_geometry() 0681 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom) 0682 tuple = (map(int, m.groups())) 0683 return tuple 0684 0685 def close_event(self, event): 0686 self.close() 0687 0688 def maybesave(self): 0689 if self.io: 0690 if not self.get_saved(): 0691 if self.top.state()!='normal': 0692 self.top.deiconify() 0693 self.top.lower() 0694 self.top.lift() 0695 return self.io.maybesave() 0696 0697 def close(self): 0698 reply = self.maybesave() 0699 if reply != "cancel": 0700 self._close() 0701 return reply 0702 0703 def _close(self): 0704 if self.io.filename: 0705 self.update_recent_files_list(new_file=self.io.filename) 0706 WindowList.unregister_callback(self.postwindowsmenu) 0707 if self.close_hook: 0708 self.close_hook() 0709 self.flist = None 0710 colorizing = 0 0711 self.unload_extensions() 0712 self.io.close(); self.io = None 0713 self.undo = None # XXX 0714 if self.color: 0715 colorizing = self.color.colorizing 0716 doh = colorizing and self.top 0717 self.color.close(doh) # Cancel colorization 0718 self.text = None 0719 self.tkinter_vars = None 0720 self.per.close(); self.per = None 0721 if not colorizing: 0722 self.top.destroy() 0723 0724 def load_extensions(self): 0725 self.extensions = {} 0726 self.load_standard_extensions() 0727 0728 def unload_extensions(self): 0729 for ins in self.extensions.values(): 0730 if hasattr(ins, "close"): 0731 ins.close() 0732 self.extensions = {} 0733 0734 def load_standard_extensions(self): 0735 for name in self.get_standard_extension_names(): 0736 try: 0737 self.load_extension(name) 0738 except: 0739 print "Failed to load extension", repr(name) 0740 import traceback 0741 traceback.print_exc() 0742 0743 def get_standard_extension_names(self): 0744 return idleConf.GetExtensions(editor_only=True) 0745 0746 def load_extension(self, name): 0747 mod = __import__(name, globals(), locals(), []) 0748 cls = getattr(mod, name) 0749 keydefs = idleConf.GetExtensionBindings(name) 0750 if hasattr(cls, "menudefs"): 0751 self.fill_menus(cls.menudefs, keydefs) 0752 ins = cls(self) 0753 self.extensions[name] = ins 0754 if keydefs: 0755 self.apply_bindings(keydefs) 0756 for vevent in keydefs.keys(): 0757 methodname = vevent.replace("-", "_") 0758 while methodname[:1] == '<': 0759 methodname = methodname[1:] 0760 while methodname[-1:] == '>': 0761 methodname = methodname[:-1] 0762 methodname = methodname + "_event" 0763 if hasattr(ins, methodname): 0764 self.text.bind(vevent, getattr(ins, methodname)) 0765 return ins 0766 0767 def apply_bindings(self, keydefs=None): 0768 if keydefs is None: 0769 keydefs = self.Bindings.default_keydefs 0770 text = self.text 0771 text.keydefs = keydefs 0772 for event, keylist in keydefs.items(): 0773 if keylist: 0774 text.event_add(event, *keylist) 0775 0776 def fill_menus(self, menudefs=None, keydefs=None): 0777 """Add appropriate entries to the menus and submenus 0778 0779 Menus that are absent or None in self.menudict are ignored. 0780 """ 0781 if menudefs is None: 0782 menudefs = self.Bindings.menudefs 0783 if keydefs is None: 0784 keydefs = self.Bindings.default_keydefs 0785 menudict = self.menudict 0786 text = self.text 0787 for mname, entrylist in menudefs: 0788 menu = menudict.get(mname) 0789 if not menu: 0790 continue 0791 for entry in entrylist: 0792 if not entry: 0793 menu.add_separator() 0794 else: 0795 label, eventname = entry 0796 checkbutton = (label[:1] == '!') 0797 if checkbutton: 0798 label = label[1:] 0799 underline, label = prepstr(label) 0800 accelerator = get_accelerator(keydefs, eventname) 0801 def command(text=text, eventname=eventname): 0802 text.event_generate(eventname) 0803 if checkbutton: 0804 var = self.get_var_obj(eventname, BooleanVar) 0805 menu.add_checkbutton(label=label, underline=underline, 0806 command=command, accelerator=accelerator, 0807 variable=var) 0808 else: 0809 menu.add_command(label=label, underline=underline, 0810 command=command, 0811 accelerator=accelerator) 0812 0813 def getvar(self, name): 0814 var = self.get_var_obj(name) 0815 if var: 0816 value = var.get() 0817 return value 0818 else: 0819 raise NameError, name 0820 0821 def setvar(self, name, value, vartype=None): 0822 var = self.get_var_obj(name, vartype) 0823 if var: 0824 var.set(value) 0825 else: 0826 raise NameError, name 0827 0828 def get_var_obj(self, name, vartype=None): 0829 var = self.tkinter_vars.get(name) 0830 if not var and vartype: 0831 # create a Tkinter variable object with self.text as master: 0832 self.tkinter_vars[name] = var = vartype(self.text) 0833 return var 0834 0835 # Tk implementations of "virtual text methods" -- each platform 0836 # reusing IDLE's support code needs to define these for its GUI's 0837 # flavor of widget. 0838 0839 # Is character at text_index in a Python string? Return 0 for 0840 # "guaranteed no", true for anything else. This info is expensive 0841 # to compute ab initio, but is probably already known by the 0842 # platform's colorizer. 0843 0844 def is_char_in_string(self, text_index): 0845 if self.color: 0846 # Return true iff colorizer hasn't (re)gotten this far 0847 # yet, or the character is tagged as being in a string 0848 return self.text.tag_prevrange("TODO", text_index) or \ 0849 "STRING" in self.text.tag_names(text_index) 0850 else: 0851 # The colorizer is missing: assume the worst 0852 return 1 0853 0854 # If a selection is defined in the text widget, return (start, 0855 # end) as Tkinter text indices, otherwise return (None, None) 0856 def get_selection_indices(self): 0857 try: 0858 first = self.text.index("sel.first") 0859 last = self.text.index("sel.last") 0860 return first, last 0861 except TclError: 0862 return None, None 0863 0864 # Return the text widget's current view of what a tab stop means 0865 # (equivalent width in spaces). 0866 0867 def get_tabwidth(self): 0868 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT 0869 return int(current) 0870 0871 # Set the text widget's current view of what a tab stop means. 0872 0873 def set_tabwidth(self, newtabwidth): 0874 text = self.text 0875 if self.get_tabwidth() != newtabwidth: 0876 pixels = text.tk.call("font", "measure", text["font"], 0877 "-displayof", text.master, 0878 "n" * newtabwidth) 0879 text.configure(tabs=pixels) 0880 0881 ### begin autoindent code ### 0882 0883 # usetabs true -> literal tab characters are used by indent and 0884 # dedent cmds, possibly mixed with spaces if 0885 # indentwidth is not a multiple of tabwidth 0886 # false -> tab characters are converted to spaces by indent 0887 # and dedent cmds, and ditto TAB keystrokes 0888 # indentwidth is the number of characters per logical indent level. 0889 # tabwidth is the display width of a literal tab character. 0890 # CAUTION: telling Tk to use anything other than its default 0891 # tab setting causes it to use an entirely different tabbing algorithm, 0892 # treating tab stops as fixed distances from the left margin. 0893 # Nobody expects this, so for now tabwidth should never be changed. 0894 usetabs = 0 0895 indentwidth = 4 0896 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed 0897 0898 # If context_use_ps1 is true, parsing searches back for a ps1 line; 0899 # else searches for a popular (if, def, ...) Python stmt. 0900 context_use_ps1 = 0 0901 0902 # When searching backwards for a reliable place to begin parsing, 0903 # first start num_context_lines[0] lines back, then 0904 # num_context_lines[1] lines back if that didn't work, and so on. 0905 # The last value should be huge (larger than the # of lines in a 0906 # conceivable file). 0907 # Making the initial values larger slows things down more often. 0908 num_context_lines = 50, 500, 5000000 0909 0910 def config(self, **options): 0911 for key, value in options.items(): 0912 if key == 'usetabs': 0913 self.usetabs = value 0914 elif key == 'indentwidth': 0915 self.indentwidth = value 0916 elif key == 'tabwidth': 0917 self.tabwidth = value 0918 elif key == 'context_use_ps1': 0919 self.context_use_ps1 = value 0920 else: 0921 raise KeyError, "bad option name: %r" % (key,) 0922 0923 # If ispythonsource and guess are true, guess a good value for 0924 # indentwidth based on file content (if possible), and if 0925 # indentwidth != tabwidth set usetabs false. 0926 # In any case, adjust the Text widget's view of what a tab 0927 # character means. 0928 0929 def set_indentation_params(self, ispythonsource, guess=1): 0930 if guess and ispythonsource: 0931 i = self.guess_indent() 0932 if 2 <= i <= 8: 0933 self.indentwidth = i 0934 if self.indentwidth != self.tabwidth: 0935 self.usetabs = 0 0936 0937 self.set_tabwidth(self.tabwidth) 0938 0939 def smart_backspace_event(self, event): 0940 text = self.text 0941 first, last = self.get_selection_indices() 0942 if first and last: 0943 text.delete(first, last) 0944 text.mark_set("insert", first) 0945 return "break" 0946 # Delete whitespace left, until hitting a real char or closest 0947 # preceding virtual tab stop. 0948 chars = text.get("insert linestart", "insert") 0949 if chars == '': 0950 if text.compare("insert", ">", "1.0"): 0951 # easy: delete preceding newline 0952 text.delete("insert-1c") 0953 else: 0954 text.bell() # at start of buffer 0955 return "break" 0956 if chars[-1] not in " \t": 0957 # easy: delete preceding real char 0958 text.delete("insert-1c") 0959 return "break" 0960 # Ick. It may require *inserting* spaces if we back up over a 0961 # tab character! This is written to be clear, not fast. 0962 tabwidth = self.tabwidth 0963 have = len(chars.expandtabs(tabwidth)) 0964 assert have > 0 0965 want = ((have - 1) // self.indentwidth) * self.indentwidth 0966 # Debug prompt is multilined.... 0967 last_line_of_prompt = sys.ps1.split('\n')[-1] 0968 ncharsdeleted = 0 0969 while 1: 0970 if chars == last_line_of_prompt: 0971 break 0972 chars = chars[:-1] 0973 ncharsdeleted = ncharsdeleted + 1 0974 have = len(chars.expandtabs(tabwidth)) 0975 if have <= want or chars[-1] not in " \t": 0976 break 0977 text.undo_block_start() 0978 text.delete("insert-%dc" % ncharsdeleted, "insert") 0979 if have < want: 0980 text.insert("insert", ' ' * (want - have)) 0981 text.undo_block_stop() 0982 return "break" 0983 0984 def smart_indent_event(self, event): 0985 # if intraline selection: 0986 # delete it 0987 # elif multiline selection: 0988 # do indent-region & return 0989 # indent one level 0990 text = self.text 0991 first, last = self.get_selection_indices() 0992 text.undo_block_start() 0993 try: 0994 if first and last: 0995 if index2line(first) != index2line(last): 0996 return self.indent_region_event(event) 0997 text.delete(first, last) 0998 text.mark_set("insert", first) 0999 prefix = text.get("insert linestart", "insert") 1000 raw, effective = classifyws(prefix, self.tabwidth) 1001 if raw == len(prefix): 1002 # only whitespace to the left 1003 self.reindent_to(effective + self.indentwidth) 1004 else: 1005 if self.usetabs: 1006 pad = '\t' 1007 else: 1008 effective = len(prefix.expandtabs(self.tabwidth)) 1009 n = self.indentwidth 1010 pad = ' ' * (n - effective % n) 1011 text.insert("insert", pad) 1012 text.see("insert") 1013 return "break" 1014 finally: 1015 text.undo_block_stop() 1016 1017 def newline_and_indent_event(self, event): 1018 text = self.text 1019 first, last = self.get_selection_indices() 1020 text.undo_block_start() 1021 try: 1022 if first and last: 1023 text.delete(first, last) 1024 text.mark_set("insert", first) 1025 line = text.get("insert linestart", "insert") 1026 i, n = 0, len(line) 1027 while i < n and line[i] in " \t": 1028 i = i+1 1029 if i == n: 1030 # the cursor is in or at leading indentation in a continuation 1031 # line; just inject an empty line at the start 1032 text.insert("insert linestart", '\n') 1033 return "break" 1034 indent = line[:i] 1035 # strip whitespace before insert point unless it's in the prompt 1036 i = 0 1037 last_line_of_prompt = sys.ps1.split('\n')[-1] 1038 while line and line[-1] in " \t" and line != last_line_of_prompt: 1039 line = line[:-1] 1040 i = i+1 1041 if i: 1042 text.delete("insert - %d chars" % i, "insert") 1043 # strip whitespace after insert point 1044 while text.get("insert") in " \t": 1045 text.delete("insert") 1046 # start new line 1047 text.insert("insert", '\n') 1048 1049 # adjust indentation for continuations and block 1050 # open/close first need to find the last stmt 1051 lno = index2line(text.index('insert')) 1052 y = PyParse.Parser(self.indentwidth, self.tabwidth) 1053 for context in self.num_context_lines: 1054 startat = max(lno - context, 1) 1055 startatindex = repr(startat) + ".0" 1056 rawtext = text.get(startatindex, "insert") 1057 y.set_str(rawtext) 1058 bod = y.find_good_parse_start( 1059 self.context_use_ps1, 1060 self._build_char_in_string_func(startatindex)) 1061 if bod is not None or startat == 1: 1062 break 1063 y.set_lo(bod or 0) 1064 c = y.get_continuation_type() 1065 if c != PyParse.C_NONE: 1066 # The current stmt hasn't ended yet. 1067 if c == PyParse.C_STRING: 1068 # inside a string; just mimic the current indent 1069 text.insert("insert", indent) 1070 elif c == PyParse.C_BRACKET: 1071 # line up with the first (if any) element of the 1072 # last open bracket structure; else indent one 1073 # level beyond the indent of the line with the 1074 # last open bracket 1075 self.reindent_to(y.compute_bracket_indent()) 1076 elif c == PyParse.C_BACKSLASH: 1077 # if more than one line in this stmt already, just 1078 # mimic the current indent; else if initial line 1079 # has a start on an assignment stmt, indent to 1080 # beyond leftmost =; else to beyond first chunk of 1081 # non-whitespace on initial line 1082 if y.get_num_lines_in_stmt() > 1: 1083 text.insert("insert", indent) 1084 else: 1085 self.reindent_to(y.compute_backslash_indent()) 1086 else: 1087 assert 0, "bogus continuation type %r" % (c,) 1088 return "break" 1089 1090 # This line starts a brand new stmt; indent relative to 1091 # indentation of initial line of closest preceding 1092 # interesting stmt. 1093 indent = y.get_base_indent_string() 1094 text.insert("insert", indent) 1095 if y.is_block_opener(): 1096 self.smart_indent_event(event) 1097 elif indent and y.is_block_closer(): 1098 self.smart_backspace_event(event) 1099 return "break" 1100 finally: 1101 text.see("insert") 1102 text.undo_block_stop() 1103 1104 # Our editwin provides a is_char_in_string function that works 1105 # with a Tk text index, but PyParse only knows about offsets into 1106 # a string. This builds a function for PyParse that accepts an 1107 # offset. 1108 1109 def _build_char_in_string_func(self, startindex): 1110 def inner(offset, _startindex=startindex, 1111 _icis=self.is_char_in_string): 1112 return _icis(_startindex + "+%dc" % offset) 1113 return inner 1114 1115 def indent_region_event(self, event): 1116 head, tail, chars, lines = self.get_region() 1117 for pos in range(len(lines)): 1118 line = lines[pos] 1119 if line: 1120 raw, effective = classifyws(line, self.tabwidth) 1121 effective = effective + self.indentwidth 1122 lines[pos] = self._make_blanks(effective) + line[raw:] 1123 self.set_region(head, tail, chars, lines) 1124 return "break" 1125 1126 def dedent_region_event(self, event): 1127 head, tail, chars, lines = self.get_region() 1128 for pos in range(len(lines)): 1129 line = lines[pos] 1130 if line: 1131 raw, effective = classifyws(line, self.tabwidth) 1132 effective = max(effective - self.indentwidth, 0) 1133 lines[pos] = self._make_blanks(effective) + line[raw:] 1134 self.set_region(head, tail, chars, lines) 1135 return "break" 1136 1137 def comment_region_event(self, event): 1138 head, tail, chars, lines = self.get_region() 1139 for pos in range(len(lines) - 1): 1140 line = lines[pos] 1141 lines[pos] = '##' + line 1142 self.set_region(head, tail, chars, lines) 1143 1144 def uncomment_region_event(self, event): 1145 head, tail, chars, lines = self.get_region() 1146 for pos in range(len(lines)): 1147 line = lines[pos] 1148 if not line: 1149 continue 1150 if line[:2] == '##': 1151 line = line[2:] 1152 elif line[:1] == '#': 1153 line = line[1:] 1154 lines[pos] = line 1155 self.set_region(head, tail, chars, lines) 1156 1157 def tabify_region_event(self, event): 1158 head, tail, chars, lines = self.get_region() 1159 tabwidth = self._asktabwidth() 1160 for pos in range(len(lines)): 1161 line = lines[pos] 1162 if line: 1163 raw, effective = classifyws(line, tabwidth) 1164 ntabs, nspaces = divmod(effective, tabwidth) 1165 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:] 1166 self.set_region(head, tail, chars, lines) 1167 1168 def untabify_region_event(self, event): 1169 head, tail, chars, lines = self.get_region() 1170 tabwidth = self._asktabwidth() 1171 for pos in range(len(lines)): 1172 lines[pos] = lines[pos].expandtabs(tabwidth) 1173 self.set_region(head, tail, chars, lines) 1174 1175 def toggle_tabs_event(self, event): 1176 if self.askyesno( 1177 "Toggle tabs", 1178 "Turn tabs " + ("on", "off")[self.usetabs] + "?", 1179 parent=self.text): 1180 self.usetabs = not self.usetabs 1181 return "break" 1182 1183 # XXX this isn't bound to anything -- see class tabwidth comments 1184 def change_tabwidth_event(self, event): 1185 new = self._asktabwidth() 1186 if new != self.tabwidth: 1187 self.tabwidth = new 1188 self.set_indentation_params(0, guess=0) 1189 return "break" 1190 1191 def change_indentwidth_event(self, event): 1192 new = self.askinteger( 1193 "Indent width", 1194 "New indent width (2-16)", 1195 parent=self.text, 1196 initialvalue=self.indentwidth, 1197 minvalue=2, 1198 maxvalue=16) 1199 if new and new != self.indentwidth: 1200 self.indentwidth = new 1201 return "break" 1202 1203 def get_region(self): 1204 text = self.text 1205 first, last = self.get_selection_indices() 1206 if first and last: 1207 head = text.index(first + " linestart") 1208 tail = text.index(last + "-1c lineend +1c") 1209 else: 1210 head = text.index("insert linestart") 1211 tail = text.index("insert lineend +1c") 1212 chars = text.get(head, tail) 1213 lines = chars.split("\n") 1214 return head, tail, chars, lines 1215 1216 def set_region(self, head, tail, chars, lines): 1217 text = self.text 1218 newchars = "\n".join(lines) 1219 if newchars == chars: 1220 text.bell() 1221 return 1222 text.tag_remove("sel", "1.0", "end") 1223 text.mark_set("insert", head) 1224 text.undo_block_start() 1225 text.delete(head, tail) 1226 text.insert(head, newchars) 1227 text.undo_block_stop() 1228 text.tag_add("sel", head, "insert") 1229 1230 # Make string that displays as n leading blanks. 1231 1232 def _make_blanks(self, n): 1233 if self.usetabs: 1234 ntabs, nspaces = divmod(n, self.tabwidth) 1235 return '\t' * ntabs + ' ' * nspaces 1236 else: 1237 return ' ' * n 1238 1239 # Delete from beginning of line to insert point, then reinsert 1240 # column logical (meaning use tabs if appropriate) spaces. 1241 1242 def reindent_to(self, column): 1243 text = self.text 1244 text.undo_block_start() 1245 if text.compare("insert linestart", "!=", "insert"): 1246 text.delete("insert linestart", "insert") 1247 if column: 1248 text.insert("insert", self._make_blanks(column)) 1249 text.undo_block_stop() 1250 1251 def _asktabwidth(self): 1252 return self.askinteger( 1253 "Tab width", 1254 "Spaces per tab? (2-16)", 1255 parent=self.text, 1256 initialvalue=self.indentwidth, 1257 minvalue=2, 1258 maxvalue=16) or self.tabwidth 1259 1260 # Guess indentwidth from text content. 1261 # Return guessed indentwidth. This should not be believed unless 1262 # it's in a reasonable range (e.g., it will be 0 if no indented 1263 # blocks are found). 1264 1265 def guess_indent(self): 1266 opener, indented = IndentSearcher(self.text, self.tabwidth).run() 1267 if opener and indented: 1268 raw, indentsmall = classifyws(opener, self.tabwidth) 1269 raw, indentlarge = classifyws(indented, self.tabwidth) 1270 else: 1271 indentsmall = indentlarge = 0 1272 return indentlarge - indentsmall 1273 1274 # "line.col" -> line, as an int 1275 def index2line(index): 1276 return int(float(index)) 1277 1278 # Look at the leading whitespace in s. 1279 # Return pair (# of leading ws characters, 1280 # effective # of leading blanks after expanding 1281 # tabs to width tabwidth) 1282 1283 def classifyws(s, tabwidth): 1284 raw = effective = 0 1285 for ch in s: 1286 if ch == ' ': 1287 raw = raw + 1 1288 effective = effective + 1 1289 elif ch == '\t': 1290 raw = raw + 1 1291 effective = (effective // tabwidth + 1) * tabwidth 1292 else: 1293 break 1294 return raw, effective 1295 1296 import tokenize 1297 _tokenize = tokenize 1298 del tokenize 1299 1300 class IndentSearcher: 1301 1302 # .run() chews over the Text widget, looking for a block opener 1303 # and the stmt following it. Returns a pair, 1304 # (line containing block opener, line containing stmt) 1305 # Either or both may be None. 1306 1307 def __init__(self, text, tabwidth): 1308 self.text = text 1309 self.tabwidth = tabwidth 1310 self.i = self.finished = 0 1311 self.blkopenline = self.indentedline = None 1312 1313 def readline(self): 1314 if self.finished: 1315 return "" 1316 i = self.i = self.i + 1 1317 mark = repr(i) + ".0" 1318 if self.text.compare(mark, ">=", "end"): 1319 return "" 1320 return self.text.get(mark, mark + " lineend+1c") 1321 1322 def tokeneater(self, type, token, start, end, line, 1323 INDENT=_tokenize.INDENT, 1324 NAME=_tokenize.NAME, 1325 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')): 1326 if self.finished: 1327 pass 1328 elif type == NAME and token in OPENERS: 1329 self.blkopenline = line 1330 elif type == INDENT and self.blkopenline: 1331 self.indentedline = line 1332 self.finished = 1 1333 1334 def run(self): 1335 save_tabsize = _tokenize.tabsize 1336 _tokenize.tabsize = self.tabwidth 1337 try: 1338 try: 1339 _tokenize.tokenize(self.readline, self.tokeneater) 1340 except _tokenize.TokenError: 1341 # since we cut off the tokenizer early, we can trigger 1342 # spurious errors 1343 pass 1344 finally: 1345 _tokenize.tabsize = save_tabsize 1346 return self.blkopenline, self.indentedline 1347 1348 ### end autoindent code ### 1349 1350 def prepstr(s): 1351 # Helper to extract the underscore from a string, e.g. 1352 # prepstr("Co_py") returns (2, "Copy"). 1353 i = s.find('_') 1354 if i >= 0: 1355 s = s[:i] + s[i+1:] 1356 return i, s 1357 1358 1359 keynames = { 1360 'bracketleft': '[', 1361 'bracketright': ']', 1362 'slash': '/', 1363 } 1364 1365 def get_accelerator(keydefs, eventname): 1366 keylist = keydefs.get(eventname) 1367 if not keylist: 1368 return "" 1369 s = keylist[0] 1370 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s) 1371 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s) 1372 s = re.sub("Key-", "", s) 1373 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu 1374 s = re.sub("Control-", "Ctrl-", s) 1375 s = re.sub("-", "+", s) 1376 s = re.sub("><", " ", s) 1377 s = re.sub("<", "", s) 1378 s = re.sub(">", "", s) 1379 return s 1380 1381 1382 def fixwordbreaks(root): 1383 # Make sure that Tk's double-click and next/previous word 1384 # operations use our definition of a word (i.e. an identifier) 1385 tk = root.tk 1386 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded 1387 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]') 1388 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]') 1389 1390 1391 def test(): 1392 root = Tk() 1393 fixwordbreaks(root) 1394 root.withdraw() 1395 if sys.argv[1:]: 1396 filename = sys.argv[1] 1397 else: 1398 filename = None 1399 edit = EditorWindow(root=root, filename=filename) 1400 edit.set_close_hook(root.quit) 1401 root.mainloop() 1402 root.destroy() 1403 1404 if __name__ == '__main__': 1405 test() 1406
Generated by PyXR 0.9.4