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