]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/shell.py
f522356f09d81d6efa96288bea931b47e99417d0
[wxWidgets.git] / wxPython / wxPython / lib / PyCrust / shell.py
1 """The PyCrust Shell is an interactive text control in which a user
2 types in commands to be sent to the interpreter. This particular shell
3 is based on wxPython's wxStyledTextCtrl. The latest files are always
4 available at the SourceForge project page at
5 http://sourceforge.net/projects/pycrust/.
6
7 Sponsored by Orbtech - Your source for Python programming expertise."""
8
9 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
10 __cvsid__ = "$Id$"
11 __revision__ = "$Revision$"[11:-2]
12
13 import keyword
14 import os
15 import sys
16 import time
17 from pseudo import PseudoFileIn
18 from pseudo import PseudoFileOut
19 from pseudo import PseudoFileErr
20 from shellmenu import ShellMenu
21 from version import VERSION
22 import dispatcher
23
24 try:
25 import wxd.d_wx
26 except ImportError:
27 from wxPython import wx
28 else:
29 from wxd.d_wx import wx
30
31 try:
32 import wxd.d_stc
33 except ImportError:
34 from wxPython import stc
35 else:
36 from wxd.d_stc import stc
37
38 try:
39 True
40 except NameError:
41 True = 1==1
42 False = 1==0
43
44 sys.ps3 = '<-- ' # Input prompt.
45
46 NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
47 wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PRIOR, wx.WXK_NEXT)
48
49 if wx.wxPlatform == '__WXMSW__':
50 faces = { 'times' : 'Times New Roman',
51 'mono' : 'Courier New',
52 'helv' : 'Lucida Console',
53 'lucida' : 'Lucida Console',
54 'other' : 'Comic Sans MS',
55 'size' : 10,
56 'lnsize' : 9,
57 'backcol': '#FFFFFF',
58 }
59 else: # GTK
60 faces = { 'times' : 'Times',
61 'mono' : 'Courier',
62 'helv' : 'Helvetica',
63 'other' : 'new century schoolbook',
64 'size' : 12,
65 'lnsize' : 10,
66 'backcol': '#FFFFFF',
67 }
68
69
70 class ShellFrame(wx.wxFrame, ShellMenu):
71 """Frame containing the PyCrust shell component."""
72
73 name = 'PyCrust Shell Frame'
74 revision = __revision__
75
76 def __init__(self, parent=None, id=-1, title='PyShell',
77 pos=wx.wxDefaultPosition, size=wx.wxDefaultSize,
78 style=wx.wxDEFAULT_FRAME_STYLE, locals=None,
79 InterpClass=None, *args, **kwds):
80 """Create a PyCrust ShellFrame instance."""
81 wx.wxFrame.__init__(self, parent, id, title, pos, size, style)
82 intro = 'PyCrust %s - The Flakiest Python Shell' % VERSION
83 intro += '\nSponsored by Orbtech - ' + \
84 'Your source for Python programming expertise.'
85 self.CreateStatusBar()
86 self.SetStatusText(intro.replace('\n', ', '))
87 import images
88 self.SetIcon(images.getPyCrustIcon())
89 self.shell = Shell(parent=self, id=-1, introText=intro,
90 locals=locals, InterpClass=InterpClass,
91 *args, **kwds)
92 # Override the shell so that status messages go to the status bar.
93 self.shell.setStatusText = self.SetStatusText
94 self.createMenus()
95 wx.EVT_CLOSE(self, self.OnCloseWindow)
96
97 def OnCloseWindow(self, event):
98 """Event handler for closing."""
99 # This isn't working the way I want, but I'll leave it for now.
100 if self.shell.waiting:
101 event.Veto(True)
102 else:
103 self.shell.destroy()
104 self.Destroy()
105
106
107 class ShellFacade:
108 """Simplified interface to all shell-related functionality.
109
110 This is a semi-transparent facade, in that all attributes of other
111 are accessible, even though only some are visible to the user."""
112
113 name = 'PyCrust Shell Interface'
114 revision = __revision__
115
116 def __init__(self, other):
117 """Create a ShellFacade instance."""
118 methods = [
119 'about',
120 'ask',
121 'clear',
122 'pause',
123 'prompt',
124 'quit',
125 'redirectStderr',
126 'redirectStdin',
127 'redirectStdout',
128 'run',
129 'runfile',
130 'wrap',
131 'zoom',
132 ]
133 for method in methods:
134 self.__dict__[method] = getattr(other, method)
135 d = self.__dict__
136 d['other'] = other
137 d['helpText'] = \
138 """
139 * Key bindings:
140 Home Go to the beginning of the command or line.
141 Shift+Home Select to the beginning of the command or line.
142 Shift+End Select to the end of the line.
143 End Go to the end of the line.
144 Ctrl+C Copy selected text, removing prompts.
145 Ctrl+Shift+C Copy selected text, retaining prompts.
146 Ctrl+X Cut selected text.
147 Ctrl+V Paste from clipboard.
148 Ctrl+Shift+V Paste and run multiple commands from clipboard.
149 Ctrl+Up Arrow Retrieve Previous History item.
150 Alt+P Retrieve Previous History item.
151 Ctrl+Down Arrow Retrieve Next History item.
152 Alt+N Retrieve Next History item.
153 Shift+Up Arrow Insert Previous History item.
154 Shift+Down Arrow Insert Next History item.
155 F8 Command-completion of History item.
156 (Type a few characters of a previous command and press F8.)
157 Ctrl+Enter Insert new line into multiline command.
158 Ctrl+] Increase font size.
159 Ctrl+[ Decrease font size.
160 Ctrl+= Default font size.
161 """
162
163 def help(self):
164 """Display some useful information about how to use the shell."""
165 self.write(self.helpText)
166
167 def __getattr__(self, name):
168 if hasattr(self.other, name):
169 return getattr(self.other, name)
170 else:
171 raise AttributeError, name
172
173 def __setattr__(self, name, value):
174 if self.__dict__.has_key(name):
175 self.__dict__[name] = value
176 elif hasattr(self.other, name):
177 setattr(self.other, name, value)
178 else:
179 raise AttributeError, name
180
181 def _getAttributeNames(self):
182 """Return list of magic attributes to extend introspection."""
183 list = ['autoCallTip',
184 'autoComplete',
185 'autoCompleteCaseInsensitive',
186 'autoCompleteIncludeDouble',
187 'autoCompleteIncludeMagic',
188 'autoCompleteIncludeSingle',
189 ]
190 list.sort()
191 return list
192
193
194 class Shell(stc.wxStyledTextCtrl):
195 """PyCrust Shell based on wxStyledTextCtrl."""
196
197 name = 'PyCrust Shell'
198 revision = __revision__
199
200 def __init__(self, parent, id=-1, pos=wx.wxDefaultPosition,
201 size=wx.wxDefaultSize, style=wx.wxCLIP_CHILDREN,
202 introText='', locals=None, InterpClass=None, *args, **kwds):
203 """Create a PyCrust Shell instance."""
204 stc.wxStyledTextCtrl.__init__(self, parent, id, pos, size, style)
205 if locals is None:
206 locals = {}
207 # Grab these so they can be restored by self.redirect* methods.
208 self.stdin = sys.stdin
209 self.stdout = sys.stdout
210 self.stderr = sys.stderr
211 # Add the current working directory "." to the search path.
212 sys.path.insert(0, os.curdir)
213 # Import a default interpreter class if one isn't provided.
214 if InterpClass == None:
215 from interpreter import Interpreter
216 else:
217 Interpreter = InterpClass
218 # Create a replacement for stdin.
219 self.reader = PseudoFileIn(self.readline, self.readlines)
220 self.reader.input = ''
221 self.reader.isreading = 0
222 # Set up the interpreter.
223 self.interp = Interpreter(locals=locals,
224 rawin=self.raw_input,
225 stdin=self.reader,
226 stdout=PseudoFileOut(self.writeOut),
227 stderr=PseudoFileErr(self.writeErr),
228 *args, **kwds)
229 # Find out for which keycodes the interpreter will autocomplete.
230 self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
231 # Keep track of the last non-continuation prompt positions.
232 self.promptPosStart = 0
233 self.promptPosEnd = 0
234 # Keep track of multi-line commands.
235 self.more = 0
236 # Create the command history. Commands are added into the
237 # front of the list (ie. at index 0) as they are entered.
238 # self.historyIndex is the current position in the history; it
239 # gets incremented as you retrieve the previous command,
240 # decremented as you retrieve the next, and reset when you hit
241 # Enter. self.historyIndex == -1 means you're on the current
242 # command, not in the history.
243 self.history = []
244 self.historyIndex = -1
245 # Assign handlers for keyboard events.
246 wx.EVT_KEY_DOWN(self, self.OnKeyDown)
247 wx.EVT_CHAR(self, self.OnChar)
248 # Assign handlers for wxSTC events.
249 stc.EVT_STC_UPDATEUI(self, id, self.OnUpdateUI)
250 # Assign handler for idle time.
251 self.waiting = False
252 wx.EVT_IDLE(self, self.OnIdle)
253 dispatcher.connect(receiver=self.fontsizer, signal='FontIncrease')
254 dispatcher.connect(receiver=self.fontsizer, signal='FontDecrease')
255 dispatcher.connect(receiver=self.fontsizer, signal='FontDefault')
256 # Configure various defaults and user preferences.
257 self.config()
258 # Display the introductory banner information.
259 self.showIntro(introText)
260 # Assign some pseudo keywords to the interpreter's namespace.
261 self.setBuiltinKeywords()
262 # Add 'shell' to the interpreter's local namespace.
263 self.setLocalShell()
264 # Do this last so the user has complete control over their
265 # environment. They can override anything they want.
266 self.execStartupScript(self.interp.startupScript)
267 wx.wxCallAfter(self.ScrollToLine, 0)
268
269 def fontsizer(self, signal):
270 """Receiver for Font* signals."""
271 size = self.GetZoom()
272 if signal == 'FontIncrease':
273 size += 1
274 elif signal == 'FontDecrease':
275 size -= 1
276 elif signal == 'FontDefault':
277 size = 0
278 self.SetZoom(size)
279
280 def destroy(self):
281 del self.interp
282 pass
283
284 def config(self):
285 """Configure shell based on user preferences."""
286 self.SetMarginType(1, stc.wxSTC_MARGIN_NUMBER)
287 self.SetMarginWidth(1, 40)
288
289 self.SetLexer(stc.wxSTC_LEX_PYTHON)
290 self.SetKeyWords(0, ' '.join(keyword.kwlist))
291
292 self.setStyles(faces)
293 self.SetViewWhiteSpace(0)
294 self.SetTabWidth(4)
295 self.SetUseTabs(0)
296 # Do we want to automatically pop up command completion options?
297 self.autoComplete = 1
298 self.autoCompleteIncludeMagic = 1
299 self.autoCompleteIncludeSingle = 1
300 self.autoCompleteIncludeDouble = 1
301 self.autoCompleteCaseInsensitive = 1
302 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
303 self.AutoCompSetAutoHide(False)
304 self.AutoCompStops(' .,;:([)]}\'"\\<>%^&+-=*/|`')
305 # Do we want to automatically pop up command argument help?
306 self.autoCallTip = 1
307 self.CallTipSetBackground(wx.wxColour(255, 255, 232))
308 self.wrap()
309 try:
310 self.SetEndAtLastLine(False)
311 except AttributeError:
312 pass
313
314 def showIntro(self, text=''):
315 """Display introductory text in the shell."""
316 if text:
317 if not text.endswith(os.linesep):
318 text += os.linesep
319 self.write(text)
320 try:
321 self.write(self.interp.introText)
322 except AttributeError:
323 pass
324
325 def setBuiltinKeywords(self):
326 """Create pseudo keywords as part of builtins.
327
328 This sets `close`, `exit` and `quit` to a helpful string.
329 """
330 import __builtin__
331 __builtin__.close = __builtin__.exit = __builtin__.quit = \
332 'Click on the close button to leave the application.'
333
334 def quit(self):
335 """Quit the application."""
336
337 # XXX Good enough for now but later we want to send a close event.
338
339 # In the close event handler we can make sure they want to
340 # quit. Other applications, like PythonCard, may choose to
341 # hide rather than quit so we should just post the event and
342 # let the surrounding app decide what it wants to do.
343 self.write('Click on the close button to leave the application.')
344
345 def setLocalShell(self):
346 """Add 'shell' to locals as reference to ShellFacade instance."""
347 self.interp.locals['shell'] = ShellFacade(other=self)
348
349 def execStartupScript(self, startupScript):
350 """Execute the user's PYTHONSTARTUP script if they have one."""
351 if startupScript and os.path.isfile(startupScript):
352 text = 'Startup script executed: ' + startupScript
353 self.push('print %r; execfile(%r)' % (text, startupScript))
354 else:
355 self.push('')
356
357 def setStyles(self, faces):
358 """Configure font size, typeface and color for lexer."""
359
360 # Default style
361 self.StyleSetSpec(stc.wxSTC_STYLE_DEFAULT,
362 "face:%(mono)s,size:%(size)d,back:%(backcol)s" % \
363 faces)
364
365 self.StyleClearAll()
366
367 # Built in styles
368 self.StyleSetSpec(stc.wxSTC_STYLE_LINENUMBER,
369 "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces)
370 self.StyleSetSpec(stc.wxSTC_STYLE_CONTROLCHAR,
371 "face:%(mono)s" % faces)
372 self.StyleSetSpec(stc.wxSTC_STYLE_BRACELIGHT,
373 "fore:#0000FF,back:#FFFF88")
374 self.StyleSetSpec(stc.wxSTC_STYLE_BRACEBAD,
375 "fore:#FF0000,back:#FFFF88")
376
377 # Python styles
378 self.StyleSetSpec(stc.wxSTC_P_DEFAULT,
379 "face:%(mono)s" % faces)
380 self.StyleSetSpec(stc.wxSTC_P_COMMENTLINE,
381 "fore:#007F00,face:%(mono)s" % faces)
382 self.StyleSetSpec(stc.wxSTC_P_NUMBER,
383 "")
384 self.StyleSetSpec(stc.wxSTC_P_STRING,
385 "fore:#7F007F,face:%(mono)s" % faces)
386 self.StyleSetSpec(stc.wxSTC_P_CHARACTER,
387 "fore:#7F007F,face:%(mono)s" % faces)
388 self.StyleSetSpec(stc.wxSTC_P_WORD,
389 "fore:#00007F,bold")
390 self.StyleSetSpec(stc.wxSTC_P_TRIPLE,
391 "fore:#7F0000")
392 self.StyleSetSpec(stc.wxSTC_P_TRIPLEDOUBLE,
393 "fore:#000033,back:#FFFFE8")
394 self.StyleSetSpec(stc.wxSTC_P_CLASSNAME,
395 "fore:#0000FF,bold")
396 self.StyleSetSpec(stc.wxSTC_P_DEFNAME,
397 "fore:#007F7F,bold")
398 self.StyleSetSpec(stc.wxSTC_P_OPERATOR,
399 "")
400 self.StyleSetSpec(stc.wxSTC_P_IDENTIFIER,
401 "")
402 self.StyleSetSpec(stc.wxSTC_P_COMMENTBLOCK,
403 "fore:#7F7F7F")
404 self.StyleSetSpec(stc.wxSTC_P_STRINGEOL,
405 "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces)
406
407 def about(self):
408 """Display information about PyCrust."""
409 text = """
410 Author: %r
411 PyCrust Version: %s
412 Shell Revision: %s
413 Interpreter Revision: %s
414 Python Version: %s
415 wxPython Version: %s
416 Platform: %s""" % \
417 (__author__, VERSION, self.revision, self.interp.revision,
418 sys.version.split()[0], wx.__version__, sys.platform)
419 self.write(text.strip())
420
421 def OnIdle(self, event):
422 """Free the CPU to do other things."""
423 if self.waiting:
424 time.sleep(0.05)
425
426 def OnUpdateUI(self, event):
427 """Check for matching braces."""
428 # If the auto-complete window is up let it do its thing.
429 if self.AutoCompActive():
430 return
431 braceAtCaret = -1
432 braceOpposite = -1
433 charBefore = None
434 caretPos = self.GetCurrentPos()
435 if caretPos > 0:
436 charBefore = self.GetCharAt(caretPos - 1)
437 styleBefore = self.GetStyleAt(caretPos - 1)
438
439 # Check before.
440 if charBefore and chr(charBefore) in '[]{}()' \
441 and styleBefore == stc.wxSTC_P_OPERATOR:
442 braceAtCaret = caretPos - 1
443
444 # Check after.
445 if braceAtCaret < 0:
446 charAfter = self.GetCharAt(caretPos)
447 styleAfter = self.GetStyleAt(caretPos)
448 if charAfter and chr(charAfter) in '[]{}()' \
449 and styleAfter == stc.wxSTC_P_OPERATOR:
450 braceAtCaret = caretPos
451
452 if braceAtCaret >= 0:
453 braceOpposite = self.BraceMatch(braceAtCaret)
454
455 if braceAtCaret != -1 and braceOpposite == -1:
456 self.BraceBadLight(braceAtCaret)
457 else:
458 self.BraceHighlight(braceAtCaret, braceOpposite)
459
460 def OnChar(self, event):
461 """Keypress event handler.
462
463 Only receives an event if OnKeyDown calls event.Skip() for the
464 corresponding event."""
465
466 # Prevent modification of previously submitted
467 # commands/responses.
468 if not self.CanEdit():
469 return
470 key = event.KeyCode()
471 currpos = self.GetCurrentPos()
472 stoppos = self.promptPosEnd
473 # Return (Enter) needs to be ignored in this handler.
474 if key == wx.WXK_RETURN:
475 pass
476 elif key in self.autoCompleteKeys:
477 # Usually the dot (period) key activates auto completion.
478 # Get the command between the prompt and the cursor. Add
479 # the autocomplete character to the end of the command.
480 if self.AutoCompActive():
481 self.AutoCompCancel()
482 command = self.GetTextRange(stoppos, currpos) + chr(key)
483 self.write(chr(key))
484 if self.autoComplete:
485 self.autoCompleteShow(command)
486 elif key == ord('('):
487 # The left paren activates a call tip and cancels an
488 # active auto completion.
489 if self.AutoCompActive():
490 self.AutoCompCancel()
491 # Get the command between the prompt and the cursor. Add
492 # the '(' to the end of the command.
493 self.ReplaceSelection('')
494 command = self.GetTextRange(stoppos, currpos) + '('
495 self.write('(')
496 if self.autoCallTip:
497 self.autoCallTipShow(command)
498 else:
499 # Allow the normal event handling to take place.
500 event.Skip()
501
502 def OnKeyDown(self, event):
503 """Key down event handler."""
504
505 key = event.KeyCode()
506 # If the auto-complete window is up let it do its thing.
507 if self.AutoCompActive():
508 event.Skip()
509 return
510 # Prevent modification of previously submitted
511 # commands/responses.
512 controlDown = event.ControlDown()
513 altDown = event.AltDown()
514 shiftDown = event.ShiftDown()
515 currpos = self.GetCurrentPos()
516 endpos = self.GetTextLength()
517 selecting = self.GetSelectionStart() != self.GetSelectionEnd()
518 # Return (Enter) is used to submit a command to the
519 # interpreter.
520 if not controlDown and key == wx.WXK_RETURN:
521 if self.CallTipActive():
522 self.CallTipCancel()
523 self.processLine()
524 # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
525 elif controlDown and key == wx.WXK_RETURN:
526 if self.CallTipActive():
527 self.CallTipCancel()
528 if currpos == endpos:
529 self.processLine()
530 else:
531 self.insertLineBreak()
532 # Let Ctrl-Alt-* get handled normally.
533 elif controlDown and altDown:
534 event.Skip()
535 # Clear the current, unexecuted command.
536 elif key == wx.WXK_ESCAPE:
537 if self.CallTipActive():
538 event.Skip()
539 else:
540 self.clearCommand()
541 # Increase font size.
542 elif controlDown and key in (ord(']'),):
543 dispatcher.send(signal='FontIncrease')
544 # Decrease font size.
545 elif controlDown and key in (ord('['),):
546 dispatcher.send(signal='FontDecrease')
547 # Default font size.
548 elif controlDown and key in (ord('='),):
549 dispatcher.send(signal='FontDefault')
550 # Cut to the clipboard.
551 elif (controlDown and key in (ord('X'), ord('x'))) \
552 or (shiftDown and key == wx.WXK_DELETE):
553 self.Cut()
554 # Copy to the clipboard.
555 elif controlDown and not shiftDown \
556 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
557 self.Copy()
558 # Copy to the clipboard, including prompts.
559 elif controlDown and shiftDown \
560 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
561 self.CopyWithPrompts()
562 # Copy to the clipboard, including prefixed prompts.
563 elif altDown and not controlDown \
564 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
565 self.CopyWithPromptsPrefixed()
566 # Home needs to be aware of the prompt.
567 elif key == wx.WXK_HOME:
568 home = self.promptPosEnd
569 if currpos > home:
570 self.SetCurrentPos(home)
571 if not selecting and not shiftDown:
572 self.SetAnchor(home)
573 self.EnsureCaretVisible()
574 else:
575 event.Skip()
576 #
577 # The following handlers modify text, so we need to see if
578 # there is a selection that includes text prior to the prompt.
579 #
580 # Don't modify a selection with text prior to the prompt.
581 elif selecting and key not in NAVKEYS and not self.CanEdit():
582 pass
583 # Paste from the clipboard.
584 elif (controlDown and not shiftDown and key in (ord('V'), ord('v'))) \
585 or (shiftDown and not controlDown and key == wx.WXK_INSERT):
586 self.Paste()
587 # Paste from the clipboard, run commands.
588 elif controlDown and shiftDown and key in (ord('V'), ord('v')):
589 self.PasteAndRun()
590 # Replace with the previous command from the history buffer.
591 elif (controlDown and key == wx.WXK_UP) \
592 or (altDown and key in (ord('P'), ord('p'))):
593 self.OnHistoryReplace(step=+1)
594 # Replace with the next command from the history buffer.
595 elif (controlDown and key == wx.WXK_DOWN) \
596 or (altDown and key in (ord('N'), ord('n'))):
597 self.OnHistoryReplace(step=-1)
598 # Insert the previous command from the history buffer.
599 elif (shiftDown and key == wx.WXK_UP) and self.CanEdit():
600 self.OnHistoryInsert(step=+1)
601 # Insert the next command from the history buffer.
602 elif (shiftDown and key == wx.WXK_DOWN) and self.CanEdit():
603 self.OnHistoryInsert(step=-1)
604 # Search up the history for the text in front of the cursor.
605 elif key == wx.WXK_F8:
606 self.OnHistorySearch()
607 # Don't backspace over the latest non-continuation prompt.
608 elif key == wx.WXK_BACK:
609 if selecting and self.CanEdit():
610 event.Skip()
611 elif currpos > self.promptPosEnd:
612 event.Skip()
613 # Only allow these keys after the latest prompt.
614 elif key in (wx.WXK_TAB, wx.WXK_DELETE):
615 if self.CanEdit():
616 event.Skip()
617 # Don't toggle between insert mode and overwrite mode.
618 elif key == wx.WXK_INSERT:
619 pass
620 # Don't allow line deletion.
621 elif controlDown and key in (ord('L'), ord('l')):
622 pass
623 # Don't allow line transposition.
624 elif controlDown and key in (ord('T'), ord('t')):
625 pass
626 # Basic navigation keys should work anywhere.
627 elif key in NAVKEYS:
628 event.Skip()
629 # Protect the readonly portion of the shell.
630 elif not self.CanEdit():
631 pass
632 else:
633 event.Skip()
634
635 def clearCommand(self):
636 """Delete the current, unexecuted command."""
637 startpos = self.promptPosEnd
638 endpos = self.GetTextLength()
639 self.SetSelection(startpos, endpos)
640 self.ReplaceSelection('')
641 self.more = 0
642
643 def OnHistoryReplace(self, step):
644 """Replace with the previous/next command from the history buffer."""
645 self.clearCommand()
646 self.replaceFromHistory(step)
647
648 def replaceFromHistory(self, step):
649 """Replace selection with command from the history buffer."""
650 ps2 = str(sys.ps2)
651 self.ReplaceSelection('')
652 newindex = self.historyIndex + step
653 if -1 <= newindex <= len(self.history):
654 self.historyIndex = newindex
655 if 0 <= newindex <= len(self.history)-1:
656 command = self.history[self.historyIndex]
657 command = command.replace('\n', os.linesep + ps2)
658 self.ReplaceSelection(command)
659
660 def OnHistoryInsert(self, step):
661 """Insert the previous/next command from the history buffer."""
662 if not self.CanEdit():
663 return
664 startpos = self.GetCurrentPos()
665 self.replaceFromHistory(step)
666 endpos = self.GetCurrentPos()
667 self.SetSelection(endpos, startpos)
668
669 def OnHistorySearch(self):
670 """Search up the history buffer for the text in front of the cursor."""
671 if not self.CanEdit():
672 return
673 startpos = self.GetCurrentPos()
674 # The text up to the cursor is what we search for.
675 numCharsAfterCursor = self.GetTextLength() - startpos
676 searchText = self.getCommand(rstrip=0)
677 if numCharsAfterCursor > 0:
678 searchText = searchText[:-numCharsAfterCursor]
679 if not searchText:
680 return
681 # Search upwards from the current history position and loop
682 # back to the beginning if we don't find anything.
683 if (self.historyIndex <= -1) \
684 or (self.historyIndex >= len(self.history)-2):
685 searchOrder = range(len(self.history))
686 else:
687 searchOrder = range(self.historyIndex+1, len(self.history)) + \
688 range(self.historyIndex)
689 for i in searchOrder:
690 command = self.history[i]
691 if command[:len(searchText)] == searchText:
692 # Replace the current selection with the one we found.
693 self.ReplaceSelection(command[len(searchText):])
694 endpos = self.GetCurrentPos()
695 self.SetSelection(endpos, startpos)
696 # We've now warped into middle of the history.
697 self.historyIndex = i
698 break
699
700 def setStatusText(self, text):
701 """Display status information."""
702
703 # This method will likely be replaced by the enclosing app to
704 # do something more interesting, like write to a status bar.
705 print text
706
707 def insertLineBreak(self):
708 """Insert a new line break."""
709 if self.CanEdit():
710 self.write(os.linesep)
711 self.more = 1
712 self.prompt()
713
714 def processLine(self):
715 """Process the line of text at which the user hit Enter."""
716
717 # The user hit ENTER and we need to decide what to do. They
718 # could be sitting on any line in the shell.
719
720 thepos = self.GetCurrentPos()
721 startpos = self.promptPosEnd
722 endpos = self.GetTextLength()
723 ps2 = str(sys.ps2)
724 # If they hit RETURN inside the current command, execute the
725 # command.
726 if self.CanEdit():
727 self.SetCurrentPos(endpos)
728 self.interp.more = 0
729 command = self.GetTextRange(startpos, endpos)
730 lines = command.split(os.linesep + ps2)
731 lines = [line.rstrip() for line in lines]
732 command = '\n'.join(lines)
733 if self.reader.isreading:
734 if not command:
735 # Match the behavior of the standard Python shell
736 # when the user hits return without entering a
737 # value.
738 command = '\n'
739 self.reader.input = command
740 self.write(os.linesep)
741 else:
742 self.push(command)
743 # Or replace the current command with the other command.
744 else:
745 # If the line contains a command (even an invalid one).
746 if self.getCommand(rstrip=0):
747 command = self.getMultilineCommand()
748 self.clearCommand()
749 self.write(command)
750 # Otherwise, put the cursor back where we started.
751 else:
752 self.SetCurrentPos(thepos)
753 self.SetAnchor(thepos)
754
755 def getMultilineCommand(self, rstrip=1):
756 """Extract a multi-line command from the editor.
757
758 The command may not necessarily be valid Python syntax."""
759 # XXX Need to extract real prompts here. Need to keep track of
760 # the prompt every time a command is issued.
761 ps1 = str(sys.ps1)
762 ps1size = len(ps1)
763 ps2 = str(sys.ps2)
764 ps2size = len(ps2)
765 # This is a total hack job, but it works.
766 text = self.GetCurLine()[0]
767 line = self.GetCurrentLine()
768 while text[:ps2size] == ps2 and line > 0:
769 line -= 1
770 self.GotoLine(line)
771 text = self.GetCurLine()[0]
772 if text[:ps1size] == ps1:
773 line = self.GetCurrentLine()
774 self.GotoLine(line)
775 startpos = self.GetCurrentPos() + ps1size
776 line += 1
777 self.GotoLine(line)
778 while self.GetCurLine()[0][:ps2size] == ps2:
779 line += 1
780 self.GotoLine(line)
781 stoppos = self.GetCurrentPos()
782 command = self.GetTextRange(startpos, stoppos)
783 command = command.replace(os.linesep + ps2, '\n')
784 command = command.rstrip()
785 command = command.replace('\n', os.linesep + ps2)
786 else:
787 command = ''
788 if rstrip:
789 command = command.rstrip()
790 return command
791
792 def getCommand(self, text=None, rstrip=1):
793 """Extract a command from text which may include a shell prompt.
794
795 The command may not necessarily be valid Python syntax."""
796 if not text:
797 text = self.GetCurLine()[0]
798 # Strip the prompt off the front leaving just the command.
799 command = self.lstripPrompt(text)
800 if command == text:
801 command = '' # Real commands have prompts.
802 if rstrip:
803 command = command.rstrip()
804 return command
805
806 def lstripPrompt(self, text):
807 """Return text without a leading prompt."""
808 ps1 = str(sys.ps1)
809 ps1size = len(ps1)
810 ps2 = str(sys.ps2)
811 ps2size = len(ps2)
812 # Strip the prompt off the front of text.
813 if text[:ps1size] == ps1:
814 text = text[ps1size:]
815 elif text[:ps2size] == ps2:
816 text = text[ps2size:]
817 return text
818
819 def push(self, command):
820 """Send command to the interpreter for execution."""
821 self.write(os.linesep)
822 busy = wx.wxBusyCursor()
823 self.waiting = True
824 self.more = self.interp.push(command)
825 self.waiting = False
826 del busy
827 if not self.more:
828 self.addHistory(command.rstrip())
829 self.prompt()
830
831 def addHistory(self, command):
832 """Add command to the command history."""
833 # Reset the history position.
834 self.historyIndex = -1
835 # Insert this command into the history, unless it's a blank
836 # line or the same as the last command.
837 if command != '' \
838 and (len(self.history) == 0 or command != self.history[0]):
839 self.history.insert(0, command)
840
841 def write(self, text):
842 """Display text in the shell.
843
844 Replace line endings with OS-specific endings."""
845 text = self.fixLineEndings(text)
846 self.AddText(text)
847 self.EnsureCaretVisible()
848
849 def fixLineEndings(self, text):
850 """Return text with line endings replaced by OS-specific endings."""
851 lines = text.split('\r\n')
852 for l in range(len(lines)):
853 chunks = lines[l].split('\r')
854 for c in range(len(chunks)):
855 chunks[c] = os.linesep.join(chunks[c].split('\n'))
856 lines[l] = os.linesep.join(chunks)
857 text = os.linesep.join(lines)
858 return text
859
860 def prompt(self):
861 """Display proper prompt for the context: ps1, ps2 or ps3.
862
863 If this is a continuation line, autoindent as necessary."""
864 isreading = self.reader.isreading
865 skip = 0
866 if isreading:
867 prompt = str(sys.ps3)
868 elif self.more:
869 prompt = str(sys.ps2)
870 else:
871 prompt = str(sys.ps1)
872 pos = self.GetCurLine()[1]
873 if pos > 0:
874 if isreading:
875 skip = 1
876 else:
877 self.write(os.linesep)
878 if not self.more:
879 self.promptPosStart = self.GetCurrentPos()
880 if not skip:
881 self.write(prompt)
882 if not self.more:
883 self.promptPosEnd = self.GetCurrentPos()
884 # Keep the undo feature from undoing previous responses.
885 self.EmptyUndoBuffer()
886 # XXX Add some autoindent magic here if more.
887 if self.more:
888 self.write(' '*4) # Temporary hack indentation.
889 self.EnsureCaretVisible()
890 self.ScrollToColumn(0)
891
892 def readline(self):
893 """Replacement for stdin.readline()."""
894 input = ''
895 reader = self.reader
896 reader.isreading = 1
897 self.prompt()
898 try:
899 while not reader.input:
900 wx.wxYieldIfNeeded()
901 input = reader.input
902 finally:
903 reader.input = ''
904 reader.isreading = 0
905 input = str(input) # In case of Unicode.
906 return input
907
908 def readlines(self):
909 """Replacement for stdin.readlines()."""
910 lines = []
911 while lines[-1:] != ['\n']:
912 lines.append(self.readline())
913 return lines
914
915 def raw_input(self, prompt=''):
916 """Return string based on user input."""
917 if prompt:
918 self.write(prompt)
919 return self.readline()
920
921 def ask(self, prompt='Please enter your response:'):
922 """Get response from the user using a dialog box."""
923 dialog = wx.wxTextEntryDialog(None, prompt,
924 'Input Dialog (Raw)', '')
925 try:
926 if dialog.ShowModal() == wx.wxID_OK:
927 text = dialog.GetValue()
928 return text
929 finally:
930 dialog.Destroy()
931 return ''
932
933 def pause(self):
934 """Halt execution pending a response from the user."""
935 self.ask('Press enter to continue:')
936
937 def clear(self):
938 """Delete all text from the shell."""
939 self.ClearAll()
940
941 def run(self, command, prompt=1, verbose=1):
942 """Execute command as if it was typed in directly.
943 >>> shell.run('print "this"')
944 >>> print "this"
945 this
946 >>>
947 """
948 # Go to the very bottom of the text.
949 endpos = self.GetTextLength()
950 self.SetCurrentPos(endpos)
951 command = command.rstrip()
952 if prompt: self.prompt()
953 if verbose: self.write(command)
954 self.push(command)
955
956 def runfile(self, filename):
957 """Execute all commands in file as if they were typed into the
958 shell."""
959 file = open(filename)
960 try:
961 self.prompt()
962 for command in file.readlines():
963 if command[:6] == 'shell.':
964 # Run shell methods silently.
965 self.run(command, prompt=0, verbose=0)
966 else:
967 self.run(command, prompt=0, verbose=1)
968 finally:
969 file.close()
970
971 def autoCompleteShow(self, command):
972 """Display auto-completion popup list."""
973 list = self.interp.getAutoCompleteList(command,
974 includeMagic=self.autoCompleteIncludeMagic,
975 includeSingle=self.autoCompleteIncludeSingle,
976 includeDouble=self.autoCompleteIncludeDouble)
977 if list and len(list) < 2000:
978 options = ' '.join(list)
979 offset = 0
980 self.AutoCompShow(offset, options)
981
982 def autoCallTipShow(self, command):
983 """Display argument spec and docstring in a popup window."""
984 if self.CallTipActive():
985 self.CallTipCancel()
986 (name, argspec, tip) = self.interp.getCallTip(command)
987 if argspec:
988 startpos = self.GetCurrentPos()
989 self.write(argspec + ')')
990 endpos = self.GetCurrentPos()
991 self.SetSelection(endpos, startpos)
992 if tip:
993 curpos = self.GetCurrentPos()
994 tippos = curpos - (len(name) + 1)
995 fallback = curpos - self.GetColumn(curpos)
996 # In case there isn't enough room, only go back to the
997 # fallback.
998 tippos = max(tippos, fallback)
999 dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
1000 self.CallTipShow(tippos, tip)
1001
1002 def writeOut(self, text):
1003 """Replacement for stdout."""
1004 self.write(text)
1005
1006 def writeErr(self, text):
1007 """Replacement for stderr."""
1008 self.write(text)
1009
1010 def redirectStdin(self, redirect=1):
1011 """If redirect is true then sys.stdin will come from the shell."""
1012 if redirect:
1013 sys.stdin = self.reader
1014 else:
1015 sys.stdin = self.stdin
1016
1017 def redirectStdout(self, redirect=1):
1018 """If redirect is true then sys.stdout will go to the shell."""
1019 if redirect:
1020 sys.stdout = PseudoFileOut(self.writeOut)
1021 else:
1022 sys.stdout = self.stdout
1023
1024 def redirectStderr(self, redirect=1):
1025 """If redirect is true then sys.stderr will go to the shell."""
1026 if redirect:
1027 sys.stderr = PseudoFileErr(self.writeErr)
1028 else:
1029 sys.stderr = self.stderr
1030
1031 def CanCut(self):
1032 """Return true if text is selected and can be cut."""
1033 if self.GetSelectionStart() != self.GetSelectionEnd() \
1034 and self.GetSelectionStart() >= self.promptPosEnd \
1035 and self.GetSelectionEnd() >= self.promptPosEnd:
1036 return 1
1037 else:
1038 return 0
1039
1040 def CanCopy(self):
1041 """Return true if text is selected and can be copied."""
1042 return self.GetSelectionStart() != self.GetSelectionEnd()
1043
1044 def CanPaste(self):
1045 """Return true if a paste should succeed."""
1046 if self.CanEdit() and stc.wxStyledTextCtrl.CanPaste(self):
1047 return 1
1048 else:
1049 return 0
1050
1051 def CanEdit(self):
1052 """Return true if editing should succeed."""
1053 if self.GetSelectionStart() != self.GetSelectionEnd():
1054 if self.GetSelectionStart() >= self.promptPosEnd \
1055 and self.GetSelectionEnd() >= self.promptPosEnd:
1056 return 1
1057 else:
1058 return 0
1059 else:
1060 return self.GetCurrentPos() >= self.promptPosEnd
1061
1062 def Cut(self):
1063 """Remove selection and place it on the clipboard."""
1064 if self.CanCut() and self.CanCopy():
1065 if self.AutoCompActive():
1066 self.AutoCompCancel()
1067 if self.CallTipActive():
1068 self.CallTipCancel()
1069 self.Copy()
1070 self.ReplaceSelection('')
1071
1072 def Copy(self):
1073 """Copy selection and place it on the clipboard."""
1074 if self.CanCopy():
1075 ps1 = str(sys.ps1)
1076 ps2 = str(sys.ps2)
1077 command = self.GetSelectedText()
1078 command = command.replace(os.linesep + ps2, os.linesep)
1079 command = command.replace(os.linesep + ps1, os.linesep)
1080 command = self.lstripPrompt(text=command)
1081 data = wx.wxTextDataObject(command)
1082 self._clip(data)
1083
1084 def CopyWithPrompts(self):
1085 """Copy selection, including prompts, and place it on the clipboard."""
1086 if self.CanCopy():
1087 command = self.GetSelectedText()
1088 data = wx.wxTextDataObject(command)
1089 self._clip(data)
1090
1091 def CopyWithPromptsPrefixed(self):
1092 """Copy selection, including prompts prefixed with four
1093 spaces, and place it on the clipboard."""
1094 if self.CanCopy():
1095 command = self.GetSelectedText()
1096 spaces = ' ' * 4
1097 command = spaces + command.replace(os.linesep,
1098 os.linesep + spaces)
1099 data = wx.wxTextDataObject(command)
1100 self._clip(data)
1101
1102 def _clip(self, data):
1103 if wx.wxTheClipboard.Open():
1104 wx.wxTheClipboard.UsePrimarySelection(False)
1105 wx.wxTheClipboard.SetData(data)
1106 wx.wxTheClipboard.Flush()
1107 wx.wxTheClipboard.Close()
1108
1109 def Paste(self):
1110 """Replace selection with clipboard contents."""
1111 if self.CanPaste() and wx.wxTheClipboard.Open():
1112 ps2 = str(sys.ps2)
1113 if wx.wxTheClipboard.IsSupported(wx.wxDataFormat(wx.wxDF_TEXT)):
1114 data = wx.wxTextDataObject()
1115 if wx.wxTheClipboard.GetData(data):
1116 self.ReplaceSelection('')
1117 command = data.GetText()
1118 command = command.rstrip()
1119 command = self.fixLineEndings(command)
1120 command = self.lstripPrompt(text=command)
1121 command = command.replace(os.linesep + ps2, '\n')
1122 command = command.replace(os.linesep, '\n')
1123 command = command.replace('\n', os.linesep + ps2)
1124 self.write(command)
1125 wx.wxTheClipboard.Close()
1126
1127 def PasteAndRun(self):
1128 """Replace selection with clipboard contents, run commands."""
1129 if wx.wxTheClipboard.Open():
1130 ps1 = str(sys.ps1)
1131 ps2 = str(sys.ps2)
1132 if wx.wxTheClipboard.IsSupported(wx.wxDataFormat(wx.wxDF_TEXT)):
1133 data = wx.wxTextDataObject()
1134 if wx.wxTheClipboard.GetData(data):
1135 endpos = self.GetTextLength()
1136 self.SetCurrentPos(endpos)
1137 startpos = self.promptPosEnd
1138 self.SetSelection(startpos, endpos)
1139 self.ReplaceSelection('')
1140 text = data.GetText()
1141 text = text.lstrip()
1142 text = self.fixLineEndings(text)
1143 text = self.lstripPrompt(text)
1144 text = text.replace(os.linesep + ps1, '\n')
1145 text = text.replace(os.linesep + ps2, '\n')
1146 text = text.replace(os.linesep, '\n')
1147 lines = text.split('\n')
1148 commands = []
1149 command = ''
1150 for line in lines:
1151 if line.strip() == ps2.strip():
1152 # If we are pasting from something like a
1153 # web page that drops the trailing space
1154 # from the ps2 prompt of a blank line.
1155 line = ''
1156 if line.strip() != '' and line.lstrip() == line:
1157 # New command.
1158 if command:
1159 # Add the previous command to the list.
1160 commands.append(command)
1161 # Start a new command, which may be multiline.
1162 command = line
1163 else:
1164 # Multiline command. Add to the command.
1165 command += '\n'
1166 command += line
1167 commands.append(command)
1168 for command in commands:
1169 command = command.replace('\n', os.linesep + ps2)
1170 self.write(command)
1171 self.processLine()
1172 wx.wxTheClipboard.Close()
1173
1174 def wrap(self, wrap=1):
1175 """Sets whether text is word wrapped."""
1176 try:
1177 self.SetWrapMode(wrap)
1178 except AttributeError:
1179 return 'Wrapping is not available in this version of PyCrust.'
1180
1181 def zoom(self, points=0):
1182 """Set the zoom level.
1183
1184 This number of points is added to the size of all fonts. It
1185 may be positive to magnify or negative to reduce."""
1186 self.SetZoom(points)
1187