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 Alt+Shift+C Clear Screen.
151 Shift+Enter Complete Text from History.
152 Ctrl+F Search (backwards) TODO: regexp-wholeWords-...
154 Ctrl+H "hide" lines containing selection / "unhide"
155 F12 on/off "free-edit" mode
159 """Simplified interface to all shell-related functionality.
161 This is a semi-transparent facade, in that all attributes of other
162 are accessible, even though only some are visible to the user."""
164 name
= 'Shell Interface'
165 revision
= __revision__
167 def __init__(self
, other
):
168 """Create a ShellFacade instance."""
171 d
['helpText'] = HELP_TEXT
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)
329 """Set focus to the shell."""
332 def OnIdle(self
, event
):
333 """Free the CPU to do other things."""
338 def showIntro(self
, text
=''):
339 """Display introductory text in the shell."""
341 if not text
.endswith(os
.linesep
):
345 self
.write(self
.interp
.introText
)
346 except AttributeError:
349 def setBuiltinKeywords(self
):
350 """Create pseudo keywords as part of builtins.
352 This sets `close`, `exit` and `quit` to a helpful string.
355 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
356 'Click on the close button to leave the application.'
360 """Quit the application."""
361 # XXX Good enough for now but later we want to send a close event.
362 # In the close event handler we can make sure they want to
363 # quit. Other applications, like PythonCard, may choose to
364 # hide rather than quit so we should just post the event and
365 # let the surrounding app decide what it wants to do.
366 self
.write('Click on the close button to leave the application.')
369 def setLocalShell(self
):
370 """Add 'shell' to locals as reference to ShellFacade instance."""
371 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
374 def execStartupScript(self
, startupScript
):
375 """Execute the user's PYTHONSTARTUP script if they have one."""
376 if startupScript
and os
.path
.isfile(startupScript
):
377 text
= 'Startup script executed: ' + startupScript
378 self
.push('print %r; execfile(%r)' % (text
, startupScript
))
379 self
.interp
.startupScript
= startupScript
385 """Display information about Py."""
389 Py Shell Revision: %s
390 Py Interpreter Revision: %s
393 wxPython PlatformInfo: %s
395 (__author__
, VERSION
, self
.revision
, self
.interp
.revision
,
396 sys
.version
.split()[0], wx
.VERSION_STRING
, str(wx
.PlatformInfo
),
398 self
.write(text
.strip())
401 def OnChar(self
, event
):
402 """Keypress event handler.
404 Only receives an event if OnKeyDown calls event.Skip() for the
405 corresponding event."""
411 # Prevent modification of previously submitted
412 # commands/responses.
413 if not self
.CanEdit():
415 key
= event
.KeyCode()
416 currpos
= self
.GetCurrentPos()
417 stoppos
= self
.promptPosEnd
418 # Return (Enter) needs to be ignored in this handler.
419 if key
== wx
.WXK_RETURN
:
421 elif key
in self
.autoCompleteKeys
:
422 # Usually the dot (period) key activates auto completion.
423 # Get the command between the prompt and the cursor. Add
424 # the autocomplete character to the end of the command.
425 if self
.AutoCompActive():
426 self
.AutoCompCancel()
427 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
429 if self
.autoComplete
:
430 self
.autoCompleteShow(command
)
431 elif key
== ord('('):
432 # The left paren activates a call tip and cancels an
433 # active auto completion.
434 if self
.AutoCompActive():
435 self
.AutoCompCancel()
436 # Get the command between the prompt and the cursor. Add
437 # the '(' to the end of the command.
438 self
.ReplaceSelection('')
439 command
= self
.GetTextRange(stoppos
, currpos
) + '('
441 self
.autoCallTipShow(command
, self
.GetCurrentPos() == self
.GetTextLength())
443 # Allow the normal event handling to take place.
447 def OnKeyDown(self
, event
):
448 """Key down event handler."""
450 key
= event
.KeyCode()
451 # If the auto-complete window is up let it do its thing.
452 if self
.AutoCompActive():
456 # Prevent modification of previously submitted
457 # commands/responses.
458 controlDown
= event
.ControlDown()
459 altDown
= event
.AltDown()
460 shiftDown
= event
.ShiftDown()
461 currpos
= self
.GetCurrentPos()
462 endpos
= self
.GetTextLength()
463 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
465 if controlDown
and shiftDown
and key
in (ord('F'), ord('f')):
466 li
= self
.GetCurrentLine()
467 m
= self
.MarkerGet(li
)
469 startP
= self
.PositionFromLine(li
)
470 self
.MarkerDelete(li
, 0)
471 maxli
= self
.GetLineCount()
472 li
+= 1 # li stayed visible as header-line
474 while li
<maxli
and self
.GetLineVisible(li
) == 0:
476 endP
= self
.GetLineEndPosition(li
-1)
477 self
.ShowLines(li0
, li
-1)
478 self
.SetSelection( startP
, endP
) # select reappearing text to allow "hide again"
480 startP
,endP
= self
.GetSelection()
482 startL
,endL
= self
.LineFromPosition(startP
), self
.LineFromPosition(endP
)
484 if endL
== self
.LineFromPosition(self
.promptPosEnd
): # never hide last prompt
487 m
= self
.MarkerGet(startL
)
488 self
.MarkerAdd(startL
, 0)
489 self
.HideLines(startL
+1,endL
)
490 self
.SetCurrentPos( startP
) # to ensure caret stays visible !
492 if key
== wx
.WXK_F12
: #seb
494 # self.promptPosStart not used anyway - or ?
495 self
.promptPosEnd
= self
.PositionFromLine( self
.GetLineCount()-1 ) + len(str(sys
.ps1
))
496 self
.GotoLine(self
.GetLineCount())
497 self
.GotoPos(self
.promptPosEnd
)
498 self
.prompt() #make sure we have a prompt
499 self
.SetCaretForeground("black")
500 self
.SetCaretWidth(1) #default
501 self
.SetCaretPeriod(500) #default
503 self
.SetCaretForeground("red")
504 self
.SetCaretWidth(4)
505 self
.SetCaretPeriod(0) #steady
507 self
.noteMode
= not self
.noteMode
513 # Return (Enter) is used to submit a command to the
515 if (not controlDown
and not shiftDown
and not altDown
) and key
== wx
.WXK_RETURN
:
516 if self
.CallTipActive():
520 # Complete Text (from already typed words)
521 elif shiftDown
and key
== wx
.WXK_RETURN
:
522 self
.OnShowCompHistory()
524 # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
525 elif controlDown
and key
== wx
.WXK_RETURN
:
526 if self
.CallTipActive():
528 if currpos
== endpos
:
531 self
.insertLineBreak()
533 # Let Ctrl-Alt-* get handled normally.
534 elif controlDown
and altDown
:
537 # Clear the current, unexecuted command.
538 elif key
== wx
.WXK_ESCAPE
:
539 if self
.CallTipActive():
544 # Increase font size.
545 elif controlDown
and key
in (ord(']'), wx
.WXK_NUMPAD_ADD
):
546 dispatcher
.send(signal
='FontIncrease')
548 # Decrease font size.
549 elif controlDown
and key
in (ord('['), wx
.WXK_NUMPAD_SUBTRACT
):
550 dispatcher
.send(signal
='FontDecrease')
553 elif controlDown
and key
in (ord('='), wx
.WXK_NUMPAD_DIVIDE
):
554 dispatcher
.send(signal
='FontDefault')
556 # Cut to the clipboard.
557 elif (controlDown
and key
in (ord('X'), ord('x'))) \
558 or (shiftDown
and key
== wx
.WXK_DELETE
):
561 # Copy to the clipboard.
562 elif controlDown
and not shiftDown \
563 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
566 # Copy to the clipboard, including prompts.
567 elif controlDown
and shiftDown \
568 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
569 self
.CopyWithPrompts()
571 # Copy to the clipboard, including prefixed prompts.
572 elif altDown
and not controlDown \
573 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
574 self
.CopyWithPromptsPrefixed()
576 # Home needs to be aware of the prompt.
577 elif key
== wx
.WXK_HOME
:
578 home
= self
.promptPosEnd
580 self
.SetCurrentPos(home
)
581 if not selecting
and not shiftDown
:
583 self
.EnsureCaretVisible()
588 # The following handlers modify text, so we need to see if
589 # there is a selection that includes text prior to the prompt.
591 # Don't modify a selection with text prior to the prompt.
592 elif selecting
and key
not in NAVKEYS
and not self
.CanEdit():
595 # Paste from the clipboard.
596 elif (controlDown
and not shiftDown
and key
in (ord('V'), ord('v'))) \
597 or (shiftDown
and not controlDown
and key
== wx
.WXK_INSERT
):
600 # manually invoke AutoComplete and Calltips
601 elif controlDown
and key
== wx
.WXK_SPACE
:
602 self
.OnCallTipAutoCompleteManually(shiftDown
)
604 # Paste from the clipboard, run commands.
605 elif controlDown
and shiftDown
and key
in (ord('V'), ord('v')):
608 # Replace with the previous command from the history buffer.
609 elif (controlDown
and key
== wx
.WXK_UP
) \
610 or (altDown
and key
in (ord('P'), ord('p'))):
611 self
.OnHistoryReplace(step
=+1)
613 # Replace with the next command from the history buffer.
614 elif (controlDown
and key
== wx
.WXK_DOWN
) \
615 or (altDown
and key
in (ord('N'), ord('n'))):
616 self
.OnHistoryReplace(step
=-1)
618 # Insert the previous command from the history buffer.
619 elif (shiftDown
and key
== wx
.WXK_UP
) and self
.CanEdit():
620 self
.OnHistoryInsert(step
=+1)
622 # Insert the next command from the history buffer.
623 elif (shiftDown
and key
== wx
.WXK_DOWN
) and self
.CanEdit():
624 self
.OnHistoryInsert(step
=-1)
626 # Search up the history for the text in front of the cursor.
627 elif key
== wx
.WXK_F8
:
628 self
.OnHistorySearch()
630 # Don't backspace over the latest non-continuation prompt.
631 elif key
== wx
.WXK_BACK
:
632 if selecting
and self
.CanEdit():
634 elif currpos
> self
.promptPosEnd
:
637 # Only allow these keys after the latest prompt.
638 elif key
in (wx
.WXK_TAB
, wx
.WXK_DELETE
):
642 # Don't toggle between insert mode and overwrite mode.
643 elif key
== wx
.WXK_INSERT
:
646 # Don't allow line deletion.
647 elif controlDown
and key
in (ord('L'), ord('l')):
650 # Don't allow line transposition.
651 elif controlDown
and key
in (ord('T'), ord('t')):
654 # Basic navigation keys should work anywhere.
658 # Protect the readonly portion of the shell.
659 elif not self
.CanEdit():
666 def OnShowCompHistory(self
):
667 """Show possible autocompletion Words from already typed words."""
670 his
= self
.history
[:]
672 #put together in one string
673 joined
= " ".join (his
)
676 #sort out only "good" words
677 newlist
= re
.split("[ \.\[\]=}(\)\,0-9\"]", joined
)
679 #length > 1 (mix out "trash")
685 #unique (no duplicate words
686 #oneliner from german python forum => unique list
687 unlist
= [thlist
[i
] for i
in xrange(len(thlist
)) if thlist
[i
] not in thlist
[:i
]]
690 unlist
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
692 #this is more convenient, isn't it?
693 self
.AutoCompSetIgnoreCase(True)
695 #join again together in a string
696 stringlist
= " ".join(unlist
)
698 #pos von 0 noch ausrechnen
700 #how big is the offset?
701 cpos
= self
.GetCurrentPos() - 1
702 while chr (self
.GetCharAt (cpos
)).isalnum():
705 #the most important part
706 self
.AutoCompShow(self
.GetCurrentPos() - cpos
-1, stringlist
)
709 def clearCommand(self
):
710 """Delete the current, unexecuted command."""
711 startpos
= self
.promptPosEnd
712 endpos
= self
.GetTextLength()
713 self
.SetSelection(startpos
, endpos
)
714 self
.ReplaceSelection('')
717 def OnHistoryReplace(self
, step
):
718 """Replace with the previous/next command from the history buffer."""
720 self
.replaceFromHistory(step
)
722 def replaceFromHistory(self
, step
):
723 """Replace selection with command from the history buffer."""
725 self
.ReplaceSelection('')
726 newindex
= self
.historyIndex
+ step
727 if -1 <= newindex
<= len(self
.history
):
728 self
.historyIndex
= newindex
729 if 0 <= newindex
<= len(self
.history
)-1:
730 command
= self
.history
[self
.historyIndex
]
731 command
= command
.replace('\n', os
.linesep
+ ps2
)
732 self
.ReplaceSelection(command
)
734 def OnHistoryInsert(self
, step
):
735 """Insert the previous/next command from the history buffer."""
736 if not self
.CanEdit():
738 startpos
= self
.GetCurrentPos()
739 self
.replaceFromHistory(step
)
740 endpos
= self
.GetCurrentPos()
741 self
.SetSelection(endpos
, startpos
)
743 def OnHistorySearch(self
):
744 """Search up the history buffer for the text in front of the cursor."""
745 if not self
.CanEdit():
747 startpos
= self
.GetCurrentPos()
748 # The text up to the cursor is what we search for.
749 numCharsAfterCursor
= self
.GetTextLength() - startpos
750 searchText
= self
.getCommand(rstrip
=False)
751 if numCharsAfterCursor
> 0:
752 searchText
= searchText
[:-numCharsAfterCursor
]
755 # Search upwards from the current history position and loop
756 # back to the beginning if we don't find anything.
757 if (self
.historyIndex
<= -1) \
758 or (self
.historyIndex
>= len(self
.history
)-2):
759 searchOrder
= range(len(self
.history
))
761 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
762 range(self
.historyIndex
)
763 for i
in searchOrder
:
764 command
= self
.history
[i
]
765 if command
[:len(searchText
)] == searchText
:
766 # Replace the current selection with the one we found.
767 self
.ReplaceSelection(command
[len(searchText
):])
768 endpos
= self
.GetCurrentPos()
769 self
.SetSelection(endpos
, startpos
)
770 # We've now warped into middle of the history.
771 self
.historyIndex
= i
774 def setStatusText(self
, text
):
775 """Display status information."""
777 # This method will likely be replaced by the enclosing app to
778 # do something more interesting, like write to a status bar.
781 def insertLineBreak(self
):
782 """Insert a new line break."""
784 self
.write(os
.linesep
)
788 def processLine(self
):
789 """Process the line of text at which the user hit Enter."""
791 # The user hit ENTER and we need to decide what to do. They
792 # could be sitting on any line in the shell.
794 thepos
= self
.GetCurrentPos()
795 startpos
= self
.promptPosEnd
796 endpos
= self
.GetTextLength()
798 # If they hit RETURN inside the current command, execute the
801 self
.SetCurrentPos(endpos
)
802 self
.interp
.more
= False
803 command
= self
.GetTextRange(startpos
, endpos
)
804 lines
= command
.split(os
.linesep
+ ps2
)
805 lines
= [line
.rstrip() for line
in lines
]
806 command
= '\n'.join(lines
)
807 if self
.reader
.isreading
:
809 # Match the behavior of the standard Python shell
810 # when the user hits return without entering a
813 self
.reader
.input = command
814 self
.write(os
.linesep
)
817 wx
.FutureCall(1, self
.EnsureCaretVisible
)
818 # Or replace the current command with the other command.
820 # If the line contains a command (even an invalid one).
821 if self
.getCommand(rstrip
=False):
822 command
= self
.getMultilineCommand()
825 # Otherwise, put the cursor back where we started.
827 self
.SetCurrentPos(thepos
)
828 self
.SetAnchor(thepos
)
830 def getMultilineCommand(self
, rstrip
=True):
831 """Extract a multi-line command from the editor.
833 The command may not necessarily be valid Python syntax."""
834 # XXX Need to extract real prompts here. Need to keep track of
835 # the prompt every time a command is issued.
840 # This is a total hack job, but it works.
841 text
= self
.GetCurLine()[0]
842 line
= self
.GetCurrentLine()
843 while text
[:ps2size
] == ps2
and line
> 0:
846 text
= self
.GetCurLine()[0]
847 if text
[:ps1size
] == ps1
:
848 line
= self
.GetCurrentLine()
850 startpos
= self
.GetCurrentPos() + ps1size
853 while self
.GetCurLine()[0][:ps2size
] == ps2
:
856 stoppos
= self
.GetCurrentPos()
857 command
= self
.GetTextRange(startpos
, stoppos
)
858 command
= command
.replace(os
.linesep
+ ps2
, '\n')
859 command
= command
.rstrip()
860 command
= command
.replace('\n', os
.linesep
+ ps2
)
864 command
= command
.rstrip()
867 def getCommand(self
, text
=None, rstrip
=True):
868 """Extract a command from text which may include a shell prompt.
870 The command may not necessarily be valid Python syntax."""
872 text
= self
.GetCurLine()[0]
873 # Strip the prompt off the front leaving just the command.
874 command
= self
.lstripPrompt(text
)
876 command
= '' # Real commands have prompts.
878 command
= command
.rstrip()
881 def lstripPrompt(self
, text
):
882 """Return text without a leading prompt."""
887 # Strip the prompt off the front of text.
888 if text
[:ps1size
] == ps1
:
889 text
= text
[ps1size
:]
890 elif text
[:ps2size
] == ps2
:
891 text
= text
[ps2size
:]
894 def push(self
, command
, silent
= False):
895 """Send command to the interpreter for execution."""
897 self
.write(os
.linesep
)
898 busy
= wx
.BusyCursor()
900 self
.more
= self
.interp
.push(command
)
904 self
.addHistory(command
.rstrip())
908 def addHistory(self
, command
):
909 """Add command to the command history."""
910 # Reset the history position.
911 self
.historyIndex
= -1
912 # Insert this command into the history, unless it's a blank
913 # line or the same as the last command.
915 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
916 self
.history
.insert(0, command
)
918 def write(self
, text
):
919 """Display text in the shell.
921 Replace line endings with OS-specific endings."""
922 text
= self
.fixLineEndings(text
)
924 self
.EnsureCaretVisible()
926 def fixLineEndings(self
, text
):
927 """Return text with line endings replaced by OS-specific endings."""
928 lines
= text
.split('\r\n')
929 for l
in range(len(lines
)):
930 chunks
= lines
[l
].split('\r')
931 for c
in range(len(chunks
)):
932 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
933 lines
[l
] = os
.linesep
.join(chunks
)
934 text
= os
.linesep
.join(lines
)
938 """Display proper prompt for the context: ps1, ps2 or ps3.
940 If this is a continuation line, autoindent as necessary."""
941 isreading
= self
.reader
.isreading
944 prompt
= str(sys
.ps3
)
946 prompt
= str(sys
.ps2
)
948 prompt
= str(sys
.ps1
)
949 pos
= self
.GetCurLine()[1]
954 self
.write(os
.linesep
)
956 self
.promptPosStart
= self
.GetCurrentPos()
960 self
.promptPosEnd
= self
.GetCurrentPos()
961 # Keep the undo feature from undoing previous responses.
962 self
.EmptyUndoBuffer()
963 # XXX Add some autoindent magic here if more.
965 self
.write(' '*4) # Temporary hack indentation.
966 self
.EnsureCaretVisible()
967 self
.ScrollToColumn(0)
970 """Replacement for stdin.readline()."""
973 reader
.isreading
= True
976 while not reader
.input:
981 reader
.isreading
= False
982 input = str(input) # In case of Unicode.
986 """Replacement for stdin.readlines()."""
988 while lines
[-1:] != ['\n']:
989 lines
.append(self
.readline())
992 def raw_input(self
, prompt
=''):
993 """Return string based on user input."""
996 return self
.readline()
998 def ask(self
, prompt
='Please enter your response:'):
999 """Get response from the user using a dialog box."""
1000 dialog
= wx
.TextEntryDialog(None, prompt
,
1001 'Input Dialog (Raw)', '')
1003 if dialog
.ShowModal() == wx
.ID_OK
:
1004 text
= dialog
.GetValue()
1011 """Halt execution pending a response from the user."""
1012 self
.ask('Press enter to continue:')
1015 """Delete all text from the shell."""
1018 def run(self
, command
, prompt
=True, verbose
=True):
1019 """Execute command as if it was typed in directly.
1020 >>> shell.run('print "this"')
1025 # Go to the very bottom of the text.
1026 endpos
= self
.GetTextLength()
1027 self
.SetCurrentPos(endpos
)
1028 command
= command
.rstrip()
1029 if prompt
: self
.prompt()
1030 if verbose
: self
.write(command
)
1033 def runfile(self
, filename
):
1034 """Execute all commands in file as if they were typed into the
1036 file = open(filename
)
1039 for command
in file.readlines():
1040 if command
[:6] == 'shell.':
1041 # Run shell methods silently.
1042 self
.run(command
, prompt
=False, verbose
=False)
1044 self
.run(command
, prompt
=False, verbose
=True)
1048 def autoCompleteShow(self
, command
, offset
= 0):
1049 """Display auto-completion popup list."""
1050 self
.AutoCompSetAutoHide(self
.autoCompleteAutoHide
)
1051 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
1052 list = self
.interp
.getAutoCompleteList(command
,
1053 includeMagic
=self
.autoCompleteIncludeMagic
,
1054 includeSingle
=self
.autoCompleteIncludeSingle
,
1055 includeDouble
=self
.autoCompleteIncludeDouble
)
1057 options
= ' '.join(list)
1059 self
.AutoCompShow(offset
, options
)
1061 def autoCallTipShow(self
, command
, insertcalltip
= True, forceCallTip
= False):
1062 """Display argument spec and docstring in a popup window."""
1063 if self
.CallTipActive():
1064 self
.CallTipCancel()
1065 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
1067 dispatcher
.send(signal
='Shell.calltip', sender
=self
, calltip
=tip
)
1068 if not self
.autoCallTip
and not forceCallTip
:
1070 if argspec
and insertcalltip
and self
.callTipInsert
:
1071 startpos
= self
.GetCurrentPos()
1072 self
.write(argspec
+ ')')
1073 endpos
= self
.GetCurrentPos()
1074 self
.SetSelection(endpos
, startpos
)
1076 curpos
= self
.GetCurrentPos()
1077 tippos
= curpos
- (len(name
) + 1)
1078 fallback
= curpos
- self
.GetColumn(curpos
)
1079 # In case there isn't enough room, only go back to the
1081 tippos
= max(tippos
, fallback
)
1082 self
.CallTipShow(tippos
, tip
)
1084 def OnCallTipAutoCompleteManually (self
, shiftDown
):
1085 """AutoComplete and Calltips manually."""
1086 if self
.AutoCompActive():
1087 self
.AutoCompCancel()
1088 currpos
= self
.GetCurrentPos()
1089 stoppos
= self
.promptPosEnd
1092 #go back until '.' is found
1094 while cpos
>= stoppos
:
1095 if self
.GetCharAt(cpos
) == ord ('.'):
1096 pointavailpos
= cpos
1100 #word from non whitespace until '.'
1101 if pointavailpos
!= -1:
1102 #look backward for first whitespace char
1103 textbehind
= self
.GetTextRange (pointavailpos
+ 1, currpos
)
1108 stoppos
= self
.promptPosEnd
1109 textbefore
= self
.GetTextRange(stoppos
, pointavailpos
)
1110 self
.autoCompleteShow(textbefore
, len (textbehind
))
1113 cpos
= pointavailpos
1115 while cpos
> stoppos
:
1116 if chr(self
.GetCharAt(cpos
)).isspace():
1122 ctips
= self
.GetTextRange (begpos
, currpos
)
1123 ctindex
= ctips
.find ('(')
1124 if ctindex
!= -1 and not self
.CallTipActive():
1125 #insert calltip, if current pos is '(', otherwise show it only
1126 self
.autoCallTipShow(ctips
[:ctindex
+ 1],
1127 self
.GetCharAt(currpos
- 1) == ord('(') and self
.GetCurrentPos() == self
.GetTextLength(),
1131 def writeOut(self
, text
):
1132 """Replacement for stdout."""
1135 def writeErr(self
, text
):
1136 """Replacement for stderr."""
1139 def redirectStdin(self
, redirect
=True):
1140 """If redirect is true then sys.stdin will come from the shell."""
1142 sys
.stdin
= self
.reader
1144 sys
.stdin
= self
.stdin
1146 def redirectStdout(self
, redirect
=True):
1147 """If redirect is true then sys.stdout will go to the shell."""
1149 sys
.stdout
= PseudoFileOut(self
.writeOut
)
1151 sys
.stdout
= self
.stdout
1153 def redirectStderr(self
, redirect
=True):
1154 """If redirect is true then sys.stderr will go to the shell."""
1156 sys
.stderr
= PseudoFileErr(self
.writeErr
)
1158 sys
.stderr
= self
.stderr
1161 """Return true if text is selected and can be cut."""
1162 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
1163 and self
.GetSelectionStart() >= self
.promptPosEnd \
1164 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1170 """Return true if a paste should succeed."""
1171 if self
.CanEdit() and editwindow
.EditWindow
.CanPaste(self
):
1177 """Return true if editing should succeed."""
1178 if self
.GetSelectionStart() != self
.GetSelectionEnd():
1179 if self
.GetSelectionStart() >= self
.promptPosEnd \
1180 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1185 return self
.GetCurrentPos() >= self
.promptPosEnd
1188 """Remove selection and place it on the clipboard."""
1189 if self
.CanCut() and self
.CanCopy():
1190 if self
.AutoCompActive():
1191 self
.AutoCompCancel()
1192 if self
.CallTipActive():
1193 self
.CallTipCancel()
1195 self
.ReplaceSelection('')
1198 """Copy selection and place it on the clipboard."""
1202 command
= self
.GetSelectedText()
1203 command
= command
.replace(os
.linesep
+ ps2
, os
.linesep
)
1204 command
= command
.replace(os
.linesep
+ ps1
, os
.linesep
)
1205 command
= self
.lstripPrompt(text
=command
)
1206 data
= wx
.TextDataObject(command
)
1209 def CopyWithPrompts(self
):
1210 """Copy selection, including prompts, and place it on the clipboard."""
1212 command
= self
.GetSelectedText()
1213 data
= wx
.TextDataObject(command
)
1216 def CopyWithPromptsPrefixed(self
):
1217 """Copy selection, including prompts prefixed with four
1218 spaces, and place it on the clipboard."""
1220 command
= self
.GetSelectedText()
1222 command
= spaces
+ command
.replace(os
.linesep
,
1223 os
.linesep
+ spaces
)
1224 data
= wx
.TextDataObject(command
)
1227 def _clip(self
, data
):
1228 if wx
.TheClipboard
.Open():
1229 wx
.TheClipboard
.UsePrimarySelection(False)
1230 wx
.TheClipboard
.SetData(data
)
1231 wx
.TheClipboard
.Flush()
1232 wx
.TheClipboard
.Close()
1235 """Replace selection with clipboard contents."""
1236 if self
.CanPaste() and wx
.TheClipboard
.Open():
1238 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1239 data
= wx
.TextDataObject()
1240 if wx
.TheClipboard
.GetData(data
):
1241 self
.ReplaceSelection('')
1242 command
= data
.GetText()
1243 command
= command
.rstrip()
1244 command
= self
.fixLineEndings(command
)
1245 command
= self
.lstripPrompt(text
=command
)
1246 command
= command
.replace(os
.linesep
+ ps2
, '\n')
1247 command
= command
.replace(os
.linesep
, '\n')
1248 command
= command
.replace('\n', os
.linesep
+ ps2
)
1250 wx
.TheClipboard
.Close()
1253 def PasteAndRun(self
):
1254 """Replace selection with clipboard contents, run commands."""
1256 if wx
.TheClipboard
.Open():
1257 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1258 data
= wx
.TextDataObject()
1259 if wx
.TheClipboard
.GetData(data
):
1260 text
= data
.GetText()
1261 wx
.TheClipboard
.Close()
1266 def Execute(self
, text
):
1267 """Replace selection with text and run commands."""
1270 endpos
= self
.GetTextLength()
1271 self
.SetCurrentPos(endpos
)
1272 startpos
= self
.promptPosEnd
1273 self
.SetSelection(startpos
, endpos
)
1274 self
.ReplaceSelection('')
1275 text
= text
.lstrip()
1276 text
= self
.fixLineEndings(text
)
1277 text
= self
.lstripPrompt(text
)
1278 text
= text
.replace(os
.linesep
+ ps1
, '\n')
1279 text
= text
.replace(os
.linesep
+ ps2
, '\n')
1280 text
= text
.replace(os
.linesep
, '\n')
1281 lines
= text
.split('\n')
1285 if line
.strip() == ps2
.strip():
1286 # If we are pasting from something like a
1287 # web page that drops the trailing space
1288 # from the ps2 prompt of a blank line.
1290 lstrip
= line
.lstrip()
1291 if line
.strip() != '' and lstrip
== line
and \
1292 lstrip
[:4] not in ['else','elif'] and \
1293 lstrip
[:6] != 'except':
1296 # Add the previous command to the list.
1297 commands
.append(command
)
1298 # Start a new command, which may be multiline.
1301 # Multiline command. Add to the command.
1304 commands
.append(command
)
1305 for command
in commands
:
1306 command
= command
.replace('\n', os
.linesep
+ ps2
)
1311 def wrap(self
, wrap
=True):
1312 """Sets whether text is word wrapped."""
1314 self
.SetWrapMode(wrap
)
1315 except AttributeError:
1316 return 'Wrapping is not available in this version.'
1318 def zoom(self
, points
=0):
1319 """Set the zoom level.
1321 This number of points is added to the size of all fonts. It
1322 may be positive to magnify or negative to reduce."""
1323 self
.SetZoom(points
)
1327 def LoadSettings(self
, config
):
1328 self
.autoComplete
= config
.ReadBool('Options/AutoComplete', True)
1329 self
.autoCompleteIncludeMagic
= config
.ReadBool('Options/AutoCompleteIncludeMagic', True)
1330 self
.autoCompleteIncludeSingle
= config
.ReadBool('Options/AutoCompleteIncludeSingle', True)
1331 self
.autoCompleteIncludeDouble
= config
.ReadBool('Options/AutoCompleteIncludeDouble', True)
1333 self
.autoCallTip
= config
.ReadBool('Options/AutoCallTip', True)
1334 self
.callTipInsert
= config
.ReadBool('Options/CallTipInsert', True)
1335 self
.SetWrapMode(config
.ReadBool('View/WrapMode', True))
1337 useAA
= config
.ReadBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1338 self
.SetUseAntiAliasing(useAA
)
1339 self
.lineNumbers
= config
.ReadBool('View/ShowLineNumbers', True)
1340 self
.setDisplayLineNumbers (self
.lineNumbers
)
1341 zoom
= config
.ReadInt('View/Zoom/Shell', -99)
1347 def SaveSettings(self
, config
):
1348 config
.WriteBool('Options/AutoComplete', self
.autoComplete
)
1349 config
.WriteBool('Options/AutoCompleteIncludeMagic', self
.autoCompleteIncludeMagic
)
1350 config
.WriteBool('Options/AutoCompleteIncludeSingle', self
.autoCompleteIncludeSingle
)
1351 config
.WriteBool('Options/AutoCompleteIncludeDouble', self
.autoCompleteIncludeDouble
)
1352 config
.WriteBool('Options/AutoCallTip', self
.autoCallTip
)
1353 config
.WriteBool('Options/CallTipInsert', self
.callTipInsert
)
1354 config
.WriteBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1355 config
.WriteBool('View/WrapMode', self
.GetWrapMode())
1356 config
.WriteBool('View/ShowLineNumbers', self
.lineNumbers
)
1357 config
.WriteInt('View/Zoom/Shell', self
.GetZoom())
1361 ## NOTE: The DnD of file names is disabled until I can figure out how
1362 ## best to still allow DnD of text.
1365 ## #seb : File drag and drop
1366 ## class FileDropTarget(wx.FileDropTarget):
1367 ## def __init__(self, obj):
1368 ## wx.FileDropTarget.__init__(self)
1370 ## def OnDropFiles(self, x, y, filenames):
1371 ## if len(filenames) == 1:
1372 ## txt = 'r\"%s\"' % filenames[0]
1375 ## for f in filenames:
1376 ## txt += 'r\"%s\" , ' % f
1378 ## self.obj.AppendText(txt)
1379 ## pos = self.obj.GetCurrentPos()
1380 ## self.obj.SetCurrentPos( pos )
1381 ## self.obj.SetSelection( pos, pos )
1385 ## class TextAndFileDropTarget(wx.DropTarget):
1386 ## def __init__(self, shell):
1387 ## wx.DropTarget.__init__(self)
1388 ## self.shell = shell
1389 ## self.compdo = wx.DataObjectComposite()
1390 ## self.textdo = wx.TextDataObject()
1391 ## self.filedo = wx.FileDataObject()
1392 ## self.compdo.Add(self.textdo)
1393 ## self.compdo.Add(self.filedo, True)
1395 ## self.SetDataObject(self.compdo)
1397 ## def OnDrop(self, x, y):
1400 ## def OnData(self, x, y, result):
1402 ## if self.textdo.GetTextLength() > 1:
1403 ## text = self.textdo.GetText()
1404 ## # *** Do somethign with the dragged text here...
1405 ## self.textdo.SetText('')
1407 ## filenames = str(self.filename.GetFilenames())
1408 ## if len(filenames) == 1:
1409 ## txt = 'r\"%s\"' % filenames[0]
1412 ## for f in filenames:
1413 ## txt += 'r\"%s\" , ' % f
1415 ## self.shell.AppendText(txt)
1416 ## pos = self.shell.GetCurrentPos()
1417 ## self.shell.SetCurrentPos( pos )
1418 ## self.shell.SetSelection( pos, pos )