|
| 1 | +# Arguments are: |
| 2 | +# 1. Working directory. |
| 3 | +# 2. Rope folder |
| 4 | + |
| 5 | +import io |
| 6 | +import sys |
| 7 | +import json |
| 8 | +import traceback |
| 9 | +import rope |
| 10 | + |
| 11 | +from rope.base import libutils |
| 12 | +from rope.refactor.rename import Rename |
| 13 | +from rope.refactor.extract import ExtractMethod, ExtractVariable |
| 14 | +import rope.base.project |
| 15 | +import rope.base.taskhandle |
| 16 | + |
| 17 | +WORKSPACE_ROOT = sys.argv[1] |
| 18 | +ROPE_PROJECT_FOLDER = sys.argv[2] |
| 19 | + |
| 20 | + |
| 21 | +class RefactorProgress(): |
| 22 | + """ |
| 23 | + Refactor progress information |
| 24 | + """ |
| 25 | + |
| 26 | + def __init__(self, name='Task Name', message=None, percent=0): |
| 27 | + self.name = name |
| 28 | + self.message = message |
| 29 | + self.percent = percent |
| 30 | + |
| 31 | + |
| 32 | +class ChangeType(): |
| 33 | + """ |
| 34 | + Change Type Enum |
| 35 | + """ |
| 36 | + EDIT = 0 |
| 37 | + NEW = 1 |
| 38 | + DELETE = 2 |
| 39 | + |
| 40 | + |
| 41 | +class Change(): |
| 42 | + """ |
| 43 | + """ |
| 44 | + EDIT = 0 |
| 45 | + NEW = 1 |
| 46 | + DELETE = 2 |
| 47 | + |
| 48 | + def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""): |
| 49 | + self.filePath = filePath |
| 50 | + self.diff = diff |
| 51 | + self.fileMode = fileMode |
| 52 | + |
| 53 | + |
| 54 | +class BaseRefactoring(object): |
| 55 | + """ |
| 56 | + Base class for refactorings |
| 57 | + """ |
| 58 | + |
| 59 | + def __init__(self, project, resource, name="Refactor", progressCallback=None): |
| 60 | + self._progressCallback = progressCallback |
| 61 | + self._handle = rope.base.taskhandle.TaskHandle(name) |
| 62 | + self._handle.add_observer(self._update_progress) |
| 63 | + self.project = project |
| 64 | + self.resource = resource |
| 65 | + self.changes = [] |
| 66 | + |
| 67 | + def _update_progress(self): |
| 68 | + jobset = self._handle.current_jobset() |
| 69 | + if jobset and not self._progressCallback is None: |
| 70 | + progress = RefactorProgress() |
| 71 | + # getting current job set name |
| 72 | + if jobset.get_name() is not None: |
| 73 | + progress.name = jobset.get_name() |
| 74 | + # getting active job name |
| 75 | + if jobset.get_active_job_name() is not None: |
| 76 | + progress.message = jobset.get_active_job_name() |
| 77 | + # adding done percent |
| 78 | + percent = jobset.get_percent_done() |
| 79 | + if percent is not None: |
| 80 | + progress.percent = percent |
| 81 | + if not self._progressCallback is None: |
| 82 | + self._progressCallback(progress) |
| 83 | + |
| 84 | + def stop(self): |
| 85 | + self._handle.stop() |
| 86 | + |
| 87 | + def refactor(self): |
| 88 | + try: |
| 89 | + self.onRefactor() |
| 90 | + except rope.base.exceptions.InterruptedTaskError: |
| 91 | + # we can ignore this exception, as user has cancelled refactoring |
| 92 | + pass |
| 93 | + |
| 94 | + def onRefactor(self): |
| 95 | + """ |
| 96 | + To be implemented by each base class |
| 97 | + """ |
| 98 | + pass |
| 99 | + |
| 100 | + |
| 101 | +class RenameRefactor(BaseRefactoring): |
| 102 | + |
| 103 | + def __init__(self, project, resource, name="Rename", progressCallback=None, startOffset=None, newName="new_Name"): |
| 104 | + BaseRefactoring.__init__(self, project, resource, |
| 105 | + name, progressCallback) |
| 106 | + self._newName = newName |
| 107 | + self.startOffset = startOffset |
| 108 | + |
| 109 | + def onRefactor(self): |
| 110 | + renamed = Rename(self.project, self.resource, self.startOffset) |
| 111 | + changes = renamed.get_changes(self._newName, task_handle=self._handle) |
| 112 | + for item in changes.changes: |
| 113 | + if isinstance(item, rope.base.change.ChangeContents): |
| 114 | + self.changes.append( |
| 115 | + Change(item.resource.real_path, ChangeType.EDIT, item.get_description())) |
| 116 | + else: |
| 117 | + raise Exception('Unknown Change') |
| 118 | + |
| 119 | + |
| 120 | +class ExtractVariableRefactor(BaseRefactoring): |
| 121 | + |
| 122 | + def __init__(self, project, resource, name="Extract Variable", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False): |
| 123 | + BaseRefactoring.__init__(self, project, resource, |
| 124 | + name, progressCallback) |
| 125 | + self._newName = newName |
| 126 | + self._startOffset = startOffset |
| 127 | + self._endOffset = endOffset |
| 128 | + self._similar = similar |
| 129 | + self._global = global_ |
| 130 | + |
| 131 | + def onRefactor(self): |
| 132 | + renamed = ExtractVariable( |
| 133 | + self.project, self.resource, self._startOffset, self._endOffset) |
| 134 | + changes = renamed.get_changes( |
| 135 | + self._newName, self._similar, self._global) |
| 136 | + for item in changes.changes: |
| 137 | + if isinstance(item, rope.base.change.ChangeContents): |
| 138 | + self.changes.append( |
| 139 | + Change(item.resource.real_path, ChangeType.EDIT, item.get_description())) |
| 140 | + else: |
| 141 | + raise Exception('Unknown Change') |
| 142 | + |
| 143 | + |
| 144 | +class ExtractMethodRefactor(ExtractVariableRefactor): |
| 145 | + |
| 146 | + def __init__(self, project, resource, name="Extract Method", progressCallback=None, startOffset=None, endOffset=None, newName="new_Name", similar=False, global_=False): |
| 147 | + ExtractVariableRefactor.__init__(self, project, resource, |
| 148 | + name, progressCallback, startOffset=startOffset, endOffset=endOffset, newName=newName, similar=similar, global_=global_) |
| 149 | + def onRefactor(self): |
| 150 | + renamed = ExtractMethod( |
| 151 | + self.project, self.resource, self._startOffset, self._endOffset) |
| 152 | + changes = renamed.get_changes( |
| 153 | + self._newName, self._similar, self._global) |
| 154 | + for item in changes.changes: |
| 155 | + if isinstance(item, rope.base.change.ChangeContents): |
| 156 | + self.changes.append( |
| 157 | + Change(item.resource.real_path, ChangeType.EDIT, item.get_description())) |
| 158 | + else: |
| 159 | + raise Exception('Unknown Change') |
| 160 | + |
| 161 | + |
| 162 | +class RopeRefactoring(object): |
| 163 | + |
| 164 | + def __init__(self): |
| 165 | + self.default_sys_path = sys.path |
| 166 | + self._input = io.open(sys.stdin.fileno(), encoding='utf-8') |
| 167 | + |
| 168 | + def _extractVariable(self, filePath, start, end, newName): |
| 169 | + """ |
| 170 | + Extracts a variale |
| 171 | + """ |
| 172 | + project = rope.base.project.Project(WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False) |
| 173 | + resourceToRefactor = libutils.path_to_resource(project, filePath) |
| 174 | + refactor = ExtractVariableRefactor(project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName) |
| 175 | + refactor.refactor() |
| 176 | + changes = refactor.changes |
| 177 | + project.close() |
| 178 | + valueToReturn = [] |
| 179 | + for change in changes: |
| 180 | + valueToReturn.append({'diff':change.diff}) |
| 181 | + return valueToReturn |
| 182 | + |
| 183 | + def _extractMethod(self, filePath, start, end, newName): |
| 184 | + """ |
| 185 | + Extracts a method |
| 186 | + """ |
| 187 | + project = rope.base.project.Project(WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False) |
| 188 | + resourceToRefactor = libutils.path_to_resource(project, filePath) |
| 189 | + refactor = ExtractMethodRefactor(project, resourceToRefactor, startOffset=start, endOffset=end, newName=newName) |
| 190 | + refactor.refactor() |
| 191 | + changes = refactor.changes |
| 192 | + project.close() |
| 193 | + valueToReturn = [] |
| 194 | + for change in changes: |
| 195 | + valueToReturn.append({'diff':change.diff}) |
| 196 | + return valueToReturn |
| 197 | + |
| 198 | + def _serialize(self, identifier, results): |
| 199 | + """ |
| 200 | + Serializes the refactor results |
| 201 | + """ |
| 202 | + return json.dumps({'id': identifier, 'results': results}) |
| 203 | + |
| 204 | + def _deserialize(self, request): |
| 205 | + """Deserialize request from VSCode. |
| 206 | +
|
| 207 | + Args: |
| 208 | + request: String with raw request from VSCode. |
| 209 | +
|
| 210 | + Returns: |
| 211 | + Python dictionary with request data. |
| 212 | + """ |
| 213 | + return json.loads(request) |
| 214 | + |
| 215 | + def _process_request(self, request): |
| 216 | + """Accept serialized request from VSCode and write response. |
| 217 | + """ |
| 218 | + request = self._deserialize(request) |
| 219 | + lookup = request.get('lookup', '') |
| 220 | + |
| 221 | + if lookup == '': |
| 222 | + pass |
| 223 | + elif lookup == 'extract_variable': |
| 224 | + changes = self._extractVariable(request['file'], int(request['start']), int(request['end']), request['name']) |
| 225 | + return self._write_response(self._serialize(request['id'], changes)) |
| 226 | + elif lookup == 'extract_method': |
| 227 | + changes = self._extractMethod(request['file'], int(request['start']), int(request['end']), request['name']) |
| 228 | + return self._write_response(self._serialize(request['id'], changes)) |
| 229 | + |
| 230 | + def _write_response(self, response): |
| 231 | + sys.stdout.write(response + '\n') |
| 232 | + sys.stdout.flush() |
| 233 | + |
| 234 | + def watch(self): |
| 235 | + self._write_response("STARTED") |
| 236 | + while True: |
| 237 | + try: |
| 238 | + self._process_request(self._input.readline()) |
| 239 | + except Exception as ex: |
| 240 | + message = ex.message + ' \n' + traceback.format_exc() |
| 241 | + sys.stderr.write(str(len(message)) + ':' + message) |
| 242 | + sys.stderr.flush() |
| 243 | + |
| 244 | +if __name__ == '__main__': |
| 245 | + RopeRefactoring().watch() |
0 commit comments