importreclassWorder(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. """# XXX: some of these methods fail on badly formatted or less# common code; see disabled testcases for some of themdef__init__(self,source_code):importrope.base.simplifyself.raw=source_codeself.code=rope.base.simplify.real_code(source_code)def_find_word_start(self,offset):current_offset=offsetwhilecurrent_offset>=0andself._is_id_char(current_offset):current_offset-=1returncurrent_offset+1def_find_word_end(self,offset):whileoffset+1<len(self.code)andself._is_id_char(offset+1):offset+=1returnoffset_char_pat=re.compile(r'[\'"#]')def_find_last_non_space_char(self,offset):ifoffset<=0:return0whileoffset>=0andself.code[offset].isspace():ifself.code[offset]=='\n':ifoffset>0andself.code[offset-1]=='\\':offset-=1try:start=self.code.rindex('\n',0,offset)exceptValueError:start=0match=self._char_pat.search(self.code[start:offset])ifmatchandmatch.group()=='#':offset=self.code.rindex('#',start,offset)offset-=1returnoffsetdefget_word_at(self,offset):offset=self._get_fixed_offset(offset)returnself.raw[self._find_word_start(offset):self._find_word_end(offset)+1]def_get_fixed_offset(self,offset):ifoffset>=len(self.code):returnoffset-1ifnotself._is_id_char(offset):ifoffset>0andself._is_id_char(offset-1):returnoffset-1ifoffset<len(self.code)-1andself._is_id_char(offset+1):returnoffset+1returnoffsetdef_is_id_char(self,offset):returnself.code[offset].isalnum()orself.code[offset]=='_'def_find_string_start(self,offset):kind=self.code[offset]offset-=1whileTrue:try:offset=self.code.rindex(kind,0,offset)ifoffset==0orself.code[offset-1]!='\\':returnoffsetoffset-=1exceptValueError:return0def_find_parens_start(self,offset):offset=self._find_last_non_space_char(offset-1)whileoffset>=0andself.code[offset]notin'[({':ifself.code[offset]notin':,':offset=self._find_primary_start(offset)offset=self._find_last_non_space_char(offset-1)returnoffsetdef_find_atom_start(self,offset):old_offset=offsetifself.code[offset]in'\n\t ':offset=self._find_last_non_space_char(offset)ifself.code[offset]in'\'"':returnself._find_string_start(offset)ifself.code[offset]in')]}':returnself._find_parens_start(offset)ifself._is_id_char(offset):returnself._find_word_start(offset)returnold_offsetdef_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=offsetoffset=self._find_last_non_space_char(last_atom)whileoffset>0andself.code[offset]in')]':last_atom=self._find_parens_start(offset)offset=self._find_last_non_space_char(last_atom-1)ifoffset>=0and(self.code[offset]in'"\'})]'orself._is_id_char(offset)):returnself._find_atom_start(offset)returnlast_atomdef_find_primary_start(self,offset):ifoffset>=len(self.code):offset=len(self.code)-1ifself.code[offset]!='.':offset=self._find_primary_without_dot_start(offset)else:offset=offset+1whileoffset>0:prev=self._find_last_non_space_char(offset-1)ifoffset<=0orself.code[prev]!='.':breakoffset=self._find_primary_without_dot_start(prev-1)ifnotself._is_id_char(offset):breakreturnoffsetdefget_primary_at(self,offset):offset=self._get_fixed_offset(offset)start,end=self.get_primary_range(offset)returnself.raw[start:end].strip()defget_splitted_primary_before(self,offset):"""returns expression, starting, starting_offset This function is used in `rope.codeassist.assist` function. """ifoffset==0:return('','',0)end=offset-1word_start=self._find_atom_start(end)real_start=self._find_primary_start(end)ifself.code[word_start:offset].strip()=='':word_start=endifself.code[end].isspace():word_start=endifself.code[real_start:word_start].strip()=='':real_start=word_startifreal_start==word_start==endandnotself._is_id_char(end):return('','',offset)ifreal_start==word_start:return('',self.raw[word_start:offset],word_start)else:ifself.code[end]=='.':return(self.raw[real_start:end],'',offset)last_dot_position=word_startifself.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)ifself.code[word_start].isspace():word_start=offsetreturn(self.raw[real_start:last_char_position+1],self.raw[word_start:offset],word_start)def_get_line_start(self,offset):try:returnself.code.rindex('\n',0,offset+1)exceptValueError:return0def_get_line_end(self,offset):try:returnself.code.index('\n',offset)exceptValueError:returnlen(self.code)def_is_followed_by_equals(self,offset):whileoffset<len(self.code)andself.code[offset]in' \\\t':ifself.code[offset]=='\\':offset+=1offset+=1ifoffset+1<len(self.code)and \
self.code[offset]=='='andself.code[offset+1]!='=':returnTruereturnFalsedef_is_name_assigned_in_class_body(self,offset):word_start=self._find_word_start(offset-1)word_end=self._find_word_end(offset)+1if'.'inself.code[word_start:word_end]:returnFalseline_start=self._get_line_start(word_start)line=self.code[line_start:word_start].strip()ifnotlineandself._is_followed_by_equals(word_end):returnTruereturnFalsedefis_a_class_or_function_name_in_header(self,offset):# XXX: does not handle line breaks after defword_start=self._find_word_start(offset-1)line_start=self._get_line_start(word_start)prev_word=self.code[line_start:word_start].strip()returnprev_wordin['def','class']def_find_first_non_space_char(self,offset):ifoffset>=len(self.code):returnlen(self.code)whileoffset<len(self.code):ifoffset+1<len(self.code)and \
self.code[offset]=='\\':offset+=2elifself.code[offset]in' \t\n':offset+=1else:breakreturnoffsetdefis_a_function_being_called(self,offset):word_end=self._find_word_end(offset)+1next_char=self._find_first_non_space_char(word_end)returnnext_char<len(self.code)and \
self.code[next_char]=='('and \
notself.is_a_class_or_function_name_in_header(offset)def_find_import_pair_end(self,start):next_char=self._find_first_non_space_char(start)ifnext_char>=len(self.code):returnlen(self.code)ifself.code[next_char]=='(':try:returnself.code.index(')',next_char)+1exceptValueError:returnSyntaxError('Unmatched Parens')else:offset=next_charwhileTrue:try:offset=self.code.index('\n',offset)ifoffset==0orself.code[offset-1]!='\\':returnoffsetoffset+=1exceptValueError:returnlen(self.code)defis_import_statement(self,offset):try:last_import=self.code.rindex('import ',0,offset)exceptValueError:returnFalsereturnself._find_import_pair_end(last_import+7)>=offsetdefis_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+8exceptValueError:returnFalsefrom_names=self._find_first_non_space_char(from_names)returnself._find_import_pair_end(from_names)>=offsetdefis_from_statement_module(self,offset):ifoffset>=len(self.code)-1:returnFalsestmt_start=self._find_primary_start(offset)line_start=self._get_line_start(stmt_start)prev_word=self.code[line_start:stmt_start].strip()returnprev_word=='from'defis_a_name_after_from_import(self,offset):try:# XXX: what if the char after from or around import is not# space?last_from=self.code.rindex('from ',0,offset)from_import=self.code.index(' import ',last_from)from_names=from_import+8exceptValueError:returnFalseiffrom_names-1>offset:returnFalsereturnself._find_import_pair_end(from_names)>=offsetdefget_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)returnself.get_primary_at(end)exceptValueError:passdefis_from_aliased(self,offset):ifnotself.is_a_name_after_from_import(offset):returnFalsetry: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)ifself.code[as_start:as_end+1]=='as':returnTrueexceptValueError:returnFalsedefget_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)returnself.raw[start:alias+1]exceptValueError:passdefis_function_keyword_parameter(self,offset):word_end=self._find_word_end(offset)ifword_end+1==len(self.code):returnFalsenext_char=self._find_first_non_space_char(word_end+1)ifnext_char+2>=len(self.code)or \
self.code[next_char]!='='or \
self.code[next_char+1]=='=':returnFalseword_start=self._find_word_start(offset)prev_char=self._find_last_non_space_char(word_start-1)ifprev_char-1<0orself.code[prev_char]notin',(':returnFalsereturnTruedefis_on_function_call_keyword(self,offset,stop_searching=0):ifself._is_id_char(offset):offset=self._find_word_start(offset)-1offset=self._find_last_non_space_char(offset)ifoffset<=stop_searchingor \
self.code[offset]notin'(,':returnFalseparens_start=self.find_parens_start_from_inside(offset,stop_searching)ifstop_searching<parens_start:returnTruereturnFalsedeffind_parens_start_from_inside(self,offset,stop_searching=0):opens=1whileoffset>stop_searching:ifself.code[offset]=='(':breakifself.code[offset]!=',':offset=self._find_primary_start(offset)offset-=1returnmax(stop_searching,offset)defis_assigned_here(self,offset):operation=self.get_assignment_type(offset)operations=('=','-=','+=','*=','/=','%=','**=','>>=','<<=','&=','^=','|=')returnoperationinoperationsdefget_assignment_type(self,offset):word_end=self._find_word_end(offset)next_char=self._find_first_non_space_char(word_end+1)current_char=next_charwhilecurrent_char+1<len(self.code)and \
(self.code[current_char]!='='or \
self.code[current_char+1]=='=')and \
current_char<next_char+3:current_char+=1operation=self.code[next_char:current_char+1]returnoperationdefget_primary_range(self,offset):start=self._find_primary_start(offset)end=self._find_word_end(offset)+1return(start,end)defget_word_range(self,offset):offset=max(0,offset)start=self._find_word_start(offset)end=self._find_word_end(offset)+1return(start,end)defget_word_parens_range(self,offset):ifself.is_a_function_being_called(offset)or \
self.is_a_class_or_function_name_in_header(offset):end=self._find_word_end(offset)start_parens=self.code.index('(',end)index=start_parensopen_count=0whileindex<len(self.code):ifself.code[index]=='(':open_count+=1ifself.code[index]==')':open_count-=1ifopen_count==0:return(start_parens,index+1)index+=1return(start_parens,index)return(None,None)