Poodletooth-iLand/panda/python/Lib/site-packages/wx/py/shell.py
2015-03-06 06:11:40 -06:00

1571 lines
56 KiB
Python

"""Shell is an interactive text control in which a user types in
commands to be sent to the interpreter. This particular shell is
based on wxPython's wxStyledTextCtrl.
Sponsored by Orbtech - Your source for Python programming expertise."""
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
import wx
from wx import stc
from wx.lib.six import PY3
import keyword
import os
import sys
import time
from .buffer import Buffer
from . import dispatcher
from . import editwindow
from . import frame
from .pseudo import PseudoFileIn
from .pseudo import PseudoFileOut
from .pseudo import PseudoFileErr
from .version import VERSION
from .magic import magic
from .path import ls,cd,pwd,sx
sys.ps3 = '<-- ' # Input prompt.
USE_MAGIC=True
# Force updates from long-running commands after this many seconds
PRINT_UPDATE_MAX_TIME=2
NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)
class ShellFrame(frame.Frame, frame.ShellFrameMixin):
"""Frame containing the shell component."""
name = 'Shell Frame'
def __init__(self, parent=None, id=-1, title='PyShell',
pos=wx.DefaultPosition, size=wx.DefaultSize,
style=wx.DEFAULT_FRAME_STYLE, locals=None,
InterpClass=None,
config=None, dataDir=None,
*args, **kwds):
"""Create ShellFrame instance."""
frame.Frame.__init__(self, parent, id, title, pos, size, style)
frame.ShellFrameMixin.__init__(self, config, dataDir)
if size == wx.DefaultSize:
self.SetSize((750, 525))
intro = 'PyShell %s - The Flakiest Python Shell' % VERSION
self.SetStatusText(intro.replace('\n', ', '))
self.shell = Shell(parent=self, id=-1, introText=intro,
locals=locals, InterpClass=InterpClass,
startupScript=self.startupScript,
execStartupScript=self.execStartupScript,
*args, **kwds)
# Override the shell so that status messages go to the status bar.
self.shell.setStatusText = self.SetStatusText
self.shell.SetFocus()
self.LoadSettings()
def OnClose(self, event):
"""Event handler for closing."""
# This isn't working the way I want, but I'll leave it for now.
if self.shell.waiting:
if event.CanVeto():
event.Veto(True)
else:
self.SaveSettings()
self.shell.destroy()
self.Destroy()
def OnAbout(self, event):
"""Display an About window."""
title = 'About PyShell'
text = 'PyShell %s\n\n' % VERSION + \
'Yet another Python shell, only flakier.\n\n' + \
'Half-baked by Patrick K. O\'Brien,\n' + \
'the other half is still in the oven.\n\n' + \
'Platform: %s\n' % sys.platform + \
'Python Version: %s\n' % sys.version.split()[0] + \
'wxPython Version: %s\n' % wx.VERSION_STRING + \
('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:]))
dialog = wx.MessageDialog(self, text, title,
wx.OK | wx.ICON_INFORMATION)
dialog.ShowModal()
dialog.Destroy()
def OnHelp(self, event):
"""Show a help dialog."""
frame.ShellFrameMixin.OnHelp(self, event)
def LoadSettings(self):
if self.config is not None:
frame.ShellFrameMixin.LoadSettings(self)
frame.Frame.LoadSettings(self, self.config)
self.shell.LoadSettings(self.config)
def SaveSettings(self, force=False):
if self.config is not None:
frame.ShellFrameMixin.SaveSettings(self)
if self.autoSaveSettings or force:
frame.Frame.SaveSettings(self, self.config)
self.shell.SaveSettings(self.config)
def DoSaveSettings(self):
if self.config is not None:
self.SaveSettings(force=True)
self.config.Flush()
HELP_TEXT = """\
* Key bindings:
Home Go to the beginning of the command or line.
Shift+Home Select to the beginning of the command or line.
Shift+End Select to the end of the line.
End Go to the end of the line.
Ctrl+C Copy selected text, removing prompts.
Ctrl+Shift+C Copy selected text, retaining prompts.
Alt+C Copy to the clipboard, including prefixed prompts.
Ctrl+X Cut selected text.
Ctrl+V Paste from clipboard.
Ctrl+Shift+V Paste and run multiple commands from clipboard.
Ctrl+Up Arrow Retrieve Previous History item.
Alt+P Retrieve Previous History item.
Ctrl+Down Arrow Retrieve Next History item.
Alt+N Retrieve Next History item.
Shift+Up Arrow Insert Previous History item.
Shift+Down Arrow Insert Next History item.
F8 Command-completion of History item.
(Type a few characters of a previous command and press F8.)
Ctrl+Enter Insert new line into multiline command.
Ctrl+] Increase font size.
Ctrl+[ Decrease font size.
Ctrl+= Default font size.
Ctrl-Space Show Auto Completion.
Ctrl-Alt-Space Show Call Tip.
Shift+Enter Complete Text from History.
Ctrl+F Search
F3 Search next
Ctrl+H "hide" lines containing selection / "unhide"
F12 on/off "free-edit" mode
"""
class ShellFacade:
"""Simplified interface to all shell-related functionality.
This is a semi-transparent facade, in that all attributes of other
are accessible, even though only some are visible to the user."""
name = 'Shell Interface'
def __init__(self, other):
"""Create a ShellFacade instance."""
d = self.__dict__
d['other'] = other
d['helpText'] = HELP_TEXT
def help(self):
"""Display some useful information about how to use the shell."""
self.write(self.helpText)
def __getattr__(self, name):
if hasattr(self.other, name):
return getattr(self.other, name)
else:
raise AttributeError(name)
def __setattr__(self, name, value):
if name in self.__dict__:
self.__dict__[name] = value
elif hasattr(self.other, name):
setattr(self.other, name, value)
else:
raise AttributeError(name)
def _getAttributeNames(self):
"""Return list of magic attributes to extend introspection."""
list = [
'about',
'ask',
'autoCallTip',
'autoComplete',
'autoCompleteAutoHide',
'autoCompleteCaseInsensitive',
'autoCompleteIncludeDouble',
'autoCompleteIncludeMagic',
'autoCompleteIncludeSingle',
'callTipInsert',
'clear',
'pause',
'prompt',
'quit',
'redirectStderr',
'redirectStdin',
'redirectStdout',
'run',
'runfile',
'wrap',
'zoom',
]
list.sort()
return list
#DNM
DISPLAY_TEXT="""
Author: %r
Py Version: %s
Python Version: %s
wxPython Version: %s
wxPython PlatformInfo: %s
Platform: %s"""
class Shell(editwindow.EditWindow):
"""Shell based on StyledTextCtrl."""
name = 'Shell'
def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
introText='', locals=None, InterpClass=None,
startupScript=None, execStartupScript=True,
*args, **kwds):
"""Create Shell instance."""
editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
self.wrap()
if locals is None:
import __main__
locals = __main__.__dict__
# Grab these so they can be restored by self.redirect* methods.
self.stdin = sys.stdin
self.stdout = sys.stdout
self.stderr = sys.stderr
# Import a default interpreter class if one isn't provided.
if InterpClass == None:
from .interpreter import Interpreter
else:
Interpreter = InterpClass
# Create a replacement for stdin.
self.reader = PseudoFileIn(self.readline, self.readlines)
self.reader.input = ''
self.reader.isreading = False
# Set up the interpreter.
self.interp = Interpreter(locals=locals,
rawin=self.raw_input,
stdin=self.reader,
stdout=PseudoFileOut(self.writeOut),
stderr=PseudoFileErr(self.writeErr),
*args, **kwds)
# Set up the buffer.
self.buffer = Buffer()
# Find out for which keycodes the interpreter will autocomplete.
self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
# Keep track of the last non-continuation prompt positions.
self.promptPosStart = 0
self.promptPosEnd = 0
# Keep track of multi-line commands.
self.more = False
# For use with forced updates during long-running scripts
self.lastUpdate=None
# Create the command history. Commands are added into the
# front of the list (ie. at index 0) as they are entered.
# self.historyIndex is the current position in the history; it
# gets incremented as you retrieve the previous command,
# decremented as you retrieve the next, and reset when you hit
# Enter. self.historyIndex == -1 means you're on the current
# command, not in the history.
self.history = []
self.historyIndex = -1
#seb add mode for "free edit"
self.noteMode = 0
self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT) # marker for hidden
self.searchTxt = ""
# Assign handlers for keyboard events.
self.Bind(wx.EVT_CHAR, self.OnChar)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
# Assign handler for the context menu
self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)
# Assign handlers for edit events
self.Bind(wx.EVT_MENU, lambda evt: self.Cut(), id=wx.ID_CUT)
self.Bind(wx.EVT_MENU, lambda evt: self.Copy(), id=wx.ID_COPY)
self.Bind(wx.EVT_MENU, lambda evt: self.CopyWithPrompts(), id=frame.ID_COPY_PLUS)
self.Bind(wx.EVT_MENU, lambda evt: self.Paste(), id=wx.ID_PASTE)
self.Bind(wx.EVT_MENU, lambda evt: self.PasteAndRun(), id=frame.ID_PASTE_PLUS)
self.Bind(wx.EVT_MENU, lambda evt: self.SelectAll(), id=wx.ID_SELECTALL)
self.Bind(wx.EVT_MENU, lambda evt: self.Clear(), id=wx.ID_CLEAR)
self.Bind(wx.EVT_MENU, lambda evt: self.Undo(), id=wx.ID_UNDO)
self.Bind(wx.EVT_MENU, lambda evt: self.Redo(), id=wx.ID_REDO)
# Assign handler for idle time.
self.waiting = False
self.Bind(wx.EVT_IDLE, self.OnIdle)
# Display the introductory banner information.
self.showIntro(introText)
# Assign some pseudo keywords to the interpreter's namespace.
self.setBuiltinKeywords()
# Add 'shell' to the interpreter's local namespace.
self.setLocalShell()
## NOTE: See note at bottom of this file...
## #seb: File drag and drop
## self.SetDropTarget( FileDropTarget(self) )
# Do this last so the user has complete control over their
# environment. They can override anything they want.
if execStartupScript:
if startupScript is None:
startupScript = os.environ.get('PYTHONSTARTUP')
self.execStartupScript(startupScript)
else:
self.prompt()
wx.CallAfter(self.ScrollToLine, 0)
def clearHistory(self):
self.history = []
self.historyIndex = -1
dispatcher.send(signal="Shell.clearHistory")
def destroy(self):
del self.interp
def setFocus(self):
"""Set focus to the shell."""
self.SetFocus()
def OnIdle(self, event):
"""Free the CPU to do other things."""
if self.waiting:
time.sleep(0.05)
event.Skip()
def showIntro(self, text=''):
"""Display introductory text in the shell."""
if text:
self.write(text)
try:
if self.interp.introText:
if text and not text.endswith(os.linesep):
self.write(os.linesep)
self.write(self.interp.introText)
except AttributeError:
pass
def setBuiltinKeywords(self):
"""Create pseudo keywords as part of builtins.
This sets "close", "exit" and "quit" to a helpful string.
"""
from wx.lib.six.moves import builtins
builtins.close = builtins.exit = builtins.quit = \
'Click on the close button to leave the application.'
builtins.cd = cd
builtins.ls = ls
builtins.pwd = pwd
builtins.sx = sx
def quit(self):
"""Quit the application."""
# XXX Good enough for now but later we want to send a close event.
# In the close event handler we can make sure they want to
# quit. Other applications, like PythonCard, may choose to
# hide rather than quit so we should just post the event and
# let the surrounding app decide what it wants to do.
self.write('Click on the close button to leave the application.')
def setLocalShell(self):
"""Add 'shell' to locals as reference to ShellFacade instance."""
self.interp.locals['shell'] = ShellFacade(other=self)
def execStartupScript(self, startupScript):
"""Execute the user's PYTHONSTARTUP script if they have one."""
if startupScript and os.path.isfile(startupScript):
text = 'Startup script executed: ' + startupScript
if PY3:
self.push('print(%r)' % text)
self.push('with open(%r, "r") as f:\n'
' exec(f.read())\n' % (startupScript))
else:
self.push('print(%r); execfile(%r)' % (text, startupScript))
self.interp.startupScript = startupScript
else:
self.push('')
def about(self):
"""Display information about Py."""
#DNM
text = DISPLAY_TEXT % \
(__author__, VERSION,
sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo),
sys.platform)
self.write(text.strip())
def OnChar(self, event):
"""Keypress event handler.
Only receives an event if OnKeyDown calls event.Skip() for the
corresponding event."""
if self.noteMode:
event.Skip()
return
# Prevent modification of previously submitted
# commands/responses.
if not self.CanEdit():
return
key = event.GetKeyCode()
currpos = self.GetCurrentPos()
stoppos = self.promptPosEnd
# Return (Enter) needs to be ignored in this handler.
if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
pass
elif key in self.autoCompleteKeys:
# Usually the dot (period) key activates auto completion.
# Get the command between the prompt and the cursor. Add
# the autocomplete character to the end of the command.
if self.AutoCompActive():
self.AutoCompCancel()
command = self.GetTextRange(stoppos, currpos) + chr(key)
self.write(chr(key))
if self.autoComplete:
self.autoCompleteShow(command)
elif key == ord('('):
# The left paren activates a call tip and cancels an
# active auto completion.
if self.AutoCompActive():
self.AutoCompCancel()
# Get the command between the prompt and the cursor. Add
# the '(' to the end of the command.
self.ReplaceSelection('')
command = self.GetTextRange(stoppos, currpos) + '('
self.write('(')
self.autoCallTipShow(command, self.GetCurrentPos() == self.GetTextLength())
else:
# Allow the normal event handling to take place.
event.Skip()
def OnKeyDown(self, event):
"""Key down event handler."""
key = event.GetKeyCode()
# If the auto-complete window is up let it do its thing.
if self.AutoCompActive():
event.Skip()
return
# Prevent modification of previously submitted
# commands/responses.
controlDown = event.ControlDown()
rawControlDown = event.RawControlDown()
altDown = event.AltDown()
shiftDown = event.ShiftDown()
currpos = self.GetCurrentPos()
endpos = self.GetTextLength()
selecting = self.GetSelectionStart() != self.GetSelectionEnd()
if (rawControlDown or controlDown) and shiftDown and key in (ord('F'), ord('f')):
li = self.GetCurrentLine()
m = self.MarkerGet(li)
if m & 1<<0:
startP = self.PositionFromLine(li)
self.MarkerDelete(li, 0)
maxli = self.GetLineCount()
li += 1 # li stayed visible as header-line
li0 = li
while li<maxli and self.GetLineVisible(li) == 0:
li += 1
endP = self.GetLineEndPosition(li-1)
self.ShowLines(li0, li-1)
# select reappearing text to allow "hide again"
self.SetSelection( startP, endP )
return
startP,endP = self.GetSelection()
endP-=1
startL = self.LineFromPosition(startP)
endL = self.LineFromPosition(endP)
# never hide last prompt
if endL == self.LineFromPosition(self.promptPosEnd):
endL -= 1
m = self.MarkerGet(startL)
self.MarkerAdd(startL, 0)
self.HideLines(startL+1,endL)
self.SetCurrentPos( startP ) # to ensure caret stays visible !
if key == wx.WXK_F12: #seb
if self.noteMode:
# self.promptPosStart not used anyway - or ?
self.promptPosEnd = \
self.PositionFromLine( self.GetLineCount()-1 ) + \
len(str(sys.ps1))
self.GotoLine(self.GetLineCount())
self.GotoPos(self.promptPosEnd)
self.prompt() #make sure we have a prompt
self.SetCaretForeground("black")
self.SetCaretWidth(1) #default
self.SetCaretPeriod(500) #default
else:
self.SetCaretForeground("red")
self.SetCaretWidth(4)
self.SetCaretPeriod(0) #steady
self.noteMode = not self.noteMode
return
if self.noteMode:
event.Skip()
return
# Return (Enter) is used to submit a command to the
# interpreter.
if (not (rawControlDown or controlDown) and not shiftDown and not altDown) and \
key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
if self.CallTipActive():
self.CallTipCancel()
self.processLine()
# Complete Text (from already typed words)
elif shiftDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
self.OnShowCompHistory()
# Ctrl+Return (Ctrl+Enter) is used to insert a line break.
elif (rawControlDown or controlDown) and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
if self.CallTipActive():
self.CallTipCancel()
if currpos == endpos:
self.processLine()
else:
self.insertLineBreak()
# Let Ctrl-Alt-* get handled normally.
elif (rawControlDown or controlDown) and altDown:
event.Skip()
# Clear the current, unexecuted command.
elif key == wx.WXK_ESCAPE:
if self.CallTipActive():
event.Skip()
else:
self.clearCommand()
# Clear the current command
elif key == wx.WXK_BACK and (rawControlDown or controlDown) and shiftDown:
self.clearCommand()
# Increase font size.
elif (rawControlDown or controlDown) and key in (ord(']'), wx.WXK_NUMPAD_ADD):
dispatcher.send(signal='FontIncrease')
# Decrease font size.
elif (rawControlDown or controlDown) and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT):
dispatcher.send(signal='FontDecrease')
# Default font size.
elif (rawControlDown or controlDown) and key in (ord('='), wx.WXK_NUMPAD_DIVIDE):
dispatcher.send(signal='FontDefault')
# Cut to the clipboard.
elif ((rawControlDown or controlDown) and key in (ord('X'), ord('x'))) \
or (shiftDown and key == wx.WXK_DELETE):
self.Cut()
# Copy to the clipboard.
elif (rawControlDown or controlDown) and not shiftDown \
and key in (ord('C'), ord('c'), wx.WXK_INSERT):
self.Copy()
# Copy to the clipboard, including prompts.
elif (rawControlDown or controlDown) and shiftDown \
and key in (ord('C'), ord('c'), wx.WXK_INSERT):
self.CopyWithPrompts()
# Copy to the clipboard, including prefixed prompts.
elif altDown and not controlDown \
and key in (ord('C'), ord('c'), wx.WXK_INSERT):
self.CopyWithPromptsPrefixed()
# Home needs to be aware of the prompt.
elif (rawControlDown or controlDown) and key == wx.WXK_HOME:
home = self.promptPosEnd
if currpos > home:
self.SetCurrentPos(home)
if not selecting and not shiftDown:
self.SetAnchor(home)
self.EnsureCaretVisible()
else:
event.Skip()
# Home needs to be aware of the prompt.
elif key == wx.WXK_HOME:
home = self.promptPosEnd
if currpos > home:
[line_str,line_len] = self.GetCurLine()
pos=self.GetCurrentPos()
if line_str[:4] in [sys.ps1,sys.ps2,sys.ps3]:
self.SetCurrentPos(pos+4-line_len)
#self.SetCurrentPos(home)
if not selecting and not shiftDown:
self.SetAnchor(pos+4-line_len)
self.EnsureCaretVisible()
else:
event.Skip()
else:
event.Skip()
#
# The following handlers modify text, so we need to see if
# there is a selection that includes text prior to the prompt.
#
# Don't modify a selection with text prior to the prompt.
elif selecting and key not in NAVKEYS and not self.CanEdit():
pass
# Paste from the clipboard.
elif ((rawControlDown or controlDown) and not shiftDown and key in (ord('V'), ord('v'))) \
or (shiftDown and not controlDown and key == wx.WXK_INSERT):
self.Paste()
# manually invoke AutoComplete and Calltips
elif (rawControlDown or controlDown) and key == wx.WXK_SPACE:
self.OnCallTipAutoCompleteManually(shiftDown)
# Paste from the clipboard, run commands.
elif (rawControlDown or controlDown) and shiftDown and key in (ord('V'), ord('v')):
self.PasteAndRun()
# Replace with the previous command from the history buffer.
elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_UP) \
or (altDown and key in (ord('P'), ord('p'))):
self.OnHistoryReplace(step=+1)
# Replace with the next command from the history buffer.
elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_DOWN) \
or (altDown and key in (ord('N'), ord('n'))):
self.OnHistoryReplace(step=-1)
# Insert the previous command from the history buffer.
elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_UP) and self.CanEdit():
self.OnHistoryInsert(step=+1)
# Insert the next command from the history buffer.
elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_DOWN) and self.CanEdit():
self.OnHistoryInsert(step=-1)
# Search up the history for the text in front of the cursor.
elif key == wx.WXK_F8:
self.OnHistorySearch()
# Don't backspace over the latest non-continuation prompt.
elif key == wx.WXK_BACK:
if selecting and self.CanEdit():
event.Skip()
elif currpos > self.promptPosEnd:
event.Skip()
# Only allow these keys after the latest prompt.
elif key in (wx.WXK_TAB, wx.WXK_DELETE):
if self.CanEdit():
event.Skip()
# Don't toggle between insert mode and overwrite mode.
elif key == wx.WXK_INSERT:
pass
# Don't allow line deletion.
elif controlDown and key in (ord('L'), ord('l')):
# TODO : Allow line deletion eventually...
#event.Skip()
pass
# Don't allow line transposition.
elif controlDown and key in (ord('T'), ord('t')):
# TODO : Allow line transposition eventually...
# TODO : Will have to adjust markers accordingly and test if allowed...
#event.Skip()
pass
# Basic navigation keys should work anywhere.
elif key in NAVKEYS:
event.Skip()
# Protect the readonly portion of the shell.
elif not self.CanEdit():
pass
else:
event.Skip()
def OnShowCompHistory(self):
"""Show possible autocompletion Words from already typed words."""
#copy from history
his = self.history[:]
#put together in one string
joined = " ".join (his)
import re
#sort out only "good" words
newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined)
#length > 1 (mix out "trash")
thlist = []
for i in newlist:
if len (i) > 1:
thlist.append (i)
#unique (no duplicate words
#oneliner from german python forum => unique list
unlist = [thlist[i] for i in xrange(len(thlist)) if thlist[i] not in thlist[:i]]
#sort lowercase
unlist.sort(lambda a, b: cmp(a.lower(), b.lower()))
#this is more convenient, isn't it?
self.AutoCompSetIgnoreCase(True)
#join again together in a string
stringlist = " ".join(unlist)
#pos von 0 noch ausrechnen
#how big is the offset?
cpos = self.GetCurrentPos() - 1
while chr (self.GetCharAt (cpos)).isalnum():
cpos -= 1
#the most important part
self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist)
def clearCommand(self):
"""Delete the current, unexecuted command."""
startpos = self.promptPosEnd
endpos = self.GetTextLength()
self.SetSelection(startpos, endpos)
self.ReplaceSelection('')
self.more = False
def OnHistoryReplace(self, step):
"""Replace with the previous/next command from the history buffer."""
self.clearCommand()
self.replaceFromHistory(step)
def replaceFromHistory(self, step):
"""Replace selection with command from the history buffer."""
ps2 = str(sys.ps2)
self.ReplaceSelection('')
newindex = self.historyIndex + step
if -1 <= newindex <= len(self.history):
self.historyIndex = newindex
if 0 <= newindex <= len(self.history)-1:
command = self.history[self.historyIndex]
command = command.replace('\n', os.linesep + ps2)
self.ReplaceSelection(command)
def OnHistoryInsert(self, step):
"""Insert the previous/next command from the history buffer."""
if not self.CanEdit():
return
startpos = self.GetCurrentPos()
self.replaceFromHistory(step)
endpos = self.GetCurrentPos()
self.SetSelection(endpos, startpos)
def OnHistorySearch(self):
"""Search up the history buffer for the text in front of the cursor."""
if not self.CanEdit():
return
startpos = self.GetCurrentPos()
# The text up to the cursor is what we search for.
numCharsAfterCursor = self.GetTextLength() - startpos
searchText = self.getCommand(rstrip=False)
if numCharsAfterCursor > 0:
searchText = searchText[:-numCharsAfterCursor]
if not searchText:
return
# Search upwards from the current history position and loop
# back to the beginning if we don't find anything.
if (self.historyIndex <= -1) \
or (self.historyIndex >= len(self.history)-2):
searchOrder = range(len(self.history))
else:
searchOrder = range(self.historyIndex+1, len(self.history)) + \
range(self.historyIndex)
for i in searchOrder:
command = self.history[i]
if command[:len(searchText)] == searchText:
# Replace the current selection with the one we found.
self.ReplaceSelection(command[len(searchText):])
endpos = self.GetCurrentPos()
self.SetSelection(endpos, startpos)
# We've now warped into middle of the history.
self.historyIndex = i
break
def setStatusText(self, text):
"""Display status information."""
# This method will likely be replaced by the enclosing app to
# do something more interesting, like write to a status bar.
print(text)
def insertLineBreak(self):
"""Insert a new line break."""
if self.CanEdit():
self.write(os.linesep)
self.more = True
self.prompt()
def processLine(self):
"""Process the line of text at which the user hit Enter."""
# The user hit ENTER and we need to decide what to do. They
# could be sitting on any line in the shell.
thepos = self.GetCurrentPos()
startpos = self.promptPosEnd
endpos = self.GetTextLength()
ps2 = str(sys.ps2)
# If they hit RETURN inside the current command, execute the
# command.
if self.CanEdit():
self.SetCurrentPos(endpos)
self.interp.more = False
command = self.GetTextRange(startpos, endpos)
lines = command.split(os.linesep + ps2)
lines = [line.rstrip() for line in lines]
command = '\n'.join(lines)
if self.reader.isreading:
if not command:
# Match the behavior of the standard Python shell
# when the user hits return without entering a
# value.
command = '\n'
self.reader.input = command
self.write(os.linesep)
else:
self.push(command)
wx.CallLater(1, self.EnsureCaretVisible)
# Or replace the current command with the other command.
else:
# If the line contains a command (even an invalid one).
if self.getCommand(rstrip=False):
command = self.getMultilineCommand()
self.clearCommand()
self.write(command)
# Otherwise, put the cursor back where we started.
else:
self.SetCurrentPos(thepos)
self.SetAnchor(thepos)
def getMultilineCommand(self, rstrip=True):
"""Extract a multi-line command from the editor.
The command may not necessarily be valid Python syntax."""
# XXX Need to extract real prompts here. Need to keep track of
# the prompt every time a command is issued.
ps1 = str(sys.ps1)
ps1size = len(ps1)
ps2 = str(sys.ps2)
ps2size = len(ps2)
# This is a total hack job, but it works.
text = self.GetCurLine()[0]
line = self.GetCurrentLine()
while text[:ps2size] == ps2 and line > 0:
line -= 1
self.GotoLine(line)
text = self.GetCurLine()[0]
if text[:ps1size] == ps1:
line = self.GetCurrentLine()
self.GotoLine(line)
startpos = self.GetCurrentPos() + ps1size
line += 1
self.GotoLine(line)
while self.GetCurLine()[0][:ps2size] == ps2:
line += 1
self.GotoLine(line)
stoppos = self.GetCurrentPos()
command = self.GetTextRange(startpos, stoppos)
command = command.replace(os.linesep + ps2, '\n')
command = command.rstrip()
command = command.replace('\n', os.linesep + ps2)
else:
command = ''
if rstrip:
command = command.rstrip()
return command
def getCommand(self, text=None, rstrip=True):
"""Extract a command from text which may include a shell prompt.
The command may not necessarily be valid Python syntax."""
if not text:
text = self.GetCurLine()[0]
# Strip the prompt off the front leaving just the command.
command = self.lstripPrompt(text)
if command == text:
command = '' # Real commands have prompts.
if rstrip:
command = command.rstrip()
return command
def lstripPrompt(self, text):
"""Return text without a leading prompt."""
ps1 = str(sys.ps1)
ps1size = len(ps1)
ps2 = str(sys.ps2)
ps2size = len(ps2)
# Strip the prompt off the front of text.
if text[:ps1size] == ps1:
text = text[ps1size:]
elif text[:ps2size] == ps2:
text = text[ps2size:]
return text
def push(self, command, silent = False):
"""Send command to the interpreter for execution."""
if not silent:
self.write(os.linesep)
#DNM
if USE_MAGIC:
command=magic(command)
busy = wx.BusyCursor()
self.waiting = True
self.lastUpdate=None
self.more = self.interp.push(command)
self.lastUpdate=None
self.waiting = False
del busy
if not self.more:
self.addHistory(command.rstrip())
if not silent:
self.prompt()
def addHistory(self, command):
"""Add command to the command history."""
# Reset the history position.
self.historyIndex = -1
# Insert this command into the history, unless it's a blank
# line or the same as the last command.
if command != '' \
and (len(self.history) == 0 or command != self.history[0]):
self.history.insert(0, command)
dispatcher.send(signal="Shell.addHistory", command=command)
def write(self, text):
"""Display text in the shell.
Replace line endings with OS-specific endings."""
text = self.fixLineEndings(text)
self.AddText(text)
self.EnsureCaretVisible()
if self.waiting:
if self.lastUpdate==None:
self.lastUpdate=time.time()
if time.time()-self.lastUpdate > PRINT_UPDATE_MAX_TIME:
self.Update()
self.lastUpdate=time.time()
def fixLineEndings(self, text):
"""Return text with line endings replaced by OS-specific endings."""
lines = text.split('\r\n')
for l in range(len(lines)):
chunks = lines[l].split('\r')
for c in range(len(chunks)):
chunks[c] = os.linesep.join(chunks[c].split('\n'))
lines[l] = os.linesep.join(chunks)
text = os.linesep.join(lines)
return text
def prompt(self):
"""Display proper prompt for the context: ps1, ps2 or ps3.
If this is a continuation line, autoindent as necessary."""
isreading = self.reader.isreading
skip = False
if isreading:
prompt = str(sys.ps3)
elif self.more:
prompt = str(sys.ps2)
else:
prompt = str(sys.ps1)
pos = self.GetCurLine()[1]
if pos > 0:
if isreading:
skip = True
else:
self.write(os.linesep)
if not self.more:
self.promptPosStart = self.GetCurrentPos()
if not skip:
self.write(prompt)
if not self.more:
self.promptPosEnd = self.GetCurrentPos()
# Keep the undo feature from undoing previous responses.
self.EmptyUndoBuffer()
if self.more:
line_num=self.GetCurrentLine()
currentLine=self.GetLine(line_num)
previousLine=self.GetLine(line_num-1)[len(prompt):]
pstrip=previousLine.strip()
lstrip=previousLine.lstrip()
# Get the first alnum word:
first_word=[]
for i in pstrip:
if i.isalnum():
first_word.append(i)
else:
break
first_word = ''.join(first_word)
if pstrip == '':
# because it is all whitespace!
indent=previousLine.strip('\n').strip('\r')
else:
indent=previousLine[:(len(previousLine)-len(lstrip))]
if pstrip[-1]==':' and \
first_word in ['if','else','elif','for','while',
'def','class','try','except','finally']:
indent+=' '*4
self.write(indent)
self.EnsureCaretVisible()
self.ScrollToColumn(0)
def readline(self):
"""Replacement for stdin.readline()."""
input = ''
reader = self.reader
reader.isreading = True
self.prompt()
try:
while not reader.input:
wx.YieldIfNeeded()
input = reader.input
finally:
reader.input = ''
reader.isreading = False
input = str(input) # In case of Unicode.
return input
def readlines(self):
"""Replacement for stdin.readlines()."""
lines = []
while lines[-1:] != ['\n']:
lines.append(self.readline())
return lines
def raw_input(self, prompt=''):
"""Return string based on user input."""
if prompt:
self.write(prompt)
return self.readline()
def ask(self, prompt='Please enter your response:'):
"""Get response from the user using a dialog box."""
dialog = wx.TextEntryDialog(None, prompt,
'Input Dialog (Raw)', '')
try:
if dialog.ShowModal() == wx.ID_OK:
text = dialog.GetValue()
return text
finally:
dialog.Destroy()
return ''
def pause(self):
"""Halt execution pending a response from the user."""
self.ask('Press enter to continue:')
def clear(self):
"""Delete all text from the shell."""
self.ClearAll()
def run(self, command, prompt=True, verbose=True):
"""Execute command as if it was typed in directly.
>>> shell.run('print("this")')
>>> print("this")
this
>>>
"""
# Go to the very bottom of the text.
endpos = self.GetTextLength()
self.SetCurrentPos(endpos)
command = command.rstrip()
if prompt: self.prompt()
if verbose: self.write(command)
self.push(command)
def runfile(self, filename):
"""Execute all commands in file as if they were typed into the
shell."""
file = open(filename)
try:
self.prompt()
for command in file.readlines():
if command[:6] == 'shell.':
# Run shell methods silently.
self.run(command, prompt=False, verbose=False)
else:
self.run(command, prompt=False, verbose=True)
finally:
file.close()
def autoCompleteShow(self, command, offset = 0):
"""Display auto-completion popup list."""
self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
list = self.interp.getAutoCompleteList(command,
includeMagic=self.autoCompleteIncludeMagic,
includeSingle=self.autoCompleteIncludeSingle,
includeDouble=self.autoCompleteIncludeDouble)
if list:
options = ' '.join(list)
#offset = 0
self.AutoCompShow(offset, options)
def autoCallTipShow(self, command, insertcalltip = True, forceCallTip = False):
"""Display argument spec and docstring in a popup window."""
if self.CallTipActive():
self.CallTipCancel()
(name, argspec, tip) = self.interp.getCallTip(command)
if tip:
dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
if not self.autoCallTip and not forceCallTip:
return
startpos = self.GetCurrentPos()
if argspec and insertcalltip and self.callTipInsert:
self.write(argspec + ')')
endpos = self.GetCurrentPos()
self.SetSelection(startpos, endpos)
if tip:
tippos = startpos - (len(name) + 1)
fallback = startpos - self.GetColumn(startpos)
# In case there isn't enough room, only go back to the
# fallback.
tippos = max(tippos, fallback)
self.CallTipShow(tippos, tip)
def OnCallTipAutoCompleteManually (self, shiftDown):
"""AutoComplete and Calltips manually."""
if self.AutoCompActive():
self.AutoCompCancel()
currpos = self.GetCurrentPos()
stoppos = self.promptPosEnd
cpos = currpos
#go back until '.' is found
pointavailpos = -1
while cpos >= stoppos:
if self.GetCharAt(cpos) == ord ('.'):
pointavailpos = cpos
break
cpos -= 1
#word from non whitespace until '.'
if pointavailpos != -1:
#look backward for first whitespace char
textbehind = self.GetTextRange (pointavailpos + 1, currpos)
pointavailpos += 1
if not shiftDown:
#call AutoComplete
stoppos = self.promptPosEnd
textbefore = self.GetTextRange(stoppos, pointavailpos)
self.autoCompleteShow(textbefore, len (textbehind))
else:
#call CallTips
cpos = pointavailpos
begpos = -1
while cpos > stoppos:
if chr(self.GetCharAt(cpos)).isspace():
begpos = cpos
break
cpos -= 1
if begpos == -1:
begpos = cpos
ctips = self.GetTextRange (begpos, currpos)
ctindex = ctips.find ('(')
if ctindex != -1 and not self.CallTipActive():
#insert calltip, if current pos is '(', otherwise show it only
self.autoCallTipShow(ctips[:ctindex + 1],
self.GetCharAt(currpos - 1) == ord('(') and \
self.GetCurrentPos() == self.GetTextLength(),
True)
def writeOut(self, text):
"""Replacement for stdout."""
self.write(text)
def writeErr(self, text):
"""Replacement for stderr."""
self.write(text)
def redirectStdin(self, redirect=True):
"""If redirect is true then sys.stdin will come from the shell."""
if redirect:
sys.stdin = self.reader
else:
sys.stdin = self.stdin
def redirectStdout(self, redirect=True):
"""If redirect is true then sys.stdout will go to the shell."""
if redirect:
sys.stdout = PseudoFileOut(self.writeOut)
else:
sys.stdout = self.stdout
def redirectStderr(self, redirect=True):
"""If redirect is true then sys.stderr will go to the shell."""
if redirect:
sys.stderr = PseudoFileErr(self.writeErr)
else:
sys.stderr = self.stderr
def CanCut(self):
"""Return true if text is selected and can be cut."""
if self.GetSelectionStart() != self.GetSelectionEnd() \
and self.GetSelectionStart() >= self.promptPosEnd \
and self.GetSelectionEnd() >= self.promptPosEnd:
return True
else:
return False
def CanPaste(self):
"""Return true if a paste should succeed."""
if self.CanEdit() and editwindow.EditWindow.CanPaste(self):
return True
else:
return False
def CanEdit(self):
"""Return true if editing should succeed."""
if self.GetSelectionStart() != self.GetSelectionEnd():
if self.GetSelectionStart() >= self.promptPosEnd \
and self.GetSelectionEnd() >= self.promptPosEnd:
return True
else:
return False
else:
return self.GetCurrentPos() >= self.promptPosEnd
def Cut(self):
"""Remove selection and place it on the clipboard."""
if self.CanCut() and self.CanCopy():
if self.AutoCompActive():
self.AutoCompCancel()
if self.CallTipActive():
self.CallTipCancel()
self.Copy()
self.ReplaceSelection('')
def Copy(self):
"""Copy selection and place it on the clipboard."""
if self.CanCopy():
ps1 = str(sys.ps1)
ps2 = str(sys.ps2)
command = self.GetSelectedText()
command = command.replace(os.linesep + ps2, os.linesep)
command = command.replace(os.linesep + ps1, os.linesep)
command = self.lstripPrompt(text=command)
data = wx.TextDataObject(command)
self._clip(data)
def CopyWithPrompts(self):
"""Copy selection, including prompts, and place it on the clipboard."""
if self.CanCopy():
command = self.GetSelectedText()
data = wx.TextDataObject(command)
self._clip(data)
def CopyWithPromptsPrefixed(self):
"""Copy selection, including prompts prefixed with four
spaces, and place it on the clipboard."""
if self.CanCopy():
command = self.GetSelectedText()
spaces = ' ' * 4
command = spaces + command.replace(os.linesep,
os.linesep + spaces)
data = wx.TextDataObject(command)
self._clip(data)
def _clip(self, data):
if wx.TheClipboard.Open():
wx.TheClipboard.UsePrimarySelection(False)
wx.TheClipboard.SetData(data)
wx.TheClipboard.Flush()
wx.TheClipboard.Close()
def Paste(self):
"""Replace selection with clipboard contents."""
if self.CanPaste() and wx.TheClipboard.Open():
ps2 = str(sys.ps2)
if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
data = wx.TextDataObject()
if wx.TheClipboard.GetData(data):
self.ReplaceSelection('')
command = data.GetText()
command = command.rstrip()
command = self.fixLineEndings(command)
command = self.lstripPrompt(text=command)
command = command.replace(os.linesep + ps2, '\n')
command = command.replace(os.linesep, '\n')
command = command.replace('\n', os.linesep + ps2)
self.write(command)
wx.TheClipboard.Close()
def PasteAndRun(self):
"""Replace selection with clipboard contents, run commands."""
text = ''
if wx.TheClipboard.Open():
if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
data = wx.TextDataObject()
if wx.TheClipboard.GetData(data):
text = data.GetText()
wx.TheClipboard.Close()
if text:
self.Execute(text)
def Execute(self, text):
"""Replace selection with text and run commands."""
ps1 = str(sys.ps1)
ps2 = str(sys.ps2)
endpos = self.GetTextLength()
self.SetCurrentPos(endpos)
startpos = self.promptPosEnd
self.SetSelection(startpos, endpos)
self.ReplaceSelection('')
text = text.lstrip()
text = self.fixLineEndings(text)
text = self.lstripPrompt(text)
text = text.replace(os.linesep + ps1, '\n')
text = text.replace(os.linesep + ps2, '\n')
text = text.replace(os.linesep, '\n')
lines = text.split('\n')
commands = []
command = ''
for line in lines:
if line.strip() == ps2.strip():
# If we are pasting from something like a
# web page that drops the trailing space
# from the ps2 prompt of a blank line.
line = ''
lstrip = line.lstrip()
if line.strip() != '' and lstrip == line and \
lstrip[:4] not in ['else','elif'] and \
lstrip[:6] != 'except':
# New command.
if command:
# Add the previous command to the list.
commands.append(command)
# Start a new command, which may be multiline.
command = line
else:
# Multiline command. Add to the command.
command += '\n'
command += line
commands.append(command)
for command in commands:
command = command.replace('\n', os.linesep + ps2)
self.write(command)
self.processLine()
def wrap(self, wrap=True):
"""Sets whether text is word wrapped."""
try:
self.SetWrapMode(wrap)
except AttributeError:
return 'Wrapping is not available in this version.'
def zoom(self, points=0):
"""Set the zoom level.
This number of points is added to the size of all fonts. It
may be positive to magnify or negative to reduce."""
self.SetZoom(points)
def LoadSettings(self, config):
self.autoComplete = \
config.ReadBool('Options/AutoComplete', True)
self.autoCompleteIncludeMagic = \
config.ReadBool('Options/AutoCompleteIncludeMagic', True)
self.autoCompleteIncludeSingle = \
config.ReadBool('Options/AutoCompleteIncludeSingle', True)
self.autoCompleteIncludeDouble = \
config.ReadBool('Options/AutoCompleteIncludeDouble', True)
self.autoCallTip = config.ReadBool('Options/AutoCallTip', True)
self.callTipInsert = config.ReadBool('Options/CallTipInsert', True)
self.SetWrapMode(config.ReadBool('View/WrapMode', True))
self.lineNumbers = config.ReadBool('View/ShowLineNumbers', True)
self.setDisplayLineNumbers (self.lineNumbers)
zoom = config.ReadInt('View/Zoom/Shell', -99)
if zoom != -99:
self.SetZoom(zoom)
def SaveSettings(self, config):
config.WriteBool('Options/AutoComplete', self.autoComplete)
config.WriteBool('Options/AutoCompleteIncludeMagic',
self.autoCompleteIncludeMagic)
config.WriteBool('Options/AutoCompleteIncludeSingle',
self.autoCompleteIncludeSingle)
config.WriteBool('Options/AutoCompleteIncludeDouble',
self.autoCompleteIncludeDouble)
config.WriteBool('Options/AutoCallTip', self.autoCallTip)
config.WriteBool('Options/CallTipInsert', self.callTipInsert)
config.WriteBool('View/WrapMode', self.GetWrapMode())
config.WriteBool('View/ShowLineNumbers', self.lineNumbers)
config.WriteInt('View/Zoom/Shell', self.GetZoom())
def GetContextMenu(self):
"""
Create and return a context menu for the shell.
This is used instead of the scintilla default menu
in order to correctly respect our immutable buffer.
"""
menu = wx.Menu()
menu.Append(wx.ID_UNDO, "Undo")
menu.Append(wx.ID_REDO, "Redo")
menu.AppendSeparator()
menu.Append(wx.ID_CUT, "Cut")
menu.Append(wx.ID_COPY, "Copy")
menu.Append(frame.ID_COPY_PLUS, "Copy Plus")
menu.Append(wx.ID_PASTE, "Paste")
menu.Append(frame.ID_PASTE_PLUS, "Paste Plus")
menu.Append(wx.ID_CLEAR, "Clear")
menu.AppendSeparator()
menu.Append(wx.ID_SELECTALL, "Select All")
return menu
def OnContextMenu(self, evt):
menu = self.GetContextMenu()
self.PopupMenu(menu)
def OnUpdateUI(self, evt):
id = evt.Id
if id in (wx.ID_CUT, wx.ID_CLEAR):
evt.Enable(self.CanCut())
elif id in (wx.ID_COPY, frame.ID_COPY_PLUS):
evt.Enable(self.CanCopy())
elif id in (wx.ID_PASTE, frame.ID_PASTE_PLUS):
evt.Enable(self.CanPaste())
elif id == wx.ID_UNDO:
evt.Enable(self.CanUndo())
elif id == wx.ID_REDO:
evt.Enable(self.CanRedo())
## NOTE: The DnD of file names is disabled until we can figure out how
## best to still allow DnD of text.
## #seb : File drag and drop
## class FileDropTarget(wx.FileDropTarget):
## def __init__(self, obj):
## wx.FileDropTarget.__init__(self)
## self.obj = obj
## def OnDropFiles(self, x, y, filenames):
## if len(filenames) == 1:
## txt = 'r\"%s\"' % filenames[0]
## else:
## txt = '( '
## for f in filenames:
## txt += 'r\"%s\" , ' % f
## txt += ')'
## self.obj.AppendText(txt)
## pos = self.obj.GetCurrentPos()
## self.obj.SetCurrentPos( pos )
## self.obj.SetSelection( pos, pos )
## class TextAndFileDropTarget(wx.DropTarget):
## def __init__(self, shell):
## wx.DropTarget.__init__(self)
## self.shell = shell
## self.compdo = wx.DataObjectComposite()
## self.textdo = wx.TextDataObject()
## self.filedo = wx.FileDataObject()
## self.compdo.Add(self.textdo)
## self.compdo.Add(self.filedo, True)
## self.SetDataObject(self.compdo)
## def OnDrop(self, x, y):
## return True
## def OnData(self, x, y, result):
## self.GetData()
## if self.textdo.GetTextLength() > 1:
## text = self.textdo.GetText()
## # *** Do somethign with the dragged text here...
## self.textdo.SetText('')
## else:
## filenames = str(self.filename.GetFilenames())
## if len(filenames) == 1:
## txt = 'r\"%s\"' % filenames[0]
## else:
## txt = '( '
## for f in filenames:
## txt += 'r\"%s\" , ' % f
## txt += ')'
## self.shell.AppendText(txt)
## pos = self.shell.GetCurrentPos()
## self.shell.SetCurrentPos( pos )
## self.shell.SetSelection( pos, pos )
## return result