| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- import bisect
- import keyword
- import rope.base.simplify
- def get_name_at(resource, offset):
- source_code = resource.read()
- word_finder = Worder(source_code)
- return word_finder.get_word_at(offset)
- class Worder(object):
- """A class for finding boundaries of words and expressions
- Note that in these methods, offset should be the index of the
- character not the index of the character after it.
- """
- def __init__(self, code, handle_ignores=False):
- simplified = rope.base.simplify.real_code(code)
- self.code_finder = _RealFinder(simplified, code)
- self.handle_ignores = handle_ignores
- self.code = code
- def _init_ignores(self):
- ignores = rope.base.simplify.ignored_regions(self.code)
- self.dumb_finder = _RealFinder(self.code, self.code)
- self.starts = [ignored[0] for ignored in ignores]
- self.ends = [ignored[1] for ignored in ignores]
- def _context_call(self, name, offset):
- if self.handle_ignores:
- if not hasattr(self, 'starts'):
- self._init_ignores()
- start = bisect.bisect(self.starts, offset)
- if start > 0 and offset < self.ends[start - 1]:
- return getattr(self.dumb_finder, name)(offset)
- return getattr(self.code_finder, name)(offset)
- def get_primary_at(self, offset):
- return self._context_call('get_primary_at', offset)
- def get_word_at(self, offset):
- return self._context_call('get_word_at', offset)
- def get_primary_range(self, offset):
- return self._context_call('get_primary_range', offset)
- def get_splitted_primary_before(self, offset):
- return self._context_call('get_splitted_primary_before', offset)
- def get_word_range(self, offset):
- return self._context_call('get_word_range', offset)
- def is_function_keyword_parameter(self, offset):
- return self.code_finder.is_function_keyword_parameter(offset)
- def is_a_class_or_function_name_in_header(self, offset):
- return self.code_finder.is_a_class_or_function_name_in_header(offset)
- def is_from_statement_module(self, offset):
- return self.code_finder.is_from_statement_module(offset)
- def is_from_aliased(self, offset):
- return self.code_finder.is_from_aliased(offset)
- def find_parens_start_from_inside(self, offset):
- return self.code_finder.find_parens_start_from_inside(offset)
- def is_a_name_after_from_import(self, offset):
- return self.code_finder.is_a_name_after_from_import(offset)
- def is_from_statement(self, offset):
- return self.code_finder.is_from_statement(offset)
- def get_from_aliased(self, offset):
- return self.code_finder.get_from_aliased(offset)
- def is_import_statement(self, offset):
- return self.code_finder.is_import_statement(offset)
- def is_assigned_here(self, offset):
- return self.code_finder.is_assigned_here(offset)
- def is_a_function_being_called(self, offset):
- return self.code_finder.is_a_function_being_called(offset)
- def get_word_parens_range(self, offset):
- return self.code_finder.get_word_parens_range(offset)
- def is_name_assigned_in_class_body(self, offset):
- return self.code_finder.is_name_assigned_in_class_body(offset)
- def is_on_function_call_keyword(self, offset):
- return self.code_finder.is_on_function_call_keyword(offset)
- def _find_parens_start(self, offset):
- return self.code_finder._find_parens_start(offset)
- def get_parameters(self, first, last):
- return self.code_finder.get_parameters(first, last)
- def get_from_module(self, offset):
- return self.code_finder.get_from_module(offset)
- def is_assigned_in_a_tuple_assignment(self, offset):
- return self.code_finder.is_assigned_in_a_tuple_assignment(offset)
- def get_assignment_type(self, offset):
- return self.code_finder.get_assignment_type(offset)
- def get_function_and_args_in_header(self, offset):
- return self.code_finder.get_function_and_args_in_header(offset)
- def get_lambda_and_args(self, offset):
- return self.code_finder.get_lambda_and_args(offset)
- def find_function_offset(self, offset):
- return self.code_finder.find_function_offset(offset)
- class _RealFinder(object):
- def __init__(self, code, raw):
- self.code = code
- self.raw = raw
- def _find_word_start(self, offset):
- current_offset = offset
- while current_offset >= 0 and self._is_id_char(current_offset):
- current_offset -= 1
- return current_offset + 1
- def _find_word_end(self, offset):
- while offset + 1 < len(self.code) and self._is_id_char(offset + 1):
- offset += 1
- return offset
- def _find_last_non_space_char(self, offset):
- while offset >= 0 and self.code[offset].isspace():
- if self.code[offset] == '\n':
- return offset
- offset -= 1
- return max(-1, offset)
- def get_word_at(self, offset):
- offset = self._get_fixed_offset(offset)
- return self.raw[self._find_word_start(offset):
- self._find_word_end(offset) + 1]
- def _get_fixed_offset(self, offset):
- if offset >= len(self.code):
- return offset - 1
- if not self._is_id_char(offset):
- if offset > 0 and self._is_id_char(offset - 1):
- return offset - 1
- if offset < len(self.code) - 1 and self._is_id_char(offset + 1):
- return offset + 1
- return offset
- def _is_id_char(self, offset):
- return self.code[offset].isalnum() or self.code[offset] == '_'
- def _find_string_start(self, offset):
- kind = self.code[offset]
- try:
- return self.code.rindex(kind, 0, offset)
- except ValueError:
- return 0
- def _find_parens_start(self, offset):
- offset = self._find_last_non_space_char(offset - 1)
- while offset >= 0 and self.code[offset] not in '[({':
- if self.code[offset] not in ':,':
- offset = self._find_primary_start(offset)
- offset = self._find_last_non_space_char(offset - 1)
- return offset
- def _find_atom_start(self, offset):
- old_offset = offset
- if self.code[offset] == '\n':
- return offset + 1
- if self.code[offset].isspace():
- offset = self._find_last_non_space_char(offset)
- if self.code[offset] in '\'"':
- return self._find_string_start(offset)
- if self.code[offset] in ')]}':
- return self._find_parens_start(offset)
- if self._is_id_char(offset):
- return self._find_word_start(offset)
- return old_offset
- def _find_primary_without_dot_start(self, offset):
- """It tries to find the undotted primary start
- It is different from `self._get_atom_start()` in that it
- follows function calls, too; such as in ``f(x)``.
- """
- last_atom = offset
- offset = self._find_last_non_space_char(last_atom)
- while offset > 0 and self.code[offset] in ')]':
- last_atom = self._find_parens_start(offset)
- offset = self._find_last_non_space_char(last_atom - 1)
- if offset >= 0 and (self.code[offset] in '"\'})]' or
- self._is_id_char(offset)):
- atom_start = self._find_atom_start(offset)
- if not keyword.iskeyword(self.code[atom_start:offset + 1]):
- return atom_start
- return last_atom
- def _find_primary_start(self, offset):
- if offset >= len(self.code):
- offset = len(self.code) - 1
- if self.code[offset] != '.':
- offset = self._find_primary_without_dot_start(offset)
- else:
- offset = offset + 1
- while offset > 0:
- prev = self._find_last_non_space_char(offset - 1)
- if offset <= 0 or self.code[prev] != '.':
- break
- offset = self._find_primary_without_dot_start(prev - 1)
- if not self._is_id_char(offset):
- break
- return offset
- def get_primary_at(self, offset):
- offset = self._get_fixed_offset(offset)
- start, end = self.get_primary_range(offset)
- return self.raw[start:end].strip()
- def get_splitted_primary_before(self, offset):
- """returns expression, starting, starting_offset
- This function is used in `rope.codeassist.assist` function.
- """
- if offset == 0:
- return ('', '', 0)
- end = offset - 1
- word_start = self._find_atom_start(end)
- real_start = self._find_primary_start(end)
- if self.code[word_start:offset].strip() == '':
- word_start = end
- if self.code[end].isspace():
- word_start = end
- if self.code[real_start:word_start].strip() == '':
- real_start = word_start
- if real_start == word_start == end and not self._is_id_char(end):
- return ('', '', offset)
- if real_start == word_start:
- return ('', self.raw[word_start:offset], word_start)
- else:
- if self.code[end] == '.':
- return (self.raw[real_start:end], '', offset)
- last_dot_position = word_start
- if self.code[word_start] != '.':
- last_dot_position = self._find_last_non_space_char(word_start - 1)
- last_char_position = self._find_last_non_space_char(last_dot_position - 1)
- if self.code[word_start].isspace():
- word_start = offset
- return (self.raw[real_start:last_char_position + 1],
- self.raw[word_start:offset], word_start)
- def _get_line_start(self, offset):
- try:
- return self.code.rindex('\n', 0, offset + 1)
- except ValueError:
- return 0
- def _get_line_end(self, offset):
- try:
- return self.code.index('\n', offset)
- except ValueError:
- return len(self.code)
- def is_name_assigned_in_class_body(self, offset):
- word_start = self._find_word_start(offset - 1)
- word_end = self._find_word_end(offset) + 1
- if '.' in self.code[word_start:word_end]:
- return False
- line_start = self._get_line_start(word_start)
- line = self.code[line_start:word_start].strip()
- return not line and self.get_assignment_type(offset) == '='
- def is_a_class_or_function_name_in_header(self, offset):
- word_start = self._find_word_start(offset - 1)
- line_start = self._get_line_start(word_start)
- prev_word = self.code[line_start:word_start].strip()
- return prev_word in ['def', 'class']
- def _find_first_non_space_char(self, offset):
- if offset >= len(self.code):
- return len(self.code)
- while offset < len(self.code) and self.code[offset].isspace():
- if self.code[offset] == '\n':
- return offset
- offset += 1
- return offset
- def is_a_function_being_called(self, offset):
- word_end = self._find_word_end(offset) + 1
- next_char = self._find_first_non_space_char(word_end)
- return next_char < len(self.code) and \
- self.code[next_char] == '(' and \
- not self.is_a_class_or_function_name_in_header(offset)
- def _find_import_end(self, start):
- return self._get_line_end(start)
- def is_import_statement(self, offset):
- try:
- last_import = self.code.rindex('import ', 0, offset)
- except ValueError:
- return False
- return self._find_import_end(last_import + 7) >= offset
- def is_from_statement(self, offset):
- try:
- last_from = self.code.rindex('from ', 0, offset)
- from_import = self.code.index(' import ', last_from)
- from_names = from_import + 8
- except ValueError:
- return False
- from_names = self._find_first_non_space_char(from_names)
- return self._find_import_end(from_names) >= offset
- def is_from_statement_module(self, offset):
- if offset >= len(self.code) - 1:
- return False
- stmt_start = self._find_primary_start(offset)
- line_start = self._get_line_start(stmt_start)
- prev_word = self.code[line_start:stmt_start].strip()
- return prev_word == 'from'
- def is_a_name_after_from_import(self, offset):
- try:
- if len(self.code) > offset and self.code[offset] == '\n':
- line_start = self._get_line_start(offset - 1)
- else:
- line_start = self._get_line_start(offset)
- last_from = self.code.rindex('from ', line_start, offset)
- from_import = self.code.index(' import ', last_from)
- from_names = from_import + 8
- except ValueError:
- return False
- if from_names - 1 > offset:
- return False
- return self._find_import_end(from_names) >= offset
- def get_from_module(self, offset):
- try:
- last_from = self.code.rindex('from ', 0, offset)
- import_offset = self.code.index(' import ', last_from)
- end = self._find_last_non_space_char(import_offset)
- return self.get_primary_at(end)
- except ValueError:
- pass
- def is_from_aliased(self, offset):
- if not self.is_a_name_after_from_import(offset):
- return False
- try:
- end = self._find_word_end(offset)
- as_end = min(self._find_word_end(end + 1), len(self.code))
- as_start = self._find_word_start(as_end)
- if self.code[as_start:as_end + 1] == 'as':
- return True
- except ValueError:
- return False
- def get_from_aliased(self, offset):
- try:
- end = self._find_word_end(offset)
- as_ = self._find_word_end(end + 1)
- alias = self._find_word_end(as_ + 1)
- start = self._find_word_start(alias)
- return self.raw[start:alias + 1]
- except ValueError:
- pass
- def is_function_keyword_parameter(self, offset):
- word_end = self._find_word_end(offset)
- if word_end + 1 == len(self.code):
- return False
- next_char = self._find_first_non_space_char(word_end + 1)
- equals = self.code[next_char:next_char + 2]
- if equals == '==' or not equals.startswith('='):
- return False
- word_start = self._find_word_start(offset)
- prev_char = self._find_last_non_space_char(word_start - 1)
- return prev_char - 1 >= 0 and self.code[prev_char] in ',('
- def is_on_function_call_keyword(self, offset):
- stop = self._get_line_start(offset)
- if self._is_id_char(offset):
- offset = self._find_word_start(offset) - 1
- offset = self._find_last_non_space_char(offset)
- if offset <= stop or self.code[offset] not in '(,':
- return False
- parens_start = self.find_parens_start_from_inside(offset)
- return stop < parens_start
- def find_parens_start_from_inside(self, offset):
- stop = self._get_line_start(offset)
- opens = 1
- while offset > stop:
- if self.code[offset] == '(':
- break
- if self.code[offset] != ',':
- offset = self._find_primary_start(offset)
- offset -= 1
- return max(stop, offset)
- def is_assigned_here(self, offset):
- return self.get_assignment_type(offset) is not None
- def get_assignment_type(self, offset):
- # XXX: does not handle tuple assignments
- word_end = self._find_word_end(offset)
- next_char = self._find_first_non_space_char(word_end + 1)
- single = self.code[next_char:next_char + 1]
- double = self.code[next_char:next_char + 2]
- triple = self.code[next_char:next_char + 3]
- if double not in ('==', '<=', '>=', '!='):
- for op in [single, double, triple]:
- if op.endswith('='):
- return op
- def get_primary_range(self, offset):
- start = self._find_primary_start(offset)
- end = self._find_word_end(offset) + 1
- return (start, end)
- def get_word_range(self, offset):
- offset = max(0, offset)
- start = self._find_word_start(offset)
- end = self._find_word_end(offset) + 1
- return (start, end)
- def get_word_parens_range(self, offset, opening='(', closing=')'):
- end = self._find_word_end(offset)
- start_parens = self.code.index(opening, end)
- index = start_parens
- open_count = 0
- while index < len(self.code):
- if self.code[index] == opening:
- open_count += 1
- if self.code[index] == closing:
- open_count -= 1
- if open_count == 0:
- return (start_parens, index + 1)
- index += 1
- return (start_parens, index)
- def get_parameters(self, first, last):
- keywords = []
- args = []
- current = self._find_last_non_space_char(last - 1)
- while current > first:
- primary_start = current
- current = self._find_primary_start(current)
- while current != first and self.code[current] not in '=,':
- current = self._find_last_non_space_char(current - 1)
- primary = self.raw[current + 1:primary_start + 1].strip()
- if self.code[current] == '=':
- primary_start = current - 1
- current -= 1
- while current != first and self.code[current] not in ',':
- current = self._find_last_non_space_char(current - 1)
- param_name = self.raw[current + 1:primary_start + 1].strip()
- keywords.append((param_name, primary))
- else:
- args.append(primary)
- current = self._find_last_non_space_char(current - 1)
- args.reverse()
- keywords.reverse()
- return args, keywords
- def is_assigned_in_a_tuple_assignment(self, offset):
- start = self._get_line_start(offset)
- end = self._get_line_end(offset)
- primary_start = self._find_primary_start(offset)
- primary_end = self._find_word_end(offset)
- prev_char_offset = self._find_last_non_space_char(primary_start - 1)
- next_char_offset = self._find_first_non_space_char(primary_end + 1)
- next_char = prev_char = ''
- if prev_char_offset >= start:
- prev_char = self.code[prev_char_offset]
- if next_char_offset < end:
- next_char = self.code[next_char_offset]
- try:
- equals_offset = self.code.index('=', start, end)
- except ValueError:
- return False
- if prev_char not in '(,' and next_char not in ',)':
- return False
- parens_start = self.find_parens_start_from_inside(offset)
- # XXX: only handling (x, y) = value
- return offset < equals_offset and \
- self.code[start:parens_start].strip() == ''
- def get_function_and_args_in_header(self, offset):
- offset = self.find_function_offset(offset)
- lparens, rparens = self.get_word_parens_range(offset)
- return self.raw[offset:rparens + 1]
- def find_function_offset(self, offset, definition='def '):
- while True:
- offset = self.code.index(definition, offset)
- if offset == 0 or not self._is_id_char(offset - 1):
- break
- offset += 1
- def_ = offset + 4
- return self._find_first_non_space_char(def_)
- def get_lambda_and_args(self, offset):
- offset = self.find_function_offset(offset, definition = 'lambda ')
- lparens, rparens = self.get_word_parens_range(offset, opening=' ', closing=':')
- return self.raw[offset:rparens + 1]
|