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