mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-26 21:22:27 -06:00
3794 lines
148 KiB
Python
3794 lines
148 KiB
Python
#----------------------------------------------------------------------
|
|
# Name: sliceshell.py
|
|
# Author: David N. Mashburn, Patrick K. O'Brien
|
|
# Tags: phoenix-port
|
|
#----------------------------------------------------------------------
|
|
"""Slices 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.
|
|
Slices is a version of shell modified by David Mashburn."""
|
|
|
|
__author__ = "David N. Mashburn <david.n.mashburn@gmail.com> / "
|
|
__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 editor
|
|
from . import editwindow
|
|
from . import document
|
|
from . import frame
|
|
from .pseudo import PseudoFileIn
|
|
from .pseudo import PseudoFileOut
|
|
from .pseudo import PseudoFileErr
|
|
from .version import VERSION
|
|
from .magic import magic
|
|
from .parse import testForContinuations
|
|
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_HOME, wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
|
|
wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)
|
|
|
|
GROUPING_SELECTING=0
|
|
IO_SELECTING = 1
|
|
|
|
GROUPING_START = 2
|
|
GROUPING_START_FOLDED = 3
|
|
GROUPING_MIDDLE = 4
|
|
GROUPING_END = 5
|
|
INPUT_START = 6
|
|
INPUT_START_FOLDED = 7
|
|
INPUT_MIDDLE = 8
|
|
INPUT_END = 9
|
|
OUTPUT_START = 10
|
|
OUTPUT_START_FOLDED = 11
|
|
OUTPUT_MIDDLE = 12
|
|
OUTPUT_END = 13
|
|
|
|
OUTPUT_BG = 14
|
|
READLINE_BG = 15
|
|
INPUT_READLINE = 16
|
|
|
|
# Could add C integration right into the markers...
|
|
# Non-editable file marker for auto-loaded files...
|
|
# Weave VariableInput = 15
|
|
# Weave C code = 16
|
|
# C code = 17 (only for use with Pyrex)
|
|
# Pyrex / Cython code = 18
|
|
|
|
GROUPING_MASK = ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED |
|
|
1<<GROUPING_MIDDLE | 1<<GROUPING_END )
|
|
|
|
INPUT_MASK = ( 1<<INPUT_START | 1<<INPUT_START_FOLDED |
|
|
1<<INPUT_MIDDLE | 1<<INPUT_END )
|
|
OUTPUT_MASK = ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED |
|
|
1<<OUTPUT_MIDDLE | 1<<OUTPUT_END )
|
|
IO_MASK = ( INPUT_MASK | OUTPUT_MASK )
|
|
|
|
IO_START_MASK = ( 1<<INPUT_START | 1<<OUTPUT_START )
|
|
IO_START_FOLDED_MASK = ( 1<<INPUT_START_FOLDED | 1<<OUTPUT_START_FOLDED )
|
|
IO_ANY_START_MASK = ( 1<<INPUT_START | 1<<OUTPUT_START |
|
|
1<<INPUT_START_FOLDED | 1<<OUTPUT_START_FOLDED )
|
|
IO_MIDDLE_MASK = ( 1<<INPUT_MIDDLE | 1<<OUTPUT_MIDDLE )
|
|
IO_END_MASK = ( 1<<INPUT_END | 1<<OUTPUT_END )
|
|
|
|
usrBinEnvPythonText = '#!/usr/bin/env python\n'
|
|
pyslicesFormatHeaderText = ['#PySlices Save Format Version 1.1 (PySlices v0.9.7.8 and later)\n',
|
|
'#PySlices Save Format Version 1.2 (PySlices v0.9.8 and later)\n']
|
|
groupingStartText = '#PySlices Marker Information -- Begin Grouping Slice\n'
|
|
inputStartText = '#PySlices Marker Information -- Begin Input Slice\n'
|
|
outputStartText = '#PySlices Marker Information -- Begin Output Slice\n'
|
|
|
|
tutorialText = """
|
|
|
|
Tutorial!!!
|
|
------------------------------------------------------------------------
|
|
PySlices is the newest member of the Py suite!
|
|
It is a modified version of PyCrust that supports multi-line commands.
|
|
|
|
Input and output are contained in "Slices" shown as markers in the left margin.
|
|
Input Slices have RED margins (active, editable).
|
|
Output Slices have BLUE margins (frozen, not editable).
|
|
|
|
Commands in slices can be on more than one line, as with Sage or Mathematica.
|
|
For example, the command:
|
|
a=1
|
|
b=2
|
|
print(a+b)
|
|
will all run in sequence, much like a script.
|
|
Try running the above Input Slice by clicking somewhere in its text and
|
|
using Ctrl-Return, Shift-Return, or Numpad Enter to execute.
|
|
Previous commands (Old Slices) can be re-edited and run again in place.
|
|
|
|
Slices can also be:
|
|
* selceted (click on the margin, Shift-click for multiple selection)
|
|
* folded (click the margin twice)
|
|
* selected and deleted (hit delete while selected)
|
|
* divided (Ctrl-D)
|
|
* merged (Ctrl-M while selecting adjacent, like-colored slices)
|
|
|
|
Try deleting the slice above this one by clicking on the red margin.
|
|
|
|
If you want a more traditional shell feel, try enabling "Shell Mode" in
|
|
"Options->Settings->Shell Mode" (or try PyCrust).
|
|
In Shell Mode, two returns in a row executes the command, and
|
|
Ctrl-Return and Shift-Return always print newlines.
|
|
|
|
Saving and opening "sessions" is now supported! This is a little
|
|
different to other shells where the history is saved. With PySlices,
|
|
the whole document is saved in a simple text format!
|
|
|
|
To disable this Tutorial on startup, uncheck it in the menu at:
|
|
"Options->Startup->Show PySlices tutorial"
|
|
|
|
PySlices may not be the best thing since sliced bread, but
|
|
I hope it makes using Python a little bit sweeter!
|
|
"""
|
|
|
|
class SlicesShellFrame(frame.Frame, frame.ShellFrameMixin):
|
|
"""Frame containing the sliceshell component."""
|
|
|
|
name = 'SlicesShell Frame'
|
|
|
|
def __init__(self, parent=None, id=-1, title='PySlicesShell',
|
|
pos=wx.DefaultPosition, size=wx.DefaultSize,
|
|
style=wx.DEFAULT_FRAME_STYLE, locals=None,
|
|
InterpClass=None,
|
|
config=None, dataDir=None, filename=None,
|
|
*args, **kwds):
|
|
"""Create SlicesShellFrame instance."""
|
|
frame.Frame.__init__(self, parent, id, title, pos, size, style,shellName='PySlices')
|
|
frame.ShellFrameMixin.__init__(self, config, dataDir)
|
|
|
|
if size == wx.DefaultSize:
|
|
self.SetSize((750, 525))
|
|
|
|
intro = 'PySlices %s - The Flakiest Python Shell... Cut Up!' % VERSION
|
|
self.SetStatusText(intro.replace('\n', ', '))
|
|
self.sliceshell = SlicesShell(parent=self, id=-1, introText=intro,
|
|
locals=locals, InterpClass=InterpClass,
|
|
startupScript=self.startupScript,
|
|
execStartupScript=self.execStartupScript,
|
|
showPySlicesTutorial=self.showPySlicesTutorial,
|
|
enableShellMode=self.enableShellMode,
|
|
hideFoldingMargin=self.hideFoldingMargin,
|
|
*args, **kwds)
|
|
self.buffer = self.sliceshell.buffer
|
|
|
|
# Override the shell so that status messages go to the status bar.
|
|
self.sliceshell.setStatusText = self.SetStatusText
|
|
|
|
self.sliceshell.SetFocus()
|
|
self.LoadSettings()
|
|
|
|
self.currentDirectory = os.path.expanduser('~')
|
|
|
|
if filename!=None:
|
|
self.bufferOpen(filename)
|
|
|
|
self.Bind(wx.EVT_IDLE, self.OnIdle)
|
|
|
|
|
|
def OnClose(self, event):
|
|
"""Event handler for closing."""
|
|
self.bufferClose()
|
|
# This isn't working the way I want, but I'll leave it for now.
|
|
#if self.sliceshell.waiting:
|
|
# if event.CanVeto():
|
|
# event.Veto(True)
|
|
#else:
|
|
# # TODO: Add check for saving
|
|
# self.SaveSettings()
|
|
# self.sliceshell.destroy()
|
|
# self.Destroy()
|
|
|
|
def OnAbout(self, event):
|
|
"""Display an About window."""
|
|
title = 'About PySliceShell'
|
|
text = 'PySliceShell %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.sliceshell.LoadSettings(self.config)
|
|
|
|
def SaveSettings(self, force=False):
|
|
if self.config is not None:
|
|
frame.ShellFrameMixin.SaveSettings(self,force)
|
|
if self.autoSaveSettings or force:
|
|
frame.Frame.SaveSettings(self, self.config)
|
|
self.sliceshell.SaveSettings(self.config)
|
|
|
|
def DoSaveSettings(self):
|
|
if self.config is not None:
|
|
self.SaveSettings(force=True)
|
|
self.config.Flush()
|
|
|
|
def OnEnableShellMode(self,event):
|
|
"""Change between Slices Mode and Shell Mode"""
|
|
frame.Frame.OnEnableShellMode(self,event)
|
|
self.sliceshell.ToggleShellMode(self.enableShellMode)
|
|
|
|
def OnHideFoldingMargin(self,event):
|
|
"""Change between Slices Mode and Shell Mode"""
|
|
frame.Frame.OnHideFoldingMargin(self,event)
|
|
self.sliceshell.ToggleFoldingMargin(self.hideFoldingMargin)
|
|
# Copied Straight from crustslices.py (update both with any changes...)
|
|
# Stolen Straight from editor.EditorFrame
|
|
# Modified a little... :)
|
|
# ||
|
|
# \/
|
|
def OnIdle(self, event):
|
|
"""Event handler for idle time."""
|
|
self._updateTitle()
|
|
event.Skip()
|
|
|
|
def _updateTitle(self):
|
|
"""Show current title information."""
|
|
title = self.GetTitle()
|
|
if self.bufferHasChanged():
|
|
if title.startswith('* '):
|
|
pass
|
|
else:
|
|
self.SetTitle('* ' + title)
|
|
else:
|
|
if title.startswith('* '):
|
|
self.SetTitle(title[2:])
|
|
|
|
def hasBuffer(self):
|
|
"""Return True if there is a current buffer."""
|
|
if self.buffer:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def bufferClose(self):
|
|
"""Close buffer."""
|
|
if self.buffer.hasChanged():
|
|
cancel = self.bufferSuggestSave()
|
|
if cancel:
|
|
#event.Veto()
|
|
return cancel
|
|
self.SaveSettings()
|
|
self.sliceshell.destroy()
|
|
self.bufferDestroy()
|
|
self.Destroy()
|
|
|
|
return False
|
|
|
|
def bufferCreate(self, filename=None):
|
|
"""Create new buffer."""
|
|
self.bufferDestroy()
|
|
buffer = Buffer()
|
|
self.panel = panel = wx.Panel(parent=self, id=-1)
|
|
panel.Bind (wx.EVT_ERASE_BACKGROUND, lambda x: x)
|
|
editor = Editor(parent=panel)
|
|
panel.editor = editor
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
sizer.Add(editor.window, 1, wx.EXPAND)
|
|
panel.SetSizer(sizer)
|
|
panel.SetAutoLayout(True)
|
|
sizer.Layout()
|
|
buffer.addEditor(editor)
|
|
buffer.open(filename)
|
|
self.setEditor(editor)
|
|
self.editor.setFocus()
|
|
self.SendSizeEvent()
|
|
|
|
|
|
def bufferDestroy(self):
|
|
"""Destroy the current buffer."""
|
|
if self.buffer:
|
|
self.editor = None
|
|
self.buffer = None
|
|
|
|
|
|
def bufferHasChanged(self):
|
|
"""Return True if buffer has changed since last save."""
|
|
if self.buffer:
|
|
return self.buffer.hasChanged()
|
|
else:
|
|
return False
|
|
|
|
def bufferNew(self):
|
|
"""Create new buffer."""
|
|
cancel = self.bufferSuggestSave()
|
|
if cancel:
|
|
return cancel
|
|
#self.bufferCreate()
|
|
self.clear()
|
|
self.SetTitle( 'PySlices')
|
|
self.sliceshell.NeedsCheckForSave=False
|
|
self.sliceshell.SetSavePoint()
|
|
self.buffer.doc = document.Document()
|
|
self.buffer.name = 'This shell'
|
|
self.buffer.modulename = self.buffer.doc.filebase
|
|
cancel = False
|
|
return cancel
|
|
|
|
def bufferOpen(self,file=None):
|
|
"""Open file in buffer."""
|
|
if self.bufferHasChanged():
|
|
cancel = self.bufferSuggestSave()
|
|
if cancel:
|
|
return cancel
|
|
|
|
if file==None:
|
|
file=wx.FileSelector('Open a PySlices File',
|
|
wildcard='*.pyslices',
|
|
default_path=self.currentDirectory)
|
|
if file!=None and file!=u'':
|
|
fid=open(file,'r')
|
|
self.sliceshell.LoadPySlicesFile(fid)
|
|
fid.close()
|
|
self.currentDirectory = os.path.split(file)[0]
|
|
self.SetTitle( os.path.split(file)[1] + ' - PySlices')
|
|
self.sliceshell.NeedsCheckForSave=False
|
|
self.sliceshell.SetSavePoint()
|
|
self.buffer.doc = document.Document(file)
|
|
self.buffer.name = self.buffer.doc.filename
|
|
self.buffer.modulename = self.buffer.doc.filebase
|
|
self.sliceshell.ScrollToLine(0)
|
|
return
|
|
|
|
## def bufferPrint(self):
|
|
## """Print buffer."""
|
|
## pass
|
|
|
|
## def bufferRevert(self):
|
|
## """Revert buffer to version of file on disk."""
|
|
## pass
|
|
|
|
# was self.buffer.save(self): # """Save buffer."""
|
|
def simpleSave(self,confirmed=False):
|
|
filepath = self.buffer.doc.filepath
|
|
self.buffer.confirmed = confirmed
|
|
if not filepath:
|
|
return # XXX Get filename
|
|
if not os.path.exists(filepath):
|
|
self.buffer.confirmed = True
|
|
if not self.buffer.confirmed:
|
|
self.buffer.confirmed = self.buffer.overwriteConfirm(filepath)
|
|
if self.buffer.confirmed:
|
|
try:
|
|
fid = open(filepath, 'wb')
|
|
self.sliceshell.SavePySlicesFile(fid)
|
|
finally:
|
|
if fid:
|
|
fid.close()
|
|
self.sliceshell.SetSavePoint()
|
|
self.SetTitle( os.path.split(filepath)[1] + ' - PySlices')
|
|
self.sliceshell.NeedsCheckForSave=False
|
|
|
|
def bufferSave(self):
|
|
"""Save buffer to its file."""
|
|
if self.buffer.doc.filepath:
|
|
# self.buffer.save()
|
|
self.simpleSave(confirmed=True)
|
|
cancel = False
|
|
else:
|
|
cancel = self.bufferSaveAs()
|
|
return cancel
|
|
|
|
def bufferSaveAs(self):
|
|
"""Save buffer to a new filename."""
|
|
if self.bufferHasChanged() and self.buffer.doc.filepath:
|
|
cancel = self.bufferSuggestSave()
|
|
if cancel:
|
|
return cancel
|
|
filedir = ''
|
|
if self.buffer and self.buffer.doc.filedir:
|
|
filedir = self.buffer.doc.filedir
|
|
result = editor.saveSingle(title='Save PySlices File',directory=filedir,
|
|
wildcard='PySlices Files (*.pyslices)|*.pyslices')
|
|
if result.path not in ['',None]:
|
|
if result.path[-9:]!=".pyslices":
|
|
result.path+=".pyslices"
|
|
|
|
self.buffer.doc = document.Document(result.path)
|
|
self.buffer.name = self.buffer.doc.filename
|
|
self.buffer.modulename = self.buffer.doc.filebase
|
|
self.simpleSave(confirmed=True) # allow overwrite
|
|
cancel = False
|
|
else:
|
|
cancel = True
|
|
return cancel
|
|
|
|
def bufferSaveACopy(self):
|
|
"""Save buffer to a new filename."""
|
|
filedir = ''
|
|
if self.buffer and self.buffer.doc.filedir:
|
|
filedir = self.buffer.doc.filedir
|
|
result = editor.saveSingle(title='Save a Copy of PySlices File',directory=filedir,
|
|
wildcard='PySlices Files (*.pyslices)|*.pyslices')
|
|
|
|
if result.path not in ['',None]:
|
|
if result.path[-9:]!=".pyslices":
|
|
result.path+=".pyslices"
|
|
|
|
# if not os.path.exists(result.path):
|
|
try: # Allow overwrite...
|
|
fid = open(result.path, 'wb')
|
|
self.sliceshell.SavePySlicesFile(fid)
|
|
finally:
|
|
if fid:
|
|
fid.close()
|
|
|
|
cancel = False
|
|
else:
|
|
cancel = True
|
|
return cancel
|
|
|
|
def bufferSuggestSave(self):
|
|
"""Suggest saving changes. Return True if user selected Cancel."""
|
|
result = editor.messageDialog(parent=None,
|
|
message='%s has changed.\n'
|
|
'Would you like to save it first'
|
|
'?' % self.buffer.name,
|
|
title='Save current file?',
|
|
style=wx.YES_NO | wx.CANCEL | wx.NO_DEFAULT |
|
|
wx.CENTRE | wx.ICON_QUESTION )
|
|
if result.positive:
|
|
cancel = self.bufferSave()
|
|
else:
|
|
cancel = result.text == 'Cancel'
|
|
return cancel
|
|
|
|
def updateNamespace(self):
|
|
"""Update the buffer namespace for autocompletion and calltips."""
|
|
if self.buffer.updateNamespace():
|
|
self.SetStatusText('Namespace updated')
|
|
else:
|
|
self.SetStatusText('Error executing, unable to update namespace')
|
|
|
|
|
|
|
|
# TODO : Update the help text
|
|
HELP_TEXT = """\
|
|
* Key bindings:
|
|
Home Go to the beginning of the line.
|
|
End Go to the end of the line.
|
|
Shift+Home Select to the beginning of the line.
|
|
Shift+End Select to the end of the line.
|
|
Ctrl-Home Jump to the beginning of the slice;
|
|
If already there, jump to beginning of previous slice
|
|
Ctrl-End Jump to the end of the slice;
|
|
If already there, jump to end of next slice
|
|
Ctrl-PageUp Jump to the beginning of the shell
|
|
Ctrl-PageDown Jump to the end of the shell
|
|
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+] Increase font size.
|
|
Ctrl+[ Decrease font size.
|
|
Ctrl+= Default font size.
|
|
|
|
Ctrl-Space Show Auto Completion.
|
|
Ctrl-Shift-Space Show Call Tip.
|
|
Ctrl-Shift-H Complete Text from History.
|
|
|
|
Ctrl+F Search
|
|
Ctrl+G Search next
|
|
F12 on/off "free-edit" mode
|
|
For testing only -- This does not preserve markers!
|
|
|
|
In "Slices Mode":
|
|
Return Insert new line
|
|
Enter (Numpad) Run command in slice
|
|
Ctrl+Return ""
|
|
Shift+Return ""
|
|
|
|
In "Shell Mode":
|
|
Return or Enter Insert a new line
|
|
Ctrl+Return ""
|
|
Shift+Return ""
|
|
2 Returns in a row Run command in slice
|
|
"""
|
|
|
|
class SlicesShellFacade:
|
|
"""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 = 'SlicesShell Interface'
|
|
|
|
def __init__(self, other):
|
|
"""Create a SlicesShellFacade instance."""
|
|
d = self.__dict__
|
|
d['other'] = other
|
|
d['helpText'] = HELP_TEXT
|
|
|
|
def help(self):
|
|
"""Display some useful information about how to use the slices shell."""
|
|
self.write(self.helpText,type='Output')
|
|
|
|
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
|
|
|
|
DISPLAY_TEXT="""
|
|
Author: %r
|
|
Py Version: %s
|
|
Python Version: %s
|
|
wxPython Version: %s
|
|
wxPython PlatformInfo: %s
|
|
Platform: %s"""
|
|
|
|
class SlicesShell(editwindow.EditWindow):
|
|
"""Notebook Shell based on StyledTextCtrl."""
|
|
|
|
name = 'SlicesShell'
|
|
|
|
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,
|
|
showPySlicesTutorial=True,enableShellMode=False,
|
|
hideFoldingMargin=False, *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()
|
|
self.id = self.GetId()
|
|
self.buffer.addEditor(self)
|
|
self.buffer.name='This shell'
|
|
self.NeedsCheckForSave=False
|
|
|
|
# Find out for which keycodes the interpreter will autocomplete.
|
|
self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
|
|
|
|
# Keep track of the last non-continuation prompt positions.
|
|
# Removed all references to these... solved a lot of odd bugs...
|
|
# self.promptPosStart = 0
|
|
# self.promptPosEnd = 0
|
|
|
|
# Keep track of multi-line commands.
|
|
self.more = False
|
|
|
|
# Use Margins to track input / output / slice number
|
|
self.margins = True
|
|
|
|
# For use with forced updates during long-running scripts
|
|
self.lastUpdate=None
|
|
|
|
if self.margins:
|
|
# margin 1 is already defined for the line numbers
|
|
# may eventually change it back to 0 like it ought to be...
|
|
self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
|
|
self.SetMarginType(3, stc.STC_MARGIN_SYMBOL)
|
|
self.SetMarginType(4, stc.STC_MARGIN_SYMBOL)
|
|
self.SetMarginWidth(2, 22)
|
|
self.SetMarginWidth(3, 22)
|
|
self.SetMarginWidth(4, 12)
|
|
self.SetMarginSensitive(2,True)
|
|
self.SetMarginSensitive(3,True)
|
|
self.SetMarginSensitive(4,True)
|
|
self.SetProperty("fold", "1")
|
|
# tabs are bad, use spaces
|
|
self.SetProperty("tab.timmy.whinge.level", "4")
|
|
self.SetMargins(0,0)
|
|
|
|
|
|
self.SetMarginMask(2, GROUPING_MASK | 1<<GROUPING_SELECTING )
|
|
# Display Markers -24...
|
|
self.SetMarginMask(3, IO_MASK | 1<<IO_SELECTING | 1<<READLINE_BG | 1<<INPUT_READLINE )
|
|
self.SetMarginMask(4, stc.STC_MASK_FOLDERS)
|
|
# Set the mask for the line markers, too...
|
|
self.SetMarginMask(1, 0)
|
|
|
|
if hideFoldingMargin:
|
|
self.SetMarginWidth(4, 0)
|
|
self.hideFoldingMargin=hideFoldingMargin
|
|
|
|
sel_color="#E0E0E0"
|
|
grouping_color="black"
|
|
input_color="red"
|
|
output_color="blue"
|
|
|
|
self.MarkerDefine(GROUPING_SELECTING, stc.STC_MARK_FULLRECT,
|
|
sel_color, sel_color)
|
|
self.MarkerDefine(IO_SELECTING, stc.STC_MARK_FULLRECT,
|
|
sel_color, sel_color)
|
|
|
|
self.MarkerDefine(GROUPING_START, stc.STC_MARK_BOXMINUS,
|
|
"white", grouping_color)
|
|
self.MarkerDefine(GROUPING_START_FOLDED, stc.STC_MARK_BOXPLUS,
|
|
"white", grouping_color)
|
|
self.MarkerDefine(GROUPING_MIDDLE, stc.STC_MARK_VLINE,
|
|
"white", grouping_color)
|
|
self.MarkerDefine(GROUPING_END, stc.STC_MARK_LCORNER,
|
|
"white", grouping_color)
|
|
|
|
self.MarkerDefine(READLINE_BG, stc.STC_MARK_FULLRECT,
|
|
wx.Colour(191,191,191), wx.Colour(191,191,191))
|
|
self.MarkerDefine(INPUT_READLINE, stc.STC_MARK_CHARACTER+ord('<'),
|
|
input_color, wx.Colour(191,191,191))
|
|
|
|
if enableShellMode:
|
|
self.mode='ShellMode'
|
|
else:
|
|
self.mode='SlicesMode'
|
|
|
|
self.execOnNextReturn=False
|
|
if self.mode=='SlicesMode':
|
|
self.MarkerDefine(INPUT_START, stc.STC_MARK_BOXMINUS,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_START_FOLDED, stc.STC_MARK_BOXPLUS,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_MIDDLE, stc.STC_MARK_VLINE,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_END, stc.STC_MARK_LCORNER,
|
|
"white", input_color)
|
|
elif self.mode=='ShellMode':
|
|
self.MarkerDefine(INPUT_START, stc.STC_MARK_ARROWS,
|
|
input_color, "white")
|
|
self.MarkerDefine(INPUT_START_FOLDED, stc.STC_MARK_BOXPLUS,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_MIDDLE, stc.STC_MARK_DOTDOTDOT,
|
|
input_color, "white")
|
|
self.MarkerDefine(INPUT_END, stc.STC_MARK_DOTDOTDOT,
|
|
input_color, "white")
|
|
|
|
self.MarkerDefine(OUTPUT_START, stc.STC_MARK_BOXMINUS,
|
|
"white", output_color)
|
|
self.MarkerDefine(OUTPUT_START_FOLDED, stc.STC_MARK_BOXPLUS,
|
|
"white", output_color)
|
|
self.MarkerDefine(OUTPUT_MIDDLE, stc.STC_MARK_VLINE,
|
|
"white", output_color)
|
|
self.MarkerDefine(OUTPUT_END, stc.STC_MARK_LCORNER,
|
|
"white", output_color)
|
|
|
|
self.MarkerDefine(OUTPUT_BG, stc.STC_MARK_BACKGROUND,
|
|
"white", wx.Colour(242,242,255))
|
|
|
|
# Markers for folding margin...
|
|
self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS,
|
|
"white", "#808080")
|
|
self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS,
|
|
"white", "#808080")
|
|
self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE,
|
|
"white", "#808080")
|
|
self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER,
|
|
"white", "#808080")
|
|
self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED,
|
|
"white", "#808080")
|
|
self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED,
|
|
"white", "#808080")
|
|
self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,
|
|
"white", "#808080")
|
|
|
|
# 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
|
|
|
|
#DNM -- disable these markers...
|
|
#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)
|
|
|
|
self.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
|
|
# TODO : Add a general functions to handle mouse clicks in the
|
|
# TODO: STC window whose sole purpose is to make it so
|
|
# TODO: that margin selection becomes unselected...
|
|
|
|
# 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)
|
|
|
|
outStart,outEnd,inStart,inMiddle,inEnd = [[],[],[],[],[]]
|
|
|
|
# Make "executed startup script move to the top..."
|
|
if showPySlicesTutorial:
|
|
self.write(tutorialText,'Output')
|
|
tutStart=5
|
|
testStart=16
|
|
outStart=[tutStart,testStart+3]
|
|
outEnd=[tutStart-1,testStart-1]
|
|
inStart=[testStart]
|
|
inMiddle=[testStart+1]
|
|
inEnd=[testStart+2]
|
|
|
|
# Assign some pseudo keywords to the interpreter's namespace.
|
|
self.setBuiltinKeywords()
|
|
|
|
# Add 'shell' to the interpreter's local namespace.
|
|
self.setLocalShell()
|
|
|
|
# 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()
|
|
|
|
outStart+=[0]
|
|
outEnd+=[self.GetLineCount()-2]
|
|
inStart+=[self.GetLineCount()-1]
|
|
# Set all the line markers to the proper initial states...
|
|
for i in range(self.GetLineCount()):
|
|
self.clearGroupingMarkers(i)
|
|
self.clearIOMarkers(i)
|
|
if i in outStart:
|
|
self.MarkerAdd(i,GROUPING_START)
|
|
self.MarkerAdd(i,OUTPUT_START)
|
|
# Background color is confusing for tutorial... skip it!
|
|
#self.MarkerAdd(i,OUTPUT_BG)
|
|
elif i in outEnd:
|
|
self.MarkerAdd(i,GROUPING_END)
|
|
self.MarkerAdd(i,OUTPUT_END)
|
|
#self.MarkerAdd(i,OUTPUT_BG)
|
|
elif i in inStart:
|
|
self.MarkerAdd(i,GROUPING_START)
|
|
self.MarkerAdd(i,INPUT_START)
|
|
elif i in inMiddle:
|
|
self.MarkerAdd(i,GROUPING_MIDDLE)
|
|
self.MarkerAdd(i,INPUT_MIDDLE)
|
|
elif i in inEnd:
|
|
self.MarkerAdd(i,GROUPING_END)
|
|
self.MarkerAdd(i,INPUT_END)
|
|
else:
|
|
self.MarkerAdd(i,GROUPING_MIDDLE)
|
|
self.MarkerAdd(i,OUTPUT_MIDDLE)
|
|
#self.MarkerAdd(i,OUTPUT_BG)
|
|
|
|
self.SliceSelection=False
|
|
self.runningSlice=None
|
|
|
|
## NOTE: See note at bottom of this file...
|
|
## #seb: File drag and drop
|
|
## self.SetDropTarget( FileDropTarget(self) )
|
|
|
|
#ADD UNDO
|
|
# Everywhere "ADD UNDO" appears, there is new code to handle markers
|
|
self.EmptyUndoBuffer()
|
|
|
|
wx.CallAfter(self.ScrollToLine, 0)
|
|
|
|
def ToggleShellMode(self,enableShellMode=None):
|
|
if enableShellMode==None:
|
|
if self.mode=='ShellMode': self.mode='SlicesMode'
|
|
elif self.mode=='SlicesMode': self.mode='ShellMode'
|
|
elif enableShellMode:
|
|
self.mode='ShellMode'
|
|
else:
|
|
self.mode='SlicesMode'
|
|
|
|
input_color="red"
|
|
if self.mode=='SlicesMode':
|
|
self.MarkerDefine(INPUT_START, stc.STC_MARK_BOXMINUS,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_START_FOLDED, stc.STC_MARK_BOXPLUS,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_MIDDLE, stc.STC_MARK_VLINE,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_END, stc.STC_MARK_LCORNER,
|
|
"white", input_color)
|
|
elif self.mode=='ShellMode':
|
|
self.MarkerDefine(INPUT_START, stc.STC_MARK_ARROWS,
|
|
input_color, "white")
|
|
self.MarkerDefine(INPUT_START_FOLDED, stc.STC_MARK_BOXPLUS,
|
|
"white", input_color)
|
|
self.MarkerDefine(INPUT_MIDDLE, stc.STC_MARK_DOTDOTDOT,
|
|
input_color, "white")
|
|
self.MarkerDefine(INPUT_END, stc.STC_MARK_DOTDOTDOT,
|
|
input_color, "white")
|
|
|
|
def ToggleFoldingMargin(self,hideFoldingMargin=None):
|
|
if hideFoldingMargin==None:
|
|
self.hideFoldingMargin = not self.hideFoldingMargin
|
|
else:
|
|
self.hideFoldingMargin = hideFoldingMargin
|
|
|
|
if self.hideFoldingMargin:
|
|
self.SetMarginWidth(4, 0)
|
|
else:
|
|
self.SetMarginWidth(4, 12)
|
|
|
|
def clearHistory(self):
|
|
self.history = []
|
|
self.historyIndex = -1
|
|
dispatcher.send(signal="SlicesShell.clearHistory")
|
|
|
|
|
|
def destroy(self):
|
|
del self.interp
|
|
|
|
def setFocus(self):
|
|
"""Set focus to the slices 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 slices shell."""
|
|
if text:
|
|
self.write(text,type='Output')
|
|
try:
|
|
if self.interp.introText:
|
|
if text and not text.endswith(os.linesep):
|
|
self.write(os.linesep,type='Output')
|
|
self.write(self.interp.introText,type='Output')
|
|
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 import PY3
|
|
if PY3:
|
|
import builtins
|
|
else:
|
|
import __builtin__
|
|
builtins = __builtin__
|
|
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.',
|
|
type='Output')
|
|
|
|
|
|
def setLocalShell(self):
|
|
"""Add 'slicesshell' to locals as reference to ShellFacade instance."""
|
|
self.interp.locals['slicesshell'] = SlicesShellFacade(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."""
|
|
text = DISPLAY_TEXT % \
|
|
(__author__, VERSION,
|
|
sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo),
|
|
sys.platform)
|
|
self.write(text.strip(),type='Output')
|
|
|
|
def BreakTextIntoCommands(self,text):
|
|
"""Turn a text block into multiple multi-line commands."""
|
|
|
|
#text = text.lstrip() # This should not be done!
|
|
text = self.fixLineEndings(text)
|
|
text = self.lstripPrompt(text)
|
|
text = text.replace(os.linesep, '\n')
|
|
lines = text.split('\n')
|
|
|
|
continuations = testForContinuations(text)
|
|
|
|
if len(continuations)==2: # Error case...
|
|
return None,continuations[1]
|
|
elif len(continuations)==4:
|
|
stringContinuationList,indentationBlockList, \
|
|
lineContinuationList,parentheticalContinuationList = continuations
|
|
|
|
commands = []
|
|
command = ''
|
|
for j,line in enumerate(lines):
|
|
lstrip = line.lstrip()
|
|
|
|
# Get the first alnum word:
|
|
first_word=[]
|
|
for i in lstrip:
|
|
if i.isalnum():
|
|
first_word.append(i)
|
|
else:
|
|
break
|
|
first_word = ''.join(first_word)
|
|
|
|
# Continue the command if it is blank, has indentation,
|
|
# starts with else, elif,except, or finally
|
|
# or previous line had a line continuation \
|
|
|
|
if j==0:
|
|
stringCont = False
|
|
lineCont=False
|
|
else:
|
|
stringCont = stringContinuationList[j-1]
|
|
lineCont = lineContinuationList[j-1]
|
|
|
|
if line.strip() == '' or lstrip != line or \
|
|
first_word in ['else','elif','except','finally'] or \
|
|
stringCont or lineCont:
|
|
# Multiline command. Add to the command.
|
|
command += '\n'
|
|
command += line
|
|
else:
|
|
# New command.
|
|
if command:
|
|
# Add the previous command to the list.
|
|
commands.append(command)
|
|
# Start a new command, which may be multiline.
|
|
command = line
|
|
|
|
commands.append(command)
|
|
|
|
return commands
|
|
|
|
def MarkerSet(self,line,markerBitsSet):
|
|
"""MarkerSet is the Set command for MarkerGet"""
|
|
markerBits=self.MarkerGet(line)
|
|
|
|
numMarkers=14
|
|
for i in range(numMarkers):
|
|
if (markerBitsSet & (1<<i)) and not (markerBits & (1<<i)):
|
|
self.MarkerAdd(line,i)
|
|
elif not (markerBitsSet & (1<<i)) and (markerBits & (1<<i)):
|
|
self.MarkerDelete(line,i)
|
|
def GetGroupingSlice(self,line_num=None):
|
|
"""Get the start/stop lines for the slice based on any line in the slice"""
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
num_lines=self.GetLineCount()
|
|
|
|
for i in range(line_num,-1,-1):
|
|
if self.MarkerGet(i) & (1<<GROUPING_START | 1<<GROUPING_START_FOLDED):
|
|
break
|
|
start_line=i
|
|
|
|
addition=0
|
|
|
|
for i in range(line_num,num_lines):
|
|
if self.MarkerGet(i) & 1<<GROUPING_END:
|
|
break
|
|
elif (i>line_num) and ( self.MarkerGet(i)
|
|
& (1<<GROUPING_START | 1<<GROUPING_START_FOLDED) ):
|
|
addition=-1
|
|
break # the solo case...
|
|
stop_line=i+addition
|
|
|
|
return start_line,stop_line
|
|
|
|
def GetIOSlice(self,line_num=None):
|
|
"""Get the start/stop lines for the slice based on any line in the slice"""
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
num_lines=self.GetLineCount()
|
|
|
|
for i in range(line_num,-1,-1):
|
|
if self.MarkerGet(i) & IO_ANY_START_MASK:
|
|
break
|
|
start_line=i
|
|
|
|
addition=0
|
|
|
|
for i in range(line_num,num_lines):
|
|
if self.MarkerGet(i) & IO_END_MASK:
|
|
break
|
|
elif (i>line_num) and (self.MarkerGet(i) & IO_ANY_START_MASK):
|
|
addition=-1
|
|
break # the solo case...
|
|
stop_line=i+addition
|
|
|
|
return start_line,stop_line
|
|
|
|
def FoldGroupingSlice(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
start,end=self.GetGroupingSlice(line_num)
|
|
self.HideLines(start+1,end)
|
|
marker=self.MarkerGet(start)
|
|
self.clearGroupingMarkers(start)
|
|
self.MarkerAdd(start,GROUPING_START_FOLDED)
|
|
self.clearIOMarkers(start)
|
|
if marker & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ):
|
|
self.MarkerAdd(start,INPUT_START_FOLDED)
|
|
elif marker & ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED ):
|
|
self.MarkerAdd(start,OUTPUT_START_FOLDED)
|
|
self.MarkerAdd(start,OUTPUT_BG)
|
|
else:
|
|
pass #print('Bad Markers!!!')
|
|
def FoldIOSlice(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
start,end=self.GetIOSlice(line_num)
|
|
self.HideLines(start+1,end)
|
|
marker=self.MarkerGet(start)
|
|
if (self.MarkerGet(start) & \
|
|
(1<<GROUPING_START | 1<<GROUPING_START_FOLDED )) and \
|
|
(self.MarkerGet(end) & 1<<GROUPING_END):
|
|
self.clearGroupingMarkers(start)
|
|
self.MarkerAdd(start,GROUPING_START_FOLDED)
|
|
self.clearIOMarkers(start)
|
|
if marker & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ):
|
|
self.MarkerAdd(start,INPUT_START_FOLDED)
|
|
elif marker & ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED ):
|
|
self.MarkerAdd(start,OUTPUT_START_FOLDED)
|
|
self.MarkerAdd(start,OUTPUT_BG)
|
|
else:
|
|
pass #print('Bad Markers!!!')
|
|
def UnFoldGroupingSlice(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
start,end=self.GetGroupingSlice(line_num)
|
|
self.ShowLines(start+1,end)
|
|
self.clearGroupingMarkers(start)
|
|
self.MarkerAdd(start,GROUPING_START)
|
|
for i in range(start,end):
|
|
marker=self.MarkerGet(i)
|
|
if marker & (1<<INPUT_START | 1<<INPUT_START_FOLDED):
|
|
self.clearIOMarkers(i)
|
|
self.MarkerAdd(i,INPUT_START)
|
|
elif marker & (1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED):
|
|
self.clearIOMarkers(i)
|
|
self.MarkerAdd(i,OUTPUT_START)
|
|
self.MarkerAdd(i,OUTPUT_BG)
|
|
|
|
def UnFoldIOSlice(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
start,end=self.GetIOSlice(line_num)
|
|
self.ShowLines(start+1,end)
|
|
marker=self.MarkerGet(start)
|
|
if (self.MarkerGet(start) & \
|
|
(1<<GROUPING_START | 1<<GROUPING_START_FOLDED )) and \
|
|
(self.MarkerGet(end) & 1<<GROUPING_END):
|
|
self.clearGroupingMarkers(start)
|
|
self.MarkerAdd(start,GROUPING_START)
|
|
self.clearIOMarkers(start)
|
|
if marker & 1<<INPUT_START_FOLDED:
|
|
self.MarkerAdd(start,INPUT_START)
|
|
elif marker & 1<<OUTPUT_START_FOLDED:
|
|
self.MarkerAdd(start,OUTPUT_START)
|
|
self.MarkerAdd(start,OUTPUT_BG)
|
|
|
|
def DeleteOutputSlicesAfter(self,line_num=None):
|
|
"""Delete all outputs after an input"""
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
num_lines=self.GetLineCount()
|
|
|
|
if self.MarkerGet(line_num) & OUTPUT_MASK:
|
|
#print('You can only run "DeleteOutputSlicesAfter" from an Input slice!')
|
|
return
|
|
|
|
startIn,endIn=self.GetIOSlice(line_num)
|
|
startGrouping,endGrouping=self.GetGroupingSlice(line_num)
|
|
|
|
if endIn<endGrouping:
|
|
self.SetSelection(self.PositionFromLine(endIn+1),
|
|
self.PositionFromLine(endGrouping+1))
|
|
self.ReplaceSelection('',sliceDeletion=True)
|
|
|
|
new_pos=self.GetLineEndPosition(line_num)
|
|
self.SetCurrentPos(new_pos)
|
|
self.SetSelection(new_pos,new_pos)
|
|
|
|
def SplitSlice(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
|
|
start_num,end_num=self.GetIOSlice(line_num)
|
|
|
|
if self.MarkerGet(line_num) & INPUT_MASK:
|
|
type='Input'
|
|
start=INPUT_START
|
|
end=INPUT_END
|
|
splitGrouping=True
|
|
elif self.MarkerGet(line_num) & OUTPUT_MASK:
|
|
type='Output'
|
|
start=OUTPUT_START
|
|
end=OUTPUT_END
|
|
splitGrouping=False
|
|
|
|
if start_num==end_num:
|
|
return # Can't split one line!
|
|
elif start_num==line_num:
|
|
self.clearIOMarkers(line_num+1)
|
|
self.MarkerAdd(line_num+1,start)
|
|
if type=='Output': self.MarkerAdd(line_num+1,OUTPUT_BG)
|
|
if splitGrouping:
|
|
self.clearGroupingMarkers(line_num+1)
|
|
self.MarkerAdd(line_num+1,GROUPING_START)
|
|
else:
|
|
self.clearIOMarkers(line_num)
|
|
self.MarkerAdd(line_num,start)
|
|
if type=='Output': self.MarkerAdd(line_num,OUTPUT_BG)
|
|
if splitGrouping:
|
|
self.clearGroupingMarkers(line_num)
|
|
self.MarkerAdd(line_num,GROUPING_START)
|
|
if line_num-1>start_num:
|
|
self.clearIOMarkers(line_num-1)
|
|
self.MarkerAdd(line_num-1,end)
|
|
if type=='Output': self.MarkerAdd(line_num-1,OUTPUT_BG)
|
|
if splitGrouping:
|
|
self.clearGroupingMarkers(line_num-1)
|
|
self.MarkerAdd(line_num-1,GROUPING_END)
|
|
|
|
def BackspaceWMarkers(self,force=False):
|
|
# Warning: This is not good at checking for bad markers!
|
|
c_before=self.GetCharAt(self.GetCurrentPos() - 1)
|
|
c_after=self.GetCharAt(self.GetCurrentPos())
|
|
|
|
if c_before==0:
|
|
# Disallow deleting the first line or it will destroy the markers...
|
|
return False
|
|
elif c_before in (ord('\n'),ord('\r')):
|
|
line_num=self.GetCurrentLine()
|
|
|
|
marker=self.MarkerGet(line_num)
|
|
marker_before=self.MarkerGet(line_num-1)
|
|
marker_after=self.MarkerGet(line_num+1)
|
|
if marker_before & ( 1<<GROUPING_END ) :
|
|
return False # Disallow deleting lines between slices...
|
|
elif marker & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
|
|
return False # Disallow deleting lines between slices...
|
|
else:
|
|
if marker_before & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
|
|
self.clearGroupingMarkers(line_num)
|
|
elif marker & ( 1<<GROUPING_END ) :
|
|
self.clearGroupingMarkers(line_num-1)
|
|
|
|
if (marker_before & 1<<INPUT_END) and force:
|
|
# Special case for use in processLine
|
|
self.clearIOMarkers(line_num)
|
|
elif marker_before & (1<<INPUT_END | 1<<OUTPUT_END):
|
|
return False # Disallow deleting lines between slices...
|
|
elif marker & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ) :
|
|
return False # Disallow deleting lines between slices...
|
|
else:
|
|
if marker_before & (1<<INPUT_START | 1<<INPUT_START_FOLDED |
|
|
1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED):
|
|
self.clearIOMarkers(line_num)
|
|
elif marker & ( 1<<INPUT_END | 1<<OUTPUT_END ) :
|
|
self.clearIOMarkers(line_num-1)
|
|
|
|
return True # If everything went well, return True and do the delete...
|
|
|
|
def ForwardDeleteWMarkers(self):
|
|
c_before=self.GetCharAt(self.GetCurrentPos() - 1)
|
|
c_after=self.GetCharAt(self.GetCurrentPos())
|
|
if c_after==0:
|
|
# Disallow deleting the first line or it will destroy the markers...
|
|
return False
|
|
elif c_after in (ord('\n'),ord('\r')):
|
|
line_num=self.GetCurrentLine()
|
|
|
|
marker=self.MarkerGet(line_num)
|
|
marker_before=self.MarkerGet(line_num-1)
|
|
marker_after=self.MarkerGet(line_num+1)
|
|
if marker & ( 1<<GROUPING_END ) :
|
|
return False # Disallow deleting lines between slices...
|
|
elif marker_after & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
|
|
return False # Disallow deleting lines between slices...
|
|
else:
|
|
if marker & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
|
|
self.clearGroupingMarkers(line_num+1)
|
|
elif marker_after & ( 1<<GROUPING_END ) :
|
|
self.clearGroupingMarkers(line_num)
|
|
|
|
if marker & ( 1<<INPUT_END | 1<<OUTPUT_END ) :
|
|
return False # Disallow deleting lines between slices...
|
|
elif marker_after & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ) :
|
|
return False # Disallow deleting lines between slices...
|
|
else:
|
|
if marker & (1<<INPUT_START | 1<<INPUT_START_FOLDED |
|
|
1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED) :
|
|
self.clearIOMarkers(line_num+1)
|
|
elif marker_after & ( 1<<INPUT_END | 1<<OUTPUT_END ) :
|
|
self.clearIOMarkers(line_num)
|
|
|
|
return True
|
|
|
|
def GetIOSelection(self):
|
|
started=False
|
|
start=0
|
|
end=self.GetLineCount()-1
|
|
type=None
|
|
for i in range(self.GetLineCount()):
|
|
if self.MarkerGet(i) & 1<<IO_SELECTING:
|
|
if started==False:
|
|
start=i
|
|
if self.MarkerGet(i) & INPUT_MASK:
|
|
type='input'
|
|
elif self.MarkerGet(i) & OUTPUT_MASK:
|
|
type='output'
|
|
else:
|
|
if self.MarkerGet(i) & INPUT_MASK:
|
|
if type=='output':
|
|
end=i-1
|
|
break
|
|
elif self.MarkerGet(i) & OUTPUT_MASK:
|
|
if type=='input':
|
|
end=i-1
|
|
break
|
|
started=True
|
|
elif started==True:
|
|
end=i-1
|
|
break
|
|
|
|
if started==False:
|
|
#print('No Selection!!')
|
|
self.SliceSelection=False
|
|
|
|
return start,end
|
|
|
|
def MergeAdjacentSlices(self):
|
|
"""This function merges all adjacent selected slices.\n""" + \
|
|
"""Right now, only IO Merging is allowed."""
|
|
started=False
|
|
start=0
|
|
end=self.GetLineCount()-1
|
|
type=None
|
|
for i in range(self.GetLineCount()):
|
|
if self.MarkerGet(i) & 1<<IO_SELECTING:
|
|
if started==False:
|
|
start=i
|
|
if self.MarkerGet(i) & INPUT_MASK:
|
|
type='input'
|
|
elif self.MarkerGet(i) & OUTPUT_MASK:
|
|
type='output'
|
|
else:
|
|
if self.MarkerGet(i) & INPUT_MASK:
|
|
if type=='output':
|
|
end=i-1
|
|
break
|
|
else:
|
|
self.clearIOMarkers(i)
|
|
self.clearGroupingMarkers(i)
|
|
self.MarkerAdd(i,INPUT_MIDDLE)
|
|
self.MarkerAdd(i,GROUPING_MIDDLE)
|
|
elif self.MarkerGet(i) & OUTPUT_MASK:
|
|
if type=='input':
|
|
end=i-1
|
|
break
|
|
else:
|
|
self.clearIOMarkers(i)
|
|
self.clearGroupingMarkers(i)
|
|
self.MarkerAdd(i,OUTPUT_MIDDLE)
|
|
self.MarkerAdd(i,OUTPUT_BG)
|
|
self.MarkerAdd(i,GROUPING_MIDDLE)
|
|
started=True
|
|
elif started==True:
|
|
end=i-1
|
|
break
|
|
|
|
if started and end!=start:
|
|
self.clearIOMarkers(end)
|
|
self.clearGroupingMarkers(end)
|
|
if type=='input':
|
|
self.MarkerAdd(end,INPUT_END)
|
|
if end+1<self.GetLineCount():
|
|
if self.MarkerGet(end+1) & OUTPUT_MASK:
|
|
self.MarkerAdd(end,GROUPING_MIDDLE)
|
|
else:
|
|
self.MarkerAdd(end,GROUPING_END)
|
|
else:
|
|
self.MarkerAdd(end,GROUPING_END)
|
|
else:
|
|
if self.MarkerGet(start) & 1<<GROUPING_END:
|
|
self.clearGroupingMarkers(start)
|
|
self.MarkerAdd(start,GROUPING_MIDDLE)
|
|
self.MarkerAdd(end,OUTPUT_END)
|
|
self.MarkerAdd(end,OUTPUT_BG)
|
|
self.MarkerAdd(end,GROUPING_END)
|
|
|
|
|
|
def SliceSelectionDelete(self):
|
|
"""Deletion of any selected and possibly discontinuous slices."""
|
|
if not self.SliceSelection:
|
|
return
|
|
|
|
# collect the line numbers to be deleted...
|
|
selectedSlices=[]
|
|
start,end=None,None
|
|
for i in range(self.GetLineCount()):
|
|
if self.MarkerGet(i) & (1<<GROUPING_SELECTING | 1<<IO_SELECTING):
|
|
if start==None:
|
|
start=i
|
|
end=i
|
|
elif start!=None:
|
|
selectedSlices.append([start,end])
|
|
start,end=None,None
|
|
if start!=None:
|
|
selectedSlices.append([start,end])
|
|
|
|
# Unselect everything
|
|
self.MarginUnselectAll()
|
|
self.SliceSelection=False
|
|
|
|
# Going in reverse, delete the selections, fixing the markers as we go...
|
|
for i in range(len(selectedSlices)-1,-1,-1):
|
|
self.SetSelection(self.PositionFromLine(selectedSlices[i][0]),
|
|
self.GetLineEndPosition(selectedSlices[i][1])+1)
|
|
|
|
markerNext = self.MarkerGet(selectedSlices[i][1]+1)
|
|
|
|
self.ReplaceSelection('',sliceDeletion=True)
|
|
|
|
cur_line=self.GetCurrentLine()
|
|
|
|
# If we've made a mess of the grouping markers, clean it up...
|
|
if ((self.MarkerGet(cur_line-1) & 1<<GROUPING_END) and
|
|
(self.MarkerGet(cur_line) & ( 1<<GROUPING_MIDDLE | 1<<GROUPING_END ) )):
|
|
self.clearGroupingMarkers(cur_line)
|
|
self.MarkerAdd(cur_line,GROUPING_START)
|
|
elif (( self.MarkerGet(cur_line-1) & 1<<GROUPING_MIDDLE ) and
|
|
( self.MarkerGet(cur_line) &
|
|
( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) )):
|
|
self.clearGroupingMarkers(cur_line-1)
|
|
self.MarkerAdd(cur_line-1,GROUPING_END)
|
|
|
|
if markerNext & 1<<OUTPUT_START:
|
|
self.clearIOMarkers(cur_line)
|
|
self.MarkerAdd(cur_line,OUTPUT_START)
|
|
self.MarkerAdd(cur_line,OUTPUT_BG)
|
|
elif markerNext & 1<<OUTPUT_START_FOLDED:
|
|
self.clearIOMarkers(cur_line)
|
|
self.MarkerAdd(cur_line,OUTPUT_START_FOLDED)
|
|
self.MarkerAdd(cur_line,OUTPUT_BG)
|
|
|
|
return
|
|
|
|
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 output slices
|
|
if not self.CanEdit():
|
|
return
|
|
key = event.GetKeyCode()
|
|
currpos = self.GetCurrentPos()
|
|
stoppos = self.PositionFromLine(self.GetCurrentLine())
|
|
|
|
# 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)
|
|
|
|
# write with undo wrapper...
|
|
cpos=self.GetCurrentPos()
|
|
s=chr(key)
|
|
#ADD UNDO
|
|
self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s),
|
|
forceNewAction=False)
|
|
self.write(s,type='Input')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
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) + '('
|
|
|
|
# write with undo wrapper...
|
|
cpos=self.GetCurrentPos()
|
|
s='('
|
|
self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s),
|
|
forceNewAction=True)
|
|
self.undoHistory[self.undoIndex]['allowsAppend']=True
|
|
self.write(s,type='Input')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
self.autoCallTipShow(command,
|
|
self.GetCurrentPos() == self.GetTextLength())
|
|
else:
|
|
# Allow the normal event handling to take place.
|
|
# Use undo wrapper
|
|
cpos=self.GetCurrentPos()
|
|
try:
|
|
s=chr(key)
|
|
self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s))
|
|
event.Skip()
|
|
self.UpdateUndoHistoryAfter()
|
|
except:
|
|
event.Skip()
|
|
|
|
def AutoCompActiveCallback(self):
|
|
numChars=self.GetTextLength()-self.TotalLengthForAutoCompActiveCallback
|
|
if numChars==0:
|
|
self.undoIndex-=1
|
|
del(self.undoHistory[-1])
|
|
else:
|
|
uH=self.undoHistory
|
|
uI=self.undoIndex
|
|
cpos=uH[uI]['posStart']
|
|
s=''.join([chr(self.GetCharAt(cpos+i)) for i in range(numChars)])
|
|
s.replace(os.linesep,'\n')
|
|
self.undoHistory[self.undoIndex]['charList'] = s
|
|
self.undoHistory[self.undoIndex]['posEnd'] = cpos + numChars
|
|
self.undoHistory[self.undoIndex]['numLines'] = len(s.split('\n'))
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
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():
|
|
if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
|
|
cpos=self.GetCurrentPos()
|
|
self.UpdateUndoHistoryBefore('insert','dummy',cpos,cpos+5,
|
|
forceNewAction=True)
|
|
self.undoHistory[self.undoIndex]['allowsAppend'] = True
|
|
self.TotalLengthForAutoCompActiveCallback=self.GetTextLength()
|
|
event.Skip()
|
|
wx.CallAfter(self.AutoCompActiveCallback)
|
|
if key in [wx.WXK_DELETE,wx.WXK_BACK]:
|
|
self.AutoCompCancel()
|
|
else:
|
|
event.Skip()
|
|
return
|
|
|
|
#DNM
|
|
# Prevent modification of output slices
|
|
controlDown = event.ControlDown()
|
|
altDown = event.AltDown()
|
|
shiftDown = event.ShiftDown()
|
|
currpos = self.GetCurrentPos()
|
|
endpos = self.GetTextLength()
|
|
selecting = self.GetSelectionStart() != self.GetSelectionEnd()
|
|
|
|
if key == wx.WXK_F12: #seb
|
|
if self.noteMode:
|
|
# self.promptPosStart not used anyway - or ?
|
|
## # We don't need to do this any more!
|
|
## 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
|
|
|
|
doLineBreak=False
|
|
doSubmitCommand=False
|
|
doPass=False
|
|
# Return is used to insert a line break.
|
|
# In Shell Mode, hit Return or Enter twice to submit a command
|
|
if ((not controlDown and not shiftDown and not altDown) and
|
|
key in [wx.WXK_RETURN,]):
|
|
if self.mode=='SlicesMode':
|
|
doLineBreak=True
|
|
elif self.mode=='ShellMode':
|
|
startLine,endLine = self.GetIOSlice()
|
|
startpos = self.PositionFromLine(startLine)
|
|
endpos = self.GetLineEndPosition(endLine)
|
|
command = self.GetTextRange(startpos, endpos)
|
|
strCont,indentBlock,lineCont,parenCont = testForContinuations(command,ignoreErrors=True)
|
|
|
|
lastLine = command.split('\n')[-1]
|
|
if lastLine.lstrip()=='': # all whitespace...
|
|
stillIndented=False
|
|
elif lastLine[0]==' ':
|
|
stillIndented=True
|
|
else:
|
|
stillIndented=False
|
|
|
|
if strCont[-1] or indentBlock[-1] or lineCont[-1] or \
|
|
parenCont[-1]:
|
|
doLineBreak=True
|
|
elif stillIndented:
|
|
new_pos=self.GetLineEndPosition(endLine)
|
|
self.SetCurrentPos(new_pos)
|
|
self.SetSelection(new_pos,new_pos)
|
|
doLineBreak=True
|
|
elif self.GetCurrentLine()!=endLine:
|
|
new_pos=self.GetLineEndPosition(endLine)
|
|
self.SetCurrentPos(new_pos)
|
|
self.SetSelection(new_pos,new_pos)
|
|
doPass = True
|
|
else:
|
|
doSubmitCommand=True
|
|
# Enter (Shift/Ctrl + Enter/Return) submits a command to the interpreter.
|
|
# In Shell Mode, hit Return or Enter twice to submit a command
|
|
elif ( key in [wx.WXK_NUMPAD_ENTER,] or
|
|
( (shiftDown or controlDown) and key in [wx.WXK_RETURN,
|
|
wx.WXK_NUMPAD_ENTER] ) ):
|
|
if self.mode=='SlicesMode':
|
|
doSubmitCommand=True
|
|
elif self.mode=='ShellMode':
|
|
doLineBreak=True
|
|
|
|
#Only relevant in ShellMode...
|
|
|
|
if doPass:
|
|
pass
|
|
elif doLineBreak or doSubmitCommand:
|
|
if self.CallTipActive():
|
|
self.CallTipCancel()
|
|
elif self.SliceSelection:
|
|
for i in range(self.GetLineCount()):
|
|
if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
|
|
self.DoMarginClick(i, 2, shiftDown, controlDown)
|
|
break
|
|
elif self.MarkerGet(i) & 1<<IO_SELECTING:
|
|
self.DoMarginClick(i, 3, shiftDown, controlDown)
|
|
break
|
|
elif doLineBreak:
|
|
self.insertLineBreak()
|
|
#Only relevant in ShellMode...
|
|
elif doSubmitCommand:
|
|
self.DeleteOutputSlicesAfter()
|
|
self.processLine()
|
|
|
|
# Let Ctrl-Alt-* get handled normally.
|
|
elif controlDown and altDown:
|
|
event.Skip()
|
|
|
|
# Clear the current, unexecuted command.
|
|
elif key == wx.WXK_ESCAPE:
|
|
if self.CallTipActive():
|
|
event.Skip()
|
|
# Clear the current command
|
|
elif key == wx.WXK_BACK and controlDown and shiftDown:
|
|
self.clearCommand()
|
|
|
|
# Increase font size.
|
|
elif controlDown and key in (ord(']'), wx.WXK_NUMPAD_ADD):
|
|
dispatcher.send(signal='FontIncrease')
|
|
|
|
# Decrease font size.
|
|
elif controlDown and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT):
|
|
dispatcher.send(signal='FontDecrease')
|
|
|
|
# Default font size.
|
|
elif controlDown and key in (ord('='), wx.WXK_NUMPAD_DIVIDE):
|
|
dispatcher.send(signal='FontDefault')
|
|
|
|
# Cut to the clipboard.
|
|
elif (controlDown and key in (ord('X'), ord('x'))) \
|
|
or (shiftDown and key == wx.WXK_DELETE):
|
|
self.Cut()
|
|
|
|
# Copy to the clipboard.
|
|
elif controlDown and not shiftDown \
|
|
and key in (ord('C'), ord('c'), wx.WXK_INSERT):
|
|
self.Copy()
|
|
|
|
# Copy to the clipboard, including prompts.
|
|
elif 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 controlDown and key == wx.WXK_HOME:
|
|
# Go to the beginning of the IO Slice
|
|
curLine = self.GetCurrentLine()
|
|
IOstart = self.GetIOSlice(curLine)[0]
|
|
home = self.PositionFromLine(IOstart)
|
|
if currpos == home and \
|
|
IOstart > 0:
|
|
home = self.PositionFromLine(self.GetIOSlice(curLine-1)[0])
|
|
self.SetCurrentPos(home)
|
|
if not selecting and not shiftDown:
|
|
self.SetAnchor(home)
|
|
self.EnsureCaretVisible()
|
|
|
|
elif controlDown and key == wx.WXK_END:
|
|
curLine = self.GetCurrentLine()
|
|
IOend = self.GetIOSlice(curLine)[1]
|
|
end = self.GetLineEndPosition(IOend)
|
|
if currpos == end and \
|
|
IOend < self.GetLineCount()-1:
|
|
end = self.GetLineEndPosition(self.GetIOSlice(curLine+1)[1])
|
|
self.SetCurrentPos(end)
|
|
if not selecting and not shiftDown:
|
|
self.SetAnchor(end)
|
|
self.EnsureCaretVisible()
|
|
|
|
elif controlDown and key == wx.WXK_PAGEUP:
|
|
pos=0
|
|
if currpos > pos:
|
|
self.SetCurrentPos(pos)
|
|
if not selecting and not shiftDown:
|
|
self.SetAnchor(pos)
|
|
self.EnsureCaretVisible()
|
|
|
|
elif controlDown and key == wx.WXK_PAGEDOWN:
|
|
pos = self.GetLineEndPosition(self.GetLineCount()-1)
|
|
if currpos < pos:
|
|
self.SetCurrentPos(pos)
|
|
if not selecting and not shiftDown:
|
|
self.SetAnchor(pos)
|
|
self.EnsureCaretVisible()
|
|
|
|
elif selecting and key not in NAVKEYS and not self.CanEdit():
|
|
pass
|
|
|
|
# Paste from the clipboard.
|
|
elif (controlDown and not shiftDown and key in (ord('V'), ord('v'))) \
|
|
or (shiftDown and not controlDown and key == wx.WXK_INSERT):
|
|
self.Paste()
|
|
|
|
# Paste from the clipboard, run commands.
|
|
elif controlDown and shiftDown and \
|
|
key in (ord('V'), ord('v')) and self.CanEdit():
|
|
self.PasteAndRun()
|
|
|
|
# Replace with the previous command from the history buffer.
|
|
elif (controlDown and not shiftDown and key == wx.WXK_UP) \
|
|
or (altDown and key in (ord('P'), ord('p'))) and self.CanEdit():
|
|
self.OnHistoryReplace(step=+1)
|
|
|
|
# Replace with the next command from the history buffer.
|
|
elif (controlDown and not shiftDown and key == wx.WXK_DOWN) \
|
|
or (altDown and key in (ord('N'), ord('n'))) and self.CanEdit():
|
|
self.OnHistoryReplace(step=-1)
|
|
|
|
# Insert the previous command from the history buffer.
|
|
elif (controlDown and shiftDown and key == wx.WXK_UP) and \
|
|
self.CanEdit():
|
|
self.OnHistoryInsert(step=+1)
|
|
|
|
# Insert the next command from the history buffer.
|
|
elif (controlDown and shiftDown and key == wx.WXK_DOWN) and \
|
|
self.CanEdit():
|
|
self.OnHistoryInsert(step=-1)
|
|
|
|
# Ctrl-Space shows Auto Completion
|
|
# Ctrl-Shift-Space shows CallTips
|
|
elif controlDown and key == wx.WXK_SPACE:
|
|
self.OnCallTipAutoCompleteManually(shiftDown)
|
|
|
|
# Ctrl+Shift+H is used to complete Text (from already typed words)
|
|
elif controlDown and shiftDown and key in [ord('H')]:
|
|
self.OnShowCompHistory()
|
|
|
|
# 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 self.SliceSelection:
|
|
self.SliceSelectionDelete()
|
|
wx.CallAfter(self.RestoreFirstMarker)
|
|
elif selecting and self.CanEdit():
|
|
self.ReplaceSelection('')
|
|
#event.Skip()
|
|
elif self.CanEdit():
|
|
doDelete=True
|
|
cur_line=self.GetCurrentLine()
|
|
if not cur_line==0 and \
|
|
self.GetCurrentPos()==self.PositionFromLine(cur_line):
|
|
if self.MarkerGet(cur_line-1) & OUTPUT_MASK:
|
|
doDelete=False
|
|
|
|
if doDelete:
|
|
cpos=self.GetCurrentPos()
|
|
s=chr(self.GetCharAt(cpos-1))
|
|
self.UpdateUndoHistoryBefore('delete',s,cpos-1,cpos)
|
|
if self.BackspaceWMarkers():
|
|
event.Skip()
|
|
|
|
wx.CallAfter(self.RestoreFirstMarker)
|
|
|
|
elif key == wx.WXK_DELETE:
|
|
if self.SliceSelection:
|
|
self.SliceSelectionDelete()
|
|
wx.CallAfter(self.RestoreFirstMarker)
|
|
elif selecting and self.CanEdit():
|
|
self.ReplaceSelection('')
|
|
#event.Skip()
|
|
elif self.CanEdit():
|
|
doDelete=True
|
|
cur_line=self.GetCurrentLine()
|
|
if not cur_line==self.GetLineCount()-1 and \
|
|
self.GetCurrentPos()==self.GetLineEndPosition(cur_line):
|
|
if self.MarkerGet(cur_line+1) & OUTPUT_MASK:
|
|
doDelete=False
|
|
|
|
if doDelete:
|
|
cpos=self.GetCurrentPos()
|
|
s=chr(self.GetCharAt(cpos))
|
|
self.UpdateUndoHistoryBefore('delete',s,cpos,cpos+1)
|
|
if self.ForwardDeleteWMarkers():
|
|
event.Skip()
|
|
|
|
wx.CallAfter(self.RestoreFirstMarker)
|
|
|
|
# Only allow these keys after the latest prompt.
|
|
elif key == wx.WXK_TAB and self.CanEdit():
|
|
# use the same mechanism as with autocmplete...
|
|
cpos=self.GetCurrentPos()
|
|
self.UpdateUndoHistoryBefore('insert','dummy',cpos,cpos+5,
|
|
forceNewAction=True)
|
|
self.undoHistory[self.undoIndex]['allowsAppend'] = True
|
|
self.TotalLengthForAutoCompActiveCallback=self.GetTextLength()
|
|
event.Skip()
|
|
wx.CallAfter(self.AutoCompActiveCallback)
|
|
|
|
# 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.
|
|
# Toggle Shell Mode / Slices Mode
|
|
elif controlDown and key in (ord('T'), ord('t')):
|
|
self.ToggleShellMode()
|
|
|
|
#Open and Save now work when using CrustSlicesFrames
|
|
elif controlDown and key in (ord('L'), ord('l')):
|
|
#print('Load it')
|
|
file=wx.FileSelector("Load File As New Slice")
|
|
if file!=u'':
|
|
fid=open(file,'r')
|
|
self.LoadPyFileAsSlice(fid)
|
|
fid.close()
|
|
|
|
elif controlDown and key in (ord('D'), ord('d')):
|
|
#Disallow line duplication in favor of divide slices
|
|
if self.MarkerGet(self.GetCurrentLine()) & INPUT_MASK:
|
|
#ADD UNDO
|
|
cpos=self.GetCurrentPos()
|
|
start,end = map(self.PositionFromLine,
|
|
self.GetGroupingSlice(self.LineFromPosition(cpos)))
|
|
self.UpdateUndoHistoryBefore('marker','',start,end,
|
|
forceNewAction=True)
|
|
self.SplitSlice()
|
|
# Turn off selecting
|
|
self.SetSelection(cpos,cpos)
|
|
self.ReplaceSelection('')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
elif controlDown and key in (ord('M'), ord('m')):
|
|
#ADD UNDO
|
|
if self.SliceSelection:
|
|
cpos=self.GetCurrentPos()
|
|
ioSel=self.GetIOSelection()
|
|
if self.SliceSelection:
|
|
start,end = map(self.PositionFromLine,ioSel)
|
|
self.UpdateUndoHistoryBefore('marker','',start,end,
|
|
forceNewAction=True)
|
|
self.MergeAdjacentSlices()
|
|
# Turn off selecting
|
|
self.SetSelection(cpos,cpos)
|
|
self.ReplaceSelection('')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
|
|
# Change arrow keys to allow margin behaviors...
|
|
elif self.SliceSelection and \
|
|
key in [wx.WXK_UP,wx.WXK_DOWN,wx.WXK_RIGHT,wx.WXK_LEFT]:
|
|
# TODO : This is useful, but not optimal!
|
|
if key==wx.WXK_UP:
|
|
for i in range(self.GetLineCount()):
|
|
if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
|
|
if i>0: #Grouping
|
|
self.DoMarginClick(i-1, 2, shiftDown, controlDown)
|
|
break
|
|
elif self.MarkerGet(i) & 1<<IO_SELECTING:
|
|
if i>0: #IO
|
|
self.DoMarginClick(i-1, 3, shiftDown, controlDown)
|
|
break
|
|
elif key==wx.WXK_DOWN:
|
|
for i in range(self.GetLineCount()-1,-1,-1):
|
|
if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
|
|
if i<self.GetLineCount()-1: #Grouping
|
|
self.DoMarginClick(i+1, 2, shiftDown, controlDown)
|
|
break
|
|
elif self.MarkerGet(i) & 1<<IO_SELECTING:
|
|
if i<self.GetLineCount()-1: #IO
|
|
self.DoMarginClick(i+1, 3, shiftDown, controlDown)
|
|
break
|
|
elif key==wx.WXK_RIGHT:
|
|
for i in range(self.GetLineCount()):
|
|
if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
|
|
self.DoMarginClick(i, 3, shiftDown, controlDown)
|
|
break
|
|
elif self.MarkerGet(i) & 1<<IO_SELECTING:
|
|
self.MarginUnselectAll()
|
|
# Go to the beginning of the IO Slice
|
|
self.SetCurrentPos(self.PositionFromLine(i))
|
|
if not selecting and not shiftDown:
|
|
self.SetAnchor(self.PositionFromLine(i))
|
|
self.EnsureCaretVisible()
|
|
break
|
|
elif key==wx.WXK_LEFT:
|
|
for i in range(self.GetLineCount()):
|
|
if self.MarkerGet(i) & 1<<GROUPING_SELECTING:
|
|
break
|
|
elif self.MarkerGet(i) & 1<<IO_SELECTING:
|
|
self.DoMarginClick(i, 2, shiftDown, controlDown)
|
|
break
|
|
# Basic navigation keys should work anywhere.
|
|
elif key in NAVKEYS:
|
|
event.Skip()
|
|
# Protect the readonly portion of the slices shell.
|
|
elif not self.CanEdit():
|
|
pass
|
|
else:
|
|
# Check to see if we're selecting
|
|
if self.GetSelectionEnd()>self.GetSelectionStart():
|
|
# Check to see if a normal input took place
|
|
if not controlDown and not altDown and key<256:
|
|
self.ReplaceSelection('') # This seems to work...
|
|
event.Skip()
|
|
|
|
if self.SliceSelection:
|
|
if key not in [wx.WXK_UP,wx.WXK_DOWN,wx.WXK_RIGHT,wx.WXK_LEFT,
|
|
wx.WXK_ALT,wx.WXK_COMMAND,wx.WXK_CONTROL,wx.WXK_SHIFT]:
|
|
self.MarginUnselectAll()
|
|
|
|
|
|
def MarginSelectAll(self):
|
|
num_lines=self.GetLineCount()
|
|
for i in range(num_lines):
|
|
self.MarkerAdd(i,GROUPING_SELECTING)
|
|
self.MarkerDelete(i,IO_SELECTING)
|
|
|
|
def MarginUnselectAll(self):
|
|
num_lines=self.GetLineCount()
|
|
for i in range(num_lines):
|
|
self.MarkerDelete(i,IO_SELECTING)
|
|
self.MarkerDelete(i,GROUPING_SELECTING)
|
|
self.SliceSelection=False
|
|
|
|
def DoMarginClick(self, lineClicked, margin, shiftDown, controlDown):
|
|
num_lines=self.GetLineCount()
|
|
|
|
if margin==1:
|
|
pass # these events are not sent right now...
|
|
if margin==2:
|
|
self.SliceSelection=True
|
|
start,end=self.GetGroupingSlice(lineClicked)
|
|
startPos=self.PositionFromLine(start)
|
|
self.SetCurrentPos(startPos)
|
|
self.SetSelection(startPos,startPos)
|
|
start_marker=self.MarkerGet(start)
|
|
if self.MarkerGet(lineClicked) & 1<<GROUPING_SELECTING:
|
|
toggle=self.MarkerDelete
|
|
if not shiftDown:
|
|
if start_marker & 1<<GROUPING_START:
|
|
self.FoldGroupingSlice(lineClicked)
|
|
elif start_marker & 1<<GROUPING_START_FOLDED:
|
|
self.UnFoldGroupingSlice(lineClicked)
|
|
else:
|
|
toggle=self.MarkerAdd
|
|
|
|
if not shiftDown:
|
|
self.MarginUnselectAll()
|
|
|
|
for i in range(start,end+1):
|
|
toggle(i,GROUPING_SELECTING)
|
|
elif margin==3:
|
|
self.SliceSelection=True
|
|
start,end=self.GetIOSlice(lineClicked)
|
|
startPos=self.PositionFromLine(start)
|
|
self.SetCurrentPos(startPos)
|
|
self.SetSelection(startPos,startPos)
|
|
start_marker=self.MarkerGet(start)
|
|
if self.MarkerGet(lineClicked) & 1<<IO_SELECTING:
|
|
toggle=self.MarkerDelete
|
|
if not shiftDown:
|
|
if start_marker & IO_START_MASK:
|
|
self.FoldIOSlice(lineClicked)
|
|
elif start_marker & IO_START_FOLDED_MASK:
|
|
self.UnFoldIOSlice(lineClicked)
|
|
else:
|
|
toggle=self.MarkerAdd
|
|
|
|
if not shiftDown:
|
|
self.MarginUnselectAll()
|
|
|
|
for i in range(start,end+1):
|
|
toggle(i,IO_SELECTING)
|
|
|
|
#print(start, end)
|
|
|
|
elif margin==4:
|
|
# TODO : Folding ??
|
|
if 1:#self.MarkerGet(lineClicked) & ( 1<<7 | 1<<8 ):
|
|
if shiftDown:
|
|
self.SetFoldExpanded(lineClicked, True)
|
|
self.Expand(lineClicked, True, True, 1)
|
|
elif controlDown:
|
|
if self.GetFoldExpanded(lineClicked):
|
|
self.SetFoldExpanded(lineClicked, False)
|
|
self.Expand(lineClicked, False, True, 0)
|
|
else:
|
|
self.SetFoldExpanded(lineClicked, True)
|
|
self.Expand(lineClicked, True, True, 100)
|
|
else:
|
|
self.ToggleFold(lineClicked)
|
|
else:
|
|
self.MarginUnselectAll()
|
|
if margin in [2,3]:
|
|
if toggle==self.MarkerDelete and not shiftDown:
|
|
self.SliceSelection=False
|
|
else:
|
|
self.SliceSelection=True
|
|
|
|
def OnMarginClick(self, evt):
|
|
|
|
# fold and unfold as neededNAVKEYS
|
|
lineClicked = self.LineFromPosition(evt.GetPosition())
|
|
self.DoMarginClick(lineClicked,evt.GetMargin(),evt.GetShift(),evt.GetControl())
|
|
evt.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 ReplaceSelection(self,text,sliceDeletion=False,*args,**kwds):
|
|
startIO,endIO=self.GetIOSlice()
|
|
startGrouping,endGrouping=self.GetGroupingSlice()
|
|
startSel = self.LineFromPosition(self.GetSelectionStart())
|
|
endSel = self.LineFromPosition(self.GetSelectionEnd())
|
|
|
|
#ADD UNDO
|
|
cpos=self.GetSelectionStart()
|
|
s=self.GetSelectedText()
|
|
if s!='':
|
|
self.UpdateUndoHistoryBefore('delete',s,cpos,cpos+len(s),
|
|
forceNewAction=True)
|
|
editwindow.EditWindow.ReplaceSelection(self,'',*args,**kwds)
|
|
if s!='' and not sliceDeletion:
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
if endSel-startSel>0 and not sliceDeletion:
|
|
if endSel==endIO and startIO!=self.GetCurrentLine():
|
|
self.clearIOMarkers()
|
|
self.MarkerAdd(self.GetCurrentLine(),INPUT_END)
|
|
|
|
if endSel==endGrouping and startGrouping!=self.GetCurrentLine():
|
|
self.clearGroupingMarkers()
|
|
self.MarkerAdd(self.GetCurrentLine(),GROUPING_END)
|
|
|
|
cpos=self.GetSelectionStart()
|
|
s=text
|
|
if s!='':
|
|
self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s),
|
|
forceNewAction=True)
|
|
self.write(text)
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
self.ensureSingleGroupingMarker()
|
|
self.ensureSingleIOMarker()
|
|
|
|
|
|
def clearCommand(self):
|
|
"""Delete the current, unexecuted command."""
|
|
if not self.CanEdit():
|
|
return
|
|
start,end=self.GetIOSlice()
|
|
startpos = self.PositionFromLine(start)
|
|
endpos = self.GetLineEndPosition(end)
|
|
self.SetSelection(startpos, endpos)
|
|
self.ReplaceSelection('')
|
|
self.more = False
|
|
|
|
def OnHistoryReplace(self, step):
|
|
"""Replace with the previous/next command from the history buffer."""
|
|
if not self.CanEdit():
|
|
return
|
|
self.clearCommand()
|
|
self.replaceFromHistory(step)
|
|
|
|
def replaceFromHistory(self, step):
|
|
"""Replace selection with command from the history buffer."""
|
|
if not self.CanEdit():
|
|
return
|
|
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)
|
|
|
|
# TODO: Fix Me!
|
|
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)
|
|
#print('history search', startpos, numCharsAfterCursor, searchText)
|
|
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 not self.CanEdit():
|
|
return
|
|
elif self.reader.isreading:
|
|
self.processLine()
|
|
return
|
|
|
|
|
|
# write with undo wrapper...
|
|
cpos=self.GetCurrentPos()
|
|
s=os.linesep
|
|
self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+1)
|
|
self.write(s,type='Input')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
self.more = True
|
|
self.prompt()
|
|
|
|
def processLine(self):
|
|
"""Process the line of text at which the user hit Enter or Shift+RETURN."""
|
|
# The user hit ENTER (Shift+RETURN) (Shift+ENTER) and we need to
|
|
# decide what to do. They could be sitting on any line in the slices shell.
|
|
thepos = self.GetCurrentPos()
|
|
cur_line = self.GetCurrentLine()
|
|
marker=self.MarkerGet(cur_line)
|
|
if marker & INPUT_MASK:
|
|
pass
|
|
elif marker & OUTPUT_MASK:
|
|
return
|
|
else:
|
|
pass #print('BLANK LINE!!')
|
|
|
|
startline,endline=self.GetIOSlice(cur_line)
|
|
|
|
if startline==0:
|
|
startpos=0
|
|
else:
|
|
startpos=self.PositionFromLine(startline)
|
|
|
|
endpos=self.GetLineEndPosition(endline)
|
|
|
|
# If they hit ENTER 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)
|
|
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,'Input')
|
|
self.MarkerSet(self.GetCurrentLine(),READLINE_BG)
|
|
self.MarkerSet(self.GetCurrentLine(),INPUT_READLINE)
|
|
else:
|
|
self.runningSlice = (startline,endline)
|
|
self.push(command,useMultiCommand=True)
|
|
#print('command: %s'%command)
|
|
wx.CallLater(1, self.EnsureCaretVisible)
|
|
self.runningSlice=None
|
|
|
|
skip=self.BackspaceWMarkers(force=True)
|
|
if skip:
|
|
self.DeleteBack()
|
|
|
|
if self.GetCurrentLine()==self.GetLineCount()-1:
|
|
self.write(os.linesep,type='Input')
|
|
cpos=self.GetCurrentLine()
|
|
if self.MarkerGet(cpos-1) & OUTPUT_MASK:
|
|
self.MarkerAdd(cpos-1,OUTPUT_BG)
|
|
self.SplitSlice()
|
|
else:
|
|
cur_line=self.GetCurrentLine()
|
|
new_pos=self.GetLineEndPosition(cur_line+1)
|
|
self.SetSelection(new_pos,new_pos)
|
|
self.SetCurrentPos(new_pos)
|
|
|
|
self.EmptyUndoBuffer()
|
|
self.NeedsCheckForSave=True
|
|
if self.hasSyntaxError:
|
|
pos=self.GetLineEndPosition(self.syntaxErrorRealLine)
|
|
self.SetCurrentPos(pos)
|
|
self.SetSelection(pos,pos)
|
|
|
|
# Not Used!!
|
|
def getMultilineCommand(self, rstrip=True):
|
|
"""Extract a multi-line command from the editor.
|
|
|
|
The command may not necessarily be valid Python syntax."""
|
|
# DNM
|
|
# XXX Need to extract real prompts here. Need to keep track of
|
|
# the prompt every time a command is issued.
|
|
text = self.GetCurLine()[0]
|
|
line = self.GetCurrentLine()
|
|
# Add Marker testing here...
|
|
while text == '' and line > 0: # Need to add markers handling...
|
|
line -= 1
|
|
self.GotoLine(line)
|
|
text = self.GetCurLine()[0]
|
|
if text=='':
|
|
line = self.GetCurrentLine()
|
|
self.GotoLine(line)
|
|
startpos = self.GetCurrentPos()
|
|
line += 1
|
|
self.GotoLine(line)
|
|
while self.GetCurLine()[0]=='':
|
|
line += 1
|
|
self.GotoLine(line)
|
|
stoppos = self.GetCurrentPos()
|
|
command = self.GetTextRange(startpos, stoppos)
|
|
command = command.replace(os.linesep, '\n')
|
|
command = command.rstrip()
|
|
command = command.replace('\n', os.linesep)
|
|
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)
|
|
# Change this -- Nothing has prompts!
|
|
#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,useMultiCommand=False):
|
|
"""Send command to the interpreter for execution."""
|
|
if not silent:
|
|
self.write(os.linesep,type='Output')
|
|
# TODO : What other magic might we insert here?
|
|
# TODO : Is there a good reason not to include magic?
|
|
if USE_MAGIC:
|
|
command=magic(command)
|
|
|
|
# Allows multi-component commands...
|
|
self.hasSyntaxError=False
|
|
if useMultiCommand:
|
|
result = self.BreakTextIntoCommands(command)
|
|
if result[0] == None:
|
|
commands=[command]
|
|
self.hasSyntaxError=True
|
|
syntaxErrorLine=result[1]+1
|
|
self.syntaxErrorRealLine = self.GetCurrentLine()+result[1]-len(command.split('\n'))
|
|
else:
|
|
commands=result
|
|
else:
|
|
commands=[command]
|
|
|
|
busy = wx.BusyCursor()
|
|
self.waiting = True
|
|
self.lastUpdate=None
|
|
|
|
for i in commands:
|
|
if self.hasSyntaxError:
|
|
lineno=syntaxErrorLine
|
|
offset=0 # not sure how to easily recover this information...
|
|
self.write(' File "<input>", line '+str(lineno)+'\n '+i.split('\n')[lineno-1]+'\n'+' '*offset+' ^\nSyntaxError: invalid syntax\n','Error')
|
|
else:
|
|
self.more = self.interp.push(i+'\n')
|
|
# (the \n stops many things from bouncing at the interpreter)
|
|
# I could do the following, but I don't really like it!
|
|
#if useMultiCommand:
|
|
# self.SplitSlice()
|
|
self.lastUpdate=None
|
|
|
|
if not silent:
|
|
self.MarkerAdd(self.GetIOSlice()[0],OUTPUT_BG)
|
|
|
|
self.waiting = False
|
|
del busy
|
|
if not self.more: # could loop-add to history, too, but I don't like it!
|
|
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="SlicesShell.addHistory", command=command)
|
|
|
|
def clearGroupingMarkers(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
self.MarkerDelete(line_num,GROUPING_START)
|
|
self.MarkerDelete(line_num,GROUPING_START_FOLDED)
|
|
self.MarkerDelete(line_num,GROUPING_MIDDLE)
|
|
self.MarkerDelete(line_num,GROUPING_END)
|
|
def clearIOMarkers(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
self.MarkerDelete(line_num,INPUT_START)
|
|
self.MarkerDelete(line_num,INPUT_START_FOLDED)
|
|
self.MarkerDelete(line_num,INPUT_MIDDLE)
|
|
self.MarkerDelete(line_num,INPUT_END)
|
|
self.MarkerDelete(line_num,OUTPUT_START)
|
|
self.MarkerDelete(line_num,OUTPUT_START_FOLDED)
|
|
self.MarkerDelete(line_num,OUTPUT_MIDDLE)
|
|
self.MarkerDelete(line_num,OUTPUT_END)
|
|
self.MarkerDelete(line_num,OUTPUT_BG)
|
|
self.MarkerDelete(line_num,READLINE_BG)
|
|
self.MarkerDelete(line_num,INPUT_READLINE)
|
|
def ensureSingleGroupingMarker(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
marker=self.MarkerGet(line_num)
|
|
if marker & 1<<GROUPING_START:
|
|
self.MarkerDelete(line_num,GROUPING_START_FOLDED)
|
|
self.MarkerDelete(line_num,GROUPING_MIDDLE)
|
|
self.MarkerDelete(line_num,GROUPING_END)
|
|
elif marker & 1<<GROUPING_START_FOLDED:
|
|
self.MarkerDelete(line_num,GROUPING_MIDDLE)
|
|
self.MarkerDelete(line_num,GROUPING_END)
|
|
elif marker & 1<<GROUPING_MIDDLE:
|
|
self.MarkerDelete(line_num,GROUPING_END)
|
|
elif marker & 1<<GROUPING_END:
|
|
pass
|
|
else:
|
|
#print('ERROR! NO GROUPING MARKERS!')
|
|
return 1 # Blank marker
|
|
|
|
return 0
|
|
|
|
def ensureSingleIOMarker(self,line_num=None):
|
|
if line_num==None:
|
|
line_num=self.GetCurrentLine()
|
|
marker=self.MarkerGet(line_num)
|
|
if marker & INPUT_MASK:
|
|
self.MarkerDelete(line_num,OUTPUT_START)
|
|
self.MarkerDelete(line_num,OUTPUT_START_FOLDED)
|
|
self.MarkerDelete(line_num,OUTPUT_MIDDLE)
|
|
self.MarkerDelete(line_num,OUTPUT_END)
|
|
self.MarkerDelete(line_num,OUTPUT_BG)
|
|
[start,start_folded] = [INPUT_START,INPUT_START_FOLDED]
|
|
[middle,end] = [INPUT_MIDDLE,INPUT_END]
|
|
elif marker & OUTPUT_MASK:
|
|
self.MarkerDelete(line_num,INPUT_START)
|
|
self.MarkerDelete(line_num,INPUT_START_FOLDED)
|
|
self.MarkerDelete(line_num,INPUT_MIDDLE)
|
|
self.MarkerDelete(line_num,INPUT_END)
|
|
[start,start_folded] = [OUTPUT_START,OUTPUT_START_FOLDED]
|
|
[middle,end] = [OUTPUT_MIDDLE,OUTPUT_END]
|
|
else:
|
|
#print('ERROR! NO IO MARKERS!')
|
|
return 1 # Blank marker
|
|
|
|
if marker & 1<<start:
|
|
self.MarkerDelete(line_num,start_folded)
|
|
self.MarkerDelete(line_num,middle)
|
|
self.MarkerDelete(line_num,end)
|
|
elif marker & 1<<start_folded:
|
|
self.MarkerDelete(line_num,middle)
|
|
self.MarkerDelete(line_num,end)
|
|
elif marker & 1<<middle:
|
|
self.MarkerDelete(line_num,end)
|
|
elif marker & 1<<end:
|
|
pass
|
|
|
|
return 0
|
|
|
|
def RestoreFirstMarker(self):
|
|
first_marker=self.MarkerGet(0)
|
|
self.clearGroupingMarkers(0)
|
|
self.clearIOMarkers(0)
|
|
|
|
if first_marker & 1<<GROUPING_START :
|
|
self.MarkerAdd(0,GROUPING_START)
|
|
elif first_marker & 1<<GROUPING_START_FOLDED :
|
|
self.MarkerAdd(0,GROUPING_START_FOLDED)
|
|
else:
|
|
self.MarkerAdd(0,GROUPING_START)
|
|
|
|
if first_marker & 1<<INPUT_START :
|
|
self.MarkerAdd(0,INPUT_START)
|
|
elif first_marker & 1<<INPUT_START_FOLDED :
|
|
self.MarkerAdd(0,INPUT_START_FOLDED)
|
|
elif first_marker & 1<<OUTPUT_START :
|
|
self.MarkerAdd(0,OUTPUT_START)
|
|
#self.MarkerAdd(0,OUTPUT_BG) # More harm than good??
|
|
elif first_marker & 1<<OUTPUT_START_FOLDED :
|
|
self.MarkerAdd(0,OUTPUT_START_FOLDED)
|
|
#self.MarkerAdd(0,OUTPUT_BG) # More harm than good??
|
|
else:
|
|
self.MarkerAdd(0,INPUT_START)
|
|
|
|
if self.doHistUpdate:
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
def IsAllowedPair(self,m1,m2):
|
|
"""This testing function ensures that two adjacent markers are valid"""
|
|
i_s = 1<<INPUT_START | 1<<INPUT_START_FOLDED
|
|
o_s = 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED
|
|
g_s = 1<<GROUPING_START | 1<<GROUPING_START_FOLDED
|
|
i_m,o_m,g_m = 1<<INPUT_MIDDLE, 1<<OUTPUT_MIDDLE, 1<<GROUPING_MIDDLE
|
|
i_e,o_e,g_e = 1<<INPUT_END, 1<<OUTPUT_END, 1<<GROUPING_END
|
|
|
|
if (m1 & i_s) and (m1 & g_s): #1
|
|
if (m2 & i_s) and (m2 & g_s): return True #1
|
|
elif (m2 & i_m) and (m2 & g_m): return True #2
|
|
elif (m2 & i_e) and (m2 & g_m): return True #3
|
|
elif (m2 & i_e) and (m2 & g_e): return True #4
|
|
elif (m2 & o_s) and (m2 & g_s): return False #5
|
|
elif (m2 & o_s) and (m2 & g_m): return True #6
|
|
elif (m2 & o_s) and (m2 & g_e): return True #7
|
|
elif (m2 & o_m) and (m2 & g_m): return False #8
|
|
elif (m2 & o_e) and (m2 & g_e): return False #9
|
|
else: return False
|
|
elif (m1 & i_m) and (m1 & g_m): #2
|
|
if (m2 & i_m) and (m2 & g_m): return True #2
|
|
elif (m2 & i_e) and (m2 & g_m): return True #3
|
|
elif (m2 & i_e) and (m2 & g_e): return True #4
|
|
else: return False
|
|
elif (m1 & i_e) and (m1 & g_m): #3
|
|
if (m2 & o_s) and (m2 & g_m): return True #6
|
|
elif (m2 & o_s) and (m2 & g_e): return True #7
|
|
else: return False
|
|
elif (m1 & i_e) and (m1 & g_e): #4
|
|
if (m2 & i_s) and (m2 & g_s): return True #1
|
|
elif (m2 & o_s) and (m2 & g_s): return True #5
|
|
else: return False
|
|
elif (m1 & o_s) and (m1 & g_s): #5
|
|
if (m2 & i_s) and (m2 & g_s): return True #1
|
|
elif (m2 & i_m) and (m2 & g_m): return False #2
|
|
elif (m2 & i_e) and (m2 & g_m): return False #3
|
|
elif (m2 & i_e) and (m2 & g_e): return False #4
|
|
elif (m2 & o_s) and (m2 & g_s): return True #5
|
|
elif (m2 & o_s) and (m2 & g_m): return False #6
|
|
elif (m2 & o_s) and (m2 & g_e): return False #7
|
|
elif (m2 & o_m) and (m2 & g_m): return True #8
|
|
elif (m2 & o_e) and (m2 & g_e): return True #9
|
|
else: return False
|
|
elif (m1 & o_s) and (m1 & g_m): #6
|
|
if (m2 & o_m) and (m2 & g_m): return True #8
|
|
elif (m2 & o_e) and (m2 & g_e): return True #9
|
|
else: return False
|
|
elif (m1 & o_s) and (m1 & g_e): #7
|
|
if (m2 & i_s) and (m2 & g_s): return True #1
|
|
elif (m2 & o_s) and (m2 & g_s): return True #5
|
|
else: return False
|
|
elif (m1 & o_m) and (m1 & g_m): #8
|
|
if (m2 & o_m) and (m2 & g_m): return True #8
|
|
elif (m2 & o_e) and (m2 & g_e): return True #9
|
|
else: return False
|
|
elif (m1 & o_e) and (m1 & g_e): #9
|
|
if (m2 & i_s) and (m2 & g_s): return True #1
|
|
elif (m2 & o_s) and (m2 & g_s): return True #5
|
|
else: return False
|
|
else:
|
|
return False
|
|
|
|
|
|
def CleanAllMarkers(self):
|
|
self.RestoreFirstMarker()
|
|
first_marker=self.MarkerGet(0)
|
|
last_line_num=self.GetLineCount()-1
|
|
|
|
for i in range(1,last_line_num):
|
|
self.ensureSingleGroupingMarker(i)
|
|
self.ensureSingleIOMarker(i)
|
|
|
|
previous_marker=self.MarkerGet(i-1)
|
|
marker=self.MarkerGet(i)
|
|
|
|
if not self.IsAllowedPair(previous_marker,marker):
|
|
pass # FIX MARKER!!
|
|
# FIX ME
|
|
|
|
def write(self, text,type='Input',silent=False):
|
|
"""Display text in the slices shell.
|
|
|
|
Replace line endings with OS-specific endings."""
|
|
text = self.fixLineEndings(text)
|
|
split=text.split(os.linesep)
|
|
self.AddText(text)
|
|
|
|
# This part handles all the marker stuff that accompanies
|
|
# adding or removing new lines of text...
|
|
# Get the total number of lines in the Document == last line number
|
|
last_line_num=self.GetLineCount()-1
|
|
# Get the line number we ended on in the write
|
|
end_line_num=self.GetCurrentLine()
|
|
# Get the number of returns we are using == number of lines we pasted -1
|
|
num_new_lines=text.count(os.linesep)
|
|
# So if num_new_lines==0, start_line_num and end_line_num are the same
|
|
start_line_num=end_line_num-num_new_lines+1
|
|
|
|
# This is a little unnecessary because there will always
|
|
# be a line before if we just inserted a newline!
|
|
if start_line_num == 0:
|
|
previous_line_num=None
|
|
else:
|
|
previous_line_num=start_line_num-1
|
|
|
|
#However, this is very important...
|
|
if end_line_num == last_line_num:
|
|
next_line_num=None
|
|
else:
|
|
next_line_num=end_line_num+1
|
|
|
|
if type=='Input':
|
|
start = INPUT_START
|
|
start_folded = INPUT_START_FOLDED
|
|
middle = INPUT_MIDDLE
|
|
end = INPUT_END
|
|
# preparation for more io types...
|
|
opposite_start_mask = 1<<OUTPUT_START
|
|
opposite_start_folded_mask = 1<<OUTPUT_START_FOLDED
|
|
opposite_middle_mask = 1<<OUTPUT_MIDDLE # To test for bad writes...
|
|
opposite_end_mask = 1<<OUTPUT_END # To test for bad writes...
|
|
elif type in ['Output','Error']:
|
|
#self.MarkerAdd(start_line_num,GROUPING_START_FOLDED)
|
|
start=OUTPUT_START
|
|
start_folded=OUTPUT_START_FOLDED
|
|
middle=OUTPUT_MIDDLE
|
|
end=OUTPUT_END
|
|
# preparation for more io types...
|
|
opposite_start_mask = 1<<INPUT_START
|
|
opposite_start_folded_mask = 1<<INPUT_START_FOLDED
|
|
opposite_middle_mask = 1<<INPUT_MIDDLE # To test for bad writes...
|
|
opposite_end_mask = 1<<INPUT_END # To test for bad writes...
|
|
|
|
if num_new_lines>0: #Do nothing if typing within a line...
|
|
# Update the Grouping Markers
|
|
# For the previous line and the start_line
|
|
# Test to make sure we can write ... but not here ...
|
|
# test this before we call write or before we add text...
|
|
# So we assume it already obeys the rules
|
|
|
|
badMarkers=False
|
|
fixIOEnd=True
|
|
|
|
if previous_line_num==None:
|
|
# This is an impossible case, here just for completeness...
|
|
self.clearGroupingMarkers(start_line_num)
|
|
self.MarkerAdd(start_line_num,GROUPING_START)
|
|
|
|
self.clearIOMarkers(start_line_num)
|
|
self.MarkerAdd(start_line_num,start)
|
|
if type in ['Output','Error']: self.MarkerAdd(start_line_num,OUTPUT_BG)
|
|
else:
|
|
previous_marker=self.MarkerGet(previous_line_num)
|
|
if previous_marker & opposite_middle_mask:
|
|
badMarkers=True
|
|
|
|
if next_line_num==None:
|
|
self.MarkerAdd(end_line_num,GROUPING_END)
|
|
self.MarkerAdd(end_line_num,end)
|
|
if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
|
|
fixEndMarkers=False
|
|
# May be overwritten below if start_line_num==end_line_num...
|
|
else:
|
|
next_marker=self.MarkerGet(next_line_num)
|
|
fixEndMarkers=True
|
|
if next_marker & ( opposite_middle_mask | opposite_end_mask ):
|
|
badMarkers=True
|
|
|
|
if not badMarkers:
|
|
# ensure previous_line only has one marker & turn end into middle
|
|
if previous_line_num!=None:
|
|
# Adjust previous line appropriately, ensure only one marker
|
|
# Only print errors if we are on input!
|
|
blank=False
|
|
blank=blank or self.ensureSingleGroupingMarker(previous_line_num)
|
|
blank=blank or self.ensureSingleIOMarker(previous_line_num)
|
|
|
|
if blank:
|
|
#if type=='Input' and not silent: print('BLANK LINE!' # BAD CASE)
|
|
pass
|
|
|
|
if previous_marker & 1<<GROUPING_END :
|
|
# Make GROUPING slice continue unless we hit
|
|
# an output end and are starting a new input...
|
|
if (previous_marker & OUTPUT_MASK) and type=='Input':
|
|
pass
|
|
else:
|
|
self.MarkerDelete(previous_line_num,GROUPING_END)
|
|
# ONLY CHANGING CASE
|
|
self.MarkerAdd(previous_line_num,GROUPING_MIDDLE)
|
|
|
|
if previous_marker & 1<<end :
|
|
self.MarkerDelete(previous_line_num,end)
|
|
self.MarkerAdd(previous_line_num,middle) # ONLY CHANGING CASE
|
|
if type in ['Output','Error']: self.MarkerAdd(previous_line_num,OUTPUT_BG)
|
|
elif previous_marker & opposite_middle_mask :
|
|
# BAD CASE
|
|
if type=='Input' and not silent:
|
|
#print('Should have been a bad marker!')
|
|
pass
|
|
|
|
# We can only add input to an input slice
|
|
# And can only add output to an output slice
|
|
|
|
if previous_marker & ( opposite_start_mask |
|
|
opposite_start_folded_mask |
|
|
opposite_end_mask ):
|
|
if type=='Input':
|
|
self.clearGroupingMarkers(start_line_num)
|
|
self.MarkerAdd(start_line_num,GROUPING_START)
|
|
if start_line_num==end_line_num:
|
|
fixEndMarkers=False
|
|
else:
|
|
if start_line_num==end_line_num:
|
|
fixIOEnd=False
|
|
self.clearIOMarkers(start_line_num)
|
|
self.MarkerAdd(start_line_num,start)
|
|
if type in ['Output','Error']: self.MarkerAdd(start_line_num,OUTPUT_BG)
|
|
else:
|
|
if next_line_num!=None:
|
|
self.clearGroupingMarkers(start_line_num)
|
|
self.clearIOMarkers(start_line_num)
|
|
self.MarkerAdd(start_line_num,GROUPING_MIDDLE)
|
|
self.MarkerAdd(start_line_num,middle)
|
|
if type in ['Output','Error']: self.MarkerAdd(start_line_num,OUTPUT_BG)
|
|
# This may be overwritten if start_line_num==end_line_num
|
|
|
|
# Take care of all the middle lines...
|
|
# Does nothing for only one line...
|
|
for i in range(start_line_num,end_line_num):
|
|
self.clearGroupingMarkers(i)
|
|
self.MarkerAdd(i,GROUPING_MIDDLE)
|
|
|
|
self.clearIOMarkers(i)
|
|
self.MarkerAdd(i,middle)
|
|
if type in ['Output','Error']: self.MarkerAdd(i,OUTPUT_BG)
|
|
|
|
if fixEndMarkers:
|
|
# Take care of the end_line if we haven't already done so...
|
|
blank=False
|
|
blank=blank or self.ensureSingleGroupingMarker(next_line_num)
|
|
blank=blank or self.ensureSingleIOMarker(next_line_num)
|
|
|
|
if blank:
|
|
if type=='Input' and not silent:
|
|
#print('BLANK LINE!' # BAD CASE)
|
|
pass
|
|
|
|
self.clearGroupingMarkers(end_line_num)
|
|
if fixIOEnd:
|
|
self.clearIOMarkers(end_line_num)
|
|
|
|
if next_marker & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ) :
|
|
self.MarkerAdd(end_line_num,GROUPING_END)
|
|
elif next_marker & ( 1<<GROUPING_MIDDLE | 1<<GROUPING_END ) :
|
|
self.MarkerAdd(end_line_num,GROUPING_MIDDLE)
|
|
|
|
if fixIOEnd:
|
|
if next_marker & ( 1<<start | 1<<start_folded ) :
|
|
self.MarkerAdd(end_line_num,end)
|
|
if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
|
|
elif next_marker & ( 1<<middle | 1<<end ) :
|
|
self.MarkerAdd(end_line_num,middle)
|
|
if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
|
|
elif next_marker & ( opposite_start_mask |
|
|
opposite_start_folded_mask ):
|
|
self.MarkerAdd(end_line_num,end)
|
|
if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
|
|
else:
|
|
self.MarkerAdd(end_line_num,start_folded)
|
|
if type in ['Output','Error']: self.MarkerAdd(end_line_num,OUTPUT_BG)
|
|
if type=='Input' and not silent:
|
|
#print('BAD MARKERS!')
|
|
pass
|
|
else:
|
|
if type=='Input' and not silent:
|
|
#print('BAD MARKERS!!!')
|
|
pass
|
|
|
|
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): # Autoindent added!!!
|
|
"""Display proper prompt for the context: ps1, ps2 or ps3.
|
|
|
|
If this is a continuation line, autoindent as necessary."""
|
|
# TODO : How much of this can I do away with now without prompts??
|
|
|
|
isreading = self.reader.isreading
|
|
|
|
skip = True
|
|
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,type='Input')
|
|
if not self.more:
|
|
# Not needed anymore! # self.promptPosStart = self.GetCurrentPos()
|
|
pass
|
|
if not skip:
|
|
self.write(prompt,type='Input')
|
|
if not self.more:
|
|
# Not needed anymore! # self.promptPosEnd = self.GetCurrentPos()
|
|
# Clear the undo history after running a command.
|
|
self.EmptyUndoBuffer()
|
|
|
|
#DNM/CP
|
|
# Autoindent magic
|
|
# Match the indent of the line above
|
|
# UNLESS the line above ends in a colon...then add four spaces
|
|
# (after valid keywords (if, else, etc...) only)
|
|
if self.more:
|
|
line_num=self.GetCurrentLine()
|
|
currentLine=self.GetLine(line_num)
|
|
previousLine=self.GetLine(line_num-1)
|
|
pstrip=previousLine.strip()
|
|
lstrip=previousLine.lstrip()
|
|
|
|
if pstrip == '':
|
|
# because it is all whitespace!
|
|
indent=previousLine.strip('\n').strip('\r')
|
|
else:
|
|
indent=previousLine[:(len(previousLine)-len(lstrip))]
|
|
if testForContinuations(previousLine,ignoreErrors=True)[1][0]:
|
|
indent+=' '*4
|
|
|
|
#ADD UNDO
|
|
cpos=self.GetCurrentPos()
|
|
s=indent
|
|
self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s))
|
|
self.write(s,type='Input')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
|
|
self.EnsureCaretVisible()
|
|
self.ScrollToColumn(0)
|
|
|
|
def readline(self):
|
|
"""Replacement for stdin.readline()."""
|
|
input = ''
|
|
reader = self.reader
|
|
reader.isreading = True
|
|
self.prompt()
|
|
|
|
# Ensure that we get a new line and that it's got an input marker...
|
|
# Also need to temporarily block any other action...
|
|
cLine = self.GetCurrentLine()
|
|
self.clearIOMarkers(cLine)
|
|
self.MarkerAdd(cLine,INPUT_START)
|
|
self.MarkerAdd(cLine,READLINE_BG)
|
|
self.MarkerAdd(cLine,INPUT_READLINE)
|
|
|
|
try:
|
|
while not reader.input:
|
|
wx.YieldIfNeeded()
|
|
input = reader.input
|
|
finally:
|
|
start,end = self.GetIOSlice()
|
|
start = self.runningSlice[1] + 1
|
|
for i in range(start,end+1):
|
|
self.clearIOMarkers(i)
|
|
self.clearGroupingMarkers(i)
|
|
self.MarkerAdd(i,OUTPUT_BG)
|
|
if i == start: self.MarkerAdd(i,OUTPUT_START)
|
|
elif i==end: self.MarkerAdd(i,OUTPUT_END)
|
|
else: self.MarkerAdd(i,OUTPUT_MIDDLE)
|
|
|
|
if i==end: self.MarkerAdd(i,GROUPING_END)
|
|
else: self.MarkerAdd(i,GROUPING_MIDDLE)
|
|
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,type='Output')
|
|
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 slices shell."""
|
|
self.ClearAll()
|
|
self.MarkerAdd(0,GROUPING_START)
|
|
self.MarkerAdd(0,INPUT_START)
|
|
|
|
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,type='Input')
|
|
self.push(command)
|
|
|
|
# TODO : Will have to fix this to handle other kinds of errors mentioned before...
|
|
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='SlicesShell.calltip', sender=self, calltip=tip)
|
|
if not self.autoCallTip and not forceCallTip:
|
|
return
|
|
startpos = self.GetCurrentPos()
|
|
if argspec and insertcalltip and self.callTipInsert:
|
|
# write with undo history...
|
|
cpos=self.GetCurrentPos()
|
|
s=argspec + ')'
|
|
self.UpdateUndoHistoryBefore('insert',s,cpos,cpos+len(s))
|
|
self.write(s,type='Input')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
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.PositionFromLine(self.GetIOSlice()[0])
|
|
|
|
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.PositionFromLine(self.GetIOSlice()[0])
|
|
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,type='Output')
|
|
# TODO : FLUSH?? How to make this update real-time...
|
|
|
|
def writeErr(self, text):
|
|
"""Replacement for stderr."""
|
|
self.write(text,type='Error')
|
|
|
|
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
|
|
|
|
# Take a spashot of the WHOLE grouping slice (or slices)
|
|
# The argument s is either what got added or deleted
|
|
def UpdateUndoHistoryBefore(self,actionType,s,posStart,posEnd,
|
|
forceNewAction=False):
|
|
uH=self.undoHistory
|
|
uI=self.undoIndex
|
|
|
|
s=s.replace(os.linesep,'\n')
|
|
startLine=self.LineFromPosition(posStart)
|
|
|
|
if actionType=='marker':
|
|
numLines = self.LineFromPosition(posEnd) - startLine
|
|
else:
|
|
numLines=s.count('\n')
|
|
|
|
makeNewAction=forceNewAction
|
|
|
|
if forceNewAction:
|
|
makeNewAction=True
|
|
elif self.undoIndex==-1:
|
|
makeNewAction=True
|
|
elif not uH[uI]['allowsAppend']:
|
|
makeNewAction=True
|
|
elif actionType!=uH[uI]['actionType']:
|
|
makeNewAction=True
|
|
elif actionType=='insert':
|
|
if posStart!=uH[uI]['posEnd']:
|
|
makeNewAction=True
|
|
else: # This is a continuation of the previous insert
|
|
uH[uI]['charList'] = uH[uI]['charList']+s
|
|
uH[uI]['posEnd'] = posEnd # posStart cannot move
|
|
uH[uI]['numLines'] = uH[uI]['numLines']+numLines
|
|
elif actionType=='delete':
|
|
# This is a forward continuation of the previous delete
|
|
if posStart==uH[uI]['posStart']:
|
|
uH[uI]['charList'] = uH[uI]['charList']+s
|
|
uH[uI]['posEnd'] = posEnd
|
|
uH[uI]['numLines'] = uH[uI]['numLines']+numLines
|
|
# This is a backward continuation of the previous delete
|
|
elif posEnd==uH[uI]['posStart']:
|
|
uH[uI]['charList'] = s+uH[uI]['charList']
|
|
uH[uI]['posStart'] = posStart
|
|
uH[uI]['startLine'] = startLine
|
|
uH[uI]['numLines'] = uH[uI]['numLines']+numLines
|
|
else:
|
|
makeNewAction=True
|
|
|
|
elif actionType=='marker':
|
|
makeNewAction=True
|
|
else:
|
|
pass #print('Unsupported Action Type!!')
|
|
|
|
if makeNewAction:
|
|
del(self.undoHistory[uI+1:]) # remove actions after undoIndex
|
|
|
|
uH.append({
|
|
'actionType' : actionType, # Action type ('insert','delete','marker')
|
|
'allowsAppend': not forceNewAction, # Can action be joined with others?
|
|
'charList' : s, # Character list
|
|
'posStart' : posStart, # Cursor poition at the start of the action
|
|
'posEnd' : posEnd, # Cursor position at the end of the action
|
|
'startLine' : startLine, # Start line number,
|
|
'numLines' : numLines, # Number of newlines involved
|
|
'mBStart' : None, # Starting line for markers BEFORE action
|
|
'mAStart' : None, # Starting line for markers AFTER action
|
|
'markersBefore' : None, # [markers BEFORE action]
|
|
'markersAfter' : None # [markers AFTER action]
|
|
})
|
|
|
|
self.undoIndex+=1
|
|
|
|
# Only update the before when starting a new action
|
|
start = startLine
|
|
if actionType=='insert':
|
|
end = start
|
|
else:
|
|
end = start + numLines
|
|
|
|
# Update Marker Info
|
|
newStart=self.GetGroupingSlice(start)[0]
|
|
newEnd=self.GetGroupingSlice(end)[1]
|
|
self.undoHistory[self.undoIndex]['markersBefore'] = \
|
|
[self.MarkerGet(i) for i in range(newStart,newEnd+1)]
|
|
self.undoHistory[self.undoIndex]['mBStart']=newStart
|
|
|
|
self.doHistUpdate=True
|
|
|
|
def UpdateUndoHistoryAfter(self): # s is either what got added or deleted
|
|
start = self.undoHistory[self.undoIndex]['startLine']
|
|
if self.undoHistory[self.undoIndex]['actionType']=='delete':
|
|
end = start
|
|
else:
|
|
end = start + self.undoHistory[self.undoIndex]['numLines']
|
|
|
|
newStart=min(self.GetGroupingSlice(start)[0]-1, 0)
|
|
newEnd=max(self.GetGroupingSlice(end)[1]+1, self.GetLineCount()-1)
|
|
self.undoHistory[self.undoIndex]['markersAfter'] = \
|
|
[self.MarkerGet(i) for i in range(newStart,newEnd+1)]
|
|
self.undoHistory[self.undoIndex]['mAStart']=newStart
|
|
|
|
self.doHistUpdate=False
|
|
|
|
def Undo(self):
|
|
#ADD UNDO
|
|
#Skip undo if there are no actions...
|
|
if self.undoIndex==-1:
|
|
return
|
|
|
|
uHI=self.undoHistory[self.undoIndex]
|
|
|
|
if uHI['actionType'] in ['insert','delete']:
|
|
# This will perform the opposite of the action given
|
|
editwindow.EditWindow.Undo(self)
|
|
elif uHI['actionType']=='marker': # No text changed, don't pass to STC
|
|
pass
|
|
else:
|
|
#print('Unsupported actionType in undoHistory!!')
|
|
return
|
|
|
|
numLines=len(uHI['markersBefore'])
|
|
for i in range(numLines):
|
|
self.MarkerSet( uHI['mBStart']+i , uHI['markersBefore'][i] )
|
|
|
|
self.undoIndex-=1
|
|
|
|
def Redo(self):
|
|
#ADD UNDO
|
|
# First check to see if there are any redo operations available
|
|
# Note that for redo, undoIndex=-1 is a valid input
|
|
if self.undoIndex >= len(self.undoHistory)-1:
|
|
return
|
|
self.undoIndex+=1
|
|
uHI=self.undoHistory[self.undoIndex]
|
|
|
|
if uHI['actionType'] in ['insert','delete']:
|
|
# This will re-perform the given action
|
|
editwindow.EditWindow.Redo(self)
|
|
elif uHI['actionType']=='marker': # No text changed, don't pass to STC
|
|
pass
|
|
else:
|
|
#print('Unsupported actionType in undoHistory!!')
|
|
return
|
|
|
|
numLines=len(uHI['markersAfter'])
|
|
for i in range(numLines):
|
|
self.MarkerSet( uHI['mAStart']+i , uHI['markersAfter'][i] )
|
|
|
|
def EmptyUndoBuffer(self):
|
|
editwindow.EditWindow.EmptyUndoBuffer(self)
|
|
self.undoIndex=-1
|
|
self.undoHistory=[]
|
|
self.doHistUpdate=False
|
|
|
|
def CanCut(self):
|
|
return self.CanEdit() and \
|
|
(self.GetSelectionStart() != self.GetSelectionEnd())
|
|
|
|
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."""
|
|
marker=self.MarkerGet(self.GetCurrentLine())
|
|
|
|
if marker & OUTPUT_MASK:
|
|
return False
|
|
elif marker & INPUT_MASK:
|
|
if self.reader.isreading and not \
|
|
(self.MarkerGet(self.GetCurrentLine()) & 1<<INPUT_READLINE ):
|
|
return False
|
|
start,end=self.GetIOSlice()
|
|
sliceStartPos=self.PositionFromLine(start)
|
|
sliceEndPos=self.GetLineEndPosition(end)
|
|
"""Return true if text is selected and can be cut."""
|
|
if self.GetSelectionStart() == self.GetSelectionEnd():
|
|
return True
|
|
elif self.GetSelectionStart() != self.GetSelectionEnd() \
|
|
and self.GetSelectionStart() >= sliceStartPos \
|
|
and self.GetSelectionEnd() >= sliceStartPos \
|
|
and self.GetSelectionStart() <= sliceEndPos \
|
|
and self.GetSelectionEnd() <= sliceEndPos:
|
|
return True
|
|
return False
|
|
|
|
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."""
|
|
|
|
#ADD UNDO
|
|
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)
|
|
# TODO : This is still useful... Add it back other places?
|
|
command = command.replace(os.linesep + ps2, '\n')
|
|
command = command.replace(os.linesep, '\n')
|
|
#DNM--Don't use '... '
|
|
command = command.replace('\n', os.linesep)# + ps2)
|
|
|
|
cpos=self.GetCurrentPos()
|
|
s=command
|
|
self.UpdateUndoHistoryBefore('insert', s, cpos,
|
|
cpos+len(s), forceNewAction=True)
|
|
self.write(s,type='Input')
|
|
self.UpdateUndoHistoryAfter()
|
|
|
|
# Makes paste -> type -> undo consistent with other STC apps
|
|
self.ReplaceSelection('')
|
|
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."""
|
|
start,end=self.GetIOSlice()
|
|
startpos=self.PositionFromLine(start)
|
|
endpos=self.GetLineEndPosition(end)
|
|
|
|
self.SetCurrentPos(endpos)
|
|
self.SetSelection(startpos, endpos)
|
|
self.ReplaceSelection('')
|
|
|
|
hasSyntaxError=False
|
|
result = self.BreakTextIntoCommands(command)
|
|
if result[0] == None:
|
|
commands=[command]
|
|
hasSyntaxError=True
|
|
else:
|
|
commands=result
|
|
|
|
for command in commands:
|
|
command = command.replace('\n', os.linesep)
|
|
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 slices 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())
|
|
|
|
def LoadPySlicesFile(self,fid):
|
|
invalidFileString = 'Not a valid input format'
|
|
lineCount=0
|
|
groupingStartLines=[0]
|
|
ioStartLines=[0]
|
|
ioStartTypes=[]
|
|
removeComment=False
|
|
|
|
# Read the initial three (or four) lines that have version and marker information
|
|
line=fid.readline()
|
|
if line == usrBinEnvPythonText:
|
|
line=fid.readline() # Add the option to place #!/usr/bin/env python at the top
|
|
if line not in pyslicesFormatHeaderText: print(invalidFileString); return
|
|
line=fid.readline()
|
|
if line != groupingStartText: print(invalidFileString); return
|
|
line=fid.readline()
|
|
if line == inputStartText: ioStartTypes.append('input');removeComment=False
|
|
elif line == outputStartText: ioStartTypes.append('output');removeComment=True
|
|
else: print(invalidFileString); return
|
|
|
|
self.ClearAll()
|
|
|
|
# Write the file's text to the text area
|
|
# Capture Marker information to
|
|
for i in fid:
|
|
if i==groupingStartText:
|
|
groupingStartLines.append(lineCount)
|
|
elif i==inputStartText:
|
|
ioStartLines.append(lineCount)
|
|
ioStartTypes.append('input')
|
|
removeComment=False
|
|
elif i==outputStartText:
|
|
ioStartLines.append(lineCount)
|
|
ioStartTypes.append('output')
|
|
removeComment=True
|
|
else:
|
|
if removeComment: w=i[1:].replace(os.linesep,'\n')
|
|
else: w=i.replace(os.linesep,'\n')
|
|
self.write(w,'Input',silent=True)
|
|
lineCount+=1
|
|
|
|
if w[-1]=='\n':
|
|
lineCount+=1
|
|
|
|
for i in range(lineCount+1):
|
|
self.clearGroupingMarkers(i)
|
|
self.clearIOMarkers(i)
|
|
|
|
doMiddle=False
|
|
doEnd=False
|
|
if groupingStartLines!=[]:
|
|
if i == groupingStartLines[0]:
|
|
self.MarkerAdd(i,GROUPING_START)
|
|
del groupingStartLines[0]
|
|
elif i+1 == groupingStartLines[0]:
|
|
doEnd=True
|
|
else:
|
|
doMiddle=True
|
|
elif i==lineCount-1:
|
|
doEnd=True
|
|
else:
|
|
doMiddle=True
|
|
|
|
if doMiddle:
|
|
self.MarkerAdd(i,GROUPING_MIDDLE)
|
|
elif doEnd:
|
|
self.MarkerAdd(i,GROUPING_END)
|
|
|
|
doMiddle=False
|
|
doEnd=False
|
|
if ioStartLines!=[]:
|
|
if i == ioStartLines[0]:
|
|
# Delete the old ioStartTypes (keep the current copy for later use)
|
|
if i>0: del ioStartTypes[0]
|
|
|
|
if ioStartTypes[0]=='input':
|
|
self.MarkerAdd(i,INPUT_START)
|
|
elif ioStartTypes[0]=='output':
|
|
self.MarkerAdd(i,OUTPUT_START)
|
|
self.MarkerAdd(i,OUTPUT_BG)
|
|
else:
|
|
#print('Invalid Type!')
|
|
return
|
|
|
|
# Only delete markers we are totally finished with...
|
|
# Keep one more "StartTypes" than "StartLines"
|
|
del ioStartLines[0]
|
|
elif i+1 == ioStartLines[0]:
|
|
doEnd=True
|
|
else:
|
|
doMiddle=True
|
|
elif i==lineCount-1:
|
|
doEnd=True
|
|
else:
|
|
doMiddle=True
|
|
|
|
if doMiddle:
|
|
if ioStartTypes[0]=='input':
|
|
self.MarkerAdd(i,INPUT_MIDDLE)
|
|
elif ioStartTypes[0]=='output':
|
|
self.MarkerAdd(i,OUTPUT_MIDDLE)
|
|
self.MarkerAdd(i,OUTPUT_BG)
|
|
else:
|
|
#print('Invalid Type!')
|
|
return
|
|
elif doEnd:
|
|
if ioStartTypes[0]=='input':
|
|
self.MarkerAdd(i,INPUT_END)
|
|
elif ioStartTypes[0]=='output':
|
|
self.MarkerAdd(i,OUTPUT_END)
|
|
self.MarkerAdd(i,OUTPUT_BG)
|
|
else:
|
|
#print('Invalid Type!')
|
|
return
|
|
|
|
self.EmptyUndoBuffer() # maybe not?
|
|
|
|
|
|
def SavePySlicesFile(self,fid):
|
|
addComment=False
|
|
fid.write(usrBinEnvPythonText.replace('\n',os.linesep))
|
|
fid.write(pyslicesFormatHeaderText[-1].replace('\n',os.linesep))
|
|
for i in range(self.GetLineCount()):
|
|
markers=self.MarkerGet(i)
|
|
if markers & ( 1<<GROUPING_START | 1<<GROUPING_START_FOLDED ):
|
|
fid.write(groupingStartText.replace('\n',os.linesep))
|
|
if markers & ( 1<<INPUT_START | 1<<INPUT_START_FOLDED ):
|
|
fid.write(inputStartText.replace('\n',os.linesep))
|
|
addComment=False
|
|
if markers & ( 1<<OUTPUT_START | 1<<OUTPUT_START_FOLDED ):
|
|
fid.write(outputStartText.replace('\n',os.linesep))
|
|
addComment=True
|
|
if addComment: fid.write('#')
|
|
fid.write(self.GetLine(i).replace('\n',os.linesep))
|
|
|
|
# FIX ME!!
|
|
def LoadPyFileAsSlice(self,fid):
|
|
curpos=self.GetCurrentPos()
|
|
start,end = self.GetGroupingSlice()
|
|
|
|
endpos=self.GetLineEndPosition(end)
|
|
self.SetCurrentPos(endpos)
|
|
self.SetSelection(endpos, endpos)
|
|
|
|
text='\n'+fid.read()
|
|
self.write(text,'Input')
|
|
newpos=self.GetCurrentPos()
|
|
|
|
self.SetCurrentPos(curpos)
|
|
self.SetSelection(curpos,curpos)
|
|
self.SplitSlice()
|
|
#self.SetCurrentPos(newpos)
|
|
#self.SetSelection(newpos,newpos)
|
|
|
|
def hasChanged(self):
|
|
"""Return True if contents have changed."""
|
|
return self.GetModify() or self.NeedsCheckForSave
|
|
|
|
|
|
|
|
## 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, sliceshell):
|
|
## wx.DropTarget.__init__(self)
|
|
## self.sliceshell = sliceshell
|
|
## 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.sliceshell.AppendText(txt)
|
|
## pos = self.sliceshell.GetCurrentPos()
|
|
## self.sliceshell.SetCurrentPos( pos )
|
|
## self.sliceshell.SetSelection( pos, pos )
|
|
|
|
## return result
|