jpayne@69: """Execute code from an editor. jpayne@69: jpayne@69: Check module: do a full syntax check of the current module. jpayne@69: Also run the tabnanny to catch any inconsistent tabs. jpayne@69: jpayne@69: Run module: also execute the module's code in the __main__ namespace. jpayne@69: The window must have been saved previously. The module is added to jpayne@69: sys.modules, and is also added to the __main__ namespace. jpayne@69: jpayne@69: TODO: Specify command line arguments in a dialog box. jpayne@69: """ jpayne@69: import os jpayne@69: import tabnanny jpayne@69: import tokenize jpayne@69: jpayne@69: import tkinter.messagebox as tkMessageBox jpayne@69: jpayne@69: from idlelib.config import idleConf jpayne@69: from idlelib import macosx jpayne@69: from idlelib import pyshell jpayne@69: from idlelib.query import CustomRun jpayne@69: from idlelib import outwin jpayne@69: jpayne@69: indent_message = """Error: Inconsistent indentation detected! jpayne@69: jpayne@69: 1) Your indentation is outright incorrect (easy to fix), OR jpayne@69: jpayne@69: 2) Your indentation mixes tabs and spaces. jpayne@69: jpayne@69: To fix case 2, change all tabs to spaces by using Edit->Select All followed \ jpayne@69: by Format->Untabify Region and specify the number of columns used by each tab. jpayne@69: """ jpayne@69: jpayne@69: jpayne@69: class ScriptBinding: jpayne@69: jpayne@69: def __init__(self, editwin): jpayne@69: self.editwin = editwin jpayne@69: # Provide instance variables referenced by debugger jpayne@69: # XXX This should be done differently jpayne@69: self.flist = self.editwin.flist jpayne@69: self.root = self.editwin.root jpayne@69: # cli_args is list of strings that extends sys.argv jpayne@69: self.cli_args = [] jpayne@69: jpayne@69: if macosx.isCocoaTk(): jpayne@69: self.editwin.text_frame.bind('<>', self._run_module_event) jpayne@69: jpayne@69: def check_module_event(self, event): jpayne@69: if isinstance(self.editwin, outwin.OutputWindow): jpayne@69: self.editwin.text.bell() jpayne@69: return 'break' jpayne@69: filename = self.getfilename() jpayne@69: if not filename: jpayne@69: return 'break' jpayne@69: if not self.checksyntax(filename): jpayne@69: return 'break' jpayne@69: if not self.tabnanny(filename): jpayne@69: return 'break' jpayne@69: return "break" jpayne@69: jpayne@69: def tabnanny(self, filename): jpayne@69: # XXX: tabnanny should work on binary files as well jpayne@69: with tokenize.open(filename) as f: jpayne@69: try: jpayne@69: tabnanny.process_tokens(tokenize.generate_tokens(f.readline)) jpayne@69: except tokenize.TokenError as msg: jpayne@69: msgtxt, (lineno, start) = msg.args jpayne@69: self.editwin.gotoline(lineno) jpayne@69: self.errorbox("Tabnanny Tokenizing Error", jpayne@69: "Token Error: %s" % msgtxt) jpayne@69: return False jpayne@69: except tabnanny.NannyNag as nag: jpayne@69: # The error messages from tabnanny are too confusing... jpayne@69: self.editwin.gotoline(nag.get_lineno()) jpayne@69: self.errorbox("Tab/space error", indent_message) jpayne@69: return False jpayne@69: return True jpayne@69: jpayne@69: def checksyntax(self, filename): jpayne@69: self.shell = shell = self.flist.open_shell() jpayne@69: saved_stream = shell.get_warning_stream() jpayne@69: shell.set_warning_stream(shell.stderr) jpayne@69: with open(filename, 'rb') as f: jpayne@69: source = f.read() jpayne@69: if b'\r' in source: jpayne@69: source = source.replace(b'\r\n', b'\n') jpayne@69: source = source.replace(b'\r', b'\n') jpayne@69: if source and source[-1] != ord(b'\n'): jpayne@69: source = source + b'\n' jpayne@69: editwin = self.editwin jpayne@69: text = editwin.text jpayne@69: text.tag_remove("ERROR", "1.0", "end") jpayne@69: try: jpayne@69: # If successful, return the compiled code jpayne@69: return compile(source, filename, "exec") jpayne@69: except (SyntaxError, OverflowError, ValueError) as value: jpayne@69: msg = getattr(value, 'msg', '') or value or "" jpayne@69: lineno = getattr(value, 'lineno', '') or 1 jpayne@69: offset = getattr(value, 'offset', '') or 0 jpayne@69: if offset == 0: jpayne@69: lineno += 1 #mark end of offending line jpayne@69: pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1) jpayne@69: editwin.colorize_syntax_error(text, pos) jpayne@69: self.errorbox("SyntaxError", "%-20s" % msg) jpayne@69: return False jpayne@69: finally: jpayne@69: shell.set_warning_stream(saved_stream) jpayne@69: jpayne@69: def run_module_event(self, event): jpayne@69: if macosx.isCocoaTk(): jpayne@69: # Tk-Cocoa in MacOSX is broken until at least jpayne@69: # Tk 8.5.9, and without this rather jpayne@69: # crude workaround IDLE would hang when a user jpayne@69: # tries to run a module using the keyboard shortcut jpayne@69: # (the menu item works fine). jpayne@69: self.editwin.text_frame.after(200, jpayne@69: lambda: self.editwin.text_frame.event_generate( jpayne@69: '<>')) jpayne@69: return 'break' jpayne@69: else: jpayne@69: return self._run_module_event(event) jpayne@69: jpayne@69: def run_custom_event(self, event): jpayne@69: return self._run_module_event(event, customize=True) jpayne@69: jpayne@69: def _run_module_event(self, event, *, customize=False): jpayne@69: """Run the module after setting up the environment. jpayne@69: jpayne@69: First check the syntax. Next get customization. If OK, make jpayne@69: sure the shell is active and then transfer the arguments, set jpayne@69: the run environment's working directory to the directory of the jpayne@69: module being executed and also add that directory to its jpayne@69: sys.path if not already included. jpayne@69: """ jpayne@69: if isinstance(self.editwin, outwin.OutputWindow): jpayne@69: self.editwin.text.bell() jpayne@69: return 'break' jpayne@69: filename = self.getfilename() jpayne@69: if not filename: jpayne@69: return 'break' jpayne@69: code = self.checksyntax(filename) jpayne@69: if not code: jpayne@69: return 'break' jpayne@69: if not self.tabnanny(filename): jpayne@69: return 'break' jpayne@69: if customize: jpayne@69: title = f"Customize {self.editwin.short_title()} Run" jpayne@69: run_args = CustomRun(self.shell.text, title, jpayne@69: cli_args=self.cli_args).result jpayne@69: if not run_args: # User cancelled. jpayne@69: return 'break' jpayne@69: self.cli_args, restart = run_args if customize else ([], True) jpayne@69: interp = self.shell.interp jpayne@69: if pyshell.use_subprocess and restart: jpayne@69: interp.restart_subprocess( jpayne@69: with_cwd=False, filename=filename) jpayne@69: dirname = os.path.dirname(filename) jpayne@69: argv = [filename] jpayne@69: if self.cli_args: jpayne@69: argv += self.cli_args jpayne@69: interp.runcommand(f"""if 1: jpayne@69: __file__ = {filename!r} jpayne@69: import sys as _sys jpayne@69: from os.path import basename as _basename jpayne@69: argv = {argv!r} jpayne@69: if (not _sys.argv or jpayne@69: _basename(_sys.argv[0]) != _basename(__file__) or jpayne@69: len(argv) > 1): jpayne@69: _sys.argv = argv jpayne@69: import os as _os jpayne@69: _os.chdir({dirname!r}) jpayne@69: del _sys, argv, _basename, _os jpayne@69: \n""") jpayne@69: interp.prepend_syspath(filename) jpayne@69: # XXX KBK 03Jul04 When run w/o subprocess, runtime warnings still jpayne@69: # go to __stderr__. With subprocess, they go to the shell. jpayne@69: # Need to change streams in pyshell.ModifiedInterpreter. jpayne@69: interp.runcode(code) jpayne@69: return 'break' jpayne@69: jpayne@69: def getfilename(self): jpayne@69: """Get source filename. If not saved, offer to save (or create) file jpayne@69: jpayne@69: The debugger requires a source file. Make sure there is one, and that jpayne@69: the current version of the source buffer has been saved. If the user jpayne@69: declines to save or cancels the Save As dialog, return None. jpayne@69: jpayne@69: If the user has configured IDLE for Autosave, the file will be jpayne@69: silently saved if it already exists and is dirty. jpayne@69: jpayne@69: """ jpayne@69: filename = self.editwin.io.filename jpayne@69: if not self.editwin.get_saved(): jpayne@69: autosave = idleConf.GetOption('main', 'General', jpayne@69: 'autosave', type='bool') jpayne@69: if autosave and filename: jpayne@69: self.editwin.io.save(None) jpayne@69: else: jpayne@69: confirm = self.ask_save_dialog() jpayne@69: self.editwin.text.focus_set() jpayne@69: if confirm: jpayne@69: self.editwin.io.save(None) jpayne@69: filename = self.editwin.io.filename jpayne@69: else: jpayne@69: filename = None jpayne@69: return filename jpayne@69: jpayne@69: def ask_save_dialog(self): jpayne@69: msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?" jpayne@69: confirm = tkMessageBox.askokcancel(title="Save Before Run or Check", jpayne@69: message=msg, jpayne@69: default=tkMessageBox.OK, jpayne@69: parent=self.editwin.text) jpayne@69: return confirm jpayne@69: jpayne@69: def errorbox(self, title, message): jpayne@69: # XXX This should really be a function of EditorWindow... jpayne@69: tkMessageBox.showerror(title, message, parent=self.editwin.text) jpayne@69: self.editwin.text.focus_set() jpayne@69: jpayne@69: jpayne@69: if __name__ == "__main__": jpayne@69: from unittest import main jpayne@69: main('idlelib.idle_test.test_runscript', verbosity=2,)