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