]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/shell.py
b919213c42a55002ed0b68bde024c6fb0406d865
[wxWidgets.git] / wxPython / wxPython / 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-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>&gt;&gt;&gt;&nbsp;",
119 "</tt></font><br>\n", "<br>\n...&nbsp;")
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 =(("&", "&amp;"), (">", "&gt;"), ("<", "&lt;"), (" ", "&nbsp; "))
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 EVT_SIZE(self, self.OnSize)
159 EVT_IDLE(self, self.OnIdle)
160
161 def OnSize(self, event):
162 self.client.SetSize(self.GetClientSize())
163
164 def OnIdle(self, event):
165 """when there's nothing to do, we can update display"""
166 if self.in_batch and self.dirty: self.UpdWindow()
167
168 def BeginBatch(self):
169 """do not refresh display till EndBatch()"""
170 self.in_batch =1
171
172 def EndBatch(self):
173 """end batch; start updating display immediately"""
174 self.in_batch =0
175 if self.dirty: self.UpdWindow()
176
177 def UpdWindow(self):
178 """sync display with text buffer"""
179 html =self.html
180 html.SetPage(self.text)
181 self.dirty =0
182 # scroll to the end
183 (x,y) =html.GetVirtualSize()
184 html.Scroll(0, y)
185
186 def AddText(self, text, style=None):
187 """write text to output window"""
188 # a trick needed to defer default from compile-time to execute-time
189 if style ==None: style =self.out_style
190 if 0 and __debug__: sys.__stdout__.write(text)
191 # handle entities
192 for (symbol, eref) in self.erefs:
193 text =string.replace(text, symbol, eref)
194 # replace newlines
195 text =string.replace(text, "\n", style[2])
196 # add to contents
197 self.text =self.text +style[0] +text +style[1]
198 if not self.in_batch: self.UpdWindow()
199 else: self.dirty =1
200 if self.html_debug:
201 # html debug output needn't to be too large
202 self.view.SetValue(self.text[-4096:])
203
204 def write(self, str, style=None):
205 """stdout-like interface"""
206 if style ==None: style =self.out_style
207 # do not process incomplete lines
208 if len(str) <1:
209 # hm... what was i supposed to do?
210 return
211 elif str[-1] !="\n":
212 self.line_buffer =self.line_buffer +str
213 else:
214 self.AddText(self.line_buffer +str, style)
215 self.line_buffer =""
216
217 def flush(self, style=None):
218 """write out all that was left in line buffer"""
219 if style ==None: style =self.out_style
220 self.AddText(self.line_buffer +"\n", style)
221
222 def write_in(self, str, style=None):
223 """write text in "input" style"""
224 if style ==None: style =self.in_style
225 self.AddText(str, style)
226
227 def write_exc(self, str, style=None):
228 """write text in "exception" style"""
229 if style ==None: style =self.exc_style
230 self.AddText(str, style)
231
232 class PyShell(wxPanel):
233 """interactive Python shell with wxPython interface
234
235 """
236 def __init__(self, parent, globals=globals(), locals={},
237 id=-1, pos=wxDefaultPosition, size=wxDefaultSize,
238 style=wxTAB_TRAVERSAL, name="shell"):
239 """create PyShell window"""
240 wxPanel.__init__(self, parent, id, pos, size, style, name)
241 self.globals =globals
242 self.locals =locals
243 splitter =wxSplitterWindow(self, -1)
244 self.output =PyShellOutput(splitter)
245 self.input =PyShellInput(splitter, self)
246 self.input.SetFocus()
247 splitter.SplitHorizontally(self.input, self.output)
248 splitter.SetSashPosition(100)
249 splitter.SetMinimumPaneSize(20)
250 self.splitter =splitter
251 EVT_SET_FOCUS(self, self.OnSetFocus)
252 EVT_SIZE(self, self.OnSize)
253
254 def OnSetFocus(self, event):
255 self.input.SetFocus()
256
257 def TryExec(self, source, symbol="single"):
258 """Compile and run some source in the interpreter.
259
260 borrowed from code.InteractiveInterpreter().runsource()
261 as i said above, i would rather like to inherit from that class
262
263 returns 1 if more input is required, or 0, otherwise
264 """
265 try:
266 cc = code.compile_command(source, symbol=symbol)
267 except (OverflowError, SyntaxError):
268 # [als] hm... never seen anything of that kind
269 self.ShowSyntaxError()
270 return 0
271 if cc is None:
272 # source is incomplete
273 return 1
274 # source is sucessfully compiled
275 out =self.output
276 # redirect system stdout to the output window
277 prev_out =sys.stdout
278 sys.stdout =out
279 # begin printout batch (html updates are deferred until EndBatch())
280 out.BeginBatch()
281 out.write_in(source)
282 try:
283 exec cc in self.globals, self.locals
284 except SystemExit:
285 # SystemExit is not handled and has to be re-raised
286 raise
287 except:
288 # all other exceptions produce traceback output
289 self.ShowException()
290 # switch back to saved stdout
291 sys.stdout =prev_out
292 # commit printout
293 out.flush()
294 out.EndBatch()
295 return 0
296
297 def ShowException(self):
298 """display the traceback for the latest exception"""
299 (etype, value, tb) =sys.exc_info()
300 # remove myself from traceback
301 tblist =traceback.extract_tb(tb)[1:]
302 msg =string.join(traceback.format_exception_only(etype, value)
303 +traceback.format_list(tblist))
304 self.output.write_exc(msg)
305
306 def ShowSyntaxError(self):
307 """display message about syntax error (no traceback here)"""
308 (etype, value, tb) =sys.exc_info()
309 msg =string.join(traceback.format_exception_only(etype, value))
310 self.output.write_exc(msg)
311
312 def OnSize(self, event):
313 self.splitter.SetSize(self.GetClientSize())
314
315 #----------------------------------------------------------------------
316 if __name__ == '__main__':
317 class MyFrame(wxFrame):
318 """Very standard Frame class. Nothing special here!"""
319 def __init__(self, parent=NULL, id =-1,
320 title="wxPython Interactive Shell"):
321 wxFrame.__init__(self, parent, id, title)
322 self.shell =PyShell(self)
323
324 class MyApp(wxApp):
325 """Demonstrates usage of both default and customized shells"""
326 def OnInit(self):
327 frame = MyFrame()
328 frame.Show(TRUE)
329 self.SetTopWindow(frame)
330 ## PyShellInput.PS1 =" let's get some work done..."
331 ## PyShellInput.PS2 =" ok, what do you really mean?"
332 ## PyShellOutput.in_style =(
333 ## "<I><font color=\"#008000\"><tt>&gt;&gt;&gt;&nbsp;",
334 ## "</tt></font></I><br>\n", "<br>\n...&nbsp;")
335 ## PyShellOutput.out_style =(
336 ## "<font color=\"#000080\"><tt>",
337 ## "</tt></font><br>\n", "<br>\n")
338 ## PyShellOutput.exc_style =("<B><font color=\"#FF0000\"><tt>",
339 ## "</tt></font></B>\n", "<br>\n")
340 ## PyShellOutput.intro ="<I><B>Customized wxPython Shell</B>" \
341 ## "<br>&lt;-- move this sash to see html debug output</I><br>\n"
342 ## PyShellOutput.html_debug =1
343 ## frame = MyFrame(title="Customized wxPython Shell")
344 ## frame.Show(TRUE)
345 return TRUE
346
347 app = MyApp(0)
348 app.MainLoop()
349