Skip to content

Commit

Permalink
- added a lot more caching to Rope
Browse files Browse the repository at this point in the history
- added case-insensitive completion
  • Loading branch information
Julian Eberius committed Feb 5, 2013
1 parent 1ff2dba commit 85c490c
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 16 deletions.
3 changes: 2 additions & 1 deletion lib/python2/rope/base/project.py
Expand Up @@ -18,6 +18,7 @@ def __init__(self, fscommands):
self.prefs = prefs.Prefs()
self.data_files = _DataFiles(self)

@utils.memoize
def get_resource(self, resource_name):
"""Get a resource in a project.
Expand Down Expand Up @@ -360,7 +361,7 @@ def _get_file(self, name, compress):
path += '.gz'
return self.project.get_file(path)


@utils.cached(1000)
def _realpath(path):
"""Return the real path of `path`
Expand Down
2 changes: 2 additions & 0 deletions lib/python2/rope/base/pycore.py
Expand Up @@ -183,6 +183,7 @@ def _find_module(self, modname, folder=None):
# packages, that is most of the time
# - We need a separate resource observer; `self.observer`
# does not get notified about module and folder creations
@utils.memoize
def get_source_folders(self):
"""Returns project source folders"""
if self.project.root is None:
Expand All @@ -206,6 +207,7 @@ def _is_package(self, folder):
else:
return False

@utils.memoize
def _find_source_folders(self, folder):
for resource in folder.get_folders():
if self._is_package(resource):
Expand Down
11 changes: 8 additions & 3 deletions lib/python2/rope/base/pynames.py
Expand Up @@ -101,6 +101,7 @@ def __init__(self, importing_module, module_name=None,
self.level = level
self.resource = resource
self.pymodule = _get_concluded_data(self.importing_module)
self.cached_pyobject = None

def _current_folder(self):
resource = self.importing_module.get_module().get_resource()
Expand All @@ -127,9 +128,13 @@ def _get_pymodule(self):
return self.pymodule.get()

def get_object(self):
if self._get_pymodule() is None:
return rope.base.pyobjects.get_unknown()
return self._get_pymodule()
if not self.cached_pyobject:
pymod = self._get_pymodule()
if pymod is None:
self.cached_pyobject = rope.base.pyobjects.get_unknown()
else:
self.cached_pyobject = pymod
return self.cached_pyobject

def get_definition_location(self):
pymodule = self._get_pymodule()
Expand Down
48 changes: 46 additions & 2 deletions lib/python2/rope/base/utils.py
@@ -1,5 +1,5 @@
import warnings

from functools import partial

def saveit(func):
"""A decorator that caches the return value of a function"""
Expand Down Expand Up @@ -52,7 +52,6 @@ def newfunc(*args, **kwds):
return newfunc
return _decorator


def cached(count):
"""A caching decorator based on parameter objects"""
def decorator(func):
Expand All @@ -76,3 +75,48 @@ def __call__(self, *args, **kwds):
if len(self.cache) > self.count:
del self.cache[0]
return result

class memoize(object):
"""cache the return value of a method
This class is meant to be used as a decorator of methods. The return value
from a given method invocation will be cached on the instance whose method
was invoked. All arguments passed to a method decorated with memoize must
be hashable.
If a memoized method is invoked directly on its class the result will not
be cached. Instead the method will be invoked like a static method:
class Obj(object):
@memoize
def add_to(self, arg):
return self + arg
Obj.add_to(1) # not enough arguments
Obj.add_to(1, 2) # returns 3, result is not cached
"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if obj is None:
return self.func
return partial(self, obj)
def __call__(self, *args, **kw):
obj = args[0]
try:
cache = obj.__cache
except AttributeError:
cache = obj.__cache = {}
key = (self.func, args[1:], frozenset(kw.items()))
try:
res = cache[key]
except KeyError:
res = cache[key] = self.func(*args, **kw)
return res

def lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
@property
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
30 changes: 20 additions & 10 deletions lib/python2/rope/contrib/codeassist.py
Expand Up @@ -4,14 +4,14 @@

import rope.base.codeanalyze
import rope.base.evaluate
from rope.base import pyobjects, pyobjectsdef, pynames, builtins, exceptions, worder
from rope.base import pyobjects, pyobjectsdef, pynames, builtins, exceptions, worder, utils
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):
templates=None, maxfixes=1, later_locals=True, case_sensitive=False):
"""Return python code completions as a list of `CodeAssistProposal`\s
`resource` is a `rope.base.resources.Resource` object. If
Expand All @@ -29,7 +29,7 @@ def code_assist(project, source_code, offset, resource=None,
DeprecationWarning, stacklevel=2)
assist = _PythonCodeAssist(
project, source_code, offset, resource=resource,
maxfixes=maxfixes, later_locals=later_locals)
maxfixes=maxfixes, later_locals=later_locals, case_sensitive=case_sensitive)
return assist()


Expand Down Expand Up @@ -176,7 +176,7 @@ def parameters(self):
if isinstance(pyobject, pyobjects.AbstractFunction):
return pyobject.get_param_names()

@property
@utils.lazyprop
def type(self):
pyname = self.pyname
if isinstance(pyname, builtins.BuiltinName):
Expand Down Expand Up @@ -289,16 +289,26 @@ def default_templates():
return {}


def _startswith(s1, s2):
return s1.startswith(s2)


def _case_insensitive_startswith(s1, s2):
return s1.lower().startswith(s2.lower())


class _PythonCodeAssist(object):

def __init__(self, project, source_code, offset, resource=None,
maxfixes=1, later_locals=True):
maxfixes=1, later_locals=True, case_sensitive=False):
self.project = project
self.pycore = self.project.pycore
self.code = source_code
self.resource = resource
self.maxfixes = maxfixes
self.later_locals = later_locals
self.case_sensitive = case_sensitive
self.startswith = _startswith if case_sensitive else _case_insensitive_startswith
self.word_finder = worder.Worder(source_code, True)
self.expression, self.starting, self.offset = \
self.word_finder.get_splitted_primary_before(offset)
Expand All @@ -315,7 +325,7 @@ def _find_starting_offset(self, source_code, offset):
def _matching_keywords(self, starting):
result = []
for kw in self.keywords:
if kw.startswith(starting):
if self.startswith(kw, starting):
result.append(CompletionProposal(kw, 'keyword'))
return result

Expand All @@ -338,7 +348,7 @@ def _dotted_completions(self, module_scope, holding_scope):
pyobjectsdef.PyPackage)):
compl_scope = 'imported'
for name, pyname in element.get_attributes().items():
if name.startswith(self.starting):
if self.startswith(name, self.starting):
result[name] = CompletionProposal(name, compl_scope, pyname)
return result

Expand All @@ -350,7 +360,7 @@ def _undotted_completions(self, scope, result, lineno=None):
else:
names = scope.get_names()
for name, pyname in names.items():
if name.startswith(self.starting):
if self.startswith(name, self.starting):
compl_scope = 'local'
if scope.get_kind() == 'Module':
compl_scope = 'global'
Expand All @@ -366,7 +376,7 @@ def _from_import_completions(self, pymodule):
pymodule = self._find_module(pymodule, module_name)
result = {}
for name in pymodule:
if name.startswith(self.starting):
if self.startswith(name, self.starting):
result[name] = CompletionProposal(name, scope='global',
pyname=pymodule[name])
return result
Expand Down Expand Up @@ -440,7 +450,7 @@ def _keyword_parameters(self, pymodule, scope):
pyobject.get_param_names(special_args=False))
result = {}
for name in param_names:
if name.startswith(self.starting):
if self.startswith(name, self.starting):
result[name + '='] = NamedParamProposal(
name, pyobject
)
Expand Down

0 comments on commit 85c490c

Please sign in to comment.