Commits

Ali Gholami Rudi  committed 7f34572 Merge

merged with main

  • Participants
  • Parent commits dd0176f, 6b57305

Comments (0)

Files changed (94)

 ef7c349beeed1d763e545bce89754cb0154b837c 0.7.8
 b5ee6eb7f245e9713a5a1e45892ce9e5021680f8 0.7.9
 19960b1c3511de606ede66125edb487d299b4eac 0.8
+4b4db07361c5f09f5f3efd419bdb9cdfdac17b97 0.8.1
+31646f5ab763f85df9b01ba025f1475ca4db324a 0.8.2

File README.txt

File contents unchanged.

File docs/contributing.txt

 Get Involved!
 =============
 
-Rope was started because of the lack of good refactoring tools for
-python.  But after observing python IDE limitations we grew bigger
-ideas in our heads.
-
-If you want to have a more powerful python IDE and have big ideas
-in your head, you are welcome to get involved.
+Rope's main goal is being a good refactoring tool for python.  It also
+provides some IDE helpers.  If you like to contribute, you're welcome!
 
 
 How to Help Rope?
 Wish List
 =========
 
-If you're interested you are welcome to discuss your ideas in
-`rope-dev (at) googlegroups.com`_ mailing list or send your patches.
-Here is only a list of suggestions.
+You are welcome to send your patches to `rope-dev (at)
+googlegroups.com`_ mailing list.  Here is only a list of suggestions.
 
 Issues
 ------
 Write Plugins For Other IDEs
 ----------------------------
 
-See ropemacs_ and ropeide_.
+See ropemacs_, ropevim_, eric_ and ropeide_.
 
 
 .. _ropemacs: http://rope.sf.net/ropemacs.html
+.. _ropevim: http://rope.sf.net/ropevim.html
 .. _ropeide: http://rope.sf.net/ropeide.html
+.. _eric4: http://www.die-offenbachs.de/eric/index.html
 
 
 Rope Structure
 ==============
 
-Rope package structure::
+Rope package structure:
 
-  rope
-    base
-    refactor
-    contrib
+* `rope.base`: the base part of rope
+* `rope.refactor`: refactorings and tools used in them
+* `rope.contrib`: IDE helpers
 
-Have a look at ``__init__.py`` of packages for finding their
-description.  Also have a look at `library.txt`_ for a few
-examples.
+Have a look at ``__init__.py`` of these packages or `library.txt`_ for
+more information.
 
 .. _`library.txt`: library.html
 
 Submitting patches
 ==================
 
-Patches to rope's code are welcome.
+Patches are welcome.
 
 Patch style
 -----------
 * Use four spaces for indentation.
 * Include good unit-tests if possible.
 * Rope test suite should pass after patching
+* Use ``hg export`` format to preserve your identity

File docs/dev/issues.txt

 Unresolved Issues
 =================
 
+* purging out less accurate callinfos when better ones appear?
+* using properties without calling its get?
+* global variable inlines
+* transform and extension modules
 * merging extract and usefunction
 * caching instances of PyObject
 * moving a group of elements together
 * unignored files that are not under version control
 * inline fails when there is an arg mismatch
 * evaluate function parameter defaults in staticoi?
-* recursive soi; go where the calls go with limited depth
 * saving diffs instead of old contents in ChangeContents?
 * handling tuple parameters
 * extract class
 
 Consider a restructuring like this::
 
-  pattern: ${?a} if ${?b} else ${?c}
+  pattern: ${a} if ${b} else ${c}
   goal: replacement
-  before: if ${?b}:\n    replacement = ${?a}\nelse:\n    replacement = ${?c}
+  before: if ${b}:\n    replacement = ${a}\nelse:\n    replacement = ${c}
 
 
 Memory Management

File docs/dev/todo.txt

 > Public Release 1.0
 
 
-> Public Release 0.8.1
+> Public Release 0.8.3

File docs/done.txt

 ===========
 
 
+> Public Release 0.8.2 : May 10, 2008
+
+
+- inlining parameters : May 10, 2008
+
+
+- automatic default insertion in change signature : May 10, 2008
+
+
+- adding underlined parameter to `AutoImport` : May 7, 2008
+
+
+- added `rope.contrib.findit.find_implementations()` : April 28, 2008
+
+
+- moved `find_occurrences()` to `rope.contrib.findit` : April 25, 2008
+
+
+> Public Release 0.8.1 : April 20, 2008
+
+
+- added GIT support in fscommands : April 19, 2008
+
+
+- back importing underlined names in move : April 19, 2008
+
+
+- added `codeassist.get_calltip()` : April 12, 2008
+
+
+- added `libutils.analyze_modules()` : April 12, 2008
+
+
+- added ``soa_followed_calls`` project config : April 11, 2008
+
+
+- `libutils.report_change()` reads `automatic_soa` : April 10, 2008
+
+
+- SOA can follow functions : April 10, 2008
+
+
+- better handling of for, with and except variables : April 7, 2008
+
+
+- not reparsing unchanged modules for code assists : April 6, 2008
+
+
+- handling property as decorator : April 5, 2008
+
+
 > Public Release 0.8 : April 5, 2008
 
 

File docs/library.txt

   information.
 * All configurations that are available in the ``config.py`` file can
   be specified as keyword parameters to `Project` constructor.  These
-  parameters overwrite the ones in the ``config.py`` file.
+  parameters override the ones in the ``config.py`` file.
 * Each project has a set of ignored resource patterns; You can use it
-  to ask rope to ignore files and folders matching certain patterns.
+  to tell rope to ignore files and folders matching certain patterns.
+* The ``.ropeproject`` folder can be safely copied in other clones of
+  a project if you don't want to lose your objectdb and history.
 
 
 Library Utilities
 (we'll talk about them later) use resources.
 
 In order to create a `Resource` for a path in a project we have two
-options.  The first approach uses the `Project` object (Use
+options.  The first approach uses the `Project` object (use
 `Project.get_resource()`_ method).  I prefer to describe the second
 approach since it needs less to know.
 
   myresource = libutils.path_to_resource(myproject, '/path/to/resource')
 
 
-But this is only half of the answer.  Consider we have a resource.  How
-can we know anything about it? The answer is to use its ``path`` and
-``real_path`` fields.  `Resource.real_path` is the path of the
-resource in the file-system.  The `Resource.path` field contains the
-address of a resource relative to project root (The same format as
+But this is only half of the answer.  Consider we have a resource.
+How can we know anything about it? The answer is to use its ``path``
+and ``real_path`` fields.  `Resource.real_path` is the absolute path
+of the resource in the file-system.  `Resource.path` field contains
+the address of a resource relative to project root (the same format as
 needed by `Project.get_resource()`_).
 
 
 This is actually easier for IDEs, since most GUI libraries do that
 when calculating offsets.
 
-Next we need to perform the refactoring.  IDE's usually pop up a
-dialog for letting the user configure refactoring option like the name
-of the extracted variable.  Next we need to calculate the changes::
+Next, IDE's usually pop up a dialog for letting the user configure
+refactoring options like the name of the extracted variable.
+
+After that, we can calculate the changes::
 
   changes = extractor.get_changes('extracted_variable')
 
 Validating The Project
 ----------------------
 
-When using rope as a library you probably change the files in it in
+When using rope as a library, you probably change the files in it in
 parallel (for example in IDEs).  To force rope to invalidate cached
 information about resources that have been removed or changed outside
 rope you should call `Project.validate()`_ method.  You can pass a
 for instance).
 
 
-Activating Static Object Inference
-----------------------------------
+Activating Static Object Analysis
+---------------------------------
 
-One of the greatest strengths of rope is its static object inference,
-SOI.  You can perform SOI on a module using `PyCore.analyze_module()`
-method but performing SOI on a module is not cheap.  So I decided
-that the best time for performing SOI is when saving files and only
+One of the greatest strengths of rope is its static object analysis,
+SOA.  You can perform SOA on a module using `PyCore.analyze_module()`
+method but performing SOA on a module is not cheap.  So I decided that
+the best time for performing SOA is when saving files and only
 performing it on changed scopes.
 
 But since rope is not notified about the changes the IDE performs, you
   Extract methods/variables.
 
 * `rope.refactor.inline`:
-  Inline occurrences of a method/variable.
+  Inline occurrences of a method/variable/parameter.
 
 * `rope.refactor.usefunction`:
   Try to use a function wherever possible.
   # Alternatively you can use `generate` module.
   # Creating modules and packages using `generate` module
   >>> from rope.contrib import generate
-  >>> pycore = project.get_pycore()
+  >>> pycore = project.pycore
   >>> pkg = generate.create_package(project, 'pkg')
   >>> mod2 = generate.create_module(project, 'mod2', pkg)
   >>> mod2.write('import mod1\nprint mod1.a_var\n')
 
 See pydocs and source code for more information (other functions in
 this module might be interesting, too; like `get_doc`,
-`get_definition_location` and `find_occurrences`).
+`get_definition_location`).
+
+
+`rope.contrib.findit`
+---------------------
+
+`findit` module provides `find_occurrences()` for finding occurrences
+of a name.  Also `find_implementations()` function finds the places in
+which a method is overridden.
 
 
 `rope.contrib.autoimport`

File docs/overview.txt

 want to feel the power of rope try its features and see its unit
 tests.
 
-This file is more suitable for the users.  Developers that plan to use
+This file is more suitable for the users.  Developers who plan to use
 rope as a library might find library.txt_ more useful.
 
-Most of this document was written before breaking rope and ropeide
-into two separate projects.  That's why some parts of this file
-specifically talks about ropeide_.  Also most of the keys mentioned in
-this file are the ones that are used in ropeide_ but ropemacs_ uses
-similar keys most of the time.
-
 .. contents:: Table of Contents
-.. _ropeide: http://rope.sf.net/ropeide.html
-.. _ropemacs: http://rope.sf.net/ropemacs.html
 .. _library.txt: library.html
 
 
 =======================
 
 Rope uses a folder inside projects for holding project configuration
-and data.  Its default name is ``.ropeproject``, but it can be changed
-in ``~/.ropeide`` or `Project` constructor (if using rope as a
-library).  You can also force rope not to make such a folder by using
-`None` instead of a `str`.
+and data.  Its default name is ``.ropeproject``, but it can be
+changed (you can even tell rope not to create this folder).
 
 Currently it is used for things such as:
 
 * There is a ``config.py`` file in this folder in which you can change
   project configurations.  Have look at the default ``config.py`` file
-  (is created when it does not exist) for more information.  (``C-x p
-  c`` open this file automatically in ropemacs_ or ropeide_).
+  (is created when it does not exist) for more information.
 * It can be used for saving project history, so that the next time you
-  open the project you can see and undo past changes.  (see ``project
-  history`` of ropeide_)
+  open the project you can undo past changes.
 * It can be used for saving object information to help rope object
   inference.
 * It can be used for saving global names cache which is used in
 Renaming Function Keyword Parameters
 ------------------------------------
 
-We have::
+On::
 
   def a_func(a_param):
       print a_param
   a_func(a_param=10)
   a_func(10)
 
-After performing rename refactoring on any occurrence of ``a_param``
-we will have::
+performing rename refactoring on any occurrence of ``a_param`` will
+result in::
 
   def a_func(new_param):
       print new_param
 --------------------------------------------
 
 You can tell rope to rename all occurrences of a name in comments and
-strings.  This can be done in rename refactoring dialog of ropeide_ by
-selecting its radio button or by passing ``docs=True`` to
-`Rename.get_changes()` method when using rope as a library.  Rope
-renames names in comments and strings only where the name is visible.
-For example in::
+strings.  This can be done by passing ``docs=True`` to
+`Rename.get_changes()` method.  Rope renames names in comments and
+strings only where the name is visible.  For example in::
 
   def f():
       a_var = 1
   # f prints a_var
 
 This makes it safe to assume that this option does not perform wrong
-renames most of the time and for this reason it is on by default in
-the ropeide_ (though not in `Rename.get_changes()`).
+renames most of the time.
 
 This also changes occurrences inside evaluated strings::
 
 Extract Method
 --------------
 
-The position of the mark would be region start and the current
-cursor position would be region end. (You can set the mark as in
-emacs copy and paste with ``C-space``). ::
+In these examples ``${region_start}`` and ``${region_end}`` show the
+selected region for extraction::
 
   def a_func():
       a = 1
           return a * 2
 
 
-Change Method Signature
------------------------
-
-In the change method signature dialog (for ropeide_) these shortcuts
-work:
-
-======  ======================
-key     binding
-======  ======================
-C-n     move downward
-C-p     move upward
-M-n     move parameter down
-M-p     move parameter up
-M-r     remove parameter
-M-a     add new parameter
-======  ======================
-
-The ``value`` field in add new parameter dialog changes all calls
-to pass ``value`` as this new parameter if it is non-empty.  You
-can do the same thing for existing arguments using inline argument
-default value.
-
-Inline argument default value changes all function calls that don't
-pass any value as this argument to pass the default value specified
-in function definition.
-
-While reordering arguments you should consider the python
-language order for argument types (i.e. : normal args, args with
-defaults, ``*args``, ``**keywords``).  Rope won't complain if you
-don't but python will.
-
-
 Inline Method Refactoring
 -------------------------
 
   hello = 'Saying hello to %s from %s' % ('Rope', C.__name__)
 
 
+Inlining Parameters
+-------------------
+
+`rope.refactor.inline.create_inline()` creates an `InlineParameter`
+object when it is performed on a parameter.  It passes the default
+value of the parameter wherever its function is called without passing
+it.  For instance in::
+
+  def f(p1=1, p2=1):
+      pass
+
+  f(3)
+  f()
+  f(3, 4)
+
+after inlining p2 parameter will have::
+
+  def f(p1=1, p2=1):
+      pass
+
+  f(3, 2)
+  f(p2=2)
+  f(3, 4)
+
+
 Use Function Refactoring
 ------------------------
 
   another_var = mod1.square(4)
 
 
+Automatic Default Insertion In Change Signature
+-----------------------------------------------
+
+The `rope.refactor.change_signature.ArgumentReorderer` signature
+changer takes a parameter called ``autodef``.  If not `None`, its
+value is used whenever rope needs to insert a default for a parameter
+(that happens when an argument without default is moved after another
+that has a default value).  For instance in::
+
+  def f(p1, p2=2):
+      pass
+
+if we reorder using::
+
+  changers = [ArgumentReorderer([1, 0], autodef='1')]
+
+will result in::
+
+  def f(p2=2, p1=1):
+      pass
+
+
 Sorting Imports
 ---------------
 
-Organize imports now sorts imports, too.  It will sort imports
-according to :PEP:`8`::
+Organize imports sorts imports, too.  It does that according to
+:PEP:`8`::
 
   [__future__ imports]
 
 more than 27 characters to be long.
 
 
-Import Actions On Individual Imports
-------------------------------------
-
-You can perform import actions on individual imports by using action
-prefix.  For instance for expanding an star import just move to that
-line and use ``C-u C-c i x``.
-
-
 Stoppable Refactorings
 ----------------------
 
 Some refactorings might take a long time to finish (based on the size
-of your project).  Ropeide_ shows a dialog that has a progress bar and
-a stop button for these refactorings (ropemacs uses something similar,
-also).  You can also use this feature when using rope as a library.
-The `get_changes()` method of these refactorings take a new parameter
-called `task_handle`.  If you want to monitor or stop these
-refactoring you can pass a `rope.refactor.  taskhandle.TaskHandle` to
-this method.  See `rope.refactor.taskhandle` module for more
-information.
+of your project).  The `get_changes()` method of these refactorings
+take a parameter called `task_handle`.  If you want to monitor or stop
+these refactoring you can pass a `rope.refactor.
+taskhandle.TaskHandle` to this method.  See `rope.refactor.taskhandle`
+module for more information.
 
 
 Basic Implicit Interfaces
 
 `rope.refactor.restructure` can be used for performing restructurings.
 A restructuring is a program transformation; not as well defined as
-other refactorings like rename.  Let's see some examples.
+other refactorings like rename.  Let's see some examples (for more
+examples, see the pydocs in `rope.base.restructure` module).
 
 Example 1
 '''''''''
 
   pattern: pow(${param1}, ${param2})
 
-Goal can be some thing like::
+Goal can be something like::
 
   goal: ${param1} ** ${param2}
 
 It seems to be working but what if `pow` is imported in some module or
 we have some other function defined in some other module that uses the
 same name and we don't want to change it.  Wildcard arguments come to
-rescue.  As already seen, arguments are mappings; Its keys are
-wildcard names that appear in the pattern (the names inside
-``${...}``).
+rescue.  Wildcard arguments is a mapping; Its keys are wildcard names
+that appear in the pattern (the names inside ``${...}``).
 
 The values are the parameters that are passed to wildcard matchers.
-The default wildcard takes ``name``, ``object`` and ``type`` arguments
-for checking the reference/object/type of a wildcard.
+The arguments a wildcard takes is based on its type.
 
 For checking the type of a wildcard, we can pass ``type=value`` as an
 arg; ``value`` should be resolved to a python variable (or reference).
 Restructurings come to rescue::
 
   pattern: ${inst}.f(${p1}, ${p2})
-  goal: ${inst}.f1(${p1})
-        ${inst}.f2(${p2})
+  goal:
+   ${inst}.f1(${p1})
+   ${inst}.f2(${p2})
   
-  checks: inst: type=mod.A
+  args:
+   inst: type=mod.A
 
 After performing we will have::
 
   pattern: ${x}.set(${y})
   goal: ${x} = ${y}
 
-  checks: x: type=mod.A
-
-The names in checks as you see should be the name of a wild card
-pattern like ``x`` or ``y`` in the above example.  They can have a
-``.type`` or ``.object`` prefix if you want to match the type of the
-object or the type a name holds instead of the reference itself.  The
-values in checks are the representation of python references.  They
-should start from the module that contains the element.
+  args: x: type=mod.A
 
 After performing the above restructuring we'll have::
 
 Issues
 ''''''
 
-Pattern names can only appear in at the start of an expression.  For
+Pattern names can appear only at the start of an expression.  For
 instance ``var.${name}`` is invalid.  These situations can usually be
 fixed by specifying good checks, for example on the type of `var` and
-using a ``${var}.name`` pattern.
+using a ``${var}.name``.
 
 
 Object Inference
 ================
 
-This section is a bit out of date.  SOI can do more than described
-here (see unittests).  Hope to update this someday!
+This section is a bit out of date.  Static object inference can do
+more than described here (see unittests).  Hope to update this
+someday!
 
 
 Static Object Inference
 
 Here the objects `a_var` and `c` hold are known.
 
-SOI analysis (``C-c x s`` in ropeide_) analyzes a module for finding
-useful object information.  Rope runs SOI analysis whenever a files is
-changed on the changed scopes automatically. (This can be overridden
-in project ``config.py``.)
-
-Many kinds of information is collected during SOI like per name data
+Rope collects different types of data during SOA, like per name data
 for builtin container types::
 
   l1 = [C()]
   l2.append(C())
   var2 = l2.pop()
 
-Here rope knows the type of `var1` without doing anything.  But for
-knowing the type of `var2` we need to analyze the items added to `l2`
-which might happen in other modules.  Rope can find out that by
-running SOI analysis on this module.
+Here rope can easily infer the type of `var1`.  But for knowing the
+type of `var2`, it needs to analyze the items inserted into `l2` which
+might happen in other modules.  Rope can do that by running SOA on
+that module.
 
-You might be wondering is there any reason for using DOI instead of
-SOI.  The answer is that DOI might be more accurate and handles
+You might be wondering is there any reason for using DOA instead of
+SOA.  The answer is that DOA might be more accurate and handles
 complex and dynamic situations.  For example in::
 
   def f(arg):
 
   a_var = f('C')
 
-SOI can no way conclude the object `a_var` holds but it is really
-trivial for DOI.  What's more SOI analyzes calls only in one module
-while DOI analyzes any call that happens when running a module.  That
-is, for achieving the same result as DOI you might need to run SOI on
+SOA can no way conclude the object `a_var` holds but it is really
+trivial for DOA.  What's more SOA only analyzes calls in one module
+while DOA analyzes any call that happens when running a module.  That
+is, for achieving the same result as DOA you might need to run SOA on
 more than one module and more than once (not considering dynamic
-situations.) One advantage of SOI is that it is much faster than DOI.
+situations.) One advantage of SOA is that it is much faster than DOA.
 
 
-Dynamic Object Inference
-------------------------
+Dynamic Object Analysis
+-----------------------
 
-Dynamic type inference collects its information by running modules
-(``C-c x p`` in ropeide_) or unit tests (``C-c x t`` in ropeide_).
-Since as the program runs, rope gathers type information, the program
-runs slower.  Right now rope doesn't have a good interface for running
-programs.  It just prints the output to the terminal and does not get
-inputs.  This will be enhanced in future releases. After the program
-is run, you can get better code assists and some of the refactorings
-perform much better.
+`PyCore.run_module()` runs a module and collects object information if
+``perform_doa`` project config is set.  Since as the program runs rope
+gathers type information, the program runs much slower.  After the
+program is run, you can get better code assists and some of the
+refactorings perform much better.
 
 ``mod1.py``::
 
 
 Using code assist in specified places does not give any information
 and there is actually no information about the return type of `f2` or
-the parameter `param` of `f1`.
+`param` parameter of `f1`.
 
 ``mod2.py``::
 
   a_var = A()
   mod1.f1(a_var)
 
-After running `mod2` module using rope's `Run Module`, we get good
-code assists in `mod1`.
+Retry those code assists after performing DOA on `mod2` module.
 
 
 Builtin Container Types
   func(C1())
   func(C2())
 
-After running `mod1` either SOI or DOI on this module you can test:
+After running `mod1` either SOA or DOA on this module you can test:
 
 ``mod2.py``::
 
   mod1.func(mod1.C2()).${codeassist}
 
 
-Automatic SOI Analysis
-----------------------
+Automatic SOA
+-------------
 
 When turned on, it analyzes the changed scopes of a file when saving
 for obtaining object information; So this might make saving files a
-bit more time consuming.  This feature is by default turned on, but
-you can turn it off by editing your project ``config.py`` file
-(available in ``${your_project_root}/.ropeproject/config.py``, if
-you're new to rope), though that is not recommended.
+bit more time consuming.  By default, this feature is turned on, but
+you can turn it off by editing your project ``config.py`` file, though
+that is not recommended.
 
 
-Validating ObjectDB
--------------------
+Validating Object DB
+--------------------
 
 Since files on disk change over time project objectdb might hold
 invalid information.  Currently there is a basic incremental objectdb

File docs/rope.txt

   * Rename everything!
   * Extract method/local variable
   * Move class/function/module/package/method
-  * Inline method/local variable
+  * Inline method/local variable/parameter
   * Restructuring (like converting ``${a}.f(${b})`` to
     ``${b}.g(${a})`` where ``a: type=mymod.A``)
   * Introduce factory
   * Stopping refactorings
   * Cross-project refactorings
   * Basic implicit interfaces handling in rename and change signature
-  * Mercurial_ and SVN (pysvn_ library) support in refactorings
+  * Mercurial_, GIT_ and SVN (pysvn_ library) support in refactorings
 
 * IDE helpers
 
   * Auto-completion
   * Definition location
-  * Get PyDoc
+  * Get pydoc
   * Find occurrences
   * Organize imports (remove unused and duplicate imports and sort them)
   * Generating python elements
 
 * Object Inference
 
-  * Static and dynamic object inference approaches
+  * Static and dynamic object analysis
   * Handling built-in container types
   * Saving object information on disk and validating them
 
 .. _overview.txt: overview.html
 .. _pysvn: http://pysvn.tigris.org
 .. _Mercurial: http://selenic.com/mercurial
+.. _GIT: http://git.or.cz

File rope/__init__.py

 """rope, a python refactoring library"""
 
 INFO = __doc__
-VERSION = '0.8.1'
+VERSION = '0.8.3'
 COPYRIGHT = """\
 Copyright (C) 2006-2008 Ali Gholami Rudi
 

File rope/base/arguments.py

+import rope.base.evaluate
+from rope.base import ast
+
+
+class Arguments(object):
+    """A class for evaluating parameters passed to a function
+
+    You can use the `create_arguments` factory.  It handles implicit
+    first arguments.
+
+    """
+
+    def __init__(self, args, scope):
+        self.args = args
+        self.scope = scope
+        self.instance = None
+
+    def get_arguments(self, parameters):
+        result = []
+        for pyname in self.get_pynames(parameters):
+            if pyname is None:
+                result.append(None)
+            else:
+                result.append(pyname.get_object())
+        return result
+
+    def get_pynames(self, parameters):
+        result = [None] * max(len(parameters), len(self.args))
+        for index, arg in enumerate(self.args):
+            if isinstance(arg, ast.keyword) and arg.arg in parameters:
+                result[parameters.index(arg.arg)] = self._evaluate(arg.value)
+            else:
+                result[index] = self._evaluate(arg)
+        return result
+
+    def get_instance_pyname(self):
+        if self.args:
+            return self._evaluate(self.args[0])
+
+    def _evaluate(self, ast_node):
+        return rope.base.evaluate.eval_node(self.scope, ast_node)
+
+
+def create_arguments(primary, pyfunction, call_node, scope):
+    """A factory for creating `Arguments`"""
+    args = list(call_node.args)
+    args.extend(call_node.keywords)
+    called = call_node.func
+    # XXX: Handle constructors
+    if _is_method_call(primary, pyfunction) and \
+       isinstance(called, ast.Attribute):
+        args.insert(0, called.value)
+    return Arguments(args, scope)
+
+
+class ObjectArguments(object):
+
+    def __init__(self, pynames):
+        self.pynames = pynames
+
+    def get_arguments(self, parameters):
+        result = []
+        for pyname in self.pynames:
+            if pyname is None:
+                result.append(None)
+            else:
+                result.append(pyname.get_object())
+        return result
+
+    def get_pynames(self, parameters):
+        return self.pynames
+
+    def get_instance_pyname(self):
+        return self.pynames[0]
+class MixedArguments(object):
+
+    def __init__(self, pyname, arguments, scope):
+        """`argumens` is an instance of `Arguments`"""
+        self.pyname = pyname
+        self.args = arguments
+
+    def get_pynames(self, parameters):
+        return [self.pyname] + self.args.get_pynames(parameters[1:])
+
+    def get_arguments(self, parameters):
+        result = []
+        for pyname in self.get_pynames(parameters):
+            if pyname is None:
+                result.append(None)
+            else:
+                result.append(pyname.get_object())
+        return result
+
+    def get_instance_pyname(self):
+        return self.pyname
+
+
+def _is_method_call(primary, pyfunction):
+    if primary is None:
+        return False
+    pyobject = primary.get_object()
+    if isinstance(pyobject.get_type(), rope.base.pyobjects.PyClass) and \
+       isinstance(pyfunction, rope.base.pyobjects.PyFunction) and \
+       isinstance(pyfunction.parent, rope.base.pyobjects.PyClass):
+        return True
+    if isinstance(pyobject.get_type(), rope.base.pyobjects.AbstractClass) and \
+       isinstance(pyfunction, rope.base.builtins.BuiltinFunction):
+        return True
+    return False

File rope/base/builtins.py

 import inspect
 
 import rope.base.evaluate
-from rope.base import pynames, pyobjects
+from rope.base import pynames, pyobjects, arguments, utils
 
 
 class BuiltinModule(pyobjects.AbstractModule):
         super(BuiltinModule, self).__init__()
         self.name = name
         self.initial = initial
-        self.attributes = None
+
+    parent = None
 
     def get_attributes(self):
-        if self.attributes is None:
-            self._calculate_attributes()
         return self.attributes
 
     def get_doc(self):
     def get_name(self):
         return self.name
 
-    def _calculate_attributes(self):
-        self.attributes = {}
-        if self.module is not None:
-            self.attributes = _object_attributes(self.module)
-        self.attributes.update(self.initial)
+    @property
+    @utils.cacheit
+    def attributes(self):
+        result = _object_attributes(self.module, self)
+        result.update(self.initial)
+        return result
 
-    _loaded = False
-    _module = None
     @property
+    @utils.cacheit
     def module(self):
-        if not self._loaded:
-            self._loaded = True
-            try:
-                self._module = __import__(self.name)
-            except ImportError:
-                self._module = None
-        return self._module
+        try:
+            return __import__(self.name)
+        except ImportError:
+            return
 
 
 def _create_builtin_type_getter(cls):
         attributes[name] = BuiltinName(pyobject)
     return attributes
 
-class BuiltinClass(pyobjects.AbstractClass):
+class _BuiltinElement(object):
 
-    def __init__(self, builtin, attributes):
-        super(BuiltinClass, self).__init__()
+    def __init__(self, builtin, parent=None):
         self.builtin = builtin
-        self.initial = attributes
-        self.attributes = None
-
-    def get_attributes(self):
-        if self.attributes is None:
-            self.attributes = _object_attributes(self.builtin)
-            self.attributes.update(self.initial)
-        return self.attributes
-
-    def get_doc(self):
-        return self.builtin.__doc__
-
-    def get_name(self):
-        return self.builtin.__name__
-
-
-class BuiltinFunction(pyobjects.AbstractFunction):
-
-    def __init__(self, returned=None, function=None, builtin=None, argnames=[]):
-        super(BuiltinFunction, self).__init__()
-        self.argnames = argnames
-        self.returned = returned
-        self.function = function
-        self.builtin = builtin
-
-    def get_returned_object(self, args):
-        if self.function is not None:
-            return self.function(_CallContext(self.argnames, args))
-        else:
-            return self.returned
+        self._parent = parent
 
     def get_doc(self):
         if self.builtin:
         if self.builtin:
             return self.builtin.__name__
 
+    @property
+    def parent(self):
+        if self._parent is None:
+            return builtins
+        return self._parent
+
+
+class BuiltinClass(_BuiltinElement, pyobjects.AbstractClass):
+
+    def __init__(self, builtin, attributes, parent=None):
+        _BuiltinElement.__init__(self, builtin, parent)
+        pyobjects.AbstractClass.__init__(self)
+        self.initial = attributes
+
+    @utils.cacheit
+    def get_attributes(self):
+        result = _object_attributes(self.builtin, self)
+        result.update(self.initial)
+        return result
+
+
+class BuiltinFunction(_BuiltinElement, pyobjects.AbstractFunction):
+
+    def __init__(self, returned=None, function=None, builtin=None,
+                 argnames=[], parent=None):
+        _BuiltinElement.__init__(self, builtin, parent)
+        pyobjects.AbstractFunction.__init__(self)
+        self.argnames = argnames
+        self.returned = returned
+        self.function = function
+
+    def get_returned_object(self, args):
+        if self.function is not None:
+            return self.function(_CallContext(self.argnames, args))
+        else:
+            return self.returned
+
+    def get_param_names(self, special_args=True):
+        return self.argnames
+
+
+def _object_attributes(obj, parent):
+    attributes = {}
+    for name in dir(obj):
+        if name == 'None':
+            continue
+        child = getattr(obj, name)
+        pyobject = None
+        if inspect.isclass(child):
+            pyobject = BuiltinClass(child, {}, parent=parent)
+        elif inspect.isroutine(child):
+            pyobject = BuiltinFunction(builtin=child, parent=parent)
+        else:
+            pyobject = pyobjects.get_unknown()
+        attributes[name] = BuiltinName(pyobject)
+    return attributes
+
 
 class _CallContext(object):
 
         return context.get_per_name()
 
     def _iterator_get(self, context):
-        return Iterator(self._list_get(context))
+        return get_iterator(self._list_get(context))
 
     def _self_get(self, context):
         return get_list(self._list_get(context))
             return
         new_dict = context.get_pynames(['self', 'd'])[1]
         if new_dict and isinstance(new_dict.get_object().get_type(), Dict):
-            args = rope.base.evaluate.ObjectArguments([new_dict])
+            args = arguments.ObjectArguments([new_dict])
             items = new_dict.get_object()['popitem'].\
                     get_object().get_returned_object(args)
             context.save_per_name(items)
             '__getitem__': BuiltinName(BuiltinFunction(function=self._tuple_get,
                                                        argnames=['self', 'key'])),
             '__new__': BuiltinName(BuiltinFunction(function=self._new_tuple)),
-            '__iter__': BuiltinName(BuiltinFunction(Iterator(first)))}
+            '__iter__': BuiltinName(BuiltinFunction(get_iterator(first)))}
         super(Tuple, self).__init__(tuple, attributes)
 
     def get_holding_objects(self):
         return context.get_per_name()
 
     def _iterator_get(self, context):
-        return Iterator(self._set_get(context))
+        return get_iterator(self._set_get(context))
 
     def _self_get(self, context):
         return get_list(self._set_get(context))
     def __init__(self):
         self_object = pyobjects.PyObject(self)
         collector = _AttributeCollector(str)
-        collector('__iter__', Iterator(self_object), check_existence=False)
+        collector('__iter__', get_iterator(self_object), check_existence=False)
 
         self_methods = ['__getitem__', 'captialize', 'center',
                         'encode', 'expandtabs', 'join', 'ljust',
         self.scope = scope
 
     def get_returned_object(self, args):
-        result = rope.base.evaluate.get_statement_result(self.scope,
-                                                         self.node.body)
+        result = rope.base.evaluate.eval_node(self.scope, self.node.body)
         if result is not None:
             return result.get_object()
         else:
     if pyname is None:
         return None
     seq = pyname.get_object()
-    args = rope.base.evaluate.ObjectArguments([pyname])
+    args = arguments.ObjectArguments([pyname])
     if '__iter__' in seq:
         iter = seq['__iter__'].get_object().\
                get_returned_object(args)
     return get_list()
 
 def _reversed_function(args):
-    return _create_builtin(args, Iterator)
+    return _create_builtin(args, get_iterator)
 
 def _sorted_function(args):
-    return _create_builtin(args, List)
+    return _create_builtin(args, get_list)
 
 def _super_function(args):
     passed_class, passed_self = args.get_arguments(['type', 'self'])
     else:
         holding = _infer_sequence_for_pyname(passed)
     tuple = get_tuple(None, holding)
-    return Iterator(tuple)
+    return get_iterator(tuple)
 
 def _iter_function(args):
     passed = args.get_pynames(['sequence'])[0]
         holding = None
     else:
         holding = _infer_sequence_for_pyname(passed)
-    return Iterator(holding)
+    return get_iterator(holding)
 
 def _input_function(args):
     return get_str()

File rope/base/change.py

 import warnings
 
 import rope.base.fscommands
-from rope.base import taskhandle, exceptions
+from rope.base import taskhandle, exceptions, utils
 
 
 class Change(object):
 
     Rope refactorings return `Change` objects.  They can be previewed,
     committed or undone.
-
     """
 
     def do(self, job_set=None):
         """Perform the change
         
         .. note:: Do use this directly.  Use `Project.do()` instead.
-
         """
 
     def undo(self, job_set=None):
         """Perform the change
         
         .. note:: Do use this directly.  Use `History.undo()` instead.
-
         """
 
     def get_description(self):
         """Return the description of this change
 
         This can be used for previewing the changes.
-
         """
         return str(self)
 
         """Return the list of resources that will be changed"""
         return []
 
+    @property
+    @utils.cacheit
+    def _operations(self):
+        return _ResourceOperations(self.resource.project)
+
 
 class ChangeSet(Change):
     """A collection of `Change` objects
 
     * `changes`: the list of changes
     * `description`: the goal of these changes
-
     """
 
     def __init__(self, description, timestamp=None):
 
     * `resource`: The `rope.base.resources.File` to change
     * `new_contents`: What to write in the file
-
     """
 
     def __init__(self, resource, new_contents, old_contents=None):
         # IDEA: Only saving diffs; possible problems when undo/redoing
         self.new_contents = new_contents
         self.old_contents = old_contents
-        self.operations = self.resource.project.operations
 
     @_handle_job_set
     def do(self):
         if self.old_contents is None:
             self.old_contents = self.resource.read()
-        self.operations.write_file(self.resource, self.new_contents)
+        self._operations.write_file(self.resource, self.new_contents)
 
     @_handle_job_set
     def undo(self):
         if self.old_contents is None:
             raise exceptions.HistoryError(
                 'Undoing a change that is not performed yet!')
-        self.operations.write_file(self.resource, self.old_contents)
+        self._operations.write_file(self.resource, self.old_contents)
 
     def __str__(self):
         return 'Change <%s>' % self.resource.path
     * `resource`: The `rope.base.resources.Resource` to move
     * `new_resource`: The destination for move; It is the moved
       resource not the folder containing that resource.
-
     """
 
     def __init__(self, resource, new_location, exact=False):
         self.project = resource.project
-        self.operations = resource.project.operations
         self.resource = resource
         if not exact:
             new_location = _get_destination_for_move(resource, new_location)
 
     @_handle_job_set
     def do(self):
-        self.operations.move(self.resource, self.new_resource)
+        self._operations.move(self.resource, self.new_resource)
 
     @_handle_job_set
     def undo(self):
-        self.operations.move(self.new_resource, self.resource)
+        self._operations.move(self.new_resource, self.resource)
 
     def __str__(self):
         return 'Move <%s>' % self.resource.path
     def get_changed_resources(self):
         return [self.resource, self.new_resource]
 
-    @property
-    def old_resource(self):
-        warnings.warn('Use `MoveResource.resource` instead of `old_resource`',
-                      DeprecationWarning, stacklevel=2)
-        return self.resource
-
 
 class CreateResource(Change):
     """A class to create a resource
     Fields:
 
     * `resource`: The resource to create
-
     """
 
     def __init__(self, resource):
         self.resource = resource
-        self.operations = self.resource.project.operations
 
     @_handle_job_set
     def do(self):
-        self.operations.create(self.resource)
+        self._operations.create(self.resource)
 
     @_handle_job_set
     def undo(self):
-        self.operations.remove(self.resource)
+        self._operations.remove(self.resource)
 
     def __str__(self):
         return 'Create Resource <%s>' % (self.resource.path)
 class RemoveResource(Change):
     """A class to remove a resource
 
-    Fields
+    Fields:
 
     * `resource`: The resource to be removed
-
     """
 
     def __init__(self, resource):
         self.resource = resource
-        self.operations = resource.project.operations
 
     @_handle_job_set
     def do(self):
-        self.operations.remove(self.resource)
+        self._operations.remove(self.resource)
 
     # TODO: Undoing remove operations
     @_handle_job_set
 
 class _ResourceOperations(object):
 
-    def __init__(self, project, fscommands):
+    def __init__(self, project):
         self.project = project
-        self.fscommands = fscommands
+        self.fscommands = project.fscommands
         self.direct_commands = rope.base.fscommands.FileSystemCommands()
 
     def _get_fscommands(self, resource):

File rope/base/codeanalyze.py

+import bisect
 import re
 import token
 import tokenize
-import warnings
 
 
-class WordRangeFinder(object):
-    """A class for finding boundaries of words and expressions
+class ChangeCollector(object):
 
-    Note that in these methods, offset should be the index of the
-    character not the index of the character after it.
+    def __init__(self, text):
+        self.text = text
+        self.changes = []
 
-    """
+    def add_change(self, start, end, new_text=None):
+        if new_text is None:
+            new_text = self.text[start:end]
+        self.changes.append((start, end, new_text))
 
-    # XXX: many of these methods fail on comments
-    # TODO: make disabled tests run
+    def get_changed(self):
+        if not self.changes:
+            return None
+        self.changes.sort(key=lambda change: change[:2])
+        pieces = []
+        last_changed = 0
+        for change in self.changes:
+            start, end, text = change
+            pieces.append(self.text[last_changed:start] + text)
+            last_changed = end
+        if last_changed < len(self.text):
+            pieces.append(self.text[last_changed:])
+        result = ''.join(pieces)
+        if result != self.text:
+            return result
 
-    def __init__(self, source_code):
-        self.source = source_code
 
-    def _find_word_start(self, offset):
-        current_offset = offset
-        while current_offset >= 0 and self._is_id_char(current_offset):
-            current_offset -= 1;
-        return current_offset + 1
-
-    def _find_word_end(self, offset):
-        while offset + 1 < len(self.source) and self._is_id_char(offset + 1):
-            offset += 1;
-        return offset
-
-    _char_pat = re.compile(r'[\'"#]')
-    def _find_last_non_space_char(self, offset):
-        if offset <= 0:
-            return 0
-        while offset >= 0 and self.source[offset].isspace():
-            if self.source[offset] == '\n':
-                if offset > 0 and self.source[offset - 1] == '\\':
-                    offset -= 1
-                try:
-                    start = self.source.rindex('\n', 0, offset)
-                except ValueError:
-                    start = 0
-
-                match = self._char_pat.search(self.source[start:offset])
-                if match and match.group() == '#':
-                    offset = self.source.rindex('#', start, offset)
-            offset -= 1
-        return offset
-
-    def get_word_at(self, offset):
-        offset = self._get_fixed_offset(offset)
-        return self.source[self._find_word_start(offset):
-                           self._find_word_end(offset) + 1]
-
-    def _get_fixed_offset(self, offset):
-        if offset >= len(self.source):
-            return offset - 1
-        if not self._is_id_char(offset):
-            if offset > 0 and self._is_id_char(offset - 1):
-                return offset - 1
-            if offset < len(self.source) - 1 and self._is_id_char(offset + 1):
-                return offset + 1
-        return offset
-
-    def _is_id_char(self, offset):
-        return self.source[offset].isalnum() or self.source[offset] == '_'
-
-    def _find_string_start(self, offset):
-        kind = self.source[offset]
-        offset -= 1
-        while self.source[offset] != kind:
-            offset -= 1
-        return offset
-
-    def _find_parens_start(self, offset):
-        offset = self._find_last_non_space_char(offset - 1)
-        while offset >= 0 and self.source[offset] not in '[({':
-            if self.source[offset] in ':,':
-                pass
-            else:
-                offset = self._find_primary_start(offset)
-            offset = self._find_last_non_space_char(offset - 1)
-        return offset
-
-    def _find_atom_start(self, offset):
-        old_offset = offset
-        if self.source[offset] in '\n\t ':
-            offset = self._find_last_non_space_char(offset)
-        if self.source[offset] in '\'"':
-            return self._find_string_start(offset)
-        if self.source[offset] in ')]}':
-            return self._find_parens_start(offset)
-        if self._is_id_char(offset):
-            return self._find_word_start(offset)
-        return old_offset
-
-    def _find_primary_without_dot_start(self, offset):
-        """It tries to find the undotted primary start
-
-        It is different from `self._get_atom_start()` in that it
-        follows function calls, too; such as in ``f(x)``.
-
-        """
-        last_atom = offset
-        offset = self._find_last_non_space_char(last_atom)
-        while offset > 0 and self.source[offset] in ')]':
-            last_atom = self._find_parens_start(offset)
-            offset = self._find_last_non_space_char(last_atom - 1)
-        if offset >= 0 and (self.source[offset] in '"\'})]' or
-                                   self._is_id_char(offset)):
-            return self._find_atom_start(offset)
-        return last_atom
-
-    def _find_primary_start(self, offset):
-        if offset >= len(self.source):
-            offset = len(self.source) - 1
-        if self.source[offset] != '.':
-            offset = self._find_primary_without_dot_start(offset)
-        else:
-            offset = offset + 1
-        while offset > 0:
-            prev = self._find_last_non_space_char(offset - 1)
-            if offset <= 0 or self.source[prev] != '.':
-                break
-            offset = self._find_primary_without_dot_start(prev - 1)
-            if not self._is_id_char(offset):
-                break
-
-        return offset
-
-    def get_primary_at(self, offset):
-        offset = self._get_fixed_offset(offset)
-        start, end = self.get_primary_range(offset)
-        return self.source[start:end].strip()
-
-    def get_splitted_primary_before(self, offset):
-        """returns expression, starting, starting_offset
-
-        This function is used in `rope.codeassist.assist` function.
-        """
-        if offset == 0:
-            return ('', '', 0)
-        end = offset - 1
-        word_start = self._find_atom_start(end)
-        real_start = self._find_primary_start(end)
-        if self.source[word_start:offset].strip() == '':
-            word_start = end
-        if self.source[end].isspace():
-            word_start = end
-        if self.source[real_start:word_start].strip() == '':
-            real_start = word_start
-        if real_start == word_start == end and not self._is_id_char(end):
-            return ('', '', offset)
-        if real_start == word_start:
-            return ('', self.source[word_start:offset], word_start)
-        else:
-            if self.source[end] == '.':
-                return (self.source[real_start:end], '', offset)
-            last_dot_position = word_start
-            if self.source[word_start] != '.':
-                last_dot_position = self._find_last_non_space_char(word_start - 1)
-            last_char_position = self._find_last_non_space_char(last_dot_position - 1)
-            if self.source[word_start].isspace():
-                word_start = offset
-            return (self.source[real_start:last_char_position + 1],
-                    self.source[word_start:offset], word_start)
-
-    def _get_line_start(self, offset):
-        while offset > 0 and self.source[offset] != '\n':
-            offset -= 1
-        return offset
-
-    def _get_line_end(self, offset):
-        while offset < len(self.source) and self.source[offset] != '\n':
-            offset += 1
-        return offset
-
-    def _is_followed_by_equals(self, offset):
-        while offset < len(self.source) and self.source[offset] in ' \\':
-            if self.source[offset] == '\\':
-                offset = self._get_line_end(offset)
-            offset += 1
-        if offset + 1 < len(self.source) and \
-           self.source[offset] == '=' and self.source[offset + 1] != '=':
-            return True
-        return False
-
-    def _is_name_assigned_in_class_body(self, offset):
-        word_start = self._find_word_start(offset - 1)
-        word_end = self._find_word_end(offset) + 1
-        if '.' in self.source[word_start:word_end]:
-            return False
-        line_start = self._get_line_start(word_start)
-        line = self.source[line_start:word_start].strip()
-        if line == '' and self._is_followed_by_equals(word_end):
-            return True
-        return False
-
-    def is_a_class_or_function_name_in_header(self, offset):
-        word_start = self._find_word_start(offset - 1)
-        line_start = self._get_line_start(word_start)
-        prev_word = self.source[line_start:word_start].strip()
-        return prev_word in ['def', 'class']
-
-    def _find_first_non_space_char(self, offset):
-        if offset >= len(self.source):
-            return len(self.source)
-        while offset < len(self.source):
-            if offset + 1 < len(self.source) and \
-               self.source[offset] == '\\':
-                offset += 2
-            elif self.source[offset] in ' \t\n':
-                offset += 1
-            else:
-                break
-        return offset
-
-    def is_a_function_being_called(self, offset):
-        word_end = self._find_word_end(offset) + 1
-        next_char = self._find_first_non_space_char(word_end)
-        return next_char < len(self.source) and \
-               self.source[next_char] == '(' and \
-               not self.is_a_class_or_function_name_in_header(offset)
-
-    def _find_import_pair_end(self, start):
-        next_char = self._find_first_non_space_char(start)
-        if next_char >= len(self.source):
-            return len(self.source)
-        if self.source[next_char] == '(':
-            try:
-                return self.source.index(')', next_char) + 1
-            except ValueError:
-                return SyntaxError('Unmatched Parens')
-        else:
-            offset = next_char
-            while offset < len(self.source):
-                if self.source[offset] == '\n':
-                    break
-                if self.source[offset] == '\\':
-                    offset += 1
-                offset += 1
-            return offset
-
-    def is_import_statement(self, offset):
-        try:
-            last_import = self.source.rindex('import ', 0, offset)
-        except ValueError:
-            return False
-        return self._find_import_pair_end(last_import + 7) >= offset
-
-    def is_from_statement(self, offset):
-        try:
-            last_from = self.source.rindex('from ', 0, offset)
-            from_import = self.source.index(' import ', last_from)
-            from_names = from_import + 8
-        except ValueError:
-            return False
-        from_names = self._find_first_non_space_char(from_names)
-        return self._find_import_pair_end(from_names) >= offset
-
-    def is_from_statement_module(self, offset):
-        if offset >= len(self.source) - 1:
-            return False
-        stmt_start = self._find_primary_start(offset)
-        line_start = self._get_line_start(stmt_start)
-        prev_word = self.source[line_start:stmt_start].strip()
-        return prev_word == 'from'
-
-    def is_a_name_after_from_import(self, offset):
-        try:
-            # XXX: what if there is the char after from or around
-            # import is not space?
-            last_from = self.source.rindex('from ', 0, offset)
-            from_import = self.source.index(' import ', last_from)
-            from_names = from_import + 8
-        except ValueError:
-            return False
-        if from_names - 1 > offset:
-            return False
-        return self._find_import_pair_end(from_names) >= offset
-
-    def get_from_module(self, offset):
-        try:
-            last_from = self.source.rindex('from ', 0, offset)
-            import_offset = self.source.index(' import ', last_from)
-            end = self._find_last_non_space_char(import_offset)
-            return self.get_primary_at(end)
-        except ValueError:
-            pass
-
-    def is_from_aliased(self, offset):
-        if not self.is_a_name_after_from_import(offset):
-            return False
-        try:
-            end = self._find_word_end(offset)
-            as_end = min(self._find_word_end(end + 1), len(self.source))
-            as_start = self._find_word_start(as_end)
-            if self.source[as_start:as_end + 1] == 'as':
-                return True
-        except ValueError:
-            return False
-
-    def get_from_aliased(self, offset):
-        try:
-            end = self._find_word_end(offset)
-            as_ = self._find_word_end(end + 1)
-            alias = self._find_word_end(as_ + 1)
-            start = self._find_word_start(alias)
-            return self.source[start:alias + 1]
-        except ValueError:
-            pass
-
-    def is_function_keyword_parameter(self, offset):
-        word_end = self._find_word_end(offset)
-        if word_end + 1 == len(self.source):
-            return False
-        next_char = self._find_first_non_space_char(word_end + 1)
-        if next_char + 2 >= len(self.source) or \
-           self.source[next_char] != '=' or \
-           self.source[next_char + 1] == '=':
-            return False
-        word_start = self._find_word_start(offset)
-        prev_char = self._find_last_non_space_char(word_start - 1)
-        if prev_char - 1 < 0 or self.source[prev_char] not in ',(':
-            return False
-        return True
-
-    def is_on_function_call_keyword(self, offset, stop_searching=0):
-        if self._is_id_char(offset):
-            offset = self._find_word_start(offset) - 1
-        offset = self._find_last_non_space_char(offset)
-        if offset <= stop_searching or \
-           self.source[offset] not in '(,':
-            return False
-        parens_start = self.find_parens_start_from_inside(offset, stop_searching)
-        if stop_searching < parens_start:
-            return True
-        return False
-
-    def find_parens_start_from_inside(self, offset, stop_searching=0):
-        opens = 1
-        while offset > stop_searching:
-            if self.source[offset] == '(':
-                break
-            if self.source[offset] != ',':
-                offset = self._find_primary_start(offset)
-            offset -= 1
-        return max(stop_searching, offset)
-
-    def is_assigned_here(self, offset):
-        operation = self.get_assignment_type(offset)
-        operations = ('=', '-=', '+=', '*=', '/=', '%=', '**=',
-                      '>>=', '<<=', '&=', '^=', '|=')
-        return operation in operations
-
-    def get_assignment_type(self, offset):
-        word_end = self._find_word_end(offset)
-        next_char = self._find_first_non_space_char(word_end + 1)
-        current_char = next_char
-        while current_char + 1 < len(self.source) and \
-              (self.source[current_char] != '=' or \
-               self.source[current_char + 1] == '=') and \
-              current_char < next_char + 3:
-            current_char += 1
-        operation = self.source[next_char:current_char + 1]
-        return operation
-
-    def get_primary_range(self, offset):
-        start = self._find_primary_start(offset)
-        end = self._find_word_end(offset) + 1
-        return (start, end)
-
-    def get_word_range(self, offset):
-        offset = max(0, offset)
-        start = self._find_word_start(offset)
-        end = self._find_word_end(offset) + 1
-        return (start, end)
-
-    def get_word_parens_range(self, offset):
-        if self.is_a_function_being_called(offset) or \
-           self.is_a_class_or_function_name_in_header(offset):
-            end = self._find_word_end(offset)
-            start_parens = self.source.index('(', end)
-            index = start_parens
-            open_count = 0
-            while index < len(self.source):
-                if self.source[index] == '(':
-                    open_count += 1
-                if self.source[index] == ')':
-                    open_count -= 1
-                if open_count == 0:
-                    return (start_parens, index + 1)
-                index += 1
-            return (start_parens, index)
-        return (None, None)
-
-
-def get_name_at(resource, offset):
-    source_code = resource.read()
-    word_finder = WordRangeFinder(source_code)
-    return word_finder.get_word_at(offset)
-
-
-class Lines(object):
-
-    def get_line(self, line_number):
-        pass
-
-    def length(self):
-        pass
-
-
-class SourceLinesAdapter(Lines):
-    """Adapts source_code to Lines interface
+class SourceLinesAdapter(object):
+    """Adapts source to Lines interface
 
     Note: The creation of this class is expensive.
     """
 
     def __init__(self, source_code):
-        self.source_code = source_code
-        self.line_starts = None
+        self.code = source_code
+        self.starts = None
         self._initialize_line_starts()
 
     def _initialize_line_starts(self):
-        self.line_starts = []
-        self.line_starts.append(0)
+        self.starts = []
+        self.starts.append(0)
         try:
-            i = -1
+            i = 0
             while True:
-                i = self.source_code.index('\n', i + 1)
-                self.line_starts.append(i + 1)
+                i = self.code.index('\n', i) + 1
+                self.starts.append(i)
         except ValueError:
             pass
-        self.line_starts.append(len(self.source_code) + 1)
+        self.starts.append(len(self.code) + 1)
 
-    def get_line(self, line_number):
-        return self.source_code[self.line_starts[line_number - 1]:
-                                self.line_starts[line_number] - 1]
+    def get_line(self, lineno):
+        return self.code[self.starts[lineno - 1]:
+                         self.starts[lineno] - 1]
 
     def length(self):
-        return len(self.line_starts) - 1
+        return len(self.starts) - 1
 
     def get_line_number(self, offset):
-        down = 0
-        up = len(self.line_starts)
-        current = (down + up) // 2
-        while down <= current < up:
-            if self.line_starts[current] <= offset < self.line_starts[current + 1]:
-                return current + 1
-            if offset < self.line_starts[current]:
-                up = current - 1
-            else:
-                down = current + 1
-            current = (down + up) // 2
-        return current + 1
+        return bisect.bisect(self.starts, offset)
 
-    def get_line_start(self, line_number):
-        return self.line_starts[line_number - 1]
+    def get_line_start(self, lineno):
+        return self.starts[lineno - 1]
 
-    def get_line_end(self, line_number):
-        return self.line_starts[line_number] - 1
+    def get_line_end(self, lineno):
+        return self.starts[lineno] - 1
 
 
-class ArrayLinesAdapter(Lines):
+class ArrayLinesAdapter(object):
 
     def __init__(self, lines):
         self.lines = lines
         return self.readline()
 
 
-class _CachingLogicalLineFinder(object):
+class _CustomGenerator(object):
 
     def __init__(self, lines):
         self.lines = lines
-
-    _starts = None
-    @property
-    def starts(self):
-        if self._starts is None:
-            self._init_logicals()
-        return self._starts
-
-    _ends = None
-    @property
-    def ends(self):
-        if self._ends is None:
-            self._init_logicals()
-        return self._ends
-
-    def _init_logicals(self):
-        """Should initialize _starts and _ends attributes"""
-
-    def logical_line_in(self, line_number):
-        start = line_number
-        while start > 0 and not self.starts[start]:
-            start -= 1
-        if start == 0:
-            try:
-                start = self.starts.index(True, line_number)
-            except ValueError:
-                return (line_number, line_number)
-        return (start, self.ends.index(True, start))
-
-    def generate_starts(self, start_line=1, end_line=None):
-        if end_line is None:
-            end_line = self.lines.length()
-        for index in range(start_line, end_line):
-            if self.starts[index]:
-                yield index
-
-
-class TokenizerLogicalLineFinder(_CachingLogicalLineFinder):
-
-    def __init__(self, lines):
-        super(TokenizerLogicalLineFinder, self).__init__(lines)
-        self.logical_lines = LogicalLineFinder(lines)
-
-    def _init_logicals(self):
-        self._starts = [False] * (self.lines.length() + 1)
-        self._ends = [False] * (self.lines.length() + 1)
-        for start, end in self.logical_lines.generate_regions():
-            self._starts[start] = True
-            self._ends[end] = True
-
-
-class CustomLogicalLineFinder(_CachingLogicalLineFinder):
-    """A method object for finding the range of a statement"""
-
-    def __init__(self, lines):
-        super(CustomLogicalLineFinder, self).__init__(lines)
         self.in_string = ''
         self.open_count = 0
         self.continuation = False
 
-    def _init_logicals(self):
+    def __call__(self):
         size = self.lines.length()
-        self._starts = [False] * (size + 1)
-        self._ends = [False] * (size + 1)
+        result = []
         i = 1
         while i <= size:
             while i <= size and not self.lines.get_line(i).strip():
                 i += 1
             if i <= size:
-                self._starts[i] = True
+                start = i
                 while True:
                     line = self.lines.get_line(i)
                     self._analyze_line(line)
                             self.in_string) or i == size:
                         break
                     i += 1
-                self._ends[i] = True
+                result.append((start, i))
                 i += 1
+        return result
 
     _main_chars = re.compile(r'[\'|"|#|\\|\[|\]|\{|\}|\(|\)]')
     def _analyze_line(self, line):
         else:
             self.continuation = False
 
+def custom_generator(lines):
+    return _CustomGenerator(lines)()
+
 
 class LogicalLineFinder(object):
 
         except tokenize.TokenError as e:
             pass
 
-    def get_logical_line_in(self, line_number):
-        warnings.warn('Use `LogicalLineFinder.logical_line_in()` instead',
-                      DeprecationWarning, stacklevel=2)
-        return self.logical_line_in(line_number)
-
     def _block_logical_line(self, block_start, line_number):
         readline = LinesToReadline(self.lines, block_start)
         shifted = line_number - block_start + 1
         return current
 
 
+def tokenizer_generator(lines):
+    return LogicalLineFinder(lines).generate_regions()
+
+
+class CachingLogicalLineFinder(object):
+
+    def __init__(self, lines, generate=custom_generator):