Skip to content

Commit f25259f

Browse files
authored
Add support for renaming with Jedi (#801)
1 parent 515ff00 commit f25259f

File tree

9 files changed

+135
-24
lines changed

9 files changed

+135
-24
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414

1515
python3-test:
1616
docker:
17-
- image: "python:3.5-stretch"
17+
- image: "python:3.6-stretch"
1818
steps:
1919
- checkout
2020
# To test Jedi environments
@@ -35,7 +35,7 @@ jobs:
3535

3636
publish:
3737
docker:
38-
- image: "python:3.5-stretch"
38+
- image: "python:3.6-stretch"
3939
steps:
4040
- checkout
4141
- run: ./scripts/circle/pypi.sh

appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ environment:
66
PYTHON_VERSION: "2.7.15"
77
PYTHON_ARCH: "64"
88

9-
- PYTHON: "C:\\Python35"
10-
PYTHON_VERSION: "3.5.7"
9+
- PYTHON: "C:\\Python36"
10+
PYTHON_VERSION: "3.6.8"
1111
PYTHON_ARCH: "64"
1212

1313
matrix:

pyls/plugins/jedi_rename.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2020 Palantir Technologies, Inc.
2+
import logging
3+
4+
from pyls import hookimpl, uris, _utils
5+
6+
log = logging.getLogger(__name__)
7+
8+
9+
@hookimpl
10+
def pyls_rename(config, workspace, document, position, new_name): # pylint: disable=unused-argument
11+
log.debug('Executing rename of %s to %s', document.word_at_position(position), new_name)
12+
kwargs = _utils.position_to_jedi_linecolumn(document, position)
13+
kwargs['new_name'] = new_name
14+
try:
15+
refactoring = document.jedi_script().rename(**kwargs)
16+
except NotImplementedError:
17+
raise Exception('No support for renaming in Python 2/3.5 with Jedi. '
18+
'Consider using the rope_rename plugin instead')
19+
log.debug('Finished rename: %s', refactoring.get_diff())
20+
21+
return {
22+
'documentChanges': [
23+
{
24+
'textDocument': {
25+
'uri': uris.uri_with(document.uri, path=file_path),
26+
'version': workspace.get_document(document.uri).version,
27+
},
28+
'edits': [
29+
{
30+
'range': {
31+
'start': {'line': 0, 'character': 0},
32+
'end': {
33+
'line': _num_lines(changed_file.get_new_code()),
34+
'character': 0,
35+
},
36+
},
37+
'newText': changed_file.get_new_code(),
38+
}
39+
],
40+
}
41+
for file_path, changed_file in refactoring.get_changed_files().items()
42+
],
43+
}
44+
45+
46+
def _num_lines(file_contents):
47+
'Count the number of lines in the given string.'
48+
return len(file_contents.splitlines())

pyls/plugins/rope_rename.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
log = logging.getLogger(__name__)
1111

1212

13+
@hookimpl
14+
def pyls_settings():
15+
# Default rope_rename to disabled
16+
return {'plugins': {'rope_rename': {'enabled': False}}}
17+
18+
1319
@hookimpl
1420
def pyls_rename(config, workspace, document, position, new_name):
1521
rope_config = config.settings(document_path=document.path).get('rope', {})

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
'jedi_hover = pyls.plugins.hover',
8888
'jedi_highlight = pyls.plugins.highlight',
8989
'jedi_references = pyls.plugins.references',
90+
'jedi_rename = pyls.plugins.jedi_rename',
9091
'jedi_signature_help = pyls.plugins.signature',
9192
'jedi_symbols = pyls.plugins.symbols',
9293
'mccabe = pyls.plugins.mccabe_lint',

test/fixtures.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2017 Palantir Technologies, Inc.
2+
import os
23
import sys
34
from mock import Mock
45
import pytest
@@ -50,3 +51,23 @@ def config(workspace): # pylint: disable=redefined-outer-name
5051
@pytest.fixture
5152
def doc(workspace): # pylint: disable=redefined-outer-name
5253
return Document(DOC_URI, workspace, DOC)
54+
55+
56+
@pytest.fixture
57+
def temp_workspace_factory(workspace): # pylint: disable=redefined-outer-name
58+
'''
59+
Returns a function that creates a temporary workspace from the files dict.
60+
The dict is in the format {"file_name": "file_contents"}
61+
'''
62+
def fn(files):
63+
def create_file(name, content):
64+
fn = os.path.join(workspace.root_path, name)
65+
with open(fn, 'w') as f:
66+
f.write(content)
67+
workspace.put_document(uris.from_fs_path(fn), content)
68+
69+
for name, content in files.items():
70+
create_file(name, content)
71+
return workspace
72+
73+
return fn

test/plugins/test_jedi_rename.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2020 Palantir Technologies, Inc.
2+
import os
3+
import sys
4+
5+
import pytest
6+
from pyls import uris
7+
from pyls.plugins.jedi_rename import pyls_rename
8+
from pyls.workspace import Document
9+
10+
LT_PY36 = sys.version_info.major < 3 or (sys.version_info.major == 3 and sys.version_info.minor < 6)
11+
12+
DOC_NAME = 'test1.py'
13+
DOC = '''class Test1():
14+
pass
15+
16+
class Test2(Test1):
17+
pass
18+
'''
19+
20+
21+
@pytest.fixture
22+
def tmp_workspace(temp_workspace_factory):
23+
return temp_workspace_factory({DOC_NAME: DOC})
24+
25+
26+
@pytest.mark.skipif(LT_PY36, reason='Jedi refactoring isnt supported on Python 2.x/3.5')
27+
def test_jedi_rename(tmp_workspace, config): # pylint: disable=redefined-outer-name
28+
# rename the `Test1` class
29+
position = {'line': 0, 'character': 6}
30+
DOC_URI = uris.from_fs_path(os.path.join(tmp_workspace.root_path, DOC_NAME))
31+
doc = Document(DOC_URI, tmp_workspace)
32+
33+
result = pyls_rename(config, tmp_workspace, doc, position, 'ShouldBeRenamed')
34+
assert len(result.keys()) == 1
35+
36+
changes = result.get('documentChanges')
37+
assert len(changes) == 1
38+
changes = changes[0]
39+
40+
assert changes.get('edits') == [
41+
{
42+
'range': {
43+
'start': {'line': 0, 'character': 0},
44+
'end': {'line': 5, 'character': 0},
45+
},
46+
'newText': 'class ShouldBeRenamed():\n pass\n\nclass Test2(ShouldBeRenamed):\n pass\n',
47+
}
48+
]

test/plugins/test_references.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,11 @@
2626

2727

2828
@pytest.fixture
29-
def tmp_workspace(workspace):
30-
def create_file(name, content):
31-
fn = os.path.join(workspace.root_path, name)
32-
with open(fn, 'w') as f:
33-
f.write(content)
34-
workspace.put_document(uris.from_fs_path(fn), content)
35-
36-
create_file(DOC1_NAME, DOC1)
37-
create_file(DOC2_NAME, DOC2)
38-
39-
return workspace
29+
def tmp_workspace(temp_workspace_factory):
30+
return temp_workspace_factory({
31+
DOC1_NAME: DOC1,
32+
DOC2_NAME: DOC2,
33+
})
4034

4135

4236
def test_references(tmp_workspace): # pylint: disable=redefined-outer-name

test/plugins/test_rope_rename.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,8 @@ class Test2(Test1):
1515

1616

1717
@pytest.fixture
18-
def tmp_workspace(workspace):
19-
def create_file(name, content):
20-
fn = os.path.join(workspace.root_path, name)
21-
with open(fn, "w") as f:
22-
f.write(content)
23-
workspace.put_document(uris.from_fs_path(fn), content)
24-
25-
create_file(DOC_NAME, DOC)
26-
return workspace
18+
def tmp_workspace(temp_workspace_factory):
19+
return temp_workspace_factory({DOC_NAME: DOC})
2720

2821

2922
def test_rope_rename(tmp_workspace, config): # pylint: disable=redefined-outer-name

0 commit comments

Comments
 (0)