]>
Commit | Line | Data |
---|---|---|
d14a1e28 | 1 | # shell.py |
b881fc78 RD |
2 | #---------------------------------------------------------------------- |
3 | # 12/10/2003 - Jeff Grimmett (grimmtooth@softhome.net) | |
4 | # | |
5 | # o 2.5 compatability update. | |
6 | # o Added deprecation warning. | |
7 | # | |
8 | ||
d14a1e28 | 9 | """wxPython interactive shell |
1fded56b | 10 | |
d14a1e28 RD |
11 | Copyright (c) 1999 SIA "ANK" |
12 | ||
13 | this module is free software. it may be used under same terms as Python itself | |
14 | ||
15 | Notes: | |
16 | i would like to use command completion (see rlcompleter library module), | |
17 | but i cannot load it because i don't have readline... | |
18 | ||
19 | History: | |
20 | 03-oct-1999 [als] created | |
21 | 04-oct-1999 [als] PyShellOutput.intro moved from __init__ parameters | |
22 | to class attributes; html debug disabled | |
23 | 04-oct-1999 [als] fixed bug with class attributes | |
24 | input prompts and output styles added to customized demo | |
25 | some html cleanups | |
26 | 04-oct-1999 [rpd] Changed to use the new sizers | |
27 | 05-oct-1999 [als] changes inspired by code.InteractiveInterpreter() | |
28 | from Python Library. if i knew about this class earlier, | |
29 | i would rather inherit from it. | |
30 | renamed to wxPyShell.py since i've renounced the 8.3 scheme | |
31 | ||
32 | 8-10-2001 THIS MODULE IS NOW DEPRECATED. Please see the most excellent | |
33 | PyCrust package instead. | |
34 | ||
35 | """ | |
36 | __version__ ="$Revision$" | |
37 | # $RCSfile$ | |
38 | ||
b881fc78 RD |
39 | import code |
40 | import sys | |
41 | import traceback | |
42 | import warnings | |
43 | ||
44 | import wx | |
45 | import wx.html | |
46 | ||
47 | warningmsg = r"""\ | |
48 | ||
49 | ########################################\ | |
50 | # THIS MODULE IS NOW DEPRECATED | | |
51 | # | | |
52 | # Please see the most excellent PyCrust | | |
53 | # package instead. | | |
54 | ########################################/ | |
d14a1e28 | 55 | |
b881fc78 RD |
56 | """ |
57 | ||
58 | warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) | |
59 | ||
60 | #---------------------------------------------------------------------- | |
d14a1e28 | 61 | |
b881fc78 | 62 | class PyShellInput(wx.Panel): |
d14a1e28 RD |
63 | """PyShell input window |
64 | ||
65 | """ | |
66 | PS1 =" Enter Command:" | |
67 | PS2 ="... continue:" | |
68 | def __init__(self, parent, shell, id=-1): | |
69 | """Create input window | |
70 | ||
71 | shell must be a PyShell object. | |
72 | it is used for exception handling, eval() namespaces, | |
73 | and shell.output is used for output | |
74 | (print's go to overridden stdout) | |
75 | """ | |
b881fc78 | 76 | wx.Panel.__init__(self, parent, id) |
d14a1e28 RD |
77 | self.shell =shell |
78 | # make a private copy of class attrs | |
79 | self.PS1 =PyShellInput.PS1 | |
80 | self.PS2 =PyShellInput.PS2 | |
81 | # create controls | |
b881fc78 RD |
82 | self.label =wx.StaticText(self, -1, self.PS1) |
83 | tid =wx.NewId() | |
84 | self.entry =wx.TextCtrl(self, tid, style = wx.TE_MULTILINE) | |
85 | self.entry.Bind(wx.EVT_CHAR, self.OnChar) | |
86 | self.entry.SetFont(wx.Font(9, wx.MODERN, wx.NORMAL, wx.NORMAL, False)) | |
87 | sizer =wx.BoxSizer(wx.VERTICAL) | |
88 | sizer.AddMany([(self.label, 0, wx.EXPAND), (self.entry, 1, wx.EXPAND)]) | |
d14a1e28 RD |
89 | self.SetSizer(sizer) |
90 | self.SetAutoLayout(True) | |
b881fc78 | 91 | self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) |
d14a1e28 RD |
92 | # when in "continuation" mode, |
93 | # two consecutive newlines are required | |
94 | # to avoid execution of unfinished block | |
95 | self.first_line =1 | |
96 | ||
97 | def OnSetFocus(self, event): | |
98 | self.entry.SetFocus() | |
99 | ||
100 | ||
101 | def Clear(self, event=None): | |
102 | """reset input state""" | |
103 | self.label.SetLabel(self.PS1) | |
104 | self.label.Refresh() | |
105 | self.entry.SetSelection(0, self.entry.GetLastPosition()) | |
106 | self.first_line =1 | |
107 | # self.entry.SetFocus() | |
108 | ||
109 | def OnChar(self, event): | |
110 | """called on CHARevent. executes input on newline""" | |
111 | # print "On Char:", event.__dict__.keys() | |
b881fc78 | 112 | if event.KeyCode() !=wx.WXK_RETURN: |
d14a1e28 RD |
113 | # not of our business |
114 | event.Skip() | |
115 | return | |
116 | text =self.entry.GetValue() | |
117 | # weird CRLF thingy | |
118 | text = text.replace("\r\n", "\n") | |
119 | # see if we've finished | |
120 | if (not (self.first_line or text[-1] =="\n") # in continuation mode | |
121 | or (text[-1] =="\\") # escaped newline | |
122 | ): | |
123 | # XXX should escaped newline put myself i "continuation" mode? | |
124 | event.Skip() | |
125 | return | |
126 | # ok, we can try to execute this | |
127 | rc =self.shell.TryExec(text) | |
128 | if rc: | |
129 | # code is incomplete; continue input | |
130 | if self.first_line: | |
131 | self.label.SetLabel(self.PS2) | |
132 | self.label.Refresh() | |
133 | self.first_line =0 | |
134 | event.Skip() | |
135 | else: | |
136 | self.Clear() | |
137 | ||
b881fc78 | 138 | class PyShellOutput(wx.Panel): |
d14a1e28 RD |
139 | """PyShell output window |
140 | ||
141 | for now, it is based on simple wxTextCtrl, | |
142 | but i'm looking at HTML classes to provide colorized output | |
143 | """ | |
144 | # attributes for for different (input, output, exception) display styles: | |
145 | # begin tag, end tag, newline | |
146 | in_style =(" <font color=\"#000080\"><tt>>>> ", | |
147 | "</tt></font><br>\n", "<br>\n... ") | |
148 | out_style =("<tt>", "</tt>\n", "<br>\n") | |
149 | exc_style =("<font color=\"#FF0000\"><tt>", | |
150 | "</tt></font>\n", "<br>\n") | |
151 | intro ="<H3>wxPython Interactive Shell</H3>\n" | |
152 | html_debug =0 | |
153 | # entity references | |
154 | erefs =(("&", "&"), (">", ">"), ("<", "<"), (" ", " ")) | |
155 | def __init__(self, parent, id=-1): | |
b881fc78 | 156 | wx.Panel.__init__(self, parent, id) |
d14a1e28 RD |
157 | # make a private copy of class attrs |
158 | self.in_style =PyShellOutput.in_style | |
159 | self.out_style =PyShellOutput.out_style | |
160 | self.exc_style =PyShellOutput.exc_style | |
161 | self.intro =PyShellOutput.intro | |
162 | self.html_debug =PyShellOutput.html_debug | |
163 | # create windows | |
164 | if self.html_debug: | |
165 | # this was used in html debugging, | |
166 | # but i don't want to delete it; it's funny | |
b881fc78 RD |
167 | splitter =wx.SplitterWindow(self, -1) |
168 | self.view =wx.TextCtrl(splitter, -1, | |
169 | style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL) | |
170 | self.html =wx.html.HtmlWindow(splitter) | |
d14a1e28 RD |
171 | splitter.SplitVertically(self.view, self.html) |
172 | splitter.SetSashPosition(40) | |
173 | splitter.SetMinimumPaneSize(3) | |
174 | self.client =splitter | |
175 | else: | |
176 | self.view =None | |
b881fc78 | 177 | self.html =wx.html.HtmlWindow(self) |
d14a1e28 RD |
178 | self.client =self.html # used in OnSize() |
179 | self.text =self.intro | |
180 | self.html.SetPage(self.text) | |
181 | self.html.SetAutoLayout(True) | |
182 | self.line_buffer ="" | |
183 | # refreshes are annoying | |
184 | self.in_batch =0 | |
185 | self.dirty =0 | |
b881fc78 RD |
186 | self.Bind(wx.EVT_SIZE, self.OnSize) |
187 | self.Bind(wx.EVT_IDLE, self.OnIdle) | |
d14a1e28 RD |
188 | |
189 | def OnSize(self, event): | |
190 | self.client.SetSize(self.GetClientSize()) | |
191 | ||
192 | def OnIdle(self, event): | |
193 | """when there's nothing to do, we can update display""" | |
194 | if self.in_batch and self.dirty: self.UpdWindow() | |
195 | ||
196 | def BeginBatch(self): | |
197 | """do not refresh display till EndBatch()""" | |
198 | self.in_batch =1 | |
199 | ||
200 | def EndBatch(self): | |
201 | """end batch; start updating display immediately""" | |
202 | self.in_batch =0 | |
203 | if self.dirty: self.UpdWindow() | |
204 | ||
205 | def UpdWindow(self): | |
206 | """sync display with text buffer""" | |
207 | html =self.html | |
208 | html.SetPage(self.text) | |
209 | self.dirty =0 | |
210 | # scroll to the end | |
211 | (x,y) =html.GetVirtualSize() | |
212 | html.Scroll(0, y) | |
213 | ||
214 | def AddText(self, text, style=None): | |
215 | """write text to output window""" | |
216 | # a trick needed to defer default from compile-time to execute-time | |
217 | if style ==None: style =self.out_style | |
218 | if 0 and __debug__: sys.__stdout__.write(text) | |
219 | # handle entities | |
220 | for (symbol, eref) in self.erefs: | |
221 | text = text.replace(symbol, eref) | |
222 | # replace newlines | |
223 | text = text.replace("\n", style[2]) | |
224 | # add to contents | |
225 | self.text =self.text +style[0] +text +style[1] | |
226 | if not self.in_batch: self.UpdWindow() | |
227 | else: self.dirty =1 | |
228 | if self.html_debug: | |
229 | # html debug output needn't to be too large | |
230 | self.view.SetValue(self.text[-4096:]) | |
231 | ||
232 | def write(self, str, style=None): | |
233 | """stdout-like interface""" | |
234 | if style ==None: style =self.out_style | |
235 | # do not process incomplete lines | |
236 | if len(str) <1: | |
237 | # hm... what was i supposed to do? | |
238 | return | |
239 | elif str[-1] !="\n": | |
240 | self.line_buffer =self.line_buffer +str | |
241 | else: | |
242 | self.AddText(self.line_buffer +str, style) | |
243 | self.line_buffer ="" | |
244 | ||
245 | def flush(self, style=None): | |
246 | """write out all that was left in line buffer""" | |
247 | if style ==None: style =self.out_style | |
248 | self.AddText(self.line_buffer +"\n", style) | |
249 | ||
250 | def write_in(self, str, style=None): | |
251 | """write text in "input" style""" | |
252 | if style ==None: style =self.in_style | |
253 | self.AddText(str, style) | |
254 | ||
255 | def write_exc(self, str, style=None): | |
256 | """write text in "exception" style""" | |
257 | if style ==None: style =self.exc_style | |
258 | self.AddText(str, style) | |
259 | ||
b881fc78 | 260 | class PyShell(wx.Panel): |
d14a1e28 RD |
261 | """interactive Python shell with wxPython interface |
262 | ||
263 | """ | |
264 | def __init__(self, parent, globals=globals(), locals={}, | |
b881fc78 RD |
265 | id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, |
266 | style=wx.TAB_TRAVERSAL, name="shell"): | |
d14a1e28 | 267 | """create PyShell window""" |
b881fc78 | 268 | wx.Panel.__init__(self, parent, id, pos, size, style, name) |
d14a1e28 RD |
269 | self.globals =globals |
270 | self.locals =locals | |
b881fc78 | 271 | splitter =wx.SplitterWindow(self, -1) |
d14a1e28 RD |
272 | self.output =PyShellOutput(splitter) |
273 | self.input =PyShellInput(splitter, self) | |
274 | self.input.SetFocus() | |
275 | splitter.SplitHorizontally(self.input, self.output) | |
276 | splitter.SetSashPosition(100) | |
277 | splitter.SetMinimumPaneSize(20) | |
278 | self.splitter =splitter | |
b881fc78 RD |
279 | self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) |
280 | self.Bind(wx.EVT_SIZE, self.OnSize) | |
d14a1e28 RD |
281 | |
282 | def OnSetFocus(self, event): | |
283 | self.input.SetFocus() | |
284 | ||
285 | def TryExec(self, source, symbol="single"): | |
286 | """Compile and run some source in the interpreter. | |
287 | ||
288 | borrowed from code.InteractiveInterpreter().runsource() | |
289 | as i said above, i would rather like to inherit from that class | |
290 | ||
291 | returns 1 if more input is required, or 0, otherwise | |
292 | """ | |
293 | try: | |
294 | cc = code.compile_command(source, symbol=symbol) | |
295 | except (OverflowError, SyntaxError): | |
296 | # [als] hm... never seen anything of that kind | |
297 | self.ShowSyntaxError() | |
298 | return 0 | |
299 | if cc is None: | |
300 | # source is incomplete | |
301 | return 1 | |
302 | # source is sucessfully compiled | |
303 | out =self.output | |
304 | # redirect system stdout to the output window | |
305 | prev_out =sys.stdout | |
306 | sys.stdout =out | |
307 | # begin printout batch (html updates are deferred until EndBatch()) | |
308 | out.BeginBatch() | |
309 | out.write_in(source) | |
310 | try: | |
311 | exec cc in self.globals, self.locals | |
312 | except SystemExit: | |
313 | # SystemExit is not handled and has to be re-raised | |
314 | raise | |
315 | except: | |
316 | # all other exceptions produce traceback output | |
317 | self.ShowException() | |
318 | # switch back to saved stdout | |
319 | sys.stdout =prev_out | |
320 | # commit printout | |
321 | out.flush() | |
322 | out.EndBatch() | |
323 | return 0 | |
324 | ||
325 | def ShowException(self): | |
326 | """display the traceback for the latest exception""" | |
327 | (etype, value, tb) =sys.exc_info() | |
328 | # remove myself from traceback | |
329 | tblist =traceback.extract_tb(tb)[1:] | |
330 | msg = ' '.join(traceback.format_exception_only(etype, value) | |
331 | +traceback.format_list(tblist)) | |
332 | self.output.write_exc(msg) | |
333 | ||
334 | def ShowSyntaxError(self): | |
335 | """display message about syntax error (no traceback here)""" | |
336 | (etype, value, tb) =sys.exc_info() | |
337 | msg = ' '.join(traceback.format_exception_only(etype, value)) | |
338 | self.output.write_exc(msg) | |
339 | ||
340 | def OnSize(self, event): | |
341 | self.splitter.SetSize(self.GetClientSize()) | |
342 | ||
343 | #---------------------------------------------------------------------- | |
344 | if __name__ == '__main__': | |
b881fc78 | 345 | class MyFrame(wx.Frame): |
d14a1e28 | 346 | """Very standard Frame class. Nothing special here!""" |
b881fc78 | 347 | def __init__(self, parent=None, id =-1, |
d14a1e28 | 348 | title="wxPython Interactive Shell"): |
b881fc78 | 349 | wx.Frame.__init__(self, parent, id, title) |
d14a1e28 RD |
350 | self.shell =PyShell(self) |
351 | ||
b881fc78 | 352 | class MyApp(wx.App): |
d14a1e28 RD |
353 | """Demonstrates usage of both default and customized shells""" |
354 | def OnInit(self): | |
355 | frame = MyFrame() | |
356 | frame.Show(True) | |
357 | self.SetTopWindow(frame) | |
358 | ## PyShellInput.PS1 =" let's get some work done..." | |
359 | ## PyShellInput.PS2 =" ok, what do you really mean?" | |
360 | ## PyShellOutput.in_style =( | |
361 | ## "<I><font color=\"#008000\"><tt>>>> ", | |
362 | ## "</tt></font></I><br>\n", "<br>\n... ") | |
363 | ## PyShellOutput.out_style =( | |
364 | ## "<font color=\"#000080\"><tt>", | |
365 | ## "</tt></font><br>\n", "<br>\n") | |
366 | ## PyShellOutput.exc_style =("<B><font color=\"#FF0000\"><tt>", | |
367 | ## "</tt></font></B>\n", "<br>\n") | |
368 | ## PyShellOutput.intro ="<I><B>Customized wxPython Shell</B>" \ | |
369 | ## "<br><-- move this sash to see html debug output</I><br>\n" | |
370 | ## PyShellOutput.html_debug =1 | |
371 | ## frame = MyFrame(title="Customized wxPython Shell") | |
372 | ## frame.Show(True) | |
373 | return True | |
374 | ||
375 | app = MyApp(0) | |
376 | app.MainLoop() | |
1fded56b | 377 |