PageRenderTime 225ms CodeModel.GetById 141ms app.highlight 40ms RepoModel.GetById 40ms app.codeStats 0ms

/Lib/idlelib/CallTips.py

http://unladen-swallow.googlecode.com/
Python | 221 lines | 194 code | 10 blank | 17 comment | 4 complexity | bff5e8b1da8e8d5c8ccfd8935fde916b MD5 | raw file
  1"""CallTips.py - An IDLE Extension to Jog Your Memory
  2
  3Call Tips are floating windows which display function, class, and method
  4parameter and docstring information when you type an opening parenthesis, and
  5which disappear when you type a closing parenthesis.
  6
  7"""
  8import re
  9import sys
 10import types
 11
 12import CallTipWindow
 13from HyperParser import HyperParser
 14
 15import __main__
 16
 17class CallTips:
 18
 19    menudefs = [
 20        ('edit', [
 21            ("Show call tip", "<<force-open-calltip>>"),
 22        ])
 23    ]
 24
 25    def __init__(self, editwin=None):
 26        if editwin is None:  # subprocess and test
 27            self.editwin = None
 28            return
 29        self.editwin = editwin
 30        self.text = editwin.text
 31        self.calltip = None
 32        self._make_calltip_window = self._make_tk_calltip_window
 33
 34    def close(self):
 35        self._make_calltip_window = None
 36
 37    def _make_tk_calltip_window(self):
 38        # See __init__ for usage
 39        return CallTipWindow.CallTip(self.text)
 40
 41    def _remove_calltip_window(self, event=None):
 42        if self.calltip:
 43            self.calltip.hidetip()
 44            self.calltip = None
 45
 46    def force_open_calltip_event(self, event):
 47        """Happens when the user really wants to open a CallTip, even if a
 48        function call is needed.
 49        """
 50        self.open_calltip(True)
 51
 52    def try_open_calltip_event(self, event):
 53        """Happens when it would be nice to open a CallTip, but not really
 54        necessary, for example after an opening bracket, so function calls
 55        won't be made.
 56        """
 57        self.open_calltip(False)
 58
 59    def refresh_calltip_event(self, event):
 60        """If there is already a calltip window, check if it is still needed,
 61        and if so, reload it.
 62        """
 63        if self.calltip and self.calltip.is_active():
 64            self.open_calltip(False)
 65
 66    def open_calltip(self, evalfuncs):
 67        self._remove_calltip_window()
 68
 69        hp = HyperParser(self.editwin, "insert")
 70        sur_paren = hp.get_surrounding_brackets('(')
 71        if not sur_paren:
 72            return
 73        hp.set_index(sur_paren[0])
 74        name = hp.get_expression()
 75        if not name or (not evalfuncs and name.find('(') != -1):
 76            return
 77        arg_text = self.fetch_tip(name)
 78        if not arg_text:
 79            return
 80        self.calltip = self._make_calltip_window()
 81        self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1])
 82
 83    def fetch_tip(self, name):
 84        """Return the argument list and docstring of a function or class
 85
 86        If there is a Python subprocess, get the calltip there.  Otherwise,
 87        either fetch_tip() is running in the subprocess itself or it was called
 88        in an IDLE EditorWindow before any script had been run.
 89
 90        The subprocess environment is that of the most recently run script.  If
 91        two unrelated modules are being edited some calltips in the current
 92        module may be inoperative if the module was not the last to run.
 93
 94        To find methods, fetch_tip must be fed a fully qualified name.
 95
 96        """
 97        try:
 98            rpcclt = self.editwin.flist.pyshell.interp.rpcclt
 99        except:
100            rpcclt = None
101        if rpcclt:
102            return rpcclt.remotecall("exec", "get_the_calltip",
103                                     (name,), {})
104        else:
105            entity = self.get_entity(name)
106            return get_arg_text(entity)
107
108    def get_entity(self, name):
109        "Lookup name in a namespace spanning sys.modules and __main.dict__"
110        if name:
111            namespace = sys.modules.copy()
112            namespace.update(__main__.__dict__)
113            try:
114                return eval(name, namespace)
115            except (NameError, AttributeError):
116                return None
117
118def _find_constructor(class_ob):
119    # Given a class object, return a function object used for the
120    # constructor (ie, __init__() ) or None if we can't find one.
121    try:
122        return class_ob.__init__.im_func
123    except AttributeError:
124        for base in class_ob.__bases__:
125            rc = _find_constructor(base)
126            if rc is not None: return rc
127    return None
128
129def get_arg_text(ob):
130    """Get a string describing the arguments for the given object"""
131    arg_text = ""
132    if ob is not None:
133        arg_offset = 0
134        if type(ob) in (types.ClassType, types.TypeType):
135            # Look for the highest __init__ in the class chain.
136            fob = _find_constructor(ob)
137            if fob is None:
138                fob = lambda: None
139            else:
140                arg_offset = 1
141        elif type(ob)==types.MethodType:
142            # bit of a hack for methods - turn it into a function
143            # but we drop the "self" param.
144            fob = ob.im_func
145            arg_offset = 1
146        else:
147            fob = ob
148        # Try to build one for Python defined functions
149        if type(fob) in [types.FunctionType, types.LambdaType]:
150            argcount = fob.func_code.co_argcount
151            real_args = fob.func_code.co_varnames[arg_offset:argcount]
152            defaults = fob.func_defaults or []
153            defaults = list(map(lambda name: "=%s" % repr(name), defaults))
154            defaults = [""] * (len(real_args) - len(defaults)) + defaults
155            items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
156            if fob.func_code.co_flags & 0x4:
157                items.append("...")
158            if fob.func_code.co_flags & 0x8:
159                items.append("***")
160            arg_text = ", ".join(items)
161            arg_text = "(%s)" % re.sub("\.\d+", "<tuple>", arg_text)
162        # See if we can use the docstring
163        doc = getattr(ob, "__doc__", "")
164        if doc:
165            doc = doc.lstrip()
166            pos = doc.find("\n")
167            if pos < 0 or pos > 70:
168                pos = 70
169            if arg_text:
170                arg_text += "\n"
171            arg_text += doc[:pos]
172    return arg_text
173
174#################################################
175#
176# Test code
177#
178if __name__=='__main__':
179
180    def t1(): "()"
181    def t2(a, b=None): "(a, b=None)"
182    def t3(a, *args): "(a, ...)"
183    def t4(*args): "(...)"
184    def t5(a, *args): "(a, ...)"
185    def t6(a, b=None, *args, **kw): "(a, b=None, ..., ***)"
186    def t7((a, b), c, (d, e)): "(<tuple>, c, <tuple>)"
187
188    class TC(object):
189        "(ai=None, ...)"
190        def __init__(self, ai=None, *b): "(ai=None, ...)"
191        def t1(self): "()"
192        def t2(self, ai, b=None): "(ai, b=None)"
193        def t3(self, ai, *args): "(ai, ...)"
194        def t4(self, *args): "(...)"
195        def t5(self, ai, *args): "(ai, ...)"
196        def t6(self, ai, b=None, *args, **kw): "(ai, b=None, ..., ***)"
197        def t7(self, (ai, b), c, (d, e)): "(<tuple>, c, <tuple>)"
198
199    def test(tests):
200        ct = CallTips()
201        failed=[]
202        for t in tests:
203            expected = t.__doc__ + "\n" + t.__doc__
204            name = t.__name__
205            # exercise fetch_tip(), not just get_arg_text()
206            try:
207                qualified_name = "%s.%s" % (t.im_class.__name__, name)
208            except AttributeError:
209                qualified_name = name
210            arg_text = ct.fetch_tip(qualified_name)
211            if arg_text != expected:
212                failed.append(t)
213                fmt = "%s - expected %s, but got %s"
214                print  fmt % (t.__name__, expected, get_arg_text(t))
215        print "%d of %d tests failed" % (len(failed), len(tests))
216
217    tc = TC()
218    tests = (t1, t2, t3, t4, t5, t6, t7,
219             TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6, tc.t7)
220
221    test(tests)