|
1 """Extension to execute code outside the Python shell window. |
|
2 |
|
3 This 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 |
|
12 XXX 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 |
|
20 import os |
|
21 import re |
|
22 import string |
|
23 import tabnanny |
|
24 import tokenize |
|
25 import tkMessageBox |
|
26 import PyShell |
|
27 |
|
28 from configHandler import idleConf |
|
29 |
|
30 IDENTCHARS = string.ascii_letters + string.digits + "_" |
|
31 |
|
32 indent_message = """Error: Inconsistent indentation detected! |
|
33 |
|
34 1) Your indentation is outright incorrect (easy to fix), OR |
|
35 |
|
36 2) Your indentation mixes tabs and spaces. |
|
37 |
|
38 To fix case 2, change all tabs to spaces by using Edit->Select All followed \ |
|
39 by Format->Untabify Region and specify the number of columns used by each tab. |
|
40 """ |
|
41 |
|
42 class ScriptBinding: |
|
43 |
|
44 menudefs = [ |
|
45 ('run', [None, |
|
46 ('Check Module', '<<check-module>>'), |
|
47 ('Run Module', '<<run-module>>'), ]), ] |
|
48 |
|
49 def __init__(self, editwin): |
|
50 self.editwin = editwin |
|
51 # Provide instance variables referenced by Debugger |
|
52 # XXX This should be done differently |
|
53 self.flist = self.editwin.flist |
|
54 self.root = self.editwin.root |
|
55 |
|
56 def check_module_event(self, event): |
|
57 filename = self.getfilename() |
|
58 if not filename: |
|
59 return |
|
60 if not self.checksyntax(filename): |
|
61 return |
|
62 if not self.tabnanny(filename): |
|
63 return |
|
64 |
|
65 def tabnanny(self, filename): |
|
66 f = open(filename, 'r') |
|
67 try: |
|
68 tabnanny.process_tokens(tokenize.generate_tokens(f.readline)) |
|
69 except tokenize.TokenError, msg: |
|
70 msgtxt, (lineno, start) = msg |
|
71 self.editwin.gotoline(lineno) |
|
72 self.errorbox("Tabnanny Tokenizing Error", |
|
73 "Token Error: %s" % msgtxt) |
|
74 return False |
|
75 except tabnanny.NannyNag, nag: |
|
76 # The error messages from tabnanny are too confusing... |
|
77 self.editwin.gotoline(nag.get_lineno()) |
|
78 self.errorbox("Tab/space error", indent_message) |
|
79 return False |
|
80 return True |
|
81 |
|
82 def checksyntax(self, filename): |
|
83 self.shell = shell = self.flist.open_shell() |
|
84 saved_stream = shell.get_warning_stream() |
|
85 shell.set_warning_stream(shell.stderr) |
|
86 f = open(filename, 'r') |
|
87 source = f.read() |
|
88 f.close() |
|
89 if '\r' in source: |
|
90 source = re.sub(r"\r\n", "\n", source) |
|
91 source = re.sub(r"\r", "\n", source) |
|
92 if source and source[-1] != '\n': |
|
93 source = source + '\n' |
|
94 text = self.editwin.text |
|
95 text.tag_remove("ERROR", "1.0", "end") |
|
96 try: |
|
97 try: |
|
98 # If successful, return the compiled code |
|
99 return compile(source, filename, "exec") |
|
100 except (SyntaxError, OverflowError), err: |
|
101 try: |
|
102 msg, (errorfilename, lineno, offset, line) = err |
|
103 if not errorfilename: |
|
104 err.args = msg, (filename, lineno, offset, line) |
|
105 err.filename = filename |
|
106 self.colorize_syntax_error(msg, lineno, offset) |
|
107 except: |
|
108 msg = "*** " + str(err) |
|
109 self.errorbox("Syntax error", |
|
110 "There's an error in your program:\n" + msg) |
|
111 return False |
|
112 finally: |
|
113 shell.set_warning_stream(saved_stream) |
|
114 |
|
115 def colorize_syntax_error(self, msg, lineno, offset): |
|
116 text = self.editwin.text |
|
117 pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1) |
|
118 text.tag_add("ERROR", pos) |
|
119 char = text.get(pos) |
|
120 if char and char in IDENTCHARS: |
|
121 text.tag_add("ERROR", pos + " wordstart", pos) |
|
122 if '\n' == text.get(pos): # error at line end |
|
123 text.mark_set("insert", pos) |
|
124 else: |
|
125 text.mark_set("insert", pos + "+1c") |
|
126 text.see(pos) |
|
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 |
|
140 code = self.checksyntax(filename) |
|
141 if not code: |
|
142 return |
|
143 if not self.tabnanny(filename): |
|
144 return |
|
145 shell = self.shell |
|
146 interp = shell.interp |
|
147 if PyShell.use_subprocess: |
|
148 shell.restart_shell() |
|
149 dirname = os.path.dirname(filename) |
|
150 # XXX Too often this discards arguments the user just set... |
|
151 interp.runcommand("""if 1: |
|
152 _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(_filename)): |
|
157 _sys.argv = [_filename] |
|
158 import os as _os |
|
159 _os.chdir(%r) |
|
160 del _filename, _sys, _basename, _os |
|
161 \n""" % (filename, 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 |
|
168 def getfilename(self): |
|
169 """Get source filename. If not saved, offer to save (or create) file |
|
170 |
|
171 The debugger requires a source file. Make sure there is one, and that |
|
172 the current version of the source buffer has been saved. If the user |
|
173 declines to save or cancels the Save As dialog, return None. |
|
174 |
|
175 If the user has configured IDLE for Autosave, the file will be |
|
176 silently saved if it already exists and is dirty. |
|
177 |
|
178 """ |
|
179 filename = self.editwin.io.filename |
|
180 if not self.editwin.get_saved(): |
|
181 autosave = idleConf.GetOption('main', 'General', |
|
182 'autosave', type='bool') |
|
183 if autosave and filename: |
|
184 self.editwin.io.save(None) |
|
185 else: |
|
186 reply = self.ask_save_dialog() |
|
187 self.editwin.text.focus_set() |
|
188 if reply == "ok": |
|
189 self.editwin.io.save(None) |
|
190 filename = self.editwin.io.filename |
|
191 else: |
|
192 filename = None |
|
193 return filename |
|
194 |
|
195 def ask_save_dialog(self): |
|
196 msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?" |
|
197 mb = tkMessageBox.Message(title="Save Before Run or Check", |
|
198 message=msg, |
|
199 icon=tkMessageBox.QUESTION, |
|
200 type=tkMessageBox.OKCANCEL, |
|
201 default=tkMessageBox.OK, |
|
202 master=self.editwin.text) |
|
203 return mb.show() |
|
204 |
|
205 def errorbox(self, title, message): |
|
206 # XXX This should really be a function of EditorWindow... |
|
207 tkMessageBox.showerror(title, message, master=self.editwin.text) |
|
208 self.editwin.text.focus_set() |