Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pylsp-rope for 
openSUSE:Factory checked in at 2021-10-12 21:51:34
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pylsp-rope (Old)
 and      /work/SRC/openSUSE:Factory/.python-pylsp-rope.new.2443 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pylsp-rope"

Tue Oct 12 21:51:34 2021 rev:3 rq:924932 version:0.1.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pylsp-rope/python-pylsp-rope.changes      
2021-10-05 22:34:30.358937560 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pylsp-rope.new.2443/python-pylsp-rope.changes
    2021-10-12 21:51:34.940063177 +0200
@@ -1,0 +2,10 @@
+Tue Oct 12 05:16:48 UTC 2021 - Matej Cepl <mc...@suse.com>
+
+- Update to 0.1.6:
+  - Fixed issue with missing typing-extensions dependency
+  - Add use functions refactoring
+  - Internal rewrites
+  - Add type annotation for many things
+  - Improve error handling during executeCommand()
+
+-------------------------------------------------------------------

Old:
----
  pylsp-rope-0.1.4.tar.gz

New:
----
  pylsp-rope-0.1.6.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pylsp-rope.spec ++++++
--- /var/tmp/diff_new_pack.wrUixP/_old  2021-10-12 21:51:35.424063870 +0200
+++ /var/tmp/diff_new_pack.wrUixP/_new  2021-10-12 21:51:35.432063882 +0200
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-pylsp-rope
-Version:        0.1.4
+Version:        0.1.6
 Release:        0
 Summary:        Extended refactoring capabilities for Python LSP Server using 
Rope
 License:        MIT
@@ -30,6 +30,7 @@
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  python-rpm-macros
+BuildRequires:  (python3-typing_extensions if python3-base <= 3.6)
 # SECTION test requirements
 BuildRequires:  %{python_module python-lsp-server}
 BuildRequires:  %{python_module pytest}

++++++ pylsp-rope-0.1.4.tar.gz -> pylsp-rope-0.1.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/PKG-INFO 
new/pylsp-rope-0.1.6/PKG-INFO
--- old/pylsp-rope-0.1.4/PKG-INFO       2021-10-05 05:14:48.619781300 +0200
+++ new/pylsp-rope-0.1.6/PKG-INFO       2021-10-12 04:16:42.299155200 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pylsp-rope
-Version: 0.1.4
+Version: 0.1.6
 Summary: Extended refactoring capabilities for Python LSP Server using Rope.
 Home-page: https://github.com/python-rope/pylsp-rope
 Author: Lie Ryan
@@ -17,11 +17,14 @@
 Requires-Python: >=3.6
 Description-Content-Type: text/markdown
 Provides-Extra: dev
+Provides-Extra: test
 License-File: LICENSE
 License-File: AUTHORS.txt
 
 # pylsp-rope
 
+[![Tests](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml/badge.svg)](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml)
+
 Extended refactoring capabilities for Python LSP Server using
 [Rope](https://github.com/python-rope/rope).
 
@@ -58,6 +61,7 @@
 - extract method (codeAction)
 - extract variable (codeAction)
 - inline method/variable/parameter (codeAction)
+- use function (codeAction)
 - method to method object (codeAction)
 - more to come...
 
@@ -68,19 +72,28 @@
 
 ### Extract method
 
-This refactoring works by triggering a CodeAction when selecting a block of 
code.
+This refactoring works by triggering a CodeAction when selecting a block of
+code.
 
 ### Extract variable
 
-This refactoring works by triggering a CodeAction when selecting a Python 
expression.
+This refactoring works by triggering a CodeAction when selecting a Python
+expression.
 
 ### Inline
 
-This refactoring works by triggering a CodeAction when the cursor is on a 
resolvable Python identifier.
+This refactoring works by triggering a CodeAction when the cursor is on a
+resolvable Python identifier.
+
+### Use function
+
+This works by triggering a CodeAction when the cursor is on the function name
+of a `def` statement.
 
 ### Method to method object
 
-This refactoring works when the cursor is on a function definition.
+This works by triggering a CodeAction when the cursor is on the function name
+of a `def` statement.
 
 ## Caveat
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/README.md 
new/pylsp-rope-0.1.6/README.md
--- old/pylsp-rope-0.1.4/README.md      2021-10-05 03:45:00.000000000 +0200
+++ new/pylsp-rope-0.1.6/README.md      2021-10-12 04:04:46.000000000 +0200
@@ -1,5 +1,7 @@
 # pylsp-rope
 
+[![Tests](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml/badge.svg)](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml)
+
 Extended refactoring capabilities for Python LSP Server using
 [Rope](https://github.com/python-rope/rope).
 
@@ -36,6 +38,7 @@
 - extract method (codeAction)
 - extract variable (codeAction)
 - inline method/variable/parameter (codeAction)
+- use function (codeAction)
 - method to method object (codeAction)
 - more to come...
 
@@ -46,19 +49,28 @@
 
 ### Extract method
 
-This refactoring works by triggering a CodeAction when selecting a block of 
code.
+This refactoring works by triggering a CodeAction when selecting a block of
+code.
 
 ### Extract variable
 
-This refactoring works by triggering a CodeAction when selecting a Python 
expression.
+This refactoring works by triggering a CodeAction when selecting a Python
+expression.
 
 ### Inline
 
-This refactoring works by triggering a CodeAction when the cursor is on a 
resolvable Python identifier.
+This refactoring works by triggering a CodeAction when the cursor is on a
+resolvable Python identifier.
+
+### Use function
+
+This works by triggering a CodeAction when the cursor is on the function name
+of a `def` statement.
 
 ### Method to method object
 
-This refactoring works when the cursor is on a function definition.
+This works by triggering a CodeAction when the cursor is on the function name
+of a `def` statement.
 
 ## Caveat
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/commands.py 
new/pylsp-rope-0.1.6/pylsp_rope/commands.py
--- old/pylsp-rope-0.1.4/pylsp_rope/commands.py 2021-10-05 03:38:45.000000000 
+0200
+++ new/pylsp-rope-0.1.6/pylsp_rope/commands.py 2021-10-07 19:25:19.000000000 
+0200
@@ -1,4 +1,5 @@
 COMMAND_REFACTOR_EXTRACT_METHOD = "pylsp_rope.refactor.extract.method"
 COMMAND_REFACTOR_EXTRACT_VARIABLE = "pylsp_rope.refactor.extract.variable"
 COMMAND_REFACTOR_INLINE = "pylsp_rope.refactor.inline"
+COMMAND_REFACTOR_USE_FUNCTION = "pylsp_rope.refactor.use_function"
 COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT = 
"pylsp_rope.refactor.method_to_method_object"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/lsp_diff.py 
new/pylsp-rope-0.1.6/pylsp_rope/lsp_diff.py
--- old/pylsp-rope-0.1.4/pylsp_rope/lsp_diff.py 2021-10-03 16:07:31.000000000 
+0200
+++ new/pylsp-rope-0.1.6/pylsp_rope/lsp_diff.py 2021-10-10 12:33:41.000000000 
+0200
@@ -1,16 +1,24 @@
 import difflib
+from typing import Iterator, List, Tuple, cast
+
 from pylsp_rope.text import Position
+from pylsp_rope.typing import TextEdit, Line, LineNumber
+
+
+_DifflibOpcode = Tuple[str, LineNumber, LineNumber, LineNumber, LineNumber]
 
 
-def _difflib_ops_to_text_edit_ops(ops, lines):
-    op, start_old, end_old, start_new, end_new = ops
+def _difflib_ops_to_text_edit_ops(
+    opcode: _DifflibOpcode, lines: List[Line]
+) -> TextEdit:
+    op, start_old, end_old, start_new, end_new = opcode
 
     if op == "replace" or op == "insert":
         new_text = "".join(lines[start_new:end_new])
     elif op == "delete":
         new_text = ""
     else:
-        assert False, ops
+        assert False, opcode
 
     return {
         "range": {"start": Position(start_old), "end": Position(end_old)},
@@ -18,16 +26,18 @@
     }
 
 
-def lsp_diff(lines_old, lines_new):
+def lsp_diff(lines_old: List[Line], lines_new: List[Line]) -> 
Iterator[TextEdit]:
     """
     Given two sequences of lines, produce a [TextEdit][1] changeset.
 
     [1]: 
https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textEdit
     """
     matcher = difflib.SequenceMatcher(a=lines_old, b=lines_new)
-    for ops in matcher.get_opcodes():
-        if ops[0] == "equal":
+    for opcode in matcher.get_opcodes():
+        if opcode[0] == "equal":
             continue
 
-        text_edit_ops = _difflib_ops_to_text_edit_ops(ops, lines_new)
+        text_edit_ops = _difflib_ops_to_text_edit_ops(
+            cast(_DifflibOpcode, opcode), lines_new
+        )
         yield text_edit_ops
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/plugin.py 
new/pylsp-rope-0.1.6/pylsp_rope/plugin.py
--- old/pylsp-rope-0.1.4/pylsp_rope/plugin.py   2021-10-05 05:02:12.000000000 
+0200
+++ new/pylsp-rope-0.1.6/pylsp_rope/plugin.py   2021-10-12 02:06:26.000000000 
+0200
@@ -1,16 +1,19 @@
 import ast
 import logging
+from typing import List
 
-import rope.base.exceptions
 from pylsp import hookimpl
-from rope.refactor import extract, inline, method_object
+from pylsp.lsp import MessageType
+from rope.refactor import extract, inline, method_object, usefunction
 
-from pylsp_rope import commands
+from pylsp_rope import typing, commands
 from pylsp_rope.project import (
     get_project,
     get_resource,
+    get_resources,
     apply_rope_changeset,
 )
+from pylsp_rope.typing import DocumentUri, CodeActionKind
 
 
 logger = logging.getLogger(__name__)
@@ -48,12 +51,14 @@
 
 
 @hookimpl
-def pylsp_commands(config, workspace):
+def pylsp_commands(config, workspace) -> List[str]:
     return [getattr(commands, cmd) for cmd in dir(commands) if not 
cmd.startswith("_")]
 
 
 @hookimpl
-def pylsp_code_actions(config, workspace, document, range, context):
+def pylsp_code_actions(
+    config, workspace, document, range, context
+) -> List[typing.CodeAction]:
     logger.info("textDocument/codeAction: %s %s %s", document, range, context)
 
     class info:
@@ -74,11 +79,22 @@
             document_uri=document.uri,
             range=range,
         ),
-        "Inline method/variable": CommandRefactorInline(
+        "Inline method/variable/parameter": CommandRefactorInline(
             workspace,
             document_uri=document.uri,
             position=info.position,
         ),
+        "Use function": CommandRefactorUseFunction(
+            workspace,
+            document_uri=document.uri,
+            position=info.position,
+        ),
+        "Use function for current file only": CommandRefactorUseFunction(
+            workspace,
+            document_uri=document.uri,
+            position=info.position,
+            documents=[document.uri],
+        ),
         "To method object": CommandRefactorMethodToMethodObject(
             workspace,
             document_uri=document.uri,
@@ -97,50 +113,71 @@
 def pylsp_execute_command(config, workspace, command, arguments):
     logger.info("workspace/executeCommand: %s %s", command, arguments)
 
-    commands = [
-        CommandRefactorExtractMethod,
-        CommandRefactorExtractVariable,
-        CommandRefactorInline,
-        CommandRefactorMethodToMethodObject,
-    ]
+    commands = {cmd.name: cmd for cmd in Command.__subclasses__()}
 
-    for cmd in commands:
-        if command == cmd.name:
-            cmd(workspace, **arguments[0])()
+    try:
+        return commands[command](workspace, **arguments[0])()
+    except Exception as exc:
+        logger.exception(
+            "Exception when doing workspace/executeCommand: %s",
+            str(exc),
+            exc_info=exc,
+        )
+        workspace.show_message(
+            f"pylsp-rope: {exc}",
+            msg_type=MessageType.Error,
+        )
 
 
 class Command:
+    name: str
+    title: str
+    kind: CodeActionKind
+
     def __init__(self, workspace, **arguments):
         self.workspace = workspace
         self.arguments = arguments
         self.__dict__.update(**arguments)
 
+    def __call__(self):
+        pass
+
+    def validate(self, info):
+        pass
+
     def is_valid(self, info):
         try:
-            is_valid = self._is_valid(info)
+            self.validate(info)
         except Exception:
             return False
         else:
-            return is_valid
+            return True
         return False
 
-    def _is_valid(self, info):
-        return True
-
-    def get_code_action(self, title):
+    def get_code_action(self, title: str) -> typing.CodeAction:
         return {
             "title": title,
             "kind": self.kind,
             "command": {
+                "title": title,
                 "command": self.name,
                 "arguments": [self.arguments],
             },
         }
 
+    @property  # FIXME: backport cached_property
+    def project(self):
+        if not hasattr(self, "_project"):
+            self._project = get_project(self.workspace)
+        return self._project
+
 
 class CommandRefactorExtractMethod(Command):
     name = commands.COMMAND_REFACTOR_EXTRACT_METHOD
-    kind = "refactor.extract"
+    kind: CodeActionKind = "refactor.extract"
+
+    document_uri: DocumentUri
+    range: typing.Range
 
     # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker for 
proper checking
     # def _is_valid(self, info):
@@ -150,7 +187,7 @@
         current_document, resource = get_resource(self.workspace, 
self.document_uri)
 
         refactoring = extract.ExtractMethod(
-            project=get_project(self.workspace),
+            project=self.project,
             resource=resource,
             
start_offset=current_document.offset_at_position(self.range["start"]),
             end_offset=current_document.offset_at_position(self.range["end"]),
@@ -163,18 +200,20 @@
 
 class CommandRefactorExtractVariable(Command):
     name = commands.COMMAND_REFACTOR_EXTRACT_VARIABLE
-    kind = "refactor.extract"
+    kind: CodeActionKind = "refactor.extract"
 
-    def _is_valid(self, info):
+    document_uri: DocumentUri
+    range: typing.Range
+
+    def validate(self, info):
         # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker 
for proper checking
         ast.parse(info.selected_text, mode="eval")
-        return True
 
     def __call__(self):
         current_document, resource = get_resource(self.workspace, 
self.document_uri)
 
         refactoring = extract.ExtractVariable(
-            project=get_project(self.workspace),
+            project=self.project,
             resource=resource,
             
start_offset=current_document.offset_at_position(self.range["start"]),
             end_offset=current_document.offset_at_position(self.range["end"]),
@@ -187,21 +226,23 @@
 
 class CommandRefactorInline(Command):
     name = commands.COMMAND_REFACTOR_INLINE
-    kind = "refactor.inline"
+    kind: CodeActionKind = "refactor.inline"
+
+    document_uri: DocumentUri
+    position: typing.Range
 
-    def _is_valid(self, info):
+    def validate(self, info):
         inline.create_inline(
-            project=get_project(self.workspace),
+            project=self.project,
             resource=info.resource,
             offset=info.current_document.offset_at_position(info.position),
         )
-        return True
 
     def __call__(self):
         current_document, resource = get_resource(self.workspace, 
self.document_uri)
 
         refactoring = inline.create_inline(
-            project=get_project(self.workspace),
+            project=self.project,
             resource=resource,
             offset=current_document.offset_at_position(self.position),
         )
@@ -209,15 +250,53 @@
         apply_rope_changeset(self.workspace, rope_changeset)
 
 
+class CommandRefactorUseFunction(Command):
+    name = commands.COMMAND_REFACTOR_USE_FUNCTION
+    kind: CodeActionKind = "refactor"
+
+    document_uri: DocumentUri
+    position: typing.Range
+
+    def validate(self, info):
+        usefunction.UseFunction(
+            project=self.project,
+            resource=info.resource,
+            offset=info.current_document.offset_at_position(info.position),
+        )
+
+    def __call__(self):
+        current_document, resource = get_resource(self.workspace, 
self.document_uri)
+
+        refactoring = usefunction.UseFunction(
+            project=self.project,
+            resource=resource,
+            offset=current_document.offset_at_position(self.position),
+        )
+        rope_changeset = refactoring.get_changes(
+            resources=get_resources(self.workspace, getattr(self, "documents", 
None)),
+        )
+        apply_rope_changeset(self.workspace, rope_changeset)
+
+
 class CommandRefactorMethodToMethodObject(Command):
     name = commands.COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT
-    kind = "refactor"
+    kind: CodeActionKind = "refactor.rewrite"
+
+    document_uri: DocumentUri
+    position: typing.Range
+
+    def validate(self, info):
+        method_object.MethodObject(
+            project=self.project,
+            resource=info.resource,
+            offset=info.current_document.offset_at_position(self.position),
+        )
 
     def __call__(self):
         current_document, resource = get_resource(self.workspace, 
self.document_uri)
 
         refactoring = method_object.MethodObject(
-            project=get_project(self.workspace),
+            project=self.project,
             resource=resource,
             offset=current_document.offset_at_position(self.position),
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/project.py 
new/pylsp-rope-0.1.6/pylsp_rope/project.py
--- old/pylsp-rope-0.1.4/pylsp_rope/project.py  2021-10-05 03:48:26.000000000 
+0200
+++ new/pylsp-rope-0.1.6/pylsp_rope/project.py  2021-10-12 02:06:26.000000000 
+0200
@@ -1,40 +1,54 @@
 import logging
 from functools import lru_cache
+from typing import List, Dict, Tuple
 
-from pylsp import uris
+from pylsp import uris, workspace
 from rope.base import libutils
-from rope.base.project import Project
 
+from pylsp_rope import rope
 from pylsp_rope.lsp_diff import lsp_diff
+from pylsp_rope.typing import WorkspaceEdit, DocumentUri, TextEdit, Line
 
 
 logger = logging.getLogger(__name__)
 
 
 @lru_cache(maxsize=None)
-def _get_project(workspace):
-    project = Project(workspace.root_path)
+def _get_project(workspace) -> rope.Project:
+    project = rope.Project(workspace.root_path)
     return project
 
 
-def get_project(workspace):
+def get_project(workspace) -> rope.Project:
     project = _get_project(workspace)
     project.validate()
     return project
 
 
-def get_resource(workspace, document_uri):
+def get_resource(
+    workspace, document_uri: DocumentUri
+) -> Tuple[workspace.Document, rope.Resource]:
     document = workspace.get_document(document_uri)
-    resource = libutils.path_to_resource(get_project(workspace), document.path)
+    resource = libutils.path_to_resource(_get_project(workspace), 
document.path)
     return document, resource
 
 
-def get_document(workspace, resource):
+def get_resources(
+    workspace, documents: List[workspace.Document]
+) -> List[rope.Resource]:
+    if documents is None:
+        return None
+    return [get_resource(workspace, document_uri)[1] for document_uri in 
documents]
+
+
+def get_document(workspace, resource: rope.Resource) -> workspace.Document:
     return workspace.get_document(uris.from_fs_path(resource.real_path))
 
 
-def rope_changeset_to_workspace_changeset(workspace, rope_changeset):
-    def _get_contents(change):
+def rope_changeset_to_workspace_edit(
+    workspace, rope_changeset: rope.ChangeSet
+) -> WorkspaceEdit:
+    def _get_contents(change: rope.Change) -> Tuple[List[Line], List[Line]]:
         old = change.old_contents
         new = change.new_contents
         if old is None:
@@ -44,7 +58,7 @@
                 old = ""
         return old.splitlines(keepends=True), new.splitlines(keepends=True)
 
-    workspace_changeset = {}
+    workspace_changeset: Dict[DocumentUri, List[TextEdit]] = {}
     for change in rope_changeset.changes:
         lines_old, lines_new = _get_contents(change)
 
@@ -52,18 +66,16 @@
         document_changes = workspace_changeset.setdefault(document.uri, [])
         document_changes.extend(lsp_diff(lines_old, lines_new))
 
-    return workspace_changeset
+    return {
+        "changes": workspace_changeset,
+    }
 
 
-def apply_rope_changeset(workspace, rope_changeset):
-    workspace_changeset = rope_changeset_to_workspace_changeset(
+def apply_rope_changeset(workspace, rope_changeset: rope.ChangeSet) -> None:
+    workspace_edit = rope_changeset_to_workspace_edit(
         workspace,
         rope_changeset,
     )
 
-    workspace_edit = {
-        "changes": workspace_changeset,
-    }
-
     logger.info("applying workspace edit: %s", workspace_edit)
     workspace.apply_edit(workspace_edit)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/rope.py 
new/pylsp-rope-0.1.6/pylsp_rope/rope.py
--- old/pylsp-rope-0.1.4/pylsp_rope/rope.py     1970-01-01 01:00:00.000000000 
+0100
+++ new/pylsp-rope-0.1.6/pylsp_rope/rope.py     2021-10-09 22:54:12.000000000 
+0200
@@ -0,0 +1,11 @@
+from rope.base.change import ChangeSet, Change
+from rope.base.project import Project
+from rope.base.resources import Resource
+
+
+__all__ = [
+    "Change",
+    "ChangeSet",
+    "Project",
+    "Resource",
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/text.py 
new/pylsp-rope-0.1.6/pylsp_rope/text.py
--- old/pylsp-rope-0.1.4/pylsp_rope/text.py     2021-10-02 12:38:30.000000000 
+0200
+++ new/pylsp-rope-0.1.6/pylsp_rope/text.py     2021-10-12 04:04:46.000000000 
+0200
@@ -1,11 +1,70 @@
-def Position(line, character=None, _default_character=0):
+from typing import Tuple, Union, overload, Optional
+
+from pylsp_rope import typing
+from pylsp_rope.typing import LineNumber, CharNumber, Literal
+
+
+START_OF_LINE: Literal["^"] = "^"
+END_OF_LINE: Literal["$"] = "$"
+
+
+AutoLineNumber = Union[LineNumber, int]
+AutoCharNumber = Union[CharNumber, int]
+
+
+_CharNumberOrMarker = Union[AutoCharNumber, Literal["^", "$"]]
+_PrimitiveLineCharNumber = Union[
+    AutoLineNumber, Tuple[AutoLineNumber, Optional[_CharNumberOrMarker]]
+]
+
+
+@overload
+def Position(
+    line: Tuple[AutoLineNumber, Optional[_CharNumberOrMarker]],
+    *,
+    _default_character: _CharNumberOrMarker = CharNumber(0),
+) -> typing.Position:
+    ...
+
+
+@overload
+def Position(
+    line: AutoLineNumber,
+    *,
+    _default_character: _CharNumberOrMarker = CharNumber(0),
+) -> typing.Position:
+    ...
+
+
+@overload
+def Position(
+    line: AutoLineNumber,
+    character: AutoCharNumber,
+) -> typing.Position:
+    ...
+
+
+@overload
+def Position(
+    line: AutoLineNumber,
+    character: Literal["^", "$"],
+) -> typing.Position:
+    ...
+
+
+def Position(
+    line: _PrimitiveLineCharNumber,
+    character: Optional[_CharNumberOrMarker] = None,
+    *,
+    _default_character: _CharNumberOrMarker = CharNumber(0),
+) -> typing.Position:
     """
     Returns a 
[Position](https://microsoft.github.io/language-server-protocol/specification#position)
     object for a document.
 
     `pos` can be:
 
-    - Tuple[line, character] are passed directly to the object
+    - Tuple[LineNumber, CharNumber] are passed directly to the object
     - int selects the start of the line
     - "^" the first non-blank character of the line
     - "$" the end of the line, which is the start of the next line
@@ -25,25 +84,31 @@
 
     """
     if isinstance(line, tuple):
-        assert (
-            character is None
-        ), "If `line` is a tuple, then `character` must not be supplied"
-        line, character = line
+        # assert (
+        #     character is None
+        # ), "If `line` is a tuple, then `character` must not be supplied"
+        lineno, character = line
+    else:
+        lineno = line
 
     if character is None:
         character = _default_character
 
     if character == "$":
-        line += 1
-        character = 0
+        lineno = LineNumber(lineno + 1)
+        character = CharNumber(0)
+    assert character != "^", "not implemented yet"
 
     return {
-        "line": line,
+        "line": lineno,
         "character": character,
     }
 
 
-def Range(start, end=None):
+def Range(
+    start: _PrimitiveLineCharNumber,
+    end: Optional[_PrimitiveLineCharNumber] = None,
+) -> typing.Range:
     """
     Returns a 
[Range](https://microsoft.github.io/language-server-protocol/specification#range)
     object for a document.
@@ -71,6 +136,6 @@
         end = start
 
     return {
-        "start": Position(start, _default_character=0),
-        "end": Position(end, _default_character="$"),
+        "start": Position(start, _default_character=CharNumber(0)),
+        "end": Position(end, _default_character=END_OF_LINE),
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/typing.py 
new/pylsp-rope-0.1.6/pylsp_rope/typing.py
--- old/pylsp-rope-0.1.4/pylsp_rope/typing.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/pylsp-rope-0.1.6/pylsp_rope/typing.py   2021-10-12 04:04:46.000000000 
+0200
@@ -0,0 +1,87 @@
+import sys
+from typing import List, Dict, Optional, NewType, Any
+
+
+if sys.version_info >= (3, 8):
+    from typing import TypedDict, Literal
+else:
+    from typing_extensions import TypedDict, Literal
+
+
+##########################
+### Standard LSP types ###
+##########################
+
+DocumentUri = NewType("DocumentUri", str)
+
+
+class Position(TypedDict):
+    line: int
+    character: int
+
+
+class Range(TypedDict):
+    start: Position
+    end: Position
+
+
+class TextEdit(TypedDict):
+    range: Range
+    newText: str
+
+
+class WorkspaceEdit(TypedDict):
+    changes: Optional[Dict[DocumentUri, List[TextEdit]]]
+    # documentChanges: ...
+    # changeAnnotations: ...
+
+
+class ApplyWorkspaceEditParams(TypedDict):
+    label: Optional[str]
+    edit: WorkspaceEdit
+
+
+class Command(TypedDict):
+    title: str
+    command: str
+    arguments: Optional[List[Any]]
+
+
+CodeActionKind = Literal[
+    "",
+    "quickfix",
+    "refactor",
+    "refactor.extract",
+    "refactor.inline",
+    "refactor.rewrite",
+    "source",
+    "source.organizeImports",
+    "source.fixAll",
+]
+
+
+class CodeAction(TypedDict):
+    title: str
+    kind: Optional[CodeActionKind]
+    # diagnostics: Optional[List[Diagnostic]]
+    # isPreferred: Optional[bool]
+    # disabled: Optional[_CodeActionDisabledReason]
+    # edit: Optional[WorkspaceEdit]
+    command: Optional[Command]
+    # data: Optional[Any]
+
+
+########################
+### pylsp-rope types ###
+########################
+
+DocumentContent = NewType("DocumentContent", str)
+Line = NewType("Line", str)
+LineNumber = NewType("LineNumber", int)
+CharNumber = NewType("CharNumber", int)
+
+
+class SimpleWorkspaceEdit(TypedDict):
+    """This is identical to WorkspaceEdit, but `changes` field is not 
optional."""
+
+    changes: Dict[DocumentUri, List[TextEdit]]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope.egg-info/PKG-INFO 
new/pylsp-rope-0.1.6/pylsp_rope.egg-info/PKG-INFO
--- old/pylsp-rope-0.1.4/pylsp_rope.egg-info/PKG-INFO   2021-10-05 
05:14:48.000000000 +0200
+++ new/pylsp-rope-0.1.6/pylsp_rope.egg-info/PKG-INFO   2021-10-12 
04:16:42.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pylsp-rope
-Version: 0.1.4
+Version: 0.1.6
 Summary: Extended refactoring capabilities for Python LSP Server using Rope.
 Home-page: https://github.com/python-rope/pylsp-rope
 Author: Lie Ryan
@@ -17,11 +17,14 @@
 Requires-Python: >=3.6
 Description-Content-Type: text/markdown
 Provides-Extra: dev
+Provides-Extra: test
 License-File: LICENSE
 License-File: AUTHORS.txt
 
 # pylsp-rope
 
+[![Tests](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml/badge.svg)](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml)
+
 Extended refactoring capabilities for Python LSP Server using
 [Rope](https://github.com/python-rope/rope).
 
@@ -58,6 +61,7 @@
 - extract method (codeAction)
 - extract variable (codeAction)
 - inline method/variable/parameter (codeAction)
+- use function (codeAction)
 - method to method object (codeAction)
 - more to come...
 
@@ -68,19 +72,28 @@
 
 ### Extract method
 
-This refactoring works by triggering a CodeAction when selecting a block of 
code.
+This refactoring works by triggering a CodeAction when selecting a block of
+code.
 
 ### Extract variable
 
-This refactoring works by triggering a CodeAction when selecting a Python 
expression.
+This refactoring works by triggering a CodeAction when selecting a Python
+expression.
 
 ### Inline
 
-This refactoring works by triggering a CodeAction when the cursor is on a 
resolvable Python identifier.
+This refactoring works by triggering a CodeAction when the cursor is on a
+resolvable Python identifier.
+
+### Use function
+
+This works by triggering a CodeAction when the cursor is on the function name
+of a `def` statement.
 
 ### Method to method object
 
-This refactoring works when the cursor is on a function definition.
+This works by triggering a CodeAction when the cursor is on the function name
+of a `def` statement.
 
 ## Caveat
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope.egg-info/SOURCES.txt 
new/pylsp-rope-0.1.6/pylsp_rope.egg-info/SOURCES.txt
--- old/pylsp-rope-0.1.4/pylsp_rope.egg-info/SOURCES.txt        2021-10-05 
05:14:48.000000000 +0200
+++ new/pylsp-rope-0.1.6/pylsp_rope.egg-info/SOURCES.txt        2021-10-12 
04:16:42.000000000 +0200
@@ -10,7 +10,9 @@
 pylsp_rope/lsp_diff.py
 pylsp_rope/plugin.py
 pylsp_rope/project.py
+pylsp_rope/rope.py
 pylsp_rope/text.py
+pylsp_rope/typing.py
 pylsp_rope.egg-info/PKG-INFO
 pylsp_rope.egg-info/SOURCES.txt
 pylsp_rope.egg-info/dependency_links.txt
@@ -26,10 +28,13 @@
 test/test_lsp_diff.py
 test/test_method_to_method_object.py
 test/test_project.py
+test/test_usefunction.py
 test/fixtures/function.py
 test/fixtures/many_changes.py
 test/fixtures/many_changes_inlined.py
 test/fixtures/method_object.py
+test/fixtures/method_object_use_function.py
 test/fixtures/simple.py
 test/fixtures/simple_extract_method.py
-test/fixtures/simple_extract_variable.py
\ No newline at end of file
+test/fixtures/simple_extract_variable.py
+test/fixtures/use_function.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope.egg-info/requires.txt 
new/pylsp-rope-0.1.6/pylsp_rope.egg-info/requires.txt
--- old/pylsp-rope-0.1.4/pylsp_rope.egg-info/requires.txt       2021-10-05 
05:14:48.000000000 +0200
+++ new/pylsp-rope-0.1.6/pylsp_rope.egg-info/requires.txt       2021-10-12 
04:16:42.000000000 +0200
@@ -1,6 +1,13 @@
 python-lsp-server
 rope
 
+[:python_version < "3.8"]
+typing-extensions
+
 [dev]
+build
 pytest
 twine
+
+[test]
+pytest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/pyproject.toml 
new/pylsp-rope-0.1.6/pyproject.toml
--- old/pylsp-rope-0.1.4/pyproject.toml 2021-10-02 16:59:30.000000000 +0200
+++ new/pylsp-rope-0.1.6/pyproject.toml 2021-10-12 02:48:36.000000000 +0200
@@ -1,2 +1,10 @@
 [tool.black]
 exclude = '.ropeproject|test/fixtures'
+
+
+[tool.mypy]
+python_version = "3.6"
+warn_return_any = true
+warn_unused_configs = true
+ignore_missing_imports = true
+check_untyped_defs = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/setup.cfg 
new/pylsp-rope-0.1.6/setup.cfg
--- old/pylsp-rope-0.1.4/setup.cfg      2021-10-05 05:14:48.619781300 +0200
+++ new/pylsp-rope-0.1.6/setup.cfg      2021-10-12 04:16:42.299155200 +0200
@@ -1,6 +1,6 @@
 [metadata]
 name = pylsp-rope
-version = 0.1.4
+version = 0.1.6
 author = Lie Ryan
 author_email = lie.1...@gmail.com
 url = https://github.com/python-rope/pylsp-rope
@@ -22,6 +22,7 @@
 install_requires = 
        python-lsp-server
        rope
+       typing-extensions; python_version < '3.8'
 python_requires = >= 3.6
 
 [options.entry_points]
@@ -29,8 +30,11 @@
 
 [options.extras_require]
 dev = 
+       build
        pytest
        twine
+test = 
+       pytest
 
 [egg_info]
 tag_build = 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/fixtures/function.py 
new/pylsp-rope-0.1.6/test/fixtures/function.py
--- old/pylsp-rope-0.1.4/test/fixtures/function.py      2021-10-05 
05:04:03.000000000 +0200
+++ new/pylsp-rope-0.1.6/test/fixtures/function.py      2021-10-08 
08:38:45.000000000 +0200
@@ -1,3 +1,7 @@
 def add(a, b):
-    print(f"{a} + {b} = {a + b}")
     return a + b
+
+
+def main():
+    a, b = 10, 20
+    print(f"{a} + {b} = {a + b}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/fixtures/method_object.py 
new/pylsp-rope-0.1.6/test/fixtures/method_object.py
--- old/pylsp-rope-0.1.4/test/fixtures/method_object.py 2021-10-05 
05:04:03.000000000 +0200
+++ new/pylsp-rope-0.1.6/test/fixtures/method_object.py 2021-10-08 
08:49:51.000000000 +0200
@@ -9,5 +9,9 @@
         self.b = b
 
     def __call__(self):
-        print(f"{self.a} + {self.b} = {self.a + self.b}")
         return self.a + self.b
+
+
+def main():
+    a, b = 10, 20
+    print(f"{a} + {b} = {a + b}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pylsp-rope-0.1.4/test/fixtures/method_object_use_function.py 
new/pylsp-rope-0.1.6/test/fixtures/method_object_use_function.py
--- old/pylsp-rope-0.1.4/test/fixtures/method_object_use_function.py    
1970-01-01 01:00:00.000000000 +0100
+++ new/pylsp-rope-0.1.6/test/fixtures/method_object_use_function.py    
2021-10-08 08:50:22.000000000 +0200
@@ -0,0 +1,19 @@
+import function
+
+def add(a, b):
+    return NewMethodObject(a, b)()
+
+
+class NewMethodObject(object):
+
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
+
+    def __call__(self):
+        return function.add(self.a, self.b)
+
+
+def main():
+    a, b = 10, 20
+    print(f"{a} + {b} = {function.add(a, b)}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/fixtures/use_function.py 
new/pylsp-rope-0.1.6/test/fixtures/use_function.py
--- old/pylsp-rope-0.1.4/test/fixtures/use_function.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/pylsp-rope-0.1.6/test/fixtures/use_function.py  2021-10-08 
08:39:07.000000000 +0200
@@ -0,0 +1,7 @@
+def add(a, b):
+    return a + b
+
+
+def main():
+    a, b = 10, 20
+    print(f"{a} + {b} = {add(a, b)}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/helpers.py 
new/pylsp-rope-0.1.6/test/helpers.py
--- old/pylsp-rope-0.1.4/test/helpers.py        2021-10-04 06:45:43.000000000 
+0200
+++ new/pylsp-rope-0.1.6/test/helpers.py        2021-10-08 13:30:16.000000000 
+0200
@@ -1,48 +1,90 @@
+from typing import (
+    Any,
+    Collection,
+    Dict,
+    List,
+)
 from unittest.mock import ANY, call
 
+from pylsp.workspace import Document
+
+from pylsp_rope.typing import (
+    DocumentContent,
+    DocumentUri,
+    SimpleWorkspaceEdit,
+    TextEdit,
+)
 from test.conftest import read_fixture_file
 
 
-def assert_code_actions_do_not_offer(response, command):
+def assert_code_actions_do_not_offer(response: Dict, command: str) -> None:
     for action in response:
         assert action["command"] != command, f"CodeAction should not offer 
{action}"
 
 
-def assert_wholefile_changeset(document_changeset, target):
-    assert len(document_changeset) == 1
-    (change,) = document_changeset
+def assert_text_edits(document_edits: List[TextEdit], target: str) -> 
DocumentContent:
     new_text = read_fixture_file(target)
-    assert change["newText"].strip() == new_text.strip()
-    return new_text
+    for change in document_edits:
+        assert change["newText"] in new_text
+    return DocumentContent(new_text)
 
 
-def assert_changeset(document_changeset, target):
-    new_text = read_fixture_file(target)
-    for change in document_changeset:
-        assert change["newText"] in new_text
-    return new_text
+def assert_single_document_edit(
+    edit_request: Any, document: Document
+) -> List[TextEdit]:
+    workspace_edit = assert_is_apply_edit_request(edit_request)
+
+    document_uri: DocumentUri = document.uri
+    assert_modified_documents(
+        workspace_edit,
+        document_uris={document_uri},
+    )
+
+    assert len(workspace_edit["changes"]) == 1
+    document_edits = workspace_edit["changes"][document_uri]
+    assert isinstance(document_edits, list)
+    return document_edits
 
 
-def assert_single_document_edit(edit_request, document):
+def assert_is_apply_edit_request(edit_request: Any) -> SimpleWorkspaceEdit:
     assert edit_request == call(
         "workspace/applyEdit",
         {
             "edit": {
-                "changes": {
-                    document.uri: ANY,
-                },
+                "changes": ANY,
             },
         },
     )
 
-    (document_changeset,) = edit_request[0][1]["edit"]["changes"].values()
-    for change in document_changeset:
-        assert change == {
-            "range": {
-                "start": {"line": ANY, "character": ANY},
-                "end": {"line": ANY, "character": ANY},
-            },
-            "newText": ANY,
-        }
+    workspace_edit: SimpleWorkspaceEdit = edit_request[0][1]["edit"]
+    for document_uri, document_edits in workspace_edit["changes"].items():
+        assert is_document_uri(document_uri)
+        for change in document_edits:
+            assert change == {
+                "range": {
+                    "start": {"line": ANY, "character": ANY},
+                    "end": {"line": ANY, "character": ANY},
+                },
+                "newText": ANY,
+            }
+
+    return workspace_edit
+
+
+def is_document_uri(uri: DocumentUri) -> bool:
+    return isinstance(uri, str) and uri.startswith("file://")
+
+
+def assert_modified_documents(
+    workspace_edit: SimpleWorkspaceEdit,
+    document_uris: Collection[DocumentUri],
+) -> None:
+    assert workspace_edit["changes"].keys() == set(document_uris)
+
 
-    return document_changeset
+def assert_unmodified_document(
+    workspace_edit: SimpleWorkspaceEdit,
+    document_uri: DocumentUri,
+) -> None:
+    assert is_document_uri(document_uri)
+    assert document_uri not in workspace_edit["changes"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_commands.py 
new/pylsp-rope-0.1.6/test/test_commands.py
--- old/pylsp-rope-0.1.4/test/test_commands.py  2021-10-03 07:13:06.000000000 
+0200
+++ new/pylsp-rope-0.1.6/test/test_commands.py  2021-10-07 19:25:17.000000000 
+0200
@@ -1,4 +1,10 @@
-from pylsp_rope.plugin import pylsp_commands
+from unittest.mock import patch
+
+from pylsp.lsp import MessageType
+
+from pylsp_rope import commands
+from pylsp_rope.plugin import pylsp_commands, pylsp_execute_command
+from pylsp_rope.text import Position
 
 
 def test_command_registration(config, workspace):
@@ -7,3 +13,38 @@
     assert isinstance(commands, list)
     assert all(isinstance(cmd, str) for cmd in commands)
     assert all(cmd.startswith("pylsp_rope.") for cmd in commands)
+
+
+def test_command_error_handling(caplog, config, workspace, document):
+    """
+    pylsp_execute_command should never raise an error when executeCommand().
+
+    Instead, we'll show an error message to the user.
+    """
+
+    arguments = [
+        {
+            "document_uri": document.uri,
+            "position": Position(1),
+        },
+    ]
+
+    with patch(
+        "pylsp_rope.plugin.CommandRefactorInline.__call__",
+        side_effect=Exception("some unexpected exception"),
+    ):
+        pylsp_execute_command(
+            config,
+            workspace,
+            command=commands.COMMAND_REFACTOR_INLINE,
+            arguments=arguments,
+        )
+
+    workspace._endpoint.notify.assert_called_once_with(
+        "window/showMessage",
+        params={
+            "type": MessageType.Error,
+            "message": f"pylsp-rope: some unexpected exception",
+        },
+    )
+    assert "Traceback (most recent call last):" in caplog.text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_extract.py 
new/pylsp-rope-0.1.6/test/test_extract.py
--- old/pylsp-rope-0.1.4/test/test_extract.py   2021-10-05 04:03:13.000000000 
+0200
+++ new/pylsp-rope-0.1.6/test/test_extract.py   2021-10-08 15:20:25.000000000 
+0200
@@ -1,7 +1,7 @@
-from pylsp_rope import commands, plugin
+from pylsp_rope import commands, plugin, typing
 from pylsp_rope.text import Range
 from test.helpers import (
-    assert_changeset,
+    assert_text_edits,
     assert_code_actions_do_not_offer,
     assert_single_document_edit,
 )
@@ -21,10 +21,11 @@
         context=code_action_context,
     )
 
-    expected = {
+    expected: typing.CodeAction = {
         "title": "Extract variable",
         "kind": "refactor.extract",
         "command": {
+            "title": "Extract variable",
             "command": commands.COMMAND_REFACTOR_EXTRACT_VARIABLE,
             "arguments": [
                 {
@@ -37,6 +38,7 @@
 
     assert expected in response
 
+    assert expected["command"] is not None
     command = expected["command"]["command"]
     arguments = expected["command"]["arguments"]
 
@@ -49,8 +51,8 @@
 
     edit_request = workspace._endpoint.request.call_args
 
-    document_changeset = assert_single_document_edit(edit_request, document)
-    new_text = assert_changeset(document_changeset, 
target="simple_extract_variable.py")
+    document_edits = assert_single_document_edit(edit_request, document)
+    new_text = assert_text_edits(document_edits, 
target="simple_extract_variable.py")
     assert "extracted_variable = " in new_text
 
 
@@ -87,10 +89,11 @@
         context=code_action_context,
     )
 
-    expected = {
+    expected: typing.CodeAction = {
         "title": "Extract method",
         "kind": "refactor.extract",
         "command": {
+            "title": "Extract method",
             "command": commands.COMMAND_REFACTOR_EXTRACT_METHOD,
             "arguments": [
                 {
@@ -103,6 +106,7 @@
 
     assert expected in response
 
+    assert expected["command"] is not None
     command = expected["command"]["command"]
     arguments = expected["command"]["arguments"]
 
@@ -115,6 +119,6 @@
 
     edit_request = workspace._endpoint.request.call_args
 
-    document_changeset = assert_single_document_edit(edit_request, document)
-    new_text = assert_changeset(document_changeset, 
target="simple_extract_method.py")
+    document_edits = assert_single_document_edit(edit_request, document)
+    new_text = assert_text_edits(document_edits, 
target="simple_extract_method.py")
     assert "def extracted_method(" in new_text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_inline.py 
new/pylsp-rope-0.1.6/test/test_inline.py
--- old/pylsp-rope-0.1.4/test/test_inline.py    2021-10-04 08:39:46.000000000 
+0200
+++ new/pylsp-rope-0.1.6/test/test_inline.py    2021-10-08 18:42:20.000000000 
+0200
@@ -1,8 +1,8 @@
-from pylsp_rope import plugin, commands
+from pylsp_rope import commands, plugin, typing
 from pylsp_rope.text import Range
 from test.conftest import create_document
 from test.helpers import (
-    assert_changeset,
+    assert_text_edits,
     assert_code_actions_do_not_offer,
     assert_single_document_edit,
 )
@@ -22,10 +22,11 @@
         context=code_action_context,
     )
 
-    expected = {
-        "title": "Inline method/variable",
+    expected: typing.CodeAction = {
+        "title": "Inline method/variable/parameter",
         "kind": "refactor.inline",
         "command": {
+            "title": "Inline method/variable/parameter",
             "command": commands.COMMAND_REFACTOR_INLINE,
             "arguments": [
                 {
@@ -38,6 +39,7 @@
 
     assert expected in response
 
+    assert expected["command"] is not None
     command = expected["command"]["command"]
     arguments = expected["command"]["arguments"]
 
@@ -50,8 +52,8 @@
 
     edit_request = workspace._endpoint.request.call_args
 
-    document_changeset = assert_single_document_edit(edit_request, document)
-    new_text = assert_changeset(document_changeset, target="simple.py")
+    document_edits = assert_single_document_edit(edit_request, document)
+    new_text = assert_text_edits(document_edits, target="simple.py")
     assert "extracted_method" not in new_text
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pylsp-rope-0.1.4/test/test_method_to_method_object.py 
new/pylsp-rope-0.1.6/test/test_method_to_method_object.py
--- old/pylsp-rope-0.1.4/test/test_method_to_method_object.py   2021-10-05 
03:38:45.000000000 +0200
+++ new/pylsp-rope-0.1.6/test/test_method_to_method_object.py   2021-10-09 
13:28:00.000000000 +0200
@@ -1,8 +1,8 @@
-from pylsp_rope import commands, plugin
+from pylsp_rope import commands, plugin, typing
 from pylsp_rope.text import Range
 from test.conftest import create_document
 from test.helpers import (
-    assert_changeset,
+    assert_text_edits,
     assert_code_actions_do_not_offer,
     assert_single_document_edit,
 )
@@ -22,10 +22,11 @@
         context=code_action_context,
     )
 
-    expected = {
+    expected: typing.CodeAction = {
         "title": "To method object",
-        "kind": "refactor",
+        "kind": "refactor.rewrite",
         "command": {
+            "title": "To method object",
             "command": commands.COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT,
             "arguments": [
                 {
@@ -38,6 +39,7 @@
 
     assert expected in response
 
+    assert expected["command"] is not None
     command = expected["command"]["command"]
     arguments = expected["command"]["arguments"]
 
@@ -50,7 +52,29 @@
 
     edit_request = workspace._endpoint.request.call_args
 
-    document_changeset = assert_single_document_edit(edit_request, document)
-    new_text = assert_changeset(document_changeset, target="method_object.py")
+    document_edits = assert_single_document_edit(edit_request, document)
+    new_text = assert_text_edits(document_edits, target="method_object.py")
     assert "class NewMethodObject(object)" in new_text
     assert "NewMethodObject(a, b)()" in new_text
+
+
+def test_method_to_method_object_not_offered_when_selecting_unsuitable_range(
+    config, workspace, code_action_context
+):
+    document = create_document(workspace, "function.py")
+    line = 1
+    pos = document.lines[line].index("return")
+    selection = Range((line, pos), (line, pos))
+
+    response = plugin.pylsp_code_actions(
+        config=config,
+        workspace=workspace,
+        document=document,
+        range=selection,
+        context=code_action_context,
+    )
+
+    assert_code_actions_do_not_offer(
+        response,
+        command=commands.COMMAND_REFACTOR_INLINE,
+    )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_project.py 
new/pylsp-rope-0.1.6/test/test_project.py
--- old/pylsp-rope-0.1.4/test/test_project.py   2021-10-05 03:48:26.000000000 
+0200
+++ new/pylsp-rope-0.1.6/test/test_project.py   2021-10-08 15:36:52.000000000 
+0200
@@ -3,7 +3,7 @@
 from pylsp_rope.project import (
     get_project,
     get_resource,
-    rope_changeset_to_workspace_changeset,
+    rope_changeset_to_workspace_edit,
 )
 from test.conftest import create_document
 
@@ -11,12 +11,12 @@
 def test_rope_changeset_to_workspace_changeset(workspace):
     document = create_document(workspace, "many_changes.py")
     rope_changeset = get_rope_changeset(workspace, document)
-    workspace_changeset = rope_changeset_to_workspace_changeset(
+    workspace_edit = rope_changeset_to_workspace_edit(
         workspace,
         rope_changeset,
     )
 
-    assert workspace_changeset == {
+    assert workspace_edit["changes"] == {
         document.uri: [
             {
                 "range": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_usefunction.py 
new/pylsp-rope-0.1.6/test/test_usefunction.py
--- old/pylsp-rope-0.1.4/test/test_usefunction.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/pylsp-rope-0.1.6/test/test_usefunction.py       2021-10-09 
21:23:49.000000000 +0200
@@ -0,0 +1,127 @@
+from pylsp_rope import plugin, commands, typing
+from pylsp_rope.text import Range
+from test.conftest import create_document
+from test.helpers import (
+    assert_text_edits,
+    assert_is_apply_edit_request,
+    assert_modified_documents,
+    assert_unmodified_document,
+)
+
+
+def test_use_function_globally(config, workspace, code_action_context):
+    document = create_document(workspace, "function.py")
+    document2 = create_document(workspace, "method_object.py")
+    line = 0
+    pos = document.lines[line].index("def add") + 4
+    selection = Range((line, pos), (line, pos))
+
+    response = plugin.pylsp_code_actions(
+        config=config,
+        workspace=workspace,
+        document=document,
+        range=selection,
+        context=code_action_context,
+    )
+
+    expected: typing.CodeAction = {
+        "title": "Use function",
+        "kind": "refactor",
+        "command": {
+            "title": "Use function",
+            "command": commands.COMMAND_REFACTOR_USE_FUNCTION,
+            "arguments": [
+                {
+                    "document_uri": document.uri,
+                    "position": selection["start"],
+                },
+            ],
+        },
+    }
+
+    assert expected in response
+
+    assert expected["command"] is not None
+    command = expected["command"]["command"]
+    arguments = expected["command"]["arguments"]
+
+    response = plugin.pylsp_execute_command(
+        config=config,
+        workspace=workspace,
+        command=command,
+        arguments=arguments,
+    )
+
+    edit_request = workspace._endpoint.request.call_args
+
+    workspace_edit = assert_is_apply_edit_request(edit_request)
+    assert_modified_documents(workspace_edit, {document.uri, document2.uri})
+
+    new_text = assert_text_edits(
+        workspace_edit["changes"][document.uri], target="use_function.py"
+    )
+    assert "{add(a, b)}" in new_text
+
+    new_text = assert_text_edits(
+        workspace_edit["changes"][document2.uri], 
target="method_object_use_function.py"
+    )
+    assert "import function" in new_text
+    assert "{function.add(a, b)}" in new_text
+
+
+def test_use_function_in_current_file(config, workspace, code_action_context):
+    document = create_document(workspace, "function.py")
+    document2 = create_document(workspace, "method_object.py")
+
+    line = 0
+    pos = document.lines[line].index("def add") + 4
+    selection = Range((line, pos), (line, pos))
+
+    response = plugin.pylsp_code_actions(
+        config=config,
+        workspace=workspace,
+        document=document,
+        range=selection,
+        context=code_action_context,
+    )
+
+    expected: typing.CodeAction = {
+        "title": "Use function for current file only",
+        "kind": "refactor",
+        "command": {
+            "title": "Use function for current file only",
+            "command": commands.COMMAND_REFACTOR_USE_FUNCTION,
+            "arguments": [
+                {
+                    "document_uri": document.uri,
+                    "position": selection["start"],
+                    "documents": [document.uri],
+                },
+            ],
+        },
+    }
+
+    assert expected in response
+
+    assert expected["command"] is not None
+    command = expected["command"]["command"]
+    arguments = expected["command"]["arguments"]
+
+    response = plugin.pylsp_execute_command(
+        config=config,
+        workspace=workspace,
+        command=command,
+        arguments=arguments,
+    )
+
+    edit_request = workspace._endpoint.request.call_args
+
+    workspace_edit = assert_is_apply_edit_request(edit_request)
+    assert_modified_documents(workspace_edit, {document.uri})
+
+    new_text = assert_text_edits(
+        workspace_edit["changes"][document.uri], target="use_function.py"
+    )
+    assert "{add(a, b)}" in new_text
+
+    assert_unmodified_document(workspace_edit, document2.uri)

Reply via email to