1 """Extension to execute code outside the Python shell window. 2 3 This adds the following commands: 4 5 - Check module does a full syntax check of the current module. 6 It also runs the tabnanny to catch any inconsistent tabs. 7 8 - Run module executes the module's code in the __main__ namespace. The window 9 must have been saved previously. The module is added to sys.modules, and is 10 also added to the __main__ namespace. 11 12 XXX GvR Redesign this interface (yet again) as follows: 13 14 - Present a dialog box for ``Run Module'' 15 16 - Allow specify command line arguments in the dialog box 17 18 """ 19 20 import os 21 import re 22 import string 23 import tabnanny 24 import tokenize 25 import tkMessageBox 26 from idlelib import PyShell 27 28 from idlelib.configHandler import idleConf 29 from idlelib import macosxSupport 30 31 IDENTCHARS = string.ascii_letters + string.digits + "_" 32 33 indent_message = """Error: Inconsistent indentation detected! 34 35 1) Your indentation is outright incorrect (easy to fix), OR 36 37 2) Your indentation mixes tabs and spaces. 38 39 To fix case 2, change all tabs to spaces by using Edit->Select All followed \ 40 by Format->Untabify Region and specify the number of columns used by each tab. 41 """ 42 43 class ScriptBinding: 44 45 menudefs = [ 46 ('run', [None, 47 ('Check Module', '<<check-module>>'), 48 ('Run Module', '<<run-module>>'), ]), ] 49 50 def __init__(self, editwin): 51 self.editwin = editwin 52 # Provide instance variables referenced by Debugger 53 # XXX This should be done differently 54 self.flist = self.editwin.flist 55 self.root = self.editwin.root 56 57 if macosxSupport.runningAsOSXApp(): 58 self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event) 59 60 def check_module_event(self, event): 61 filename = self.getfilename() 62 if not filename: 63 return 'break' 64 if not self.checksyntax(filename): 65 return 'break' 66 if not self.tabnanny(filename): 67 return 'break' 68 69 def tabnanny(self, filename): 70 f = open(filename, 'r') 71 try: 72 tabnanny.process_tokens(tokenize.generate_tokens(f.readline)) 73 except tokenize.TokenError, msg: 74 msgtxt, (lineno, start) = msg 75 self.editwin.gotoline(lineno) 76 self.errorbox("Tabnanny Tokenizing Error", 77 "Token Error: %s" % msgtxt) 78 return False 79 except tabnanny.NannyNag, nag: 80 # The error messages from tabnanny are too confusing... 81 self.editwin.gotoline(nag.get_lineno()) 82 self.errorbox("Tab/space error", indent_message) 83 return False 84 return True 85 86 def checksyntax(self, filename): 87 self.shell = shell = self.flist.open_shell() 88 saved_stream = shell.get_warning_stream() 89 shell.set_warning_stream(shell.stderr) 90 f = open(filename, 'r') 91 source = f.read() 92 f.close() 93 if '\r' in source: 94 source = re.sub(r"\r\n", "\n", source) 95 source = re.sub(r"\r", "\n", source) 96 if source and source[-1] != '\n': 97 source = source + '\n' 98 text = self.editwin.text 99 text.tag_remove("ERROR", "1.0", "end") 100 try: 101 try: 102 # If successful, return the compiled code 103 return compile(source, filename, "exec") 104 except (SyntaxError, OverflowError, ValueError), err: 105 try: 106 msg, (errorfilename, lineno, offset, line) = err 107 if not errorfilename: 108 err.args = msg, (filename, lineno, offset, line) 109 err.filename = filename 110 self.colorize_syntax_error(msg, lineno, offset) 111 except: 112 msg = "*** " + str(err) 113 self.errorbox("Syntax error", 114 "There's an error in your program:\n" + msg) 115 return False 116 finally: 117 shell.set_warning_stream(saved_stream) 118 119 def colorize_syntax_error(self, msg, lineno, offset): 120 text = self.editwin.text 121 pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1) 122 text.tag_add("ERROR", pos) 123 char = text.get(pos) 124 if char and char in IDENTCHARS: 125 text.tag_add("ERROR", pos + " wordstart", pos) 126 if '\n' == text.get(pos): # error at line end 127 text.mark_set("insert", pos) 128 else: 129 text.mark_set("insert", pos + "+1c") 130 text.see(pos) 131 132 def run_module_event(self, event): 133 """Run the module after setting up the environment. 134 135 First check the syntax. If OK, make sure the shell is active and 136 then transfer the arguments, set the run environment's working 137 directory to the directory of the module being executed and also 138 add that directory to its sys.path if not already included. 139 140 """ 141 filename = self.getfilename() 142 if not filename: 143 return 'break' 144 code = self.checksyntax(filename) 145 if not code: 146 return 'break' 147 if not self.tabnanny(filename): 148 return 'break' 149 interp = self.shell.interp 150 if PyShell.use_subprocess: 151 interp.restart_subprocess(with_cwd=False) 152 dirname = os.path.dirname(filename) 153 # XXX Too often this discards arguments the user just set... 154 interp.runcommand("""if 1: 155 _filename = %r 156 import sys as _sys 157 from os.path import basename as _basename 158 if (not _sys.argv or 159 _basename(_sys.argv[0]) != _basename(_filename)): 160 _sys.argv = [_filename] 161 import os as _os 162 _os.chdir(%r) 163 del _filename, _sys, _basename, _os 164 \n""" % (filename, dirname)) 165 interp.prepend_syspath(filename) 166 # XXX KBK 03Jul04 When run w/o subprocess, runtime warnings still 167 # go to __stderr__. With subprocess, they go to the shell. 168 # Need to change streams in PyShell.ModifiedInterpreter. 169 interp.runcode(code) 170 return 'break' 171 172 if macosxSupport.runningAsOSXApp(): 173 # Tk-Cocoa in MacOSX is broken until at least 174 # Tk 8.5.9, and without this rather 175 # crude workaround IDLE would hang when a user 176 # tries to run a module using the keyboard shortcut 177 # (the menu item works fine). 178 _run_module_event = run_module_event 179 180 def run_module_event(self, event): 181 self.editwin.text_frame.after(200, 182 lambda: self.editwin.text_frame.event_generate('<<run-module-event-2>>')) 183 return 'break' 184 185 def getfilename(self): 186 """Get source filename. If not saved, offer to save (or create) file 187 188 The debugger requires a source file. Make sure there is one, and that 189 the current version of the source buffer has been saved. If the user 190 declines to save or cancels the Save As dialog, return None. 191 192 If the user has configured IDLE for Autosave, the file will be 193 silently saved if it already exists and is dirty. 194 195 """ 196 filename = self.editwin.io.filename 197 if not self.editwin.get_saved(): 198 autosave = idleConf.GetOption('main', 'General', 199 'autosave', type='bool') 200 if autosave and filename: 201 self.editwin.io.save(None) 202 else: 203 confirm = self.ask_save_dialog() 204 self.editwin.text.focus_set() 205 if confirm: 206 self.editwin.io.save(None) 207 filename = self.editwin.io.filename 208 else: 209 filename = None 210 return filename 211 212 def ask_save_dialog(self): 213 msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?" 214 confirm = tkMessageBox.askokcancel(title="Save Before Run or Check", 215 message=msg, 216 default=tkMessageBox.OK, 217 master=self.editwin.text) 218 return confirm 219 220 def errorbox(self, title, message): 221 # XXX This should really be a function of EditorWindow... 222 tkMessageBox.showerror(title, message, master=self.editwin.text) 223 self.editwin.text.focus_set() 224