2 # 1. Working directory.
14 from rope.base import libutils
15 from rope.refactor.rename import Rename
16 from rope.refactor.extract import ExtractMethod, ExtractVariable
17 import rope.base.project
18 import rope.base.taskhandle
20 jsonMessage = {'error': True, 'message': 'Rope not installed', 'traceback': '', 'type': 'ModuleNotFoundError'}
21 sys.stderr.write(json.dumps(jsonMessage))
24 WORKSPACE_ROOT = sys.argv[1]
25 ROPE_PROJECT_FOLDER = '.vim/.ropeproject'
28 class RefactorProgress():
30 Refactor progress information
33 def __init__(self, name='Task Name', message=None, percent=0):
35 self.message = message
36 self.percent = percent
55 def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""):
56 self.filePath = filePath
58 self.fileMode = fileMode
60 def get_diff(changeset):
61 """This is a copy of the code form the ChangeSet.get_description method found in Rope."""
62 new = changeset.new_contents
63 old = changeset.old_contents
65 if changeset.resource.exists():
66 old = changeset.resource.read()
70 # Ensure code has a trailing empty lines, before generating a diff.
71 # https://github.com/Microsoft/vscode-python/issues/695.
72 old_lines = old.splitlines(True)
73 if not old_lines[-1].endswith('\n'):
74 old_lines[-1] = old_lines[-1] + os.linesep
75 new = new + os.linesep
77 result = difflib.unified_diff(
78 old_lines, new.splitlines(True),
79 'a/' + changeset.resource.path, 'b/' + changeset.resource.path)
80 return ''.join(list(result))
82 class BaseRefactoring(object):
84 Base class for refactorings
87 def __init__(self, project, resource, name="Refactor", progressCallback=None):
88 self._progressCallback = progressCallback
89 self._handle = rope.base.taskhandle.TaskHandle(name)
90 self._handle.add_observer(self._update_progress)
91 self.project = project
92 self.resource = resource
95 def _update_progress(self):
96 jobset = self._handle.current_jobset()
97 if jobset and not self._progressCallback is None:
98 progress = RefactorProgress()
99 # getting current job set name
100 if jobset.get_name() is not None:
101 progress.name = jobset.get_name()
102 # getting active job name
103 if jobset.get_active_job_name() is not None:
104 progress.message = jobset.get_active_job_name()
105 # adding done percent
106 percent = jobset.get_percent_done()
107 if percent is not None:
108 progress.percent = percent
109 if not self._progressCallback is None:
110 self._progressCallback(progress)
118 except rope.base.exceptions.InterruptedTaskError:
119 # we can ignore this exception, as user has cancelled refactoring
122 def onRefactor(self):
124 To be implemented by each base class
129 class RenameRefactor(BaseRefactoring):
131 def __init__(self, project, resource, name="Rename", progressCallback=None, startOffset=None, newName="new_Name"):
132 BaseRefactoring.__init__(self, project, resource,
133 name, progressCallback)
134 self._newName = newName
135 self.startOffset = startOffset
137 def onRefactor(self):
138 renamed = Rename(self.project, self.resource, self.startOffset)
139 changes = renamed.get_changes(self._newName, task_handle=self._handle)
140 for item in changes.changes:
141 if isinstance(item, rope.base.change.ChangeContents):
143 Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
145 raise Exception('Unknown Change')
148 class ExtractVariableRefactor(BaseRefactoring):
150 def __init__(self, project, resource, name="Extract Variable", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False):
151 BaseRefactoring.__init__(self, project, resource,
152 name, progressCallback)
153 self._newName = newName
154 self._startOffset = startOffset
155 self._endOffset = endOffset
156 self._similar = similar
157 self._global = global_
159 def onRefactor(self):
160 renamed = ExtractVariable(
161 self.project, self.resource, self._startOffset, self._endOffset)
162 changes = renamed.get_changes(
163 self._newName, self._similar, self._global)
164 for item in changes.changes:
165 if isinstance(item, rope.base.change.ChangeContents):
167 Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
169 raise Exception('Unknown Change')
172 class ExtractMethodRefactor(ExtractVariableRefactor):
174 def __init__(self, project, resource, name="Extract Method", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False):
175 ExtractVariableRefactor.__init__(self, project, resource,
176 name, progressCallback, startOffset=startOffset, endOffset=endOffset, newName=newName, similar=similar, global_=global_)
178 def onRefactor(self):
179 renamed = ExtractMethod(
180 self.project, self.resource, self._startOffset, self._endOffset)
181 changes = renamed.get_changes(
182 self._newName, self._similar, self._global)
183 for item in changes.changes:
184 if isinstance(item, rope.base.change.ChangeContents):
186 Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
188 raise Exception('Unknown Change')
191 class RopeRefactoring(object):
194 self.default_sys_path = sys.path
195 self._input = io.open(sys.stdin.fileno(), encoding='utf-8')
197 def _rename(self, filePath, start, newName, indent_size):
201 project = rope.base.project.Project(
202 WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size)
203 resourceToRefactor = libutils.path_to_resource(project, filePath)
204 refactor = RenameRefactor(
205 project, resourceToRefactor, startOffset=start, newName=newName)
207 changes = refactor.changes
210 for change in changes:
211 valueToReturn.append({'diff': change.diff})
214 def _extractVariable(self, filePath, start, end, newName, indent_size):
218 project = rope.base.project.Project(
219 WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size)
220 resourceToRefactor = libutils.path_to_resource(project, filePath)
221 refactor = ExtractVariableRefactor(
222 project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName, similar=True)
224 changes = refactor.changes
227 for change in changes:
228 valueToReturn.append({'diff': change.diff})
231 def _extractMethod(self, filePath, start, end, newName, indent_size):
235 project = rope.base.project.Project(
236 WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size)
237 resourceToRefactor = libutils.path_to_resource(project, filePath)
238 refactor = ExtractMethodRefactor(
239 project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName, similar=True)
241 changes = refactor.changes
244 for change in changes:
245 valueToReturn.append({'diff': change.diff})
248 def _serialize(self, identifier, results):
250 Serializes the refactor results
252 return json.dumps({'id': identifier, 'results': results})
254 def _deserialize(self, request):
255 """Deserialize request from VSCode.
258 request: String with raw request from VSCode.
261 Python dictionary with request data.
263 return json.loads(request)
265 def _process_request(self, request):
266 """Accept serialized request from VSCode and write response.
268 request = self._deserialize(request)
269 lookup = request.get('lookup', '')
273 elif lookup == 'rename':
274 changes = self._rename(request['file'], int(
275 request['start']), request['name'], int(request['indent_size']))
276 return self._write_response(self._serialize(request['id'], changes))
277 elif lookup == 'extract_variable':
278 changes = self._extractVariable(request['file'], int(
279 request['start']), int(request['end']), request['name'], int(request['indent_size']))
280 return self._write_response(self._serialize(request['id'], changes))
281 elif lookup == 'extract_method':
282 changes = self._extractMethod(request['file'], int(
283 request['start']), int(request['end']), request['name'], int(request['indent_size']))
284 return self._write_response(self._serialize(request['id'], changes))
286 def _write_response(self, response):
287 sys.stdout.write(response + '\n')
291 self._write_response("STARTED")
294 self._process_request(self._input.readline())
296 exc_type, exc_value, exc_tb = sys.exc_info()
297 tb_info = traceback.extract_tb(exc_tb)
298 jsonMessage = {'error': True, 'message': str(exc_value), 'traceback': str(tb_info), 'type': str(exc_type)}
299 sys.stderr.write(json.dumps(jsonMessage))
302 if __name__ == '__main__':
303 RopeRefactoring().watch()