Path: blob/master/elisp/emacs-for-python/rope-dist/rope/base/worder.py
1421 views
import bisect12import rope.base.simplify345def get_name_at(resource, offset):6source_code = resource.read()7word_finder = Worder(source_code)8return word_finder.get_word_at(offset)91011class Worder(object):12"""A class for finding boundaries of words and expressions1314Note that in these methods, offset should be the index of the15character not the index of the character after it.16"""1718def __init__(self, code, handle_ignores=False):19simplified = rope.base.simplify.real_code(code)20self.code_finder = _RealFinder(simplified, code)21self.handle_ignores = handle_ignores22self.code = code2324def _init_ignores(self):25ignores = rope.base.simplify.ignored_regions(self.code)26self.dumb_finder = _RealFinder(self.code, self.code)27self.starts = [ignored[0] for ignored in ignores]28self.ends = [ignored[1] for ignored in ignores]2930def _context_call(self, name, offset):31if self.handle_ignores:32if not hasattr(self, 'starts'):33self._init_ignores()34start = bisect.bisect(self.starts, offset)35if start > 0 and offset < self.ends[start - 1]:36return getattr(self.dumb_finder, name)(offset)37return getattr(self.code_finder, name)(offset)3839def get_primary_at(self, offset):40return self._context_call('get_primary_at', offset)4142def get_word_at(self, offset):43return self._context_call('get_word_at', offset)4445def get_primary_range(self, offset):46return self._context_call('get_primary_range', offset)4748def get_splitted_primary_before(self, offset):49return self._context_call('get_splitted_primary_before', offset)5051def get_word_range(self, offset):52return self._context_call('get_word_range', offset)5354def is_function_keyword_parameter(self, offset):55return self.code_finder.is_function_keyword_parameter(offset)5657def is_a_class_or_function_name_in_header(self, offset):58return self.code_finder.is_a_class_or_function_name_in_header(offset)5960def is_from_statement_module(self, offset):61return self.code_finder.is_from_statement_module(offset)6263def is_from_aliased(self, offset):64return self.code_finder.is_from_aliased(offset)6566def find_parens_start_from_inside(self, offset):67return self.code_finder.find_parens_start_from_inside(offset)6869def is_a_name_after_from_import(self, offset):70return self.code_finder.is_a_name_after_from_import(offset)7172def is_from_statement(self, offset):73return self.code_finder.is_from_statement(offset)7475def get_from_aliased(self, offset):76return self.code_finder.get_from_aliased(offset)7778def is_import_statement(self, offset):79return self.code_finder.is_import_statement(offset)8081def is_assigned_here(self, offset):82return self.code_finder.is_assigned_here(offset)8384def is_a_function_being_called(self, offset):85return self.code_finder.is_a_function_being_called(offset)8687def get_word_parens_range(self, offset):88return self.code_finder.get_word_parens_range(offset)8990def is_name_assigned_in_class_body(self, offset):91return self.code_finder.is_name_assigned_in_class_body(offset)9293def is_on_function_call_keyword(self, offset):94return self.code_finder.is_on_function_call_keyword(offset)9596def _find_parens_start(self, offset):97return self.code_finder._find_parens_start(offset)9899def get_parameters(self, first, last):100return self.code_finder.get_parameters(first, last)101102def get_from_module(self, offset):103return self.code_finder.get_from_module(offset)104105def is_assigned_in_a_tuple_assignment(self, offset):106return self.code_finder.is_assigned_in_a_tuple_assignment(offset)107108def get_assignment_type(self, offset):109return self.code_finder.get_assignment_type(offset)110111def get_function_and_args_in_header(self, offset):112return self.code_finder.get_function_and_args_in_header(offset)113114def get_lambda_and_args(self, offset):115return self.code_finder.get_lambda_and_args(offset)116117def find_function_offset(self, offset):118return self.code_finder.find_function_offset(offset)119120121class _RealFinder(object):122123def __init__(self, code, raw):124self.code = code125self.raw = raw126127def _find_word_start(self, offset):128current_offset = offset129while current_offset >= 0 and self._is_id_char(current_offset):130current_offset -= 1131return current_offset + 1132133def _find_word_end(self, offset):134while offset + 1 < len(self.code) and self._is_id_char(offset + 1):135offset += 1136return offset137138def _find_last_non_space_char(self, offset):139while offset >= 0 and self.code[offset].isspace():140if self.code[offset] == '\n':141return offset142offset -= 1143return max(-1, offset)144145def get_word_at(self, offset):146offset = self._get_fixed_offset(offset)147return self.raw[self._find_word_start(offset):148self._find_word_end(offset) + 1]149150def _get_fixed_offset(self, offset):151if offset >= len(self.code):152return offset - 1153if not self._is_id_char(offset):154if offset > 0 and self._is_id_char(offset - 1):155return offset - 1156if offset < len(self.code) - 1 and self._is_id_char(offset + 1):157return offset + 1158return offset159160def _is_id_char(self, offset):161return self.code[offset].isalnum() or self.code[offset] == '_'162163def _find_string_start(self, offset):164kind = self.code[offset]165try:166return self.code.rindex(kind, 0, offset)167except ValueError:168return 0169170def _find_parens_start(self, offset):171offset = self._find_last_non_space_char(offset - 1)172while offset >= 0 and self.code[offset] not in '[({':173if self.code[offset] not in ':,':174offset = self._find_primary_start(offset)175offset = self._find_last_non_space_char(offset - 1)176return offset177178def _find_atom_start(self, offset):179old_offset = offset180if self.code[offset] == '\n':181return offset + 1182if self.code[offset].isspace():183offset = self._find_last_non_space_char(offset)184if self.code[offset] in '\'"':185return self._find_string_start(offset)186if self.code[offset] in ')]}':187return self._find_parens_start(offset)188if self._is_id_char(offset):189return self._find_word_start(offset)190return old_offset191192def _find_primary_without_dot_start(self, offset):193"""It tries to find the undotted primary start194195It is different from `self._get_atom_start()` in that it196follows function calls, too; such as in ``f(x)``.197198"""199last_atom = offset200offset = self._find_last_non_space_char(last_atom)201while offset > 0 and self.code[offset] in ')]':202last_atom = self._find_parens_start(offset)203offset = self._find_last_non_space_char(last_atom - 1)204if offset >= 0 and (self.code[offset] in '"\'})]' or205self._is_id_char(offset)):206return self._find_atom_start(offset)207return last_atom208209def _find_primary_start(self, offset):210if offset >= len(self.code):211offset = len(self.code) - 1212if self.code[offset] != '.':213offset = self._find_primary_without_dot_start(offset)214else:215offset = offset + 1216while offset > 0:217prev = self._find_last_non_space_char(offset - 1)218if offset <= 0 or self.code[prev] != '.':219break220offset = self._find_primary_without_dot_start(prev - 1)221if not self._is_id_char(offset):222break223224return offset225226def get_primary_at(self, offset):227offset = self._get_fixed_offset(offset)228start, end = self.get_primary_range(offset)229return self.raw[start:end].strip()230231def get_splitted_primary_before(self, offset):232"""returns expression, starting, starting_offset233234This function is used in `rope.codeassist.assist` function.235"""236if offset == 0:237return ('', '', 0)238end = offset - 1239word_start = self._find_atom_start(end)240real_start = self._find_primary_start(end)241if self.code[word_start:offset].strip() == '':242word_start = end243if self.code[end].isspace():244word_start = end245if self.code[real_start:word_start].strip() == '':246real_start = word_start247if real_start == word_start == end and not self._is_id_char(end):248return ('', '', offset)249if real_start == word_start:250return ('', self.raw[word_start:offset], word_start)251else:252if self.code[end] == '.':253return (self.raw[real_start:end], '', offset)254last_dot_position = word_start255if self.code[word_start] != '.':256last_dot_position = self._find_last_non_space_char(word_start - 1)257last_char_position = self._find_last_non_space_char(last_dot_position - 1)258if self.code[word_start].isspace():259word_start = offset260return (self.raw[real_start:last_char_position + 1],261self.raw[word_start:offset], word_start)262263def _get_line_start(self, offset):264try:265return self.code.rindex('\n', 0, offset + 1)266except ValueError:267return 0268269def _get_line_end(self, offset):270try:271return self.code.index('\n', offset)272except ValueError:273return len(self.code)274275def is_name_assigned_in_class_body(self, offset):276word_start = self._find_word_start(offset - 1)277word_end = self._find_word_end(offset) + 1278if '.' in self.code[word_start:word_end]:279return False280line_start = self._get_line_start(word_start)281line = self.code[line_start:word_start].strip()282return not line and self.get_assignment_type(offset) == '='283284def is_a_class_or_function_name_in_header(self, offset):285word_start = self._find_word_start(offset - 1)286line_start = self._get_line_start(word_start)287prev_word = self.code[line_start:word_start].strip()288return prev_word in ['def', 'class']289290def _find_first_non_space_char(self, offset):291if offset >= len(self.code):292return len(self.code)293while offset < len(self.code) and self.code[offset].isspace():294if self.code[offset] == '\n':295return offset296offset += 1297return offset298299def is_a_function_being_called(self, offset):300word_end = self._find_word_end(offset) + 1301next_char = self._find_first_non_space_char(word_end)302return next_char < len(self.code) and \303self.code[next_char] == '(' and \304not self.is_a_class_or_function_name_in_header(offset)305306def _find_import_end(self, start):307return self._get_line_end(start)308309def is_import_statement(self, offset):310try:311last_import = self.code.rindex('import ', 0, offset)312except ValueError:313return False314return self._find_import_end(last_import + 7) >= offset315316def is_from_statement(self, offset):317try:318last_from = self.code.rindex('from ', 0, offset)319from_import = self.code.index(' import ', last_from)320from_names = from_import + 8321except ValueError:322return False323from_names = self._find_first_non_space_char(from_names)324return self._find_import_end(from_names) >= offset325326def is_from_statement_module(self, offset):327if offset >= len(self.code) - 1:328return False329stmt_start = self._find_primary_start(offset)330line_start = self._get_line_start(stmt_start)331prev_word = self.code[line_start:stmt_start].strip()332return prev_word == 'from'333334def is_a_name_after_from_import(self, offset):335try:336line_start = self._get_line_start(offset)337last_from = self.code.rindex('from ', line_start, offset)338from_import = self.code.index(' import ', last_from)339from_names = from_import + 8340except ValueError:341return False342if from_names - 1 > offset:343return False344return self._find_import_end(from_names) >= offset345346def get_from_module(self, offset):347try:348last_from = self.code.rindex('from ', 0, offset)349import_offset = self.code.index(' import ', last_from)350end = self._find_last_non_space_char(import_offset)351return self.get_primary_at(end)352except ValueError:353pass354355def is_from_aliased(self, offset):356if not self.is_a_name_after_from_import(offset):357return False358try:359end = self._find_word_end(offset)360as_end = min(self._find_word_end(end + 1), len(self.code))361as_start = self._find_word_start(as_end)362if self.code[as_start:as_end + 1] == 'as':363return True364except ValueError:365return False366367def get_from_aliased(self, offset):368try:369end = self._find_word_end(offset)370as_ = self._find_word_end(end + 1)371alias = self._find_word_end(as_ + 1)372start = self._find_word_start(alias)373return self.raw[start:alias + 1]374except ValueError:375pass376377def is_function_keyword_parameter(self, offset):378word_end = self._find_word_end(offset)379if word_end + 1 == len(self.code):380return False381next_char = self._find_first_non_space_char(word_end + 1)382equals = self.code[next_char:next_char + 2]383if equals == '==' or not equals.startswith('='):384return False385word_start = self._find_word_start(offset)386prev_char = self._find_last_non_space_char(word_start - 1)387return prev_char - 1 >= 0 and self.code[prev_char] in ',('388389def is_on_function_call_keyword(self, offset):390stop = self._get_line_start(offset)391if self._is_id_char(offset):392offset = self._find_word_start(offset) - 1393offset = self._find_last_non_space_char(offset)394if offset <= stop or self.code[offset] not in '(,':395return False396parens_start = self.find_parens_start_from_inside(offset)397return stop < parens_start398399def find_parens_start_from_inside(self, offset):400stop = self._get_line_start(offset)401opens = 1402while offset > stop:403if self.code[offset] == '(':404break405if self.code[offset] != ',':406offset = self._find_primary_start(offset)407offset -= 1408return max(stop, offset)409410def is_assigned_here(self, offset):411return self.get_assignment_type(offset) is not None412413def get_assignment_type(self, offset):414# XXX: does not handle tuple assignments415word_end = self._find_word_end(offset)416next_char = self._find_first_non_space_char(word_end + 1)417single = self.code[next_char:next_char + 1]418double = self.code[next_char:next_char + 2]419triple = self.code[next_char:next_char + 3]420if double not in ('==', '<=', '>=', '!='):421for op in [single, double, triple]:422if op.endswith('='):423return op424425def get_primary_range(self, offset):426start = self._find_primary_start(offset)427end = self._find_word_end(offset) + 1428return (start, end)429430def get_word_range(self, offset):431offset = max(0, offset)432start = self._find_word_start(offset)433end = self._find_word_end(offset) + 1434return (start, end)435436def get_word_parens_range(self, offset, opening='(', closing=')'):437end = self._find_word_end(offset)438start_parens = self.code.index(opening, end)439index = start_parens440open_count = 0441while index < len(self.code):442if self.code[index] == opening:443open_count += 1444if self.code[index] == closing:445open_count -= 1446if open_count == 0:447return (start_parens, index + 1)448index += 1449return (start_parens, index)450451def get_parameters(self, first, last):452keywords = []453args = []454current = self._find_last_non_space_char(last - 1)455while current > first:456primary_start = current457current = self._find_primary_start(current)458while current != first and self.code[current] not in '=,':459current = self._find_last_non_space_char(current - 1)460primary = self.raw[current + 1:primary_start + 1].strip()461if self.code[current] == '=':462primary_start = current - 1463current -= 1464while current != first and self.code[current] not in ',':465current = self._find_last_non_space_char(current - 1)466param_name = self.raw[current + 1:primary_start + 1].strip()467keywords.append((param_name, primary))468else:469args.append(primary)470current = self._find_last_non_space_char(current - 1)471args.reverse()472keywords.reverse()473return args, keywords474475def is_assigned_in_a_tuple_assignment(self, offset):476start = self._get_line_start(offset)477end = self._get_line_end(offset)478primary_start = self._find_primary_start(offset)479primary_end = self._find_word_end(offset)480481prev_char_offset = self._find_last_non_space_char(primary_start - 1)482next_char_offset = self._find_first_non_space_char(primary_end + 1)483next_char = prev_char = ''484if prev_char_offset >= start:485prev_char = self.code[prev_char_offset]486if next_char_offset < end:487next_char = self.code[next_char_offset]488try:489equals_offset = self.code.index('=', start, end)490except ValueError:491return False492if prev_char not in '(,' and next_char not in ',)':493return False494parens_start = self.find_parens_start_from_inside(offset)495# XXX: only handling (x, y) = value496return offset < equals_offset and \497self.code[start:parens_start].strip() == ''498499def get_function_and_args_in_header(self, offset):500offset = self.find_function_offset(offset)501lparens, rparens = self.get_word_parens_range(offset)502return self.raw[offset:rparens + 1]503504def find_function_offset(self, offset, definition='def '):505while True:506offset = self.code.index(definition, offset)507if offset == 0 or not self._is_id_char(offset - 1):508break509offset += 1510def_ = offset + 4511return self._find_first_non_space_char(def_)512513def get_lambda_and_args(self, offset):514offset = self.find_function_offset(offset, definition = 'lambda ')515lparens, rparens = self.get_word_parens_range(offset, opening=' ', closing=':')516return self.raw[offset:rparens + 1]517518519520