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