dabo Commit
Revision 6036
Date: 2010-09-26 17:35:57 -0700 (Sun, 26 Sep 2010)
Author: Ed
Trac: http://trac.dabodev.com/changeset/6036
Changed:
U trunk/dabo/ui/uiwx/dEditor.py
U trunk/dabo/ui/uiwx/dShell.py
Log:
Added a code editor to the dShell class. This is a first pass, but I have been
using it in my personal work for several weeks, and it's finally polished
enough to commit.
Frequently when working in dShell, I need to run more than a single line of
code, which, while possible, is not very easy to do in the shell. Editing
across multiple lines is difficult, and you can only run one block of code at a
time.
Now you can switch to the code editing page, write and edit the code you want
to run, then press Ctrl-Enter (or click the 'Run' button) to push that code
into the shell and execute it as if you had typed it there. The code editor
maintains a history of code you have executed, which you can access via
Ctrl-Up/Ctrl-Down.
I also improved some of the code in dEditor, and added some additional handling
so that the code editor in dShell can pick up on the local namespace of the
shell.
Diff:
Modified: trunk/dabo/ui/uiwx/dEditor.py
===================================================================
--- trunk/dabo/ui/uiwx/dEditor.py 2010-09-26 13:16:04 UTC (rev 6035)
+++ trunk/dabo/ui/uiwx/dEditor.py 2010-09-27 00:35:57 UTC (rev 6036)
@@ -305,6 +305,8 @@
self.Bind(stc.EVT_STC_MODIFIED, self.OnModified)
self.Bind(stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded)
self.Bind(stc.EVT_STC_NEEDSHOWN, self.OnNeedShown)
+ self.bindEvent(dEvents.KeyDown, self.__onKeyDown)
+ self.bindEvent(dEvents.KeyChar, self.__onKeyChar)
if delay:
self.bindEvent(dEvents.Idle, self.onIdle)
@@ -339,6 +341,9 @@
self._bookmarks = {}
# This holds the last saved bookmark status
self._lastBookmarks = []
+ # This attribute lets external program define an additional
+ # local namespace for code completion, etc.
+ self.locals = {}
if self.UseStyleTimer:
self._styleTimer.mode = "container"
@@ -914,10 +919,6 @@
"fore:#000000,face:%s,back:#E0C0E0,eol,size:%d" %
(fontFace, fontSize))
-
-
-
-
def onCommentLine(self, evt):
sel = self.GetSelection()
begLine = self.LineFromPosition(sel[0])
@@ -951,7 +952,7 @@
self.PositionFromLine(endLine+1))
- def onKeyDown(self, evt):
+ def __onKeyDown(self, evt):
keyCode = evt.EventData["keyCode"]
if keyCode == wx.WXK_RETURN and self.AutoIndent and not
self.AutoCompActive():
## Insert auto indentation as necessary. This code was
adapted from
@@ -987,7 +988,7 @@
self.SetSelection(pos, pos)
- def onKeyChar(self, evt):
+ def __onKeyChar(self, evt):
keyChar = evt.EventData["keyChar"]
self._insertChar = ""
@@ -1239,7 +1240,10 @@
# Get the name of object the user is pressing "." after.
# This could be 'self', 'dabo', or a reference to any object
# previously defined.
- obj = self._getRuntimeObject(self._getRuntimeObjectName())
+ nm = self._getRuntimeObjectName()
+ obj = self._getRuntimeObject(nm)
+ if not obj:
+ obj = self.locals.get(nm)
if obj is not None:
kw = []
pos = self.GetCurrentPos()
@@ -1251,7 +1255,7 @@
# Images are specified with a appended "?type"
for i in range(len(kw)):
try:
- obj_ = eval("obj.%s" % kw[i])
+ obj_ = getattr(obj, kw[i])
except (AttributeError, TypeError):
continue
isEvent = False
@@ -1797,6 +1801,7 @@
#Catch errors like module objects not having an
attribute
pass
+
def _getRuntimeObject(self, runtimeObjectName):
"""Given a runtimeObjectName, get the object.
@@ -1807,9 +1812,9 @@
if len(runtimeObjectName.strip()) == 0:
return None
self._fillNamespaces()
- s = runtimeObjectName.split(".")
- outerObjectName = s[0].strip()
- if len(outerObjectName) == 0:
+ dotSplitName = runtimeObjectName.split(".")
+ outerObjectName = dotSplitName[0].strip()
+ if not outerObjectName:
return None
if outerObjectName == "self":
@@ -1822,15 +1827,16 @@
# Different editor usages may require additional namespace
# hacks, such as the above. This is a hook for adding such
hacks.
self._namespaceHacks()
- o = self._namespaces.get(outerObjectName)
- if o is not None:
- innerObjectNames = '.'.join(s[1:])
- if len(innerObjectNames) > 0:
+ obj = self._namespaces.get(outerObjectName)
+ if obj is not None:
+ for nm in dotSplitName:
try:
- o = eval("o.%s" % innerObjectNames)
- except (AttributeError, SyntaxError):
- o = None
- return o
+ obj = getattr(obj, nm)
+ except:
+ # Not an object path
+ obj = None
+ break
+ return obj
def _namespaceHacks(self):
Modified: trunk/dabo/ui/uiwx/dShell.py
===================================================================
--- trunk/dabo/ui/uiwx/dShell.py 2010-09-26 13:16:04 UTC (rev 6035)
+++ trunk/dabo/ui/uiwx/dShell.py 2010-09-27 00:35:57 UTC (rev 6036)
@@ -174,9 +174,16 @@
super(_Shell, self).processLine()
if edt:
# push the latest command into the stack
- self.Form.addToHistory(self.history[0])
+ self.Form.addToHistory()
+ def getAutoCompleteList(self, cmd):
+ return self.interp.getAutoCompleteList(cmd,
+ includeMagic=self.autoCompleteIncludeMagic,
+ includeSingle=self.autoCompleteIncludeSingle,
+ includeDouble=self.autoCompleteIncludeDouble)
+
+
def setDefaultFont(self, fontFace, fontSize):
# Global default styles for all languages
self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%s,size:%d" %
(fontFace, fontSize))
@@ -262,7 +269,6 @@
if cmdDown and (key == wx.WXK_LEFT):
# Equivalent to Home
home = self.promptPosEnd
- print home
if currpos > home:
self.SetCurrentPos(home)
if not selecting and not shiftDown:
@@ -374,7 +380,15 @@
cp.Sizer = dabo.ui.dSizer()
op.Sizer = dabo.ui.dSizer()
- self.shell = _Shell(self.CmdPanel)
+ pgf = self.pgfCodeShell = dabo.ui.dPageFrame(cp, PageCount=2)
+ self.pgShell = pgf.Pages[0]
+ self.pgCode = pgf.Pages[1]
+ self.pgShell.Caption = _("Shell")
+ self.pgCode.Caption = _("Code")
+ cp.Sizer.append1x(pgf)
+
+ self.shell = _Shell(self.pgShell)
+ self.pgShell.Sizer.append1x(self.shell, border=4)
# Configure the shell's behavior
self.shell.AutoCompSetIgnoreCase(True)
self.shell.AutoCompSetAutoHide(False) ## don't hide when the
typed string no longer matches
@@ -382,21 +396,47 @@
self.shell.AutoCompSetFillUps(".(")
# This lets you go all the way back to the '.' without losing
the AutoComplete
self.shell.AutoCompSetCancelAtStart(False)
+ self.shell.Bind(wx.EVT_RIGHT_UP, self.onShellRight)
+ self.shell.Bind(wx.wx.EVT_CONTEXT_MENU, self.onShellContext)
- cp.Sizer.append1x(self.shell)
- self.shell.Bind(wx.EVT_RIGHT_UP, self.shellRight)
- # Bring up history search
- self.bindKey("Ctrl+R", self.onHistoryPop)
+ # Create the Code control
+ codeControl = dabo.ui.dEditor(self.pgCode, RegID="edtCode",
+ Language="python", OnKeyDown=self.onCodeKeyDown,
+ OnMouseRightDown=self.onCodeRightDown,
+ DroppedTextHandler=self,
DroppedFileHandler=self)
+ self.pgCode.Sizer.append1x(codeControl, border=4)
+ # This adds the interpreter's local namespace to the editor for
code completion, etc.
+ codeControl.locals = self.shell.interp.locals
+ lbl = dabo.ui.dLabel(self.pgCode, ForeColor="blue",
WordWrap=True,
+ Caption=_("""Ctrl-Enter to run the code (or
click the button to the right).
+Ctrl-Up/Down to scroll through history."""))
+ lbl.FontSize -= 3
+ runButton = dabo.ui.dButton(self.pgCode, Caption=_("Run"),
+ OnHit=self.onRunCode)
+ hsz = dabo.ui.dSizer("h")
+ hsz.appendSpacer(20)
+ hsz.append(lbl)
+ hsz.append1x(dabo.ui.dPanel(self.pgCode))
+ hsz.append(runButton, valign="middle")
+ hsz.appendSpacer(20)
+ self.pgCode.Sizer.append(hsz, "x")
+ # Stack to hold code history
+ self._codeStack = []
+ self._codeStackPos = 0
# Restore the history
self.restoreHistory()
+ # Bring up history search
+ self.bindKey("Ctrl+R", self.onHistoryPop)
+ # Show/hide the code editing pane
+ self.bindKey("Ctrl+E", self.onToggleCodePane)
# create the output control
outControl = dabo.ui.dEditBox(op, RegID="edtOut",
ReadOnly=True)
op.Sizer.append1x(outControl)
outControl.bindEvent(dEvents.MouseRightDown,
- self.outputRightDown)
+ self.onOutputRightDown)
self._stdOut = self.shell.interp.stdout
self._stdErr = self.shell.interp.stderr
@@ -428,7 +468,9 @@
dabo.ui.callAfter(ed.SetSelection, endpos, endpos)
- def addToHistory(self, cmd):
+ def addToHistory(self, cmd=None):
+ if cmd is None:
+ cmd = self.shell.history[0]
chk = self.cmdHistKey
if cmd == self._lastCmd:
# Don't add again
@@ -453,6 +495,27 @@
return dsu
+ def onToggleCodePane(self, evt):
+ """Toggle between the Code Pane and the Output Pane"""
+ self.pgfCodeShell.cyclePages(1)
+
+
+ def processDroppedFiles(self, filelist):
+ """This will fire if files are dropped on the code editor. If
more than one
+ file is dropped, only open the first, and warn the user."""
+ if len(filelist) > 1:
+ dabo.ui.exclaim(_("Only one file can be dropped at a
time"))
+ self.edtCode.Value = file(filelist[0]).read()
+
+
+ def processDroppedText(self, txt):
+ """Add the text to the code editor."""
+ cc = self.edtCode
+ currText = cc.Value
+ selStart, selEnd = cc.SelectionPosition
+ cc.Value = "%s%s%s" % (currText[:selStart], txt,
currText[selEnd:])
+
+
def onHistoryPop(self, evt):
"""Let the user type in part of a command, and retrieve the
matching commands
from their history.
@@ -496,7 +559,56 @@
ck.deletePref(bs)
- def outputRightDown(self, evt):
+ def onRunCode(self, evt, addReturn=True):
+ code = self.edtCode.Value.rstrip()
+ if not code:
+ return
+ # See if this is already in the stack
+ try:
+ self._codeStackPos = self._codeStack.index(code)
+ except ValueError:
+ self._codeStack.append(code)
+ self._codeStackPos = len(self._codeStack)
+ self.edtCode.Value = ""
+ self.shell.Execute(code)
+ # If the last line is indented, run a blank line to complete
the block
+ if code.splitlines()[-1][0] in " \t":
+ self.shell.run("", prompt=False)
+ self.addToHistory()
+ self.pgfCodeShell.SelectedPage = self.pgShell
+
+
+ def onCodeKeyDown(self, evt):
+ if not evt.controlDown:
+ return
+ keyCode = evt.keyCode
+ if (keyCode == 13):
+ evt.stop()
+ self.onRunCode(None, addReturn=True)
+ elif keyCode in (dKeys.key_Up, dKeys.key_Down):
+ direction = {dKeys.key_Up: -1, dKeys.key_Down:
1}[keyCode]
+ self.moveCodeStack(direction)
+
+
+ def moveCodeStack(self, direction):
+ size = len(self._codeStack)
+ pos = self._codeStackPos
+ newpos = max(0, pos + direction)
+ if newpos == size:
+ # at the end; clear the code
+ self._codeStackPos = size - 1
+ self.edtCode.Value = ""
+ else:
+ code = self._codeStack[newpos]
+ self._codeStackPos = newpos
+ self.edtCode.Value = code
+
+
+ def onCodeRightDown(self, evt):
+ dabo.ui.info("Code!")
+
+
+ def onOutputRightDown(self, evt):
pop = dabo.ui.dMenu()
pop.append(_("Clear"), OnHit=self.onClearOutput)
if self.edtOut.SelectionLength:
@@ -509,7 +621,7 @@
self.edtOut.Value = ""
- def shellRight(self, evt):
+ def onShellContext(self, evt):
pop = dabo.ui.dMenu()
if self.SplitState:
pmpt = _("Unsplit")
@@ -520,8 +632,19 @@
evt.StopPropagation()
+ def onShellRight(self, evt):
+ pop = dabo.ui.dMenu()
+ if self.SplitState:
+ pmpt = _("Unsplit")
+ else:
+ pmpt = _("Split")
+ pop.append(pmpt, OnHit=self.onSplitContext)
+ self.showContextMenu(pop)
+ evt.StopPropagation()
+
+
def onSplitContext(self, evt):
- self.SplitState = (evt.EventObject.Caption == _("Split"))
+ self.SplitState = not self.SplitState
evt.stop()
@@ -551,6 +674,9 @@
viewMenu.append(_("Zoom &Out"), HotKey="Ctrl+-",
OnHit=self.onViewZoomOut,
ItemID="view_zoomout",
bmp="zoomOut", help=_("Zoom Out"))
+ viewMenu.append(_("&Toggle Code Pane"), HotKey="Ctrl+E",
OnHit=self.onToggleCodePane,
+ ItemID="view_togglecode",
+ bmp="", help=_("Show/hide Code Pane"))
editMenu = self.MenuBar.getMenu("base_edit")
if editMenu.Children:
editMenu.appendSeparator()
_______________________________________________
Post Messages to: [email protected]
Subscription Maintenance: http://leafe.com/mailman/listinfo/dabo-dev
Searchable Archives: http://leafe.com/archives/search/dabo-dev
This message:
http://leafe.com/archives/byMID/[email protected]