1 """Shell is an interactive text control in which a user types in
2 commands to be sent to the interpreter. This particular shell is
3 based on wxPython's wxStyledTextCtrl.
5 Sponsored by Orbtech - Your source for Python programming expertise."""
7 __author__
= "Patrick K. O'Brien <pobrien@orbtech.com>"
9 __revision__
= "$Revision$"[11:-2]
19 from buffer import Buffer
23 from pseudo
import PseudoFileIn
24 from pseudo
import PseudoFileOut
25 from pseudo
import PseudoFileErr
26 from version
import VERSION
28 sys
.ps3
= '<-- ' # Input prompt.
30 NAVKEYS
= (wx
.WXK_END
, wx
.WXK_LEFT
, wx
.WXK_RIGHT
,
31 wx
.WXK_UP
, wx
.WXK_DOWN
, wx
.WXK_PRIOR
, wx
.WXK_NEXT
)
34 class ShellFrame(frame
.Frame
, frame
.ShellFrameMixin
):
35 """Frame containing the shell component."""
38 revision
= __revision__
40 def __init__(self
, parent
=None, id=-1, title
='PyShell',
41 pos
=wx
.DefaultPosition
, size
=wx
.DefaultSize
,
42 style
=wx
.DEFAULT_FRAME_STYLE
, locals=None,
44 config
=None, dataDir
=None,
46 """Create ShellFrame instance."""
47 frame
.Frame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
48 frame
.ShellFrameMixin
.__init
__(self
, config
, dataDir
)
50 if size
== wx
.DefaultSize
:
51 self
.SetSize((750, 525))
53 intro
= 'PyShell %s - The Flakiest Python Shell' % VERSION
54 self
.SetStatusText(intro
.replace('\n', ', '))
55 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
,
56 locals=locals, InterpClass
=InterpClass
,
57 startupScript
=self
.startupScript
,
58 execStartupScript
=self
.execStartupScript
,
61 # Override the shell so that status messages go to the status bar.
62 self
.shell
.setStatusText
= self
.SetStatusText
68 def OnClose(self
, event
):
69 """Event handler for closing."""
70 # This isn't working the way I want, but I'll leave it for now.
71 if self
.shell
.waiting
:
79 def OnAbout(self
, event
):
80 """Display an About window."""
81 title
= 'About PyShell'
82 text
= 'PyShell %s\n\n' % VERSION
+ \
83 'Yet another Python shell, only flakier.\n\n' + \
84 'Half-baked by Patrick K. O\'Brien,\n' + \
85 'the other half is still in the oven.\n\n' + \
86 'Shell Revision: %s\n' % self
.shell
.revision
+ \
87 'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision
+ \
88 'Platform: %s\n' % sys
.platform
+ \
89 'Python Version: %s\n' % sys
.version
.split()[0] + \
90 'wxPython Version: %s\n' % wx
.VERSION_STRING
+ \
91 ('\t(%s)\n' % ", ".join(wx
.PlatformInfo
[1:]))
92 dialog
= wx
.MessageDialog(self
, text
, title
,
93 wx
.OK | wx
.ICON_INFORMATION
)
98 def OnHelp(self
, event
):
99 """Show a help dialog."""
100 frame
.ShellFrameMixin
.OnHelp(self
, event
)
103 def LoadSettings(self
):
104 if self
.config
is not None:
105 frame
.ShellFrameMixin
.LoadSettings(self
)
106 frame
.Frame
.LoadSettings(self
, self
.config
)
107 self
.shell
.LoadSettings(self
.config
)
109 def SaveSettings(self
, force
=False):
110 if self
.config
is not None:
111 frame
.ShellFrameMixin
.SaveSettings(self
)
112 if self
.autoSaveSettings
or force
:
113 frame
.Frame
.SaveSettings(self
, self
.config
)
114 self
.shell
.SaveSettings(self
.config
)
116 def DoSaveSettings(self
):
117 if self
.config
is not None:
118 self
.SaveSettings(force
=True)
126 Home Go to the beginning of the command or line.
127 Shift+Home Select to the beginning of the command or line.
128 Shift+End Select to the end of the line.
129 End Go to the end of the line.
130 Ctrl+C Copy selected text, removing prompts.
131 Ctrl+Shift+C Copy selected text, retaining prompts.
132 Alt+C Copy to the clipboard, including prefixed prompts.
133 Ctrl+X Cut selected text.
134 Ctrl+V Paste from clipboard.
135 Ctrl+Shift+V Paste and run multiple commands from clipboard.
136 Ctrl+Up Arrow Retrieve Previous History item.
137 Alt+P Retrieve Previous History item.
138 Ctrl+Down Arrow Retrieve Next History item.
139 Alt+N Retrieve Next History item.
140 Shift+Up Arrow Insert Previous History item.
141 Shift+Down Arrow Insert Next History item.
142 F8 Command-completion of History item.
143 (Type a few characters of a previous command and press F8.)
144 Ctrl+Enter Insert new line into multiline command.
145 Ctrl+] Increase font size.
146 Ctrl+[ Decrease font size.
147 Ctrl+= Default font size.
148 Ctrl-Space Show Auto Completion.
149 Ctrl-Alt-Space Show Call Tip.
150 Shift+Enter Complete Text from History.
153 Ctrl+H "hide" lines containing selection / "unhide"
154 F12 on/off "free-edit" mode
158 """Simplified interface to all shell-related functionality.
160 This is a semi-transparent facade, in that all attributes of other
161 are accessible, even though only some are visible to the user."""
163 name
= 'Shell Interface'
164 revision
= __revision__
166 def __init__(self
, other
):
167 """Create a ShellFacade instance."""
170 d
['helpText'] = HELP_TEXT
171 d
['this'] = other
.this
174 """Display some useful information about how to use the shell."""
175 self
.write(self
.helpText
)
177 def __getattr__(self
, name
):
178 if hasattr(self
.other
, name
):
179 return getattr(self
.other
, name
)
181 raise AttributeError, name
183 def __setattr__(self
, name
, value
):
184 if self
.__dict
__.has_key(name
):
185 self
.__dict
__[name
] = value
186 elif hasattr(self
.other
, name
):
187 setattr(self
.other
, name
, value
)
189 raise AttributeError, name
191 def _getAttributeNames(self
):
192 """Return list of magic attributes to extend introspection."""
198 'autoCompleteAutoHide',
199 'autoCompleteCaseInsensitive',
200 'autoCompleteIncludeDouble',
201 'autoCompleteIncludeMagic',
202 'autoCompleteIncludeSingle',
221 class Shell(editwindow
.EditWindow
):
222 """Shell based on StyledTextCtrl."""
225 revision
= __revision__
227 def __init__(self
, parent
, id=-1, pos
=wx
.DefaultPosition
,
228 size
=wx
.DefaultSize
, style
=wx
.CLIP_CHILDREN
,
229 introText
='', locals=None, InterpClass
=None,
230 startupScript
=None, execStartupScript
=True,
232 """Create Shell instance."""
233 editwindow
.EditWindow
.__init
__(self
, parent
, id, pos
, size
, style
)
237 locals = __main__
.__dict
__
239 # Grab these so they can be restored by self.redirect* methods.
240 self
.stdin
= sys
.stdin
241 self
.stdout
= sys
.stdout
242 self
.stderr
= sys
.stderr
244 # Import a default interpreter class if one isn't provided.
245 if InterpClass
== None:
246 from interpreter
import Interpreter
248 Interpreter
= InterpClass
250 # Create a replacement for stdin.
251 self
.reader
= PseudoFileIn(self
.readline
, self
.readlines
)
252 self
.reader
.input = ''
253 self
.reader
.isreading
= False
255 # Set up the interpreter.
256 self
.interp
= Interpreter(locals=locals,
257 rawin
=self
.raw_input,
259 stdout
=PseudoFileOut(self
.writeOut
),
260 stderr
=PseudoFileErr(self
.writeErr
),
264 self
.buffer = Buffer()
266 # Find out for which keycodes the interpreter will autocomplete.
267 self
.autoCompleteKeys
= self
.interp
.getAutoCompleteKeys()
269 # Keep track of the last non-continuation prompt positions.
270 self
.promptPosStart
= 0
271 self
.promptPosEnd
= 0
273 # Keep track of multi-line commands.
276 # Create the command history. Commands are added into the
277 # front of the list (ie. at index 0) as they are entered.
278 # self.historyIndex is the current position in the history; it
279 # gets incremented as you retrieve the previous command,
280 # decremented as you retrieve the next, and reset when you hit
281 # Enter. self.historyIndex == -1 means you're on the current
282 # command, not in the history.
284 self
.historyIndex
= -1
286 #seb add mode for "free edit"
288 self
.MarkerDefine(0,stc
.STC_MARK_ROUNDRECT
) # marker for hidden
291 # Assign handlers for keyboard events.
292 self
.Bind(wx
.EVT_CHAR
, self
.OnChar
)
293 self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
)
295 # Assign handler for the context menu
296 self
.Bind(wx
.EVT_CONTEXT_MENU
, self
.OnContextMenu
)
297 self
.Bind(wx
.EVT_UPDATE_UI
, self
.OnUpdateUI
)
299 # Assign handlers for edit events
300 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.Cut(), id=wx
.ID_CUT
)
301 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.Copy(), id=wx
.ID_COPY
)
302 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.CopyWithPrompts(), id=frame
.ID_COPY_PLUS
)
303 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.Paste(), id=wx
.ID_PASTE
)
304 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.PasteAndRun(), id=frame
.ID_PASTE_PLUS
)
305 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.SelectAll(), id=wx
.ID_SELECTALL
)
306 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.Clear(), id=wx
.ID_CLEAR
)
307 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.Undo(), id=wx
.ID_UNDO
)
308 self
.Bind(wx
.EVT_MENU
, lambda evt
: self
.Redo(), id=wx
.ID_REDO
)
311 # Assign handler for idle time.
313 self
.Bind(wx
.EVT_IDLE
, self
.OnIdle
)
315 # Display the introductory banner information.
316 self
.showIntro(introText
)
318 # Assign some pseudo keywords to the interpreter's namespace.
319 self
.setBuiltinKeywords()
321 # Add 'shell' to the interpreter's local namespace.
324 ## NOTE: See note at bottom of this file...
325 ## #seb: File drag and drop
326 ## self.SetDropTarget( FileDropTarget(self) )
328 # Do this last so the user has complete control over their
329 # environment. They can override anything they want.
330 if execStartupScript
:
331 if startupScript
is None:
332 startupScript
= os
.environ
.get('PYTHONSTARTUP')
333 self
.execStartupScript(startupScript
)
337 wx
.CallAfter(self
.ScrollToLine
, 0)
340 def clearHistory(self
):
342 self
.historyIndex
= -1
343 dispatcher
.send(signal
="Shell.clearHistory")
350 """Set focus to the shell."""
353 def OnIdle(self
, event
):
354 """Free the CPU to do other things."""
359 def showIntro(self
, text
=''):
360 """Display introductory text in the shell."""
364 if self
.interp
.introText
:
365 if text
and not text
.endswith(os
.linesep
):
366 self
.write(os
.linesep
)
367 self
.write(self
.interp
.introText
)
368 except AttributeError:
371 def setBuiltinKeywords(self
):
372 """Create pseudo keywords as part of builtins.
374 This sets "close", "exit" and "quit" to a helpful string.
377 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
378 'Click on the close button to leave the application.'
382 """Quit the application."""
383 # XXX Good enough for now but later we want to send a close event.
384 # In the close event handler we can make sure they want to
385 # quit. Other applications, like PythonCard, may choose to
386 # hide rather than quit so we should just post the event and
387 # let the surrounding app decide what it wants to do.
388 self
.write('Click on the close button to leave the application.')
391 def setLocalShell(self
):
392 """Add 'shell' to locals as reference to ShellFacade instance."""
393 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
396 def execStartupScript(self
, startupScript
):
397 """Execute the user's PYTHONSTARTUP script if they have one."""
398 if startupScript
and os
.path
.isfile(startupScript
):
399 text
= 'Startup script executed: ' + startupScript
400 self
.push('print %r; execfile(%r)' % (text
, startupScript
))
401 self
.interp
.startupScript
= startupScript
407 """Display information about Py."""
411 Py Shell Revision: %s
412 Py Interpreter Revision: %s
415 wxPython PlatformInfo: %s
417 (__author__
, VERSION
, self
.revision
, self
.interp
.revision
,
418 sys
.version
.split()[0], wx
.VERSION_STRING
, str(wx
.PlatformInfo
),
420 self
.write(text
.strip())
423 def OnChar(self
, event
):
424 """Keypress event handler.
426 Only receives an event if OnKeyDown calls event.Skip() for the
427 corresponding event."""
433 # Prevent modification of previously submitted
434 # commands/responses.
435 if not self
.CanEdit():
437 key
= event
.GetKeyCode()
438 currpos
= self
.GetCurrentPos()
439 stoppos
= self
.promptPosEnd
440 # Return (Enter) needs to be ignored in this handler.
441 if key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
443 elif key
in self
.autoCompleteKeys
:
444 # Usually the dot (period) key activates auto completion.
445 # Get the command between the prompt and the cursor. Add
446 # the autocomplete character to the end of the command.
447 if self
.AutoCompActive():
448 self
.AutoCompCancel()
449 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
451 if self
.autoComplete
:
452 self
.autoCompleteShow(command
)
453 elif key
== ord('('):
454 # The left paren activates a call tip and cancels an
455 # active auto completion.
456 if self
.AutoCompActive():
457 self
.AutoCompCancel()
458 # Get the command between the prompt and the cursor. Add
459 # the '(' to the end of the command.
460 self
.ReplaceSelection('')
461 command
= self
.GetTextRange(stoppos
, currpos
) + '('
463 self
.autoCallTipShow(command
, self
.GetCurrentPos() == self
.GetTextLength())
465 # Allow the normal event handling to take place.
469 def OnKeyDown(self
, event
):
470 """Key down event handler."""
472 key
= event
.GetKeyCode()
473 # If the auto-complete window is up let it do its thing.
474 if self
.AutoCompActive():
478 # Prevent modification of previously submitted
479 # commands/responses.
480 controlDown
= event
.ControlDown()
481 altDown
= event
.AltDown()
482 shiftDown
= event
.ShiftDown()
483 currpos
= self
.GetCurrentPos()
484 endpos
= self
.GetTextLength()
485 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
487 if controlDown
and shiftDown
and key
in (ord('F'), ord('f')):
488 li
= self
.GetCurrentLine()
489 m
= self
.MarkerGet(li
)
491 startP
= self
.PositionFromLine(li
)
492 self
.MarkerDelete(li
, 0)
493 maxli
= self
.GetLineCount()
494 li
+= 1 # li stayed visible as header-line
496 while li
<maxli
and self
.GetLineVisible(li
) == 0:
498 endP
= self
.GetLineEndPosition(li
-1)
499 self
.ShowLines(li0
, li
-1)
500 self
.SetSelection( startP
, endP
) # select reappearing text to allow "hide again"
502 startP
,endP
= self
.GetSelection()
504 startL
,endL
= self
.LineFromPosition(startP
), self
.LineFromPosition(endP
)
506 if endL
== self
.LineFromPosition(self
.promptPosEnd
): # never hide last prompt
509 m
= self
.MarkerGet(startL
)
510 self
.MarkerAdd(startL
, 0)
511 self
.HideLines(startL
+1,endL
)
512 self
.SetCurrentPos( startP
) # to ensure caret stays visible !
514 if key
== wx
.WXK_F12
: #seb
516 # self.promptPosStart not used anyway - or ?
517 self
.promptPosEnd
= self
.PositionFromLine( self
.GetLineCount()-1 ) + len(str(sys
.ps1
))
518 self
.GotoLine(self
.GetLineCount())
519 self
.GotoPos(self
.promptPosEnd
)
520 self
.prompt() #make sure we have a prompt
521 self
.SetCaretForeground("black")
522 self
.SetCaretWidth(1) #default
523 self
.SetCaretPeriod(500) #default
525 self
.SetCaretForeground("red")
526 self
.SetCaretWidth(4)
527 self
.SetCaretPeriod(0) #steady
529 self
.noteMode
= not self
.noteMode
535 # Return (Enter) is used to submit a command to the
537 if (not controlDown
and not shiftDown
and not altDown
) and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
538 if self
.CallTipActive():
542 # Complete Text (from already typed words)
543 elif shiftDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
544 self
.OnShowCompHistory()
546 # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
547 elif controlDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
548 if self
.CallTipActive():
550 if currpos
== endpos
:
553 self
.insertLineBreak()
555 # Let Ctrl-Alt-* get handled normally.
556 elif controlDown
and altDown
:
559 # Clear the current, unexecuted command.
560 elif key
== wx
.WXK_ESCAPE
:
561 if self
.CallTipActive():
566 # Clear the current command
567 elif key
== wx
.WXK_BACK
and controlDown
and shiftDown
:
570 # Increase font size.
571 elif controlDown
and key
in (ord(']'), wx
.WXK_NUMPAD_ADD
):
572 dispatcher
.send(signal
='FontIncrease')
574 # Decrease font size.
575 elif controlDown
and key
in (ord('['), wx
.WXK_NUMPAD_SUBTRACT
):
576 dispatcher
.send(signal
='FontDecrease')
579 elif controlDown
and key
in (ord('='), wx
.WXK_NUMPAD_DIVIDE
):
580 dispatcher
.send(signal
='FontDefault')
582 # Cut to the clipboard.
583 elif (controlDown
and key
in (ord('X'), ord('x'))) \
584 or (shiftDown
and key
== wx
.WXK_DELETE
):
587 # Copy to the clipboard.
588 elif controlDown
and not shiftDown \
589 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
592 # Copy to the clipboard, including prompts.
593 elif controlDown
and shiftDown \
594 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
595 self
.CopyWithPrompts()
597 # Copy to the clipboard, including prefixed prompts.
598 elif altDown
and not controlDown \
599 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
600 self
.CopyWithPromptsPrefixed()
602 # Home needs to be aware of the prompt.
603 elif key
== wx
.WXK_HOME
:
604 home
= self
.promptPosEnd
606 self
.SetCurrentPos(home
)
607 if not selecting
and not shiftDown
:
609 self
.EnsureCaretVisible()
614 # The following handlers modify text, so we need to see if
615 # there is a selection that includes text prior to the prompt.
617 # Don't modify a selection with text prior to the prompt.
618 elif selecting
and key
not in NAVKEYS
and not self
.CanEdit():
621 # Paste from the clipboard.
622 elif (controlDown
and not shiftDown
and key
in (ord('V'), ord('v'))) \
623 or (shiftDown
and not controlDown
and key
== wx
.WXK_INSERT
):
626 # manually invoke AutoComplete and Calltips
627 elif controlDown
and key
== wx
.WXK_SPACE
:
628 self
.OnCallTipAutoCompleteManually(shiftDown
)
630 # Paste from the clipboard, run commands.
631 elif controlDown
and shiftDown
and key
in (ord('V'), ord('v')):
634 # Replace with the previous command from the history buffer.
635 elif (controlDown
and key
== wx
.WXK_UP
) \
636 or (altDown
and key
in (ord('P'), ord('p'))):
637 self
.OnHistoryReplace(step
=+1)
639 # Replace with the next command from the history buffer.
640 elif (controlDown
and key
== wx
.WXK_DOWN
) \
641 or (altDown
and key
in (ord('N'), ord('n'))):
642 self
.OnHistoryReplace(step
=-1)
644 # Insert the previous command from the history buffer.
645 elif (shiftDown
and key
== wx
.WXK_UP
) and self
.CanEdit():
646 self
.OnHistoryInsert(step
=+1)
648 # Insert the next command from the history buffer.
649 elif (shiftDown
and key
== wx
.WXK_DOWN
) and self
.CanEdit():
650 self
.OnHistoryInsert(step
=-1)
652 # Search up the history for the text in front of the cursor.
653 elif key
== wx
.WXK_F8
:
654 self
.OnHistorySearch()
656 # Don't backspace over the latest non-continuation prompt.
657 elif key
== wx
.WXK_BACK
:
658 if selecting
and self
.CanEdit():
660 elif currpos
> self
.promptPosEnd
:
663 # Only allow these keys after the latest prompt.
664 elif key
in (wx
.WXK_TAB
, wx
.WXK_DELETE
):
668 # Don't toggle between insert mode and overwrite mode.
669 elif key
== wx
.WXK_INSERT
:
672 # Don't allow line deletion.
673 elif controlDown
and key
in (ord('L'), ord('l')):
676 # Don't allow line transposition.
677 elif controlDown
and key
in (ord('T'), ord('t')):
680 # Basic navigation keys should work anywhere.
684 # Protect the readonly portion of the shell.
685 elif not self
.CanEdit():
692 def OnShowCompHistory(self
):
693 """Show possible autocompletion Words from already typed words."""
696 his
= self
.history
[:]
698 #put together in one string
699 joined
= " ".join (his
)
702 #sort out only "good" words
703 newlist
= re
.split("[ \.\[\]=}(\)\,0-9\"]", joined
)
705 #length > 1 (mix out "trash")
711 #unique (no duplicate words
712 #oneliner from german python forum => unique list
713 unlist
= [thlist
[i
] for i
in xrange(len(thlist
)) if thlist
[i
] not in thlist
[:i
]]
716 unlist
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
718 #this is more convenient, isn't it?
719 self
.AutoCompSetIgnoreCase(True)
721 #join again together in a string
722 stringlist
= " ".join(unlist
)
724 #pos von 0 noch ausrechnen
726 #how big is the offset?
727 cpos
= self
.GetCurrentPos() - 1
728 while chr (self
.GetCharAt (cpos
)).isalnum():
731 #the most important part
732 self
.AutoCompShow(self
.GetCurrentPos() - cpos
-1, stringlist
)
735 def clearCommand(self
):
736 """Delete the current, unexecuted command."""
737 startpos
= self
.promptPosEnd
738 endpos
= self
.GetTextLength()
739 self
.SetSelection(startpos
, endpos
)
740 self
.ReplaceSelection('')
743 def OnHistoryReplace(self
, step
):
744 """Replace with the previous/next command from the history buffer."""
746 self
.replaceFromHistory(step
)
748 def replaceFromHistory(self
, step
):
749 """Replace selection with command from the history buffer."""
751 self
.ReplaceSelection('')
752 newindex
= self
.historyIndex
+ step
753 if -1 <= newindex
<= len(self
.history
):
754 self
.historyIndex
= newindex
755 if 0 <= newindex
<= len(self
.history
)-1:
756 command
= self
.history
[self
.historyIndex
]
757 command
= command
.replace('\n', os
.linesep
+ ps2
)
758 self
.ReplaceSelection(command
)
760 def OnHistoryInsert(self
, step
):
761 """Insert the previous/next command from the history buffer."""
762 if not self
.CanEdit():
764 startpos
= self
.GetCurrentPos()
765 self
.replaceFromHistory(step
)
766 endpos
= self
.GetCurrentPos()
767 self
.SetSelection(endpos
, startpos
)
769 def OnHistorySearch(self
):
770 """Search up the history buffer for the text in front of the cursor."""
771 if not self
.CanEdit():
773 startpos
= self
.GetCurrentPos()
774 # The text up to the cursor is what we search for.
775 numCharsAfterCursor
= self
.GetTextLength() - startpos
776 searchText
= self
.getCommand(rstrip
=False)
777 if numCharsAfterCursor
> 0:
778 searchText
= searchText
[:-numCharsAfterCursor
]
781 # Search upwards from the current history position and loop
782 # back to the beginning if we don't find anything.
783 if (self
.historyIndex
<= -1) \
784 or (self
.historyIndex
>= len(self
.history
)-2):
785 searchOrder
= range(len(self
.history
))
787 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
788 range(self
.historyIndex
)
789 for i
in searchOrder
:
790 command
= self
.history
[i
]
791 if command
[:len(searchText
)] == searchText
:
792 # Replace the current selection with the one we found.
793 self
.ReplaceSelection(command
[len(searchText
):])
794 endpos
= self
.GetCurrentPos()
795 self
.SetSelection(endpos
, startpos
)
796 # We've now warped into middle of the history.
797 self
.historyIndex
= i
800 def setStatusText(self
, text
):
801 """Display status information."""
803 # This method will likely be replaced by the enclosing app to
804 # do something more interesting, like write to a status bar.
807 def insertLineBreak(self
):
808 """Insert a new line break."""
810 self
.write(os
.linesep
)
814 def processLine(self
):
815 """Process the line of text at which the user hit Enter."""
817 # The user hit ENTER and we need to decide what to do. They
818 # could be sitting on any line in the shell.
820 thepos
= self
.GetCurrentPos()
821 startpos
= self
.promptPosEnd
822 endpos
= self
.GetTextLength()
824 # If they hit RETURN inside the current command, execute the
827 self
.SetCurrentPos(endpos
)
828 self
.interp
.more
= False
829 command
= self
.GetTextRange(startpos
, endpos
)
830 lines
= command
.split(os
.linesep
+ ps2
)
831 lines
= [line
.rstrip() for line
in lines
]
832 command
= '\n'.join(lines
)
833 if self
.reader
.isreading
:
835 # Match the behavior of the standard Python shell
836 # when the user hits return without entering a
839 self
.reader
.input = command
840 self
.write(os
.linesep
)
843 wx
.FutureCall(1, self
.EnsureCaretVisible
)
844 # Or replace the current command with the other command.
846 # If the line contains a command (even an invalid one).
847 if self
.getCommand(rstrip
=False):
848 command
= self
.getMultilineCommand()
851 # Otherwise, put the cursor back where we started.
853 self
.SetCurrentPos(thepos
)
854 self
.SetAnchor(thepos
)
856 def getMultilineCommand(self
, rstrip
=True):
857 """Extract a multi-line command from the editor.
859 The command may not necessarily be valid Python syntax."""
860 # XXX Need to extract real prompts here. Need to keep track of
861 # the prompt every time a command is issued.
866 # This is a total hack job, but it works.
867 text
= self
.GetCurLine()[0]
868 line
= self
.GetCurrentLine()
869 while text
[:ps2size
] == ps2
and line
> 0:
872 text
= self
.GetCurLine()[0]
873 if text
[:ps1size
] == ps1
:
874 line
= self
.GetCurrentLine()
876 startpos
= self
.GetCurrentPos() + ps1size
879 while self
.GetCurLine()[0][:ps2size
] == ps2
:
882 stoppos
= self
.GetCurrentPos()
883 command
= self
.GetTextRange(startpos
, stoppos
)
884 command
= command
.replace(os
.linesep
+ ps2
, '\n')
885 command
= command
.rstrip()
886 command
= command
.replace('\n', os
.linesep
+ ps2
)
890 command
= command
.rstrip()
893 def getCommand(self
, text
=None, rstrip
=True):
894 """Extract a command from text which may include a shell prompt.
896 The command may not necessarily be valid Python syntax."""
898 text
= self
.GetCurLine()[0]
899 # Strip the prompt off the front leaving just the command.
900 command
= self
.lstripPrompt(text
)
902 command
= '' # Real commands have prompts.
904 command
= command
.rstrip()
907 def lstripPrompt(self
, text
):
908 """Return text without a leading prompt."""
913 # Strip the prompt off the front of text.
914 if text
[:ps1size
] == ps1
:
915 text
= text
[ps1size
:]
916 elif text
[:ps2size
] == ps2
:
917 text
= text
[ps2size
:]
920 def push(self
, command
, silent
= False):
921 """Send command to the interpreter for execution."""
923 self
.write(os
.linesep
)
924 busy
= wx
.BusyCursor()
926 self
.more
= self
.interp
.push(command
)
930 self
.addHistory(command
.rstrip())
934 def addHistory(self
, command
):
935 """Add command to the command history."""
936 # Reset the history position.
937 self
.historyIndex
= -1
938 # Insert this command into the history, unless it's a blank
939 # line or the same as the last command.
941 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
942 self
.history
.insert(0, command
)
943 dispatcher
.send(signal
="Shell.addHistory", command
=command
)
945 def write(self
, text
):
946 """Display text in the shell.
948 Replace line endings with OS-specific endings."""
949 text
= self
.fixLineEndings(text
)
951 self
.EnsureCaretVisible()
953 def fixLineEndings(self
, text
):
954 """Return text with line endings replaced by OS-specific endings."""
955 lines
= text
.split('\r\n')
956 for l
in range(len(lines
)):
957 chunks
= lines
[l
].split('\r')
958 for c
in range(len(chunks
)):
959 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
960 lines
[l
] = os
.linesep
.join(chunks
)
961 text
= os
.linesep
.join(lines
)
965 """Display proper prompt for the context: ps1, ps2 or ps3.
967 If this is a continuation line, autoindent as necessary."""
968 isreading
= self
.reader
.isreading
971 prompt
= str(sys
.ps3
)
973 prompt
= str(sys
.ps2
)
975 prompt
= str(sys
.ps1
)
976 pos
= self
.GetCurLine()[1]
981 self
.write(os
.linesep
)
983 self
.promptPosStart
= self
.GetCurrentPos()
987 self
.promptPosEnd
= self
.GetCurrentPos()
988 # Keep the undo feature from undoing previous responses.
989 self
.EmptyUndoBuffer()
990 # XXX Add some autoindent magic here if more.
992 self
.write(' '*4) # Temporary hack indentation.
993 self
.EnsureCaretVisible()
994 self
.ScrollToColumn(0)
997 """Replacement for stdin.readline()."""
1000 reader
.isreading
= True
1003 while not reader
.input:
1005 input = reader
.input
1008 reader
.isreading
= False
1009 input = str(input) # In case of Unicode.
1012 def readlines(self
):
1013 """Replacement for stdin.readlines()."""
1015 while lines
[-1:] != ['\n']:
1016 lines
.append(self
.readline())
1019 def raw_input(self
, prompt
=''):
1020 """Return string based on user input."""
1023 return self
.readline()
1025 def ask(self
, prompt
='Please enter your response:'):
1026 """Get response from the user using a dialog box."""
1027 dialog
= wx
.TextEntryDialog(None, prompt
,
1028 'Input Dialog (Raw)', '')
1030 if dialog
.ShowModal() == wx
.ID_OK
:
1031 text
= dialog
.GetValue()
1038 """Halt execution pending a response from the user."""
1039 self
.ask('Press enter to continue:')
1042 """Delete all text from the shell."""
1045 def run(self
, command
, prompt
=True, verbose
=True):
1046 """Execute command as if it was typed in directly.
1047 >>> shell.run('print "this"')
1052 # Go to the very bottom of the text.
1053 endpos
= self
.GetTextLength()
1054 self
.SetCurrentPos(endpos
)
1055 command
= command
.rstrip()
1056 if prompt
: self
.prompt()
1057 if verbose
: self
.write(command
)
1060 def runfile(self
, filename
):
1061 """Execute all commands in file as if they were typed into the
1063 file = open(filename
)
1066 for command
in file.readlines():
1067 if command
[:6] == 'shell.':
1068 # Run shell methods silently.
1069 self
.run(command
, prompt
=False, verbose
=False)
1071 self
.run(command
, prompt
=False, verbose
=True)
1075 def autoCompleteShow(self
, command
, offset
= 0):
1076 """Display auto-completion popup list."""
1077 self
.AutoCompSetAutoHide(self
.autoCompleteAutoHide
)
1078 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
1079 list = self
.interp
.getAutoCompleteList(command
,
1080 includeMagic
=self
.autoCompleteIncludeMagic
,
1081 includeSingle
=self
.autoCompleteIncludeSingle
,
1082 includeDouble
=self
.autoCompleteIncludeDouble
)
1084 options
= ' '.join(list)
1086 self
.AutoCompShow(offset
, options
)
1088 def autoCallTipShow(self
, command
, insertcalltip
= True, forceCallTip
= False):
1089 """Display argument spec and docstring in a popup window."""
1090 if self
.CallTipActive():
1091 self
.CallTipCancel()
1092 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
1094 dispatcher
.send(signal
='Shell.calltip', sender
=self
, calltip
=tip
)
1095 if not self
.autoCallTip
and not forceCallTip
:
1097 if argspec
and insertcalltip
and self
.callTipInsert
:
1098 startpos
= self
.GetCurrentPos()
1099 self
.write(argspec
+ ')')
1100 endpos
= self
.GetCurrentPos()
1101 self
.SetSelection(endpos
, startpos
)
1103 curpos
= self
.GetCurrentPos()
1104 tippos
= curpos
- (len(name
) + 1)
1105 fallback
= curpos
- self
.GetColumn(curpos
)
1106 # In case there isn't enough room, only go back to the
1108 tippos
= max(tippos
, fallback
)
1109 self
.CallTipShow(tippos
, tip
)
1111 def OnCallTipAutoCompleteManually (self
, shiftDown
):
1112 """AutoComplete and Calltips manually."""
1113 if self
.AutoCompActive():
1114 self
.AutoCompCancel()
1115 currpos
= self
.GetCurrentPos()
1116 stoppos
= self
.promptPosEnd
1119 #go back until '.' is found
1121 while cpos
>= stoppos
:
1122 if self
.GetCharAt(cpos
) == ord ('.'):
1123 pointavailpos
= cpos
1127 #word from non whitespace until '.'
1128 if pointavailpos
!= -1:
1129 #look backward for first whitespace char
1130 textbehind
= self
.GetTextRange (pointavailpos
+ 1, currpos
)
1135 stoppos
= self
.promptPosEnd
1136 textbefore
= self
.GetTextRange(stoppos
, pointavailpos
)
1137 self
.autoCompleteShow(textbefore
, len (textbehind
))
1140 cpos
= pointavailpos
1142 while cpos
> stoppos
:
1143 if chr(self
.GetCharAt(cpos
)).isspace():
1149 ctips
= self
.GetTextRange (begpos
, currpos
)
1150 ctindex
= ctips
.find ('(')
1151 if ctindex
!= -1 and not self
.CallTipActive():
1152 #insert calltip, if current pos is '(', otherwise show it only
1153 self
.autoCallTipShow(ctips
[:ctindex
+ 1],
1154 self
.GetCharAt(currpos
- 1) == ord('(') and self
.GetCurrentPos() == self
.GetTextLength(),
1158 def writeOut(self
, text
):
1159 """Replacement for stdout."""
1162 def writeErr(self
, text
):
1163 """Replacement for stderr."""
1166 def redirectStdin(self
, redirect
=True):
1167 """If redirect is true then sys.stdin will come from the shell."""
1169 sys
.stdin
= self
.reader
1171 sys
.stdin
= self
.stdin
1173 def redirectStdout(self
, redirect
=True):
1174 """If redirect is true then sys.stdout will go to the shell."""
1176 sys
.stdout
= PseudoFileOut(self
.writeOut
)
1178 sys
.stdout
= self
.stdout
1180 def redirectStderr(self
, redirect
=True):
1181 """If redirect is true then sys.stderr will go to the shell."""
1183 sys
.stderr
= PseudoFileErr(self
.writeErr
)
1185 sys
.stderr
= self
.stderr
1188 """Return true if text is selected and can be cut."""
1189 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
1190 and self
.GetSelectionStart() >= self
.promptPosEnd \
1191 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1197 """Return true if a paste should succeed."""
1198 if self
.CanEdit() and editwindow
.EditWindow
.CanPaste(self
):
1204 """Return true if editing should succeed."""
1205 if self
.GetSelectionStart() != self
.GetSelectionEnd():
1206 if self
.GetSelectionStart() >= self
.promptPosEnd \
1207 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1212 return self
.GetCurrentPos() >= self
.promptPosEnd
1215 """Remove selection and place it on the clipboard."""
1216 if self
.CanCut() and self
.CanCopy():
1217 if self
.AutoCompActive():
1218 self
.AutoCompCancel()
1219 if self
.CallTipActive():
1220 self
.CallTipCancel()
1222 self
.ReplaceSelection('')
1225 """Copy selection and place it on the clipboard."""
1229 command
= self
.GetSelectedText()
1230 command
= command
.replace(os
.linesep
+ ps2
, os
.linesep
)
1231 command
= command
.replace(os
.linesep
+ ps1
, os
.linesep
)
1232 command
= self
.lstripPrompt(text
=command
)
1233 data
= wx
.TextDataObject(command
)
1236 def CopyWithPrompts(self
):
1237 """Copy selection, including prompts, and place it on the clipboard."""
1239 command
= self
.GetSelectedText()
1240 data
= wx
.TextDataObject(command
)
1243 def CopyWithPromptsPrefixed(self
):
1244 """Copy selection, including prompts prefixed with four
1245 spaces, and place it on the clipboard."""
1247 command
= self
.GetSelectedText()
1249 command
= spaces
+ command
.replace(os
.linesep
,
1250 os
.linesep
+ spaces
)
1251 data
= wx
.TextDataObject(command
)
1254 def _clip(self
, data
):
1255 if wx
.TheClipboard
.Open():
1256 wx
.TheClipboard
.UsePrimarySelection(False)
1257 wx
.TheClipboard
.SetData(data
)
1258 wx
.TheClipboard
.Flush()
1259 wx
.TheClipboard
.Close()
1262 """Replace selection with clipboard contents."""
1263 if self
.CanPaste() and wx
.TheClipboard
.Open():
1265 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1266 data
= wx
.TextDataObject()
1267 if wx
.TheClipboard
.GetData(data
):
1268 self
.ReplaceSelection('')
1269 command
= data
.GetText()
1270 command
= command
.rstrip()
1271 command
= self
.fixLineEndings(command
)
1272 command
= self
.lstripPrompt(text
=command
)
1273 command
= command
.replace(os
.linesep
+ ps2
, '\n')
1274 command
= command
.replace(os
.linesep
, '\n')
1275 command
= command
.replace('\n', os
.linesep
+ ps2
)
1277 wx
.TheClipboard
.Close()
1280 def PasteAndRun(self
):
1281 """Replace selection with clipboard contents, run commands."""
1283 if wx
.TheClipboard
.Open():
1284 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1285 data
= wx
.TextDataObject()
1286 if wx
.TheClipboard
.GetData(data
):
1287 text
= data
.GetText()
1288 wx
.TheClipboard
.Close()
1293 def Execute(self
, text
):
1294 """Replace selection with text and run commands."""
1297 endpos
= self
.GetTextLength()
1298 self
.SetCurrentPos(endpos
)
1299 startpos
= self
.promptPosEnd
1300 self
.SetSelection(startpos
, endpos
)
1301 self
.ReplaceSelection('')
1302 text
= text
.lstrip()
1303 text
= self
.fixLineEndings(text
)
1304 text
= self
.lstripPrompt(text
)
1305 text
= text
.replace(os
.linesep
+ ps1
, '\n')
1306 text
= text
.replace(os
.linesep
+ ps2
, '\n')
1307 text
= text
.replace(os
.linesep
, '\n')
1308 lines
= text
.split('\n')
1312 if line
.strip() == ps2
.strip():
1313 # If we are pasting from something like a
1314 # web page that drops the trailing space
1315 # from the ps2 prompt of a blank line.
1317 lstrip
= line
.lstrip()
1318 if line
.strip() != '' and lstrip
== line
and \
1319 lstrip
[:4] not in ['else','elif'] and \
1320 lstrip
[:6] != 'except':
1323 # Add the previous command to the list.
1324 commands
.append(command
)
1325 # Start a new command, which may be multiline.
1328 # Multiline command. Add to the command.
1331 commands
.append(command
)
1332 for command
in commands
:
1333 command
= command
.replace('\n', os
.linesep
+ ps2
)
1338 def wrap(self
, wrap
=True):
1339 """Sets whether text is word wrapped."""
1341 self
.SetWrapMode(wrap
)
1342 except AttributeError:
1343 return 'Wrapping is not available in this version.'
1345 def zoom(self
, points
=0):
1346 """Set the zoom level.
1348 This number of points is added to the size of all fonts. It
1349 may be positive to magnify or negative to reduce."""
1350 self
.SetZoom(points
)
1354 def LoadSettings(self
, config
):
1355 self
.autoComplete
= config
.ReadBool('Options/AutoComplete', True)
1356 self
.autoCompleteIncludeMagic
= config
.ReadBool('Options/AutoCompleteIncludeMagic', True)
1357 self
.autoCompleteIncludeSingle
= config
.ReadBool('Options/AutoCompleteIncludeSingle', True)
1358 self
.autoCompleteIncludeDouble
= config
.ReadBool('Options/AutoCompleteIncludeDouble', True)
1360 self
.autoCallTip
= config
.ReadBool('Options/AutoCallTip', True)
1361 self
.callTipInsert
= config
.ReadBool('Options/CallTipInsert', True)
1362 self
.SetWrapMode(config
.ReadBool('View/WrapMode', True))
1364 useAA
= config
.ReadBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1365 self
.SetUseAntiAliasing(useAA
)
1366 self
.lineNumbers
= config
.ReadBool('View/ShowLineNumbers', True)
1367 self
.setDisplayLineNumbers (self
.lineNumbers
)
1368 zoom
= config
.ReadInt('View/Zoom/Shell', -99)
1374 def SaveSettings(self
, config
):
1375 config
.WriteBool('Options/AutoComplete', self
.autoComplete
)
1376 config
.WriteBool('Options/AutoCompleteIncludeMagic', self
.autoCompleteIncludeMagic
)
1377 config
.WriteBool('Options/AutoCompleteIncludeSingle', self
.autoCompleteIncludeSingle
)
1378 config
.WriteBool('Options/AutoCompleteIncludeDouble', self
.autoCompleteIncludeDouble
)
1379 config
.WriteBool('Options/AutoCallTip', self
.autoCallTip
)
1380 config
.WriteBool('Options/CallTipInsert', self
.callTipInsert
)
1381 config
.WriteBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1382 config
.WriteBool('View/WrapMode', self
.GetWrapMode())
1383 config
.WriteBool('View/ShowLineNumbers', self
.lineNumbers
)
1384 config
.WriteInt('View/Zoom/Shell', self
.GetZoom())
1386 def GetContextMenu(self
):
1388 Create and return a context menu for the shell.
1389 This is used instead of the scintilla default menu
1390 in order to correctly respect our immutable buffer.
1393 menu
.Append(wx
.ID_UNDO
, "Undo")
1394 menu
.Append(wx
.ID_REDO
, "Redo")
1396 menu
.AppendSeparator()
1398 menu
.Append(wx
.ID_CUT
, "Cut")
1399 menu
.Append(wx
.ID_COPY
, "Copy")
1400 menu
.Append(frame
.ID_COPY_PLUS
, "Copy Plus")
1401 menu
.Append(wx
.ID_PASTE
, "Paste")
1402 menu
.Append(frame
.ID_PASTE_PLUS
, "Paste Plus")
1403 menu
.Append(wx
.ID_CLEAR
, "Clear")
1405 menu
.AppendSeparator()
1407 menu
.Append(wx
.ID_SELECTALL
, "Select All")
1410 def OnContextMenu(self
, evt
):
1411 menu
= self
.GetContextMenu()
1412 self
.PopupMenu(menu
)
1414 def OnUpdateUI(self
, evt
):
1416 if id in (wx
.ID_CUT
, wx
.ID_CLEAR
):
1417 evt
.Enable(self
.CanCut())
1418 elif id in (wx
.ID_COPY
, frame
.ID_COPY_PLUS
):
1419 evt
.Enable(self
.CanCopy())
1420 elif id in (wx
.ID_PASTE
, frame
.ID_PASTE_PLUS
):
1421 evt
.Enable(self
.CanPaste())
1422 elif id == wx
.ID_UNDO
:
1423 evt
.Enable(self
.CanUndo())
1424 elif id == wx
.ID_REDO
:
1425 evt
.Enable(self
.CanRedo())
1430 ## NOTE: The DnD of file names is disabled until I can figure out how
1431 ## best to still allow DnD of text.
1434 ## #seb : File drag and drop
1435 ## class FileDropTarget(wx.FileDropTarget):
1436 ## def __init__(self, obj):
1437 ## wx.FileDropTarget.__init__(self)
1439 ## def OnDropFiles(self, x, y, filenames):
1440 ## if len(filenames) == 1:
1441 ## txt = 'r\"%s\"' % filenames[0]
1444 ## for f in filenames:
1445 ## txt += 'r\"%s\" , ' % f
1447 ## self.obj.AppendText(txt)
1448 ## pos = self.obj.GetCurrentPos()
1449 ## self.obj.SetCurrentPos( pos )
1450 ## self.obj.SetSelection( pos, pos )
1454 ## class TextAndFileDropTarget(wx.DropTarget):
1455 ## def __init__(self, shell):
1456 ## wx.DropTarget.__init__(self)
1457 ## self.shell = shell
1458 ## self.compdo = wx.DataObjectComposite()
1459 ## self.textdo = wx.TextDataObject()
1460 ## self.filedo = wx.FileDataObject()
1461 ## self.compdo.Add(self.textdo)
1462 ## self.compdo.Add(self.filedo, True)
1464 ## self.SetDataObject(self.compdo)
1466 ## def OnDrop(self, x, y):
1469 ## def OnData(self, x, y, result):
1471 ## if self.textdo.GetTextLength() > 1:
1472 ## text = self.textdo.GetText()
1473 ## # *** Do somethign with the dragged text here...
1474 ## self.textdo.SetText('')
1476 ## filenames = str(self.filename.GetFilenames())
1477 ## if len(filenames) == 1:
1478 ## txt = 'r\"%s\"' % filenames[0]
1481 ## for f in filenames:
1482 ## txt += 'r\"%s\" , ' % f
1484 ## self.shell.AppendText(txt)
1485 ## pos = self.shell.GetCurrentPos()
1486 ## self.shell.SetCurrentPos( pos )
1487 ## self.shell.SetSelection( pos, pos )