[Idle-dev] IDLE extension

martin mc.liebmann@t-online.de
Sat, 2 Mar 2002 20:06:43 +0100


This is a multi-part message in MIME format.

------=_NextPart_000_0005_01C1C225.C63009A0
Content-Type: multipart/alternative;
	boundary="----=_NextPart_001_0006_01C1C225.C63009A0"


------=_NextPart_001_0006_01C1C225.C63009A0
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

Hi,

the attached files implement a (quick and dirty but useful) extension to =
IDLE 0.8 (Python 2.2).

Similar to "CallTips" this extension shows member functions and =
attributes after hitting a "."
The toplevel help window supports scrolling (up- and down-Keys). The =
'space'-key is used to
insert the topmost item into the IDLE text window.

Martin

------=_NextPart_001_0006_01C1C225.C63009A0
Content-Type: text/html;
	charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; =
charset=3Diso-8859-1">
<META content=3D"MSHTML 6.00.2600.0" name=3DGENERATOR>
<STYLE></STYLE>
</HEAD>
<BODY bgColor=3D#ffffff>
<DIV><FONT face=3DArial size=3D2>Hi,</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>the attached files implement a (quick =
and dirty but=20
useful) extension to IDLE 0.8 (Python 2.2).</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>Similar to "CallTips" this extension =
shows member=20
functions and attributes after hitting a "."</FONT></DIV>
<DIV><FONT face=3DArial size=3D2>The toplevel help window supports =
scrolling (up-=20
and down-Keys). The 'space'-key is used to</FONT></DIV>
<DIV><FONT face=3DArial size=3D2>insert&nbsp;the topmost item into the =
IDLE text=20
window.</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>Martin</FONT></DIV></BODY></HTML>

------=_NextPart_001_0006_01C1C225.C63009A0--

------=_NextPart_000_0005_01C1C225.C63009A0
Content-Type: text/plain;
	name="CallTips.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="CallTips.py"

# CallTips.py - An IDLE extension that provides "Call Tips" - ie, a =
floating window that
# displays parameter information as you open parens.

import string
import sys
import types
import inspect

class CallTips:

    menudefs =3D [
    ]

    keydefs =3D {
        '<<paren-open>>': ['<Key-parenleft>'],
        '<<paren-close>>': ['<Key-parenright>'],
        '<<calltip-cancel>>': ['<ButtonPress>', '<Key-Escape>'],       =20
        '<<dot-check>>': ['<Key-.>'],
        '<<check-key>>': ['<Key>'],
        '<<calltip-down>>': ['<Key-Down>'],
        '<<calltip-up>>': ['<Key-Up>'],
        '<<check-calltip-cancel>>': ['<KeyPress-BackSpace>'],
    }

    windows_keydefs =3D {
    }

    unix_keydefs =3D {
    }

    def __init__(self, editwin):
        self.editwin =3D editwin
        self.text =3D editwin.text
        self.calltip =3D None
       =20
        self.line =3D 0
        self.member =3D None
        self.members =3D []
        self.sub_members =3D []
        self.word =3D ""
        self.wordchars =3D string.replace(string.printable, '.', '')
        self.pattern =3D ""
       =20
        if hasattr(self.text, "make_calltip_window"):
            self._make_calltip_window =3D self.text.make_calltip_window
        else:
            self._make_calltip_window =3D self._make_tk_calltip_window

    def close(self):
        self._make_calltip_window =3D None

    # Makes a Tk based calltip window.  Used by IDLE, but not Pythonwin.
    # See __init__ above for how this is used.
    def _make_tk_calltip_window(self):
        import CallTipWindow
        return CallTipWindow.CallTip(self.text)

    def _remove_calltip_window(self):
        if self.calltip:
            self.calltip.hidetip()
            self.calltip =3D None

    def paren_open_event(self, event):
        self.member =3D None
        self._remove_calltip_window()
        arg_text =3D get_arg_text(self.get_object_at_cursor())
        if arg_text:
            self.calltip_start =3D self.text.index("insert+1c")
            self.calltip =3D self._make_calltip_window()
            list =3D []
            list.append(arg_text)
            self.calltip.showtip(list)
        return "" #so the event is handled normally.

    def paren_close_event(self, event):
        # Now just hides, but later we should check if other
        # paren'd expressions remain open.
        self._remove_calltip_window()
        return "" #so the event is handled normally.

    def _dir_main(self,obj):
        "Execute dir(obj) within namespace __main__"
        from __main__ import *
        return dir(obj)
   =20
    def dot_check_event(self, event):
        self._remove_calltip_window()
        obj =3D self.get_object_at_cursor()
      =20
        if obj:       =20
       =20
            names =3D self._dir_main(obj)
            names.sort()
            self.member =3D 'true'
            self.members =3D []
            self.word =3D ""
            self.line =3D 0
            for name in names:
                if (string.find(name,"__",0) =3D=3D 0):
                     pass
                else:
                    subobj =3D getattr(obj, name)                   =20
                    if inspect.isclass(subobj):
                        self.members.append(name + "(...)")
                    elif inspect.isfunction(subobj):                     =
     =20
                        self.members.append(name + "(...)")
                    elif inspect.ismethod(subobj):
                        self.members.append(name + "(...)")
                    elif inspect.ismodule(subobj):
                        pass
                    elif inspect.isroutine(subobj):
                        self.members.append(name + "(...)")
                    else:
                        self.members.append(name)
               =20
            if self.members:
                self.calltip_start =3D self.text.index("insert+1c")
                self.calltip =3D self._make_calltip_window()
                self.sub_members =3D self.members
                self.calltip.showtip(self.members,1)

        return "" #so the event is handled normally.   =20
   =20
    def check_key_event(self, event):
        if not self.member:
            return ""
       =20
        if self.calltip:
            if event.keysym =3D=3D 'space':
                self._remove_calltip_window()
               =20
                if self.sub_members:
                    first =3D len(self.word)
                    last =3D string.rfind(self.sub_members[self.line], =
"(")
                    if last < 0:
                       last =3D len(self.sub_members[self.line])
                    self.text.insert("insert", =
self.sub_members[self.line][first:last])
                    return "break"
               =20
            elif (string.find(self.wordchars,event.keysym) > -1):
                text =3D self.text
                chars =3D text.get("insert linestart", "insert")
                i =3D len(chars)
                while i and chars[i-1] in self.wordchars:
                    i =3D i-1
                self.word =3D chars[i:] + event.keysym
                if self.word:
                    self.sub_members =3D []
                    self.line =3D 0
                    for member in self.members:
                        if string.find(member, self.word) =3D=3D 0:
                            self.sub_members.append(member)
                    if len(self.sub_members) =3D=3D 0:
                        self._remove_calltip_window()
                    else:
                        self._remove_calltip_window()
                        # self.calltip_start =3D =
self.text.index("insert")
                        self.calltip =3D self._make_calltip_window()
                        =
self.calltip.showtip(self.sub_members,1-len(self.word))
            else:
                pass
        return "" #so the event is handled normally.

    def calltip_down_event(self, event):
        if self.calltip:
            if self.line < (len(self.sub_members) - 1):
                self.line +=3D 1
            self._remove_calltip_window()
            self.calltip =3D self._make_calltip_window()
            =
self.calltip.showtip(self.sub_members,-len(self.word),self.line)         =
  =20
            return "break" #skip further event handling.
        else:
            return "" #so the event is handled normally.

    def calltip_up_event(self, event):
        if self.calltip:
            if self.line > 0:
                self.line -=3D 1
            self._remove_calltip_window()
            self.calltip =3D self._make_calltip_window()
            =
self.calltip.showtip(self.sub_members,-len(self.word),self.line) =20
            return "break" #skip further event handling.
        else:
            return "" #so the event is handled normally.
   =20
    def check_calltip_cancel_event(self, event):
        if self.calltip:
            # If we have moved before the start of the calltip,
            # or off the calltip line, then cancel the tip.
            # (Later need to be smarter about multi-line, etc)
            if self.text.compare("insert", "<=3D", self.calltip_start) =
or \
               self.text.compare("insert", ">", self.calltip_start + " =
lineend"):
                self._remove_calltip_window()
       =20
        return "" #so the event is handled normally.

    def calltip_cancel_event(self, event):
        self._remove_calltip_window()
        return "" #so the event is handled normally.

    def get_object_at_cursor(self,
                             wordchars=3D"._" + string.uppercase + =
string.lowercase + string.digits):
        # XXX - This needs to be moved to a better place
        # so the "." attribute lookup code can also use it.
        text =3D self.text
        chars =3D text.get("insert linestart", "insert")
        i =3D len(chars)
        while i and chars[i-1] in wordchars:
            i =3D i-1
        word =3D chars[i:]
        if word:
            # How is this for a hack!
            import sys, __main__
            namespace =3D sys.modules.copy()
            namespace.update(__main__.__dict__)
            try:
                return eval(word, namespace)
            except:
                pass
        return None # Can't find an object.

def _find_constructor(class_ob):
    # Given a class object, return a function object used for the
    # constructor (ie, __init__() ) or None if we can't find one.
    try:
        return class_ob.__init__.im_func
    except AttributeError:
        for base in class_ob.__bases__:
            rc =3D _find_constructor(base)
            if rc is not None: return rc
    return None

def get_arg_text(ob):
    # Get a string describing the arguments for the given object.
    argText =3D ""
    if ob is not None:
        argOffset =3D 0
        if type(ob)=3D=3Dtypes.ClassType:
            # Look for the highest __init__ in the class chain.
            fob =3D _find_constructor(ob)
            if fob is None:
                fob =3D lambda: None
            else:
                argOffset =3D 1
        elif type(ob)=3D=3Dtypes.MethodType:
            # bit of a hack for methods - turn it into a function
            # but we drop the "self" param.
            fob =3D ob.im_func
            argOffset =3D 1
        else:
            fob =3D ob
        # Try and build one for Python defined functions
        if type(fob) in [types.FunctionType, types.LambdaType]:
            try:
                realArgs =3D =
fob.func_code.co_varnames[argOffset:fob.func_code.co_argcount]
                defaults =3D fob.func_defaults or []
                defaults =3D list(map(lambda name: "=3D%s" % name, =
defaults))
                defaults =3D [""] * (len(realArgs)-len(defaults)) + =
defaults
                items =3D map(lambda arg, dflt: arg+dflt, realArgs, =
defaults)
                if fob.func_code.co_flags & 0x4:
                    items.append("...")
                if fob.func_code.co_flags & 0x8:
                    items.append("***")
                argText =3D string.join(items , ", ")
                argText =3D "(%s)" % argText
            except:
                pass
        # See if we can use the docstring
        doc =3D getattr(ob, "__doc__", "")
        if doc:
            while doc[:1] in " \t\n":
                doc =3D doc[1:]
            pos =3D doc.find("\n")
            if pos < 0 or pos > 70:
                pos =3D 70
            if argText:
                argText +=3D "\n"
            argText +=3D doc[:pos]

    return argText

#################################################
#
# Test code
#
if __name__=3D=3D'__main__':

    def t1(): "()"
    def t2(a, b=3DNone): "(a, b=3DNone)"
    def t3(a, *args): "(a, ...)"
    def t4(*args): "(...)"
    def t5(a, *args): "(a, ...)"
    def t6(a, b=3DNone, *args, **kw): "(a, b=3DNone, ..., ***)"

    class TC:
        "(a=3DNone, ...)"
        def __init__(self, a=3DNone, *b): "(a=3DNone, ...)"
        def t1(self): "()"
        def t2(self, a, b=3DNone): "(a, b=3DNone)"
        def t3(self, a, *args): "(a, ...)"
        def t4(self, *args): "(...)"
        def t5(self, a, *args): "(a, ...)"
        def t6(self, a, b=3DNone, *args, **kw): "(a, b=3DNone, ..., =
***)"

    def test( tests ):
        failed=3D[]
        for t in tests:
            expected =3D t.__doc__ + "\n" + t.__doc__
            if get_arg_text(t) !=3D expected:
                failed.append(t)
                print "%s - expected %s, but got %s" % (t, `expected`, =
`get_arg_text(t)`)
        print "%d of %d tests failed" % (len(failed), len(tests))

    tc =3D TC()
    tests =3D t1, t2, t3, t4, t5, t6, \
            TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6

    test(tests)

------=_NextPart_000_0005_01C1C225.C63009A0
Content-Type: text/plain;
	name="CallTipWindow.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="CallTipWindow.py"

# A CallTip window class for Tkinter/IDLE.
# After ToolTip.py, which uses ideas gleaned from PySol

# Used by the CallTips IDLE extension.
import os
import string
from Tkinter import *

class CallTip:

    def __init__(self, widget):
        self.widget = widget
        self.tipwindow = None
        self.id = None
        self.x = self.y = 0

    def showtip(self, texts, xoffset=0, line=0, maxcount=10):
        self.texts = texts # List of text strings
        
        if self.tipwindow or not self.texts:
            return
        
        # Setup visible region
        if maxcount < 0:
            maxcount = 1
        first = 0
        last = len(self.texts)
        if line > first:
            first = line
        if first > last:
            first = last
        if (last - first + 1) > maxcount:
            last = first + maxcount

        # Extract visible region
        self.text = string.join(self.texts[first:last], "\n")
        
        self.widget.see("insert")
        # To avoid incorrect values for cx take "insert - 1 chars"
        x, y, cx, cy = self.widget.bbox("insert - 1 chars")
        # Estimate current cursor position
        x = x + cx

        # Top left coordinates for the new toplevel widget
        x = x + cx*xoffset + self.widget.winfo_rootx() - 1 
        y = y + cy + self.widget.winfo_rooty()
        self.tipwindow = tw = Toplevel(self.widget)
        tw.wm_overrideredirect(1)
        tw.wm_geometry("+%d+%d" % (x, y))
        label = Label(tw, text=self.text, justify=LEFT,
                      background="#ffffe0", relief=SOLID, borderwidth=1,
                      font = self.widget['font'])
        label.pack()

    def hidetip(self):
        tw = self.tipwindow
        self.tipwindow = None
        if tw:
            tw.destroy()


###############################
#
# Test Code
#
class container: # Conceptually an editor_window
    def __init__(self):
        root = Tk()
        text = self.text = Text(root)
        text.pack(side=LEFT, fill=BOTH, expand=1)
        text.insert("insert", "string.split")
        root.update()
        self.calltip = CallTip(text)

        text.event_add("<<calltip-show>>", "(")
        text.event_add("<<calltip-hide>>", ")")
        text.bind("<<calltip-show>>", self.calltip_show)
        text.bind("<<calltip-hide>>", self.calltip_hide)

        text.focus_set()
        # root.mainloop() # not in idle

    def calltip_show(self, event):
        self.calltip.showtip(["Hello world"])

    def calltip_hide(self, event):
        self.calltip.hidetip()

def main():
    # Test code
    c=container()

if __name__=='__main__':
    main()

------=_NextPart_000_0005_01C1C225.C63009A0--