summaryrefslogtreecommitdiffstats
path: root/Tools/idle/CallTips.py
blob: 33d129053beb4e5e7e4f8181c5b66f48c77bb7ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import string
import sys
import types

class CallTips:

    menudefs = [
    ]

    keydefs = {
        '<<paren-open>>': ['<Key-parenleft>'],
        '<<paren-close>>': ['<Key-parenright>'],
        '<<check-calltip-cancel>>': ['<KeyRelease>'],
        '<<calltip-cancel>>': ['<ButtonPress>', '<Key-Escape>'],
    }

    windows_keydefs = {
    }

    unix_keydefs = {
    }

    def __init__(self, editwin):
        self.editwin = editwin
        self.text = editwin.text
        self.calltip = None
        if hasattr(self.text, "make_calltip_window"):
            self._make_calltip_window = self.text.make_calltip_window
        else:
            self._make_calltip_window = self._make_tk_calltip_window
        
    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 = None
        
    def paren_open_event(self, event):
        self._remove_calltip_window()
        arg_text = get_arg_text(self.get_object_at_cursor())
        if arg_text:
            self.calltip_start = self.text.index("insert")
            self.calltip = self._make_calltip_window()
            self.calltip.showtip(arg_text)
        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 check_calltip_cancel_event(self, event):
        # This doesnt quite work correctly as it is processed
        # _before_ the key is handled.  Thus, when the "Up" key
        # is pressed, this test happens before the cursor is moved.
        # This will do for now.
        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", "<=", self.calltip_start) or \
               self.text.compare("insert", ">", self.calltip_start + " lineend"):
                self._remove_calltip_window()
        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="._" + string.uppercase + string.lowercase + string.digits):
        # XXX - This need to be moved to a better place
        # as the "." attribute lookup code can also use it.
        text = self.text
        chars = text.get("insert linestart", "insert")
        i = len(chars)
        while i and chars[i-1] in wordchars:
            i = i-1
        word = chars[i:]
        if word:
            # How is this for a hack!
            import sys, __main__
            namespace = sys.modules.copy()
            namespace.update(__main__.__dict__)
            try:
                    return eval(word, namespace)
            except:
                    pass
        return None # Can't find an object.

def get_arg_text(ob):
    # Get a string describing the arguments for the given object.
    argText = ""
    if ob is not None:
        argOffset = 0
        # bit of a hack for methods - turn it into a function
        # but we drop the "self" param.
        if type(ob)==types.MethodType:
            ob = ob.im_func
            argOffset = 1
        # Try and build one for Python defined functions
        if type(ob) in [types.FunctionType, types.LambdaType]:
            try:
                realArgs = ob.func_code.co_varnames[argOffset:ob.func_code.co_argcount]
                defaults = ob.func_defaults or []
                defaults = list(map(lambda name: "=%s" % name, defaults))
                defaults = [""] * (len(realArgs)-len(defaults)) + defaults
                argText = string.join( map(lambda arg, dflt: arg+dflt, realArgs, defaults), ", ")
                if len(realArgs)+argOffset < (len(ob.func_code.co_varnames) - len(ob.func_code.co_names) ):
                    if argText: argText = argText + ", "
                    argText = argText + "..."
                argText = "(%s)" % argText
            except:
                pass
        # Can't build an argument list - see if we can use a docstring.
        if hasattr(ob, "__doc__") and ob.__doc__:
            pos = string.find(ob.__doc__, "\n")
            if pos<0 or pos>70: pos=70
            if argText: argText = argText + "\n"
            argText = argText + ob.__doc__[:pos]

    return argText

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

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

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

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

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

    test(tests)