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