Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/emacs-for-python/rope-dist/rope/contrib/codeassist.py
1421 views
1
import keyword
2
import sys
3
import warnings
4
5
import rope.base.codeanalyze
6
import rope.base.evaluate
7
from rope.base import pyobjects, pyobjectsdef, pynames, builtins, exceptions, worder
8
from rope.base.codeanalyze import SourceLinesAdapter
9
from rope.contrib import fixsyntax
10
from rope.refactor import functionutils
11
12
13
def code_assist(project, source_code, offset, resource=None,
14
templates=None, maxfixes=1, later_locals=True):
15
"""Return python code completions as a list of `CodeAssistProposal`\s
16
17
`resource` is a `rope.base.resources.Resource` object. If
18
provided, relative imports are handled.
19
20
`maxfixes` is the maximum number of errors to fix if the code has
21
errors in it.
22
23
If `later_locals` is `False` names defined in this scope and after
24
this line is ignored.
25
26
"""
27
if templates is not None:
28
warnings.warn('Codeassist no longer supports templates',
29
DeprecationWarning, stacklevel=2)
30
assist = _PythonCodeAssist(
31
project, source_code, offset, resource=resource,
32
maxfixes=maxfixes, later_locals=later_locals)
33
return assist()
34
35
36
def starting_offset(source_code, offset):
37
"""Return the offset in which the completion should be inserted
38
39
Usually code assist proposals should be inserted like::
40
41
completion = proposal.name
42
result = (source_code[:starting_offset] +
43
completion + source_code[offset:])
44
45
Where starting_offset is the offset returned by this function.
46
47
"""
48
word_finder = worder.Worder(source_code, True)
49
expression, starting, starting_offset = \
50
word_finder.get_splitted_primary_before(offset)
51
return starting_offset
52
53
54
def get_doc(project, source_code, offset, resource=None, maxfixes=1):
55
"""Get the pydoc"""
56
fixer = fixsyntax.FixSyntax(project.pycore, source_code,
57
resource, maxfixes)
58
pymodule = fixer.get_pymodule()
59
pyname = fixer.pyname_at(offset)
60
if pyname is None:
61
return None
62
pyobject = pyname.get_object()
63
return PyDocExtractor().get_doc(pyobject)
64
65
66
def get_calltip(project, source_code, offset, resource=None,
67
maxfixes=1, ignore_unknown=False, remove_self=False):
68
"""Get the calltip of a function
69
70
The format of the returned string is
71
``module_name.holding_scope_names.function_name(arguments)``. For
72
classes `__init__()` and for normal objects `__call__()` function
73
is used.
74
75
Note that the offset is on the function itself *not* after the its
76
open parenthesis. (Actually it used to be the other way but it
77
was easily confused when string literals were involved. So I
78
decided it is better for it not to try to be too clever when it
79
cannot be clever enough). You can use a simple search like::
80
81
offset = source_code.rindex('(', 0, offset) - 1
82
83
to handle simple situations.
84
85
If `ignore_unknown` is `True`, `None` is returned for functions
86
without source-code like builtins and extensions.
87
88
If `remove_self` is `True`, the first parameter whose name is self
89
will be removed for methods.
90
"""
91
fixer = fixsyntax.FixSyntax(project.pycore, source_code,
92
resource, maxfixes)
93
pymodule = fixer.get_pymodule()
94
pyname = fixer.pyname_at(offset)
95
if pyname is None:
96
return None
97
pyobject = pyname.get_object()
98
return PyDocExtractor().get_calltip(pyobject, ignore_unknown, remove_self)
99
100
101
def get_definition_location(project, source_code, offset,
102
resource=None, maxfixes=1):
103
"""Return the definition location of the python name at `offset`
104
105
Return a (`rope.base.resources.Resource`, lineno) tuple. If no
106
`resource` is given and the definition is inside the same module,
107
the first element of the returned tuple would be `None`. If the
108
location cannot be determined ``(None, None)`` is returned.
109
110
"""
111
fixer = fixsyntax.FixSyntax(project.pycore, source_code,
112
resource, maxfixes)
113
pymodule = fixer.get_pymodule()
114
pyname = fixer.pyname_at(offset)
115
if pyname is not None:
116
module, lineno = pyname.get_definition_location()
117
if module is not None:
118
return module.get_module().get_resource(), lineno
119
return (None, None)
120
121
122
def find_occurrences(*args, **kwds):
123
import rope.contrib.findit
124
warnings.warn('Use `rope.contrib.findit.find_occurrences()` instead',
125
DeprecationWarning, stacklevel=2)
126
return rope.contrib.findit.find_occurrences(*args, **kwds)
127
128
129
class CompletionProposal(object):
130
"""A completion proposal
131
132
The `scope` instance variable shows where proposed name came from
133
and can be 'global', 'local', 'builtin', 'attribute', 'keyword',
134
'imported', 'parameter_keyword'.
135
136
The `type` instance variable shows the approximate type of the
137
proposed object and can be 'instance', 'class', 'function', 'module',
138
and `None`.
139
140
All possible relations between proposal's `scope` and `type` are shown
141
in the table below (different scopes in rows and types in columns):
142
143
| instance | class | function | module | None
144
local | + | + | + | + |
145
global | + | + | + | + |
146
builtin | + | + | + | |
147
attribute | + | + | + | + |
148
imported | + | + | + | + |
149
keyword | | | | | +
150
parameter_keyword | | | | | +
151
152
"""
153
154
def __init__(self, name, scope, pyname=None):
155
self.name = name
156
self.scope = scope
157
self.pyname = pyname
158
if pyname is not None:
159
self.type = self._get_type()
160
else:
161
self.type = None
162
163
def __str__(self):
164
return '%s (%s, %s)' % (self.name, self.scope, self.type)
165
166
def __repr__(self):
167
return str(self)
168
169
@property
170
def parameters(self):
171
"""The names of the parameters the function takes.
172
173
Returns None if this completion is not a function.
174
"""
175
pyname = self.pyname
176
if isinstance(pyname, pynames.ImportedName):
177
pyname = pyname._get_imported_pyname()
178
if isinstance(pyname, pynames.DefinedName):
179
pyobject = pyname.get_object()
180
if isinstance(pyobject, pyobjects.AbstractFunction):
181
return pyobject.get_param_names()
182
183
def _get_type(self):
184
pyname = self.pyname
185
if isinstance(pyname, builtins.BuiltinName):
186
self.scope = 'builtin'
187
pyobject = pyname.get_object()
188
if isinstance(pyobject, builtins.BuiltinFunction):
189
return 'function'
190
elif isinstance(pyobject, builtins.BuiltinClass):
191
clsobj = pyobject.builtin
192
return 'class'
193
elif isinstance(pyobject, builtins.BuiltinObject) or \
194
isinstance(pyobject, builtins.BuiltinName):
195
return 'instance'
196
elif isinstance(pyname, pynames.ImportedModule):
197
self.scope = 'imported'
198
return 'module'
199
elif isinstance(pyname, pynames.ImportedName) or \
200
isinstance(pyname, pynames.DefinedName):
201
if isinstance(pyname, pynames.ImportedName):
202
self.scope = 'imported'
203
pyobject = pyname.get_object()
204
if isinstance(pyobject, pyobjects.AbstractFunction):
205
return 'function'
206
if isinstance(pyobject, pyobjects.AbstractClass):
207
return 'class'
208
return 'instance'
209
210
def get_doc(self):
211
"""Get the proposed object's docstring.
212
213
Returns None if it can not be get.
214
"""
215
if not self.pyname:
216
return None
217
pyobject = self.pyname.get_object()
218
if not hasattr(pyobject, 'get_doc'):
219
return None
220
return self.pyname.get_object().get_doc()
221
222
@property
223
def kind(self):
224
warnings.warn("the proposal's `kind` property is deprecated, " \
225
"use `scope` instead")
226
return self.scope
227
228
229
# leaved for backward compatibility
230
CodeAssistProposal = CompletionProposal
231
232
233
class NamedParamProposal(CompletionProposal):
234
"""A parameter keyword completion proposal
235
236
Holds reference to ``_function`` -- the function which
237
parameter ``name`` belongs to. This allows to determine
238
default value for this parameter.
239
"""
240
def __init__(self, name, function):
241
self.argname = name
242
name = '%s=' % name
243
super(NamedParamProposal, self).__init__(name, 'parameter_keyword')
244
self._function = function
245
246
def get_default(self):
247
"""Get a string representation of a param's default value.
248
249
Returns None if there is no default value for this param.
250
"""
251
definfo = functionutils.DefinitionInfo.read(self._function)
252
for arg, default in definfo.args_with_defaults:
253
if self.argname == arg:
254
return default
255
return None
256
257
258
def sorted_proposals(proposals, scopepref=None, typepref=None):
259
"""Sort a list of proposals
260
261
Return a sorted list of the given `CodeAssistProposal`\s.
262
263
`scopepref` can be a list of proposal scopes. Defaults to
264
``['parameter_keyword', 'local', 'global', 'imported',
265
'attribute', 'builtin', 'keyword']``.
266
267
`typepref` can be a list of proposal types. Defaults to
268
``['class', 'function', 'instance', 'module', None]``.
269
(`None` stands for completions with no type like keywords.)
270
"""
271
sorter = _ProposalSorter(proposals, scopepref, typepref)
272
return sorter.get_sorted_proposal_list()
273
274
275
def starting_expression(source_code, offset):
276
"""Return the expression to complete"""
277
word_finder = worder.Worder(source_code, True)
278
expression, starting, starting_offset = \
279
word_finder.get_splitted_primary_before(offset)
280
if expression:
281
return expression + '.' + starting
282
return starting
283
284
285
def default_templates():
286
warnings.warn('default_templates() is deprecated.',
287
DeprecationWarning, stacklevel=2)
288
return {}
289
290
291
class _PythonCodeAssist(object):
292
293
def __init__(self, project, source_code, offset, resource=None,
294
maxfixes=1, later_locals=True):
295
self.project = project
296
self.pycore = self.project.pycore
297
self.code = source_code
298
self.resource = resource
299
self.maxfixes = maxfixes
300
self.later_locals = later_locals
301
self.word_finder = worder.Worder(source_code, True)
302
self.expression, self.starting, self.offset = \
303
self.word_finder.get_splitted_primary_before(offset)
304
305
keywords = keyword.kwlist
306
307
def _find_starting_offset(self, source_code, offset):
308
current_offset = offset - 1
309
while current_offset >= 0 and (source_code[current_offset].isalnum() or
310
source_code[current_offset] in '_'):
311
current_offset -= 1;
312
return current_offset + 1
313
314
def _matching_keywords(self, starting):
315
result = []
316
for kw in self.keywords:
317
if kw.startswith(starting):
318
result.append(CompletionProposal(kw, 'keyword'))
319
return result
320
321
def __call__(self):
322
if self.offset > len(self.code):
323
return []
324
completions = list(self._code_completions().values())
325
if self.expression.strip() == '' and self.starting.strip() != '':
326
completions.extend(self._matching_keywords(self.starting))
327
return completions
328
329
def _dotted_completions(self, module_scope, holding_scope):
330
result = {}
331
found_pyname = rope.base.evaluate.eval_str(holding_scope,
332
self.expression)
333
if found_pyname is not None:
334
element = found_pyname.get_object()
335
compl_scope = 'attribute'
336
if isinstance(element, (pyobjectsdef.PyModule,
337
pyobjectsdef.PyPackage)):
338
compl_scope = 'imported'
339
for name, pyname in element.get_attributes().items():
340
if name.startswith(self.starting):
341
result[name] = CompletionProposal(name, compl_scope, pyname)
342
return result
343
344
def _undotted_completions(self, scope, result, lineno=None):
345
if scope.parent != None:
346
self._undotted_completions(scope.parent, result)
347
if lineno is None:
348
names = scope.get_propagated_names()
349
else:
350
names = scope.get_names()
351
for name, pyname in names.items():
352
if name.startswith(self.starting):
353
compl_scope = 'local'
354
if scope.get_kind() == 'Module':
355
compl_scope = 'global'
356
if lineno is None or self.later_locals or \
357
not self._is_defined_after(scope, pyname, lineno):
358
result[name] = CompletionProposal(name, compl_scope,
359
pyname)
360
361
def _from_import_completions(self, pymodule):
362
module_name = self.word_finder.get_from_module(self.offset)
363
if module_name is None:
364
return {}
365
pymodule = self._find_module(pymodule, module_name)
366
result = {}
367
for name in pymodule:
368
if name.startswith(self.starting):
369
result[name] = CompletionProposal(name, scope='global',
370
pyname=pymodule[name])
371
return result
372
373
def _find_module(self, pymodule, module_name):
374
dots = 0
375
while module_name[dots] == '.':
376
dots += 1
377
pyname = pynames.ImportedModule(pymodule,
378
module_name[dots:], dots)
379
return pyname.get_object()
380
381
def _is_defined_after(self, scope, pyname, lineno):
382
location = pyname.get_definition_location()
383
if location is not None and location[1] is not None:
384
if location[0] == scope.pyobject.get_module() and \
385
lineno <= location[1] <= scope.get_end():
386
return True
387
388
def _code_completions(self):
389
lineno = self.code.count('\n', 0, self.offset) + 1
390
fixer = fixsyntax.FixSyntax(self.pycore, self.code,
391
self.resource, self.maxfixes)
392
pymodule = fixer.get_pymodule()
393
module_scope = pymodule.get_scope()
394
code = pymodule.source_code
395
lines = code.split('\n')
396
result = {}
397
start = fixsyntax._logical_start(lines, lineno)
398
indents = fixsyntax._get_line_indents(lines[start - 1])
399
inner_scope = module_scope.get_inner_scope_for_line(start, indents)
400
if self.word_finder.is_a_name_after_from_import(self.offset):
401
return self._from_import_completions(pymodule)
402
if self.expression.strip() != '':
403
result.update(self._dotted_completions(module_scope, inner_scope))
404
else:
405
result.update(self._keyword_parameters(module_scope.pyobject,
406
inner_scope))
407
self._undotted_completions(inner_scope, result, lineno=lineno)
408
return result
409
410
def _keyword_parameters(self, pymodule, scope):
411
offset = self.offset
412
if offset == 0:
413
return {}
414
word_finder = worder.Worder(self.code, True)
415
lines = SourceLinesAdapter(self.code)
416
lineno = lines.get_line_number(offset)
417
if word_finder.is_on_function_call_keyword(offset - 1):
418
name_finder = rope.base.evaluate.ScopeNameFinder(pymodule)
419
function_parens = word_finder.\
420
find_parens_start_from_inside(offset - 1)
421
primary = word_finder.get_primary_at(function_parens - 1)
422
try:
423
function_pyname = rope.base.evaluate.\
424
eval_str(scope, primary)
425
except exceptions.BadIdentifierError, e:
426
return {}
427
if function_pyname is not None:
428
pyobject = function_pyname.get_object()
429
if isinstance(pyobject, pyobjects.AbstractFunction):
430
pass
431
elif isinstance(pyobject, pyobjects.AbstractClass) and \
432
'__init__' in pyobject:
433
pyobject = pyobject['__init__'].get_object()
434
elif '__call__' in pyobject:
435
pyobject = pyobject['__call__'].get_object()
436
if isinstance(pyobject, pyobjects.AbstractFunction):
437
param_names = []
438
param_names.extend(
439
pyobject.get_param_names(special_args=False))
440
result = {}
441
for name in param_names:
442
if name.startswith(self.starting):
443
result[name + '='] = NamedParamProposal(
444
name, pyobject
445
)
446
return result
447
return {}
448
449
450
class _ProposalSorter(object):
451
"""Sort a list of code assist proposals"""
452
453
def __init__(self, code_assist_proposals, scopepref=None, typepref=None):
454
self.proposals = code_assist_proposals
455
if scopepref is None:
456
scopepref = ['parameter_keyword', 'local', 'global', 'imported',
457
'attribute', 'builtin', 'keyword']
458
self.scopepref = scopepref
459
if typepref is None:
460
typepref = ['class', 'function', 'instance', 'module', None]
461
self.typerank = dict((type, index)
462
for index, type in enumerate(typepref))
463
464
def get_sorted_proposal_list(self):
465
"""Return a list of `CodeAssistProposal`"""
466
proposals = {}
467
for proposal in self.proposals:
468
proposals.setdefault(proposal.scope, []).append(proposal)
469
result = []
470
for scope in self.scopepref:
471
scope_proposals = proposals.get(scope, [])
472
scope_proposals = [proposal for proposal in scope_proposals
473
if proposal.type in self.typerank]
474
scope_proposals.sort(self._proposal_cmp)
475
result.extend(scope_proposals)
476
return result
477
478
def _proposal_cmp(self, proposal1, proposal2):
479
if proposal1.type != proposal2.type:
480
return cmp(self.typerank.get(proposal1.type, 100),
481
self.typerank.get(proposal2.type, 100))
482
return self._compare_underlined_names(proposal1.name,
483
proposal2.name)
484
485
def _compare_underlined_names(self, name1, name2):
486
def underline_count(name):
487
result = 0
488
while result < len(name) and name[result] == '_':
489
result += 1
490
return result
491
underline_count1 = underline_count(name1)
492
underline_count2 = underline_count(name2)
493
if underline_count1 != underline_count2:
494
return cmp(underline_count1, underline_count2)
495
return cmp(name1, name2)
496
497
498
class PyDocExtractor(object):
499
500
def get_doc(self, pyobject):
501
if isinstance(pyobject, pyobjects.AbstractFunction):
502
return self._get_function_docstring(pyobject)
503
elif isinstance(pyobject, pyobjects.AbstractClass):
504
return self._get_class_docstring(pyobject)
505
elif isinstance(pyobject, pyobjects.AbstractModule):
506
return self._trim_docstring(pyobject.get_doc())
507
return None
508
509
def get_calltip(self, pyobject, ignore_unknown=False, remove_self=False):
510
try:
511
if isinstance(pyobject, pyobjects.AbstractClass):
512
pyobject = pyobject['__init__'].get_object()
513
if not isinstance(pyobject, pyobjects.AbstractFunction):
514
pyobject = pyobject['__call__'].get_object()
515
except exceptions.AttributeNotFoundError:
516
return None
517
if ignore_unknown and not isinstance(pyobject, pyobjects.PyFunction):
518
return
519
if isinstance(pyobject, pyobjects.AbstractFunction):
520
result = self._get_function_signature(pyobject, add_module=True)
521
if remove_self and self._is_method(pyobject):
522
return result.replace('(self)', '()').replace('(self, ', '(')
523
return result
524
525
def _get_class_docstring(self, pyclass):
526
contents = self._trim_docstring(pyclass.get_doc(), 2)
527
supers = [super.get_name() for super in pyclass.get_superclasses()]
528
doc = 'class %s(%s):\n\n' % (pyclass.get_name(), ', '.join(supers)) + contents
529
530
if '__init__' in pyclass:
531
init = pyclass['__init__'].get_object()
532
if isinstance(init, pyobjects.AbstractFunction):
533
doc += '\n\n' + self._get_single_function_docstring(init)
534
return doc
535
536
def _get_function_docstring(self, pyfunction):
537
functions = [pyfunction]
538
if self._is_method(pyfunction):
539
functions.extend(self._get_super_methods(pyfunction.parent,
540
pyfunction.get_name()))
541
return '\n\n'.join([self._get_single_function_docstring(function)
542
for function in functions])
543
544
def _is_method(self, pyfunction):
545
return isinstance(pyfunction, pyobjects.PyFunction) and \
546
isinstance(pyfunction.parent, pyobjects.PyClass)
547
548
def _get_single_function_docstring(self, pyfunction):
549
signature = self._get_function_signature(pyfunction)
550
docs = self._trim_docstring(pyfunction.get_doc(), indents=2)
551
return signature + ':\n\n' + docs
552
553
def _get_super_methods(self, pyclass, name):
554
result = []
555
for super_class in pyclass.get_superclasses():
556
if name in super_class:
557
function = super_class[name].get_object()
558
if isinstance(function, pyobjects.AbstractFunction):
559
result.append(function)
560
result.extend(self._get_super_methods(super_class, name))
561
return result
562
563
def _get_function_signature(self, pyfunction, add_module=False):
564
location = self._location(pyfunction, add_module)
565
if isinstance(pyfunction, pyobjects.PyFunction):
566
info = functionutils.DefinitionInfo.read(pyfunction)
567
return location + info.to_string()
568
else:
569
return '%s(%s)' % (location + pyfunction.get_name(),
570
', '.join(pyfunction.get_param_names()))
571
572
def _location(self, pyobject, add_module=False):
573
location = []
574
parent = pyobject.parent
575
while parent and not isinstance(parent, pyobjects.AbstractModule):
576
location.append(parent.get_name())
577
location.append('.')
578
parent = parent.parent
579
if add_module:
580
if isinstance(pyobject, pyobjects.PyFunction):
581
module = pyobject.get_module()
582
location.insert(0, self._get_module(pyobject))
583
if isinstance(parent, builtins.BuiltinModule):
584
location.insert(0, parent.get_name() + '.')
585
return ''.join(location)
586
587
def _get_module(self, pyfunction):
588
module = pyfunction.get_module()
589
if module is not None:
590
resource = module.get_resource()
591
if resource is not None:
592
return pyfunction.pycore.modname(resource) + '.'
593
return ''
594
595
def _trim_docstring(self, docstring, indents=0):
596
"""The sample code from :PEP:`257`"""
597
if not docstring:
598
return ''
599
# Convert tabs to spaces (following normal Python rules)
600
# and split into a list of lines:
601
lines = docstring.expandtabs().splitlines()
602
# Determine minimum indentation (first line doesn't count):
603
indent = sys.maxint
604
for line in lines[1:]:
605
stripped = line.lstrip()
606
if stripped:
607
indent = min(indent, len(line) - len(stripped))
608
# Remove indentation (first line is special):
609
trimmed = [lines[0].strip()]
610
if indent < sys.maxint:
611
for line in lines[1:]:
612
trimmed.append(line[indent:].rstrip())
613
# Strip off trailing and leading blank lines:
614
while trimmed and not trimmed[-1]:
615
trimmed.pop()
616
while trimmed and not trimmed[0]:
617
trimmed.pop(0)
618
# Return a single string:
619
return '\n'.join((' ' * indents + line for line in trimmed))
620
621
622
# Deprecated classes
623
624
class TemplateProposal(CodeAssistProposal):
625
def __init__(self, name, template):
626
warnings.warn('TemplateProposal is deprecated.',
627
DeprecationWarning, stacklevel=2)
628
super(TemplateProposal, self).__init__(name, 'template')
629
self.template = template
630
631
632
class Template(object):
633
634
def __init__(self, template):
635
self.template = template
636
warnings.warn('Template is deprecated.',
637
DeprecationWarning, stacklevel=2)
638
639
def variables(self):
640
return []
641
642
def substitute(self, mapping):
643
return self.template
644
645
def get_cursor_location(self, mapping):
646
return len(self.template)
647
648