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]

Reply via email to