orestis / pysmell

PySmell is an attempt to create an IDE completion helper for python.

pysmell / idehelper.py
100644 216 lines (172 sloc) 7.979 kb
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# idehelper.py
# Copyright (C) 2008 Orestis Markou
# All rights reserved
# E-mail: orestis@orestis.gr
 
# http://orestis.gr
 
# Released subject to the BSD License
 
import os
from codefinder import infer, findRootPackageList
from matchers import MATCHERS
 
    
def findPYSMELLDICT(filename):
    directory, basename = os.path.split(filename)
    partialDict = {}
    while not os.path.exists(os.path.join(directory, 'PYSMELLTAGS')):
        if os.path.exists(os.path.join(directory, 'PYSMELLTAGS.partial')):
            tagsFile = os.path.join(directory, 'PYSMELLTAGS.partial')
            partialDict.update(eval(file(tagsFile).read()))
        directory, _ = os.path.split(directory)
    tagsFile = os.path.join(directory, 'PYSMELLTAGS')
    if not os.path.exists(tagsFile):
        print 'Could not file PYSMELLTAGS for omnicompletion'
        return
    PYSMELLDICT = eval(file(tagsFile).read())
    PYSMELLDICT.update(partialDict)
    return PYSMELLDICT
 
 
def debug(vim, msg):
    if vim is None: return
    if int(vim.eval('g:pysmell_debug')):
        debBuffer = None
        for b in vim.buffers:
            if b.name.endswith('DEBUG'):
                debBuffer = b
        debBuffer.append(msg)
 
 
def inferClass(fullPath, origSource, origLineNo, PYSMELLDICT, vim=None):
    pathParts = []
    fullPath = fullPath
    head, tail = os.path.split(fullPath[:-3])
    pathParts.append(tail)
    while head and tail:
        head, tail = os.path.split(head)
        if tail:
            pathParts.append(tail)
    pathParts.reverse()
    klass, parents = infer(origSource, origLineNo)
    # replace POINTERS with their full reference
    for index, parent in enumerate(parents):
        if parent in PYSMELLDICT['POINTERS']:
            parents[index] = PYSMELLDICT['POINTERS'][parent]
        else:
            for pointer in PYSMELLDICT['POINTERS']:
                if pointer.endswith('*') and parent.startswith(pointer[:-2]):
                    parents[index] = '%s.%s' % (PYSMELLDICT['POINTERS'][pointer][:-2], parent.split('.', 1)[-1])
 
 
    fullKlass = klass
    while pathParts:
        fullKlass = "%s.%s" % (pathParts.pop(), fullKlass)
        if fullKlass in PYSMELLDICT['CLASSES'].keys():
            break
    else:
        # we don't know about this class, look in the file system
        path, filename = os.path.split(fullPath)
        packages = findRootPackageList(path, filename)
        fullKlass = "%s.%s.%s" % (".".join(packages), filename[:-3], klass)
        
    return fullKlass, parents
    
 
def detectCompletionType(fullPath, origSource, origLineText, origLineNo, origCol, base, PYSMELLDICT):
    leftSide, rightSide = origLineText[:origCol], origLineText[origCol:]
 
    isAttrLookup = '.' in leftSide
    klass = None
    rindex = None
    funcName = None
    parents = None
 
    isClassLookup = isAttrLookup and leftSide[:leftSide.rindex('.')].endswith('self')
    if isClassLookup:
        klass, parents = inferClass(fullPath, origSource, origLineNo, PYSMELLDICT)
 
                
 
    isArgCompletion = base.endswith('(') and leftSide.endswith(base)
    if isArgCompletion:
        lindex = 0
        if isAttrLookup:
            lindex = leftSide.rindex('.') + 1
        funcName = leftSide[lindex:-1].lstrip()
        if rightSide.startswith(')'):
            rindex = -1
 
    return (isAttrLookup, klass, parents, funcName, rindex)
 
 
def findCompletions(base, PYSMELLDICT, options, matcher=None):
    doesMatch = MATCHERS[matcher](base)
 
    isAttrLookup, klass, parents, funcName, rindex = options
 
    completions = _createCompletionList(PYSMELLDICT, isAttrLookup, klass, parents)
 
    if base and not funcName:
        filteredCompletions = [comp for comp in completions if doesMatch(comp['word'])]
    elif funcName:
        filteredCompletions = [comp for comp in completions if comp['word'] == funcName]
    else:
        filteredCompletions = completions
 
    filteredCompletions.sort(sortCompletions)
 
    if filteredCompletions and funcName:
        #return the arg list instead
        oldComp = filteredCompletions[0]
        if oldComp['word'] == funcName:
            oldComp['word'] = oldComp['abbr'][:rindex]
    return filteredCompletions
 
 
def _findAllParents(klass, classesDICT, ancList):
    klassDict = classesDICT.get(klass, None)
    if klassDict is None: return
    for anc in klassDict['bases']:
        if anc in __builtins__: continue
        ancList.append(anc)
        _findAllParents(anc, classesDICT, ancList)
 
 
def _getCompForConstant(word):
    module, const = word.rsplit('.', 1)
    return dict(word=const, kind='d', menu=module, dup='1')
 
 
def _getCompForFunction(func, kind, module=None):
    if module is None:
        module, funcName = func[0].rsplit('.', 1)
    else:
        funcName = func[0]
    return dict(word=funcName, kind=kind, menu=module, dup='1',
                            abbr='%s(%s)' % (funcName, _argsList(func[1])))
 
def _getCompForConstructor(klass, klassDict):
    module, klassName = klass.rsplit('.', 1)
    return dict(word=klassName, kind='t', menu=module, dup='1', abbr='%s(%s)' % (klassName, _argsList(klassDict['constructor'])))
 
def _createCompletionList(PYSMELLDICT, isAttrLookup, klass, parents):
    completions = []
    if not isAttrLookup:
        completions.extend([_getCompForConstant(word) for word in PYSMELLDICT['CONSTANTS']])
        completions.extend([_getCompForFunction(func, 'f') for func in PYSMELLDICT['FUNCTIONS']])
        completions.extend([_getCompForConstructor(klass, klassDict) for (klass, klassDict) in PYSMELLDICT['CLASSES'].items()])
    elif klass:
        completions.extend(getCompletionsForClass(klass, parents, PYSMELLDICT))
    else: #plain attribute lookup
        for klass, klassDict in PYSMELLDICT['CLASSES'].items():
            addCompletionsForClass(klass, klassDict, completions)
    
    return completions
 
def getCompletionsForClass(klass, parents, PYSMELLDICT):
        klassDict = PYSMELLDICT['CLASSES'].get(klass, None)
        completions = []
        ancestorList = []
        nonBuiltinParents = [p for p in parents if p not in __builtins__]
        if klassDict is None and not nonBuiltinParents:
            return completions
        elif klassDict is None and nonBuiltinParents:
            for anc in nonBuiltinParents:
                _findAllParents(anc, PYSMELLDICT['CLASSES'], ancestorList)
                ancDict = PYSMELLDICT['CLASSES'].get(anc, None)
                if ancDict is None: continue
                addCompletionsForClass(anc, ancDict, completions)
            for anc in ancestorList:
                ancDict = PYSMELLDICT['CLASSES'].get(anc, None)
                if ancDict is None: continue
                addCompletionsForClass(anc, ancDict, completions)
            return completions
            
        _findAllParents(klass, PYSMELLDICT['CLASSES'], ancestorList)
        addCompletionsForClass(klass, klassDict, completions)
        for anc in ancestorList:
            ancDict = PYSMELLDICT['CLASSES'].get(anc, None)
            if ancDict is None: continue
            addCompletionsForClass(anc, ancDict, completions)
        return completions
 
 
def addCompletionsForClass(klass, klassDict, completions):
    module, klassName = klass.rsplit('.', 1)
    completions.extend([dict(word=prop, kind='m', dup='1', menu='%s:%s' %
                    (module, klassName)) for prop in klassDict['properties']])
    completions.extend([_getCompForFunction(func, 'm', module='%s:%s' % (module,
                klassName)) for func in klassDict['methods']])
 
def _argsList(l):
     return ', '.join([str(arg) for arg in l])
 
def sortCompletions(comp1, comp2):
    word1, word2 = comp1['word'], comp2['word']
    return _sortCompletions(word1, word2)
 
def _sortCompletions(word1, word2):
    if word1.startswith('_'):
        return _sortCompletions(word1[1:], word2) + 2
    if word2.startswith('_'):
        return _sortCompletions(word1, word2[1:]) - 2
    return cmp(word1, word2)