]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/shell.py
0c71206bd24af8624e1a1123d3549d7e024e81e9
[wxWidgets.git] / wxPython / wxPython / lib / PyCrust / shell.py
1 """PyCrust Shell is the gui text control in which a user interacts and types
2 in commands to be sent to the interpreter. This particular shell is based on
3 wxPython's wxStyledTextCtrl.
4 """
5
6 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
7 __cvsid__ = "$Id$"
8 __date__ = "July 1, 2001"
9 __version__ = "$Revision$"[11:-2]
10
11 from wxPython.wx import *
12 from wxPython.stc import *
13
14 import keyword
15 import os
16 import sys
17
18 from version import VERSION
19
20 if wxPlatform == '__WXMSW__':
21 faces = { 'times' : 'Times New Roman',
22 'mono' : 'Courier New',
23 'helv' : 'Lucida Console',
24 'lucida' : 'Lucida Console',
25 'other' : 'Comic Sans MS',
26 'size' : 8,
27 'lnsize' : 7,
28 'backcol': '#FFFFFF',
29 }
30 else: # GTK
31 faces = { 'times' : 'Times',
32 'mono' : 'Courier',
33 'helv' : 'Helvetica',
34 'other' : 'new century schoolbook',
35 'size' : 9,
36 'lnsize' : 8,
37 'backcol': '#FFFFFF',
38 }
39
40
41 class Shell(wxStyledTextCtrl):
42 """PyCrust Shell based on wxStyledTextCtrl."""
43 name = 'PyCrust Shell'
44 revision = __version__
45 def __init__(self, parent, id, introText='', locals=None, interp=None):
46 """Create a PyCrust Shell object."""
47 wxStyledTextCtrl.__init__(self, parent, id, style=wxCLIP_CHILDREN)
48 self.introText = introText
49 # Keep track of the most recent prompt starting and ending positions.
50 self.promptPos = [0, 0]
51 # Keep track of multi-line commands.
52 self.more = 0
53 # Assign handlers for keyboard events.
54 EVT_KEY_DOWN(self, self.OnKeyDown)
55 EVT_CHAR(self, self.OnChar)
56 # Create a default interpreter if one isn't provided.
57 if interp == None:
58 from interpreter import Interpreter
59 from pseudo import PseudoFileIn, PseudoFileOut, PseudoFileErr
60 self.stdin = PseudoFileIn(self.readIn)
61 self.stdout = PseudoFileOut(self.writeOut)
62 self.stderr = PseudoFileErr(self.writeErr)
63 # Override the default locals so we have something interesting.
64 self.locals = {'__name__': 'PyCrust',
65 '__doc__': 'PyCrust, The Python Shell.',
66 '__version__': VERSION,
67 }
68 # Add the dictionary that was passed in.
69 if locals:
70 self.locals.update(locals)
71 self.interp = Interpreter(locals=self.locals,
72 rawin=self.readRaw,
73 stdin=self.stdin,
74 stdout=self.stdout,
75 stderr=self.stderr)
76 else:
77 self.interp = interp
78
79 # Configure various defaults and user preferences.
80 self.config()
81
82 try:
83 self.showIntro(self.introText)
84 except:
85 pass
86
87 try:
88 self.setBuiltinKeywords()
89 except:
90 pass
91
92 try:
93 self.setLocalShell()
94 except:
95 pass
96
97 # Do this last so the user has complete control over their
98 # environment. They can override anything they want.
99 try:
100 self.execStartupScript(self.interp.startupScript)
101 except:
102 pass
103
104 def destroy(self):
105 del self.stdin
106 del self.stdout
107 del self.stderr
108 del self.interp
109
110 def config(self):
111 """Configure shell based on user preferences."""
112 self.SetMarginType(1, wxSTC_MARGIN_NUMBER)
113 self.SetMarginWidth(1, 40)
114
115 self.SetLexer(wxSTC_LEX_PYTHON)
116 self.SetKeyWords(0, ' '.join(keyword.kwlist))
117
118 self.setStyles(faces)
119 self.SetViewWhiteSpace(0)
120 self.SetTabWidth(4)
121 self.SetUseTabs(0)
122 # Do we want to automatically pop up command completion options?
123 self.autoComplete = 1
124 self.autoCompleteIncludeMagic = 1
125 self.autoCompleteIncludeSingle = 1
126 self.autoCompleteIncludeDouble = 1
127 self.autoCompleteCaseInsensitive = 1
128 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
129 # De we want to automatically pop up command argument help?
130 self.autoCallTip = 1
131 self.CallTipSetBackground(wxColour(255, 255, 232))
132
133 def showIntro(self, text=''):
134 """Display introductory text in the shell."""
135 if text:
136 if text[-1] != '\n': text += '\n'
137 self.write(text)
138 try:
139 self.write(self.interp.introText)
140 except AttributeError:
141 pass
142
143 def setBuiltinKeywords(self):
144 """Create pseudo keywords as part of builtins.
145
146 This is a rather clever hack that sets "close", "exit" and "quit"
147 to a PseudoKeyword object so that we can make them do what we want.
148 In this case what we want is to call our self.quit() method.
149 The user can type "close", "exit" or "quit" without the final parens.
150 """
151 import __builtin__
152 from pseudo import PseudoKeyword
153 __builtin__.close = __builtin__.exit = __builtin__.quit = \
154 PseudoKeyword(self.quit)
155
156 def quit(self):
157 """Quit the application."""
158
159 # XXX Good enough for now but later we want to send a close event.
160
161 # In the close event handler we can prompt to make sure they want to quit.
162 # Other applications, like PythonCard, may choose to hide rather than
163 # quit so we should just post the event and let the surrounding app
164 # decide what it wants to do.
165 self.write('Click on the close button to leave the application.')
166
167 def setLocalShell(self):
168 """Add 'shell' to locals."""
169 self.interp.locals['shell'] = self
170
171 def execStartupScript(self, startupScript):
172 """Execute the user's PYTHONSTARTUP script if they have one."""
173 if startupScript and os.path.isfile(startupScript):
174 startupText = 'Startup script executed: ' + startupScript
175 self.push('print %s;execfile(%s)' % \
176 (`startupText`, `startupScript`))
177 else:
178 self.push('')
179
180 def setStyles(self, faces):
181 """Configure font size, typeface and color for lexer."""
182
183 # Default style
184 self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
185
186 self.StyleClearAll()
187
188 # Built in styles
189 self.StyleSetSpec(wxSTC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces)
190 self.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces)
191 self.StyleSetSpec(wxSTC_STYLE_BRACELIGHT, "fore:#0000FF,back:#FFFF88")
192 self.StyleSetSpec(wxSTC_STYLE_BRACEBAD, "fore:#FF0000,back:#FFFF88")
193
194 # Python styles
195 self.StyleSetSpec(wxSTC_P_DEFAULT, "face:%(mono)s" % faces)
196 self.StyleSetSpec(wxSTC_P_COMMENTLINE, "fore:#007F00,face:%(mono)s" % faces)
197 self.StyleSetSpec(wxSTC_P_NUMBER, "")
198 self.StyleSetSpec(wxSTC_P_STRING, "fore:#7F007F,face:%(mono)s" % faces)
199 self.StyleSetSpec(wxSTC_P_CHARACTER, "fore:#7F007F,face:%(mono)s" % faces)
200 self.StyleSetSpec(wxSTC_P_WORD, "fore:#00007F,bold")
201 self.StyleSetSpec(wxSTC_P_TRIPLE, "fore:#7F0000")
202 self.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE, "fore:#000033,back:#FFFFE8")
203 self.StyleSetSpec(wxSTC_P_CLASSNAME, "fore:#0000FF,bold")
204 self.StyleSetSpec(wxSTC_P_DEFNAME, "fore:#007F7F,bold")
205 self.StyleSetSpec(wxSTC_P_OPERATOR, "")
206 self.StyleSetSpec(wxSTC_P_IDENTIFIER, "")
207 self.StyleSetSpec(wxSTC_P_COMMENTBLOCK, "fore:#7F7F7F")
208 self.StyleSetSpec(wxSTC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces)
209
210 def OnKeyDown(self, event):
211 """Key down event handler.
212
213 The main goal here is to not allow modifications to previous
214 lines of text.
215 """
216 key = event.KeyCode()
217 currpos = self.GetCurrentPos()
218 stoppos = self.promptPos[1]
219 # If the auto-complete window is up let it do its thing.
220 if self.AutoCompActive():
221 event.Skip()
222 # Return is used to submit a command to the interpreter.
223 elif key == WXK_RETURN:
224 if self.CallTipActive: self.CallTipCancel()
225 self.processLine()
226 # Home needs to be aware of the prompt.
227 elif key == WXK_HOME:
228 if currpos >= stoppos:
229 self.SetCurrentPos(stoppos)
230 self.SetAnchor(stoppos)
231 else:
232 event.Skip()
233 # Basic navigation keys should work anywhere.
234 elif key in (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, \
235 WXK_PRIOR, WXK_NEXT):
236 event.Skip()
237 # Don't backspace over the latest prompt.
238 elif key == WXK_BACK:
239 if currpos > stoppos:
240 event.Skip()
241 # Only allow these keys after the latest prompt.
242 elif key in (WXK_TAB, WXK_DELETE):
243 if currpos >= stoppos:
244 event.Skip()
245 # Don't toggle between insert mode and overwrite mode.
246 elif key == WXK_INSERT:
247 pass
248 else:
249 event.Skip()
250
251 def OnChar(self, event):
252 """Keypress event handler.
253
254 The main goal here is to not allow modifications to previous
255 lines of text.
256 """
257 key = event.KeyCode()
258 currpos = self.GetCurrentPos()
259 stoppos = self.promptPos[1]
260 if currpos >= stoppos:
261 if key == 46:
262 # "." The dot or period key activates auto completion.
263 # Get the command between the prompt and the cursor.
264 # Add a dot to the end of the command.
265 command = self.GetTextRange(stoppos, currpos) + '.'
266 self.write('.')
267 if self.autoComplete: self.autoCompleteShow(command)
268 elif key == 40:
269 # "(" The left paren activates a call tip and cancels
270 # an active auto completion.
271 if self.AutoCompActive(): self.AutoCompCancel()
272 # Get the command between the prompt and the cursor.
273 # Add the '(' to the end of the command.
274 command = self.GetTextRange(stoppos, currpos) + '('
275 self.write('(')
276 if self.autoCallTip: self.autoCallTipShow(command)
277 else:
278 # Allow the normal event handling to take place.
279 event.Skip()
280 else:
281 pass
282
283 def setStatusText(self, text):
284 """Display status information."""
285
286 # This method will most likely be replaced by the enclosing app
287 # to do something more interesting, like write to a status bar.
288 print text
289
290 def processLine(self):
291 """Process the line of text at which the user hit Enter."""
292
293 # The user hit ENTER and we need to decide what to do. They could be
294 # sitting on any line in the shell.
295
296 # Grab information about the current line.
297 thepos = self.GetCurrentPos()
298 theline = self.GetCurrentLine()
299 thetext = self.GetCurLine()[0]
300 command = self.getCommand(thetext)
301 # Go to the very bottom of the text.
302 endpos = self.GetTextLength()
303 self.SetCurrentPos(endpos)
304 endline = self.GetCurrentLine()
305 # If they hit RETURN on the last line, execute the command.
306 if theline == endline:
307 self.push(command)
308 # Otherwise, replace the last line with the new line.
309 else:
310 # If the new line contains a command (even an invalid one).
311 if command:
312 startpos = self.PositionFromLine(endline)
313 self.SetSelection(startpos, endpos)
314 self.ReplaceSelection('')
315 self.prompt()
316 self.write(command)
317 # Otherwise, put the cursor back where we started.
318 else:
319 self.SetCurrentPos(thepos)
320 self.SetAnchor(thepos)
321
322 def getCommand(self, text):
323 """Extract a command from text which may include a shell prompt.
324
325 The command may not necessarily be valid Python syntax.
326 """
327
328 # XXX Need to extract real prompts here. Need to keep track of the
329 # prompt every time a command is issued. Do this in the interpreter
330 # with a line number, prompt, command dictionary. For the history, perhaps.
331 ps1 = str(sys.ps1)
332 ps1size = len(ps1)
333 ps2 = str(sys.ps2)
334 ps2size = len(ps2)
335 text = text.rstrip()
336 # Strip the prompt off the front of text leaving just the command.
337 if text[:ps1size] == ps1:
338 command = text[ps1size:]
339 elif text[:ps2size] == ps2:
340 command = text[ps2size:]
341 else:
342 command = ''
343 return command
344
345 def push(self, command):
346 """Send command to the interpreter for execution."""
347 self.write('\n')
348 self.more = self.interp.push(command)
349 self.prompt()
350 # Keep the undo feature from undoing previous responses. The only
351 # thing that can be undone is stuff typed after the prompt, before
352 # hitting enter. After they hit enter it becomes permanent.
353 self.EmptyUndoBuffer()
354
355 def write(self, text):
356 """Display text in the shell."""
357 self.AddText(text)
358 self.EnsureCaretVisible()
359 #self.ScrollToColumn(0)
360
361 def prompt(self):
362 """Display appropriate prompt for the context, either ps1 or ps2.
363
364 If this is a continuation line, autoindent as necessary.
365 """
366 if self.more:
367 prompt = str(sys.ps2)
368 else:
369 prompt = str(sys.ps1)
370 pos = self.GetCurLine()[1]
371 if pos > 0: self.write('\n')
372 self.promptPos[0] = self.GetCurrentPos()
373 self.write(prompt)
374 self.promptPos[1] = self.GetCurrentPos()
375 # XXX Add some autoindent magic here if more.
376 if self.more:
377 self.write('\t') # Temporary hack indentation.
378 self.EnsureCaretVisible()
379 self.ScrollToColumn(0)
380
381 def readIn(self):
382 """Replacement for stdin."""
383 prompt = 'Please enter your response:'
384 dialog = wxTextEntryDialog(None, prompt, \
385 'Input Dialog (Standard)', '')
386 try:
387 if dialog.ShowModal() == wxID_OK:
388 text = dialog.GetValue()
389 self.write(text + '\n')
390 return text
391 finally:
392 dialog.Destroy()
393 return ''
394
395 def readRaw(self, prompt='Please enter your response:'):
396 """Replacement for raw_input."""
397 dialog = wxTextEntryDialog(None, prompt, \
398 'Input Dialog (Raw)', '')
399 try:
400 if dialog.ShowModal() == wxID_OK:
401 text = dialog.GetValue()
402 return text
403 finally:
404 dialog.Destroy()
405 return ''
406
407 def ask(self, prompt='Please enter your response:'):
408 """Get response from the user."""
409 return raw_input(prompt=prompt)
410
411 def pause(self):
412 """Halt execution pending a response from the user."""
413 self.ask('Press enter to continue:')
414
415 def clear(self):
416 """Delete all text from the shell."""
417 self.ClearAll()
418
419 def run(self, command, prompt=1, verbose=1):
420 """Execute command within the shell as if it was typed in directly.
421 >>> shell.run('print "this"')
422 >>> print "this"
423 this
424 >>>
425 """
426 command = command.rstrip()
427 if prompt: self.prompt()
428 if verbose: self.write(command)
429 self.push(command)
430
431 def runfile(self, filename):
432 """Execute all commands in file as if they were typed into the shell."""
433 file = open(filename)
434 try:
435 self.prompt()
436 for command in file.readlines():
437 if command[:6] == 'shell.': # Run shell methods silently.
438 self.run(command, prompt=0, verbose=0)
439 else:
440 self.run(command, prompt=0, verbose=1)
441 finally:
442 file.close()
443
444 def autoCompleteShow(self, command):
445 """Display auto-completion popup list."""
446 list = self.interp.getAutoCompleteList(command, \
447 includeMagic=self.autoCompleteIncludeMagic, \
448 includeSingle=self.autoCompleteIncludeSingle, \
449 includeDouble=self.autoCompleteIncludeDouble)
450 if list:
451 options = ' '.join(list)
452 offset = 0
453 self.AutoCompShow(offset, options)
454
455 def autoCallTipShow(self, command):
456 """Display argument spec and docstring in a popup bubble thingie."""
457 if self.CallTipActive: self.CallTipCancel()
458 tip = self.interp.getCallTip(command)
459 if tip:
460 offset = self.GetCurrentPos()
461 self.CallTipShow(offset, tip)
462
463 def writeOut(self, text):
464 """Replacement for stdout."""
465 self.write(text)
466
467 def writeErr(self, text):
468 """Replacement for stderr."""
469 self.write(text)
470
471 def CanCut(self):
472 """Return true if text is selected and can be cut."""
473 return self.GetSelectionStart() != self.GetSelectionEnd()
474
475 def CanCopy(self):
476 """Return true if text is selected and can be copied."""
477 return self.GetSelectionStart() != self.GetSelectionEnd()
478