• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Extension to execute code outside the Python shell window.
2
3This adds the following commands:
4
5- Check module does a full syntax check of the current module.
6  It also runs the tabnanny to catch any inconsistent tabs.
7
8- Run module executes the module's code in the __main__ namespace.  The window
9  must have been saved previously. The module is added to sys.modules, and is
10  also added to the __main__ namespace.
11
12XXX GvR Redesign this interface (yet again) as follows:
13
14- Present a dialog box for ``Run Module''
15
16- Allow specify command line arguments in the dialog box
17
18"""
19
20import os
21import tabnanny
22import tokenize
23
24import tkinter.messagebox as tkMessageBox
25
26from idlelib.config import idleConf
27from idlelib import macosx
28from idlelib import pyshell
29
30indent_message = """Error: Inconsistent indentation detected!
31
321) Your indentation is outright incorrect (easy to fix), OR
33
342) Your indentation mixes tabs and spaces.
35
36To fix case 2, change all tabs to spaces by using Edit->Select All followed \
37by Format->Untabify Region and specify the number of columns used by each tab.
38"""
39
40
41class ScriptBinding:
42
43    menudefs = [
44        ('run', [None,
45                 ('Check Module', '<<check-module>>'),
46                 ('Run Module', '<<run-module>>'), ]), ]
47
48    def __init__(self, editwin):
49        self.editwin = editwin
50        # Provide instance variables referenced by debugger
51        # XXX This should be done differently
52        self.flist = self.editwin.flist
53        self.root = self.editwin.root
54
55        if macosx.isCocoaTk():
56            self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event)
57
58    def check_module_event(self, event):
59        filename = self.getfilename()
60        if not filename:
61            return 'break'
62        if not self.checksyntax(filename):
63            return 'break'
64        if not self.tabnanny(filename):
65            return 'break'
66
67    def tabnanny(self, filename):
68        # XXX: tabnanny should work on binary files as well
69        with tokenize.open(filename) as f:
70            try:
71                tabnanny.process_tokens(tokenize.generate_tokens(f.readline))
72            except tokenize.TokenError as msg:
73                msgtxt, (lineno, start) = msg.args
74                self.editwin.gotoline(lineno)
75                self.errorbox("Tabnanny Tokenizing Error",
76                              "Token Error: %s" % msgtxt)
77                return False
78            except tabnanny.NannyNag as nag:
79                # The error messages from tabnanny are too confusing...
80                self.editwin.gotoline(nag.get_lineno())
81                self.errorbox("Tab/space error", indent_message)
82                return False
83        return True
84
85    def checksyntax(self, filename):
86        self.shell = shell = self.flist.open_shell()
87        saved_stream = shell.get_warning_stream()
88        shell.set_warning_stream(shell.stderr)
89        with open(filename, 'rb') as f:
90            source = f.read()
91        if b'\r' in source:
92            source = source.replace(b'\r\n', b'\n')
93            source = source.replace(b'\r', b'\n')
94        if source and source[-1] != ord(b'\n'):
95            source = source + b'\n'
96        editwin = self.editwin
97        text = editwin.text
98        text.tag_remove("ERROR", "1.0", "end")
99        try:
100            # If successful, return the compiled code
101            return compile(source, filename, "exec")
102        except (SyntaxError, OverflowError, ValueError) as value:
103            msg = getattr(value, 'msg', '') or value or "<no detail available>"
104            lineno = getattr(value, 'lineno', '') or 1
105            offset = getattr(value, 'offset', '') or 0
106            if offset == 0:
107                lineno += 1  #mark end of offending line
108            pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1)
109            editwin.colorize_syntax_error(text, pos)
110            self.errorbox("SyntaxError", "%-20s" % msg)
111            return False
112        finally:
113            shell.set_warning_stream(saved_stream)
114
115    def run_module_event(self, event):
116        if macosx.isCocoaTk():
117            # Tk-Cocoa in MacOSX is broken until at least
118            # Tk 8.5.9, and without this rather
119            # crude workaround IDLE would hang when a user
120            # tries to run a module using the keyboard shortcut
121            # (the menu item works fine).
122            self.editwin.text_frame.after(200,
123                lambda: self.editwin.text_frame.event_generate('<<run-module-event-2>>'))
124            return 'break'
125        else:
126            return self._run_module_event(event)
127
128    def _run_module_event(self, event):
129        """Run the module after setting up the environment.
130
131        First check the syntax.  If OK, make sure the shell is active and
132        then transfer the arguments, set the run environment's working
133        directory to the directory of the module being executed and also
134        add that directory to its sys.path if not already included.
135        """
136
137        filename = self.getfilename()
138        if not filename:
139            return 'break'
140        code = self.checksyntax(filename)
141        if not code:
142            return 'break'
143        if not self.tabnanny(filename):
144            return 'break'
145        interp = self.shell.interp
146        if pyshell.use_subprocess:
147            interp.restart_subprocess(with_cwd=False, filename=
148                        self.editwin._filename_to_unicode(filename))
149        dirname = os.path.dirname(filename)
150        # XXX Too often this discards arguments the user just set...
151        interp.runcommand("""if 1:
152            __file__ = {filename!r}
153            import sys as _sys
154            from os.path import basename as _basename
155            if (not _sys.argv or
156                _basename(_sys.argv[0]) != _basename(__file__)):
157                _sys.argv = [__file__]
158            import os as _os
159            _os.chdir({dirname!r})
160            del _sys, _basename, _os
161            \n""".format(filename=filename, dirname=dirname))
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 = tkMessageBox.askokcancel(title="Save Before Run or Check",
199                                           message=msg,
200                                           default=tkMessageBox.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        tkMessageBox.showerror(title, message, parent=self.editwin.text)
207        self.editwin.text.focus_set()
208