2 # 1. Working directory.
14 import rope.base.project
15 import rope.base.taskhandle
16 from rope.base import libutils
17 from rope.refactor.rename import Rename
18 from rope.refactor.extract import ExtractMethod, ExtractVariable
19 from rope.refactor.importutils import FromImport, NormalImport
20 from rope.refactor.importutils.module_imports import ModuleImports
24 "message": "Rope not installed",
26 "type": "ModuleNotFoundError",
28 sys.stderr.write(json.dumps(jsonMessage))
31 WORKSPACE_ROOT = sys.argv[1]
32 ROPE_PROJECT_FOLDER = '.vim/.ropeproject'
35 class RefactorProgress:
37 Refactor progress information
40 def __init__(self, name="Task Name", message=None, percent=0):
42 self.message = message
43 self.percent = percent
63 def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""):
64 self.filePath = filePath
66 self.fileMode = fileMode
70 new = x["new_contents"]
71 old = x["old_contents"]
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(
83 return "".join(list(result))
86 def get_diff(changeset):
87 """This is a copy of the code form the ChangeSet.get_description method found in Rope."""
88 new = changeset.new_contents
89 old = changeset.old_contents
91 if changeset.resource.exists():
92 old = changeset.resource.read()
96 # Ensure code has a trailing empty lines, before generating a diff.
97 # https://github.com/Microsoft/vscode-python/issues/695.
98 old_lines = old.splitlines(True)
99 if not old_lines[-1].endswith("\n"):
100 old_lines[-1] = old_lines[-1] + os.linesep
101 new = new + os.linesep
103 result = difflib.unified_diff(
105 new.splitlines(True),
106 "a/" + changeset.resource.path,
107 "b/" + changeset.resource.path,
109 return "".join(list(result))
112 class BaseRefactoring(object):
114 Base class for refactorings
117 def __init__(self, project, resource, name="Refactor", progressCallback=None):
118 self._progressCallback = progressCallback
119 self._handle = rope.base.taskhandle.TaskHandle(name)
120 self._handle.add_observer(self._update_progress)
121 self.project = project
122 self.resource = resource
125 def _update_progress(self):
126 jobset = self._handle.current_jobset()
127 if jobset and not self._progressCallback is None:
128 progress = RefactorProgress()
129 # getting current job set name
130 if jobset.get_name() is not None:
131 progress.name = jobset.get_name()
132 # getting active job name
133 if jobset.get_active_job_name() is not None:
134 progress.message = jobset.get_active_job_name()
135 # adding done percent
136 percent = jobset.get_percent_done()
137 if percent is not None:
138 progress.percent = percent
139 if not self._progressCallback is None:
140 self._progressCallback(progress)
148 except rope.base.exceptions.InterruptedTaskError:
149 # we can ignore this exception, as user has cancelled refactoring
152 def onRefactor(self):
154 To be implemented by each base class
159 class RenameRefactor(BaseRefactoring):
165 progressCallback=None,
169 BaseRefactoring.__init__(self, project, resource, name, progressCallback)
170 self._newName = newName
171 self.startOffset = startOffset
173 def onRefactor(self):
174 renamed = Rename(self.project, self.resource, self.startOffset)
175 changes = renamed.get_changes(self._newName, task_handle=self._handle)
176 for item in changes.changes:
177 if isinstance(item, rope.base.change.ChangeContents):
179 Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))
182 raise Exception("Unknown Change")
185 class ExtractVariableRefactor(BaseRefactoring):
190 name="Extract Variable",
191 progressCallback=None,
198 BaseRefactoring.__init__(self, project, resource, name, progressCallback)
199 self._newName = newName
200 self._startOffset = startOffset
201 self._endOffset = endOffset
202 self._similar = similar
203 self._global = global_
205 def onRefactor(self):
206 renamed = ExtractVariable(
207 self.project, self.resource, self._startOffset, self._endOffset
209 changes = renamed.get_changes(self._newName, self._similar, self._global)
210 for item in changes.changes:
211 if isinstance(item, rope.base.change.ChangeContents):
213 Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))
216 raise Exception("Unknown Change")
219 class ExtractMethodRefactor(ExtractVariableRefactor):
224 name="Extract Method",
225 progressCallback=None,
232 ExtractVariableRefactor.__init__(
238 startOffset=startOffset,
245 def onRefactor(self):
246 renamed = ExtractMethod(
247 self.project, self.resource, self._startOffset, self._endOffset
249 changes = renamed.get_changes(self._newName, self._similar, self._global)
250 for item in changes.changes:
251 if isinstance(item, rope.base.change.ChangeContents):
253 Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))
256 raise Exception("Unknown Change")
259 class ImportRefactor(BaseRefactoring):
268 BaseRefactoring.__init__(self, project, resource, name='Add Import', progressCallback=None)
271 self._parent = parent
273 def onRefactor(self):
275 import_info = FromImport(self._parent, 0, [(self._name, None)])
277 import_info = NormalImport([(self._name, None)])
279 pymodule = self.project.get_pymodule(self.resource)
280 module_imports = ModuleImports(self.project, pymodule)
281 module_imports.add_import(import_info)
282 changed_source = module_imports.get_changed_source()
285 "old_contents": self._text,
286 "new_contents": changed_source,
287 "path": self.resource.path
289 self.changes.append(Change(self.resource.path, ChangeType.EDIT, x_diff(changeset)))
291 raise Exception('Unknown Change')
294 class RopeRefactoring(object):
296 self.default_sys_path = sys.path
297 self._input = io.open(sys.stdin.fileno(), encoding="utf-8")
299 def _rename(self, filePath, start, newName, indent_size):
303 project = rope.base.project.Project(
305 ropefolder=ROPE_PROJECT_FOLDER,
307 indent_size=indent_size,
309 resourceToRefactor = libutils.path_to_resource(project, filePath)
310 refactor = RenameRefactor(
311 project, resourceToRefactor, startOffset=start, newName=newName
314 changes = refactor.changes
317 for change in changes:
318 valueToReturn.append({"diff": change.diff})
321 def _extractVariable(self, filePath, start, end, newName, indent_size):
325 project = rope.base.project.Project(
327 ropefolder=ROPE_PROJECT_FOLDER,
329 indent_size=indent_size,
331 resourceToRefactor = libutils.path_to_resource(project, filePath)
332 refactor = ExtractVariableRefactor(
341 changes = refactor.changes
344 for change in changes:
345 valueToReturn.append({"diff": change.diff})
348 def _extractMethod(self, filePath, start, end, newName, indent_size):
352 project = rope.base.project.Project(
354 ropefolder=ROPE_PROJECT_FOLDER,
356 indent_size=indent_size,
358 resourceToRefactor = libutils.path_to_resource(project, filePath)
359 refactor = ExtractMethodRefactor(
368 changes = refactor.changes
371 for change in changes:
372 valueToReturn.append({"diff": change.diff})
375 def _add_import(self, filePath, text, name, parent, indent_size):
379 project = rope.base.project.Project(
381 ropefolder=ROPE_PROJECT_FOLDER,
383 indent_size=indent_size,
385 resourceToRefactor = libutils.path_to_resource(project, filePath)
386 refactor = ImportRefactor(
394 changes = refactor.changes
397 for change in changes:
398 valueToReturn.append({"diff": change.diff})
401 def _serialize(self, identifier, results):
403 Serializes the refactor results
405 return json.dumps({"id": identifier, "results": results})
407 def _deserialize(self, request):
408 """Deserialize request from VSCode.
411 request: String with raw request from VSCode.
414 Python dictionary with request data.
416 return json.loads(request)
418 def _process_request(self, request):
419 """Accept serialized request from VSCode and write response."""
420 request = self._deserialize(request)
421 lookup = request.get("lookup", "")
425 elif lookup == "rename":
426 changes = self._rename(
428 int(request["start"]),
430 int(request["indent_size"]),
432 return self._write_response(self._serialize(request["id"], changes))
433 elif lookup == "add_import":
434 changes = self._add_import(
438 request.get("parent", None),
439 int(request["indent_size"]),
441 return self._write_response(self._serialize(request["id"], changes))
442 elif lookup == "extract_variable":
443 changes = self._extractVariable(
445 int(request["start"]),
448 int(request["indent_size"]),
450 return self._write_response(self._serialize(request["id"], changes))
451 elif lookup == "extract_method":
452 changes = self._extractMethod(
454 int(request["start"]),
457 int(request["indent_size"]),
459 return self._write_response(self._serialize(request["id"], changes))
461 def _write_response(self, response):
462 sys.stdout.write(response + "\n")
466 self._write_response("STARTED")
469 self._process_request(self._input.readline())
471 exc_type, exc_value, exc_tb = sys.exc_info()
472 tb_info = traceback.extract_tb(exc_tb)
475 "message": str(exc_value),
476 "traceback": str(tb_info),
477 "type": str(exc_type),
479 sys.stderr.write(json.dumps(jsonMessage))
483 if __name__ == "__main__":
484 RopeRefactoring().watch()