]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/shell.py
d698017780420fd4c69f7a3c203e11c9348eb978
[wxWidgets.git] / wxPython / wxPython / lib / PyCrust / shell.py
1 """The PyCrust Shell is an interactive text control in which a user types in
2 commands to be sent to the interpreter. This particular shell is based on
3 wxPython's wxStyledTextCtrl. The latest files are always available at the
4 SourceForge project page at http://sourceforge.net/projects/pycrust/.
5 Sponsored by Orbtech.com - Your Source For Python Development Services"""
6
7 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
8 __cvsid__ = "$Id$"
9 __version__ = "$Revision$"[11:-2]
10
11 from wxPython.wx import *
12 from wxPython.stc import *
13 import keyword
14 import os
15 import sys
16 from pseudo import PseudoFileIn
17 from pseudo import PseudoFileOut
18 from pseudo import PseudoFileErr
19 from version import VERSION
20
21
22 if wxPlatform == '__WXMSW__':
23 faces = { 'times' : 'Times New Roman',
24 'mono' : 'Courier New',
25 'helv' : 'Lucida Console',
26 'lucida' : 'Lucida Console',
27 'other' : 'Comic Sans MS',
28 'size' : 10,
29 'lnsize' : 9,
30 'backcol': '#FFFFFF',
31 }
32 # Versions of wxPython prior to 2.3.2 had a sizing bug on Win platform.
33 # The font was 2 points too large. So we need to reduce the font size.
34 if ((wxMAJOR_VERSION, wxMINOR_VERSION) == (2, 3) and wxRELEASE_NUMBER < 2) \
35 or (wxMAJOR_VERSION <= 2 and wxMINOR_VERSION <= 2):
36 faces['size'] -= 2
37 faces['lnsize'] -= 2
38 else: # GTK
39 faces = { 'times' : 'Times',
40 'mono' : 'Courier',
41 'helv' : 'Helvetica',
42 'other' : 'new century schoolbook',
43 'size' : 12,
44 'lnsize' : 10,
45 'backcol': '#FFFFFF',
46 }
47
48
49 class ShellFacade:
50 """Simplified interface to all shell-related functionality.
51
52 This is a semi-transparent facade, in that all attributes of other are
53 still accessible, even though only some are visible to the user."""
54
55 name = 'PyCrust Shell Interface'
56 revision = __version__
57
58 def __init__(self, other):
59 """Create a ShellFacade instance."""
60 methods = ['ask',
61 'clear',
62 'pause',
63 'prompt',
64 'quit',
65 'redirectStderr',
66 'redirectStdin',
67 'redirectStdout',
68 'run',
69 'runfile',
70 ]
71 for method in methods:
72 self.__dict__[method] = getattr(other, method)
73 d = self.__dict__
74 d['other'] = other
75 d['help'] = 'There is no help available, yet.'
76
77
78 def __getattr__(self, name):
79 if hasattr(self.other, name):
80 return getattr(self.other, name)
81 else:
82 raise AttributeError, name
83
84 def __setattr__(self, name, value):
85 if self.__dict__.has_key(name):
86 self.__dict__[name] = value
87 elif hasattr(self.other, name):
88 return setattr(self.other, name, value)
89 else:
90 raise AttributeError, name
91
92 def _getAttributeNames(self):
93 """Return list of magic attributes to extend introspection."""
94 list = ['autoCallTip',
95 'autoComplete',
96 'autoCompleteCaseInsensitive',
97 'autoCompleteIncludeDouble',
98 'autoCompleteIncludeMagic',
99 'autoCompleteIncludeSingle',
100 ]
101 list.sort()
102 return list
103
104
105 class Shell(wxStyledTextCtrl):
106 """PyCrust Shell based on wxStyledTextCtrl."""
107
108 name = 'PyCrust Shell'
109 revision = __version__
110
111 def __init__(self, parent, id=-1, pos=wxDefaultPosition, \
112 size=wxDefaultSize, style=wxCLIP_CHILDREN, introText='', \
113 locals=None, InterpClass=None, *args, **kwds):
114 """Create a PyCrust Shell instance."""
115 wxStyledTextCtrl.__init__(self, parent, id, pos, size, style)
116 # Grab these so they can be restored by self.redirect* methods.
117 self.stdin = sys.stdin
118 self.stdout = sys.stdout
119 self.stderr = sys.stderr
120 # Add the current working directory "." to the search path.
121 sys.path.insert(0, os.curdir)
122 # Import a default interpreter class if one isn't provided.
123 if InterpClass == None:
124 from interpreter import Interpreter
125 else:
126 Interpreter = InterpClass
127 # Create default locals so we have something interesting.
128 shellLocals = {'__name__': 'PyCrust-Shell',
129 '__doc__': 'PyCrust-Shell, The PyCrust Python Shell.',
130 '__version__': VERSION,
131 }
132 # Add the dictionary that was passed in.
133 if locals:
134 shellLocals.update(locals)
135 self.interp = Interpreter(locals=shellLocals, \
136 rawin=self.readRaw, \
137 stdin=PseudoFileIn(self.readIn), \
138 stdout=PseudoFileOut(self.writeOut), \
139 stderr=PseudoFileErr(self.writeErr), \
140 *args, **kwds)
141 # Keep track of the most recent prompt starting and ending positions.
142 self.promptPos = [0, 0]
143 # Keep track of the most recent non-continuation prompt.
144 self.prompt1Pos = [0, 0]
145 # Keep track of multi-line commands.
146 self.more = 0
147 # Create the command history. Commands are added into the front of
148 # the list (ie. at index 0) as they are entered. self.historyIndex
149 # is the current position in the history; it gets incremented as you
150 # retrieve the previous command, decremented as you retrieve the
151 # next, and reset when you hit Enter. self.historyIndex == -1 means
152 # you're on the current command, not in the history.
153 self.history = []
154 self.historyIndex = -1
155 # Assign handlers for keyboard events.
156 EVT_KEY_DOWN(self, self.OnKeyDown)
157 EVT_CHAR(self, self.OnChar)
158 # Configure various defaults and user preferences.
159 self.config()
160 # Display the introductory banner information.
161 try: self.showIntro(introText)
162 except: pass
163 # Assign some pseudo keywords to the interpreter's namespace.
164 try: self.setBuiltinKeywords()
165 except: pass
166 # Add 'shell' to the interpreter's local namespace.
167 try: self.setLocalShell()
168 except: pass
169 # Do this last so the user has complete control over their
170 # environment. They can override anything they want.
171 try: self.execStartupScript(self.interp.startupScript)
172 except: pass
173
174 def destroy(self):
175 del self.interp
176
177 def config(self):
178 """Configure shell based on user preferences."""
179 self.SetMarginType(1, wxSTC_MARGIN_NUMBER)
180 self.SetMarginWidth(1, 40)
181
182 self.SetLexer(wxSTC_LEX_PYTHON)
183 self.SetKeyWords(0, ' '.join(keyword.kwlist))
184
185 self.setStyles(faces)
186 self.SetViewWhiteSpace(0)
187 self.SetTabWidth(4)
188 self.SetUseTabs(0)
189 # Do we want to automatically pop up command completion options?
190 self.autoComplete = 1
191 self.autoCompleteIncludeMagic = 1
192 self.autoCompleteIncludeSingle = 1
193 self.autoCompleteIncludeDouble = 1
194 self.autoCompleteCaseInsensitive = 1
195 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
196 # De we want to automatically pop up command argument help?
197 self.autoCallTip = 1
198 self.CallTipSetBackground(wxColour(255, 255, 232))
199
200 def showIntro(self, text=''):
201 """Display introductory text in the shell."""
202 if text:
203 if not text.endswith(os.linesep): text += os.linesep
204 self.write(text)
205 try:
206 self.write(self.interp.introText)
207 except AttributeError:
208 pass
209
210 def setBuiltinKeywords(self):
211 """Create pseudo keywords as part of builtins.
212
213 This is a rather clever hack that sets "close", "exit" and "quit"
214 to a PseudoKeyword object so that we can make them do what we want.
215 In this case what we want is to call our self.quit() method.
216 The user can type "close", "exit" or "quit" without the final parens.
217 """
218 ## POB: This is having some weird side-effects so I'm taking it out.
219 ## import __builtin__
220 ## from pseudo import PseudoKeyword
221 ## __builtin__.close = __builtin__.exit = __builtin__.quit = \
222 ## PseudoKeyword(self.quit)
223 import __builtin__
224 from pseudo import PseudoKeyword
225 __builtin__.close = __builtin__.exit = __builtin__.quit = \
226 'Click on the close button to leave the application.'
227
228 def quit(self):
229 """Quit the application."""
230
231 # XXX Good enough for now but later we want to send a close event.
232
233 # In the close event handler we can make sure they want to quit.
234 # Other applications, like PythonCard, may choose to hide rather than
235 # quit so we should just post the event and let the surrounding app
236 # decide what it wants to do.
237 self.write('Click on the close button to leave the application.')
238
239 def setLocalShell(self):
240 """Add 'shell' to locals as reference to ShellFacade instance."""
241 self.interp.locals['shell'] = ShellFacade(other=self)
242
243 def execStartupScript(self, startupScript):
244 """Execute the user's PYTHONSTARTUP script if they have one."""
245 if startupScript and os.path.isfile(startupScript):
246 startupText = 'Startup script executed: ' + startupScript
247 self.push('print %s;execfile(%s)' % \
248 (`startupText`, `startupScript`))
249 else:
250 self.push('')
251
252 def setStyles(self, faces):
253 """Configure font size, typeface and color for lexer."""
254
255 # Default style
256 self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
257
258 self.StyleClearAll()
259
260 # Built in styles
261 self.StyleSetSpec(wxSTC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces)
262 self.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces)
263 self.StyleSetSpec(wxSTC_STYLE_BRACELIGHT, "fore:#0000FF,back:#FFFF88")
264 self.StyleSetSpec(wxSTC_STYLE_BRACEBAD, "fore:#FF0000,back:#FFFF88")
265
266 # Python styles
267 self.StyleSetSpec(wxSTC_P_DEFAULT, "face:%(mono)s" % faces)
268 self.StyleSetSpec(wxSTC_P_COMMENTLINE, "fore:#007F00,face:%(mono)s" % faces)
269 self.StyleSetSpec(wxSTC_P_NUMBER, "")
270 self.StyleSetSpec(wxSTC_P_STRING, "fore:#7F007F,face:%(mono)s" % faces)
271 self.StyleSetSpec(wxSTC_P_CHARACTER, "fore:#7F007F,face:%(mono)s" % faces)
272 self.StyleSetSpec(wxSTC_P_WORD, "fore:#00007F,bold")
273 self.StyleSetSpec(wxSTC_P_TRIPLE, "fore:#7F0000")
274 self.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE, "fore:#000033,back:#FFFFE8")
275 self.StyleSetSpec(wxSTC_P_CLASSNAME, "fore:#0000FF,bold")
276 self.StyleSetSpec(wxSTC_P_DEFNAME, "fore:#007F7F,bold")
277 self.StyleSetSpec(wxSTC_P_OPERATOR, "")
278 self.StyleSetSpec(wxSTC_P_IDENTIFIER, "")
279 self.StyleSetSpec(wxSTC_P_COMMENTBLOCK, "fore:#7F7F7F")
280 self.StyleSetSpec(wxSTC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces)
281
282 def OnChar(self, event):
283 """Keypress event handler.
284
285 Prevents modification of previously submitted commands/responses."""
286 if not self.CanEdit():
287 return
288 key = event.KeyCode()
289 currpos = self.GetCurrentPos()
290 stoppos = self.promptPos[1]
291 if key == ord('.'):
292 # The dot or period key activates auto completion.
293 # Get the command between the prompt and the cursor.
294 # Add a dot to the end of the command.
295 command = self.GetTextRange(stoppos, currpos) + '.'
296 self.write('.')
297 if self.autoComplete: self.autoCompleteShow(command)
298 elif key == ord('('):
299 # The left paren activates a call tip and cancels
300 # an active auto completion.
301 if self.AutoCompActive(): self.AutoCompCancel()
302 # Get the command between the prompt and the cursor.
303 # Add the '(' to the end of the command.
304 command = self.GetTextRange(stoppos, currpos) + '('
305 self.write('(')
306 if self.autoCallTip: self.autoCallTipShow(command)
307 else:
308 # Allow the normal event handling to take place.
309 event.Skip()
310
311 def OnKeyDown(self, event):
312 """Key down event handler.
313
314 Prevents modification of previously submitted commands/responses."""
315 key = event.KeyCode()
316 currpos = self.GetCurrentPos()
317 stoppos = self.promptPos[1]
318 # Return is used to submit a command to the interpreter.
319 if key == WXK_RETURN:
320 if self.AutoCompActive(): self.AutoCompCancel()
321 if self.CallTipActive: self.CallTipCancel()
322 self.processLine()
323 # If the auto-complete window is up let it do its thing.
324 elif self.AutoCompActive():
325 event.Skip()
326 # Cut to the clipboard.
327 elif event.ControlDown() and key in (ord('X'), ord('x')):
328 self.Cut()
329 # Copy to the clipboard.
330 elif event.ControlDown() and not event.ShiftDown() \
331 and key in (ord('C'), ord('c')):
332 self.Copy()
333 # Copy to the clipboard, including prompts.
334 elif event.ControlDown() and event.ShiftDown() \
335 and key in (ord('C'), ord('c')):
336 self.CopyWithPrompts()
337 # Paste from the clipboard.
338 elif event.ControlDown() and key in (ord('V'), ord('v')):
339 self.Paste()
340 # Retrieve the previous command from the history buffer.
341 elif (event.ControlDown() and key == WXK_UP) \
342 or (event.AltDown() and key in (ord('P'), ord('p'))):
343 self.OnHistoryRetrieve(step=+1)
344 # Retrieve the next command from the history buffer.
345 elif (event.ControlDown() and key == WXK_DOWN) \
346 or (event.AltDown() and key in (ord('N'), ord('n'))):
347 self.OnHistoryRetrieve(step=-1)
348 # Search up the history for the text in front of the cursor.
349 elif key == WXK_F8:
350 self.OnHistorySearch()
351 # Home needs to be aware of the prompt.
352 elif key == WXK_HOME:
353 if currpos >= stoppos:
354 if event.ShiftDown():
355 # Select text from current position to end of prompt.
356 self.SetSelection(self.GetCurrentPos(), stoppos)
357 else:
358 self.SetCurrentPos(stoppos)
359 self.SetAnchor(stoppos)
360 else:
361 event.Skip()
362 # Basic navigation keys should work anywhere.
363 elif key in (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, \
364 WXK_PRIOR, WXK_NEXT):
365 event.Skip()
366 # Don't backspace over the latest prompt.
367 elif key == WXK_BACK:
368 if currpos > self.prompt1Pos[1]:
369 event.Skip()
370 # Only allow these keys after the latest prompt.
371 elif key in (WXK_TAB, WXK_DELETE):
372 if self.CanEdit():
373 event.Skip()
374 # Don't toggle between insert mode and overwrite mode.
375 elif key == WXK_INSERT:
376 pass
377 # Protect the readonly portion of the shell.
378 elif not self.CanEdit():
379 pass
380 else:
381 event.Skip()
382
383 def OnHistoryRetrieve(self, step):
384 """Retrieve the previous/next command from the history buffer."""
385 if not self.CanEdit():
386 return
387 startpos = self.GetCurrentPos()
388 newindex = self.historyIndex + step
389 if not (-1 <= newindex < len(self.history)):
390 return
391 self.historyIndex = newindex
392 if newindex == -1:
393 self.ReplaceSelection('')
394 else:
395 self.ReplaceSelection('')
396 command = self.history[self.historyIndex]
397 command = command.replace('\n', os.linesep + sys.ps2)
398 self.ReplaceSelection(command)
399 endpos = self.GetCurrentPos()
400 self.SetSelection(endpos, startpos)
401
402 def OnHistorySearch(self):
403 """Search up the history buffer for the text in front of the cursor."""
404 if not self.CanEdit():
405 return
406 startpos = self.GetCurrentPos()
407 # The text up to the cursor is what we search for.
408 numCharsAfterCursor = self.GetTextLength() - startpos
409 searchText = self.getCommand(rstrip=0)
410 if numCharsAfterCursor > 0:
411 searchText = searchText[:-numCharsAfterCursor]
412 if not searchText:
413 return
414 # Search upwards from the current history position and loop back
415 # to the beginning if we don't find anything.
416 if (self.historyIndex <= -1) \
417 or (self.historyIndex >= len(self.history)-2):
418 searchOrder = range(len(self.history))
419 else:
420 searchOrder = range(self.historyIndex+1, len(self.history)) + \
421 range(self.historyIndex)
422 for i in searchOrder:
423 command = self.history[i]
424 if command[:len(searchText)] == searchText:
425 # Replace the current selection with the one we've found.
426 self.ReplaceSelection(command[len(searchText):])
427 endpos = self.GetCurrentPos()
428 self.SetSelection(endpos, startpos)
429 # We've now warped into middle of the history.
430 self.historyIndex = i
431 break
432
433 def setStatusText(self, text):
434 """Display status information."""
435
436 # This method will most likely be replaced by the enclosing app
437 # to do something more interesting, like write to a status bar.
438 print text
439
440 def processLine(self):
441 """Process the line of text at which the user hit Enter."""
442
443 # The user hit ENTER and we need to decide what to do. They could be
444 # sitting on any line in the shell.
445
446 thepos = self.GetCurrentPos()
447 endpos = self.GetTextLength()
448 # If they hit RETURN at the very bottom, execute the command.
449 if thepos == endpos:
450 self.interp.more = 0
451 if self.getCommand():
452 command = self.GetTextRange(self.prompt1Pos[1], endpos)
453 else:
454 # This is a hack, now that we allow editing of previous
455 # lines, which throws off our promptPos values.
456 newend = endpos - len(self.getCommand(rstrip=0))
457 command = self.GetTextRange(self.prompt1Pos[1], newend)
458 command = command.replace(os.linesep + sys.ps2, '\n')
459 self.push(command)
460 # Or replace the current command with the other command.
461 elif thepos < self.prompt1Pos[0]:
462 theline = self.GetCurrentLine()
463 command = self.getCommand(rstrip=0)
464 # If the new line contains a command (even an invalid one).
465 if command:
466 command = self.getMultilineCommand()
467 self.SetCurrentPos(endpos)
468 startpos = self.prompt1Pos[1]
469 self.SetSelection(startpos, endpos)
470 self.ReplaceSelection('')
471 self.write(command)
472 self.more = 0
473 # Otherwise, put the cursor back where we started.
474 else:
475 self.SetCurrentPos(thepos)
476 self.SetAnchor(thepos)
477 # Or add a new line to the current single or multi-line command.
478 elif thepos > self.prompt1Pos[1]:
479 self.write(os.linesep)
480 self.more = 1
481 self.prompt()
482
483 def getMultilineCommand(self, rstrip=1):
484 """Extract a multi-line command from the editor.
485
486 The command may not necessarily be valid Python syntax."""
487 # XXX Need to extract real prompts here. Need to keep track of the
488 # prompt every time a command is issued.
489 ps1 = str(sys.ps1)
490 ps1size = len(ps1)
491 ps2 = str(sys.ps2)
492 ps2size = len(ps2)
493 # This is a total hack job, but it works.
494 text = self.GetCurLine()[0]
495 line = self.GetCurrentLine()
496 while text[:ps2size] == ps2 and line > 0:
497 line -= 1
498 self.GotoLine(line)
499 text = self.GetCurLine()[0]
500 if text[:ps1size] == ps1:
501 line = self.GetCurrentLine()
502 self.GotoLine(line)
503 startpos = self.GetCurrentPos() + ps1size
504 line += 1
505 self.GotoLine(line)
506 while self.GetCurLine()[0][:ps2size] == ps2:
507 line += 1
508 self.GotoLine(line)
509 stoppos = self.GetCurrentPos()
510 command = self.GetTextRange(startpos, stoppos)
511 command = command.replace(os.linesep + sys.ps2, '\n')
512 command = command.rstrip()
513 command = command.replace('\n', os.linesep + sys.ps2)
514 else:
515 command = ''
516 if rstrip:
517 command = command.rstrip()
518 return command
519
520 def getCommand(self, text=None, rstrip=1):
521 """Extract a command from text which may include a shell prompt.
522
523 The command may not necessarily be valid Python syntax."""
524 if not text:
525 text = self.GetCurLine()[0]
526 # Strip the prompt off the front of text leaving just the command.
527 command = self.lstripPrompt(text)
528 if command == text:
529 command = '' # Real commands have prompts.
530 if rstrip:
531 command = command.rstrip()
532 return command
533
534 def lstripPrompt(self, text):
535 """Return text without a leading prompt."""
536 ps1 = str(sys.ps1)
537 ps1size = len(ps1)
538 ps2 = str(sys.ps2)
539 ps2size = len(ps2)
540 # Strip the prompt off the front of text.
541 if text[:ps1size] == ps1:
542 text = text[ps1size:]
543 elif text[:ps2size] == ps2:
544 text = text[ps2size:]
545 return text
546
547 def push(self, command):
548 """Send command to the interpreter for execution."""
549 self.write(os.linesep)
550 self.more = self.interp.push(command)
551 if not self.more:
552 self.addHistory(command.rstrip())
553 self.prompt()
554
555 def addHistory(self, command):
556 """Add command to the command history."""
557 # Reset the history position.
558 self.historyIndex = -1
559 # Insert this command into the history, unless it's a blank
560 # line or the same as the last command.
561 if command != '' \
562 and (len(self.history) == 0 or command != self.history[0]):
563 self.history.insert(0, command)
564
565 def write(self, text):
566 """Display text in the shell.
567
568 Replace line endings with OS-specific endings."""
569 text = self.fixLineEndings(text)
570 self.AddText(text)
571 self.EnsureCaretVisible()
572
573 def fixLineEndings(self, text):
574 """Return text with line endings replaced by OS-specific endings."""
575 lines = text.split('\r\n')
576 for l in range(len(lines)):
577 chunks = lines[l].split('\r')
578 for c in range(len(chunks)):
579 chunks[c] = os.linesep.join(chunks[c].split('\n'))
580 lines[l] = os.linesep.join(chunks)
581 text = os.linesep.join(lines)
582 return text
583
584 def prompt(self):
585 """Display appropriate prompt for the context, either ps1 or ps2.
586
587 If this is a continuation line, autoindent as necessary."""
588 if self.more:
589 prompt = str(sys.ps2)
590 else:
591 prompt = str(sys.ps1)
592 pos = self.GetCurLine()[1]
593 if pos > 0: self.write(os.linesep)
594 self.promptPos[0] = self.GetCurrentPos()
595 if not self.more: self.prompt1Pos[0] = self.GetCurrentPos()
596 self.write(prompt)
597 self.promptPos[1] = self.GetCurrentPos()
598 if not self.more:
599 self.prompt1Pos[1] = self.GetCurrentPos()
600 # Keep the undo feature from undoing previous responses.
601 self.EmptyUndoBuffer()
602 # XXX Add some autoindent magic here if more.
603 if self.more:
604 self.write(' '*4) # Temporary hack indentation.
605 self.EnsureCaretVisible()
606 self.ScrollToColumn(0)
607
608 def readIn(self):
609 """Replacement for stdin."""
610 prompt = 'Please enter your response:'
611 dialog = wxTextEntryDialog(None, prompt, \
612 'Input Dialog (Standard)', '')
613 try:
614 if dialog.ShowModal() == wxID_OK:
615 text = dialog.GetValue()
616 self.write(text + os.linesep)
617 return text
618 finally:
619 dialog.Destroy()
620 return ''
621
622 def readRaw(self, prompt='Please enter your response:'):
623 """Replacement for raw_input."""
624 dialog = wxTextEntryDialog(None, prompt, \
625 'Input Dialog (Raw)', '')
626 try:
627 if dialog.ShowModal() == wxID_OK:
628 text = dialog.GetValue()
629 return text
630 finally:
631 dialog.Destroy()
632 return ''
633
634 def ask(self, prompt='Please enter your response:'):
635 """Get response from the user."""
636 return raw_input(prompt=prompt)
637
638 def pause(self):
639 """Halt execution pending a response from the user."""
640 self.ask('Press enter to continue:')
641
642 def clear(self):
643 """Delete all text from the shell."""
644 self.ClearAll()
645
646 def run(self, command, prompt=1, verbose=1):
647 """Execute command within the shell as if it was typed in directly.
648 >>> shell.run('print "this"')
649 >>> print "this"
650 this
651 >>>
652 """
653 # Go to the very bottom of the text.
654 endpos = self.GetTextLength()
655 self.SetCurrentPos(endpos)
656 command = command.rstrip()
657 if prompt: self.prompt()
658 if verbose: self.write(command)
659 self.push(command)
660
661 def runfile(self, filename):
662 """Execute all commands in file as if they were typed into the shell."""
663 file = open(filename)
664 try:
665 self.prompt()
666 for command in file.readlines():
667 if command[:6] == 'shell.': # Run shell methods silently.
668 self.run(command, prompt=0, verbose=0)
669 else:
670 self.run(command, prompt=0, verbose=1)
671 finally:
672 file.close()
673
674 def autoCompleteShow(self, command):
675 """Display auto-completion popup list."""
676 list = self.interp.getAutoCompleteList(command, \
677 includeMagic=self.autoCompleteIncludeMagic, \
678 includeSingle=self.autoCompleteIncludeSingle, \
679 includeDouble=self.autoCompleteIncludeDouble)
680 if list:
681 options = ' '.join(list)
682 offset = 0
683 self.AutoCompShow(offset, options)
684
685 def autoCallTipShow(self, command):
686 """Display argument spec and docstring in a popup bubble thingie."""
687 if self.CallTipActive: self.CallTipCancel()
688 tip = self.interp.getCallTip(command)
689 if tip:
690 offset = self.GetCurrentPos()
691 self.CallTipShow(offset, tip)
692
693 def writeOut(self, text):
694 """Replacement for stdout."""
695 self.write(text)
696
697 def writeErr(self, text):
698 """Replacement for stderr."""
699 self.write(text)
700
701 def redirectStdin(self, redirect=1):
702 """If redirect is true then sys.stdin will come from the shell."""
703 if redirect:
704 sys.stdin = PseudoFileIn(self.readIn)
705 else:
706 sys.stdin = self.stdin
707
708 def redirectStdout(self, redirect=1):
709 """If redirect is true then sys.stdout will go to the shell."""
710 if redirect:
711 sys.stdout = PseudoFileOut(self.writeOut)
712 else:
713 sys.stdout = self.stdout
714
715 def redirectStderr(self, redirect=1):
716 """If redirect is true then sys.stderr will go to the shell."""
717 if redirect:
718 sys.stderr = PseudoFileErr(self.writeErr)
719 else:
720 sys.stderr = self.stderr
721
722 def CanCut(self):
723 """Return true if text is selected and can be cut."""
724 if self.GetSelectionStart() != self.GetSelectionEnd() \
725 and self.GetSelectionStart() >= self.prompt1Pos[1] \
726 and self.GetSelectionEnd() >= self.prompt1Pos[1]:
727 return 1
728 else:
729 return 0
730
731 def CanCopy(self):
732 """Return true if text is selected and can be copied."""
733 return self.GetSelectionStart() != self.GetSelectionEnd()
734
735 def CanPaste(self):
736 """Return true if a paste should succeed."""
737 if self.CanEdit() and wxStyledTextCtrl.CanPaste(self):
738 return 1
739 else:
740 return 0
741
742 def CanEdit(self):
743 """Return true if editing should succeed."""
744 return self.GetCurrentPos() >= self.prompt1Pos[1]
745
746 def Cut(self):
747 """Remove selection and place it on the clipboard."""
748 if self.CanCut() and self.CanCopy():
749 if self.AutoCompActive(): self.AutoCompCancel()
750 if self.CallTipActive: self.CallTipCancel()
751 self.Copy()
752 self.ReplaceSelection('')
753
754 def Copy(self):
755 """Copy selection and place it on the clipboard."""
756 if self.CanCopy():
757 command = self.GetSelectedText()
758 command = command.replace(os.linesep + sys.ps2, os.linesep)
759 command = command.replace(os.linesep + sys.ps1, os.linesep)
760 command = self.lstripPrompt(text=command)
761 data = wxTextDataObject(command)
762 if wxTheClipboard.Open():
763 wxTheClipboard.SetData(data)
764 wxTheClipboard.Close()
765
766 def CopyWithPrompts(self):
767 """Copy selection, including prompts, and place it on the clipboard."""
768 if self.CanCopy():
769 command = self.GetSelectedText()
770 data = wxTextDataObject(command)
771 if wxTheClipboard.Open():
772 wxTheClipboard.SetData(data)
773 wxTheClipboard.Close()
774
775 def Paste(self):
776 """Replace selection with clipboard contents."""
777 if self.CanPaste():
778 if wxTheClipboard.Open():
779 if wxTheClipboard.IsSupported(wxDataFormat(wxDF_TEXT)):
780 data = wxTextDataObject()
781 if wxTheClipboard.GetData(data):
782 command = data.GetText()
783 command = command.rstrip()
784 command = self.fixLineEndings(command)
785 command = self.lstripPrompt(text=command)
786 command = command.replace(os.linesep + sys.ps2, '\n')
787 command = command.replace(os.linesep, '\n')
788 command = command.replace('\n', os.linesep + sys.ps2)
789 self.ReplaceSelection('')
790 self.write(command)
791 wxTheClipboard.Close()
792
793
794 wxID_SELECTALL = NewId() # This *should* be defined by wxPython.
795 ID_AUTOCOMP = NewId()
796 ID_AUTOCOMP_SHOW = NewId()
797 ID_AUTOCOMP_INCLUDE_MAGIC = NewId()
798 ID_AUTOCOMP_INCLUDE_SINGLE = NewId()
799 ID_AUTOCOMP_INCLUDE_DOUBLE = NewId()
800 ID_CALLTIPS = NewId()
801 ID_CALLTIPS_SHOW = NewId()
802
803
804 class ShellMenu:
805 """Mixin class to add standard menu items."""
806
807 def createMenus(self):
808 m = self.fileMenu = wxMenu()
809 m.AppendSeparator()
810 m.Append(wxID_EXIT, 'E&xit', 'Exit PyCrust')
811
812 m = self.editMenu = wxMenu()
813 m.Append(wxID_UNDO, '&Undo \tCtrl+Z', 'Undo the last action')
814 m.Append(wxID_REDO, '&Redo \tCtrl+Y', 'Redo the last undone action')
815 m.AppendSeparator()
816 m.Append(wxID_CUT, 'Cu&t \tCtrl+X', 'Cut the selection')
817 m.Append(wxID_COPY, '&Copy \tCtrl+C', 'Copy the selection')
818 m.Append(wxID_PASTE, '&Paste \tCtrl+V', 'Paste')
819 m.AppendSeparator()
820 m.Append(wxID_CLEAR, 'Cle&ar', 'Delete the selection')
821 m.Append(wxID_SELECTALL, 'Select A&ll', 'Select all text')
822
823 m = self.autocompMenu = wxMenu()
824 m.Append(ID_AUTOCOMP_SHOW, 'Show Auto Completion', \
825 'Show auto completion during dot syntax', \
826 checkable=1)
827 m.Append(ID_AUTOCOMP_INCLUDE_MAGIC, 'Include Magic Attributes', \
828 'Include attributes visible to __getattr__ and __setattr__', \
829 checkable=1)
830 m.Append(ID_AUTOCOMP_INCLUDE_SINGLE, 'Include Single Underscores', \
831 'Include attibutes prefixed by a single underscore', \
832 checkable=1)
833 m.Append(ID_AUTOCOMP_INCLUDE_DOUBLE, 'Include Double Underscores', \
834 'Include attibutes prefixed by a double underscore', \
835 checkable=1)
836
837 m = self.calltipsMenu = wxMenu()
838 m.Append(ID_CALLTIPS_SHOW, 'Show Call Tips', \
839 'Show call tips with argument specifications', checkable=1)
840
841 m = self.optionsMenu = wxMenu()
842 m.AppendMenu(ID_AUTOCOMP, '&Auto Completion', self.autocompMenu, \
843 'Auto Completion Options')
844 m.AppendMenu(ID_CALLTIPS, '&Call Tips', self.calltipsMenu, \
845 'Call Tip Options')
846
847 m = self.helpMenu = wxMenu()
848 m.AppendSeparator()
849 m.Append(wxID_ABOUT, '&About...', 'About PyCrust')
850
851 b = self.menuBar = wxMenuBar()
852 b.Append(self.fileMenu, '&File')
853 b.Append(self.editMenu, '&Edit')
854 b.Append(self.optionsMenu, '&Options')
855 b.Append(self.helpMenu, '&Help')
856 self.SetMenuBar(b)
857
858 EVT_MENU(self, wxID_EXIT, self.OnExit)
859 EVT_MENU(self, wxID_UNDO, self.OnUndo)
860 EVT_MENU(self, wxID_REDO, self.OnRedo)
861 EVT_MENU(self, wxID_CUT, self.OnCut)
862 EVT_MENU(self, wxID_COPY, self.OnCopy)
863 EVT_MENU(self, wxID_PASTE, self.OnPaste)
864 EVT_MENU(self, wxID_CLEAR, self.OnClear)
865 EVT_MENU(self, wxID_SELECTALL, self.OnSelectAll)
866 EVT_MENU(self, wxID_ABOUT, self.OnAbout)
867 EVT_MENU(self, ID_AUTOCOMP_SHOW, \
868 self.OnAutoCompleteShow)
869 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_MAGIC, \
870 self.OnAutoCompleteIncludeMagic)
871 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_SINGLE, \
872 self.OnAutoCompleteIncludeSingle)
873 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_DOUBLE, \
874 self.OnAutoCompleteIncludeDouble)
875 EVT_MENU(self, ID_CALLTIPS_SHOW, \
876 self.OnCallTipsShow)
877
878 EVT_UPDATE_UI(self, wxID_UNDO, self.OnUpdateMenu)
879 EVT_UPDATE_UI(self, wxID_REDO, self.OnUpdateMenu)
880 EVT_UPDATE_UI(self, wxID_CUT, self.OnUpdateMenu)
881 EVT_UPDATE_UI(self, wxID_COPY, self.OnUpdateMenu)
882 EVT_UPDATE_UI(self, wxID_PASTE, self.OnUpdateMenu)
883 EVT_UPDATE_UI(self, wxID_CLEAR, self.OnUpdateMenu)
884 EVT_UPDATE_UI(self, ID_AUTOCOMP_SHOW, self.OnUpdateMenu)
885 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_MAGIC, self.OnUpdateMenu)
886 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_SINGLE, self.OnUpdateMenu)
887 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_DOUBLE, self.OnUpdateMenu)
888 EVT_UPDATE_UI(self, ID_CALLTIPS_SHOW, self.OnUpdateMenu)
889
890 def OnExit(self, event):
891 self.Close(true)
892
893 def OnUndo(self, event):
894 self.shell.Undo()
895
896 def OnRedo(self, event):
897 self.shell.Redo()
898
899 def OnCut(self, event):
900 self.shell.Cut()
901
902 def OnCopy(self, event):
903 self.shell.Copy()
904
905 def OnPaste(self, event):
906 self.shell.Paste()
907
908 def OnClear(self, event):
909 self.shell.Clear()
910
911 def OnSelectAll(self, event):
912 self.shell.SelectAll()
913
914 def OnAbout(self, event):
915 """Display an About PyCrust window."""
916 import sys
917 title = 'About PyCrust'
918 text = 'PyCrust %s\n\n' % VERSION + \
919 'Yet another Python shell, only flakier.\n\n' + \
920 'Half-baked by Patrick K. O\'Brien,\n' + \
921 'the other half is still in the oven.\n\n' + \
922 'Shell Revision: %s\n' % self.shell.revision + \
923 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \
924 'Python Version: %s\n' % sys.version.split()[0] + \
925 'wxPython Version: %s\n' % wx.__version__ + \
926 'Platform: %s\n' % sys.platform
927 dialog = wxMessageDialog(self, text, title, wxOK | wxICON_INFORMATION)
928 dialog.ShowModal()
929 dialog.Destroy()
930
931 def OnAutoCompleteShow(self, event):
932 self.shell.autoComplete = event.IsChecked()
933
934 def OnAutoCompleteIncludeMagic(self, event):
935 self.shell.autoCompleteIncludeMagic = event.IsChecked()
936
937 def OnAutoCompleteIncludeSingle(self, event):
938 self.shell.autoCompleteIncludeSingle = event.IsChecked()
939
940 def OnAutoCompleteIncludeDouble(self, event):
941 self.shell.autoCompleteIncludeDouble = event.IsChecked()
942
943 def OnCallTipsShow(self, event):
944 self.shell.autoCallTip = event.IsChecked()
945
946 def OnUpdateMenu(self, event):
947 """Update menu items based on current status."""
948 id = event.GetId()
949 if id == wxID_UNDO:
950 event.Enable(self.shell.CanUndo())
951 elif id == wxID_REDO:
952 event.Enable(self.shell.CanRedo())
953 elif id == wxID_CUT:
954 event.Enable(self.shell.CanCut())
955 elif id == wxID_COPY:
956 event.Enable(self.shell.CanCopy())
957 elif id == wxID_PASTE:
958 event.Enable(self.shell.CanPaste())
959 elif id == wxID_CLEAR:
960 event.Enable(self.shell.CanCut())
961 elif id == ID_AUTOCOMP_SHOW:
962 event.Check(self.shell.autoComplete)
963 elif id == ID_AUTOCOMP_INCLUDE_MAGIC:
964 event.Check(self.shell.autoCompleteIncludeMagic)
965 elif id == ID_AUTOCOMP_INCLUDE_SINGLE:
966 event.Check(self.shell.autoCompleteIncludeSingle)
967 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE:
968 event.Check(self.shell.autoCompleteIncludeDouble)
969 elif id == ID_CALLTIPS_SHOW:
970 event.Check(self.shell.autoCallTip)
971
972
973 class ShellFrame(wxFrame, ShellMenu):
974 """Frame containing the PyCrust shell component."""
975
976 name = 'PyCrust Shell Frame'
977 revision = __version__
978
979 def __init__(self, parent=None, id=-1, title='PyShell', \
980 pos=wxDefaultPosition, size=wxDefaultSize, \
981 style=wxDEFAULT_FRAME_STYLE, locals=None, \
982 InterpClass=None, *args, **kwds):
983 """Create a PyCrust ShellFrame instance."""
984 wxFrame.__init__(self, parent, id, title, pos, size, style)
985 intro = 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
986 intro += '\nSponsored by Orbtech.com - Your Source For Python Development Services'
987 self.CreateStatusBar()
988 self.SetStatusText(intro.replace('\n', ', '))
989 if wxPlatform == '__WXMSW__':
990 import os
991 filename = os.path.join(os.path.dirname(__file__), 'PyCrust.ico')
992 icon = wxIcon(filename, wxBITMAP_TYPE_ICO)
993 self.SetIcon(icon)
994 self.shell = Shell(parent=self, id=-1, introText=intro, \
995 locals=locals, InterpClass=InterpClass, \
996 *args, **kwds)
997 # Override the shell so that status messages go to the status bar.
998 self.shell.setStatusText = self.SetStatusText
999 self.createMenus()
1000
1001
1002
1003