ScriptBinding.py 7.86 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
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
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 57 58
        if macosxSupport.runningAsOSXApp():
            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
        f = open(filename, 'rb')
David Scherer's avatar
David Scherer committed
91 92
        source = f.read()
        f.close()
93 94 95 96 97
        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'
98 99
        editwin = self.editwin
        text = editwin.text
100
        text.tag_remove("ERROR", "1.0", "end")
David Scherer's avatar
David Scherer committed
101
        try:
102 103
            # If successful, return the compiled code
            return compile(source, filename, "exec")
104 105 106 107
        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
108 109 110 111 112 113
            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
114 115
        finally:
            shell.set_warning_stream(saved_stream)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
116

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
117
    def run_module_event(self, event):
118 119 120 121 122 123 124 125 126 127 128 129 130
        if macosxSupport.runningAsOSXApp():
            # 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
131 132 133 134 135 136 137
        """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.
        """
138

139 140
        filename = self.getfilename()
        if not filename:
Christian Heimes's avatar
Christian Heimes committed
141
            return 'break'
142
        code = self.checksyntax(filename)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
143
        if not code:
Christian Heimes's avatar
Christian Heimes committed
144
            return 'break'
145
        if not self.tabnanny(filename):
Christian Heimes's avatar
Christian Heimes committed
146
            return 'break'
147
        interp = self.shell.interp
148
        if PyShell.use_subprocess:
149
            interp.restart_subprocess(with_cwd=False)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
150
        dirname = os.path.dirname(filename)
Chui Tey's avatar
Chui Tey committed
151 152
        # XXX Too often this discards arguments the user just set...
        interp.runcommand("""if 1:
153
            _filename = %r
Chui Tey's avatar
Chui Tey committed
154 155 156 157 158
            import sys as _sys
            from os.path import basename as _basename
            if (not _sys.argv or
                _basename(_sys.argv[0]) != _basename(_filename)):
                _sys.argv = [_filename]
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
159
            import os as _os
160
            _os.chdir(%r)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
161
            del _filename, _sys, _basename, _os
162
            \n""" % (filename, dirname))
163
        interp.prepend_syspath(filename)
164 165 166
        # 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
167
        interp.runcode(code)
Christian Heimes's avatar
Christian Heimes committed
168
        return 'break'
David Scherer's avatar
David Scherer committed
169 170

    def getfilename(self):
171 172 173 174 175
        """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.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
176 177 178

        If the user has configured IDLE for Autosave, the file will be
        silently saved if it already exists and is dirty.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
179

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

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

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