PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/pymode/rope.py

https://gitlab.com/vim-IDE/python-mode
Python | 915 lines | 883 code | 16 blank | 16 comment | 9 complexity | c03483673c78de498e26012973f9162c MD5 | raw file
  1. """ Rope support in pymode. """
  2. from __future__ import absolute_import, print_function
  3. import os.path
  4. import re
  5. import site
  6. import sys
  7. from rope.base import project, libutils, exceptions, change, worder # noqa
  8. from rope.base.fscommands import FileSystemCommands # noqa
  9. from rope.base.taskhandle import TaskHandle # noqa
  10. from rope.contrib import autoimport as rope_autoimport, codeassist, findit, generate # noqa
  11. from rope.refactor import ModuleToPackage, ImportOrganizer, rename, extract, inline, usefunction, move, change_signature, importutils # noqa
  12. from .environment import env
  13. def look_ropeproject(path):
  14. """ Search for ropeproject in current and parent dirs.
  15. :return str|None: A finded path
  16. """
  17. env.debug('Look project', path)
  18. p = os.path.abspath(path)
  19. while True:
  20. if '.ropeproject' in os.listdir(p):
  21. return p
  22. new_p = os.path.abspath(os.path.join(p, ".."))
  23. if new_p == p:
  24. return path
  25. p = new_p
  26. @env.catch_exceptions
  27. def completions():
  28. """ Search completions.
  29. :return None:
  30. """
  31. row, col = env.cursor
  32. if env.var('a:findstart', True):
  33. count = 0
  34. for char in reversed(env.current.line[:col]):
  35. if not re.match(r'[\w\d]', char):
  36. break
  37. count += 1
  38. env.debug('Complete find start', (col - count))
  39. return env.stop(col - count)
  40. base = env.var('a:base')
  41. source, offset = env.get_offset_params((row, col), base)
  42. proposals = get_proporsals(source, offset, base)
  43. return env.stop(proposals)
  44. FROM_RE = re.compile(r'^\s*from\s+[\.\w\d_]+$')
  45. @env.catch_exceptions
  46. def complete(dot=False):
  47. """ Ctrl+Space completion.
  48. :return bool: success
  49. """
  50. row, col = env.cursor
  51. source, offset = env.get_offset_params()
  52. cline = env.current.line[:col]
  53. env.debug('dot completion', cline)
  54. if FROM_RE.match(cline) or cline.endswith('..') or cline.endswith('\.'):
  55. return env.stop("")
  56. proposals = get_proporsals(source, offset, dot=dot)
  57. if not proposals:
  58. return False
  59. prefix = proposals[0]['word']
  60. # Find common part
  61. for p in proposals:
  62. common = len([
  63. c1 for c1, c2 in zip(prefix, p['word']) if c1 == c2 and c1 != ' '
  64. ])
  65. prefix = prefix[:common]
  66. s_offset = codeassist.starting_offset(source, offset)
  67. p_prefix = prefix[offset - s_offset:]
  68. line = env.lines[row - 1]
  69. cline = line[:col] + p_prefix + line[col:]
  70. if cline != line:
  71. env.curbuf[row - 1] = env.prepare_value(cline, dumps=False)
  72. env.current.window.cursor = (row, col + len(p_prefix))
  73. env.run('complete', col - len(prefix) + len(p_prefix) + 1, proposals)
  74. return True
  75. def get_proporsals(source, offset, base='', dot=False):
  76. """ Code assist.
  77. :return str:
  78. """
  79. with RopeContext() as ctx:
  80. try:
  81. proposals = codeassist.code_assist(
  82. ctx.project, source, offset, ctx.resource, maxfixes=3,
  83. later_locals=False)
  84. except exceptions.ModuleSyntaxError:
  85. proposals = []
  86. proposals = sorted(proposals, key=_sort_proporsals)
  87. out = []
  88. preview = 'preview' in ctx.options.get('completeopt')
  89. for p in proposals:
  90. out.append(dict(
  91. word=p.name,
  92. menu=p.type,
  93. kind=p.scope + ':',
  94. info=p.get_doc() or "No docs." if preview else "",
  95. ))
  96. out = _get_autoimport_proposals(out, ctx, source, offset, dot=dot)
  97. return out
  98. @env.catch_exceptions
  99. def goto():
  100. """ Goto definition. """
  101. with RopeContext() as ctx:
  102. source, offset = env.get_offset_params()
  103. found_resource, line = codeassist.get_definition_location(
  104. ctx.project, source, offset, ctx.resource, maxfixes=3)
  105. if not found_resource:
  106. env.error('Definition not found')
  107. return
  108. env.goto_file(
  109. found_resource.real_path,
  110. cmd=ctx.options.get('goto_definition_cmd'))
  111. env.goto_line(line)
  112. @env.catch_exceptions
  113. def show_doc():
  114. """ Show documentation. """
  115. with RopeContext() as ctx:
  116. source, offset = env.get_offset_params()
  117. try:
  118. doc = codeassist.get_doc(
  119. ctx.project, source, offset, ctx.resource, maxfixes=3)
  120. if not doc:
  121. raise exceptions.BadIdentifierError
  122. env.let('l:output', doc.split('\n'))
  123. except exceptions.BadIdentifierError:
  124. env.error("No documentation found.")
  125. def find_it():
  126. """ Find occurrences. """
  127. with RopeContext() as ctx:
  128. _, offset = env.get_offset_params()
  129. try:
  130. occurrences = findit.find_occurrences(
  131. ctx.project, ctx.resource, offset)
  132. except exceptions.BadIdentifierError:
  133. occurrences = []
  134. lst = []
  135. for oc in occurrences:
  136. lst.append(dict(
  137. filename=oc.resource.path,
  138. text=env.lines[oc.lineno - 1] if oc.resource.real_path == env.curbuf.name else "", # noqa
  139. lnum=oc.lineno,
  140. ))
  141. env.let('loclist._loclist', lst)
  142. def update_python_path(paths):
  143. """ Update sys.path and make sure the new items come first. """
  144. old_sys_path_items = list(sys.path)
  145. for path in paths:
  146. # see if it is a site dir
  147. if path.find('site-packages') != -1:
  148. site.addsitedir(path)
  149. else:
  150. sys.path.insert(0, path)
  151. # Reorder sys.path so new directories at the front.
  152. new_sys_path_items = set(sys.path) - set(old_sys_path_items)
  153. sys.path = list(new_sys_path_items) + old_sys_path_items
  154. def organize_imports():
  155. """ Organize imports in current file. """
  156. with RopeContext() as ctx:
  157. organizer = ImportOrganizer(ctx.project)
  158. changes = organizer.organize_imports(ctx.resource)
  159. if changes is not None:
  160. progress = ProgressHandler('Organize imports')
  161. ctx.project.do(changes, task_handle=progress.handle)
  162. reload_changes(changes)
  163. @env.catch_exceptions
  164. def regenerate():
  165. """ Clear cache. """
  166. with RopeContext() as ctx:
  167. ctx.project.pycore._invalidate_resource_cache(ctx.resource) # noqa
  168. ctx.importer.generate_cache()
  169. ctx.project.sync()
  170. def new():
  171. """ Create a new project. """
  172. root = None
  173. if env.var('a:0') != '0':
  174. root = env.var('a:1')
  175. else:
  176. default = env.var('g:pymode_rope_project_root')
  177. if not default:
  178. default = env.var('getcwd()')
  179. root = env.var('input("Enter project root: ", "%s")' % default)
  180. ropefolder = env.var('g:pymode_rope_ropefolder')
  181. prj = project.Project(projectroot=root, ropefolder=ropefolder)
  182. prj.close()
  183. env.message("Project is opened: %s" % root)
  184. def undo():
  185. """ Undo last changes.
  186. :return bool:
  187. """
  188. with RopeContext() as ctx:
  189. changes = ctx.project.history.tobe_undone
  190. if changes is None:
  191. env.error('Nothing to undo!')
  192. return False
  193. if env.user_confirm('Undo [%s]?' % str(changes)):
  194. progress = ProgressHandler('Undo %s' % str(changes))
  195. for c in ctx.project.history.undo(task_handle=progress.handle):
  196. reload_changes(c)
  197. def redo():
  198. """ Redo last changes.
  199. :return bool:
  200. """
  201. with RopeContext() as ctx:
  202. changes = ctx.project.history.tobe_redone
  203. if changes is None:
  204. env.error('Nothing to redo!')
  205. return False
  206. if env.user_confirm('Redo [%s]?' % str(changes)):
  207. progress = ProgressHandler('Redo %s' % str(changes))
  208. for c in ctx.project.history.redo(task_handle=progress.handle):
  209. reload_changes(c)
  210. def cache_project(cls):
  211. """ Cache projects.
  212. :return func:
  213. """
  214. projects = dict()
  215. resources = dict()
  216. def get_ctx(*args, **kwargs):
  217. path = env.curbuf.name
  218. if resources.get(path):
  219. return resources.get(path)
  220. project_path = env.var('g:pymode_rope_project_root')
  221. if not project_path:
  222. project_path = env.curdir
  223. env.debug('Look ctx', project_path)
  224. if env.var('g:pymode_rope_lookup_project', True):
  225. project_path = look_ropeproject(project_path)
  226. if not os.path.exists(project_path):
  227. env.error("Rope project root not exist: %s" % project_path)
  228. ctx = None
  229. else:
  230. ctx = projects.get(project_path)
  231. if not ctx:
  232. projects[project_path] = ctx = cls(path, project_path)
  233. resources[path] = ctx
  234. return ctx
  235. return get_ctx
  236. def autoimport():
  237. """ Autoimport modules.
  238. :return bool:
  239. """
  240. word = env.var('a:word')
  241. if not word:
  242. env.error("Should be word under cursor.")
  243. return False
  244. with RopeContext() as ctx:
  245. if not ctx.importer.names:
  246. ctx.generate_autoimport_cache()
  247. modules = ctx.importer.get_modules(word)
  248. if not modules:
  249. env.message('Global name %s not found.' % word)
  250. return False
  251. if len(modules) == 1:
  252. _insert_import(word, modules[0], ctx)
  253. else:
  254. module = env.user_input_choices(
  255. 'Which module to import:', *modules)
  256. _insert_import(word, module, ctx)
  257. return True
  258. @cache_project
  259. class RopeContext(object):
  260. """ A context manager to have a rope project context. """
  261. def __init__(self, path, project_path):
  262. """ Init Rope context. """
  263. self.path = path
  264. self.project = project.Project(project_path, fscommands=FileSystemCommands())
  265. self.importer = rope_autoimport.AutoImport(
  266. project=self.project, observe=False)
  267. update_python_path(self.project.prefs.get('python_path', []))
  268. self.resource = None
  269. self.current = None
  270. self.options = dict(
  271. completeopt=env.var('&completeopt'),
  272. autoimport=env.var('g:pymode_rope_autoimport', True),
  273. autoimport_modules=env.var('g:pymode_rope_autoimport_modules'),
  274. goto_definition_cmd=env.var('g:pymode_rope_goto_definition_cmd'),
  275. )
  276. if os.path.exists("%s/__init__.py" % project_path):
  277. sys.path.append(project_path)
  278. if self.options.get('autoimport'):
  279. self.generate_autoimport_cache()
  280. env.debug('Context init', project_path)
  281. env.message('Init Rope project: %s' % project_path)
  282. def __enter__(self):
  283. """ Enter to Rope ctx. """
  284. env.let('g:pymode_rope_current', self.project.root.real_path)
  285. self.project.validate(self.project.root)
  286. self.resource = libutils.path_to_resource(
  287. self.project, env.curbuf.name, 'file')
  288. if not self.resource.exists() or os.path.isdir(
  289. self.resource.real_path):
  290. self.resource = None
  291. else:
  292. env.debug('Found resource', self.resource.path)
  293. return self
  294. def __exit__(self, t, value, traceback):
  295. """ Exit from Rope ctx. """
  296. if t is None:
  297. self.project.close()
  298. def generate_autoimport_cache(self):
  299. """ Update autoimport cache. """
  300. env.message('Regenerate autoimport cache.')
  301. modules = self.options.get('autoimport_modules', [])
  302. def _update_cache(importer, modules=None):
  303. importer.generate_cache()
  304. if modules:
  305. importer.generate_modules_cache(modules)
  306. importer.project.sync()
  307. _update_cache(self.importer, modules)
  308. class ProgressHandler(object):
  309. """ Handle task progress. """
  310. def __init__(self, msg):
  311. """ Init progress handler. """
  312. self.handle = TaskHandle(name="refactoring_handle")
  313. self.handle.add_observer(self)
  314. self.message = msg
  315. def __call__(self):
  316. """ Show current progress. """
  317. percent_done = self.handle.current_jobset().get_percent_done()
  318. env.message('%s - done %s%%' % (self.message, percent_done))
  319. _scope_weight = {
  320. 'local': 10, 'attribute': 20, 'global': 30, 'imported': 40, 'builtin': 50}
  321. def _sort_proporsals(p):
  322. return (
  323. _scope_weight.get(p.scope, 100), int(p.name.startswith('_')), p.name)
  324. class Refactoring(object): # noqa
  325. """ Base class for refactor operations. """
  326. def run(self):
  327. """ Run refactoring.
  328. :return bool:
  329. """
  330. with RopeContext() as ctx:
  331. if not ctx.resource:
  332. env.error("You should save the file before refactoring.")
  333. return None
  334. try:
  335. env.message(self.__doc__)
  336. refactor = self.get_refactor(ctx)
  337. input_str = self.get_input_str(refactor, ctx)
  338. if not input_str:
  339. return False
  340. action = env.user_input_choices(
  341. 'Choose what to do:', 'perform', 'preview',
  342. 'perform in class hierarchy',
  343. 'preview in class hierarchy')
  344. in_hierarchy = action.endswith("in class hierarchy")
  345. changes = self.get_changes(refactor, input_str, in_hierarchy)
  346. if not action:
  347. return False
  348. if action.startswith('preview'):
  349. print("\n ")
  350. print("-------------------------------")
  351. print("\n%s\n" % changes.get_description())
  352. print("-------------------------------\n\n")
  353. if not env.user_confirm('Do the changes?'):
  354. return False
  355. progress = ProgressHandler('Apply changes ...')
  356. ctx.project.do(changes, task_handle=progress.handle)
  357. reload_changes(changes)
  358. except exceptions.RefactoringError as e:
  359. env.error(str(e))
  360. except Exception as e: # noqa
  361. env.error('Unhandled exception in Pymode: %s' % e)
  362. @staticmethod
  363. def get_refactor(ctx):
  364. """ Get refactor object. """
  365. raise NotImplementedError
  366. @staticmethod
  367. def get_input_str(refactor, ctx):
  368. """ Get user input. Skip by default.
  369. :return bool: True
  370. """
  371. return True
  372. @staticmethod
  373. def get_changes(refactor, input_str, in_hierarchy=False):
  374. return refactor.get_changes(input_str)
  375. class RenameRefactoring(Refactoring):
  376. """ Rename var/function/method/class. """
  377. def __init__(self, module=False):
  378. self.module = module
  379. super(RenameRefactoring, self).__init__()
  380. def get_refactor(self, ctx):
  381. """ Function description.
  382. :return Rename:
  383. """
  384. offset = None
  385. if not self.module:
  386. _, offset = env.get_offset_params()
  387. env.debug('Prepare rename', offset)
  388. return rename.Rename(ctx.project, ctx.resource, offset)
  389. def get_input_str(self, refactor, ctx):
  390. """ Return user input. """
  391. oldname = str(refactor.get_old_name())
  392. msg = 'Renaming method/variable. New name:'
  393. if self.module:
  394. msg = 'Renaming module. New name:'
  395. newname = env.user_input(msg, oldname)
  396. if newname == oldname:
  397. env.message("Nothing to do.")
  398. return False
  399. return newname
  400. @staticmethod
  401. def get_changes(refactor, input_str, in_hierarchy=False):
  402. """ Get changes.
  403. :return Changes:
  404. """
  405. return refactor.get_changes(input_str, in_hierarchy=in_hierarchy)
  406. class ExtractMethodRefactoring(Refactoring):
  407. """ Extract method. """
  408. @staticmethod
  409. def get_input_str(refactor, ctx):
  410. """ Return user input. """
  411. return env.user_input('New method name:')
  412. @staticmethod
  413. def get_refactor(ctx):
  414. """ Function description.
  415. :return Rename:
  416. """
  417. cursor1, cursor2 = env.curbuf.mark('<'), env.curbuf.mark('>')
  418. _, offset1 = env.get_offset_params(cursor1)
  419. _, offset2 = env.get_offset_params(cursor2)
  420. return extract.ExtractMethod(
  421. ctx.project, ctx.resource, offset1, offset2)
  422. class ExtractVariableRefactoring(Refactoring):
  423. """ Extract variable. """
  424. @staticmethod
  425. def get_input_str(refactor, ctx):
  426. """ Return user input. """
  427. return env.user_input('New variable name:')
  428. @staticmethod
  429. def get_refactor(ctx):
  430. """ Function description.
  431. :return Rename:
  432. """
  433. cursor1, cursor2 = env.curbuf.mark('<'), env.curbuf.mark('>')
  434. _, offset1 = env.get_offset_params(cursor1)
  435. _, offset2 = env.get_offset_params(cursor2)
  436. return extract.ExtractVariable(
  437. ctx.project, ctx.resource, offset1, offset2)
  438. class InlineRefactoring(Refactoring):
  439. """ Inline variable/method. """
  440. @staticmethod
  441. def get_refactor(ctx):
  442. """ Function description.
  443. :return Rename:
  444. """
  445. _, offset = env.get_offset_params()
  446. return inline.create_inline(ctx.project, ctx.resource, offset)
  447. @staticmethod
  448. def get_changes(refactor, input_str, in_hierarchy=False):
  449. """ Get changes.
  450. :return Changes:
  451. """
  452. return refactor.get_changes()
  453. class UseFunctionRefactoring(Refactoring):
  454. """ Use selected function as possible. """
  455. @staticmethod
  456. def get_refactor(ctx):
  457. """ Function description.
  458. :return Rename:
  459. """
  460. _, offset = env.get_offset_params()
  461. return usefunction.UseFunction(ctx.project, ctx.resource, offset)
  462. @staticmethod
  463. def get_changes(refactor, input_str, in_hierarchy=False):
  464. """ Get changes.
  465. :return Changes:
  466. """
  467. return refactor.get_changes()
  468. class ModuleToPackageRefactoring(Refactoring):
  469. """ Convert module to package. """
  470. @staticmethod
  471. def get_refactor(ctx):
  472. """ Function description.
  473. :return Rename:
  474. """
  475. return ModuleToPackage(ctx.project, ctx.resource)
  476. @staticmethod
  477. def get_changes(refactor, input_str, in_hierarchy=False):
  478. """ Get changes.
  479. :return Changes:
  480. """
  481. return refactor.get_changes()
  482. class MoveRefactoring(Refactoring):
  483. """ Move method/module to other class/global. """
  484. @staticmethod
  485. def get_input_str(refactor, ctx):
  486. """ Get destination.
  487. :return str:
  488. """
  489. return env.user_input('Enter destination:')
  490. @staticmethod
  491. def get_refactor(ctx):
  492. """ Function description.
  493. :return Rename:
  494. """
  495. _, offset = env.get_offset_params()
  496. if offset == 0:
  497. offset = None
  498. return move.create_move(ctx.project, ctx.resource, offset)
  499. class ChangeSignatureRefactoring(Refactoring):
  500. """ Change function signature (add/remove/sort arguments). """
  501. @staticmethod
  502. def get_input_str(refactor, ctx):
  503. """ Get destination.
  504. :return str:
  505. """
  506. args = refactor.get_args()
  507. default = ', '.join(a[0] for a in args)
  508. return env.user_input('Change the signature:', default)
  509. @staticmethod
  510. def get_refactor(ctx):
  511. """ Function description.
  512. :return Rename:
  513. """
  514. _, offset = env.get_offset_params()
  515. return change_signature.ChangeSignature(
  516. ctx.project, ctx.resource, offset)
  517. def get_changes(self, refactor, input_string, in_hierarchy=False):
  518. """ Function description.
  519. :return Rope.changes:
  520. """
  521. args = re.sub(r'[\s\(\)]+', '', input_string).split(',')
  522. olds = [arg[0] for arg in refactor.get_args()]
  523. changers = []
  524. for arg in [a for a in olds if a not in args]:
  525. changers.append(change_signature.ArgumentRemover(olds.index(arg)))
  526. olds.remove(arg)
  527. order = []
  528. for index, arg in enumerate(args):
  529. if arg not in olds:
  530. changers.append(change_signature.ArgumentAdder(index, arg))
  531. olds.insert(index, arg)
  532. order.append(olds.index(arg))
  533. changers.append(change_signature.ArgumentReorderer(
  534. order, autodef='None'))
  535. return refactor.get_changes(changers, in_hierarchy=in_hierarchy)
  536. class GenerateElementRefactoring(Refactoring):
  537. """ Class description. """
  538. def __init__(self, kind, *args, **kwargs):
  539. """ Function description. """
  540. self.kind = kind
  541. super(GenerateElementRefactoring, self).__init__(*args, **kwargs)
  542. def get_refactor(self, ctx):
  543. """ Function description.
  544. :return Rename:
  545. """
  546. _, offset = env.get_offset_params()
  547. return generate.create_generate(
  548. self.kind, ctx.project, ctx.resource, offset)
  549. def get_changes(self, refactor, input_str, in_hierarchy=False):
  550. """ Function description.
  551. :return Rope.changes:
  552. """
  553. return refactor.get_changes()
  554. @env.catch_exceptions
  555. def reload_changes(changes):
  556. """ Reload changed buffers. """
  557. resources = changes.get_changed_resources()
  558. moved = _get_moved_resources(changes) # noqa
  559. current = env.curbuf.number
  560. for f in resources:
  561. bufnr = env.var('bufnr("%s")' % f.real_path)
  562. env.goto_buffer(bufnr)
  563. path = env.curbuf.name
  564. if f in moved:
  565. path = moved[f].real_path
  566. env.debug('Reload', f.real_path, path, bufnr)
  567. env.goto_file(path, 'e!', force=True)
  568. env.message("%s has been changed." % f.real_path, history=True)
  569. env.goto_buffer(current)
  570. def _get_moved_resources(changes):
  571. moved = dict()
  572. if isinstance(changes, change.ChangeSet):
  573. for c in changes.changes:
  574. moved.update(_get_moved_resources(c))
  575. if isinstance(changes, change.MoveResource):
  576. moved[changes.resource] = changes.new_resource
  577. return moved
  578. def _get_autoimport_proposals(out, ctx, source, offset, dot=False):
  579. if not ctx.options.get('autoimport') or dot:
  580. return out
  581. if '.' in codeassist.starting_expression(source, offset):
  582. return out
  583. current_offset = offset - 1
  584. while current_offset > 0 and (
  585. source[current_offset].isalnum() or source[current_offset] == '_'):
  586. current_offset -= 1
  587. starting = source[current_offset:offset]
  588. starting = starting.strip()
  589. if not starting:
  590. return out
  591. for assist in ctx.importer.import_assist(starting):
  592. out.append(dict(
  593. abbr=' : '.join(assist),
  594. word=assist[0],
  595. kind='autoimport:',
  596. ))
  597. return out
  598. @env.catch_exceptions
  599. def complete_check():
  600. """ Function description.
  601. :return bool:
  602. """
  603. row, column = env.cursor
  604. line = env.lines[row - 1]
  605. word_finder = worder.Worder(line, True)
  606. parent, name, _ = word_finder.get_splitted_primary_before(column - 1)
  607. if parent:
  608. return False
  609. with RopeContext() as ctx:
  610. modules = ctx.importer.get_modules(name)
  611. if not modules:
  612. return False
  613. if name in ctx.project.pycore.resource_to_pyobject(ctx.resource):
  614. return False
  615. if not env.user_confirm("Import %s?" % name, True):
  616. return False
  617. if len(modules) == 1:
  618. _insert_import(name, modules[0], ctx)
  619. else:
  620. module = env.user_input_choices('With module to import:', *modules)
  621. if module:
  622. _insert_import(name, module, ctx)
  623. def _insert_import(name, module, ctx):
  624. if not ctx.resource:
  625. source, _ = env.get_offset_params()
  626. lineno = ctx.importer.find_insertion_line(source)
  627. line = 'from %s import %s' % (module, name)
  628. env.curbuf[lineno - 1:lineno - 1] = [
  629. env.prepare_value(line, dumps=False)]
  630. return True
  631. pyobject = ctx.project.pycore.resource_to_pyobject(ctx.resource)
  632. import_tools = importutils.ImportTools(ctx.project.pycore)
  633. module_imports = import_tools.module_imports(pyobject)
  634. new_import = importutils.FromImport(module, 0, [[name, None]])
  635. module_imports.add_import(new_import)
  636. changes = change.ChangeContents(
  637. ctx.resource, module_imports.get_changed_source())
  638. action = env.user_input_choices(
  639. 'Choose what to do:', 'perform', 'preview')
  640. if not action:
  641. return False
  642. if action == 'preview':
  643. print("\n ")
  644. print("-------------------------------")
  645. print("\n%s\n" % changes.get_description())
  646. print("-------------------------------\n\n")
  647. if not env.user_confirm('Do the changes?'):
  648. return False
  649. progress = ProgressHandler('Apply changes ...')
  650. ctx.project.do(changes, task_handle=progress.handle)
  651. reload_changes(changes)