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 LoadSettings(self
):
99 if self
.config
is not None:
100 frame
.ShellFrameMixin
.LoadSettings(self
)
101 frame
.Frame
.LoadSettings(self
, self
.config
)
102 self
.shell
.LoadSettings(self
.config
)
104 def SaveSettings(self
):
105 if self
.config
is not None:
106 frame
.ShellFrameMixin
.SaveSettings(self
)
107 if self
.autoSaveSettings
:
108 frame
.Frame
.SaveSettings(self
, self
.config
)
109 self
.shell
.SaveSettings(self
.config
)
111 def DoSaveSettings(self
):
112 if self
.config
is not None:
121 Home Go to the beginning of the command or line.
122 Shift+Home Select to the beginning of the command or line.
123 Shift+End Select to the end of the line.
124 End Go to the end of the line.
125 Ctrl+C Copy selected text, removing prompts.
126 Ctrl+Shift+C Copy selected text, retaining prompts.
127 Alt+C Copy to the clipboard, including prefixed prompts.
128 Ctrl+X Cut selected text.
129 Ctrl+V Paste from clipboard.
130 Ctrl+Shift+V Paste and run multiple commands from clipboard.
131 Ctrl+Up Arrow Retrieve Previous History item.
132 Alt+P Retrieve Previous History item.
133 Ctrl+Down Arrow Retrieve Next History item.
134 Alt+N Retrieve Next History item.
135 Shift+Up Arrow Insert Previous History item.
136 Shift+Down Arrow Insert Next History item.
137 F8 Command-completion of History item.
138 (Type a few characters of a previous command and press F8.)
139 Ctrl+Enter Insert new line into multiline command.
140 Ctrl+] Increase font size.
141 Ctrl+[ Decrease font size.
142 Ctrl+= Default font size.
143 Ctrl-Space Show Auto Completion.
144 Ctrl-Alt-Space Show Call Tip.
145 Alt+Shift+C Clear Screen.
146 Shift+Enter Complete Text from History.
147 Ctrl+F Search (backwards) TODO: regexp-wholeWords-...
149 Ctrl+H "hide" lines containing selection / "unhide"
150 F12 on/off "free-edit" mode
154 """Simplified interface to all shell-related functionality.
156 This is a semi-transparent facade, in that all attributes of other
157 are accessible, even though only some are visible to the user."""
159 name
= 'Shell Interface'
160 revision
= __revision__
162 def __init__(self
, other
):
163 """Create a ShellFacade instance."""
166 d
['helpText'] = HELP_TEXT
169 """Display some useful information about how to use the shell."""
170 self
.write(self
.helpText
)
172 def __getattr__(self
, name
):
173 if hasattr(self
.other
, name
):
174 return getattr(self
.other
, name
)
176 raise AttributeError, name
178 def __setattr__(self
, name
, value
):
179 if self
.__dict
__.has_key(name
):
180 self
.__dict
__[name
] = value
181 elif hasattr(self
.other
, name
):
182 setattr(self
.other
, name
, value
)
184 raise AttributeError, name
186 def _getAttributeNames(self
):
187 """Return list of magic attributes to extend introspection."""
193 'autoCompleteAutoHide',
194 'autoCompleteCaseInsensitive',
195 'autoCompleteIncludeDouble',
196 'autoCompleteIncludeMagic',
197 'autoCompleteIncludeSingle',
216 class Shell(editwindow
.EditWindow
):
217 """Shell based on StyledTextCtrl."""
220 revision
= __revision__
222 def __init__(self
, parent
, id=-1, pos
=wx
.DefaultPosition
,
223 size
=wx
.DefaultSize
, style
=wx
.CLIP_CHILDREN
,
224 introText
='', locals=None, InterpClass
=None,
225 startupScript
=None, execStartupScript
=True,
227 """Create Shell instance."""
228 editwindow
.EditWindow
.__init
__(self
, parent
, id, pos
, size
, style
)
232 locals = __main__
.__dict
__
234 # Grab these so they can be restored by self.redirect* methods.
235 self
.stdin
= sys
.stdin
236 self
.stdout
= sys
.stdout
237 self
.stderr
= sys
.stderr
239 # Import a default interpreter class if one isn't provided.
240 if InterpClass
== None:
241 from interpreter
import Interpreter
243 Interpreter
= InterpClass
245 # Create a replacement for stdin.
246 self
.reader
= PseudoFileIn(self
.readline
, self
.readlines
)
247 self
.reader
.input = ''
248 self
.reader
.isreading
= False
250 # Set up the interpreter.
251 self
.interp
= Interpreter(locals=locals,
252 rawin
=self
.raw_input,
254 stdout
=PseudoFileOut(self
.writeOut
),
255 stderr
=PseudoFileErr(self
.writeErr
),
259 self
.buffer = Buffer()
261 # Find out for which keycodes the interpreter will autocomplete.
262 self
.autoCompleteKeys
= self
.interp
.getAutoCompleteKeys()
264 # Keep track of the last non-continuation prompt positions.
265 self
.promptPosStart
= 0
266 self
.promptPosEnd
= 0
268 # Keep track of multi-line commands.
271 # Create the command history. Commands are added into the
272 # front of the list (ie. at index 0) as they are entered.
273 # self.historyIndex is the current position in the history; it
274 # gets incremented as you retrieve the previous command,
275 # decremented as you retrieve the next, and reset when you hit
276 # Enter. self.historyIndex == -1 means you're on the current
277 # command, not in the history.
279 self
.historyIndex
= -1
281 #seb add mode for "free edit"
283 self
.MarkerDefine(0,stc
.STC_MARK_ROUNDRECT
) # marker for hidden
286 # Assign handlers for keyboard events.
287 self
.Bind(wx
.EVT_CHAR
, self
.OnChar
)
288 self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
)
290 # Assign handler for idle time.
292 self
.Bind(wx
.EVT_IDLE
, self
.OnIdle
)
294 # Display the introductory banner information.
295 self
.showIntro(introText
)
297 # Assign some pseudo keywords to the interpreter's namespace.
298 self
.setBuiltinKeywords()
300 # Add 'shell' to the interpreter's local namespace.
303 ## NOTE: See note at bottom of this file...
304 ## #seb: File drag and drop
305 ## self.SetDropTarget( FileDropTarget(self) )
307 # Do this last so the user has complete control over their
308 # environment. They can override anything they want.
309 if execStartupScript
:
310 if startupScript
is None:
311 startupScript
= os
.environ
.get('PYTHONSTARTUP')
312 self
.execStartupScript(startupScript
)
316 wx
.CallAfter(self
.ScrollToLine
, 0)
324 """Set focus to the shell."""
327 def OnIdle(self
, event
):
328 """Free the CPU to do other things."""
333 def showIntro(self
, text
=''):
334 """Display introductory text in the shell."""
336 if not text
.endswith(os
.linesep
):
340 self
.write(self
.interp
.introText
)
341 except AttributeError:
344 def setBuiltinKeywords(self
):
345 """Create pseudo keywords as part of builtins.
347 This sets `close`, `exit` and `quit` to a helpful string.
350 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
351 'Click on the close button to leave the application.'
355 """Quit the application."""
356 # XXX Good enough for now but later we want to send a close event.
357 # In the close event handler we can make sure they want to
358 # quit. Other applications, like PythonCard, may choose to
359 # hide rather than quit so we should just post the event and
360 # let the surrounding app decide what it wants to do.
361 self
.write('Click on the close button to leave the application.')
364 def setLocalShell(self
):
365 """Add 'shell' to locals as reference to ShellFacade instance."""
366 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
369 def execStartupScript(self
, startupScript
):
370 """Execute the user's PYTHONSTARTUP script if they have one."""
371 if startupScript
and os
.path
.isfile(startupScript
):
372 text
= 'Startup script executed: ' + startupScript
373 self
.push('print %r; execfile(%r)' % (text
, startupScript
))
374 self
.interp
.startupScript
= startupScript
380 """Display information about Py."""
384 Py Shell Revision: %s
385 Py Interpreter Revision: %s
388 wxPython PlatformInfo: %s
390 (__author__
, VERSION
, self
.revision
, self
.interp
.revision
,
391 sys
.version
.split()[0], wx
.VERSION_STRING
, str(wx
.PlatformInfo
),
393 self
.write(text
.strip())
396 def OnChar(self
, event
):
397 """Keypress event handler.
399 Only receives an event if OnKeyDown calls event.Skip() for the
400 corresponding event."""
406 # Prevent modification of previously submitted
407 # commands/responses.
408 if not self
.CanEdit():
410 key
= event
.KeyCode()
411 currpos
= self
.GetCurrentPos()
412 stoppos
= self
.promptPosEnd
413 # Return (Enter) needs to be ignored in this handler.
414 if key
== wx
.WXK_RETURN
:
416 elif key
in self
.autoCompleteKeys
:
417 # Usually the dot (period) key activates auto completion.
418 # Get the command between the prompt and the cursor. Add
419 # the autocomplete character to the end of the command.
420 if self
.AutoCompActive():
421 self
.AutoCompCancel()
422 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
424 if self
.autoComplete
:
425 self
.autoCompleteShow(command
)
426 elif key
== ord('('):
427 # The left paren activates a call tip and cancels an
428 # active auto completion.
429 if self
.AutoCompActive():
430 self
.AutoCompCancel()
431 # Get the command between the prompt and the cursor. Add
432 # the '(' to the end of the command.
433 self
.ReplaceSelection('')
434 command
= self
.GetTextRange(stoppos
, currpos
) + '('
436 self
.autoCallTipShow(command
, self
.GetCurrentPos() == self
.GetTextLength())
438 # Allow the normal event handling to take place.
442 def OnKeyDown(self
, event
):
443 """Key down event handler."""
445 key
= event
.KeyCode()
446 # If the auto-complete window is up let it do its thing.
447 if self
.AutoCompActive():
450 # Prevent modification of previously submitted
451 # commands/responses.
452 controlDown
= event
.ControlDown()
453 altDown
= event
.AltDown()
454 shiftDown
= event
.ShiftDown()
455 currpos
= self
.GetCurrentPos()
456 endpos
= self
.GetTextLength()
457 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
459 if controlDown
and key
in (ord('H'), ord('h')):
460 li
= self
.GetCurrentLine()
461 m
= self
.MarkerGet(li
)
463 startP
= self
.PositionFromLine(li
)
464 self
.MarkerDelete(li
, 0)
465 maxli
= self
.GetLineCount()
466 li
+= 1 # li stayed visible as header-line
468 while li
<maxli
and self
.GetLineVisible(li
) == 0:
470 endP
= self
.GetLineEndPosition(li
-1)
471 self
.ShowLines(li0
, li
-1)
472 self
.SetSelection( startP
, endP
) # select reappearing text to allow "hide again"
474 startP
,endP
= self
.GetSelection()
476 startL
,endL
= self
.LineFromPosition(startP
), self
.LineFromPosition(endP
)
478 if endL
== self
.LineFromPosition(self
.promptPosEnd
): # never hide last prompt
481 m
= self
.MarkerGet(startL
)
482 self
.MarkerAdd(startL
, 0)
483 self
.HideLines(startL
+1,endL
)
484 self
.SetCurrentPos( startP
) # to ensure caret stays visible !
486 if key
== wx
.WXK_F12
: #seb
488 # self.promptPosStart not used anyway - or ?
489 self
.promptPosEnd
= self
.PositionFromLine( self
.GetLineCount()-1 ) + len(str(sys
.ps1
))
490 self
.GotoLine(self
.GetLineCount())
491 self
.GotoPos(self
.promptPosEnd
)
492 self
.prompt() #make sure we have a prompt
493 self
.SetCaretForeground("black")
494 self
.SetCaretWidth(1) #default
495 self
.SetCaretPeriod(500) #default
497 self
.SetCaretForeground("red")
498 self
.SetCaretWidth(4)
499 self
.SetCaretPeriod(0) #steady
501 self
.noteMode
= not self
.noteMode
507 # Return (Enter) is used to submit a command to the
509 if (not controlDown
and not shiftDown
and not altDown
) and key
== wx
.WXK_RETURN
:
510 if self
.CallTipActive():
513 #Complete Text (from already typed words)
514 elif shiftDown
and key
== wx
.WXK_RETURN
:
515 self
.OnShowCompHistory()
516 # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
517 elif controlDown
and key
== wx
.WXK_RETURN
:
518 if self
.CallTipActive():
520 if currpos
== endpos
:
523 self
.insertLineBreak()
524 # Let Ctrl-Alt-* get handled normally.
525 elif controlDown
and altDown
:
527 # Clear the current, unexecuted command.
528 elif key
== wx
.WXK_ESCAPE
:
529 if self
.CallTipActive():
533 # Increase font size.
534 elif controlDown
and key
in (ord(']'),):
535 dispatcher
.send(signal
='FontIncrease')
536 # Decrease font size.
537 elif controlDown
and key
in (ord('['),):
538 dispatcher
.send(signal
='FontDecrease')
540 elif controlDown
and key
in (ord('='),):
541 dispatcher
.send(signal
='FontDefault')
542 # Cut to the clipboard.
543 elif (controlDown
and key
in (ord('X'), ord('x'))) \
544 or (shiftDown
and key
== wx
.WXK_DELETE
):
546 # Copy to the clipboard.
547 elif controlDown
and not shiftDown \
548 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
550 # Copy to the clipboard, including prompts.
551 elif controlDown
and shiftDown \
552 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
553 self
.CopyWithPrompts()
554 # Copy to the clipboard, including prefixed prompts.
555 elif altDown
and not controlDown \
556 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
557 self
.CopyWithPromptsPrefixed()
558 # Home needs to be aware of the prompt.
559 elif key
== wx
.WXK_HOME
:
560 home
= self
.promptPosEnd
562 self
.SetCurrentPos(home
)
563 if not selecting
and not shiftDown
:
565 self
.EnsureCaretVisible()
569 # The following handlers modify text, so we need to see if
570 # there is a selection that includes text prior to the prompt.
572 # Don't modify a selection with text prior to the prompt.
573 elif selecting
and key
not in NAVKEYS
and not self
.CanEdit():
575 # Paste from the clipboard.
576 elif (controlDown
and not shiftDown
and key
in (ord('V'), ord('v'))) \
577 or (shiftDown
and not controlDown
and key
== wx
.WXK_INSERT
):
579 elif controlDown
and key
== wx
.WXK_SPACE
:
580 """AutoComplete and Calltips manually."""
581 self
.OnCallTipAutoCompleteManually (shiftDown
)
582 # Paste from the clipboard, run commands.
583 elif controlDown
and shiftDown
and key
in (ord('V'), ord('v')):
585 # Replace with the previous command from the history buffer.
586 elif (controlDown
and key
== wx
.WXK_UP
) \
587 or (altDown
and key
in (ord('P'), ord('p'))):
588 self
.OnHistoryReplace(step
=+1)
589 # Replace with the next command from the history buffer.
590 elif (controlDown
and key
== wx
.WXK_DOWN
) \
591 or (altDown
and key
in (ord('N'), ord('n'))):
592 self
.OnHistoryReplace(step
=-1)
593 # Insert the previous command from the history buffer.
594 elif (shiftDown
and key
== wx
.WXK_UP
) and self
.CanEdit():
595 self
.OnHistoryInsert(step
=+1)
596 # Insert the next command from the history buffer.
597 elif (shiftDown
and key
== wx
.WXK_DOWN
) and self
.CanEdit():
598 self
.OnHistoryInsert(step
=-1)
599 # Search up the history for the text in front of the cursor.
600 elif key
== wx
.WXK_F8
:
601 self
.OnHistorySearch()
602 # Don't backspace over the latest non-continuation prompt.
603 elif key
== wx
.WXK_BACK
:
604 if selecting
and self
.CanEdit():
606 elif currpos
> self
.promptPosEnd
:
608 # Only allow these keys after the latest prompt.
609 elif key
in (wx
.WXK_TAB
, wx
.WXK_DELETE
):
612 # Don't toggle between insert mode and overwrite mode.
613 elif key
== wx
.WXK_INSERT
:
615 # Don't allow line deletion.
616 elif controlDown
and key
in (ord('L'), ord('l')):
618 # Don't allow line transposition.
619 elif controlDown
and key
in (ord('T'), ord('t')):
621 # Basic navigation keys should work anywhere.
624 # Protect the readonly portion of the shell.
625 elif not self
.CanEdit():
630 def OnShowCompHistory(self
):
631 """Show possible autocompletion Words from already typed words."""
634 his
= self
.history
[:]
636 #put together in one string
637 joined
= " ".join (his
)
640 #sort out only "good" words
641 newlist
= re
.split("[ \.\[\]=}(\)\,0-9\"]", joined
)
643 #length > 1 (mix out "trash")
649 #unique (no duplicate words
650 #oneliner from german python forum => unique list
651 unlist
= [thlist
[i
] for i
in xrange(len(thlist
)) if thlist
[i
] not in thlist
[:i
]]
654 unlist
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
656 #this is more convenient, isn't it?
657 self
.AutoCompSetIgnoreCase(True)
659 #join again together in a string
660 stringlist
= " ".join(unlist
)
662 #pos von 0 noch ausrechnen
664 #how big is the offset?
665 cpos
= self
.GetCurrentPos() - 1
666 while chr (self
.GetCharAt (cpos
)).isalnum():
669 #the most important part
670 self
.AutoCompShow(self
.GetCurrentPos() - cpos
-1, stringlist
)
673 def clearCommand(self
):
674 """Delete the current, unexecuted command."""
675 startpos
= self
.promptPosEnd
676 endpos
= self
.GetTextLength()
677 self
.SetSelection(startpos
, endpos
)
678 self
.ReplaceSelection('')
681 def OnHistoryReplace(self
, step
):
682 """Replace with the previous/next command from the history buffer."""
684 self
.replaceFromHistory(step
)
686 def replaceFromHistory(self
, step
):
687 """Replace selection with command from the history buffer."""
689 self
.ReplaceSelection('')
690 newindex
= self
.historyIndex
+ step
691 if -1 <= newindex
<= len(self
.history
):
692 self
.historyIndex
= newindex
693 if 0 <= newindex
<= len(self
.history
)-1:
694 command
= self
.history
[self
.historyIndex
]
695 command
= command
.replace('\n', os
.linesep
+ ps2
)
696 self
.ReplaceSelection(command
)
698 def OnHistoryInsert(self
, step
):
699 """Insert the previous/next command from the history buffer."""
700 if not self
.CanEdit():
702 startpos
= self
.GetCurrentPos()
703 self
.replaceFromHistory(step
)
704 endpos
= self
.GetCurrentPos()
705 self
.SetSelection(endpos
, startpos
)
707 def OnHistorySearch(self
):
708 """Search up the history buffer for the text in front of the cursor."""
709 if not self
.CanEdit():
711 startpos
= self
.GetCurrentPos()
712 # The text up to the cursor is what we search for.
713 numCharsAfterCursor
= self
.GetTextLength() - startpos
714 searchText
= self
.getCommand(rstrip
=False)
715 if numCharsAfterCursor
> 0:
716 searchText
= searchText
[:-numCharsAfterCursor
]
719 # Search upwards from the current history position and loop
720 # back to the beginning if we don't find anything.
721 if (self
.historyIndex
<= -1) \
722 or (self
.historyIndex
>= len(self
.history
)-2):
723 searchOrder
= range(len(self
.history
))
725 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
726 range(self
.historyIndex
)
727 for i
in searchOrder
:
728 command
= self
.history
[i
]
729 if command
[:len(searchText
)] == searchText
:
730 # Replace the current selection with the one we found.
731 self
.ReplaceSelection(command
[len(searchText
):])
732 endpos
= self
.GetCurrentPos()
733 self
.SetSelection(endpos
, startpos
)
734 # We've now warped into middle of the history.
735 self
.historyIndex
= i
738 def setStatusText(self
, text
):
739 """Display status information."""
741 # This method will likely be replaced by the enclosing app to
742 # do something more interesting, like write to a status bar.
745 def insertLineBreak(self
):
746 """Insert a new line break."""
748 self
.write(os
.linesep
)
752 def processLine(self
):
753 """Process the line of text at which the user hit Enter."""
755 # The user hit ENTER and we need to decide what to do. They
756 # could be sitting on any line in the shell.
758 thepos
= self
.GetCurrentPos()
759 startpos
= self
.promptPosEnd
760 endpos
= self
.GetTextLength()
762 # If they hit RETURN inside the current command, execute the
765 self
.SetCurrentPos(endpos
)
766 self
.interp
.more
= False
767 command
= self
.GetTextRange(startpos
, endpos
)
768 lines
= command
.split(os
.linesep
+ ps2
)
769 lines
= [line
.rstrip() for line
in lines
]
770 command
= '\n'.join(lines
)
771 if self
.reader
.isreading
:
773 # Match the behavior of the standard Python shell
774 # when the user hits return without entering a
777 self
.reader
.input = command
778 self
.write(os
.linesep
)
781 wx
.FutureCall(1, self
.EnsureCaretVisible
)
782 # Or replace the current command with the other command.
784 # If the line contains a command (even an invalid one).
785 if self
.getCommand(rstrip
=False):
786 command
= self
.getMultilineCommand()
789 # Otherwise, put the cursor back where we started.
791 self
.SetCurrentPos(thepos
)
792 self
.SetAnchor(thepos
)
794 def getMultilineCommand(self
, rstrip
=True):
795 """Extract a multi-line command from the editor.
797 The command may not necessarily be valid Python syntax."""
798 # XXX Need to extract real prompts here. Need to keep track of
799 # the prompt every time a command is issued.
804 # This is a total hack job, but it works.
805 text
= self
.GetCurLine()[0]
806 line
= self
.GetCurrentLine()
807 while text
[:ps2size
] == ps2
and line
> 0:
810 text
= self
.GetCurLine()[0]
811 if text
[:ps1size
] == ps1
:
812 line
= self
.GetCurrentLine()
814 startpos
= self
.GetCurrentPos() + ps1size
817 while self
.GetCurLine()[0][:ps2size
] == ps2
:
820 stoppos
= self
.GetCurrentPos()
821 command
= self
.GetTextRange(startpos
, stoppos
)
822 command
= command
.replace(os
.linesep
+ ps2
, '\n')
823 command
= command
.rstrip()
824 command
= command
.replace('\n', os
.linesep
+ ps2
)
828 command
= command
.rstrip()
831 def getCommand(self
, text
=None, rstrip
=True):
832 """Extract a command from text which may include a shell prompt.
834 The command may not necessarily be valid Python syntax."""
836 text
= self
.GetCurLine()[0]
837 # Strip the prompt off the front leaving just the command.
838 command
= self
.lstripPrompt(text
)
840 command
= '' # Real commands have prompts.
842 command
= command
.rstrip()
845 def lstripPrompt(self
, text
):
846 """Return text without a leading prompt."""
851 # Strip the prompt off the front of text.
852 if text
[:ps1size
] == ps1
:
853 text
= text
[ps1size
:]
854 elif text
[:ps2size
] == ps2
:
855 text
= text
[ps2size
:]
858 def push(self
, command
, silent
= False):
859 """Send command to the interpreter for execution."""
861 self
.write(os
.linesep
)
862 busy
= wx
.BusyCursor()
864 self
.more
= self
.interp
.push(command
)
868 self
.addHistory(command
.rstrip())
872 def addHistory(self
, command
):
873 """Add command to the command history."""
874 # Reset the history position.
875 self
.historyIndex
= -1
876 # Insert this command into the history, unless it's a blank
877 # line or the same as the last command.
879 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
880 self
.history
.insert(0, command
)
882 def write(self
, text
):
883 """Display text in the shell.
885 Replace line endings with OS-specific endings."""
886 text
= self
.fixLineEndings(text
)
888 self
.EnsureCaretVisible()
890 def fixLineEndings(self
, text
):
891 """Return text with line endings replaced by OS-specific endings."""
892 lines
= text
.split('\r\n')
893 for l
in range(len(lines
)):
894 chunks
= lines
[l
].split('\r')
895 for c
in range(len(chunks
)):
896 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
897 lines
[l
] = os
.linesep
.join(chunks
)
898 text
= os
.linesep
.join(lines
)
902 """Display proper prompt for the context: ps1, ps2 or ps3.
904 If this is a continuation line, autoindent as necessary."""
905 isreading
= self
.reader
.isreading
908 prompt
= str(sys
.ps3
)
910 prompt
= str(sys
.ps2
)
912 prompt
= str(sys
.ps1
)
913 pos
= self
.GetCurLine()[1]
918 self
.write(os
.linesep
)
920 self
.promptPosStart
= self
.GetCurrentPos()
924 self
.promptPosEnd
= self
.GetCurrentPos()
925 # Keep the undo feature from undoing previous responses.
926 self
.EmptyUndoBuffer()
927 # XXX Add some autoindent magic here if more.
929 self
.write(' '*4) # Temporary hack indentation.
930 self
.EnsureCaretVisible()
931 self
.ScrollToColumn(0)
934 """Replacement for stdin.readline()."""
937 reader
.isreading
= True
940 while not reader
.input:
945 reader
.isreading
= False
946 input = str(input) # In case of Unicode.
950 """Replacement for stdin.readlines()."""
952 while lines
[-1:] != ['\n']:
953 lines
.append(self
.readline())
956 def raw_input(self
, prompt
=''):
957 """Return string based on user input."""
960 return self
.readline()
962 def ask(self
, prompt
='Please enter your response:'):
963 """Get response from the user using a dialog box."""
964 dialog
= wx
.TextEntryDialog(None, prompt
,
965 'Input Dialog (Raw)', '')
967 if dialog
.ShowModal() == wx
.ID_OK
:
968 text
= dialog
.GetValue()
975 """Halt execution pending a response from the user."""
976 self
.ask('Press enter to continue:')
979 """Delete all text from the shell."""
982 def run(self
, command
, prompt
=True, verbose
=True):
983 """Execute command as if it was typed in directly.
984 >>> shell.run('print "this"')
989 # Go to the very bottom of the text.
990 endpos
= self
.GetTextLength()
991 self
.SetCurrentPos(endpos
)
992 command
= command
.rstrip()
993 if prompt
: self
.prompt()
994 if verbose
: self
.write(command
)
997 def runfile(self
, filename
):
998 """Execute all commands in file as if they were typed into the
1000 file = open(filename
)
1003 for command
in file.readlines():
1004 if command
[:6] == 'shell.':
1005 # Run shell methods silently.
1006 self
.run(command
, prompt
=False, verbose
=False)
1008 self
.run(command
, prompt
=False, verbose
=True)
1012 def autoCompleteShow(self
, command
, offset
= 0):
1013 """Display auto-completion popup list."""
1014 self
.AutoCompSetAutoHide(self
.autoCompleteAutoHide
)
1015 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
1016 list = self
.interp
.getAutoCompleteList(command
,
1017 includeMagic
=self
.autoCompleteIncludeMagic
,
1018 includeSingle
=self
.autoCompleteIncludeSingle
,
1019 includeDouble
=self
.autoCompleteIncludeDouble
)
1021 options
= ' '.join(list)
1023 self
.AutoCompShow(offset
, options
)
1025 def autoCallTipShow(self
, command
, insertcalltip
= True, forceCallTip
= False):
1026 """Display argument spec and docstring in a popup window."""
1027 if self
.CallTipActive():
1028 self
.CallTipCancel()
1029 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
1031 dispatcher
.send(signal
='Shell.calltip', sender
=self
, calltip
=tip
)
1032 if not self
.autoCallTip
and not forceCallTip
:
1034 if argspec
and insertcalltip
and self
.callTipInsert
:
1035 startpos
= self
.GetCurrentPos()
1036 self
.write(argspec
+ ')')
1037 endpos
= self
.GetCurrentPos()
1038 self
.SetSelection(endpos
, startpos
)
1040 curpos
= self
.GetCurrentPos()
1041 tippos
= curpos
- (len(name
) + 1)
1042 fallback
= curpos
- self
.GetColumn(curpos
)
1043 # In case there isn't enough room, only go back to the
1045 tippos
= max(tippos
, fallback
)
1046 self
.CallTipShow(tippos
, tip
)
1048 def OnCallTipAutoCompleteManually (self
, shiftDown
):
1049 """AutoComplete and Calltips manually."""
1050 if self
.AutoCompActive():
1051 self
.AutoCompCancel()
1052 currpos
= self
.GetCurrentPos()
1053 stoppos
= self
.promptPosEnd
1056 #go back until '.' is found
1058 while cpos
>= stoppos
:
1059 if self
.GetCharAt(cpos
) == ord ('.'):
1060 pointavailpos
= cpos
1064 #word from non whitespace until '.'
1065 if pointavailpos
!= -1:
1066 #look backward for first whitespace char
1067 textbehind
= self
.GetTextRange (pointavailpos
+ 1, currpos
)
1072 stoppos
= self
.promptPosEnd
1073 textbefore
= self
.GetTextRange(stoppos
, pointavailpos
)
1074 self
.autoCompleteShow(textbefore
, len (textbehind
))
1077 cpos
= pointavailpos
1079 while cpos
> stoppos
:
1080 if chr(self
.GetCharAt(cpos
)).isspace():
1086 ctips
= self
.GetTextRange (begpos
, currpos
)
1087 ctindex
= ctips
.find ('(')
1088 if ctindex
!= -1 and not self
.CallTipActive():
1089 #insert calltip, if current pos is '(', otherwise show it only
1090 self
.autoCallTipShow(ctips
[:ctindex
+ 1],
1091 self
.GetCharAt(currpos
- 1) == ord('(') and self
.GetCurrentPos() == self
.GetTextLength(),
1095 def writeOut(self
, text
):
1096 """Replacement for stdout."""
1099 def writeErr(self
, text
):
1100 """Replacement for stderr."""
1103 def redirectStdin(self
, redirect
=True):
1104 """If redirect is true then sys.stdin will come from the shell."""
1106 sys
.stdin
= self
.reader
1108 sys
.stdin
= self
.stdin
1110 def redirectStdout(self
, redirect
=True):
1111 """If redirect is true then sys.stdout will go to the shell."""
1113 sys
.stdout
= PseudoFileOut(self
.writeOut
)
1115 sys
.stdout
= self
.stdout
1117 def redirectStderr(self
, redirect
=True):
1118 """If redirect is true then sys.stderr will go to the shell."""
1120 sys
.stderr
= PseudoFileErr(self
.writeErr
)
1122 sys
.stderr
= self
.stderr
1125 """Return true if text is selected and can be cut."""
1126 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
1127 and self
.GetSelectionStart() >= self
.promptPosEnd \
1128 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1134 """Return true if a paste should succeed."""
1135 if self
.CanEdit() and editwindow
.EditWindow
.CanPaste(self
):
1141 """Return true if editing should succeed."""
1142 if self
.GetSelectionStart() != self
.GetSelectionEnd():
1143 if self
.GetSelectionStart() >= self
.promptPosEnd \
1144 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1149 return self
.GetCurrentPos() >= self
.promptPosEnd
1152 """Remove selection and place it on the clipboard."""
1153 if self
.CanCut() and self
.CanCopy():
1154 if self
.AutoCompActive():
1155 self
.AutoCompCancel()
1156 if self
.CallTipActive():
1157 self
.CallTipCancel()
1159 self
.ReplaceSelection('')
1162 """Copy selection and place it on the clipboard."""
1166 command
= self
.GetSelectedText()
1167 command
= command
.replace(os
.linesep
+ ps2
, os
.linesep
)
1168 command
= command
.replace(os
.linesep
+ ps1
, os
.linesep
)
1169 command
= self
.lstripPrompt(text
=command
)
1170 data
= wx
.TextDataObject(command
)
1173 def CopyWithPrompts(self
):
1174 """Copy selection, including prompts, and place it on the clipboard."""
1176 command
= self
.GetSelectedText()
1177 data
= wx
.TextDataObject(command
)
1180 def CopyWithPromptsPrefixed(self
):
1181 """Copy selection, including prompts prefixed with four
1182 spaces, and place it on the clipboard."""
1184 command
= self
.GetSelectedText()
1186 command
= spaces
+ command
.replace(os
.linesep
,
1187 os
.linesep
+ spaces
)
1188 data
= wx
.TextDataObject(command
)
1191 def _clip(self
, data
):
1192 if wx
.TheClipboard
.Open():
1193 wx
.TheClipboard
.UsePrimarySelection(False)
1194 wx
.TheClipboard
.SetData(data
)
1195 wx
.TheClipboard
.Flush()
1196 wx
.TheClipboard
.Close()
1199 """Replace selection with clipboard contents."""
1200 if self
.CanPaste() and wx
.TheClipboard
.Open():
1202 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1203 data
= wx
.TextDataObject()
1204 if wx
.TheClipboard
.GetData(data
):
1205 self
.ReplaceSelection('')
1206 command
= data
.GetText()
1207 command
= command
.rstrip()
1208 command
= self
.fixLineEndings(command
)
1209 command
= self
.lstripPrompt(text
=command
)
1210 command
= command
.replace(os
.linesep
+ ps2
, '\n')
1211 command
= command
.replace(os
.linesep
, '\n')
1212 command
= command
.replace('\n', os
.linesep
+ ps2
)
1214 wx
.TheClipboard
.Close()
1217 def PasteAndRun(self
):
1218 """Replace selection with clipboard contents, run commands."""
1220 if wx
.TheClipboard
.Open():
1221 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1222 data
= wx
.TextDataObject()
1223 if wx
.TheClipboard
.GetData(data
):
1224 text
= data
.GetText()
1225 wx
.TheClipboard
.Close()
1230 def Execute(self
, text
):
1231 """Replace selection with text and run commands."""
1234 endpos
= self
.GetTextLength()
1235 self
.SetCurrentPos(endpos
)
1236 startpos
= self
.promptPosEnd
1237 self
.SetSelection(startpos
, endpos
)
1238 self
.ReplaceSelection('')
1239 text
= text
.lstrip()
1240 text
= self
.fixLineEndings(text
)
1241 text
= self
.lstripPrompt(text
)
1242 text
= text
.replace(os
.linesep
+ ps1
, '\n')
1243 text
= text
.replace(os
.linesep
+ ps2
, '\n')
1244 text
= text
.replace(os
.linesep
, '\n')
1245 lines
= text
.split('\n')
1249 if line
.strip() == ps2
.strip():
1250 # If we are pasting from something like a
1251 # web page that drops the trailing space
1252 # from the ps2 prompt of a blank line.
1254 lstrip
= line
.lstrip()
1255 if line
.strip() != '' and lstrip
== line
and \
1256 lstrip
[:4] not in ['else','elif'] and \
1257 lstrip
[:6] != 'except':
1260 # Add the previous command to the list.
1261 commands
.append(command
)
1262 # Start a new command, which may be multiline.
1265 # Multiline command. Add to the command.
1268 commands
.append(command
)
1269 for command
in commands
:
1270 command
= command
.replace('\n', os
.linesep
+ ps2
)
1275 def wrap(self
, wrap
=True):
1276 """Sets whether text is word wrapped."""
1278 self
.SetWrapMode(wrap
)
1279 except AttributeError:
1280 return 'Wrapping is not available in this version.'
1282 def zoom(self
, points
=0):
1283 """Set the zoom level.
1285 This number of points is added to the size of all fonts. It
1286 may be positive to magnify or negative to reduce."""
1287 self
.SetZoom(points
)
1291 def LoadSettings(self
, config
):
1292 self
.autoComplete
= config
.ReadBool('Options/AutoComplete', True)
1293 self
.autoCompleteIncludeMagic
= config
.ReadBool('Options/AutoCompleteIncludeMagic', True)
1294 self
.autoCompleteIncludeSingle
= config
.ReadBool('Options/AutoCompleteIncludeSingle', True)
1295 self
.autoCompleteIncludeDouble
= config
.ReadBool('Options/AutoCompleteIncludeDouble', True)
1297 self
.autoCallTip
= config
.ReadBool('Options/AutoCallTip', True)
1298 self
.callTipInsert
= config
.ReadBool('Options/CallTipInsert', True)
1299 self
.SetWrapMode(config
.ReadBool('View/WrapMode', True))
1301 useAA
= config
.ReadBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1302 self
.SetUseAntiAliasing(useAA
)
1303 self
.lineNumbers
= config
.ReadBool('View/ShowLineNumbers', True)
1304 self
.setDisplayLineNumbers (self
.lineNumbers
)
1305 zoom
= config
.ReadInt('View/Zoom/Shell', -99)
1311 def SaveSettings(self
, config
):
1312 config
.WriteBool('Options/AutoComplete', self
.autoComplete
)
1313 config
.WriteBool('Options/AutoCompleteIncludeMagic', self
.autoCompleteIncludeMagic
)
1314 config
.WriteBool('Options/AutoCompleteIncludeSingle', self
.autoCompleteIncludeSingle
)
1315 config
.WriteBool('Options/AutoCompleteIncludeDouble', self
.autoCompleteIncludeDouble
)
1316 config
.WriteBool('Options/AutoCallTip', self
.autoCallTip
)
1317 config
.WriteBool('Options/CallTipInsert', self
.callTipInsert
)
1318 config
.WriteBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1319 config
.WriteBool('View/WrapMode', self
.GetWrapMode())
1320 config
.WriteBool('View/ShowLineNumbers', self
.lineNumbers
)
1321 config
.WriteInt('View/Zoom/Shell', self
.GetZoom())
1325 ## NOTE: The DnD of file names is disabled until I can figure out how
1326 ## best to still allow DnD of text.
1329 ## #seb : File drag and drop
1330 ## class FileDropTarget(wx.FileDropTarget):
1331 ## def __init__(self, obj):
1332 ## wx.FileDropTarget.__init__(self)
1334 ## def OnDropFiles(self, x, y, filenames):
1335 ## if len(filenames) == 1:
1336 ## txt = 'r\"%s\"' % filenames[0]
1339 ## for f in filenames:
1340 ## txt += 'r\"%s\" , ' % f
1342 ## self.obj.AppendText(txt)
1343 ## pos = self.obj.GetCurrentPos()
1344 ## self.obj.SetCurrentPos( pos )
1345 ## self.obj.SetSelection( pos, pos )
1349 ## class TextAndFileDropTarget(wx.DropTarget):
1350 ## def __init__(self, shell):
1351 ## wx.DropTarget.__init__(self)
1352 ## self.shell = shell
1353 ## self.compdo = wx.DataObjectComposite()
1354 ## self.textdo = wx.TextDataObject()
1355 ## self.filedo = wx.FileDataObject()
1356 ## self.compdo.Add(self.textdo)
1357 ## self.compdo.Add(self.filedo, True)
1359 ## self.SetDataObject(self.compdo)
1361 ## def OnDrop(self, x, y):
1364 ## def OnData(self, x, y, result):
1366 ## if self.textdo.GetTextLength() > 1:
1367 ## text = self.textdo.GetText()
1368 ## # *** Do somethign with the dragged text here...
1369 ## self.textdo.SetText('')
1371 ## filenames = str(self.filename.GetFilenames())
1372 ## if len(filenames) == 1:
1373 ## txt = 'r\"%s\"' % filenames[0]
1376 ## for f in filenames:
1377 ## txt += 'r\"%s\" , ' % f
1379 ## self.shell.AppendText(txt)
1380 ## pos = self.shell.GetCurrentPos()
1381 ## self.shell.SetCurrentPos( pos )
1382 ## self.shell.SetSelection( pos, pos )