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