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