interface.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. import os
  2. import rope.base.change
  3. from rope.base import libutils, utils, exceptions
  4. from rope.contrib import codeassist, generate, autoimport, findit
  5. from ropemode import refactor, decorators, dialog
  6. class RopeMode(object):
  7. def __init__(self, env):
  8. self.project = None
  9. self.old_content = None
  10. self.env = env
  11. self._assist = None
  12. self._prepare_refactorings()
  13. self.autoimport = None
  14. for attrname in dir(self):
  15. attr = getattr(self, attrname)
  16. if not callable(attr):
  17. continue
  18. kind = getattr(attr, 'kind', None)
  19. if kind == 'local':
  20. key = getattr(attr, 'local_key', None)
  21. prefix = getattr(attr, 'prefix', None)
  22. self.env.local_command(attrname, attr, key, prefix)
  23. if kind == 'global':
  24. key = getattr(attr, 'global_key', None)
  25. prefix = getattr(attr, 'prefix', None)
  26. self.env.global_command(attrname, attr, key, prefix)
  27. if kind == 'hook':
  28. hook = getattr(attr, 'hook', None)
  29. self.env.add_hook(attrname, attr, hook)
  30. def _prepare_refactorings(self):
  31. for name in dir(refactor):
  32. if not name.startswith('_') and name != 'Refactoring':
  33. attr = getattr(refactor, name)
  34. if isinstance(attr, type) and \
  35. issubclass(attr, refactor.Refactoring):
  36. refname = self._refactoring_name(attr)
  37. @decorators.local_command(attr.key, 'P', None, refname)
  38. def do_refactor(prefix, self=self, refactoring=attr):
  39. initial_asking = prefix is None
  40. refactoring(self, self.env).show(initial_asking=initial_asking)
  41. setattr(self, refname, do_refactor)
  42. @staticmethod
  43. def _refactoring_name(refactoring):
  44. return refactor.refactoring_name(refactoring)
  45. @decorators.rope_hook('before_save')
  46. def before_save_actions(self):
  47. if self.project is not None:
  48. if not self._is_python_file(self.env.filename()):
  49. return
  50. resource = self._get_resource()
  51. if resource.exists():
  52. self.old_content = resource.read()
  53. else:
  54. self.old_content = ''
  55. @decorators.rope_hook('after_save')
  56. def after_save_actions(self):
  57. if self.project is not None and self.old_content is not None:
  58. libutils.report_change(self.project, self.env.filename(),
  59. self.old_content)
  60. self.old_content = None
  61. @decorators.rope_hook('exit')
  62. def exiting_actions(self):
  63. if self.project is not None:
  64. self.close_project()
  65. @decorators.global_command('o')
  66. def open_project(self, root=None):
  67. if not root:
  68. if self.env.get('auto_project'):
  69. root = self.env.get_cur_dir()
  70. else:
  71. root = self.env.ask_directory('Rope project root folder: ')
  72. if self.project is not None:
  73. self.close_project()
  74. address = rope.base.project._realpath(os.path.join(root,
  75. '.ropeproject'))
  76. if not os.path.exists(address) and not self.env.get('auto_project'):
  77. if not self.env.y_or_n('Project not exists in %s, create one?' % root):
  78. self.env.message("Project creation aborted")
  79. return
  80. progress = self.env.create_progress('Opening [%s] project' % root)
  81. self.project = rope.base.project.Project(root)
  82. if self.env.get('enable_autoimport'):
  83. underlined = self.env.get('autoimport_underlineds')
  84. self.autoimport = autoimport.AutoImport(self.project,
  85. underlined=underlined)
  86. progress.done()
  87. @decorators.global_command('k')
  88. def close_project(self):
  89. if self.project is not None:
  90. progress = self.env.create_progress('Closing [%s] project' %
  91. self.project.address)
  92. self.project.close()
  93. self.project = None
  94. progress.done()
  95. @decorators.global_command()
  96. def write_project(self):
  97. if self.project is not None:
  98. progress = self.env.create_progress(
  99. 'Writing [%s] project data to disk' % self.project.address)
  100. self.project.sync()
  101. progress.done()
  102. @decorators.global_command('u')
  103. def undo(self):
  104. self._check_project()
  105. change = self.project.history.tobe_undone
  106. if change is None:
  107. self.env.message('Nothing to undo!')
  108. return
  109. if self.env.y_or_n('Undo [%s]? ' % str(change)):
  110. def undo(handle):
  111. for changes in self.project.history.undo(task_handle=handle):
  112. self._reload_buffers(changes, undo=True)
  113. refactor.runtask(self.env, undo, 'Undo refactoring',
  114. interrupts=False)
  115. @decorators.global_command('r')
  116. def redo(self):
  117. self._check_project()
  118. change = self.project.history.tobe_redone
  119. if change is None:
  120. self.env.message('Nothing to redo!')
  121. return
  122. if self.env.y_or_n('Redo [%s]? ' % str(change)):
  123. def redo(handle):
  124. for changes in self.project.history.redo(task_handle=handle):
  125. self._reload_buffers(changes)
  126. refactor.runtask(self.env, redo, 'Redo refactoring',
  127. interrupts=False)
  128. @decorators.local_command('a g', shortcut='C-c g')
  129. def goto_definition(self):
  130. definition = self._base_definition_location()
  131. if definition:
  132. self.env.push_mark()
  133. self._goto_location(definition[0], definition[1])
  134. else:
  135. self.env.message('Cannot find the definition!')
  136. @decorators.local_command()
  137. def pop_mark(self):
  138. self.env.pop_mark()
  139. @decorators.local_command()
  140. def definition_location(self):
  141. definition = self._base_definition_location()
  142. if definition:
  143. return str(definition[0].real_path), definition[1]
  144. return None
  145. def _base_definition_location(self):
  146. self._check_project()
  147. resource, offset = self._get_location()
  148. maxfixes = self.env.get('codeassist_maxfixes')
  149. try:
  150. definition = codeassist.get_definition_location(
  151. self.project, self._get_text(), offset, resource, maxfixes)
  152. except exceptions.BadIdentifierError:
  153. return None
  154. if tuple(definition) != (None, None):
  155. return definition
  156. return None
  157. @decorators.local_command('a d', 'P', 'C-c d')
  158. def show_doc(self, prefix):
  159. self._check_project()
  160. self._base_show_doc(prefix, self._base_get_doc(codeassist.get_doc))
  161. @decorators.local_command()
  162. def get_calltip(self):
  163. self._check_project()
  164. def _get_doc(project, text, offset, *args, **kwds):
  165. try:
  166. offset = text.rindex('(', 0, offset) - 1
  167. except ValueError:
  168. return None
  169. return codeassist.get_calltip(project, text, offset, *args, **kwds)
  170. return self._base_get_doc(_get_doc)
  171. @decorators.local_command('a c', 'P')
  172. def show_calltip(self, prefix):
  173. self._base_show_doc(prefix, self.get_calltip())
  174. def _base_show_doc(self, prefix, docs):
  175. if docs:
  176. self.env.show_doc(docs, prefix)
  177. else:
  178. self.env.message('No docs available!')
  179. @decorators.local_command()
  180. def get_doc(self):
  181. self._check_project()
  182. return self._base_get_doc(codeassist.get_doc)
  183. def _base_get_doc(self, get_doc):
  184. maxfixes = self.env.get('codeassist_maxfixes')
  185. text = self._get_text()
  186. offset = self.env.get_offset()
  187. try:
  188. return get_doc(self.project, text, offset,
  189. self.resource, maxfixes)
  190. except exceptions.BadIdentifierError:
  191. return None
  192. def _get_text(self):
  193. resource = self.resource
  194. if not self.env.is_modified() and resource is not None:
  195. return resource.read()
  196. return self.env.get_text()
  197. def _base_findit(self, do_find, optionals, get_kwds):
  198. self._check_project()
  199. self._save_buffers()
  200. resource, offset = self._get_location()
  201. action, values = dialog.show_dialog(
  202. self._askdata, ['search', 'cancel'], optionals=optionals)
  203. if action == 'search':
  204. kwds = get_kwds(values)
  205. def calculate(handle):
  206. resources = refactor._resources(self.project,
  207. values.get('resources'))
  208. return do_find(self.project, resource, offset,
  209. resources=resources, task_handle=handle, **kwds)
  210. result = refactor.runtask(self.env, calculate, 'Find Occurrences')
  211. locations = [Location(location) for location in result]
  212. self.env.show_occurrences(locations)
  213. @decorators.local_command('a f', shortcut='C-c f')
  214. def find_occurrences(self):
  215. optionals = {
  216. 'unsure': dialog.Data('Find uncertain occurrences: ',
  217. default='no', values=['yes', 'no']),
  218. 'resources': dialog.Data('Files to search: '),
  219. 'in_hierarchy': dialog.Data(
  220. 'Rename methods in class hierarchy: ',
  221. default='no', values=['yes', 'no'])}
  222. def get_kwds(values):
  223. return {'unsure': values.get('unsure') == 'yes',
  224. 'in_hierarchy': values.get('in_hierarchy') == 'yes'}
  225. self._base_findit(findit.find_occurrences, optionals, get_kwds)
  226. @decorators.local_command('a i')
  227. def find_implementations(self):
  228. optionals = {'resources': dialog.Data('Files to search: ')}
  229. def get_kwds(values):
  230. return {}
  231. self._base_findit(findit.find_implementations, optionals, get_kwds)
  232. @decorators.local_command('a /', 'P', 'M-/')
  233. def code_assist(self, prefix):
  234. _CodeAssist(self, self.env).code_assist(prefix)
  235. @decorators.local_command('a ?', 'P', 'M-?')
  236. def lucky_assist(self, prefix):
  237. _CodeAssist(self, self.env).lucky_assist(prefix)
  238. @decorators.local_command(prefix='P')
  239. def omni_complete(self, prefix):
  240. self._assist.omni_complete(prefix)
  241. def _find_start(self):
  242. self._assist = _CodeAssist(self, self.env)
  243. start = (self.env.cursor[1] - self.env.get_offset()
  244. + self._assist.starting_offset)
  245. self.env._command('let g:pymode_offset = %s' % start)
  246. @decorators.local_command('a')
  247. def auto_import(self):
  248. _CodeAssist(self, self.env).auto_import()
  249. @decorators.local_command()
  250. def completions(self):
  251. return _CodeAssist(self, self.env).completions()
  252. @decorators.local_command()
  253. def extended_completions(self):
  254. return _CodeAssist(self, self.env).extended_completions()
  255. def _check_autoimport(self):
  256. self._check_project()
  257. if self.autoimport is None:
  258. self.env.message('autoimport is disabled; '
  259. 'see `enable_autoimport\' variable')
  260. return False
  261. return True
  262. @decorators.global_command('g')
  263. def generate_autoimport_cache(self):
  264. if not self._check_autoimport():
  265. return
  266. modules = self.env.get('autoimport_modules')
  267. modules = [ m if isinstance(m, basestring) else m.value() for m in modules ]
  268. def generate(handle):
  269. self.autoimport.generate_cache(task_handle=handle)
  270. self.autoimport.generate_modules_cache(modules, task_handle=handle)
  271. refactor.runtask(self.env, generate, 'Generate autoimport cache')
  272. self.write_project()
  273. @decorators.global_command('f', 'P')
  274. def find_file(self, prefix):
  275. file = self._base_find_file(prefix)
  276. if file is not None:
  277. self.env.find_file(file.real_path)
  278. @decorators.global_command('4 f', 'P')
  279. def find_file_other_window(self, prefix):
  280. file = self._base_find_file(prefix)
  281. if file is not None:
  282. self.env.find_file(file.real_path, other=True)
  283. def _base_find_file(self, prefix):
  284. self._check_project()
  285. if prefix:
  286. files = self.project.pycore.get_python_files()
  287. else:
  288. files = self.project.get_files()
  289. return self._ask_file(files)
  290. def _ask_file(self, files):
  291. names = []
  292. for file in files:
  293. names.append('<'.join(reversed(file.path.split('/'))))
  294. result = self.env.ask_values('Rope Find File: ', names)
  295. if result is not None:
  296. path = '/'.join(reversed(result.split('<')))
  297. file = self.project.get_file(path)
  298. return file
  299. self.env.message('No file selected')
  300. @decorators.local_command('a j')
  301. def jump_to_global(self):
  302. if not self._check_autoimport():
  303. return
  304. all_names = list(self.autoimport.get_all_names())
  305. name = self.env.ask_values('Global name: ', all_names)
  306. result = dict(self.autoimport.get_name_locations(name))
  307. if len(result) == 1:
  308. resource = list(result.keys())[0]
  309. else:
  310. resource = self._ask_file(result.keys())
  311. if resource:
  312. self._goto_location(resource, result[resource])
  313. @decorators.global_command('c')
  314. def project_config(self):
  315. self._check_project()
  316. if self.project.ropefolder is not None:
  317. config = self.project.ropefolder.get_child('config.py')
  318. self.env.find_file(config.real_path)
  319. else:
  320. self.env.message('No rope project folder found')
  321. @decorators.global_command('n m')
  322. def create_module(self):
  323. def callback(sourcefolder, name):
  324. return generate.create_module(self.project, name, sourcefolder)
  325. self._create('module', callback)
  326. @decorators.global_command('n p')
  327. def create_package(self):
  328. def callback(sourcefolder, name):
  329. folder = generate.create_package(self.project, name, sourcefolder)
  330. return folder.get_child('__init__.py')
  331. self._create('package', callback)
  332. @decorators.global_command('n f')
  333. def create_file(self):
  334. def callback(parent, name):
  335. return parent.create_file(name)
  336. self._create('file', callback, 'parent')
  337. @decorators.global_command('n d')
  338. def create_directory(self):
  339. def callback(parent, name):
  340. parent.create_folder(name)
  341. self._create('directory', callback, 'parent')
  342. @decorators.local_command()
  343. def analyze_module(self):
  344. """Perform static object analysis on this module"""
  345. self._check_project()
  346. self.project.pycore.analyze_module(self.resource)
  347. @decorators.global_command()
  348. def analyze_modules(self):
  349. """Perform static object analysis on all project modules"""
  350. self._check_project()
  351. def _analyze_modules(handle):
  352. libutils.analyze_modules(self.project, task_handle=handle)
  353. refactor.runtask(self.env, _analyze_modules, 'Analyze project modules')
  354. @decorators.local_command()
  355. def run_module(self):
  356. """Run and perform dynamic object analysis on this module"""
  357. self._check_project()
  358. process = self.project.pycore.run_module(self.resource)
  359. try:
  360. process.wait_process()
  361. finally:
  362. process.kill_process()
  363. def _create(self, name, callback, parentname='source'):
  364. self._check_project()
  365. confs = {'name': dialog.Data(name.title() + ' name: ')}
  366. parentname = parentname + 'folder'
  367. optionals = {parentname: dialog.Data(
  368. parentname.title() + ' Folder: ',
  369. default=self.project.address, kind='directory')}
  370. action, values = dialog.show_dialog(
  371. self._askdata, ['perform', 'cancel'], confs, optionals)
  372. if action == 'perform':
  373. parent = libutils.path_to_resource(
  374. self.project, values.get(parentname, self.project.address))
  375. resource = callback(parent, values['name'])
  376. if resource:
  377. self.env.find_file(resource.real_path)
  378. def _goto_location(self, resource, lineno):
  379. if resource:
  380. self.env.find_file(str(resource.real_path),
  381. other=self.env.get('goto_def_newwin'))
  382. if lineno:
  383. self.env.goto_line(lineno)
  384. def _get_location(self):
  385. offset = self.env.get_offset()
  386. return self.resource, offset
  387. def _get_resource(self, filename=None):
  388. if filename is None:
  389. filename = self.env.filename()
  390. if filename is None or self.project is None:
  391. return
  392. resource = libutils.path_to_resource(self.project, filename, 'file')
  393. return resource
  394. @property
  395. def resource(self):
  396. """the current resource
  397. Returns `None` when file does not exist.
  398. """
  399. resource = self._get_resource()
  400. if resource and resource.exists():
  401. return resource
  402. @decorators.global_command()
  403. def get_project_root(self):
  404. if self.project is not None:
  405. return self.project.root.real_path
  406. else:
  407. return None
  408. def _check_project(self):
  409. if self.project is None:
  410. if self.env.get('guess_project'):
  411. self.open_project(self._guess_project())
  412. else:
  413. self.open_project()
  414. else:
  415. self.project.validate(self.project.root)
  416. def _guess_project(self):
  417. cwd = self.env.filename()
  418. if cwd is not None:
  419. while True:
  420. ropefolder = os.path.join(cwd, '.ropeproject')
  421. if os.path.exists(ropefolder) and os.path.isdir(ropefolder):
  422. return cwd
  423. newcwd = os.path.dirname(cwd)
  424. if newcwd == cwd:
  425. break
  426. cwd = newcwd
  427. def _reload_buffers(self, changes, undo=False):
  428. self._reload_buffers_for_changes(
  429. changes.get_changed_resources(),
  430. self._get_moved_resources(changes, undo))
  431. def _reload_buffers_for_changes(self, changed, moved={}):
  432. filenames = [resource.real_path for resource in changed]
  433. moved = dict([(resource.real_path, moved[resource].real_path)
  434. for resource in moved])
  435. self.env.reload_files(filenames, moved)
  436. def _get_moved_resources(self, changes, undo=False):
  437. result = {}
  438. if isinstance(changes, rope.base.change.ChangeSet):
  439. for change in changes.changes:
  440. result.update(self._get_moved_resources(change))
  441. if isinstance(changes, rope.base.change.MoveResource):
  442. result[changes.resource] = changes.new_resource
  443. if undo:
  444. return dict([(value, key) for key, value in result.items()])
  445. return result
  446. def _save_buffers(self, only_current=False):
  447. if only_current:
  448. filenames = [self.env.filename()]
  449. else:
  450. filenames = self.env.filenames()
  451. pythons = []
  452. for filename in filenames:
  453. if self._is_python_file(filename):
  454. pythons.append(filename)
  455. self.env.save_files(pythons)
  456. def _is_python_file(self, path):
  457. resource = self._get_resource(path)
  458. return (resource is not None and
  459. resource.project == self.project and
  460. self.project.pycore.is_python_file(resource))
  461. def _askdata(self, data, starting=None):
  462. ask_func = self.env.ask
  463. ask_args = {'prompt': data.prompt, 'starting': starting,
  464. 'default': data.default}
  465. if data.values:
  466. ask_func = self.env.ask_values
  467. ask_args['values'] = data.values
  468. elif data.kind == 'directory':
  469. ask_func = self.env.ask_directory
  470. return ask_func(**ask_args)
  471. class Location(object):
  472. def __init__(self, location):
  473. self.location = location
  474. self.filename = location.resource.real_path
  475. self.offset = location.offset
  476. self.note = ''
  477. if location.unsure:
  478. self.note = '?'
  479. @property
  480. def lineno(self):
  481. if hasattr(self.location, 'lineno'):
  482. return self.location.lineno
  483. return self.location.resource.read().count('\n', 0, self.offset) + 1
  484. class _CodeAssist(object):
  485. def __init__(self, interface, env):
  486. self.interface = interface
  487. self.env = env
  488. def code_assist(self, prefix):
  489. proposals = self._calculate_proposals()
  490. if prefix is not None:
  491. arg = self.env.prefix_value(prefix)
  492. if arg == 0:
  493. arg = len(proposals)
  494. common_start = self._calculate_prefix(proposals[:arg])
  495. self.env.insert(common_start[self.offset - self.starting_offset:])
  496. self._starting = common_start
  497. self._offset = self.starting_offset + len(common_start)
  498. prompt = 'Completion for %s: ' % self.expression
  499. proposals = map(self.env._completion_data, proposals)
  500. result = self.env.ask_completion(prompt, proposals, self.starting)
  501. if result is not None:
  502. self._apply_assist(result)
  503. def omni_complete(self, prefix):
  504. proposals = self._calculate_proposals()
  505. proposals = self.env._update_proposals(proposals)
  506. command = u'let g:pythoncomplete_completions = [%s]' % proposals
  507. self.env._command(command, encode=True)
  508. def lucky_assist(self, prefix):
  509. proposals = self._calculate_proposals()
  510. selected = 0
  511. if prefix is not None:
  512. selected = self.env.prefix_value(prefix)
  513. if 0 <= selected < len(proposals):
  514. result = self.env._completion_text(proposals[selected])
  515. else:
  516. self.env.message('Not enough proposals!')
  517. return
  518. self._apply_assist(result)
  519. def auto_import(self):
  520. if not self.interface._check_autoimport():
  521. return
  522. if not self.autoimport.names and self.env.get('autoimport_generate'):
  523. self.interface.generate_autoimport_cache()
  524. name = self.env.current_word()
  525. modules = self.autoimport.get_modules(name)
  526. if modules:
  527. if len(modules) == 1:
  528. module = modules[0]
  529. else:
  530. module = self.env.ask_values(
  531. 'Which module to import: ', modules)
  532. self._insert_import(name, module)
  533. else:
  534. self.env.message('Global name %s not found!' % name)
  535. def completions(self):
  536. proposals = self._calculate_proposals()
  537. prefix = self.offset - self.starting_offset
  538. return [self.env._completion_text(proposal)[prefix:]
  539. for proposal in proposals]
  540. def extended_completions(self):
  541. proposals = self._calculate_proposals()
  542. prefix = self.offset - self.starting_offset
  543. return [[proposal.name[prefix:], proposal.get_doc(),
  544. proposal.type] for proposal in proposals]
  545. def _apply_assist(self, assist):
  546. if ' : ' in assist:
  547. name, module = assist.rsplit(' : ', 1)
  548. self.env.delete(self.starting_offset + 1, self.offset + 1)
  549. self.env.insert(name)
  550. self._insert_import(name, module)
  551. else:
  552. self.env.delete(self.starting_offset + 1, self.offset + 1)
  553. self.env.insert(assist)
  554. def _calculate_proposals(self):
  555. self.interface._check_project()
  556. resource = self.interface.resource
  557. maxfixes = self.env.get('codeassist_maxfixes')
  558. proposals = codeassist.code_assist(
  559. self.interface.project, self.source, self.offset,
  560. resource, maxfixes=maxfixes)
  561. if self.env.get('sorted_completions', True):
  562. proposals = codeassist.sorted_proposals(proposals)
  563. if self.autoimport is not None:
  564. if self.starting.strip() and '.' not in self.expression:
  565. import_assists = self.autoimport.import_assist(self.starting)
  566. for assist in import_assists:
  567. p = codeassist.CompletionProposal(' : '.join(assist),
  568. 'autoimport')
  569. proposals.append(p)
  570. return proposals
  571. def _insert_import(self, name, module):
  572. lineno = self.autoimport.find_insertion_line(self.source)
  573. line = 'from %s import %s' % (module, name)
  574. self.env.insert_line(line, lineno)
  575. def _calculate_prefix(self, proposals):
  576. if not proposals:
  577. return ''
  578. prefix = self.env._completion_text(proposals[0])
  579. for proposal in proposals:
  580. common = 0
  581. name = self.env._completion_text(proposal)
  582. for c1, c2 in zip(prefix, name):
  583. if c1 != c2 or ' ' in (c1, c2):
  584. break
  585. common += 1
  586. prefix = prefix[:common]
  587. return prefix
  588. @property
  589. @utils.cacheit
  590. def offset(self):
  591. return self.env.get_offset()
  592. @property
  593. @utils.cacheit
  594. def source(self):
  595. return self.interface._get_text()
  596. @property
  597. @utils.cacheit
  598. def starting_offset(self):
  599. return codeassist.starting_offset(self.source, self.offset)
  600. @property
  601. @utils.cacheit
  602. def starting(self):
  603. return self.source[self.starting_offset:self.offset]
  604. @property
  605. @utils.cacheit
  606. def expression(self):
  607. return codeassist.starting_expression(self.source, self.offset)
  608. @property
  609. def autoimport(self):
  610. return self.interface.autoimport