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