• 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 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