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