]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/shell.py
2c3c9e2d464ec125d770fb6845fddb25a4114e5c
[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' : 8,
29 'lnsize' : 7,
30 'backcol': '#FFFFFF',
31 }
32 else: # GTK
33 faces = { 'times' : 'Times',
34 'mono' : 'Courier',
35 'helv' : 'Helvetica',
36 'other' : 'new century schoolbook',
37 'size' : 12,
38 'lnsize' : 10,
39 'backcol': '#FFFFFF',
40 }
41
42
43 class Shell(wxStyledTextCtrl):
44 """PyCrust Shell based on wxStyledTextCtrl."""
45
46 name = 'PyCrust Shell'
47 revision = __version__
48
49 def __init__(self, parent, id=-1, pos=wxDefaultPosition, \
50 size=wxDefaultSize, style=wxCLIP_CHILDREN, introText='', \
51 locals=None, InterpClass=None, *args, **kwds):
52 """Create a PyCrust Shell instance."""
53 wxStyledTextCtrl.__init__(self, parent, id, pos, size, style)
54 # Grab these so they can be restored by self.redirect* methods.
55 self.stdin = sys.stdin
56 self.stdout = sys.stdout
57 self.stderr = sys.stderr
58 # Add the current working directory "." to the search path.
59 sys.path.insert(0, os.curdir)
60 # Import a default interpreter class if one isn't provided.
61 if InterpClass == None:
62 from interpreter import Interpreter
63 else:
64 Interpreter = InterpClass
65 # Create default locals so we have something interesting.
66 shellLocals = {'__name__': 'PyShell',
67 '__doc__': 'PyShell, The PyCrust Python Shell.',
68 '__version__': VERSION,
69 }
70 # Add the dictionary that was passed in.
71 if locals:
72 shellLocals.update(locals)
73 self.interp = Interpreter(locals=shellLocals, \
74 rawin=self.readRaw, \
75 stdin=PseudoFileIn(self.readIn), \
76 stdout=PseudoFileOut(self.writeOut), \
77 stderr=PseudoFileErr(self.writeErr), \
78 *args, **kwds)
79 # Keep track of the most recent prompt starting and ending positions.
80 self.promptPos = [0, 0]
81 # Keep track of multi-line commands.
82 self.more = 0
83 # Create the command history. Commands are added into the front of
84 # the list (ie. at index 0) as they are entered. self.historyPos is
85 # the current position in the history; it gets incremented as you
86 # retrieve the previous command, decremented as you retrieve the next,
87 # and reset when you hit Enter. self.historyPos == -1 means you're on
88 # the current command, not in the history. self.tempCommand is
89 # storage space for whatever was on the last line when you first hit
90 # "Retrieve-Previous", so that the final "Retrieve-Next" will restore
91 # whatever was originally there. self.lastCommandRecalled remembers
92 # the index of the last command to be recalled from the history, so
93 # you can repeat a group of commands by going up-up-up-enter to find
94 # the first one in the group then down-enter-down-enter to recall each
95 # subsequent command. Also useful for multiline commands, in lieu of
96 # a proper implementation of those.
97 self.history = []
98 self.historyPos = -1
99 self.tempCommand = ''
100 self.lastCommandRecalled = -1
101 # Assign handlers for keyboard events.
102 EVT_KEY_DOWN(self, self.OnKeyDown)
103 EVT_CHAR(self, self.OnChar)
104 # Configure various defaults and user preferences.
105 self.config()
106 # Display the introductory banner information.
107 try: self.showIntro(introText)
108 except: pass
109 # Assign some pseudo keywords to the interpreter's namespace.
110 try: self.setBuiltinKeywords()
111 except: pass
112 # Add 'shell' to the interpreter's local namespace.
113 try: self.setLocalShell()
114 except: pass
115 # Do this last so the user has complete control over their
116 # environment. They can override anything they want.
117 try: self.execStartupScript(self.interp.startupScript)
118 except: pass
119
120 def destroy(self):
121 del self.interp
122
123 def config(self):
124 """Configure shell based on user preferences."""
125 self.SetMarginType(1, wxSTC_MARGIN_NUMBER)
126 self.SetMarginWidth(1, 40)
127
128 self.SetLexer(wxSTC_LEX_PYTHON)
129 self.SetKeyWords(0, ' '.join(keyword.kwlist))
130
131 self.setStyles(faces)
132 self.SetViewWhiteSpace(0)
133 self.SetTabWidth(4)
134 self.SetUseTabs(0)
135 # Do we want to automatically pop up command completion options?
136 self.autoComplete = 1
137 self.autoCompleteIncludeMagic = 1
138 self.autoCompleteIncludeSingle = 1
139 self.autoCompleteIncludeDouble = 1
140 self.autoCompleteCaseInsensitive = 1
141 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
142 # De we want to automatically pop up command argument help?
143 self.autoCallTip = 1
144 self.CallTipSetBackground(wxColour(255, 255, 232))
145
146 def showIntro(self, text=''):
147 """Display introductory text in the shell."""
148 if text:
149 if not text.endswith(os.linesep): text += os.linesep
150 self.write(text)
151 try:
152 self.write(self.interp.introText)
153 except AttributeError:
154 pass
155
156 def setBuiltinKeywords(self):
157 """Create pseudo keywords as part of builtins.
158
159 This is a rather clever hack that sets "close", "exit" and "quit"
160 to a PseudoKeyword object so that we can make them do what we want.
161 In this case what we want is to call our self.quit() method.
162 The user can type "close", "exit" or "quit" without the final parens.
163 """
164 ## POB: This is having some weird side-effects so I'm taking it out.
165 ## import __builtin__
166 ## from pseudo import PseudoKeyword
167 ## __builtin__.close = __builtin__.exit = __builtin__.quit = \
168 ## PseudoKeyword(self.quit)
169 import __builtin__
170 from pseudo import PseudoKeyword
171 __builtin__.close = __builtin__.exit = __builtin__.quit = \
172 'Click on the close button to leave the application.'
173
174 def quit(self):
175 """Quit the application."""
176
177 # XXX Good enough for now but later we want to send a close event.
178
179 # In the close event handler we can prompt to make sure they want to quit.
180 # Other applications, like PythonCard, may choose to hide rather than
181 # quit so we should just post the event and let the surrounding app
182 # decide what it wants to do.
183 self.write('Click on the close button to leave the application.')
184
185 def setLocalShell(self):
186 """Add 'shell' to locals."""
187 self.interp.locals['shell'] = self
188
189 def execStartupScript(self, startupScript):
190 """Execute the user's PYTHONSTARTUP script if they have one."""
191 if startupScript and os.path.isfile(startupScript):
192 startupText = 'Startup script executed: ' + startupScript
193 self.push('print %s;execfile(%s)' % \
194 (`startupText`, `startupScript`))
195 else:
196 self.push('')
197
198 def setStyles(self, faces):
199 """Configure font size, typeface and color for lexer."""
200
201 # Default style
202 self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
203
204 self.StyleClearAll()
205
206 # Built in styles
207 self.StyleSetSpec(wxSTC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces)
208 self.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces)
209 self.StyleSetSpec(wxSTC_STYLE_BRACELIGHT, "fore:#0000FF,back:#FFFF88")
210 self.StyleSetSpec(wxSTC_STYLE_BRACEBAD, "fore:#FF0000,back:#FFFF88")
211
212 # Python styles
213 self.StyleSetSpec(wxSTC_P_DEFAULT, "face:%(mono)s" % faces)
214 self.StyleSetSpec(wxSTC_P_COMMENTLINE, "fore:#007F00,face:%(mono)s" % faces)
215 self.StyleSetSpec(wxSTC_P_NUMBER, "")
216 self.StyleSetSpec(wxSTC_P_STRING, "fore:#7F007F,face:%(mono)s" % faces)
217 self.StyleSetSpec(wxSTC_P_CHARACTER, "fore:#7F007F,face:%(mono)s" % faces)
218 self.StyleSetSpec(wxSTC_P_WORD, "fore:#00007F,bold")
219 self.StyleSetSpec(wxSTC_P_TRIPLE, "fore:#7F0000")
220 self.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE, "fore:#000033,back:#FFFFE8")
221 self.StyleSetSpec(wxSTC_P_CLASSNAME, "fore:#0000FF,bold")
222 self.StyleSetSpec(wxSTC_P_DEFNAME, "fore:#007F7F,bold")
223 self.StyleSetSpec(wxSTC_P_OPERATOR, "")
224 self.StyleSetSpec(wxSTC_P_IDENTIFIER, "")
225 self.StyleSetSpec(wxSTC_P_COMMENTBLOCK, "fore:#7F7F7F")
226 self.StyleSetSpec(wxSTC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces)
227
228 def OnKeyDown(self, event):
229 """Key down event handler.
230
231 The main goal here is to not allow modifications to previous
232 lines of text."""
233 key = event.KeyCode()
234 currpos = self.GetCurrentPos()
235 stoppos = self.promptPos[1]
236 # If the auto-complete window is up let it do its thing.
237 if self.AutoCompActive():
238 event.Skip()
239 # Control+UpArrow steps up through the history.
240 elif key == WXK_UP and event.ControlDown() \
241 and self.historyPos < len(self.history) - 1:
242 # Move to the end of the buffer.
243 endpos = self.GetTextLength()
244 self.SetCurrentPos(endpos)
245 # The first Control+Up stores the current command;
246 # Control+Down brings it back.
247 if self.historyPos == -1:
248 self.tempCommand = self.getCommand()
249 # Now replace the current line with the next one from the history.
250 self.historyPos = self.historyPos + 1
251 self.SetSelection(stoppos, endpos)
252 self.ReplaceSelection(self.history[self.historyPos])
253 # Control+DownArrow steps down through the history.
254 elif key == WXK_DOWN and event.ControlDown():
255 # Move to the end of the buffer.
256 endpos = self.GetTextLength()
257 self.SetCurrentPos(endpos)
258 # Are we at the bottom end of the history?
259 if self.historyPos == -1:
260 # Do we have a lastCommandRecalled stored?
261 if self.lastCommandRecalled >= 0:
262 # Replace the current line with the command after the
263 # last-recalled command (you'd think there should be a +1
264 # here but there isn't because the history was shuffled up
265 # by 1 after the previous command was recalled).
266 self.SetSelection(stoppos, endpos)
267 self.ReplaceSelection(self.history[self.lastCommandRecalled])
268 # We've now warped into middle of the history.
269 self.historyPos = self.lastCommandRecalled
270 self.lastCommandRecalled = -1
271 else:
272 # Fetch either the previous line from the history, or the saved
273 # command if we're back at the start.
274 self.historyPos = self.historyPos - 1
275 if self.historyPos == -1:
276 newText = self.tempCommand
277 else:
278 newText = self.history[self.historyPos]
279 # Replace the current line with the new text.
280 self.SetSelection(stoppos, endpos)
281 self.ReplaceSelection(newText)
282 # F8 on the last line does a search up the history for the text in
283 # front of the cursor.
284 elif key == WXK_F8 and self.GetCurrentLine() == self.GetLineCount()-1:
285 tempCommand = self.getCommand()
286 # The first F8 saves the current command, just like Control+Up.
287 if self.historyPos == -1:
288 self.tempCommand = tempCommand
289 # The text up to the cursor is what we search for.
290 searchText = tempCommand
291 numCharsAfterCursor = self.GetTextLength() - self.GetCurrentPos()
292 if numCharsAfterCursor > 0:
293 searchText = searchText[:-numCharsAfterCursor]
294 # Search upwards from the current history position and loop back
295 # to the beginning if we don't find anything.
296 for i in range(self.historyPos+1, len(self.history)) + \
297 range(self.historyPos):
298 command = self.history[i]
299 if command[:len(searchText)] == searchText:
300 # Replace the current line with the one we've found.
301 endpos = self.GetTextLength()
302 self.SetSelection(stoppos, endpos)
303 self.ReplaceSelection(command)
304 # Put the cursor back at the end of the search text.
305 pos = self.GetTextLength() - len(command) + len(searchText)
306 self.SetCurrentPos(pos)
307 self.SetAnchor(pos)
308 # We've now warped into middle of the history.
309 self.historyPos = i
310 self.lastCommandRecalled = -1
311 break
312 # Return is used to submit a command to the interpreter.
313 elif key == WXK_RETURN:
314 if self.CallTipActive: self.CallTipCancel()
315 self.processLine()
316 # Home needs to be aware of the prompt.
317 elif key == WXK_HOME:
318 if currpos >= stoppos:
319 self.SetCurrentPos(stoppos)
320 self.SetAnchor(stoppos)
321 else:
322 event.Skip()
323 # Basic navigation keys should work anywhere.
324 elif key in (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, \
325 WXK_PRIOR, WXK_NEXT):
326 event.Skip()
327 # Don't backspace over the latest prompt.
328 elif key == WXK_BACK:
329 if currpos > stoppos:
330 event.Skip()
331 # Only allow these keys after the latest prompt.
332 elif key in (WXK_TAB, WXK_DELETE):
333 if currpos >= stoppos:
334 event.Skip()
335 # Don't toggle between insert mode and overwrite mode.
336 elif key == WXK_INSERT:
337 pass
338 else:
339 event.Skip()
340
341 def OnChar(self, event):
342 """Keypress event handler.
343
344 The main goal here is to not allow modifications to previous
345 lines of text."""
346 key = event.KeyCode()
347 currpos = self.GetCurrentPos()
348 stoppos = self.promptPos[1]
349 if currpos >= stoppos:
350 if key == 46:
351 # "." The dot or period key activates auto completion.
352 # Get the command between the prompt and the cursor.
353 # Add a dot to the end of the command.
354 command = self.GetTextRange(stoppos, currpos) + '.'
355 self.write('.')
356 if self.autoComplete: self.autoCompleteShow(command)
357 elif key == 40:
358 # "(" The left paren activates a call tip and cancels
359 # an active auto completion.
360 if self.AutoCompActive(): self.AutoCompCancel()
361 # Get the command between the prompt and the cursor.
362 # Add the '(' to the end of the command.
363 command = self.GetTextRange(stoppos, currpos) + '('
364 self.write('(')
365 if self.autoCallTip: self.autoCallTipShow(command)
366 else:
367 # Allow the normal event handling to take place.
368 event.Skip()
369 else:
370 pass
371
372 def setStatusText(self, text):
373 """Display status information."""
374
375 # This method will most likely be replaced by the enclosing app
376 # to do something more interesting, like write to a status bar.
377 print text
378
379 def processLine(self):
380 """Process the line of text at which the user hit Enter."""
381
382 # The user hit ENTER and we need to decide what to do. They could be
383 # sitting on any line in the shell.
384
385 # Grab information about the current line.
386 thepos = self.GetCurrentPos()
387 theline = self.GetCurrentLine()
388 command = self.getCommand()
389 # Go to the very bottom of the text.
390 endpos = self.GetTextLength()
391 self.SetCurrentPos(endpos)
392 endline = self.GetCurrentLine()
393 # If they hit RETURN on the last line, execute the command.
394 if theline == endline:
395 self.push(command)
396 # Otherwise, replace the last line with the new line.
397 else:
398 # If the new line contains a command (even an invalid one).
399 if command:
400 startpos = self.PositionFromLine(endline)
401 self.SetSelection(startpos, endpos)
402 self.ReplaceSelection('')
403 self.prompt()
404 self.write(command)
405 # Otherwise, put the cursor back where we started.
406 else:
407 self.SetCurrentPos(thepos)
408 self.SetAnchor(thepos)
409
410 def getCommand(self, text=None):
411 """Extract a command from text which may include a shell prompt.
412
413 The command may not necessarily be valid Python syntax."""
414 if not text:
415 text = self.GetCurLine()[0]
416 # XXX Need to extract real prompts here. Need to keep track of the
417 # prompt every time a command is issued.
418 ps1 = str(sys.ps1)
419 ps1size = len(ps1)
420 ps2 = str(sys.ps2)
421 ps2size = len(ps2)
422 text = text.rstrip()
423 # Strip the prompt off the front of text leaving just the command.
424 if text[:ps1size] == ps1:
425 command = text[ps1size:]
426 elif text[:ps2size] == ps2:
427 command = text[ps2size:]
428 else:
429 command = ''
430 return command
431
432 def push(self, command):
433 """Send command to the interpreter for execution."""
434 self.addHistory(command)
435 self.write(os.linesep)
436 self.more = self.interp.push(command)
437 self.prompt()
438 # Keep the undo feature from undoing previous responses. The only
439 # thing that can be undone is stuff typed after the prompt, before
440 # hitting enter. After they hit enter it becomes permanent.
441 self.EmptyUndoBuffer()
442
443 def addHistory(self, command):
444 """Add command to the command history."""
445 # Store the last-recalled command; see the main comment for
446 # self.lastCommandRecalled.
447 if command != '':
448 self.lastCommandRecalled = self.historyPos
449 # Reset the history position.
450 self.historyPos = -1
451 # Insert this command into the history, unless it's a blank
452 # line or the same as the last command.
453 if command != '' \
454 and (len(self.history) == 0 or command != self.history[0]):
455 self.history.insert(0, command)
456
457 def write(self, text):
458 """Display text in the shell.
459
460 Replace line endings with OS-specific endings."""
461 lines = text.split('\r\n')
462 for l in range(len(lines)):
463 chunks = lines[l].split('\r')
464 for c in range(len(chunks)):
465 chunks[c] = os.linesep.join(chunks[c].split('\n'))
466 lines[l] = os.linesep.join(chunks)
467 text = os.linesep.join(lines)
468 self.AddText(text)
469 self.EnsureCaretVisible()
470 #self.ScrollToColumn(0)
471
472 def prompt(self):
473 """Display appropriate prompt for the context, either ps1 or ps2.
474
475 If this is a continuation line, autoindent as necessary."""
476 if self.more:
477 prompt = str(sys.ps2)
478 else:
479 prompt = str(sys.ps1)
480 pos = self.GetCurLine()[1]
481 if pos > 0: self.write(os.linesep)
482 self.promptPos[0] = self.GetCurrentPos()
483 self.write(prompt)
484 self.promptPos[1] = self.GetCurrentPos()
485 # XXX Add some autoindent magic here if more.
486 if self.more:
487 self.write('\t') # Temporary hack indentation.
488 self.EnsureCaretVisible()
489 self.ScrollToColumn(0)
490
491 def readIn(self):
492 """Replacement for stdin."""
493 prompt = 'Please enter your response:'
494 dialog = wxTextEntryDialog(None, prompt, \
495 'Input Dialog (Standard)', '')
496 try:
497 if dialog.ShowModal() == wxID_OK:
498 text = dialog.GetValue()
499 self.write(text + os.linesep)
500 return text
501 finally:
502 dialog.Destroy()
503 return ''
504
505 def readRaw(self, prompt='Please enter your response:'):
506 """Replacement for raw_input."""
507 dialog = wxTextEntryDialog(None, prompt, \
508 'Input Dialog (Raw)', '')
509 try:
510 if dialog.ShowModal() == wxID_OK:
511 text = dialog.GetValue()
512 return text
513 finally:
514 dialog.Destroy()
515 return ''
516
517 def ask(self, prompt='Please enter your response:'):
518 """Get response from the user."""
519 return raw_input(prompt=prompt)
520
521 def pause(self):
522 """Halt execution pending a response from the user."""
523 self.ask('Press enter to continue:')
524
525 def clear(self):
526 """Delete all text from the shell."""
527 self.ClearAll()
528
529 def run(self, command, prompt=1, verbose=1):
530 """Execute command within the shell as if it was typed in directly.
531 >>> shell.run('print "this"')
532 >>> print "this"
533 this
534 >>>
535 """
536 command = command.rstrip()
537 if prompt: self.prompt()
538 if verbose: self.write(command)
539 self.push(command)
540
541 def runfile(self, filename):
542 """Execute all commands in file as if they were typed into the shell."""
543 file = open(filename)
544 try:
545 self.prompt()
546 for command in file.readlines():
547 if command[:6] == 'shell.': # Run shell methods silently.
548 self.run(command, prompt=0, verbose=0)
549 else:
550 self.run(command, prompt=0, verbose=1)
551 finally:
552 file.close()
553
554 def autoCompleteShow(self, command):
555 """Display auto-completion popup list."""
556 list = self.interp.getAutoCompleteList(command, \
557 includeMagic=self.autoCompleteIncludeMagic, \
558 includeSingle=self.autoCompleteIncludeSingle, \
559 includeDouble=self.autoCompleteIncludeDouble)
560 if list:
561 options = ' '.join(list)
562 offset = 0
563 self.AutoCompShow(offset, options)
564
565 def autoCallTipShow(self, command):
566 """Display argument spec and docstring in a popup bubble thingie."""
567 if self.CallTipActive: self.CallTipCancel()
568 tip = self.interp.getCallTip(command)
569 if tip:
570 offset = self.GetCurrentPos()
571 self.CallTipShow(offset, tip)
572
573 def writeOut(self, text):
574 """Replacement for stdout."""
575 self.write(text)
576
577 def writeErr(self, text):
578 """Replacement for stderr."""
579 self.write(text)
580
581 def redirectStdin(self, redirect=1):
582 """If redirect is true then sys.stdin will come from the shell."""
583 if redirect:
584 sys.stdin = PseudoFileIn(self.readIn)
585 else:
586 sys.stdin = self.stdin
587
588 def redirectStdout(self, redirect=1):
589 """If redirect is true then sys.stdout will go to the shell."""
590 if redirect:
591 sys.stdout = PseudoFileOut(self.writeOut)
592 else:
593 sys.stdout = self.stdout
594
595 def redirectStderr(self, redirect=1):
596 """If redirect is true then sys.stderr will go to the shell."""
597 if redirect:
598 sys.stderr = PseudoFileErr(self.writeErr)
599 else:
600 sys.stderr = self.stderr
601
602 def CanCut(self):
603 """Return true if text is selected and can be cut."""
604 return self.GetSelectionStart() != self.GetSelectionEnd()
605
606 def CanCopy(self):
607 """Return true if text is selected and can be copied."""
608 return self.GetSelectionStart() != self.GetSelectionEnd()
609
610
611 wxID_SELECTALL = NewId() # This *should* be defined by wxPython.
612 ID_AUTOCOMP = NewId()
613 ID_AUTOCOMP_SHOW = NewId()
614 ID_AUTOCOMP_INCLUDE_MAGIC = NewId()
615 ID_AUTOCOMP_INCLUDE_SINGLE = NewId()
616 ID_AUTOCOMP_INCLUDE_DOUBLE = NewId()
617 ID_CALLTIPS = NewId()
618 ID_CALLTIPS_SHOW = NewId()
619
620
621 class ShellMenu:
622 """Mixin class to add standard menu items."""
623
624 def createMenus(self):
625 m = self.fileMenu = wxMenu()
626 m.AppendSeparator()
627 m.Append(wxID_EXIT, 'E&xit', 'Exit PyCrust')
628
629 m = self.editMenu = wxMenu()
630 m.Append(wxID_UNDO, '&Undo \tCtrl+Z', 'Undo the last action')
631 m.Append(wxID_REDO, '&Redo \tCtrl+Y', 'Redo the last undone action')
632 m.AppendSeparator()
633 m.Append(wxID_CUT, 'Cu&t', 'Cut the selection')
634 m.Append(wxID_COPY, '&Copy', 'Copy the selection')
635 m.Append(wxID_PASTE, '&Paste', 'Paste')
636 m.AppendSeparator()
637 m.Append(wxID_CLEAR, 'Cle&ar', 'Delete the selection')
638 m.Append(wxID_SELECTALL, 'Select A&ll', 'Select all text')
639
640 m = self.autocompMenu = wxMenu()
641 m.Append(ID_AUTOCOMP_SHOW, 'Show Auto Completion', \
642 'Show auto completion during dot syntax', \
643 checkable=1)
644 m.Append(ID_AUTOCOMP_INCLUDE_MAGIC, 'Include Magic Attributes', \
645 'Include attributes visible to __getattr__ and __setattr__', \
646 checkable=1)
647 m.Append(ID_AUTOCOMP_INCLUDE_SINGLE, 'Include Single Underscores', \
648 'Include attibutes prefixed by a single underscore', \
649 checkable=1)
650 m.Append(ID_AUTOCOMP_INCLUDE_DOUBLE, 'Include Double Underscores', \
651 'Include attibutes prefixed by a double underscore', \
652 checkable=1)
653
654 m = self.calltipsMenu = wxMenu()
655 m.Append(ID_CALLTIPS_SHOW, 'Show Call Tips', \
656 'Show call tips with argument specifications', checkable=1)
657
658 m = self.optionsMenu = wxMenu()
659 m.AppendMenu(ID_AUTOCOMP, '&Auto Completion', self.autocompMenu, \
660 'Auto Completion Options')
661 m.AppendMenu(ID_CALLTIPS, '&Call Tips', self.calltipsMenu, \
662 'Call Tip Options')
663
664 m = self.helpMenu = wxMenu()
665 m.AppendSeparator()
666 m.Append(wxID_ABOUT, '&About...', 'About PyCrust')
667
668 b = self.menuBar = wxMenuBar()
669 b.Append(self.fileMenu, '&File')
670 b.Append(self.editMenu, '&Edit')
671 b.Append(self.optionsMenu, '&Options')
672 b.Append(self.helpMenu, '&Help')
673 self.SetMenuBar(b)
674
675 EVT_MENU(self, wxID_EXIT, self.OnExit)
676 EVT_MENU(self, wxID_UNDO, self.OnUndo)
677 EVT_MENU(self, wxID_REDO, self.OnRedo)
678 EVT_MENU(self, wxID_CUT, self.OnCut)
679 EVT_MENU(self, wxID_COPY, self.OnCopy)
680 EVT_MENU(self, wxID_PASTE, self.OnPaste)
681 EVT_MENU(self, wxID_CLEAR, self.OnClear)
682 EVT_MENU(self, wxID_SELECTALL, self.OnSelectAll)
683 EVT_MENU(self, wxID_ABOUT, self.OnAbout)
684 EVT_MENU(self, ID_AUTOCOMP_SHOW, \
685 self.OnAutoCompleteShow)
686 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_MAGIC, \
687 self.OnAutoCompleteIncludeMagic)
688 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_SINGLE, \
689 self.OnAutoCompleteIncludeSingle)
690 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_DOUBLE, \
691 self.OnAutoCompleteIncludeDouble)
692 EVT_MENU(self, ID_CALLTIPS_SHOW, \
693 self.OnCallTipsShow)
694
695 EVT_UPDATE_UI(self, wxID_UNDO, self.OnUpdateMenu)
696 EVT_UPDATE_UI(self, wxID_REDO, self.OnUpdateMenu)
697 EVT_UPDATE_UI(self, wxID_CUT, self.OnUpdateMenu)
698 EVT_UPDATE_UI(self, wxID_COPY, self.OnUpdateMenu)
699 EVT_UPDATE_UI(self, wxID_PASTE, self.OnUpdateMenu)
700 EVT_UPDATE_UI(self, wxID_CLEAR, self.OnUpdateMenu)
701 EVT_UPDATE_UI(self, ID_AUTOCOMP_SHOW, self.OnUpdateMenu)
702 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_MAGIC, self.OnUpdateMenu)
703 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_SINGLE, self.OnUpdateMenu)
704 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_DOUBLE, self.OnUpdateMenu)
705 EVT_UPDATE_UI(self, ID_CALLTIPS_SHOW, self.OnUpdateMenu)
706
707 def OnExit(self, event):
708 self.Close(true)
709
710 def OnUndo(self, event):
711 self.shell.Undo()
712
713 def OnRedo(self, event):
714 self.shell.Redo()
715
716 def OnCut(self, event):
717 self.shell.Cut()
718
719 def OnCopy(self, event):
720 self.shell.Copy()
721
722 def OnPaste(self, event):
723 self.shell.Paste()
724
725 def OnClear(self, event):
726 self.shell.Clear()
727
728 def OnSelectAll(self, event):
729 self.shell.SelectAll()
730
731 def OnAbout(self, event):
732 """Display an About PyCrust window."""
733 import sys
734 title = 'About PyCrust'
735 text = 'PyCrust %s\n\n' % VERSION + \
736 'Yet another Python shell, only flakier.\n\n' + \
737 'Half-baked by Patrick K. O\'Brien,\n' + \
738 'the other half is still in the oven.\n\n' + \
739 'Shell Revision: %s\n' % self.shell.revision + \
740 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \
741 'Python Version: %s\n' % sys.version.split()[0] + \
742 'wxPython Version: %s\n' % wx.__version__ + \
743 'Platform: %s\n' % sys.platform
744 dialog = wxMessageDialog(self, text, title, wxOK | wxICON_INFORMATION)
745 dialog.ShowModal()
746 dialog.Destroy()
747
748 def OnAutoCompleteShow(self, event):
749 self.shell.autoComplete = event.IsChecked()
750
751 def OnAutoCompleteIncludeMagic(self, event):
752 self.shell.autoCompleteIncludeMagic = event.IsChecked()
753
754 def OnAutoCompleteIncludeSingle(self, event):
755 self.shell.autoCompleteIncludeSingle = event.IsChecked()
756
757 def OnAutoCompleteIncludeDouble(self, event):
758 self.shell.autoCompleteIncludeDouble = event.IsChecked()
759
760 def OnCallTipsShow(self, event):
761 self.shell.autoCallTip = event.IsChecked()
762
763 def OnUpdateMenu(self, event):
764 """Update menu items based on current status."""
765 id = event.GetId()
766 if id == wxID_UNDO:
767 event.Enable(self.shell.CanUndo())
768 elif id == wxID_REDO:
769 event.Enable(self.shell.CanRedo())
770 elif id == wxID_CUT:
771 event.Enable(self.shell.CanCut())
772 elif id == wxID_COPY:
773 event.Enable(self.shell.CanCopy())
774 elif id == wxID_PASTE:
775 event.Enable(self.shell.CanPaste())
776 elif id == wxID_CLEAR:
777 event.Enable(self.shell.CanCut())
778 elif id == ID_AUTOCOMP_SHOW:
779 event.Check(self.shell.autoComplete)
780 elif id == ID_AUTOCOMP_INCLUDE_MAGIC:
781 event.Check(self.shell.autoCompleteIncludeMagic)
782 elif id == ID_AUTOCOMP_INCLUDE_SINGLE:
783 event.Check(self.shell.autoCompleteIncludeSingle)
784 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE:
785 event.Check(self.shell.autoCompleteIncludeDouble)
786 elif id == ID_CALLTIPS_SHOW:
787 event.Check(self.shell.autoCallTip)
788
789
790 class ShellFrame(wxFrame, ShellMenu):
791 """Frame containing the PyCrust shell component."""
792
793 name = 'PyCrust Shell Frame'
794 revision = __version__
795
796 def __init__(self, parent=None, id=-1, title='PyShell', \
797 pos=wxDefaultPosition, size=wxDefaultSize, \
798 style=wxDEFAULT_FRAME_STYLE, locals=None, \
799 InterpClass=None, *args, **kwds):
800 """Create a PyCrust ShellFrame instance."""
801 wxFrame.__init__(self, parent, id, title, pos, size, style)
802 intro = 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
803 self.CreateStatusBar()
804 self.SetStatusText(intro)
805 if wxPlatform == '__WXMSW__':
806 icon = wxIcon('PyCrust.ico', wxBITMAP_TYPE_ICO)
807 self.SetIcon(icon)
808 self.shell = Shell(parent=self, id=-1, introText=intro, \
809 locals=locals, InterpClass=InterpClass, \
810 *args, **kwds)
811 # Override the shell so that status messages go to the status bar.
812 self.shell.setStatusText = self.SetStatusText
813 self.createMenus()
814
815