# Arguments are: # 1. Working directory. # 2. Rope folder import difflib import io import json import os import sys import traceback try: import rope from rope.base import libutils from rope.refactor.rename import Rename from rope.refactor.extract import ExtractMethod, ExtractVariable import rope.base.project import rope.base.taskhandle except: jsonMessage = {'error': True, 'message': 'Rope not installed', 'traceback': '', 'type': 'ModuleNotFoundError'} sys.stderr.write(json.dumps(jsonMessage)) sys.stderr.flush() WORKSPACE_ROOT = sys.argv[1] ROPE_PROJECT_FOLDER = '.vim/.ropeproject' class RefactorProgress(): """ Refactor progress information """ def __init__(self, name='Task Name', message=None, percent=0): self.name = name self.message = message self.percent = percent class ChangeType(): """ Change Type Enum """ EDIT = 0 NEW = 1 DELETE = 2 class Change(): """ """ EDIT = 0 NEW = 1 DELETE = 2 def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""): self.filePath = filePath self.diff = diff self.fileMode = fileMode def get_diff(changeset): """This is a copy of the code form the ChangeSet.get_description method found in Rope.""" new = changeset.new_contents old = changeset.old_contents if old is None: if changeset.resource.exists(): old = changeset.resource.read() else: old = '' # Ensure code has a trailing empty lines, before generating a diff. # https://github.com/Microsoft/vscode-python/issues/695. old_lines = old.splitlines(True) if not old_lines[-1].endswith('\n'): old_lines[-1] = old_lines[-1] + os.linesep new = new + os.linesep result = difflib.unified_diff( old_lines, new.splitlines(True), 'a/' + changeset.resource.path, 'b/' + changeset.resource.path) return ''.join(list(result)) class BaseRefactoring(object): """ Base class for refactorings """ def __init__(self, project, resource, name="Refactor", progressCallback=None): self._progressCallback = progressCallback self._handle = rope.base.taskhandle.TaskHandle(name) self._handle.add_observer(self._update_progress) self.project = project self.resource = resource self.changes = [] def _update_progress(self): jobset = self._handle.current_jobset() if jobset and not self._progressCallback is None: progress = RefactorProgress() # getting current job set name if jobset.get_name() is not None: progress.name = jobset.get_name() # getting active job name if jobset.get_active_job_name() is not None: progress.message = jobset.get_active_job_name() # adding done percent percent = jobset.get_percent_done() if percent is not None: progress.percent = percent if not self._progressCallback is None: self._progressCallback(progress) def stop(self): self._handle.stop() def refactor(self): try: self.onRefactor() except rope.base.exceptions.InterruptedTaskError: # we can ignore this exception, as user has cancelled refactoring pass def onRefactor(self): """ To be implemented by each base class """ pass class RenameRefactor(BaseRefactoring): def __init__(self, project, resource, name="Rename", progressCallback=None, startOffset=None, newName="new_Name"): BaseRefactoring.__init__(self, project, resource, name, progressCallback) self._newName = newName self.startOffset = startOffset def onRefactor(self): renamed = Rename(self.project, self.resource, self.startOffset) changes = renamed.get_changes(self._newName, task_handle=self._handle) for item in changes.changes: if isinstance(item, rope.base.change.ChangeContents): self.changes.append( Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) else: raise Exception('Unknown Change') class ExtractVariableRefactor(BaseRefactoring): def __init__(self, project, resource, name="Extract Variable", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False): BaseRefactoring.__init__(self, project, resource, name, progressCallback) self._newName = newName self._startOffset = startOffset self._endOffset = endOffset self._similar = similar self._global = global_ def onRefactor(self): renamed = ExtractVariable( self.project, self.resource, self._startOffset, self._endOffset) changes = renamed.get_changes( self._newName, self._similar, self._global) for item in changes.changes: if isinstance(item, rope.base.change.ChangeContents): self.changes.append( Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) else: raise Exception('Unknown Change') class ExtractMethodRefactor(ExtractVariableRefactor): def __init__(self, project, resource, name="Extract Method", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False): ExtractVariableRefactor.__init__(self, project, resource, name, progressCallback, startOffset=startOffset, endOffset=endOffset, newName=newName, similar=similar, global_=global_) def onRefactor(self): renamed = ExtractMethod( self.project, self.resource, self._startOffset, self._endOffset) changes = renamed.get_changes( self._newName, self._similar, self._global) for item in changes.changes: if isinstance(item, rope.base.change.ChangeContents): self.changes.append( Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) else: raise Exception('Unknown Change') class RopeRefactoring(object): def __init__(self): self.default_sys_path = sys.path self._input = io.open(sys.stdin.fileno(), encoding='utf-8') def _rename(self, filePath, start, newName, indent_size): """ Renames a variable """ project = rope.base.project.Project( WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size) resourceToRefactor = libutils.path_to_resource(project, filePath) refactor = RenameRefactor( project, resourceToRefactor, startOffset=start, newName=newName) refactor.refactor() changes = refactor.changes project.close() valueToReturn = [] for change in changes: valueToReturn.append({'diff': change.diff}) return valueToReturn def _extractVariable(self, filePath, start, end, newName, indent_size): """ Extracts a variable """ project = rope.base.project.Project( WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size) resourceToRefactor = libutils.path_to_resource(project, filePath) refactor = ExtractVariableRefactor( project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName, similar=True) refactor.refactor() changes = refactor.changes project.close() valueToReturn = [] for change in changes: valueToReturn.append({'diff': change.diff}) return valueToReturn def _extractMethod(self, filePath, start, end, newName, indent_size): """ Extracts a method """ project = rope.base.project.Project( WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size) resourceToRefactor = libutils.path_to_resource(project, filePath) refactor = ExtractMethodRefactor( project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName, similar=True) refactor.refactor() changes = refactor.changes project.close() valueToReturn = [] for change in changes: valueToReturn.append({'diff': change.diff}) return valueToReturn def _serialize(self, identifier, results): """ Serializes the refactor results """ return json.dumps({'id': identifier, 'results': results}) def _deserialize(self, request): """Deserialize request from VSCode. Args: request: String with raw request from VSCode. Returns: Python dictionary with request data. """ return json.loads(request) def _process_request(self, request): """Accept serialized request from VSCode and write response. """ request = self._deserialize(request) lookup = request.get('lookup', '') if lookup == '': pass elif lookup == 'rename': changes = self._rename(request['file'], int( request['start']), request['name'], int(request['indent_size'])) return self._write_response(self._serialize(request['id'], changes)) elif lookup == 'extract_variable': changes = self._extractVariable(request['file'], int( request['start']), int(request['end']), request['name'], int(request['indent_size'])) return self._write_response(self._serialize(request['id'], changes)) elif lookup == 'extract_method': changes = self._extractMethod(request['file'], int( request['start']), int(request['end']), request['name'], int(request['indent_size'])) return self._write_response(self._serialize(request['id'], changes)) def _write_response(self, response): sys.stdout.write(response + '\n') sys.stdout.flush() def watch(self): self._write_response("STARTED") while True: try: self._process_request(self._input.readline()) except: exc_type, exc_value, exc_tb = sys.exc_info() tb_info = traceback.extract_tb(exc_tb) jsonMessage = {'error': True, 'message': str(exc_value), 'traceback': str(tb_info), 'type': str(exc_type)} sys.stderr.write(json.dumps(jsonMessage)) sys.stderr.flush() if __name__ == '__main__': RopeRefactoring().watch()