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."""
346 if not text
.endswith(os
.linesep
):
350 self
.write(self
.interp
.introText
)
351 except AttributeError:
354 def setBuiltinKeywords(self
):
355 """Create pseudo keywords as part of builtins.
357 This sets "close", "exit" and "quit" to a helpful string.
360 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
361 'Click on the close button to leave the application.'
365 """Quit the application."""
366 # XXX Good enough for now but later we want to send a close event.
367 # In the close event handler we can make sure they want to
368 # quit. Other applications, like PythonCard, may choose to
369 # hide rather than quit so we should just post the event and
370 # let the surrounding app decide what it wants to do.
371 self
.write('Click on the close button to leave the application.')
374 def setLocalShell(self
):
375 """Add 'shell' to locals as reference to ShellFacade instance."""
376 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
379 def execStartupScript(self
, startupScript
):
380 """Execute the user's PYTHONSTARTUP script if they have one."""
381 if startupScript
and os
.path
.isfile(startupScript
):
382 text
= 'Startup script executed: ' + startupScript
383 self
.push('print %r; execfile(%r)' % (text
, startupScript
))
384 self
.interp
.startupScript
= startupScript
390 """Display information about Py."""
394 Py Shell Revision: %s
395 Py Interpreter Revision: %s
398 wxPython PlatformInfo: %s
400 (__author__
, VERSION
, self
.revision
, self
.interp
.revision
,
401 sys
.version
.split()[0], wx
.VERSION_STRING
, str(wx
.PlatformInfo
),
403 self
.write(text
.strip())
406 def OnChar(self
, event
):
407 """Keypress event handler.
409 Only receives an event if OnKeyDown calls event.Skip() for the
410 corresponding event."""
416 # Prevent modification of previously submitted
417 # commands/responses.
418 if not self
.CanEdit():
420 key
= event
.GetKeyCode()
421 currpos
= self
.GetCurrentPos()
422 stoppos
= self
.promptPosEnd
423 # Return (Enter) needs to be ignored in this handler.
424 if key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
426 elif key
in self
.autoCompleteKeys
:
427 # Usually the dot (period) key activates auto completion.
428 # Get the command between the prompt and the cursor. Add
429 # the autocomplete character to the end of the command.
430 if self
.AutoCompActive():
431 self
.AutoCompCancel()
432 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
434 if self
.autoComplete
:
435 self
.autoCompleteShow(command
)
436 elif key
== ord('('):
437 # The left paren activates a call tip and cancels an
438 # active auto completion.
439 if self
.AutoCompActive():
440 self
.AutoCompCancel()
441 # Get the command between the prompt and the cursor. Add
442 # the '(' to the end of the command.
443 self
.ReplaceSelection('')
444 command
= self
.GetTextRange(stoppos
, currpos
) + '('
446 self
.autoCallTipShow(command
, self
.GetCurrentPos() == self
.GetTextLength())
448 # Allow the normal event handling to take place.
452 def OnKeyDown(self
, event
):
453 """Key down event handler."""
455 key
= event
.GetKeyCode()
456 # If the auto-complete window is up let it do its thing.
457 if self
.AutoCompActive():
461 # Prevent modification of previously submitted
462 # commands/responses.
463 controlDown
= event
.ControlDown()
464 altDown
= event
.AltDown()
465 shiftDown
= event
.ShiftDown()
466 currpos
= self
.GetCurrentPos()
467 endpos
= self
.GetTextLength()
468 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
470 if controlDown
and shiftDown
and key
in (ord('F'), ord('f')):
471 li
= self
.GetCurrentLine()
472 m
= self
.MarkerGet(li
)
474 startP
= self
.PositionFromLine(li
)
475 self
.MarkerDelete(li
, 0)
476 maxli
= self
.GetLineCount()
477 li
+= 1 # li stayed visible as header-line
479 while li
<maxli
and self
.GetLineVisible(li
) == 0:
481 endP
= self
.GetLineEndPosition(li
-1)
482 self
.ShowLines(li0
, li
-1)
483 self
.SetSelection( startP
, endP
) # select reappearing text to allow "hide again"
485 startP
,endP
= self
.GetSelection()
487 startL
,endL
= self
.LineFromPosition(startP
), self
.LineFromPosition(endP
)
489 if endL
== self
.LineFromPosition(self
.promptPosEnd
): # never hide last prompt
492 m
= self
.MarkerGet(startL
)
493 self
.MarkerAdd(startL
, 0)
494 self
.HideLines(startL
+1,endL
)
495 self
.SetCurrentPos( startP
) # to ensure caret stays visible !
497 if key
== wx
.WXK_F12
: #seb
499 # self.promptPosStart not used anyway - or ?
500 self
.promptPosEnd
= self
.PositionFromLine( self
.GetLineCount()-1 ) + len(str(sys
.ps1
))
501 self
.GotoLine(self
.GetLineCount())
502 self
.GotoPos(self
.promptPosEnd
)
503 self
.prompt() #make sure we have a prompt
504 self
.SetCaretForeground("black")
505 self
.SetCaretWidth(1) #default
506 self
.SetCaretPeriod(500) #default
508 self
.SetCaretForeground("red")
509 self
.SetCaretWidth(4)
510 self
.SetCaretPeriod(0) #steady
512 self
.noteMode
= not self
.noteMode
518 # Return (Enter) is used to submit a command to the
520 if (not controlDown
and not shiftDown
and not altDown
) and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
521 if self
.CallTipActive():
525 # Complete Text (from already typed words)
526 elif shiftDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
527 self
.OnShowCompHistory()
529 # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
530 elif controlDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
531 if self
.CallTipActive():
533 if currpos
== endpos
:
536 self
.insertLineBreak()
538 # Let Ctrl-Alt-* get handled normally.
539 elif controlDown
and altDown
:
542 # Clear the current, unexecuted command.
543 elif key
== wx
.WXK_ESCAPE
:
544 if self
.CallTipActive():
549 # Clear the current command
550 elif key
== wx
.WXK_BACK
and controlDown
and shiftDown
:
553 # Increase font size.
554 elif controlDown
and key
in (ord(']'), wx
.WXK_NUMPAD_ADD
):
555 dispatcher
.send(signal
='FontIncrease')
557 # Decrease font size.
558 elif controlDown
and key
in (ord('['), wx
.WXK_NUMPAD_SUBTRACT
):
559 dispatcher
.send(signal
='FontDecrease')
562 elif controlDown
and key
in (ord('='), wx
.WXK_NUMPAD_DIVIDE
):
563 dispatcher
.send(signal
='FontDefault')
565 # Cut to the clipboard.
566 elif (controlDown
and key
in (ord('X'), ord('x'))) \
567 or (shiftDown
and key
== wx
.WXK_DELETE
):
570 # Copy to the clipboard.
571 elif controlDown
and not shiftDown \
572 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
575 # Copy to the clipboard, including prompts.
576 elif controlDown
and shiftDown \
577 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
578 self
.CopyWithPrompts()
580 # Copy to the clipboard, including prefixed prompts.
581 elif altDown
and not controlDown \
582 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
583 self
.CopyWithPromptsPrefixed()
585 # Home needs to be aware of the prompt.
586 elif key
== wx
.WXK_HOME
:
587 home
= self
.promptPosEnd
589 self
.SetCurrentPos(home
)
590 if not selecting
and not shiftDown
:
592 self
.EnsureCaretVisible()
597 # The following handlers modify text, so we need to see if
598 # there is a selection that includes text prior to the prompt.
600 # Don't modify a selection with text prior to the prompt.
601 elif selecting
and key
not in NAVKEYS
and not self
.CanEdit():
604 # Paste from the clipboard.
605 elif (controlDown
and not shiftDown
and key
in (ord('V'), ord('v'))) \
606 or (shiftDown
and not controlDown
and key
== wx
.WXK_INSERT
):
609 # manually invoke AutoComplete and Calltips
610 elif controlDown
and key
== wx
.WXK_SPACE
:
611 self
.OnCallTipAutoCompleteManually(shiftDown
)
613 # Paste from the clipboard, run commands.
614 elif controlDown
and shiftDown
and key
in (ord('V'), ord('v')):
617 # Replace with the previous command from the history buffer.
618 elif (controlDown
and key
== wx
.WXK_UP
) \
619 or (altDown
and key
in (ord('P'), ord('p'))):
620 self
.OnHistoryReplace(step
=+1)
622 # Replace with the next command from the history buffer.
623 elif (controlDown
and key
== wx
.WXK_DOWN
) \
624 or (altDown
and key
in (ord('N'), ord('n'))):
625 self
.OnHistoryReplace(step
=-1)
627 # Insert the previous command from the history buffer.
628 elif (shiftDown
and key
== wx
.WXK_UP
) and self
.CanEdit():
629 self
.OnHistoryInsert(step
=+1)
631 # Insert the next command from the history buffer.
632 elif (shiftDown
and key
== wx
.WXK_DOWN
) and self
.CanEdit():
633 self
.OnHistoryInsert(step
=-1)
635 # Search up the history for the text in front of the cursor.
636 elif key
== wx
.WXK_F8
:
637 self
.OnHistorySearch()
639 # Don't backspace over the latest non-continuation prompt.
640 elif key
== wx
.WXK_BACK
:
641 if selecting
and self
.CanEdit():
643 elif currpos
> self
.promptPosEnd
:
646 # Only allow these keys after the latest prompt.
647 elif key
in (wx
.WXK_TAB
, wx
.WXK_DELETE
):
651 # Don't toggle between insert mode and overwrite mode.
652 elif key
== wx
.WXK_INSERT
:
655 # Don't allow line deletion.
656 elif controlDown
and key
in (ord('L'), ord('l')):
659 # Don't allow line transposition.
660 elif controlDown
and key
in (ord('T'), ord('t')):
663 # Basic navigation keys should work anywhere.
667 # Protect the readonly portion of the shell.
668 elif not self
.CanEdit():
675 def OnShowCompHistory(self
):
676 """Show possible autocompletion Words from already typed words."""
679 his
= self
.history
[:]
681 #put together in one string
682 joined
= " ".join (his
)
685 #sort out only "good" words
686 newlist
= re
.split("[ \.\[\]=}(\)\,0-9\"]", joined
)
688 #length > 1 (mix out "trash")
694 #unique (no duplicate words
695 #oneliner from german python forum => unique list
696 unlist
= [thlist
[i
] for i
in xrange(len(thlist
)) if thlist
[i
] not in thlist
[:i
]]
699 unlist
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
701 #this is more convenient, isn't it?
702 self
.AutoCompSetIgnoreCase(True)
704 #join again together in a string
705 stringlist
= " ".join(unlist
)
707 #pos von 0 noch ausrechnen
709 #how big is the offset?
710 cpos
= self
.GetCurrentPos() - 1
711 while chr (self
.GetCharAt (cpos
)).isalnum():
714 #the most important part
715 self
.AutoCompShow(self
.GetCurrentPos() - cpos
-1, stringlist
)
718 def clearCommand(self
):
719 """Delete the current, unexecuted command."""
720 startpos
= self
.promptPosEnd
721 endpos
= self
.GetTextLength()
722 self
.SetSelection(startpos
, endpos
)
723 self
.ReplaceSelection('')
726 def OnHistoryReplace(self
, step
):
727 """Replace with the previous/next command from the history buffer."""
729 self
.replaceFromHistory(step
)
731 def replaceFromHistory(self
, step
):
732 """Replace selection with command from the history buffer."""
734 self
.ReplaceSelection('')
735 newindex
= self
.historyIndex
+ step
736 if -1 <= newindex
<= len(self
.history
):
737 self
.historyIndex
= newindex
738 if 0 <= newindex
<= len(self
.history
)-1:
739 command
= self
.history
[self
.historyIndex
]
740 command
= command
.replace('\n', os
.linesep
+ ps2
)
741 self
.ReplaceSelection(command
)
743 def OnHistoryInsert(self
, step
):
744 """Insert the previous/next command from the history buffer."""
745 if not self
.CanEdit():
747 startpos
= self
.GetCurrentPos()
748 self
.replaceFromHistory(step
)
749 endpos
= self
.GetCurrentPos()
750 self
.SetSelection(endpos
, startpos
)
752 def OnHistorySearch(self
):
753 """Search up the history buffer for the text in front of the cursor."""
754 if not self
.CanEdit():
756 startpos
= self
.GetCurrentPos()
757 # The text up to the cursor is what we search for.
758 numCharsAfterCursor
= self
.GetTextLength() - startpos
759 searchText
= self
.getCommand(rstrip
=False)
760 if numCharsAfterCursor
> 0:
761 searchText
= searchText
[:-numCharsAfterCursor
]
764 # Search upwards from the current history position and loop
765 # back to the beginning if we don't find anything.
766 if (self
.historyIndex
<= -1) \
767 or (self
.historyIndex
>= len(self
.history
)-2):
768 searchOrder
= range(len(self
.history
))
770 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
771 range(self
.historyIndex
)
772 for i
in searchOrder
:
773 command
= self
.history
[i
]
774 if command
[:len(searchText
)] == searchText
:
775 # Replace the current selection with the one we found.
776 self
.ReplaceSelection(command
[len(searchText
):])
777 endpos
= self
.GetCurrentPos()
778 self
.SetSelection(endpos
, startpos
)
779 # We've now warped into middle of the history.
780 self
.historyIndex
= i
783 def setStatusText(self
, text
):
784 """Display status information."""
786 # This method will likely be replaced by the enclosing app to
787 # do something more interesting, like write to a status bar.
790 def insertLineBreak(self
):
791 """Insert a new line break."""
793 self
.write(os
.linesep
)
797 def processLine(self
):
798 """Process the line of text at which the user hit Enter."""
800 # The user hit ENTER and we need to decide what to do. They
801 # could be sitting on any line in the shell.
803 thepos
= self
.GetCurrentPos()
804 startpos
= self
.promptPosEnd
805 endpos
= self
.GetTextLength()
807 # If they hit RETURN inside the current command, execute the
810 self
.SetCurrentPos(endpos
)
811 self
.interp
.more
= False
812 command
= self
.GetTextRange(startpos
, endpos
)
813 lines
= command
.split(os
.linesep
+ ps2
)
814 lines
= [line
.rstrip() for line
in lines
]
815 command
= '\n'.join(lines
)
816 if self
.reader
.isreading
:
818 # Match the behavior of the standard Python shell
819 # when the user hits return without entering a
822 self
.reader
.input = command
823 self
.write(os
.linesep
)
826 wx
.FutureCall(1, self
.EnsureCaretVisible
)
827 # Or replace the current command with the other command.
829 # If the line contains a command (even an invalid one).
830 if self
.getCommand(rstrip
=False):
831 command
= self
.getMultilineCommand()
834 # Otherwise, put the cursor back where we started.
836 self
.SetCurrentPos(thepos
)
837 self
.SetAnchor(thepos
)
839 def getMultilineCommand(self
, rstrip
=True):
840 """Extract a multi-line command from the editor.
842 The command may not necessarily be valid Python syntax."""
843 # XXX Need to extract real prompts here. Need to keep track of
844 # the prompt every time a command is issued.
849 # This is a total hack job, but it works.
850 text
= self
.GetCurLine()[0]
851 line
= self
.GetCurrentLine()
852 while text
[:ps2size
] == ps2
and line
> 0:
855 text
= self
.GetCurLine()[0]
856 if text
[:ps1size
] == ps1
:
857 line
= self
.GetCurrentLine()
859 startpos
= self
.GetCurrentPos() + ps1size
862 while self
.GetCurLine()[0][:ps2size
] == ps2
:
865 stoppos
= self
.GetCurrentPos()
866 command
= self
.GetTextRange(startpos
, stoppos
)
867 command
= command
.replace(os
.linesep
+ ps2
, '\n')
868 command
= command
.rstrip()
869 command
= command
.replace('\n', os
.linesep
+ ps2
)
873 command
= command
.rstrip()
876 def getCommand(self
, text
=None, rstrip
=True):
877 """Extract a command from text which may include a shell prompt.
879 The command may not necessarily be valid Python syntax."""
881 text
= self
.GetCurLine()[0]
882 # Strip the prompt off the front leaving just the command.
883 command
= self
.lstripPrompt(text
)
885 command
= '' # Real commands have prompts.
887 command
= command
.rstrip()
890 def lstripPrompt(self
, text
):
891 """Return text without a leading prompt."""
896 # Strip the prompt off the front of text.
897 if text
[:ps1size
] == ps1
:
898 text
= text
[ps1size
:]
899 elif text
[:ps2size
] == ps2
:
900 text
= text
[ps2size
:]
903 def push(self
, command
, silent
= False):
904 """Send command to the interpreter for execution."""
906 self
.write(os
.linesep
)
907 busy
= wx
.BusyCursor()
909 self
.more
= self
.interp
.push(command
)
913 self
.addHistory(command
.rstrip())
917 def addHistory(self
, command
):
918 """Add command to the command history."""
919 # Reset the history position.
920 self
.historyIndex
= -1
921 # Insert this command into the history, unless it's a blank
922 # line or the same as the last command.
924 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
925 self
.history
.insert(0, command
)
926 dispatcher
.send(signal
="Shell.addHistory", command
=command
)
928 def write(self
, text
):
929 """Display text in the shell.
931 Replace line endings with OS-specific endings."""
932 text
= self
.fixLineEndings(text
)
934 self
.EnsureCaretVisible()
936 def fixLineEndings(self
, text
):
937 """Return text with line endings replaced by OS-specific endings."""
938 lines
= text
.split('\r\n')
939 for l
in range(len(lines
)):
940 chunks
= lines
[l
].split('\r')
941 for c
in range(len(chunks
)):
942 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
943 lines
[l
] = os
.linesep
.join(chunks
)
944 text
= os
.linesep
.join(lines
)
948 """Display proper prompt for the context: ps1, ps2 or ps3.
950 If this is a continuation line, autoindent as necessary."""
951 isreading
= self
.reader
.isreading
954 prompt
= str(sys
.ps3
)
956 prompt
= str(sys
.ps2
)
958 prompt
= str(sys
.ps1
)
959 pos
= self
.GetCurLine()[1]
964 self
.write(os
.linesep
)
966 self
.promptPosStart
= self
.GetCurrentPos()
970 self
.promptPosEnd
= self
.GetCurrentPos()
971 # Keep the undo feature from undoing previous responses.
972 self
.EmptyUndoBuffer()
973 # XXX Add some autoindent magic here if more.
975 self
.write(' '*4) # Temporary hack indentation.
976 self
.EnsureCaretVisible()
977 self
.ScrollToColumn(0)
980 """Replacement for stdin.readline()."""
983 reader
.isreading
= True
986 while not reader
.input:
991 reader
.isreading
= False
992 input = str(input) # In case of Unicode.
996 """Replacement for stdin.readlines()."""
998 while lines
[-1:] != ['\n']:
999 lines
.append(self
.readline())
1002 def raw_input(self
, prompt
=''):
1003 """Return string based on user input."""
1006 return self
.readline()
1008 def ask(self
, prompt
='Please enter your response:'):
1009 """Get response from the user using a dialog box."""
1010 dialog
= wx
.TextEntryDialog(None, prompt
,
1011 'Input Dialog (Raw)', '')
1013 if dialog
.ShowModal() == wx
.ID_OK
:
1014 text
= dialog
.GetValue()
1021 """Halt execution pending a response from the user."""
1022 self
.ask('Press enter to continue:')
1025 """Delete all text from the shell."""
1028 def run(self
, command
, prompt
=True, verbose
=True):
1029 """Execute command as if it was typed in directly.
1030 >>> shell.run('print "this"')
1035 # Go to the very bottom of the text.
1036 endpos
= self
.GetTextLength()
1037 self
.SetCurrentPos(endpos
)
1038 command
= command
.rstrip()
1039 if prompt
: self
.prompt()
1040 if verbose
: self
.write(command
)
1043 def runfile(self
, filename
):
1044 """Execute all commands in file as if they were typed into the
1046 file = open(filename
)
1049 for command
in file.readlines():
1050 if command
[:6] == 'shell.':
1051 # Run shell methods silently.
1052 self
.run(command
, prompt
=False, verbose
=False)
1054 self
.run(command
, prompt
=False, verbose
=True)
1058 def autoCompleteShow(self
, command
, offset
= 0):
1059 """Display auto-completion popup list."""
1060 self
.AutoCompSetAutoHide(self
.autoCompleteAutoHide
)
1061 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
1062 list = self
.interp
.getAutoCompleteList(command
,
1063 includeMagic
=self
.autoCompleteIncludeMagic
,
1064 includeSingle
=self
.autoCompleteIncludeSingle
,
1065 includeDouble
=self
.autoCompleteIncludeDouble
)
1067 options
= ' '.join(list)
1069 self
.AutoCompShow(offset
, options
)
1071 def autoCallTipShow(self
, command
, insertcalltip
= True, forceCallTip
= False):
1072 """Display argument spec and docstring in a popup window."""
1073 if self
.CallTipActive():
1074 self
.CallTipCancel()
1075 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
1077 dispatcher
.send(signal
='Shell.calltip', sender
=self
, calltip
=tip
)
1078 if not self
.autoCallTip
and not forceCallTip
:
1080 if argspec
and insertcalltip
and self
.callTipInsert
:
1081 startpos
= self
.GetCurrentPos()
1082 self
.write(argspec
+ ')')
1083 endpos
= self
.GetCurrentPos()
1084 self
.SetSelection(endpos
, startpos
)
1086 curpos
= self
.GetCurrentPos()
1087 tippos
= curpos
- (len(name
) + 1)
1088 fallback
= curpos
- self
.GetColumn(curpos
)
1089 # In case there isn't enough room, only go back to the
1091 tippos
= max(tippos
, fallback
)
1092 self
.CallTipShow(tippos
, tip
)
1094 def OnCallTipAutoCompleteManually (self
, shiftDown
):
1095 """AutoComplete and Calltips manually."""
1096 if self
.AutoCompActive():
1097 self
.AutoCompCancel()
1098 currpos
= self
.GetCurrentPos()
1099 stoppos
= self
.promptPosEnd
1102 #go back until '.' is found
1104 while cpos
>= stoppos
:
1105 if self
.GetCharAt(cpos
) == ord ('.'):
1106 pointavailpos
= cpos
1110 #word from non whitespace until '.'
1111 if pointavailpos
!= -1:
1112 #look backward for first whitespace char
1113 textbehind
= self
.GetTextRange (pointavailpos
+ 1, currpos
)
1118 stoppos
= self
.promptPosEnd
1119 textbefore
= self
.GetTextRange(stoppos
, pointavailpos
)
1120 self
.autoCompleteShow(textbefore
, len (textbehind
))
1123 cpos
= pointavailpos
1125 while cpos
> stoppos
:
1126 if chr(self
.GetCharAt(cpos
)).isspace():
1132 ctips
= self
.GetTextRange (begpos
, currpos
)
1133 ctindex
= ctips
.find ('(')
1134 if ctindex
!= -1 and not self
.CallTipActive():
1135 #insert calltip, if current pos is '(', otherwise show it only
1136 self
.autoCallTipShow(ctips
[:ctindex
+ 1],
1137 self
.GetCharAt(currpos
- 1) == ord('(') and self
.GetCurrentPos() == self
.GetTextLength(),
1141 def writeOut(self
, text
):
1142 """Replacement for stdout."""
1145 def writeErr(self
, text
):
1146 """Replacement for stderr."""
1149 def redirectStdin(self
, redirect
=True):
1150 """If redirect is true then sys.stdin will come from the shell."""
1152 sys
.stdin
= self
.reader
1154 sys
.stdin
= self
.stdin
1156 def redirectStdout(self
, redirect
=True):
1157 """If redirect is true then sys.stdout will go to the shell."""
1159 sys
.stdout
= PseudoFileOut(self
.writeOut
)
1161 sys
.stdout
= self
.stdout
1163 def redirectStderr(self
, redirect
=True):
1164 """If redirect is true then sys.stderr will go to the shell."""
1166 sys
.stderr
= PseudoFileErr(self
.writeErr
)
1168 sys
.stderr
= self
.stderr
1171 """Return true if text is selected and can be cut."""
1172 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
1173 and self
.GetSelectionStart() >= self
.promptPosEnd \
1174 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1180 """Return true if a paste should succeed."""
1181 if self
.CanEdit() and editwindow
.EditWindow
.CanPaste(self
):
1187 """Return true if editing should succeed."""
1188 if self
.GetSelectionStart() != self
.GetSelectionEnd():
1189 if self
.GetSelectionStart() >= self
.promptPosEnd \
1190 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1195 return self
.GetCurrentPos() >= self
.promptPosEnd
1198 """Remove selection and place it on the clipboard."""
1199 if self
.CanCut() and self
.CanCopy():
1200 if self
.AutoCompActive():
1201 self
.AutoCompCancel()
1202 if self
.CallTipActive():
1203 self
.CallTipCancel()
1205 self
.ReplaceSelection('')
1208 """Copy selection and place it on the clipboard."""
1212 command
= self
.GetSelectedText()
1213 command
= command
.replace(os
.linesep
+ ps2
, os
.linesep
)
1214 command
= command
.replace(os
.linesep
+ ps1
, os
.linesep
)
1215 command
= self
.lstripPrompt(text
=command
)
1216 data
= wx
.TextDataObject(command
)
1219 def CopyWithPrompts(self
):
1220 """Copy selection, including prompts, and place it on the clipboard."""
1222 command
= self
.GetSelectedText()
1223 data
= wx
.TextDataObject(command
)
1226 def CopyWithPromptsPrefixed(self
):
1227 """Copy selection, including prompts prefixed with four
1228 spaces, and place it on the clipboard."""
1230 command
= self
.GetSelectedText()
1232 command
= spaces
+ command
.replace(os
.linesep
,
1233 os
.linesep
+ spaces
)
1234 data
= wx
.TextDataObject(command
)
1237 def _clip(self
, data
):
1238 if wx
.TheClipboard
.Open():
1239 wx
.TheClipboard
.UsePrimarySelection(False)
1240 wx
.TheClipboard
.SetData(data
)
1241 wx
.TheClipboard
.Flush()
1242 wx
.TheClipboard
.Close()
1245 """Replace selection with clipboard contents."""
1246 if self
.CanPaste() and wx
.TheClipboard
.Open():
1248 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1249 data
= wx
.TextDataObject()
1250 if wx
.TheClipboard
.GetData(data
):
1251 self
.ReplaceSelection('')
1252 command
= data
.GetText()
1253 command
= command
.rstrip()
1254 command
= self
.fixLineEndings(command
)
1255 command
= self
.lstripPrompt(text
=command
)
1256 command
= command
.replace(os
.linesep
+ ps2
, '\n')
1257 command
= command
.replace(os
.linesep
, '\n')
1258 command
= command
.replace('\n', os
.linesep
+ ps2
)
1260 wx
.TheClipboard
.Close()
1263 def PasteAndRun(self
):
1264 """Replace selection with clipboard contents, run commands."""
1266 if wx
.TheClipboard
.Open():
1267 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1268 data
= wx
.TextDataObject()
1269 if wx
.TheClipboard
.GetData(data
):
1270 text
= data
.GetText()
1271 wx
.TheClipboard
.Close()
1276 def Execute(self
, text
):
1277 """Replace selection with text and run commands."""
1280 endpos
= self
.GetTextLength()
1281 self
.SetCurrentPos(endpos
)
1282 startpos
= self
.promptPosEnd
1283 self
.SetSelection(startpos
, endpos
)
1284 self
.ReplaceSelection('')
1285 text
= text
.lstrip()
1286 text
= self
.fixLineEndings(text
)
1287 text
= self
.lstripPrompt(text
)
1288 text
= text
.replace(os
.linesep
+ ps1
, '\n')
1289 text
= text
.replace(os
.linesep
+ ps2
, '\n')
1290 text
= text
.replace(os
.linesep
, '\n')
1291 lines
= text
.split('\n')
1295 if line
.strip() == ps2
.strip():
1296 # If we are pasting from something like a
1297 # web page that drops the trailing space
1298 # from the ps2 prompt of a blank line.
1300 lstrip
= line
.lstrip()
1301 if line
.strip() != '' and lstrip
== line
and \
1302 lstrip
[:4] not in ['else','elif'] and \
1303 lstrip
[:6] != 'except':
1306 # Add the previous command to the list.
1307 commands
.append(command
)
1308 # Start a new command, which may be multiline.
1311 # Multiline command. Add to the command.
1314 commands
.append(command
)
1315 for command
in commands
:
1316 command
= command
.replace('\n', os
.linesep
+ ps2
)
1321 def wrap(self
, wrap
=True):
1322 """Sets whether text is word wrapped."""
1324 self
.SetWrapMode(wrap
)
1325 except AttributeError:
1326 return 'Wrapping is not available in this version.'
1328 def zoom(self
, points
=0):
1329 """Set the zoom level.
1331 This number of points is added to the size of all fonts. It
1332 may be positive to magnify or negative to reduce."""
1333 self
.SetZoom(points
)
1337 def LoadSettings(self
, config
):
1338 self
.autoComplete
= config
.ReadBool('Options/AutoComplete', True)
1339 self
.autoCompleteIncludeMagic
= config
.ReadBool('Options/AutoCompleteIncludeMagic', True)
1340 self
.autoCompleteIncludeSingle
= config
.ReadBool('Options/AutoCompleteIncludeSingle', True)
1341 self
.autoCompleteIncludeDouble
= config
.ReadBool('Options/AutoCompleteIncludeDouble', True)
1343 self
.autoCallTip
= config
.ReadBool('Options/AutoCallTip', True)
1344 self
.callTipInsert
= config
.ReadBool('Options/CallTipInsert', True)
1345 self
.SetWrapMode(config
.ReadBool('View/WrapMode', True))
1347 useAA
= config
.ReadBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1348 self
.SetUseAntiAliasing(useAA
)
1349 self
.lineNumbers
= config
.ReadBool('View/ShowLineNumbers', True)
1350 self
.setDisplayLineNumbers (self
.lineNumbers
)
1351 zoom
= config
.ReadInt('View/Zoom/Shell', -99)
1357 def SaveSettings(self
, config
):
1358 config
.WriteBool('Options/AutoComplete', self
.autoComplete
)
1359 config
.WriteBool('Options/AutoCompleteIncludeMagic', self
.autoCompleteIncludeMagic
)
1360 config
.WriteBool('Options/AutoCompleteIncludeSingle', self
.autoCompleteIncludeSingle
)
1361 config
.WriteBool('Options/AutoCompleteIncludeDouble', self
.autoCompleteIncludeDouble
)
1362 config
.WriteBool('Options/AutoCallTip', self
.autoCallTip
)
1363 config
.WriteBool('Options/CallTipInsert', self
.callTipInsert
)
1364 config
.WriteBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1365 config
.WriteBool('View/WrapMode', self
.GetWrapMode())
1366 config
.WriteBool('View/ShowLineNumbers', self
.lineNumbers
)
1367 config
.WriteInt('View/Zoom/Shell', self
.GetZoom())
1371 ## NOTE: The DnD of file names is disabled until I can figure out how
1372 ## best to still allow DnD of text.
1375 ## #seb : File drag and drop
1376 ## class FileDropTarget(wx.FileDropTarget):
1377 ## def __init__(self, obj):
1378 ## wx.FileDropTarget.__init__(self)
1380 ## def OnDropFiles(self, x, y, filenames):
1381 ## if len(filenames) == 1:
1382 ## txt = 'r\"%s\"' % filenames[0]
1385 ## for f in filenames:
1386 ## txt += 'r\"%s\" , ' % f
1388 ## self.obj.AppendText(txt)
1389 ## pos = self.obj.GetCurrentPos()
1390 ## self.obj.SetCurrentPos( pos )
1391 ## self.obj.SetSelection( pos, pos )
1395 ## class TextAndFileDropTarget(wx.DropTarget):
1396 ## def __init__(self, shell):
1397 ## wx.DropTarget.__init__(self)
1398 ## self.shell = shell
1399 ## self.compdo = wx.DataObjectComposite()
1400 ## self.textdo = wx.TextDataObject()
1401 ## self.filedo = wx.FileDataObject()
1402 ## self.compdo.Add(self.textdo)
1403 ## self.compdo.Add(self.filedo, True)
1405 ## self.SetDataObject(self.compdo)
1407 ## def OnDrop(self, x, y):
1410 ## def OnData(self, x, y, result):
1412 ## if self.textdo.GetTextLength() > 1:
1413 ## text = self.textdo.GetText()
1414 ## # *** Do somethign with the dragged text here...
1415 ## self.textdo.SetText('')
1417 ## filenames = str(self.filename.GetFilenames())
1418 ## if len(filenames) == 1:
1419 ## txt = 'r\"%s\"' % filenames[0]
1422 ## for f in filenames:
1423 ## txt += 'r\"%s\" , ' % f
1425 ## self.shell.AppendText(txt)
1426 ## pos = self.shell.GetCurrentPos()
1427 ## self.shell.SetCurrentPos( pos )
1428 ## self.shell.SetSelection( pos, pos )