| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647 |
- import keyword
- import sys
- import warnings
- import rope.base.codeanalyze
- import rope.base.evaluate
- from rope.base import pyobjects, pyobjectsdef, pynames, builtins, exceptions, worder
- from rope.base.codeanalyze import SourceLinesAdapter
- from rope.contrib import fixsyntax
- from rope.refactor import functionutils
- def code_assist(project, source_code, offset, resource=None,
- templates=None, maxfixes=1, later_locals=True):
- """Return python code completions as a list of `CodeAssistProposal`\s
- `resource` is a `rope.base.resources.Resource` object. If
- provided, relative imports are handled.
- `maxfixes` is the maximum number of errors to fix if the code has
- errors in it.
- If `later_locals` is `False` names defined in this scope and after
- this line is ignored.
- """
- if templates is not None:
- warnings.warn('Codeassist no longer supports templates',
- DeprecationWarning, stacklevel=2)
- assist = _PythonCodeAssist(
- project, source_code, offset, resource=resource,
- maxfixes=maxfixes, later_locals=later_locals)
- return assist()
- def starting_offset(source_code, offset):
- """Return the offset in which the completion should be inserted
- Usually code assist proposals should be inserted like::
- completion = proposal.name
- result = (source_code[:starting_offset] +
- completion + source_code[offset:])
- Where starting_offset is the offset returned by this function.
- """
- word_finder = worder.Worder(source_code, True)
- expression, starting, starting_offset = \
- word_finder.get_splitted_primary_before(offset)
- return starting_offset
- def get_doc(project, source_code, offset, resource=None, maxfixes=1):
- """Get the pydoc"""
- fixer = fixsyntax.FixSyntax(project.pycore, source_code,
- resource, maxfixes)
- pymodule = fixer.get_pymodule()
- pyname = fixer.pyname_at(offset)
- if pyname is None:
- return None
- pyobject = pyname.get_object()
- return PyDocExtractor().get_doc(pyobject)
- def get_calltip(project, source_code, offset, resource=None,
- maxfixes=1, ignore_unknown=False, remove_self=False):
- """Get the calltip of a function
- The format of the returned string is
- ``module_name.holding_scope_names.function_name(arguments)``. For
- classes `__init__()` and for normal objects `__call__()` function
- is used.
- Note that the offset is on the function itself *not* after the its
- open parenthesis. (Actually it used to be the other way but it
- was easily confused when string literals were involved. So I
- decided it is better for it not to try to be too clever when it
- cannot be clever enough). You can use a simple search like::
- offset = source_code.rindex('(', 0, offset) - 1
- to handle simple situations.
- If `ignore_unknown` is `True`, `None` is returned for functions
- without source-code like builtins and extensions.
- If `remove_self` is `True`, the first parameter whose name is self
- will be removed for methods.
- """
- fixer = fixsyntax.FixSyntax(project.pycore, source_code,
- resource, maxfixes)
- pymodule = fixer.get_pymodule()
- pyname = fixer.pyname_at(offset)
- if pyname is None:
- return None
- pyobject = pyname.get_object()
- return PyDocExtractor().get_calltip(pyobject, ignore_unknown, remove_self)
- def get_definition_location(project, source_code, offset,
- resource=None, maxfixes=1):
- """Return the definition location of the python name at `offset`
- Return a (`rope.base.resources.Resource`, lineno) tuple. If no
- `resource` is given and the definition is inside the same module,
- the first element of the returned tuple would be `None`. If the
- location cannot be determined ``(None, None)`` is returned.
- """
- fixer = fixsyntax.FixSyntax(project.pycore, source_code,
- resource, maxfixes)
- pymodule = fixer.get_pymodule()
- pyname = fixer.pyname_at(offset)
- if pyname is not None:
- module, lineno = pyname.get_definition_location()
- if module is not None:
- return module.get_module().get_resource(), lineno
- return (None, None)
- def find_occurrences(*args, **kwds):
- import rope.contrib.findit
- warnings.warn('Use `rope.contrib.findit.find_occurrences()` instead',
- DeprecationWarning, stacklevel=2)
- return rope.contrib.findit.find_occurrences(*args, **kwds)
- class CompletionProposal(object):
- """A completion proposal
- The `scope` instance variable shows where proposed name came from
- and can be 'global', 'local', 'builtin', 'attribute', 'keyword',
- 'imported', 'parameter_keyword'.
- The `type` instance variable shows the approximate type of the
- proposed object and can be 'instance', 'class', 'function', 'module',
- and `None`.
- All possible relations between proposal's `scope` and `type` are shown
- in the table below (different scopes in rows and types in columns):
- | instance | class | function | module | None
- local | + | + | + | + |
- global | + | + | + | + |
- builtin | + | + | + | |
- attribute | + | + | + | + |
- imported | + | + | + | + |
- keyword | | | | | +
- parameter_keyword | | | | | +
- """
- def __init__(self, name, scope, pyname=None):
- self.name = name
- self.pyname = pyname
- self.scope = self._get_scope(scope)
- def __str__(self):
- return '%s (%s, %s)' % (self.name, self.scope, self.type)
- def __repr__(self):
- return str(self)
- @property
- def parameters(self):
- """The names of the parameters the function takes.
- Returns None if this completion is not a function.
- """
- pyname = self.pyname
- if isinstance(pyname, pynames.ImportedName):
- pyname = pyname._get_imported_pyname()
- if isinstance(pyname, pynames.DefinedName):
- pyobject = pyname.get_object()
- if isinstance(pyobject, pyobjects.AbstractFunction):
- return pyobject.get_param_names()
- @property
- def type(self):
- pyname = self.pyname
- if isinstance(pyname, builtins.BuiltinName):
- pyobject = pyname.get_object()
- if isinstance(pyobject, builtins.BuiltinFunction):
- return 'function'
- elif isinstance(pyobject, builtins.BuiltinClass):
- clsobj = pyobject.builtin
- return 'class'
- elif isinstance(pyobject, builtins.BuiltinObject) or \
- isinstance(pyobject, builtins.BuiltinName):
- return 'instance'
- elif isinstance(pyname, pynames.ImportedModule):
- return 'module'
- elif isinstance(pyname, pynames.ImportedName) or \
- isinstance(pyname, pynames.DefinedName):
- pyobject = pyname.get_object()
- if isinstance(pyobject, pyobjects.AbstractFunction):
- return 'function'
- if isinstance(pyobject, pyobjects.AbstractClass):
- return 'class'
- return 'instance'
- def _get_scope(self, scope):
- if isinstance(self.pyname, builtins.BuiltinName):
- return 'builtin'
- if isinstance(self.pyname, pynames.ImportedModule) or \
- isinstance(self.pyname, pynames.ImportedName):
- return 'imported'
- return scope
- def get_doc(self):
- """Get the proposed object's docstring.
- Returns None if it can not be get.
- """
- if not self.pyname:
- return None
- pyobject = self.pyname.get_object()
- if not hasattr(pyobject, 'get_doc'):
- return None
- return self.pyname.get_object().get_doc()
- @property
- def kind(self):
- warnings.warn("the proposal's `kind` property is deprecated, " \
- "use `scope` instead")
- return self.scope
- # leaved for backward compatibility
- CodeAssistProposal = CompletionProposal
- class NamedParamProposal(CompletionProposal):
- """A parameter keyword completion proposal
- Holds reference to ``_function`` -- the function which
- parameter ``name`` belongs to. This allows to determine
- default value for this parameter.
- """
- def __init__(self, name, function):
- self.argname = name
- name = '%s=' % name
- super(NamedParamProposal, self).__init__(name, 'parameter_keyword')
- self._function = function
- def get_default(self):
- """Get a string representation of a param's default value.
- Returns None if there is no default value for this param.
- """
- definfo = functionutils.DefinitionInfo.read(self._function)
- for arg, default in definfo.args_with_defaults:
- if self.argname == arg:
- return default
- return None
- def sorted_proposals(proposals, scopepref=None, typepref=None):
- """Sort a list of proposals
- Return a sorted list of the given `CodeAssistProposal`\s.
- `scopepref` can be a list of proposal scopes. Defaults to
- ``['parameter_keyword', 'local', 'global', 'imported',
- 'attribute', 'builtin', 'keyword']``.
- `typepref` can be a list of proposal types. Defaults to
- ``['class', 'function', 'instance', 'module', None]``.
- (`None` stands for completions with no type like keywords.)
- """
- sorter = _ProposalSorter(proposals, scopepref, typepref)
- return sorter.get_sorted_proposal_list()
- def starting_expression(source_code, offset):
- """Return the expression to complete"""
- word_finder = worder.Worder(source_code, True)
- expression, starting, starting_offset = \
- word_finder.get_splitted_primary_before(offset)
- if expression:
- return expression + '.' + starting
- return starting
- def default_templates():
- warnings.warn('default_templates() is deprecated.',
- DeprecationWarning, stacklevel=2)
- return {}
- class _PythonCodeAssist(object):
- def __init__(self, project, source_code, offset, resource=None,
- maxfixes=1, later_locals=True):
- self.project = project
- self.pycore = self.project.pycore
- self.code = source_code
- self.resource = resource
- self.maxfixes = maxfixes
- self.later_locals = later_locals
- self.word_finder = worder.Worder(source_code, True)
- self.expression, self.starting, self.offset = \
- self.word_finder.get_splitted_primary_before(offset)
- keywords = keyword.kwlist
- def _find_starting_offset(self, source_code, offset):
- current_offset = offset - 1
- while current_offset >= 0 and (source_code[current_offset].isalnum() or
- source_code[current_offset] in '_'):
- current_offset -= 1;
- return current_offset + 1
- def _matching_keywords(self, starting):
- result = []
- for kw in self.keywords:
- if kw.startswith(starting):
- result.append(CompletionProposal(kw, 'keyword'))
- return result
- def __call__(self):
- if self.offset > len(self.code):
- return []
- completions = list(self._code_completions().values())
- if self.expression.strip() == '' and self.starting.strip() != '':
- completions.extend(self._matching_keywords(self.starting))
- return completions
- def _dotted_completions(self, module_scope, holding_scope):
- result = {}
- found_pyname = rope.base.evaluate.eval_str(holding_scope,
- self.expression)
- if found_pyname is not None:
- element = found_pyname.get_object()
- compl_scope = 'attribute'
- if isinstance(element, (pyobjectsdef.PyModule,
- pyobjectsdef.PyPackage)):
- compl_scope = 'imported'
- for name, pyname in element.get_attributes().items():
- if name.startswith(self.starting):
- result[name] = CompletionProposal(name, compl_scope, pyname)
- return result
- def _undotted_completions(self, scope, result, lineno=None):
- if scope.parent != None:
- self._undotted_completions(scope.parent, result)
- if lineno is None:
- names = scope.get_propagated_names()
- else:
- names = scope.get_names()
- for name, pyname in names.items():
- if name.startswith(self.starting):
- compl_scope = 'local'
- if scope.get_kind() == 'Module':
- compl_scope = 'global'
- if lineno is None or self.later_locals or \
- not self._is_defined_after(scope, pyname, lineno):
- result[name] = CompletionProposal(name, compl_scope,
- pyname)
- def _from_import_completions(self, pymodule):
- module_name = self.word_finder.get_from_module(self.offset)
- if module_name is None:
- return {}
- pymodule = self._find_module(pymodule, module_name)
- result = {}
- for name in pymodule:
- if name.startswith(self.starting):
- result[name] = CompletionProposal(name, scope='global',
- pyname=pymodule[name])
- return result
- def _find_module(self, pymodule, module_name):
- dots = 0
- while module_name[dots] == '.':
- dots += 1
- pyname = pynames.ImportedModule(pymodule,
- module_name[dots:], dots)
- return pyname.get_object()
- def _is_defined_after(self, scope, pyname, lineno):
- location = pyname.get_definition_location()
- if location is not None and location[1] is not None:
- if location[0] == scope.pyobject.get_module() and \
- lineno <= location[1] <= scope.get_end():
- return True
- def _code_completions(self):
- lineno = self.code.count('\n', 0, self.offset) + 1
- fixer = fixsyntax.FixSyntax(self.pycore, self.code,
- self.resource, self.maxfixes)
- pymodule = fixer.get_pymodule()
- module_scope = pymodule.get_scope()
- code = pymodule.source_code
- lines = code.split('\n')
- result = {}
- start = fixsyntax._logical_start(lines, lineno)
- indents = fixsyntax._get_line_indents(lines[start - 1])
- inner_scope = module_scope.get_inner_scope_for_line(start, indents)
- if self.word_finder.is_a_name_after_from_import(self.offset):
- return self._from_import_completions(pymodule)
- if self.expression.strip() != '':
- result.update(self._dotted_completions(module_scope, inner_scope))
- else:
- result.update(self._keyword_parameters(module_scope.pyobject,
- inner_scope))
- self._undotted_completions(inner_scope, result, lineno=lineno)
- return result
- def _keyword_parameters(self, pymodule, scope):
- offset = self.offset
- if offset == 0:
- return {}
- word_finder = worder.Worder(self.code, True)
- lines = SourceLinesAdapter(self.code)
- lineno = lines.get_line_number(offset)
- if word_finder.is_on_function_call_keyword(offset - 1):
- name_finder = rope.base.evaluate.ScopeNameFinder(pymodule)
- function_parens = word_finder.\
- find_parens_start_from_inside(offset - 1)
- primary = word_finder.get_primary_at(function_parens - 1)
- try:
- function_pyname = rope.base.evaluate.\
- eval_str(scope, primary)
- except exceptions.BadIdentifierError, e:
- return {}
- if function_pyname is not None:
- pyobject = function_pyname.get_object()
- if isinstance(pyobject, pyobjects.AbstractFunction):
- pass
- elif isinstance(pyobject, pyobjects.AbstractClass) and \
- '__init__' in pyobject:
- pyobject = pyobject['__init__'].get_object()
- elif '__call__' in pyobject:
- pyobject = pyobject['__call__'].get_object()
- if isinstance(pyobject, pyobjects.AbstractFunction):
- param_names = []
- param_names.extend(
- pyobject.get_param_names(special_args=False))
- result = {}
- for name in param_names:
- if name.startswith(self.starting):
- result[name + '='] = NamedParamProposal(
- name, pyobject
- )
- return result
- return {}
- class _ProposalSorter(object):
- """Sort a list of code assist proposals"""
- def __init__(self, code_assist_proposals, scopepref=None, typepref=None):
- self.proposals = code_assist_proposals
- if scopepref is None:
- scopepref = ['parameter_keyword', 'local', 'global', 'imported',
- 'attribute', 'builtin', 'keyword']
- self.scopepref = scopepref
- if typepref is None:
- typepref = ['class', 'function', 'instance', 'module', None]
- self.typerank = dict((type, index)
- for index, type in enumerate(typepref))
- def get_sorted_proposal_list(self):
- """Return a list of `CodeAssistProposal`"""
- proposals = {}
- for proposal in self.proposals:
- proposals.setdefault(proposal.scope, []).append(proposal)
- result = []
- for scope in self.scopepref:
- scope_proposals = proposals.get(scope, [])
- scope_proposals = [proposal for proposal in scope_proposals
- if proposal.type in self.typerank]
- scope_proposals.sort(self._proposal_cmp)
- result.extend(scope_proposals)
- return result
- def _proposal_cmp(self, proposal1, proposal2):
- if proposal1.type != proposal2.type:
- return cmp(self.typerank.get(proposal1.type, 100),
- self.typerank.get(proposal2.type, 100))
- return self._compare_underlined_names(proposal1.name,
- proposal2.name)
- def _compare_underlined_names(self, name1, name2):
- def underline_count(name):
- result = 0
- while result < len(name) and name[result] == '_':
- result += 1
- return result
- underline_count1 = underline_count(name1)
- underline_count2 = underline_count(name2)
- if underline_count1 != underline_count2:
- return cmp(underline_count1, underline_count2)
- return cmp(name1, name2)
- class PyDocExtractor(object):
- def get_doc(self, pyobject):
- if isinstance(pyobject, pyobjects.AbstractFunction):
- return self._get_function_docstring(pyobject)
- elif isinstance(pyobject, pyobjects.AbstractClass):
- return self._get_class_docstring(pyobject)
- elif isinstance(pyobject, pyobjects.AbstractModule):
- return self._trim_docstring(pyobject.get_doc())
- return None
- def get_calltip(self, pyobject, ignore_unknown=False, remove_self=False):
- try:
- if isinstance(pyobject, pyobjects.AbstractClass):
- pyobject = pyobject['__init__'].get_object()
- if not isinstance(pyobject, pyobjects.AbstractFunction):
- pyobject = pyobject['__call__'].get_object()
- except exceptions.AttributeNotFoundError:
- return None
- if ignore_unknown and not isinstance(pyobject, pyobjects.PyFunction):
- return
- if isinstance(pyobject, pyobjects.AbstractFunction):
- result = self._get_function_signature(pyobject, add_module=True)
- if remove_self and self._is_method(pyobject):
- return result.replace('(self)', '()').replace('(self, ', '(')
- return result
- def _get_class_docstring(self, pyclass):
- contents = self._trim_docstring(pyclass.get_doc(), 2)
- supers = [super.get_name() for super in pyclass.get_superclasses()]
- doc = 'class %s(%s):\n\n' % (pyclass.get_name(), ', '.join(supers)) + contents
- if '__init__' in pyclass:
- init = pyclass['__init__'].get_object()
- if isinstance(init, pyobjects.AbstractFunction):
- doc += '\n\n' + self._get_single_function_docstring(init)
- return doc
- def _get_function_docstring(self, pyfunction):
- functions = [pyfunction]
- if self._is_method(pyfunction):
- functions.extend(self._get_super_methods(pyfunction.parent,
- pyfunction.get_name()))
- return '\n\n'.join([self._get_single_function_docstring(function)
- for function in functions])
- def _is_method(self, pyfunction):
- return isinstance(pyfunction, pyobjects.PyFunction) and \
- isinstance(pyfunction.parent, pyobjects.PyClass)
- def _get_single_function_docstring(self, pyfunction):
- signature = self._get_function_signature(pyfunction)
- docs = self._trim_docstring(pyfunction.get_doc(), indents=2)
- return signature + ':\n\n' + docs
- def _get_super_methods(self, pyclass, name):
- result = []
- for super_class in pyclass.get_superclasses():
- if name in super_class:
- function = super_class[name].get_object()
- if isinstance(function, pyobjects.AbstractFunction):
- result.append(function)
- result.extend(self._get_super_methods(super_class, name))
- return result
- def _get_function_signature(self, pyfunction, add_module=False):
- location = self._location(pyfunction, add_module)
- if isinstance(pyfunction, pyobjects.PyFunction):
- info = functionutils.DefinitionInfo.read(pyfunction)
- return location + info.to_string()
- else:
- return '%s(%s)' % (location + pyfunction.get_name(),
- ', '.join(pyfunction.get_param_names()))
- def _location(self, pyobject, add_module=False):
- location = []
- parent = pyobject.parent
- while parent and not isinstance(parent, pyobjects.AbstractModule):
- location.append(parent.get_name())
- location.append('.')
- parent = parent.parent
- if add_module:
- if isinstance(pyobject, pyobjects.PyFunction):
- module = pyobject.get_module()
- location.insert(0, self._get_module(pyobject))
- if isinstance(parent, builtins.BuiltinModule):
- location.insert(0, parent.get_name() + '.')
- return ''.join(location)
- def _get_module(self, pyfunction):
- module = pyfunction.get_module()
- if module is not None:
- resource = module.get_resource()
- if resource is not None:
- return pyfunction.pycore.modname(resource) + '.'
- return ''
- def _trim_docstring(self, docstring, indents=0):
- """The sample code from :PEP:`257`"""
- if not docstring:
- return ''
- # Convert tabs to spaces (following normal Python rules)
- # and split into a list of lines:
- lines = docstring.expandtabs().splitlines()
- # Determine minimum indentation (first line doesn't count):
- indent = sys.maxint
- for line in lines[1:]:
- stripped = line.lstrip()
- if stripped:
- indent = min(indent, len(line) - len(stripped))
- # Remove indentation (first line is special):
- trimmed = [lines[0].strip()]
- if indent < sys.maxint:
- for line in lines[1:]:
- trimmed.append(line[indent:].rstrip())
- # Strip off trailing and leading blank lines:
- while trimmed and not trimmed[-1]:
- trimmed.pop()
- while trimmed and not trimmed[0]:
- trimmed.pop(0)
- # Return a single string:
- return '\n'.join((' ' * indents + line for line in trimmed))
- # Deprecated classes
- class TemplateProposal(CodeAssistProposal):
- def __init__(self, name, template):
- warnings.warn('TemplateProposal is deprecated.',
- DeprecationWarning, stacklevel=2)
- super(TemplateProposal, self).__init__(name, 'template')
- self.template = template
- class Template(object):
- def __init__(self, template):
- self.template = template
- warnings.warn('Template is deprecated.',
- DeprecationWarning, stacklevel=2)
- def variables(self):
- return []
- def substitute(self, mapping):
- return self.template
- def get_cursor_location(self, mapping):
- return len(self.template)
|