ScriptBinding.py 7.87 KB
Newer Older
David Scherer's avatar
David Scherer committed
1 2
"""Extension to execute code outside the Python shell window.

3
This adds the following commands:
David Scherer's avatar
David Scherer committed
4

5
- Check module does a full syntax check of the current module.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
6
  It also runs the tabnanny to catch any inconsistent tabs.
David Scherer's avatar
David Scherer committed
7

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
8
- Run module executes the module's code in the __main__ namespace.  The window
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
9 10
  must have been saved previously. The module is added to sys.modules, and is
  also added to the __main__ namespace.
David Scherer's avatar
David Scherer committed
11

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
12
XXX GvR Redesign this interface (yet again) as follows:
Chui Tey's avatar
Chui Tey committed
13

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
14
- Present a dialog box for ``Run Module''
Chui Tey's avatar
Chui Tey committed
15 16 17

- Allow specify command line arguments in the dialog box

David Scherer's avatar
David Scherer committed
18 19
"""

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
20
import os
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
21 22 23 24
import re
import string
import tabnanny
import tokenize
25
import tkinter.messagebox as tkMessageBox
26
from idlelib.EditorWindow import EditorWindow
27
from idlelib import PyShell, IOBinding
David Scherer's avatar
David Scherer committed
28

29
from idlelib.configHandler import idleConf
30
from idlelib import macosxSupport
31

David Scherer's avatar
David Scherer committed
32 33
indent_message = """Error: Inconsistent indentation detected!

34
1) Your indentation is outright incorrect (easy to fix), OR
David Scherer's avatar
David Scherer committed
35

36
2) Your indentation mixes tabs and spaces.
David Scherer's avatar
David Scherer committed
37

38 39 40
To fix case 2, change all tabs to spaces by using Edit->Select All followed \
by Format->Untabify Region and specify the number of columns used by each tab.
"""
41

David Scherer's avatar
David Scherer committed
42
class ScriptBinding:
43

David Scherer's avatar
David Scherer committed
44
    menudefs = [
45
        ('run', [None,
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
46
                 ('Check Module', '<<check-module>>'),
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
47
                 ('Run Module', '<<run-module>>'), ]), ]
David Scherer's avatar
David Scherer committed
48 49 50 51 52 53

    def __init__(self, editwin):
        self.editwin = editwin
        # Provide instance variables referenced by Debugger
        # XXX This should be done differently
        self.flist = self.editwin.flist
54
        self.root = self.editwin.root
David Scherer's avatar
David Scherer committed
55

56
        if macosxSupport.isCocoaTk():
57 58
            self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event)

59
    def check_module_event(self, event):
David Scherer's avatar
David Scherer committed
60 61
        filename = self.getfilename()
        if not filename:
Christian Heimes's avatar
Christian Heimes committed
62
            return 'break'
63
        if not self.checksyntax(filename):
Christian Heimes's avatar
Christian Heimes committed
64
            return 'break'
David Scherer's avatar
David Scherer committed
65
        if not self.tabnanny(filename):
Christian Heimes's avatar
Christian Heimes committed
66
            return 'break'
David Scherer's avatar
David Scherer committed
67 68

    def tabnanny(self, filename):
69
        # XXX: tabnanny should work on binary files as well
70 71 72 73 74 75 76 77 78 79 80 81 82 83
        with tokenize.open(filename) as f:
            try:
                tabnanny.process_tokens(tokenize.generate_tokens(f.readline))
            except tokenize.TokenError as msg:
                msgtxt, (lineno, start) = msg
                self.editwin.gotoline(lineno)
                self.errorbox("Tabnanny Tokenizing Error",
                              "Token Error: %s" % msgtxt)
                return False
            except tabnanny.NannyNag as nag:
                # The error messages from tabnanny are too confusing...
                self.editwin.gotoline(nag.get_lineno())
                self.errorbox("Tab/space error", indent_message)
                return False
Neal Norwitz's avatar
Neal Norwitz committed
84
        return True
David Scherer's avatar
David Scherer committed
85 86

    def checksyntax(self, filename):
87 88 89
        self.shell = shell = self.flist.open_shell()
        saved_stream = shell.get_warning_stream()
        shell.set_warning_stream(shell.stderr)
90 91
        with open(filename, 'rb') as f:
            source = f.read()
92 93 94 95 96
        if b'\r' in source:
            source = source.replace(b'\r\n', b'\n')
            source = source.replace(b'\r', b'\n')
        if source and source[-1] != ord(b'\n'):
            source = source + b'\n'
97 98
        editwin = self.editwin
        text = editwin.text
99
        text.tag_remove("ERROR", "1.0", "end")
David Scherer's avatar
David Scherer committed
100
        try:
101 102
            # If successful, return the compiled code
            return compile(source, filename, "exec")
103 104 105 106
        except (SyntaxError, OverflowError, ValueError) as value:
            msg = getattr(value, 'msg', '') or value or "<no detail available>"
            lineno = getattr(value, 'lineno', '') or 1
            offset = getattr(value, 'offset', '') or 0
107 108 109 110 111 112
            if offset == 0:
                lineno += 1  #mark end of offending line
            pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1)
            editwin.colorize_syntax_error(text, pos)
            self.errorbox("SyntaxError", "%-20s" % msg)
            return False
113 114
        finally:
            shell.set_warning_stream(saved_stream)
115

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
116
    def run_module_event(self, event):
117
        if macosxSupport.isCocoaTk():
118 119 120 121 122 123 124 125 126 127 128 129
            # Tk-Cocoa in MacOSX is broken until at least
            # Tk 8.5.9, and without this rather
            # crude workaround IDLE would hang when a user
            # tries to run a module using the keyboard shortcut
            # (the menu item works fine).
            self.editwin.text_frame.after(200,
                lambda: self.editwin.text_frame.event_generate('<<run-module-event-2>>'))
            return 'break'
        else:
            return self._run_module_event(event)

    def _run_module_event(self, event):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
130 131 132 133 134 135 136
        """Run the module after setting up the environment.

        First check the syntax.  If OK, make sure the shell is active and
        then transfer the arguments, set the run environment's working
        directory to the directory of the module being executed and also
        add that directory to its sys.path if not already included.
        """
137

138 139
        filename = self.getfilename()
        if not filename:
Christian Heimes's avatar
Christian Heimes committed
140
            return 'break'
141
        code = self.checksyntax(filename)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
142
        if not code:
Christian Heimes's avatar
Christian Heimes committed
143
            return 'break'
144
        if not self.tabnanny(filename):
Christian Heimes's avatar
Christian Heimes committed
145
            return 'break'
146
        interp = self.shell.interp
147
        if PyShell.use_subprocess:
148
            interp.restart_subprocess(with_cwd=False)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
149
        dirname = os.path.dirname(filename)
Chui Tey's avatar
Chui Tey committed
150 151
        # XXX Too often this discards arguments the user just set...
        interp.runcommand("""if 1:
152
            __file__ = {filename!r}
Chui Tey's avatar
Chui Tey committed
153 154 155
            import sys as _sys
            from os.path import basename as _basename
            if (not _sys.argv or
156 157
                _basename(_sys.argv[0]) != _basename(__file__)):
                _sys.argv = [__file__]
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
158
            import os as _os
159 160 161
            _os.chdir({dirname!r})
            del _sys, _basename, _os
            \n""".format(filename=filename, dirname=dirname))
162
        interp.prepend_syspath(filename)
163 164 165
        # XXX KBK 03Jul04 When run w/o subprocess, runtime warnings still
        #         go to __stderr__.  With subprocess, they go to the shell.
        #         Need to change streams in PyShell.ModifiedInterpreter.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
166
        interp.runcode(code)
Christian Heimes's avatar
Christian Heimes committed
167
        return 'break'
David Scherer's avatar
David Scherer committed
168 169

    def getfilename(self):
170 171 172 173 174
        """Get source filename.  If not saved, offer to save (or create) file

        The debugger requires a source file.  Make sure there is one, and that
        the current version of the source buffer has been saved.  If the user
        declines to save or cancels the Save As dialog, return None.
175 176 177

        If the user has configured IDLE for Autosave, the file will be
        silently saved if it already exists and is dirty.
178

179
        """
180
        filename = self.editwin.io.filename
David Scherer's avatar
David Scherer committed
181
        if not self.editwin.get_saved():
182 183 184
            autosave = idleConf.GetOption('main', 'General',
                                          'autosave', type='bool')
            if autosave and filename:
185 186
                self.editwin.io.save(None)
            else:
187
                confirm = self.ask_save_dialog()
188
                self.editwin.text.focus_set()
189
                if confirm:
190 191 192 193
                    self.editwin.io.save(None)
                    filename = self.editwin.io.filename
                else:
                    filename = None
David Scherer's avatar
David Scherer committed
194 195
        return filename

196 197
    def ask_save_dialog(self):
        msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?"
198 199 200 201 202
        confirm = tkMessageBox.askokcancel(title="Save Before Run or Check",
                                           message=msg,
                                           default=tkMessageBox.OK,
                                           master=self.editwin.text)
        return confirm
203

David Scherer's avatar
David Scherer committed
204 205
    def errorbox(self, title, message):
        # XXX This should really be a function of EditorWindow...
206
        tkMessageBox.showerror(title, message, master=self.editwin.text)
David Scherer's avatar
David Scherer committed
207
        self.editwin.text.focus_set()