]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/shell.py
Patches from Andrea
[wxWidgets.git] / wxPython / wx / lib / shell.py
1 # shell.py
2 #----------------------------------------------------------------------
3 # 12/10/2003 - Jeff Grimmett (grimmtooth@softhome.net)
4 #
5 # o 2.5 compatability update.
6 # o Added deprecation warning.
7 #
8
9 """wxPython interactive shell
10
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
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 ########################################/
55
56 """
57
58 warnings.warn(warningmsg, DeprecationWarning, stacklevel=2)
59
60 #----------------------------------------------------------------------
61
62 class PyShellInput(wx.Panel):
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 """
76 wx.Panel.__init__(self, parent, id)
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
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)])
89 self.SetSizer(sizer)
90 self.SetAutoLayout(True)
91 self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
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()
112 if event.GetKeyCode() !=wx.WXK_RETURN:
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
138 class PyShellOutput(wx.Panel):
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>&gt;&gt;&gt;&nbsp;",
147 "</tt></font><br>\n", "<br>\n...&nbsp;")
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 =(("&", "&amp;"), (">", "&gt;"), ("<", "&lt;"), (" ", "&nbsp; "))
155 def __init__(self, parent, id=-1):
156 wx.Panel.__init__(self, parent, id)
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
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)
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
177 self.html =wx.html.HtmlWindow(self)
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
186 self.Bind(wx.EVT_SIZE, self.OnSize)
187 self.Bind(wx.EVT_IDLE, self.OnIdle)
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
260 class PyShell(wx.Panel):
261 """interactive Python shell with wxPython interface
262
263 """
264 def __init__(self, parent, globals=globals(), locals={},
265 id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize,
266 style=wx.TAB_TRAVERSAL, name="shell"):
267 """create PyShell window"""
268 wx.Panel.__init__(self, parent, id, pos, size, style, name)
269 self.globals =globals
270 self.locals =locals
271 splitter =wx.SplitterWindow(self, -1)
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
279 self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
280 self.Bind(wx.EVT_SIZE, self.OnSize)
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__':
345 class MyFrame(wx.Frame):
346 """Very standard Frame class. Nothing special here!"""
347 def __init__(self, parent=None, id =-1,
348 title="wxPython Interactive Shell"):
349 wx.Frame.__init__(self, parent, id, title)
350 self.shell =PyShell(self)
351
352 class MyApp(wx.App):
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>&gt;&gt;&gt;&nbsp;",
362 ## "</tt></font></I><br>\n", "<br>\n...&nbsp;")
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>&lt;-- 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()
377