]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/shell.py
Fixed the code to actually work.
[wxWidgets.git] / wxPython / wx / lib / shell.py
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-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>&gt;&gt;&gt;&nbsp;",
122 "</tt></font><br>\n", "<br>\n...&nbsp;")
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 =(("&", "&amp;"), (">", "&gt;"), ("<", "&lt;"), (" ", "&nbsp; "))
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>&gt;&gt;&gt;&nbsp;",
337 ## "</tt></font></I><br>\n", "<br>\n...&nbsp;")
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>&lt;-- 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()
352