minimal adjustments
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-python / pythonFiles / refactor.py
1 # Arguments are:
2 # 1. Working directory.
3 # 2. Rope folder
4
5 import difflib
6 import io
7 import json
8 import os
9 import sys
10 import traceback
11
12 try:
13     import rope
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
19 except:
20     jsonMessage = {'error': True, 'message': 'Rope not installed', 'traceback': '', 'type': 'ModuleNotFoundError'}
21     sys.stderr.write(json.dumps(jsonMessage))
22     sys.stderr.flush()
23
24 WORKSPACE_ROOT = sys.argv[1]
25 ROPE_PROJECT_FOLDER = '.vim/.ropeproject'
26
27
28 class RefactorProgress():
29     """
30     Refactor progress information
31     """
32
33     def __init__(self, name='Task Name', message=None, percent=0):
34         self.name = name
35         self.message = message
36         self.percent = percent
37
38
39 class ChangeType():
40     """
41     Change Type Enum
42     """
43     EDIT = 0
44     NEW = 1
45     DELETE = 2
46
47
48 class Change():
49     """
50     """
51     EDIT = 0
52     NEW = 1
53     DELETE = 2
54
55     def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""):
56         self.filePath = filePath
57         self.diff = diff
58         self.fileMode = fileMode
59
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
64     if old is None:
65         if changeset.resource.exists():
66             old = changeset.resource.read()
67         else:
68             old = ''
69
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
76     
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))
81
82 class BaseRefactoring(object):
83     """
84     Base class for refactorings
85     """
86
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
93         self.changes = []
94
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)
111
112     def stop(self):
113         self._handle.stop()
114
115     def refactor(self):
116         try:
117             self.onRefactor()
118         except rope.base.exceptions.InterruptedTaskError:
119             # we can ignore this exception, as user has cancelled refactoring
120             pass
121
122     def onRefactor(self):
123         """
124         To be implemented by each base class
125         """
126         pass
127
128
129 class RenameRefactor(BaseRefactoring):
130
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
136
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):
142                 self.changes.append(
143                     Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
144             else:
145                 raise Exception('Unknown Change')
146
147
148 class ExtractVariableRefactor(BaseRefactoring):
149
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_
158
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):
166                 self.changes.append(
167                     Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
168             else:
169                 raise Exception('Unknown Change')
170
171
172 class ExtractMethodRefactor(ExtractVariableRefactor):
173
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_)
177
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):
185                 self.changes.append(
186                     Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
187             else:
188                 raise Exception('Unknown Change')
189
190
191 class RopeRefactoring(object):
192
193     def __init__(self):
194         self.default_sys_path = sys.path
195         self._input = io.open(sys.stdin.fileno(), encoding='utf-8')
196
197     def _rename(self, filePath, start, newName, indent_size):
198         """
199         Renames a variable
200         """
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)
206         refactor.refactor()
207         changes = refactor.changes
208         project.close()
209         valueToReturn = []
210         for change in changes:
211             valueToReturn.append({'diff': change.diff})
212         return valueToReturn
213
214     def _extractVariable(self, filePath, start, end, newName, indent_size):
215         """
216         Extracts a variable
217         """
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)
223         refactor.refactor()
224         changes = refactor.changes
225         project.close()
226         valueToReturn = []
227         for change in changes:
228             valueToReturn.append({'diff': change.diff})
229         return valueToReturn
230
231     def _extractMethod(self, filePath, start, end, newName, indent_size):
232         """
233         Extracts a method
234         """
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)
240         refactor.refactor()
241         changes = refactor.changes
242         project.close()
243         valueToReturn = []
244         for change in changes:
245             valueToReturn.append({'diff': change.diff})
246         return valueToReturn
247
248     def _serialize(self, identifier, results):
249         """
250         Serializes the refactor results
251         """
252         return json.dumps({'id': identifier, 'results': results})
253
254     def _deserialize(self, request):
255         """Deserialize request from VSCode.
256
257         Args:
258             request: String with raw request from VSCode.
259
260         Returns:
261             Python dictionary with request data.
262         """
263         return json.loads(request)
264
265     def _process_request(self, request):
266         """Accept serialized request from VSCode and write response.
267         """
268         request = self._deserialize(request)
269         lookup = request.get('lookup', '')
270
271         if lookup == '':
272             pass
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))
285
286     def _write_response(self, response):
287         sys.stdout.write(response + '\n')
288         sys.stdout.flush()
289
290     def watch(self):
291         self._write_response("STARTED")
292         while True:
293             try:
294                 self._process_request(self._input.readline())
295             except:
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))
300                 sys.stderr.flush()
301
302 if __name__ == '__main__':
303     RopeRefactoring().watch()