]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/shell.py
a86d637f354be663238dd70dc9f02eed7c2eb8dc
[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
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 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__': 'PyShell',
129 '__doc__': 'PyShell, 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 key = event.KeyCode()
287 currpos = self.GetCurrentPos()
288 if currpos < self.prompt1Pos[1]:
289 wxBell()
290 return
291 stoppos = self.promptPos[1]
292 if key == ord('.'):
293 # The dot or period key activates auto completion.
294 # Get the command between the prompt and the cursor.
295 # Add a dot to the end of the command.
296 command = self.GetTextRange(stoppos, currpos) + '.'
297 self.write('.')
298 if self.autoComplete: self.autoCompleteShow(command)
299 elif key == ord('('):
300 # The left paren activates a call tip and cancels
301 # an active auto completion.
302 if self.AutoCompActive(): self.AutoCompCancel()
303 # Get the command between the prompt and the cursor.
304 # Add the '(' to the end of the command.
305 command = self.GetTextRange(stoppos, currpos) + '('
306 self.write('(')
307 if self.autoCallTip: self.autoCallTipShow(command)
308 else:
309 # Allow the normal event handling to take place.
310 event.Skip()
311
312 def OnKeyDown(self, event):
313 """Key down event handler.
314
315 Prevents modification of previously submitted commands/responses."""
316 key = event.KeyCode()
317 currpos = self.GetCurrentPos()
318 stoppos = self.promptPos[1]
319 # If the auto-complete window is up let it do its thing.
320 if self.AutoCompActive():
321 event.Skip()
322 # Retrieve the previous command from the history buffer.
323 elif (event.ControlDown() and key == WXK_UP) \
324 or (event.AltDown() and key in (ord('P'), ord('p'))):
325 self.OnHistoryRetrieve(step=+1)
326 # Retrieve the next command from the history buffer.
327 elif (event.ControlDown() and key == WXK_DOWN) \
328 or (event.AltDown() and key in (ord('N'), ord('n'))):
329 self.OnHistoryRetrieve(step=-1)
330 # Search up the history for the text in front of the cursor.
331 elif key == WXK_F8:
332 self.OnHistorySearch()
333 # Return is used to submit a command to the interpreter.
334 elif key == WXK_RETURN:
335 if self.CallTipActive: self.CallTipCancel()
336 self.processLine()
337 # Home needs to be aware of the prompt.
338 elif key == WXK_HOME:
339 if currpos >= stoppos:
340 if event.ShiftDown():
341 # Select text from current position to end of prompt.
342 self.SetSelection(self.GetCurrentPos(), stoppos)
343 else:
344 self.SetCurrentPos(stoppos)
345 self.SetAnchor(stoppos)
346 else:
347 event.Skip()
348 # Basic navigation keys should work anywhere.
349 elif key in (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, \
350 WXK_PRIOR, WXK_NEXT):
351 event.Skip()
352 # Don't backspace over the latest prompt.
353 elif key == WXK_BACK:
354 if currpos > self.prompt1Pos[1]:
355 event.Skip()
356 # Only allow these keys after the latest prompt.
357 elif key in (WXK_TAB, WXK_DELETE):
358 if currpos >= self.prompt1Pos[1]:
359 event.Skip()
360 # Don't toggle between insert mode and overwrite mode.
361 elif key == WXK_INSERT:
362 pass
363 else:
364 event.Skip()
365
366 def OnHistoryRetrieve(self, step):
367 """Retrieve the previous/next command from the history buffer."""
368 startpos = self.GetCurrentPos()
369 if startpos < self.prompt1Pos[1]:
370 wxBell()
371 return
372 newindex = self.historyIndex + step
373 if not (-1 <= newindex < len(self.history)):
374 wxBell()
375 return
376 self.historyIndex = newindex
377 if newindex == -1:
378 self.ReplaceSelection('')
379 else:
380 self.ReplaceSelection('')
381 command = self.history[self.historyIndex]
382 command = command.replace('\n', os.linesep + sys.ps2)
383 self.ReplaceSelection(command)
384 endpos = self.GetCurrentPos()
385 self.SetSelection(endpos, startpos)
386
387 def OnHistorySearch(self):
388 """Search up the history buffer for the text in front of the cursor."""
389 startpos = self.GetCurrentPos()
390 if startpos < self.prompt1Pos[1]:
391 wxBell()
392 return
393 # The text up to the cursor is what we search for.
394 numCharsAfterCursor = self.GetTextLength() - startpos
395 searchText = self.getCommand(rstrip=0)
396 if numCharsAfterCursor > 0:
397 searchText = searchText[:-numCharsAfterCursor]
398 if not searchText:
399 return
400 # Search upwards from the current history position and loop back
401 # to the beginning if we don't find anything.
402 if (self.historyIndex <= -1) \
403 or (self.historyIndex >= len(self.history)-2):
404 searchOrder = range(len(self.history))
405 else:
406 searchOrder = range(self.historyIndex+1, len(self.history)) + \
407 range(self.historyIndex)
408 for i in searchOrder:
409 command = self.history[i]
410 if command[:len(searchText)] == searchText:
411 # Replace the current selection with the one we've found.
412 self.ReplaceSelection(command[len(searchText):])
413 endpos = self.GetCurrentPos()
414 self.SetSelection(endpos, startpos)
415 # We've now warped into middle of the history.
416 self.historyIndex = i
417 break
418
419 def setStatusText(self, text):
420 """Display status information."""
421
422 # This method will most likely be replaced by the enclosing app
423 # to do something more interesting, like write to a status bar.
424 print text
425
426 def processLine(self):
427 """Process the line of text at which the user hit Enter."""
428
429 # The user hit ENTER and we need to decide what to do. They could be
430 # sitting on any line in the shell.
431
432 thepos = self.GetCurrentPos()
433 endpos = self.GetTextLength()
434 # If they hit RETURN at the very bottom, execute the command.
435 if thepos == endpos:
436 self.interp.more = 0
437 if self.getCommand():
438 command = self.GetTextRange(self.prompt1Pos[1], endpos)
439 else:
440 command = self.GetTextRange(self.prompt1Pos[1], \
441 self.promptPos[1])
442 command = command.replace(os.linesep + sys.ps2, '\n')
443 self.push(command)
444 # Otherwise, replace the last command with the new command.
445 elif thepos < self.prompt1Pos[0]:
446 theline = self.GetCurrentLine()
447 command = self.getCommand()
448 # If the new line contains a command (even an invalid one).
449 if command:
450 self.SetCurrentPos(endpos)
451 startpos = self.prompt1Pos[1]
452 self.SetSelection(startpos, endpos)
453 self.ReplaceSelection('')
454 self.write(command)
455 self.more = 0
456 # Otherwise, put the cursor back where we started.
457 else:
458 self.SetCurrentPos(thepos)
459 self.SetAnchor(thepos)
460
461 def getCommand(self, text=None, rstrip=1):
462 """Extract a command from text which may include a shell prompt.
463
464 The command may not necessarily be valid Python syntax."""
465 if not text:
466 text = self.GetCurLine()[0]
467 # XXX Need to extract real prompts here. Need to keep track of the
468 # prompt every time a command is issued.
469 ps1 = str(sys.ps1)
470 ps1size = len(ps1)
471 ps2 = str(sys.ps2)
472 ps2size = len(ps2)
473 if rstrip:
474 text = text.rstrip()
475 # Strip the prompt off the front of text leaving just the command.
476 if text[:ps1size] == ps1:
477 command = text[ps1size:]
478 elif text[:ps2size] == ps2:
479 command = text[ps2size:]
480 else:
481 command = ''
482 return command
483
484 def push(self, command):
485 """Send command to the interpreter for execution."""
486 self.write(os.linesep)
487 self.more = self.interp.push(command)
488 if not self.more:
489 self.addHistory(command.rstrip())
490 self.prompt()
491 # Keep the undo feature from undoing previous responses. The only
492 # thing that can be undone is stuff typed after the prompt, before
493 # hitting enter. After they hit enter it becomes permanent.
494 self.EmptyUndoBuffer()
495
496 def addHistory(self, command):
497 """Add command to the command history."""
498 # Reset the history position.
499 self.historyIndex = -1
500 # Insert this command into the history, unless it's a blank
501 # line or the same as the last command.
502 if command != '' \
503 and (len(self.history) == 0 or command != self.history[0]):
504 self.history.insert(0, command)
505
506 def write(self, text):
507 """Display text in the shell.
508
509 Replace line endings with OS-specific endings."""
510 lines = text.split('\r\n')
511 for l in range(len(lines)):
512 chunks = lines[l].split('\r')
513 for c in range(len(chunks)):
514 chunks[c] = os.linesep.join(chunks[c].split('\n'))
515 lines[l] = os.linesep.join(chunks)
516 text = os.linesep.join(lines)
517 self.AddText(text)
518 self.EnsureCaretVisible()
519 #self.ScrollToColumn(0)
520
521 def prompt(self):
522 """Display appropriate prompt for the context, either ps1 or ps2.
523
524 If this is a continuation line, autoindent as necessary."""
525 if self.more:
526 prompt = str(sys.ps2)
527 else:
528 prompt = str(sys.ps1)
529 pos = self.GetCurLine()[1]
530 if pos > 0: self.write(os.linesep)
531 self.promptPos[0] = self.GetCurrentPos()
532 if not self.more: self.prompt1Pos[0] = self.GetCurrentPos()
533 self.write(prompt)
534 self.promptPos[1] = self.GetCurrentPos()
535 if not self.more: self.prompt1Pos[1] = self.GetCurrentPos()
536 # XXX Add some autoindent magic here if more.
537 if self.more:
538 self.write(' '*4) # Temporary hack indentation.
539 self.EnsureCaretVisible()
540 self.ScrollToColumn(0)
541
542 def readIn(self):
543 """Replacement for stdin."""
544 prompt = 'Please enter your response:'
545 dialog = wxTextEntryDialog(None, prompt, \
546 'Input Dialog (Standard)', '')
547 try:
548 if dialog.ShowModal() == wxID_OK:
549 text = dialog.GetValue()
550 self.write(text + os.linesep)
551 return text
552 finally:
553 dialog.Destroy()
554 return ''
555
556 def readRaw(self, prompt='Please enter your response:'):
557 """Replacement for raw_input."""
558 dialog = wxTextEntryDialog(None, prompt, \
559 'Input Dialog (Raw)', '')
560 try:
561 if dialog.ShowModal() == wxID_OK:
562 text = dialog.GetValue()
563 return text
564 finally:
565 dialog.Destroy()
566 return ''
567
568 def ask(self, prompt='Please enter your response:'):
569 """Get response from the user."""
570 return raw_input(prompt=prompt)
571
572 def pause(self):
573 """Halt execution pending a response from the user."""
574 self.ask('Press enter to continue:')
575
576 def clear(self):
577 """Delete all text from the shell."""
578 self.ClearAll()
579
580 def run(self, command, prompt=1, verbose=1):
581 """Execute command within the shell as if it was typed in directly.
582 >>> shell.run('print "this"')
583 >>> print "this"
584 this
585 >>>
586 """
587 # Go to the very bottom of the text.
588 endpos = self.GetTextLength()
589 self.SetCurrentPos(endpos)
590 command = command.rstrip()
591 if prompt: self.prompt()
592 if verbose: self.write(command)
593 self.push(command)
594
595 def runfile(self, filename):
596 """Execute all commands in file as if they were typed into the shell."""
597 file = open(filename)
598 try:
599 self.prompt()
600 for command in file.readlines():
601 if command[:6] == 'shell.': # Run shell methods silently.
602 self.run(command, prompt=0, verbose=0)
603 else:
604 self.run(command, prompt=0, verbose=1)
605 finally:
606 file.close()
607
608 def autoCompleteShow(self, command):
609 """Display auto-completion popup list."""
610 list = self.interp.getAutoCompleteList(command, \
611 includeMagic=self.autoCompleteIncludeMagic, \
612 includeSingle=self.autoCompleteIncludeSingle, \
613 includeDouble=self.autoCompleteIncludeDouble)
614 if list:
615 options = ' '.join(list)
616 offset = 0
617 self.AutoCompShow(offset, options)
618
619 def autoCallTipShow(self, command):
620 """Display argument spec and docstring in a popup bubble thingie."""
621 if self.CallTipActive: self.CallTipCancel()
622 tip = self.interp.getCallTip(command)
623 if tip:
624 offset = self.GetCurrentPos()
625 self.CallTipShow(offset, tip)
626
627 def writeOut(self, text):
628 """Replacement for stdout."""
629 self.write(text)
630
631 def writeErr(self, text):
632 """Replacement for stderr."""
633 self.write(text)
634
635 def redirectStdin(self, redirect=1):
636 """If redirect is true then sys.stdin will come from the shell."""
637 if redirect:
638 sys.stdin = PseudoFileIn(self.readIn)
639 else:
640 sys.stdin = self.stdin
641
642 def redirectStdout(self, redirect=1):
643 """If redirect is true then sys.stdout will go to the shell."""
644 if redirect:
645 sys.stdout = PseudoFileOut(self.writeOut)
646 else:
647 sys.stdout = self.stdout
648
649 def redirectStderr(self, redirect=1):
650 """If redirect is true then sys.stderr will go to the shell."""
651 if redirect:
652 sys.stderr = PseudoFileErr(self.writeErr)
653 else:
654 sys.stderr = self.stderr
655
656 def CanCut(self):
657 """Return true if text is selected and can be cut."""
658 return self.GetSelectionStart() != self.GetSelectionEnd()
659
660 def CanCopy(self):
661 """Return true if text is selected and can be copied."""
662 return self.GetSelectionStart() != self.GetSelectionEnd()
663
664
665 wxID_SELECTALL = NewId() # This *should* be defined by wxPython.
666 ID_AUTOCOMP = NewId()
667 ID_AUTOCOMP_SHOW = NewId()
668 ID_AUTOCOMP_INCLUDE_MAGIC = NewId()
669 ID_AUTOCOMP_INCLUDE_SINGLE = NewId()
670 ID_AUTOCOMP_INCLUDE_DOUBLE = NewId()
671 ID_CALLTIPS = NewId()
672 ID_CALLTIPS_SHOW = NewId()
673
674
675 class ShellMenu:
676 """Mixin class to add standard menu items."""
677
678 def createMenus(self):
679 m = self.fileMenu = wxMenu()
680 m.AppendSeparator()
681 m.Append(wxID_EXIT, 'E&xit', 'Exit PyCrust')
682
683 m = self.editMenu = wxMenu()
684 m.Append(wxID_UNDO, '&Undo \tCtrl+Z', 'Undo the last action')
685 m.Append(wxID_REDO, '&Redo \tCtrl+Y', 'Redo the last undone action')
686 m.AppendSeparator()
687 m.Append(wxID_CUT, 'Cu&t', 'Cut the selection')
688 m.Append(wxID_COPY, '&Copy', 'Copy the selection')
689 m.Append(wxID_PASTE, '&Paste', 'Paste')
690 m.AppendSeparator()
691 m.Append(wxID_CLEAR, 'Cle&ar', 'Delete the selection')
692 m.Append(wxID_SELECTALL, 'Select A&ll', 'Select all text')
693
694 m = self.autocompMenu = wxMenu()
695 m.Append(ID_AUTOCOMP_SHOW, 'Show Auto Completion', \
696 'Show auto completion during dot syntax', \
697 checkable=1)
698 m.Append(ID_AUTOCOMP_INCLUDE_MAGIC, 'Include Magic Attributes', \
699 'Include attributes visible to __getattr__ and __setattr__', \
700 checkable=1)
701 m.Append(ID_AUTOCOMP_INCLUDE_SINGLE, 'Include Single Underscores', \
702 'Include attibutes prefixed by a single underscore', \
703 checkable=1)
704 m.Append(ID_AUTOCOMP_INCLUDE_DOUBLE, 'Include Double Underscores', \
705 'Include attibutes prefixed by a double underscore', \
706 checkable=1)
707
708 m = self.calltipsMenu = wxMenu()
709 m.Append(ID_CALLTIPS_SHOW, 'Show Call Tips', \
710 'Show call tips with argument specifications', checkable=1)
711
712 m = self.optionsMenu = wxMenu()
713 m.AppendMenu(ID_AUTOCOMP, '&Auto Completion', self.autocompMenu, \
714 'Auto Completion Options')
715 m.AppendMenu(ID_CALLTIPS, '&Call Tips', self.calltipsMenu, \
716 'Call Tip Options')
717
718 m = self.helpMenu = wxMenu()
719 m.AppendSeparator()
720 m.Append(wxID_ABOUT, '&About...', 'About PyCrust')
721
722 b = self.menuBar = wxMenuBar()
723 b.Append(self.fileMenu, '&File')
724 b.Append(self.editMenu, '&Edit')
725 b.Append(self.optionsMenu, '&Options')
726 b.Append(self.helpMenu, '&Help')
727 self.SetMenuBar(b)
728
729 EVT_MENU(self, wxID_EXIT, self.OnExit)
730 EVT_MENU(self, wxID_UNDO, self.OnUndo)
731 EVT_MENU(self, wxID_REDO, self.OnRedo)
732 EVT_MENU(self, wxID_CUT, self.OnCut)
733 EVT_MENU(self, wxID_COPY, self.OnCopy)
734 EVT_MENU(self, wxID_PASTE, self.OnPaste)
735 EVT_MENU(self, wxID_CLEAR, self.OnClear)
736 EVT_MENU(self, wxID_SELECTALL, self.OnSelectAll)
737 EVT_MENU(self, wxID_ABOUT, self.OnAbout)
738 EVT_MENU(self, ID_AUTOCOMP_SHOW, \
739 self.OnAutoCompleteShow)
740 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_MAGIC, \
741 self.OnAutoCompleteIncludeMagic)
742 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_SINGLE, \
743 self.OnAutoCompleteIncludeSingle)
744 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_DOUBLE, \
745 self.OnAutoCompleteIncludeDouble)
746 EVT_MENU(self, ID_CALLTIPS_SHOW, \
747 self.OnCallTipsShow)
748
749 EVT_UPDATE_UI(self, wxID_UNDO, self.OnUpdateMenu)
750 EVT_UPDATE_UI(self, wxID_REDO, self.OnUpdateMenu)
751 EVT_UPDATE_UI(self, wxID_CUT, self.OnUpdateMenu)
752 EVT_UPDATE_UI(self, wxID_COPY, self.OnUpdateMenu)
753 EVT_UPDATE_UI(self, wxID_PASTE, self.OnUpdateMenu)
754 EVT_UPDATE_UI(self, wxID_CLEAR, self.OnUpdateMenu)
755 EVT_UPDATE_UI(self, ID_AUTOCOMP_SHOW, self.OnUpdateMenu)
756 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_MAGIC, self.OnUpdateMenu)
757 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_SINGLE, self.OnUpdateMenu)
758 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_DOUBLE, self.OnUpdateMenu)
759 EVT_UPDATE_UI(self, ID_CALLTIPS_SHOW, self.OnUpdateMenu)
760
761 def OnExit(self, event):
762 self.Close(true)
763
764 def OnUndo(self, event):
765 self.shell.Undo()
766
767 def OnRedo(self, event):
768 self.shell.Redo()
769
770 def OnCut(self, event):
771 self.shell.Cut()
772
773 def OnCopy(self, event):
774 self.shell.Copy()
775
776 def OnPaste(self, event):
777 self.shell.Paste()
778
779 def OnClear(self, event):
780 self.shell.Clear()
781
782 def OnSelectAll(self, event):
783 self.shell.SelectAll()
784
785 def OnAbout(self, event):
786 """Display an About PyCrust window."""
787 import sys
788 title = 'About PyCrust'
789 text = 'PyCrust %s\n\n' % VERSION + \
790 'Yet another Python shell, only flakier.\n\n' + \
791 'Half-baked by Patrick K. O\'Brien,\n' + \
792 'the other half is still in the oven.\n\n' + \
793 'Shell Revision: %s\n' % self.shell.revision + \
794 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \
795 'Python Version: %s\n' % sys.version.split()[0] + \
796 'wxPython Version: %s\n' % wx.__version__ + \
797 'Platform: %s\n' % sys.platform
798 dialog = wxMessageDialog(self, text, title, wxOK | wxICON_INFORMATION)
799 dialog.ShowModal()
800 dialog.Destroy()
801
802 def OnAutoCompleteShow(self, event):
803 self.shell.autoComplete = event.IsChecked()
804
805 def OnAutoCompleteIncludeMagic(self, event):
806 self.shell.autoCompleteIncludeMagic = event.IsChecked()
807
808 def OnAutoCompleteIncludeSingle(self, event):
809 self.shell.autoCompleteIncludeSingle = event.IsChecked()
810
811 def OnAutoCompleteIncludeDouble(self, event):
812 self.shell.autoCompleteIncludeDouble = event.IsChecked()
813
814 def OnCallTipsShow(self, event):
815 self.shell.autoCallTip = event.IsChecked()
816
817 def OnUpdateMenu(self, event):
818 """Update menu items based on current status."""
819 id = event.GetId()
820 if id == wxID_UNDO:
821 event.Enable(self.shell.CanUndo())
822 elif id == wxID_REDO:
823 event.Enable(self.shell.CanRedo())
824 elif id == wxID_CUT:
825 event.Enable(self.shell.CanCut())
826 elif id == wxID_COPY:
827 event.Enable(self.shell.CanCopy())
828 elif id == wxID_PASTE:
829 event.Enable(self.shell.CanPaste())
830 elif id == wxID_CLEAR:
831 event.Enable(self.shell.CanCut())
832 elif id == ID_AUTOCOMP_SHOW:
833 event.Check(self.shell.autoComplete)
834 elif id == ID_AUTOCOMP_INCLUDE_MAGIC:
835 event.Check(self.shell.autoCompleteIncludeMagic)
836 elif id == ID_AUTOCOMP_INCLUDE_SINGLE:
837 event.Check(self.shell.autoCompleteIncludeSingle)
838 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE:
839 event.Check(self.shell.autoCompleteIncludeDouble)
840 elif id == ID_CALLTIPS_SHOW:
841 event.Check(self.shell.autoCallTip)
842
843
844 class ShellFrame(wxFrame, ShellMenu):
845 """Frame containing the PyCrust shell component."""
846
847 name = 'PyCrust Shell Frame'
848 revision = __version__
849
850 def __init__(self, parent=None, id=-1, title='PyShell', \
851 pos=wxDefaultPosition, size=wxDefaultSize, \
852 style=wxDEFAULT_FRAME_STYLE, locals=None, \
853 InterpClass=None, *args, **kwds):
854 """Create a PyCrust ShellFrame instance."""
855 wxFrame.__init__(self, parent, id, title, pos, size, style)
856 intro = 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
857 self.CreateStatusBar()
858 self.SetStatusText(intro)
859 if wxPlatform == '__WXMSW__':
860 icon = wxIcon('PyCrust.ico', wxBITMAP_TYPE_ICO)
861 self.SetIcon(icon)
862 self.shell = Shell(parent=self, id=-1, introText=intro, \
863 locals=locals, InterpClass=InterpClass, \
864 *args, **kwds)
865 # Override the shell so that status messages go to the status bar.
866 self.shell.setStatusText = self.SetStatusText
867 self.createMenus()
868
869
870