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