| Index: tools/telemetry/third_party/rope/rope/base/worder.py
|
| diff --git a/tools/telemetry/third_party/rope/rope/base/worder.py b/tools/telemetry/third_party/rope/rope/base/worder.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c85c6b3662833e3b138573596ef1b0563f9284a0
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/rope/rope/base/worder.py
|
| @@ -0,0 +1,525 @@
|
| +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)
|
| + 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]
|
|
|