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 idle time.
297 self
.Bind(wx
.EVT_IDLE
, self
.OnIdle
)
299 # Display the introductory banner information.
300 self
.showIntro(introText
)
302 # Assign some pseudo keywords to the interpreter's namespace.
303 self
.setBuiltinKeywords()
305 # Add 'shell' to the interpreter's local namespace.
308 ## NOTE: See note at bottom of this file...
309 ## #seb: File drag and drop
310 ## self.SetDropTarget( FileDropTarget(self) )
312 # Do this last so the user has complete control over their
313 # environment. They can override anything they want.
314 if execStartupScript
:
315 if startupScript
is None:
316 startupScript
= os
.environ
.get('PYTHONSTARTUP')
317 self
.execStartupScript(startupScript
)
321 wx
.CallAfter(self
.ScrollToLine
, 0)
324 def clearHistory(self
):
326 self
.historyIndex
= -1
327 dispatcher
.send(signal
="Shell.clearHistory")
334 """Set focus to the shell."""
337 def OnIdle(self
, event
):
338 """Free the CPU to do other things."""
343 def showIntro(self
, text
=''):
344 """Display introductory text in the shell."""
348 if self
.interp
.introText
:
349 if text
and not text
.endswith(os
.linesep
):
350 self
.write(os
.linesep
)
351 self
.write(self
.interp
.introText
)
352 except AttributeError:
355 def setBuiltinKeywords(self
):
356 """Create pseudo keywords as part of builtins.
358 This sets "close", "exit" and "quit" to a helpful string.
361 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
362 'Click on the close button to leave the application.'
366 """Quit the application."""
367 # XXX Good enough for now but later we want to send a close event.
368 # In the close event handler we can make sure they want to
369 # quit. Other applications, like PythonCard, may choose to
370 # hide rather than quit so we should just post the event and
371 # let the surrounding app decide what it wants to do.
372 self
.write('Click on the close button to leave the application.')
375 def setLocalShell(self
):
376 """Add 'shell' to locals as reference to ShellFacade instance."""
377 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
380 def execStartupScript(self
, startupScript
):
381 """Execute the user's PYTHONSTARTUP script if they have one."""
382 if startupScript
and os
.path
.isfile(startupScript
):
383 text
= 'Startup script executed: ' + startupScript
384 self
.push('print %r; execfile(%r)' % (text
, startupScript
))
385 self
.interp
.startupScript
= startupScript
391 """Display information about Py."""
395 Py Shell Revision: %s
396 Py Interpreter Revision: %s
399 wxPython PlatformInfo: %s
401 (__author__
, VERSION
, self
.revision
, self
.interp
.revision
,
402 sys
.version
.split()[0], wx
.VERSION_STRING
, str(wx
.PlatformInfo
),
404 self
.write(text
.strip())
407 def OnChar(self
, event
):
408 """Keypress event handler.
410 Only receives an event if OnKeyDown calls event.Skip() for the
411 corresponding event."""
417 # Prevent modification of previously submitted
418 # commands/responses.
419 if not self
.CanEdit():
421 key
= event
.GetKeyCode()
422 currpos
= self
.GetCurrentPos()
423 stoppos
= self
.promptPosEnd
424 # Return (Enter) needs to be ignored in this handler.
425 if key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
427 elif key
in self
.autoCompleteKeys
:
428 # Usually the dot (period) key activates auto completion.
429 # Get the command between the prompt and the cursor. Add
430 # the autocomplete character to the end of the command.
431 if self
.AutoCompActive():
432 self
.AutoCompCancel()
433 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
435 if self
.autoComplete
:
436 self
.autoCompleteShow(command
)
437 elif key
== ord('('):
438 # The left paren activates a call tip and cancels an
439 # active auto completion.
440 if self
.AutoCompActive():
441 self
.AutoCompCancel()
442 # Get the command between the prompt and the cursor. Add
443 # the '(' to the end of the command.
444 self
.ReplaceSelection('')
445 command
= self
.GetTextRange(stoppos
, currpos
) + '('
447 self
.autoCallTipShow(command
, self
.GetCurrentPos() == self
.GetTextLength())
449 # Allow the normal event handling to take place.
453 def OnKeyDown(self
, event
):
454 """Key down event handler."""
456 key
= event
.GetKeyCode()
457 # If the auto-complete window is up let it do its thing.
458 if self
.AutoCompActive():
462 # Prevent modification of previously submitted
463 # commands/responses.
464 controlDown
= event
.ControlDown()
465 altDown
= event
.AltDown()
466 shiftDown
= event
.ShiftDown()
467 currpos
= self
.GetCurrentPos()
468 endpos
= self
.GetTextLength()
469 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
471 if controlDown
and shiftDown
and key
in (ord('F'), ord('f')):
472 li
= self
.GetCurrentLine()
473 m
= self
.MarkerGet(li
)
475 startP
= self
.PositionFromLine(li
)
476 self
.MarkerDelete(li
, 0)
477 maxli
= self
.GetLineCount()
478 li
+= 1 # li stayed visible as header-line
480 while li
<maxli
and self
.GetLineVisible(li
) == 0:
482 endP
= self
.GetLineEndPosition(li
-1)
483 self
.ShowLines(li0
, li
-1)
484 self
.SetSelection( startP
, endP
) # select reappearing text to allow "hide again"
486 startP
,endP
= self
.GetSelection()
488 startL
,endL
= self
.LineFromPosition(startP
), self
.LineFromPosition(endP
)
490 if endL
== self
.LineFromPosition(self
.promptPosEnd
): # never hide last prompt
493 m
= self
.MarkerGet(startL
)
494 self
.MarkerAdd(startL
, 0)
495 self
.HideLines(startL
+1,endL
)
496 self
.SetCurrentPos( startP
) # to ensure caret stays visible !
498 if key
== wx
.WXK_F12
: #seb
500 # self.promptPosStart not used anyway - or ?
501 self
.promptPosEnd
= self
.PositionFromLine( self
.GetLineCount()-1 ) + len(str(sys
.ps1
))
502 self
.GotoLine(self
.GetLineCount())
503 self
.GotoPos(self
.promptPosEnd
)
504 self
.prompt() #make sure we have a prompt
505 self
.SetCaretForeground("black")
506 self
.SetCaretWidth(1) #default
507 self
.SetCaretPeriod(500) #default
509 self
.SetCaretForeground("red")
510 self
.SetCaretWidth(4)
511 self
.SetCaretPeriod(0) #steady
513 self
.noteMode
= not self
.noteMode
519 # Return (Enter) is used to submit a command to the
521 if (not controlDown
and not shiftDown
and not altDown
) and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
522 if self
.CallTipActive():
526 # Complete Text (from already typed words)
527 elif shiftDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
528 self
.OnShowCompHistory()
530 # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
531 elif controlDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
532 if self
.CallTipActive():
534 if currpos
== endpos
:
537 self
.insertLineBreak()
539 # Let Ctrl-Alt-* get handled normally.
540 elif controlDown
and altDown
:
543 # Clear the current, unexecuted command.
544 elif key
== wx
.WXK_ESCAPE
:
545 if self
.CallTipActive():
550 # Clear the current command
551 elif key
== wx
.WXK_BACK
and controlDown
and shiftDown
:
554 # Increase font size.
555 elif controlDown
and key
in (ord(']'), wx
.WXK_NUMPAD_ADD
):
556 dispatcher
.send(signal
='FontIncrease')
558 # Decrease font size.
559 elif controlDown
and key
in (ord('['), wx
.WXK_NUMPAD_SUBTRACT
):
560 dispatcher
.send(signal
='FontDecrease')
563 elif controlDown
and key
in (ord('='), wx
.WXK_NUMPAD_DIVIDE
):
564 dispatcher
.send(signal
='FontDefault')
566 # Cut to the clipboard.
567 elif (controlDown
and key
in (ord('X'), ord('x'))) \
568 or (shiftDown
and key
== wx
.WXK_DELETE
):
571 # Copy to the clipboard.
572 elif controlDown
and not shiftDown \
573 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
576 # Copy to the clipboard, including prompts.
577 elif controlDown
and shiftDown \
578 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
579 self
.CopyWithPrompts()
581 # Copy to the clipboard, including prefixed prompts.
582 elif altDown
and not controlDown \
583 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
584 self
.CopyWithPromptsPrefixed()
586 # Home needs to be aware of the prompt.
587 elif key
== wx
.WXK_HOME
:
588 home
= self
.promptPosEnd
590 self
.SetCurrentPos(home
)
591 if not selecting
and not shiftDown
:
593 self
.EnsureCaretVisible()
598 # The following handlers modify text, so we need to see if
599 # there is a selection that includes text prior to the prompt.
601 # Don't modify a selection with text prior to the prompt.
602 elif selecting
and key
not in NAVKEYS
and not self
.CanEdit():
605 # Paste from the clipboard.
606 elif (controlDown
and not shiftDown
and key
in (ord('V'), ord('v'))) \
607 or (shiftDown
and not controlDown
and key
== wx
.WXK_INSERT
):
610 # manually invoke AutoComplete and Calltips
611 elif controlDown
and key
== wx
.WXK_SPACE
:
612 self
.OnCallTipAutoCompleteManually(shiftDown
)
614 # Paste from the clipboard, run commands.
615 elif controlDown
and shiftDown
and key
in (ord('V'), ord('v')):
618 # Replace with the previous command from the history buffer.
619 elif (controlDown
and key
== wx
.WXK_UP
) \
620 or (altDown
and key
in (ord('P'), ord('p'))):
621 self
.OnHistoryReplace(step
=+1)
623 # Replace with the next command from the history buffer.
624 elif (controlDown
and key
== wx
.WXK_DOWN
) \
625 or (altDown
and key
in (ord('N'), ord('n'))):
626 self
.OnHistoryReplace(step
=-1)
628 # Insert the previous command from the history buffer.
629 elif (shiftDown
and key
== wx
.WXK_UP
) and self
.CanEdit():
630 self
.OnHistoryInsert(step
=+1)
632 # Insert the next command from the history buffer.
633 elif (shiftDown
and key
== wx
.WXK_DOWN
) and self
.CanEdit():
634 self
.OnHistoryInsert(step
=-1)
636 # Search up the history for the text in front of the cursor.
637 elif key
== wx
.WXK_F8
:
638 self
.OnHistorySearch()
640 # Don't backspace over the latest non-continuation prompt.
641 elif key
== wx
.WXK_BACK
:
642 if selecting
and self
.CanEdit():
644 elif currpos
> self
.promptPosEnd
:
647 # Only allow these keys after the latest prompt.
648 elif key
in (wx
.WXK_TAB
, wx
.WXK_DELETE
):
652 # Don't toggle between insert mode and overwrite mode.
653 elif key
== wx
.WXK_INSERT
:
656 # Don't allow line deletion.
657 elif controlDown
and key
in (ord('L'), ord('l')):
660 # Don't allow line transposition.
661 elif controlDown
and key
in (ord('T'), ord('t')):
664 # Basic navigation keys should work anywhere.
668 # Protect the readonly portion of the shell.
669 elif not self
.CanEdit():
676 def OnShowCompHistory(self
):
677 """Show possible autocompletion Words from already typed words."""
680 his
= self
.history
[:]
682 #put together in one string
683 joined
= " ".join (his
)
686 #sort out only "good" words
687 newlist
= re
.split("[ \.\[\]=}(\)\,0-9\"]", joined
)
689 #length > 1 (mix out "trash")
695 #unique (no duplicate words
696 #oneliner from german python forum => unique list
697 unlist
= [thlist
[i
] for i
in xrange(len(thlist
)) if thlist
[i
] not in thlist
[:i
]]
700 unlist
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
702 #this is more convenient, isn't it?
703 self
.AutoCompSetIgnoreCase(True)
705 #join again together in a string
706 stringlist
= " ".join(unlist
)
708 #pos von 0 noch ausrechnen
710 #how big is the offset?
711 cpos
= self
.GetCurrentPos() - 1
712 while chr (self
.GetCharAt (cpos
)).isalnum():
715 #the most important part
716 self
.AutoCompShow(self
.GetCurrentPos() - cpos
-1, stringlist
)
719 def clearCommand(self
):
720 """Delete the current, unexecuted command."""
721 startpos
= self
.promptPosEnd
722 endpos
= self
.GetTextLength()
723 self
.SetSelection(startpos
, endpos
)
724 self
.ReplaceSelection('')
727 def OnHistoryReplace(self
, step
):
728 """Replace with the previous/next command from the history buffer."""
730 self
.replaceFromHistory(step
)
732 def replaceFromHistory(self
, step
):
733 """Replace selection with command from the history buffer."""
735 self
.ReplaceSelection('')
736 newindex
= self
.historyIndex
+ step
737 if -1 <= newindex
<= len(self
.history
):
738 self
.historyIndex
= newindex
739 if 0 <= newindex
<= len(self
.history
)-1:
740 command
= self
.history
[self
.historyIndex
]
741 command
= command
.replace('\n', os
.linesep
+ ps2
)
742 self
.ReplaceSelection(command
)
744 def OnHistoryInsert(self
, step
):
745 """Insert the previous/next command from the history buffer."""
746 if not self
.CanEdit():
748 startpos
= self
.GetCurrentPos()
749 self
.replaceFromHistory(step
)
750 endpos
= self
.GetCurrentPos()
751 self
.SetSelection(endpos
, startpos
)
753 def OnHistorySearch(self
):
754 """Search up the history buffer for the text in front of the cursor."""
755 if not self
.CanEdit():
757 startpos
= self
.GetCurrentPos()
758 # The text up to the cursor is what we search for.
759 numCharsAfterCursor
= self
.GetTextLength() - startpos
760 searchText
= self
.getCommand(rstrip
=False)
761 if numCharsAfterCursor
> 0:
762 searchText
= searchText
[:-numCharsAfterCursor
]
765 # Search upwards from the current history position and loop
766 # back to the beginning if we don't find anything.
767 if (self
.historyIndex
<= -1) \
768 or (self
.historyIndex
>= len(self
.history
)-2):
769 searchOrder
= range(len(self
.history
))
771 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
772 range(self
.historyIndex
)
773 for i
in searchOrder
:
774 command
= self
.history
[i
]
775 if command
[:len(searchText
)] == searchText
:
776 # Replace the current selection with the one we found.
777 self
.ReplaceSelection(command
[len(searchText
):])
778 endpos
= self
.GetCurrentPos()
779 self
.SetSelection(endpos
, startpos
)
780 # We've now warped into middle of the history.
781 self
.historyIndex
= i
784 def setStatusText(self
, text
):
785 """Display status information."""
787 # This method will likely be replaced by the enclosing app to
788 # do something more interesting, like write to a status bar.
791 def insertLineBreak(self
):
792 """Insert a new line break."""
794 self
.write(os
.linesep
)
798 def processLine(self
):
799 """Process the line of text at which the user hit Enter."""
801 # The user hit ENTER and we need to decide what to do. They
802 # could be sitting on any line in the shell.
804 thepos
= self
.GetCurrentPos()
805 startpos
= self
.promptPosEnd
806 endpos
= self
.GetTextLength()
808 # If they hit RETURN inside the current command, execute the
811 self
.SetCurrentPos(endpos
)
812 self
.interp
.more
= False
813 command
= self
.GetTextRange(startpos
, endpos
)
814 lines
= command
.split(os
.linesep
+ ps2
)
815 lines
= [line
.rstrip() for line
in lines
]
816 command
= '\n'.join(lines
)
817 if self
.reader
.isreading
:
819 # Match the behavior of the standard Python shell
820 # when the user hits return without entering a
823 self
.reader
.input = command
824 self
.write(os
.linesep
)
827 wx
.FutureCall(1, self
.EnsureCaretVisible
)
828 # Or replace the current command with the other command.
830 # If the line contains a command (even an invalid one).
831 if self
.getCommand(rstrip
=False):
832 command
= self
.getMultilineCommand()
835 # Otherwise, put the cursor back where we started.
837 self
.SetCurrentPos(thepos
)
838 self
.SetAnchor(thepos
)
840 def getMultilineCommand(self
, rstrip
=True):
841 """Extract a multi-line command from the editor.
843 The command may not necessarily be valid Python syntax."""
844 # XXX Need to extract real prompts here. Need to keep track of
845 # the prompt every time a command is issued.
850 # This is a total hack job, but it works.
851 text
= self
.GetCurLine()[0]
852 line
= self
.GetCurrentLine()
853 while text
[:ps2size
] == ps2
and line
> 0:
856 text
= self
.GetCurLine()[0]
857 if text
[:ps1size
] == ps1
:
858 line
= self
.GetCurrentLine()
860 startpos
= self
.GetCurrentPos() + ps1size
863 while self
.GetCurLine()[0][:ps2size
] == ps2
:
866 stoppos
= self
.GetCurrentPos()
867 command
= self
.GetTextRange(startpos
, stoppos
)
868 command
= command
.replace(os
.linesep
+ ps2
, '\n')
869 command
= command
.rstrip()
870 command
= command
.replace('\n', os
.linesep
+ ps2
)
874 command
= command
.rstrip()
877 def getCommand(self
, text
=None, rstrip
=True):
878 """Extract a command from text which may include a shell prompt.
880 The command may not necessarily be valid Python syntax."""
882 text
= self
.GetCurLine()[0]
883 # Strip the prompt off the front leaving just the command.
884 command
= self
.lstripPrompt(text
)
886 command
= '' # Real commands have prompts.
888 command
= command
.rstrip()
891 def lstripPrompt(self
, text
):
892 """Return text without a leading prompt."""
897 # Strip the prompt off the front of text.
898 if text
[:ps1size
] == ps1
:
899 text
= text
[ps1size
:]
900 elif text
[:ps2size
] == ps2
:
901 text
= text
[ps2size
:]
904 def push(self
, command
, silent
= False):
905 """Send command to the interpreter for execution."""
907 self
.write(os
.linesep
)
908 busy
= wx
.BusyCursor()
910 self
.more
= self
.interp
.push(command
)
914 self
.addHistory(command
.rstrip())
918 def addHistory(self
, command
):
919 """Add command to the command history."""
920 # Reset the history position.
921 self
.historyIndex
= -1
922 # Insert this command into the history, unless it's a blank
923 # line or the same as the last command.
925 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
926 self
.history
.insert(0, command
)
927 dispatcher
.send(signal
="Shell.addHistory", command
=command
)
929 def write(self
, text
):
930 """Display text in the shell.
932 Replace line endings with OS-specific endings."""
933 text
= self
.fixLineEndings(text
)
935 self
.EnsureCaretVisible()
937 def fixLineEndings(self
, text
):
938 """Return text with line endings replaced by OS-specific endings."""
939 lines
= text
.split('\r\n')
940 for l
in range(len(lines
)):
941 chunks
= lines
[l
].split('\r')
942 for c
in range(len(chunks
)):
943 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
944 lines
[l
] = os
.linesep
.join(chunks
)
945 text
= os
.linesep
.join(lines
)
949 """Display proper prompt for the context: ps1, ps2 or ps3.
951 If this is a continuation line, autoindent as necessary."""
952 isreading
= self
.reader
.isreading
955 prompt
= str(sys
.ps3
)
957 prompt
= str(sys
.ps2
)
959 prompt
= str(sys
.ps1
)
960 pos
= self
.GetCurLine()[1]
965 self
.write(os
.linesep
)
967 self
.promptPosStart
= self
.GetCurrentPos()
971 self
.promptPosEnd
= self
.GetCurrentPos()
972 # Keep the undo feature from undoing previous responses.
973 self
.EmptyUndoBuffer()
974 # XXX Add some autoindent magic here if more.
976 self
.write(' '*4) # Temporary hack indentation.
977 self
.EnsureCaretVisible()
978 self
.ScrollToColumn(0)
981 """Replacement for stdin.readline()."""
984 reader
.isreading
= True
987 while not reader
.input:
992 reader
.isreading
= False
993 input = str(input) # In case of Unicode.
997 """Replacement for stdin.readlines()."""
999 while lines
[-1:] != ['\n']:
1000 lines
.append(self
.readline())
1003 def raw_input(self
, prompt
=''):
1004 """Return string based on user input."""
1007 return self
.readline()
1009 def ask(self
, prompt
='Please enter your response:'):
1010 """Get response from the user using a dialog box."""
1011 dialog
= wx
.TextEntryDialog(None, prompt
,
1012 'Input Dialog (Raw)', '')
1014 if dialog
.ShowModal() == wx
.ID_OK
:
1015 text
= dialog
.GetValue()
1022 """Halt execution pending a response from the user."""
1023 self
.ask('Press enter to continue:')
1026 """Delete all text from the shell."""
1029 def run(self
, command
, prompt
=True, verbose
=True):
1030 """Execute command as if it was typed in directly.
1031 >>> shell.run('print "this"')
1036 # Go to the very bottom of the text.
1037 endpos
= self
.GetTextLength()
1038 self
.SetCurrentPos(endpos
)
1039 command
= command
.rstrip()
1040 if prompt
: self
.prompt()
1041 if verbose
: self
.write(command
)
1044 def runfile(self
, filename
):
1045 """Execute all commands in file as if they were typed into the
1047 file = open(filename
)
1050 for command
in file.readlines():
1051 if command
[:6] == 'shell.':
1052 # Run shell methods silently.
1053 self
.run(command
, prompt
=False, verbose
=False)
1055 self
.run(command
, prompt
=False, verbose
=True)
1059 def autoCompleteShow(self
, command
, offset
= 0):
1060 """Display auto-completion popup list."""
1061 self
.AutoCompSetAutoHide(self
.autoCompleteAutoHide
)
1062 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
1063 list = self
.interp
.getAutoCompleteList(command
,
1064 includeMagic
=self
.autoCompleteIncludeMagic
,
1065 includeSingle
=self
.autoCompleteIncludeSingle
,
1066 includeDouble
=self
.autoCompleteIncludeDouble
)
1068 options
= ' '.join(list)
1070 self
.AutoCompShow(offset
, options
)
1072 def autoCallTipShow(self
, command
, insertcalltip
= True, forceCallTip
= False):
1073 """Display argument spec and docstring in a popup window."""
1074 if self
.CallTipActive():
1075 self
.CallTipCancel()
1076 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
1078 dispatcher
.send(signal
='Shell.calltip', sender
=self
, calltip
=tip
)
1079 if not self
.autoCallTip
and not forceCallTip
:
1081 if argspec
and insertcalltip
and self
.callTipInsert
:
1082 startpos
= self
.GetCurrentPos()
1083 self
.write(argspec
+ ')')
1084 endpos
= self
.GetCurrentPos()
1085 self
.SetSelection(endpos
, startpos
)
1087 curpos
= self
.GetCurrentPos()
1088 tippos
= curpos
- (len(name
) + 1)
1089 fallback
= curpos
- self
.GetColumn(curpos
)
1090 # In case there isn't enough room, only go back to the
1092 tippos
= max(tippos
, fallback
)
1093 self
.CallTipShow(tippos
, tip
)
1095 def OnCallTipAutoCompleteManually (self
, shiftDown
):
1096 """AutoComplete and Calltips manually."""
1097 if self
.AutoCompActive():
1098 self
.AutoCompCancel()
1099 currpos
= self
.GetCurrentPos()
1100 stoppos
= self
.promptPosEnd
1103 #go back until '.' is found
1105 while cpos
>= stoppos
:
1106 if self
.GetCharAt(cpos
) == ord ('.'):
1107 pointavailpos
= cpos
1111 #word from non whitespace until '.'
1112 if pointavailpos
!= -1:
1113 #look backward for first whitespace char
1114 textbehind
= self
.GetTextRange (pointavailpos
+ 1, currpos
)
1119 stoppos
= self
.promptPosEnd
1120 textbefore
= self
.GetTextRange(stoppos
, pointavailpos
)
1121 self
.autoCompleteShow(textbefore
, len (textbehind
))
1124 cpos
= pointavailpos
1126 while cpos
> stoppos
:
1127 if chr(self
.GetCharAt(cpos
)).isspace():
1133 ctips
= self
.GetTextRange (begpos
, currpos
)
1134 ctindex
= ctips
.find ('(')
1135 if ctindex
!= -1 and not self
.CallTipActive():
1136 #insert calltip, if current pos is '(', otherwise show it only
1137 self
.autoCallTipShow(ctips
[:ctindex
+ 1],
1138 self
.GetCharAt(currpos
- 1) == ord('(') and self
.GetCurrentPos() == self
.GetTextLength(),
1142 def writeOut(self
, text
):
1143 """Replacement for stdout."""
1146 def writeErr(self
, text
):
1147 """Replacement for stderr."""
1150 def redirectStdin(self
, redirect
=True):
1151 """If redirect is true then sys.stdin will come from the shell."""
1153 sys
.stdin
= self
.reader
1155 sys
.stdin
= self
.stdin
1157 def redirectStdout(self
, redirect
=True):
1158 """If redirect is true then sys.stdout will go to the shell."""
1160 sys
.stdout
= PseudoFileOut(self
.writeOut
)
1162 sys
.stdout
= self
.stdout
1164 def redirectStderr(self
, redirect
=True):
1165 """If redirect is true then sys.stderr will go to the shell."""
1167 sys
.stderr
= PseudoFileErr(self
.writeErr
)
1169 sys
.stderr
= self
.stderr
1172 """Return true if text is selected and can be cut."""
1173 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
1174 and self
.GetSelectionStart() >= self
.promptPosEnd \
1175 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1181 """Return true if a paste should succeed."""
1182 if self
.CanEdit() and editwindow
.EditWindow
.CanPaste(self
):
1188 """Return true if editing should succeed."""
1189 if self
.GetSelectionStart() != self
.GetSelectionEnd():
1190 if self
.GetSelectionStart() >= self
.promptPosEnd \
1191 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1196 return self
.GetCurrentPos() >= self
.promptPosEnd
1199 """Remove selection and place it on the clipboard."""
1200 if self
.CanCut() and self
.CanCopy():
1201 if self
.AutoCompActive():
1202 self
.AutoCompCancel()
1203 if self
.CallTipActive():
1204 self
.CallTipCancel()
1206 self
.ReplaceSelection('')
1209 """Copy selection and place it on the clipboard."""
1213 command
= self
.GetSelectedText()
1214 command
= command
.replace(os
.linesep
+ ps2
, os
.linesep
)
1215 command
= command
.replace(os
.linesep
+ ps1
, os
.linesep
)
1216 command
= self
.lstripPrompt(text
=command
)
1217 data
= wx
.TextDataObject(command
)
1220 def CopyWithPrompts(self
):
1221 """Copy selection, including prompts, and place it on the clipboard."""
1223 command
= self
.GetSelectedText()
1224 data
= wx
.TextDataObject(command
)
1227 def CopyWithPromptsPrefixed(self
):
1228 """Copy selection, including prompts prefixed with four
1229 spaces, and place it on the clipboard."""
1231 command
= self
.GetSelectedText()
1233 command
= spaces
+ command
.replace(os
.linesep
,
1234 os
.linesep
+ spaces
)
1235 data
= wx
.TextDataObject(command
)
1238 def _clip(self
, data
):
1239 if wx
.TheClipboard
.Open():
1240 wx
.TheClipboard
.UsePrimarySelection(False)
1241 wx
.TheClipboard
.SetData(data
)
1242 wx
.TheClipboard
.Flush()
1243 wx
.TheClipboard
.Close()
1246 """Replace selection with clipboard contents."""
1247 if self
.CanPaste() and wx
.TheClipboard
.Open():
1249 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1250 data
= wx
.TextDataObject()
1251 if wx
.TheClipboard
.GetData(data
):
1252 self
.ReplaceSelection('')
1253 command
= data
.GetText()
1254 command
= command
.rstrip()
1255 command
= self
.fixLineEndings(command
)
1256 command
= self
.lstripPrompt(text
=command
)
1257 command
= command
.replace(os
.linesep
+ ps2
, '\n')
1258 command
= command
.replace(os
.linesep
, '\n')
1259 command
= command
.replace('\n', os
.linesep
+ ps2
)
1261 wx
.TheClipboard
.Close()
1264 def PasteAndRun(self
):
1265 """Replace selection with clipboard contents, run commands."""
1267 if wx
.TheClipboard
.Open():
1268 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1269 data
= wx
.TextDataObject()
1270 if wx
.TheClipboard
.GetData(data
):
1271 text
= data
.GetText()
1272 wx
.TheClipboard
.Close()
1277 def Execute(self
, text
):
1278 """Replace selection with text and run commands."""
1281 endpos
= self
.GetTextLength()
1282 self
.SetCurrentPos(endpos
)
1283 startpos
= self
.promptPosEnd
1284 self
.SetSelection(startpos
, endpos
)
1285 self
.ReplaceSelection('')
1286 text
= text
.lstrip()
1287 text
= self
.fixLineEndings(text
)
1288 text
= self
.lstripPrompt(text
)
1289 text
= text
.replace(os
.linesep
+ ps1
, '\n')
1290 text
= text
.replace(os
.linesep
+ ps2
, '\n')
1291 text
= text
.replace(os
.linesep
, '\n')
1292 lines
= text
.split('\n')
1296 if line
.strip() == ps2
.strip():
1297 # If we are pasting from something like a
1298 # web page that drops the trailing space
1299 # from the ps2 prompt of a blank line.
1301 lstrip
= line
.lstrip()
1302 if line
.strip() != '' and lstrip
== line
and \
1303 lstrip
[:4] not in ['else','elif'] and \
1304 lstrip
[:6] != 'except':
1307 # Add the previous command to the list.
1308 commands
.append(command
)
1309 # Start a new command, which may be multiline.
1312 # Multiline command. Add to the command.
1315 commands
.append(command
)
1316 for command
in commands
:
1317 command
= command
.replace('\n', os
.linesep
+ ps2
)
1322 def wrap(self
, wrap
=True):
1323 """Sets whether text is word wrapped."""
1325 self
.SetWrapMode(wrap
)
1326 except AttributeError:
1327 return 'Wrapping is not available in this version.'
1329 def zoom(self
, points
=0):
1330 """Set the zoom level.
1332 This number of points is added to the size of all fonts. It
1333 may be positive to magnify or negative to reduce."""
1334 self
.SetZoom(points
)
1338 def LoadSettings(self
, config
):
1339 self
.autoComplete
= config
.ReadBool('Options/AutoComplete', True)
1340 self
.autoCompleteIncludeMagic
= config
.ReadBool('Options/AutoCompleteIncludeMagic', True)
1341 self
.autoCompleteIncludeSingle
= config
.ReadBool('Options/AutoCompleteIncludeSingle', True)
1342 self
.autoCompleteIncludeDouble
= config
.ReadBool('Options/AutoCompleteIncludeDouble', True)
1344 self
.autoCallTip
= config
.ReadBool('Options/AutoCallTip', True)
1345 self
.callTipInsert
= config
.ReadBool('Options/CallTipInsert', True)
1346 self
.SetWrapMode(config
.ReadBool('View/WrapMode', True))
1348 useAA
= config
.ReadBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1349 self
.SetUseAntiAliasing(useAA
)
1350 self
.lineNumbers
= config
.ReadBool('View/ShowLineNumbers', True)
1351 self
.setDisplayLineNumbers (self
.lineNumbers
)
1352 zoom
= config
.ReadInt('View/Zoom/Shell', -99)
1358 def SaveSettings(self
, config
):
1359 config
.WriteBool('Options/AutoComplete', self
.autoComplete
)
1360 config
.WriteBool('Options/AutoCompleteIncludeMagic', self
.autoCompleteIncludeMagic
)
1361 config
.WriteBool('Options/AutoCompleteIncludeSingle', self
.autoCompleteIncludeSingle
)
1362 config
.WriteBool('Options/AutoCompleteIncludeDouble', self
.autoCompleteIncludeDouble
)
1363 config
.WriteBool('Options/AutoCallTip', self
.autoCallTip
)
1364 config
.WriteBool('Options/CallTipInsert', self
.callTipInsert
)
1365 config
.WriteBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1366 config
.WriteBool('View/WrapMode', self
.GetWrapMode())
1367 config
.WriteBool('View/ShowLineNumbers', self
.lineNumbers
)
1368 config
.WriteInt('View/Zoom/Shell', self
.GetZoom())
1372 ## NOTE: The DnD of file names is disabled until I can figure out how
1373 ## best to still allow DnD of text.
1376 ## #seb : File drag and drop
1377 ## class FileDropTarget(wx.FileDropTarget):
1378 ## def __init__(self, obj):
1379 ## wx.FileDropTarget.__init__(self)
1381 ## def OnDropFiles(self, x, y, filenames):
1382 ## if len(filenames) == 1:
1383 ## txt = 'r\"%s\"' % filenames[0]
1386 ## for f in filenames:
1387 ## txt += 'r\"%s\" , ' % f
1389 ## self.obj.AppendText(txt)
1390 ## pos = self.obj.GetCurrentPos()
1391 ## self.obj.SetCurrentPos( pos )
1392 ## self.obj.SetSelection( pos, pos )
1396 ## class TextAndFileDropTarget(wx.DropTarget):
1397 ## def __init__(self, shell):
1398 ## wx.DropTarget.__init__(self)
1399 ## self.shell = shell
1400 ## self.compdo = wx.DataObjectComposite()
1401 ## self.textdo = wx.TextDataObject()
1402 ## self.filedo = wx.FileDataObject()
1403 ## self.compdo.Add(self.textdo)
1404 ## self.compdo.Add(self.filedo, True)
1406 ## self.SetDataObject(self.compdo)
1408 ## def OnDrop(self, x, y):
1411 ## def OnData(self, x, y, result):
1413 ## if self.textdo.GetTextLength() > 1:
1414 ## text = self.textdo.GetText()
1415 ## # *** Do somethign with the dragged text here...
1416 ## self.textdo.SetText('')
1418 ## filenames = str(self.filename.GetFilenames())
1419 ## if len(filenames) == 1:
1420 ## txt = 'r\"%s\"' % filenames[0]
1423 ## for f in filenames:
1424 ## txt += 'r\"%s\" , ' % f
1426 ## self.shell.AppendText(txt)
1427 ## pos = self.shell.GetCurrentPos()
1428 ## self.shell.SetCurrentPos( pos )
1429 ## self.shell.SetSelection( pos, pos )