Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/emacs-for-python/rope-dist/rope/base/worder.py
1421 views
1
import bisect
2
3
import rope.base.simplify
4
5
6
def get_name_at(resource, offset):
7
source_code = resource.read()
8
word_finder = Worder(source_code)
9
return word_finder.get_word_at(offset)
10
11
12
class Worder(object):
13
"""A class for finding boundaries of words and expressions
14
15
Note that in these methods, offset should be the index of the
16
character not the index of the character after it.
17
"""
18
19
def __init__(self, code, handle_ignores=False):
20
simplified = rope.base.simplify.real_code(code)
21
self.code_finder = _RealFinder(simplified, code)
22
self.handle_ignores = handle_ignores
23
self.code = code
24
25
def _init_ignores(self):
26
ignores = rope.base.simplify.ignored_regions(self.code)
27
self.dumb_finder = _RealFinder(self.code, self.code)
28
self.starts = [ignored[0] for ignored in ignores]
29
self.ends = [ignored[1] for ignored in ignores]
30
31
def _context_call(self, name, offset):
32
if self.handle_ignores:
33
if not hasattr(self, 'starts'):
34
self._init_ignores()
35
start = bisect.bisect(self.starts, offset)
36
if start > 0 and offset < self.ends[start - 1]:
37
return getattr(self.dumb_finder, name)(offset)
38
return getattr(self.code_finder, name)(offset)
39
40
def get_primary_at(self, offset):
41
return self._context_call('get_primary_at', offset)
42
43
def get_word_at(self, offset):
44
return self._context_call('get_word_at', offset)
45
46
def get_primary_range(self, offset):
47
return self._context_call('get_primary_range', offset)
48
49
def get_splitted_primary_before(self, offset):
50
return self._context_call('get_splitted_primary_before', offset)
51
52
def get_word_range(self, offset):
53
return self._context_call('get_word_range', offset)
54
55
def is_function_keyword_parameter(self, offset):
56
return self.code_finder.is_function_keyword_parameter(offset)
57
58
def is_a_class_or_function_name_in_header(self, offset):
59
return self.code_finder.is_a_class_or_function_name_in_header(offset)
60
61
def is_from_statement_module(self, offset):
62
return self.code_finder.is_from_statement_module(offset)
63
64
def is_from_aliased(self, offset):
65
return self.code_finder.is_from_aliased(offset)
66
67
def find_parens_start_from_inside(self, offset):
68
return self.code_finder.find_parens_start_from_inside(offset)
69
70
def is_a_name_after_from_import(self, offset):
71
return self.code_finder.is_a_name_after_from_import(offset)
72
73
def is_from_statement(self, offset):
74
return self.code_finder.is_from_statement(offset)
75
76
def get_from_aliased(self, offset):
77
return self.code_finder.get_from_aliased(offset)
78
79
def is_import_statement(self, offset):
80
return self.code_finder.is_import_statement(offset)
81
82
def is_assigned_here(self, offset):
83
return self.code_finder.is_assigned_here(offset)
84
85
def is_a_function_being_called(self, offset):
86
return self.code_finder.is_a_function_being_called(offset)
87
88
def get_word_parens_range(self, offset):
89
return self.code_finder.get_word_parens_range(offset)
90
91
def is_name_assigned_in_class_body(self, offset):
92
return self.code_finder.is_name_assigned_in_class_body(offset)
93
94
def is_on_function_call_keyword(self, offset):
95
return self.code_finder.is_on_function_call_keyword(offset)
96
97
def _find_parens_start(self, offset):
98
return self.code_finder._find_parens_start(offset)
99
100
def get_parameters(self, first, last):
101
return self.code_finder.get_parameters(first, last)
102
103
def get_from_module(self, offset):
104
return self.code_finder.get_from_module(offset)
105
106
def is_assigned_in_a_tuple_assignment(self, offset):
107
return self.code_finder.is_assigned_in_a_tuple_assignment(offset)
108
109
def get_assignment_type(self, offset):
110
return self.code_finder.get_assignment_type(offset)
111
112
def get_function_and_args_in_header(self, offset):
113
return self.code_finder.get_function_and_args_in_header(offset)
114
115
def get_lambda_and_args(self, offset):
116
return self.code_finder.get_lambda_and_args(offset)
117
118
def find_function_offset(self, offset):
119
return self.code_finder.find_function_offset(offset)
120
121
122
class _RealFinder(object):
123
124
def __init__(self, code, raw):
125
self.code = code
126
self.raw = raw
127
128
def _find_word_start(self, offset):
129
current_offset = offset
130
while current_offset >= 0 and self._is_id_char(current_offset):
131
current_offset -= 1
132
return current_offset + 1
133
134
def _find_word_end(self, offset):
135
while offset + 1 < len(self.code) and self._is_id_char(offset + 1):
136
offset += 1
137
return offset
138
139
def _find_last_non_space_char(self, offset):
140
while offset >= 0 and self.code[offset].isspace():
141
if self.code[offset] == '\n':
142
return offset
143
offset -= 1
144
return max(-1, offset)
145
146
def get_word_at(self, offset):
147
offset = self._get_fixed_offset(offset)
148
return self.raw[self._find_word_start(offset):
149
self._find_word_end(offset) + 1]
150
151
def _get_fixed_offset(self, offset):
152
if offset >= len(self.code):
153
return offset - 1
154
if not self._is_id_char(offset):
155
if offset > 0 and self._is_id_char(offset - 1):
156
return offset - 1
157
if offset < len(self.code) - 1 and self._is_id_char(offset + 1):
158
return offset + 1
159
return offset
160
161
def _is_id_char(self, offset):
162
return self.code[offset].isalnum() or self.code[offset] == '_'
163
164
def _find_string_start(self, offset):
165
kind = self.code[offset]
166
try:
167
return self.code.rindex(kind, 0, offset)
168
except ValueError:
169
return 0
170
171
def _find_parens_start(self, offset):
172
offset = self._find_last_non_space_char(offset - 1)
173
while offset >= 0 and self.code[offset] not in '[({':
174
if self.code[offset] not in ':,':
175
offset = self._find_primary_start(offset)
176
offset = self._find_last_non_space_char(offset - 1)
177
return offset
178
179
def _find_atom_start(self, offset):
180
old_offset = offset
181
if self.code[offset] == '\n':
182
return offset + 1
183
if self.code[offset].isspace():
184
offset = self._find_last_non_space_char(offset)
185
if self.code[offset] in '\'"':
186
return self._find_string_start(offset)
187
if self.code[offset] in ')]}':
188
return self._find_parens_start(offset)
189
if self._is_id_char(offset):
190
return self._find_word_start(offset)
191
return old_offset
192
193
def _find_primary_without_dot_start(self, offset):
194
"""It tries to find the undotted primary start
195
196
It is different from `self._get_atom_start()` in that it
197
follows function calls, too; such as in ``f(x)``.
198
199
"""
200
last_atom = offset
201
offset = self._find_last_non_space_char(last_atom)
202
while offset > 0 and self.code[offset] in ')]':
203
last_atom = self._find_parens_start(offset)
204
offset = self._find_last_non_space_char(last_atom - 1)
205
if offset >= 0 and (self.code[offset] in '"\'})]' or
206
self._is_id_char(offset)):
207
return self._find_atom_start(offset)
208
return last_atom
209
210
def _find_primary_start(self, offset):
211
if offset >= len(self.code):
212
offset = len(self.code) - 1
213
if self.code[offset] != '.':
214
offset = self._find_primary_without_dot_start(offset)
215
else:
216
offset = offset + 1
217
while offset > 0:
218
prev = self._find_last_non_space_char(offset - 1)
219
if offset <= 0 or self.code[prev] != '.':
220
break
221
offset = self._find_primary_without_dot_start(prev - 1)
222
if not self._is_id_char(offset):
223
break
224
225
return offset
226
227
def get_primary_at(self, offset):
228
offset = self._get_fixed_offset(offset)
229
start, end = self.get_primary_range(offset)
230
return self.raw[start:end].strip()
231
232
def get_splitted_primary_before(self, offset):
233
"""returns expression, starting, starting_offset
234
235
This function is used in `rope.codeassist.assist` function.
236
"""
237
if offset == 0:
238
return ('', '', 0)
239
end = offset - 1
240
word_start = self._find_atom_start(end)
241
real_start = self._find_primary_start(end)
242
if self.code[word_start:offset].strip() == '':
243
word_start = end
244
if self.code[end].isspace():
245
word_start = end
246
if self.code[real_start:word_start].strip() == '':
247
real_start = word_start
248
if real_start == word_start == end and not self._is_id_char(end):
249
return ('', '', offset)
250
if real_start == word_start:
251
return ('', self.raw[word_start:offset], word_start)
252
else:
253
if self.code[end] == '.':
254
return (self.raw[real_start:end], '', offset)
255
last_dot_position = word_start
256
if self.code[word_start] != '.':
257
last_dot_position = self._find_last_non_space_char(word_start - 1)
258
last_char_position = self._find_last_non_space_char(last_dot_position - 1)
259
if self.code[word_start].isspace():
260
word_start = offset
261
return (self.raw[real_start:last_char_position + 1],
262
self.raw[word_start:offset], word_start)
263
264
def _get_line_start(self, offset):
265
try:
266
return self.code.rindex('\n', 0, offset + 1)
267
except ValueError:
268
return 0
269
270
def _get_line_end(self, offset):
271
try:
272
return self.code.index('\n', offset)
273
except ValueError:
274
return len(self.code)
275
276
def is_name_assigned_in_class_body(self, offset):
277
word_start = self._find_word_start(offset - 1)
278
word_end = self._find_word_end(offset) + 1
279
if '.' in self.code[word_start:word_end]:
280
return False
281
line_start = self._get_line_start(word_start)
282
line = self.code[line_start:word_start].strip()
283
return not line and self.get_assignment_type(offset) == '='
284
285
def is_a_class_or_function_name_in_header(self, offset):
286
word_start = self._find_word_start(offset - 1)
287
line_start = self._get_line_start(word_start)
288
prev_word = self.code[line_start:word_start].strip()
289
return prev_word in ['def', 'class']
290
291
def _find_first_non_space_char(self, offset):
292
if offset >= len(self.code):
293
return len(self.code)
294
while offset < len(self.code) and self.code[offset].isspace():
295
if self.code[offset] == '\n':
296
return offset
297
offset += 1
298
return offset
299
300
def is_a_function_being_called(self, offset):
301
word_end = self._find_word_end(offset) + 1
302
next_char = self._find_first_non_space_char(word_end)
303
return next_char < len(self.code) and \
304
self.code[next_char] == '(' and \
305
not self.is_a_class_or_function_name_in_header(offset)
306
307
def _find_import_end(self, start):
308
return self._get_line_end(start)
309
310
def is_import_statement(self, offset):
311
try:
312
last_import = self.code.rindex('import ', 0, offset)
313
except ValueError:
314
return False
315
return self._find_import_end(last_import + 7) >= offset
316
317
def is_from_statement(self, offset):
318
try:
319
last_from = self.code.rindex('from ', 0, offset)
320
from_import = self.code.index(' import ', last_from)
321
from_names = from_import + 8
322
except ValueError:
323
return False
324
from_names = self._find_first_non_space_char(from_names)
325
return self._find_import_end(from_names) >= offset
326
327
def is_from_statement_module(self, offset):
328
if offset >= len(self.code) - 1:
329
return False
330
stmt_start = self._find_primary_start(offset)
331
line_start = self._get_line_start(stmt_start)
332
prev_word = self.code[line_start:stmt_start].strip()
333
return prev_word == 'from'
334
335
def is_a_name_after_from_import(self, offset):
336
try:
337
line_start = self._get_line_start(offset)
338
last_from = self.code.rindex('from ', line_start, offset)
339
from_import = self.code.index(' import ', last_from)
340
from_names = from_import + 8
341
except ValueError:
342
return False
343
if from_names - 1 > offset:
344
return False
345
return self._find_import_end(from_names) >= offset
346
347
def get_from_module(self, offset):
348
try:
349
last_from = self.code.rindex('from ', 0, offset)
350
import_offset = self.code.index(' import ', last_from)
351
end = self._find_last_non_space_char(import_offset)
352
return self.get_primary_at(end)
353
except ValueError:
354
pass
355
356
def is_from_aliased(self, offset):
357
if not self.is_a_name_after_from_import(offset):
358
return False
359
try:
360
end = self._find_word_end(offset)
361
as_end = min(self._find_word_end(end + 1), len(self.code))
362
as_start = self._find_word_start(as_end)
363
if self.code[as_start:as_end + 1] == 'as':
364
return True
365
except ValueError:
366
return False
367
368
def get_from_aliased(self, offset):
369
try:
370
end = self._find_word_end(offset)
371
as_ = self._find_word_end(end + 1)
372
alias = self._find_word_end(as_ + 1)
373
start = self._find_word_start(alias)
374
return self.raw[start:alias + 1]
375
except ValueError:
376
pass
377
378
def is_function_keyword_parameter(self, offset):
379
word_end = self._find_word_end(offset)
380
if word_end + 1 == len(self.code):
381
return False
382
next_char = self._find_first_non_space_char(word_end + 1)
383
equals = self.code[next_char:next_char + 2]
384
if equals == '==' or not equals.startswith('='):
385
return False
386
word_start = self._find_word_start(offset)
387
prev_char = self._find_last_non_space_char(word_start - 1)
388
return prev_char - 1 >= 0 and self.code[prev_char] in ',('
389
390
def is_on_function_call_keyword(self, offset):
391
stop = self._get_line_start(offset)
392
if self._is_id_char(offset):
393
offset = self._find_word_start(offset) - 1
394
offset = self._find_last_non_space_char(offset)
395
if offset <= stop or self.code[offset] not in '(,':
396
return False
397
parens_start = self.find_parens_start_from_inside(offset)
398
return stop < parens_start
399
400
def find_parens_start_from_inside(self, offset):
401
stop = self._get_line_start(offset)
402
opens = 1
403
while offset > stop:
404
if self.code[offset] == '(':
405
break
406
if self.code[offset] != ',':
407
offset = self._find_primary_start(offset)
408
offset -= 1
409
return max(stop, offset)
410
411
def is_assigned_here(self, offset):
412
return self.get_assignment_type(offset) is not None
413
414
def get_assignment_type(self, offset):
415
# XXX: does not handle tuple assignments
416
word_end = self._find_word_end(offset)
417
next_char = self._find_first_non_space_char(word_end + 1)
418
single = self.code[next_char:next_char + 1]
419
double = self.code[next_char:next_char + 2]
420
triple = self.code[next_char:next_char + 3]
421
if double not in ('==', '<=', '>=', '!='):
422
for op in [single, double, triple]:
423
if op.endswith('='):
424
return op
425
426
def get_primary_range(self, offset):
427
start = self._find_primary_start(offset)
428
end = self._find_word_end(offset) + 1
429
return (start, end)
430
431
def get_word_range(self, offset):
432
offset = max(0, offset)
433
start = self._find_word_start(offset)
434
end = self._find_word_end(offset) + 1
435
return (start, end)
436
437
def get_word_parens_range(self, offset, opening='(', closing=')'):
438
end = self._find_word_end(offset)
439
start_parens = self.code.index(opening, end)
440
index = start_parens
441
open_count = 0
442
while index < len(self.code):
443
if self.code[index] == opening:
444
open_count += 1
445
if self.code[index] == closing:
446
open_count -= 1
447
if open_count == 0:
448
return (start_parens, index + 1)
449
index += 1
450
return (start_parens, index)
451
452
def get_parameters(self, first, last):
453
keywords = []
454
args = []
455
current = self._find_last_non_space_char(last - 1)
456
while current > first:
457
primary_start = current
458
current = self._find_primary_start(current)
459
while current != first and self.code[current] not in '=,':
460
current = self._find_last_non_space_char(current - 1)
461
primary = self.raw[current + 1:primary_start + 1].strip()
462
if self.code[current] == '=':
463
primary_start = current - 1
464
current -= 1
465
while current != first and self.code[current] not in ',':
466
current = self._find_last_non_space_char(current - 1)
467
param_name = self.raw[current + 1:primary_start + 1].strip()
468
keywords.append((param_name, primary))
469
else:
470
args.append(primary)
471
current = self._find_last_non_space_char(current - 1)
472
args.reverse()
473
keywords.reverse()
474
return args, keywords
475
476
def is_assigned_in_a_tuple_assignment(self, offset):
477
start = self._get_line_start(offset)
478
end = self._get_line_end(offset)
479
primary_start = self._find_primary_start(offset)
480
primary_end = self._find_word_end(offset)
481
482
prev_char_offset = self._find_last_non_space_char(primary_start - 1)
483
next_char_offset = self._find_first_non_space_char(primary_end + 1)
484
next_char = prev_char = ''
485
if prev_char_offset >= start:
486
prev_char = self.code[prev_char_offset]
487
if next_char_offset < end:
488
next_char = self.code[next_char_offset]
489
try:
490
equals_offset = self.code.index('=', start, end)
491
except ValueError:
492
return False
493
if prev_char not in '(,' and next_char not in ',)':
494
return False
495
parens_start = self.find_parens_start_from_inside(offset)
496
# XXX: only handling (x, y) = value
497
return offset < equals_offset and \
498
self.code[start:parens_start].strip() == ''
499
500
def get_function_and_args_in_header(self, offset):
501
offset = self.find_function_offset(offset)
502
lparens, rparens = self.get_word_parens_range(offset)
503
return self.raw[offset:rparens + 1]
504
505
def find_function_offset(self, offset, definition='def '):
506
while True:
507
offset = self.code.index(definition, offset)
508
if offset == 0 or not self._is_id_char(offset - 1):
509
break
510
offset += 1
511
def_ = offset + 4
512
return self._find_first_non_space_char(def_)
513
514
def get_lambda_and_args(self, offset):
515
offset = self.find_function_offset(offset, definition = 'lambda ')
516
lparens, rparens = self.get_word_parens_range(offset, opening=' ', closing=':')
517
return self.raw[offset:rparens + 1]
518
519
520