refactor.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import re
  2. import rope.base.change
  3. import rope.contrib.generate
  4. import rope.refactor.change_signature
  5. import rope.refactor.extract
  6. import rope.refactor.inline
  7. import rope.refactor.introduce_factory
  8. import rope.refactor.method_object
  9. import rope.refactor.move
  10. import rope.refactor.rename
  11. import rope.refactor.restructure
  12. import rope.refactor.usefunction
  13. from rope.base import taskhandle
  14. from ropemode import dialog, filter as file_filter
  15. class Refactoring(object):
  16. key = None
  17. confs = {}
  18. optionals = {}
  19. saveall = True
  20. def __init__(self, interface, env):
  21. self.interface = interface
  22. self.env = env
  23. def show(self, initial_asking=True):
  24. self.interface._check_project()
  25. self.interface._save_buffers(only_current=not self.saveall)
  26. self._create_refactoring()
  27. action, result = dialog.show_dialog(
  28. self.interface._askdata, ['perform', 'preview', 'cancel'],
  29. self._get_confs(), self._get_optionals(),
  30. initial_asking=initial_asking)
  31. if action == 'cancel':
  32. self.env.message('Cancelled!')
  33. return
  34. def calculate(handle):
  35. return self._calculate_changes(result, handle)
  36. name = 'Calculating %s changes' % self.name
  37. changes = runtask(self.env, calculate, name=name)
  38. if action == 'perform':
  39. self._perform(changes)
  40. if action == 'preview':
  41. if changes is not None:
  42. diffs = changes.get_description()
  43. if self.env.preview_changes(diffs):
  44. self._perform(changes)
  45. else:
  46. self.env.message('Thrown away!')
  47. else:
  48. self.env.message('No changes!')
  49. @property
  50. def project(self):
  51. return self.interface.project
  52. @property
  53. def resource(self):
  54. return self.interface._get_resource()
  55. @property
  56. def offset(self):
  57. return self.env.get_offset()
  58. @property
  59. def region(self):
  60. return self.env.get_region()
  61. @property
  62. def name(self):
  63. return refactoring_name(self.__class__)
  64. def _calculate_changes(self, option_values, task_handle):
  65. pass
  66. def _create_refactoring(self):
  67. pass
  68. def _done(self):
  69. pass
  70. def _perform(self, changes):
  71. if changes is None:
  72. self.env.message('No changes!')
  73. return
  74. def perform(handle, self=self, changes=changes):
  75. self.project.do(changes, task_handle=handle)
  76. self.interface._reload_buffers(changes)
  77. self._done()
  78. runtask(self.env, perform, 'Making %s changes' % self.name,
  79. interrupts=False)
  80. self.env.message(str(changes.description) + ' finished')
  81. def _get_confs(self):
  82. return self.confs
  83. def _get_optionals(self):
  84. return self.optionals
  85. @property
  86. def resources_option(self):
  87. return dialog.Data('Files to apply this refactoring on: ',
  88. decode=self._decode_resources)
  89. def _decode_resources(self, value):
  90. return _resources(self.project, value)
  91. class Rename(Refactoring):
  92. key = 'r'
  93. saveall = True
  94. def _create_refactoring(self):
  95. self.renamer = rope.refactor.rename.Rename(
  96. self.project, self.resource, self.offset)
  97. def _calculate_changes(self, values, task_handle):
  98. return self.renamer.get_changes(task_handle=task_handle, **values)
  99. def _get_optionals(self):
  100. opts = {}
  101. opts['docs'] = dialog.Boolean('Search comments and docs: ', True)
  102. if self.renamer.is_method():
  103. opts['in_hierarchy'] = dialog.Boolean('Rename methods in '
  104. 'class hierarchy: ')
  105. opts['resources'] = self.resources_option
  106. opts['unsure'] = dialog.Data('Unsure occurrences: ',
  107. decode=self._decode_unsure,
  108. values=['ignore', 'match'],
  109. default='ignore')
  110. return opts
  111. def _get_confs(self):
  112. oldname = str(self.renamer.get_old_name())
  113. return {'new_name': dialog.Data('New name: ', default=oldname)}
  114. def _decode_unsure(self, value):
  115. unsure = value == 'match'
  116. return lambda occurrence: unsure
  117. class RenameCurrentModule(Rename):
  118. key = '1 r'
  119. offset = None
  120. class Restructure(Refactoring):
  121. key = 'x'
  122. confs = {'pattern': dialog.Data('Restructuring pattern: '),
  123. 'goal': dialog.Data('Restructuring goal: ')}
  124. def _calculate_changes(self, values, task_handle):
  125. restructuring = rope.refactor.restructure.Restructure(
  126. self.project, values['pattern'], values['goal'],
  127. args=values['args'], imports=values['imports'])
  128. return restructuring.get_changes(resources=values['resources'],
  129. task_handle=task_handle)
  130. def _get_optionals(self):
  131. return {
  132. 'args': dialog.Data('Arguments: ', decode=self._decode_args),
  133. 'imports': dialog.Data('Imports: ', decode=self._decode_imports),
  134. 'resources': self.resources_option}
  135. def _decode_args(self, value):
  136. if value:
  137. args = {}
  138. for raw_check in value.split('\n'):
  139. if raw_check:
  140. key, value = raw_check.split(':', 1)
  141. args[key.strip()] = value.strip()
  142. return args
  143. def _decode_imports(self, value):
  144. if value:
  145. return [line.strip() for line in value.split('\n')]
  146. class UseFunction(Refactoring):
  147. key = 'u'
  148. def _create_refactoring(self):
  149. self.user = rope.refactor.usefunction.UseFunction(
  150. self.project, self.resource, self.offset)
  151. def _calculate_changes(self, values, task_handle):
  152. return self.user.get_changes(task_handle=task_handle, **values)
  153. def _get_optionals(self):
  154. return {'resources': self.resources_option}
  155. class Move(Refactoring):
  156. key = 'v'
  157. def _create_refactoring(self):
  158. self.mover = rope.refactor.move.create_move(self.project,
  159. self.resource,
  160. self.offset)
  161. def _calculate_changes(self, values, task_handle):
  162. destination = values['destination']
  163. resources = values.get('resources', None)
  164. if isinstance(self.mover, rope.refactor.move.MoveGlobal):
  165. return self._move_global(destination, resources, task_handle)
  166. if isinstance(self.mover, rope.refactor.move.MoveModule):
  167. return self._move_module(destination, resources, task_handle)
  168. if isinstance(self.mover, rope.refactor.move.MoveMethod):
  169. return self._move_method(destination, resources, task_handle)
  170. def _move_global(self, dest, resources, handle):
  171. destination = self.project.pycore.find_module(dest)
  172. return self.mover.get_changes(
  173. destination, resources=resources, task_handle=handle)
  174. def _move_method(self, dest, resources, handle):
  175. return self.mover.get_changes(
  176. dest, self.mover.get_method_name(),
  177. resources=resources, task_handle=handle)
  178. def _move_module(self, dest, resources, handle):
  179. destination = self.project.pycore.find_module(dest)
  180. return self.mover.get_changes(
  181. destination, resources=resources, task_handle=handle)
  182. def _get_confs(self):
  183. if isinstance(self.mover, rope.refactor.move.MoveGlobal):
  184. prompt = 'Destination module: '
  185. if isinstance(self.mover, rope.refactor.move.MoveModule):
  186. prompt = 'Destination package: '
  187. if isinstance(self.mover, rope.refactor.move.MoveMethod):
  188. prompt = 'Destination attribute: '
  189. return {'destination': dialog.Data(prompt)}
  190. def _get_optionals(self):
  191. return {'resources': self.resources_option}
  192. class MoveCurrentModule(Move):
  193. key = '1 v'
  194. offset = None
  195. class ModuleToPackage(Refactoring):
  196. key = '1 p'
  197. saveall = False
  198. def _create_refactoring(self):
  199. self.packager = rope.refactor.ModuleToPackage(
  200. self.project, self.resource)
  201. def _calculate_changes(self, values, task_handle):
  202. return self.packager.get_changes()
  203. class Inline(Refactoring):
  204. key = 'i'
  205. def _create_refactoring(self):
  206. self.inliner = rope.refactor.inline.create_inline(
  207. self.project, self.resource, self.offset)
  208. def _calculate_changes(self, values, task_handle):
  209. return self.inliner.get_changes(task_handle=task_handle, **values)
  210. def _get_optionals(self):
  211. opts = {'resources': self.resources_option}
  212. if self.inliner.get_kind() == 'parameter':
  213. opts['in_hierarchy'] = dialog.Boolean(
  214. 'Apply on all matching methods in class hierarchy: ', False)
  215. else:
  216. opts['remove'] = dialog.Boolean('Remove the definition: ', True)
  217. opts['only_current'] = dialog.Boolean('Inline this '
  218. 'occurrence only: ')
  219. return opts
  220. class _Extract(Refactoring):
  221. saveall = False
  222. optionals = {'similar': dialog.Boolean('Extract similar pieces: ', True),
  223. 'global_': dialog.Boolean('Make global: ')}
  224. kind = None
  225. constructor = rope.refactor.extract.ExtractVariable
  226. def __init__(self, *args):
  227. super(_Extract, self).__init__(*args)
  228. self.extractor = None
  229. def _create_refactoring(self):
  230. start, end = self.region
  231. self.extractor = self.constructor(self.project,
  232. self.resource, start, end)
  233. def _calculate_changes(self, values, task_handle):
  234. similar = values.get('similar')
  235. global_ = values.get('global_')
  236. return self.extractor.get_changes(values['name'], similar=similar,
  237. global_=global_)
  238. def _get_confs(self):
  239. return {'name': dialog.Data('Extracted %s name: ' % self.kind)}
  240. class ExtractVariable(_Extract):
  241. key = 'l'
  242. kind = 'variable'
  243. constructor = rope.refactor.extract.ExtractVariable
  244. class ExtractMethod(_Extract):
  245. key = 'm'
  246. kind = 'method'
  247. constructor = rope.refactor.extract.ExtractMethod
  248. class OrganizeImports(Refactoring):
  249. key = 'o'
  250. saveall = False
  251. def _create_refactoring(self):
  252. self.organizer = rope.refactor.ImportOrganizer(self.project)
  253. def _calculate_changes(self, values, task_handle):
  254. return self.organizer.organize_imports(self.resource)
  255. class MethodObject(Refactoring):
  256. saveall = False
  257. confs = {'classname': dialog.Data('New class name: ',
  258. default='_ExtractedClass')}
  259. def _create_refactoring(self):
  260. self.objecter = rope.refactor.method_object.MethodObject(
  261. self.project, self.resource, self.offset)
  262. def _calculate_changes(self, values, task_handle):
  263. classname = values.get('classname')
  264. return self.objecter.get_changes(classname)
  265. class IntroduceFactory(Refactoring):
  266. saveall = True
  267. key = 'f'
  268. def _create_refactoring(self):
  269. self.factory = rope.refactor.introduce_factory.IntroduceFactory(
  270. self.project, self.resource, self.offset)
  271. def _calculate_changes(self, values, task_handle):
  272. return self.factory.get_changes(task_handle=task_handle, **values)
  273. def _get_confs(self):
  274. default = 'create_%s' % self.factory.old_name.lower()
  275. return {'factory_name': dialog.Data('Factory name: ', default)}
  276. def _get_optionals(self):
  277. return {'global_factory': dialog.Boolean('Make global: ', True),
  278. 'resources': self.resources_option}
  279. class ChangeSignature(Refactoring):
  280. saveall = True
  281. key = 's'
  282. def _create_refactoring(self):
  283. self.changer = rope.refactor.change_signature.ChangeSignature(
  284. self.project, self.resource, self.offset)
  285. def _calculate_changes(self, values, task_handle):
  286. signature = values.get('signature')
  287. args = re.sub(r'[\s\(\)]+', '', signature).split(',')
  288. olds = [arg[0] for arg in self._get_args()]
  289. changers = []
  290. for arg in list(olds):
  291. if arg in args:
  292. continue
  293. changers.append(rope.refactor.change_signature.
  294. ArgumentRemover(olds.index(arg)))
  295. olds.remove(arg)
  296. order = []
  297. for index, arg in enumerate(args):
  298. if arg not in olds:
  299. changers.append(rope.refactor.change_signature.
  300. ArgumentAdder(index, arg))
  301. olds.insert(index, arg)
  302. order.append(olds.index(arg))
  303. changers.append(rope.refactor.change_signature.
  304. ArgumentReorderer(order, autodef='None'))
  305. del values['signature']
  306. return self.changer.get_changes(changers, task_handle=task_handle,
  307. **values)
  308. def _get_args(self):
  309. if hasattr(self.changer, 'get_args'):
  310. return self.changer.get_args()
  311. return self.changer.get_definition_info().args_with_defaults
  312. def _get_confs(self):
  313. args = []
  314. for arg, default in self._get_args():
  315. args.append(arg)
  316. signature = '(' + ', '.join(args) + ')'
  317. return {'signature': dialog.Data('Change the signature: ',
  318. default=signature)}
  319. def _get_optionals(self):
  320. opts = {'resources': self.resources_option}
  321. if self.changer.is_method():
  322. opts['in_hierarchy'] = dialog.Boolean('Rename methods in '
  323. 'class hierarchy: ')
  324. return opts
  325. class _GenerateElement(Refactoring):
  326. def _create_refactoring(self):
  327. kind = self.name.split('_')[-1]
  328. self.generator = rope.contrib.generate.create_generate(
  329. kind, self.project, self.resource, self.offset)
  330. def _calculate_changes(self, values, task_handle):
  331. return self.generator.get_changes()
  332. def _done(self):
  333. resource, lineno = self.generator.get_location()
  334. self.interface._goto_location(resource, lineno)
  335. class GenerateVariable(_GenerateElement):
  336. key = 'n v'
  337. class GenerateFunction(_GenerateElement):
  338. key = 'n f'
  339. class GenerateClass(_GenerateElement):
  340. key = 'n c'
  341. class GenerateModule(_GenerateElement):
  342. key = 'n m'
  343. class GeneratePackage(_GenerateElement):
  344. key = 'n p'
  345. def refactoring_name(refactoring):
  346. classname = refactoring.__name__
  347. result = []
  348. for c in classname:
  349. if result and c.isupper():
  350. result.append('_')
  351. result.append(c.lower())
  352. name = ''.join(result)
  353. return name
  354. def _resources(project, text):
  355. if text is None or text.strip() == '':
  356. return None
  357. return file_filter.resources(project, text)
  358. def runtask(env, command, name, interrupts=True):
  359. return RunTask(env, command, name, interrupts)()
  360. class RunTask(object):
  361. def __init__(self, env, task, name, interrupts=True):
  362. self.env = env
  363. self.task = task
  364. self.name = name
  365. self.interrupts = interrupts
  366. def __call__(self):
  367. handle = taskhandle.TaskHandle(name=self.name)
  368. progress = self.env.create_progress(self.name)
  369. def update_progress():
  370. jobset = handle.current_jobset()
  371. if jobset:
  372. percent = jobset.get_percent_done()
  373. if percent is not None:
  374. progress.update(percent)
  375. handle.add_observer(update_progress)
  376. result = self.task(handle)
  377. progress.done()
  378. return result