def find_prev_lone_bracket(view, start, tags, unbalanced=0): # TODO: Extract common functionality from here and the % motion instead of duplicating code. new_start = start for i in range(unbalanced or 1): prev_opening_bracket = reverse_search_by_pt(view, tags[0], start=0, end=new_start, flags=sublime.IGNORECASE) if prev_opening_bracket is None: # Unbalanced tags; nothing we can do. return new_start = prev_opening_bracket.begin() nested = 0 while True: next_closing_bracket = reverse_search_by_pt(view, tags[1], start=prev_opening_bracket.a, end=start, flags=sublime.IGNORECASE) if not next_closing_bracket: break nested += 1 start = next_closing_bracket.begin() if nested > 0: return find_prev_lone_bracket(view, prev_opening_bracket.begin(), tags, nested) else: return prev_opening_bracket
def find_balanced_opening_bracket(self, start, brackets, unbalanced=0): new_start = start for i in range(unbalanced or 1): prev_opening_bracket = reverse_search_by_pt( self.view, brackets[0], start=0, end=new_start, flags=sublime.LITERAL ) if prev_opening_bracket is None: # Unbalanced brackets; nothing we can do. return new_start = prev_opening_bracket.begin() nested = 0 while True: next_closing_bracket = reverse_search_by_pt( self.view, brackets[1], start=prev_opening_bracket.a, end=start, flags=sublime.LITERAL ) if not next_closing_bracket: break nested += 1 start = next_closing_bracket.begin() if nested > 0: return self.find_balanced_opening_bracket(prev_opening_bracket.begin(), brackets, nested) else: return prev_opening_bracket.begin()
def find_balanced_opening_bracket(self, start, brackets, unbalanced=0): new_start = start for i in range(unbalanced or 1): prev_opening_bracket = reverse_search_by_pt(self.view, brackets[0], start=0, end=new_start, flags=sublime.LITERAL) if prev_opening_bracket is None: # Unbalanced brackets; nothing we can do. return new_start = prev_opening_bracket.begin() nested = 0 while True: next_closing_bracket = reverse_search_by_pt( self.view, brackets[1], start=prev_opening_bracket.a, end=start, flags=sublime.LITERAL) if not next_closing_bracket: break nested += 1 start = next_closing_bracket.begin() if nested > 0: return self.find_balanced_opening_bracket( prev_opening_bracket.begin(), brackets, nested) else: return prev_opening_bracket.begin()
def find_prev_lone_bracket(view, start, tags, unbalanced=0): # TODO: Extract common functionality from here and the % motion instead of # duplicating code. # XXX: refactor this if view.substr(start) == tags[0][1] if len(tags[0]) > 1 else tags[0]: if not unbalanced and view.substr(start - 1) != '\\': return sublime.Region(start, start + 1) new_start = start for i in range(unbalanced or 1): prev_opening_bracket = reverse_search_by_pt(view, tags[0], start=0, end=new_start, flags=sublime.IGNORECASE) if prev_opening_bracket is None: # Check whether the caret is exactly at a bracket. # Tag names may be escaped, so slice them. if (i == 0 and view.substr(start) == tags[0][-1] and view.substr(start - 1) != '\\'): return sublime.Region(start, start + 1) # Unbalanced tags; nothing we can do. return while view.substr(prev_opening_bracket.begin() - 1) == '\\': prev_opening_bracket = reverse_search_by_pt( view, tags[0], start=0, end=prev_opening_bracket.begin(), flags=sublime.IGNORECASE) if prev_opening_bracket is None: return new_start = prev_opening_bracket.begin() nested = 0 while True: next_closing_bracket = reverse_search_by_pt(view, tags[1], start=prev_opening_bracket.a, end=start, flags=sublime.IGNORECASE) if not next_closing_bracket: break nested += 1 start = next_closing_bracket.begin() if nested > 0: return find_prev_lone_bracket(view, prev_opening_bracket.begin(), tags, nested) else: return prev_opening_bracket
def previous_begin_tag(view, pattern, start=0, end=0): assert pattern, 'bad call' region = reverse_search_by_pt(view, RX_ANY_TAG, start, end, sublime.IGNORECASE) if not region: return None, None, None match = re.search(RX_ANY_TAG, view.substr(region)) return (region, match.group(1), match.group(0)[1] != '/')
def resolve_notation(self, view, token, current): ''' Returns a line number. ''' if isinstance(token, TokenDot): pt = view.text_point(current, 0) return row_at(view, pt) if isinstance(token, TokenDigits): return max(int(str(token)) - 1, -1) if isinstance(token, TokenPercent): return row_at(view, view.size()) if isinstance(token, TokenDollar): return row_at(view, view.size()) if isinstance(token, TokenOffset): return current + sum(token.content) if isinstance(token, TokenSearchForward): start_pt = view.text_point(current, 0) match = view.find(str(token)[1:-1], start_pt) if not match: # TODO: Convert this to a VimError or something like that. raise ValueError('pattern not found') return row_at(view, match.a) if isinstance(token, TokenSearchBackward): start_pt = view.text_point(current, 0) match = reverse_search_by_pt(view, str(token)[1:-1], 0, start_pt) if not match: # TODO: Convert this to a VimError or something like that. raise ValueError('pattern not found') return row_at(view, match.a) if isinstance(token, TokenMark): return self.resolve_mark(view, token) raise NotImplementedError()
def resolve_notation(self, view, token, current): """ Returns a line number. """ if isinstance(token, TokenDot): pt = view.text_point(current, 0) return row_at(view, pt) if isinstance(token, TokenDigits): return max(int(str(token)) - 1, -1) if isinstance(token, TokenPercent): return row_at(view, view.size()) if isinstance(token, TokenDollar): return row_at(view, view.size()) if isinstance(token, TokenOffset): return current + sum(token.content) if isinstance(token, TokenSearchForward): start_pt = view.text_point(current, 0) match = view.find(str(token)[1:-1], start_pt) if not match: # TODO: Convert this to a VimError or something like that. raise ValueError("pattern not found") return row_at(view, match.a) if isinstance(token, TokenSearchBackward): start_pt = view.text_point(current, 0) match = reverse_search_by_pt(view, str(token)[1:-1], 0, start_pt) if not match: # TODO: Convert this to a VimError or something like that. raise ValueError("pattern not found") return row_at(view, match.a) if isinstance(token, TokenMark): return self.resolve_mark(view, token) raise NotImplementedError()
def get_text_object_region(view, s, text_object, inclusive=False): try: delims, type_ = PAIRS[text_object] except KeyError: return s if type_ == TAG: return find_tag_text_object(view, s, inclusive) if type_ == BRACKET: opening = find_prev_lone_bracket(view, s.b, delims) closing = find_next_lone_bracket(view, s.b, delims) if not (opening and closing): return s if inclusive: return sublime.Region(opening.a, closing.b) return sublime.Region(opening.a + 1, closing.b - 1) if type_ == QUOTE: prev_quote = reverse_search_by_pt(view, delims[0], start=0, end=s.b, flags=sublime.IGNORECASE) next_quote = find_in_range(view, delims[0], start=s.b, end=view.size(), flags=sublime.IGNORECASE) if not (prev_quote and next_quote): return s if inclusive: return sublime.Region(prev_quote.a, next_quote.b) return sublime.Region(prev_quote.a + 1, next_quote.b - 1) if type_ == WORD: # TODO: Improve this -- specify word separators. word_start = view.find_by_class(s.b, forward=True, classes=sublime.CLASS_WORD_START) w = view.word(s.b) # XXX: I don't think this is necessary? if not w: return s if inclusive: return sublime.Region(w.a, word_start) else: return w if type_ == SENTENCE: # FIXME: This doesn't work well. # TODO: Improve this. sentence_start = view.find_by_class(s.b, forward=False, classes=sublime.CLASS_EMPTY_LINE) sentence_start_2 = reverse_search_by_pt(view, "[.?!:]\s+|[.?!:]$", start=0, end=s.b) if sentence_start_2: sentence_start = sentence_start + 1 if sentence_start > sentence_start_2.b else sentence_start_2.b else: sentence_start = sentence_start + 1 sentence_end = find_in_range(view, "[.?!:)](?=\s)|[.?!:)]$", start=s.b, end=view.size()) if not (sentence_end): return s if inclusive: return sublime.Region(sentence_start, sentence_end.b) else: return sublime.Region(sentence_start, sentence_end.b) return s
def get_text_object_region(view, s, text_object, inclusive=False, count=1): try: delims, type_ = PAIRS[text_object] except KeyError: return s if type_ == TAG: begin_tag, end_tag, _ = find_containing_tag(view, s.b) if inclusive: return sublime.Region(begin_tag.a, end_tag.b) else: return sublime.Region(begin_tag.b, end_tag.a) if type_ == PARAGRAPH: return find_paragraph_text_object(view, s, inclusive=inclusive, count=count) if type_ == BRACKET: opening = find_prev_lone_bracket(view, s.b, delims) closing = find_next_lone_bracket(view, s.b, delims) if not (opening and closing): return s if inclusive: return sublime.Region(opening.a, closing.b) return sublime.Region(opening.a + 1, closing.b - 1) if type_ == QUOTE: # Vim only operates on the current line. line = view.line(s) # FIXME: Escape sequences like \" are probably syntax-dependant. prev_quote = reverse_search_by_pt(view, r'(?<!\\\\)' + delims[0], start=line.a, end=s.b) next_quote = find_in_range(view, r'(?<!\\\\)' + delims[0], start=s.b, end=line.b) if next_quote and not prev_quote: prev_quote = next_quote next_quote = find_in_range(view, r'(?<!\\\\)' + delims[0], start=prev_quote.b, end=line.b) if not (prev_quote and next_quote): return s if inclusive: return sublime.Region(prev_quote.a, next_quote.b) return sublime.Region(prev_quote.a + 1, next_quote.b - 1) if type_ == WORD: w = a_word(view, s.b, inclusive=inclusive, count=count) if not w: return s if s.size() <= 1: return w return sublime.Region(s.a, w.b) if type_ == BIG_WORD: w = a_big_word(view, s.b, inclusive=inclusive, count=count) if not w: return s if s.size() <= 1: return w return sublime.Region(s.a, w.b) if type_ == SENTENCE: # FIXME: This doesn't work well. # TODO: Improve this. sentence_start = view.find_by_class(s.b, forward=False, classes=sublime.CLASS_EMPTY_LINE) sentence_start_2 = reverse_search_by_pt(view, r'[.?!:]\s+|[.?!:]$', start=0, end=s.b) if sentence_start_2: sentence_start = (sentence_start + 1 if (sentence_start > sentence_start_2.b) else sentence_start_2.b) else: sentence_start = sentence_start + 1 sentence_end = find_in_range(view, r'([.?!:)](?=\s))|([.?!:)]$)', start=s.b, end=view.size()) if not (sentence_end): return s if inclusive: return sublime.Region(sentence_start, sentence_end.b) else: return sublime.Region(sentence_start, sentence_end.b) return s
def get_text_object_region(view, s, text_object, inclusive=False, count=1): try: delims, type_ = PAIRS[text_object] except KeyError: return s if type_ == TAG: return find_tag_text_object(view, s, inclusive) if type_ == BRACKET: opening = find_prev_lone_bracket(view, s.b, delims) closing = find_next_lone_bracket(view, s.b, delims) if not (opening and closing): return s if inclusive: return sublime.Region(opening.a, closing.b) return sublime.Region(opening.a + 1, closing.b - 1) if type_ == QUOTE: # Vim only operates on the current line. line = view.line(s) # FIXME: Escape sequences like \" are probably syntax-dependant. prev_quote = reverse_search_by_pt(view, '(?<!\\\\)' + delims[0], start=line.a, end=s.b) next_quote = find_in_range(view, '(?<!\\\\)' + delims[0], start=s.b, end=line.b) if next_quote and not prev_quote: prev_quote = next_quote next_quote = find_in_range(view, '(?<!\\\\)' + delims[0], start=prev_quote.b, end=line.b) if not (prev_quote and next_quote): return s if inclusive: return sublime.Region(prev_quote.a, next_quote.b) return sublime.Region(prev_quote.a + 1, next_quote.b - 1) if type_ == WORD: w = a_word(view, s.b, inclusive=inclusive, count=count) if not w: return s return w if type_ == BIG_WORD: w = a_big_word(view, s.b, inclusive=inclusive, count=count) if not w: return s return w if type_ == SENTENCE: # FIXME: This doesn't work well. # TODO: Improve this. sentence_start = view.find_by_class(s.b, forward=False, classes=sublime.CLASS_EMPTY_LINE) sentence_start_2 = reverse_search_by_pt(view, "[.?!:]\s+|[.?!:]$", start=0, end=s.b) if sentence_start_2: sentence_start = sentence_start + 1 if sentence_start > sentence_start_2.b else sentence_start_2.b else: sentence_start = sentence_start + 1 sentence_end = find_in_range(view, "[.?!:)](?=\s)|[.?!:)]$", start=s.b, end=view.size()) if not (sentence_end): return s if inclusive: return sublime.Region(sentence_start, sentence_end.b) else: return sublime.Region(sentence_start, sentence_end.b) return s
def find_tag_text_object(view, s, inclusive=False): if (view.score_selector(s.b, 'text.html') == 0 and view.score_selector(s.b, 'text.xml') == 0): # TODO: What happens with other xml formats? return s # TODO: Receive the actual mode in the parameter list? current_pt = (s.b - 1) if view.has_non_empty_selection_region() else s.b start_pt = utils.previous_white_space_char(view, current_pt, white_space=' \t\n') + 1 if view.substr(sublime.Region(start_pt, start_pt + 2)) == '</': closing_tag = view.find(RX_ANY_END_TAG, start_pt, sublime.IGNORECASE) name = get_tag_name(view.substr(closing_tag)) start_tag_pattern = r'<({0}).*?>'.format(name) start_tag = search.reverse_search_by_pt(view, start_tag_pattern, 0, start_pt) elif view.substr(start_pt) == '<': start_tag = view.find(RX_ANY_START_TAG, start_pt, sublime.IGNORECASE) if start_tag.a != start_pt: return s else: start_tag = search.reverse_search_by_pt(view, RX_ANY_START_TAG, 0, start_pt) if not start_tag: return s tag_name = get_tag_name(view.substr(start_tag)) literal_end_tag = r'</{0}>'.format(tag_name) end_tag = None current_pt = start_tag.b while True: temp_end_tag = view.find(literal_end_tag, current_pt, sublime.IGNORECASE) if not end_tag and not temp_end_tag: return s elif not temp_end_tag: break end_tag = temp_end_tag current_pt = end_tag.b where = view.substr(sublime.Region(start_pt, end_tag.end())) opening_tags = re.findall(r'<{0}.*?>'.format(tag_name), where, re.IGNORECASE) closing_tags = re.findall(literal_end_tag, where, sublime.IGNORECASE) if len(opening_tags) == len(closing_tags): break if not end_tag: return s # Perhaps this should be handled further up by the command itself? was_visual = view.has_non_empty_selection_region() if not inclusive: if not was_visual: return sublime.Region(start_tag.b, end_tag.a) else: if start_tag.b == end_tag.a: return sublime.Region(start_tag.b, start_tag.b + 1) else: return sublime.Region(start_tag.b, end_tag.a) if not was_visual: return sublime.Region(start_tag.a, end_tag.b) else: if start_tag.a == end_tag.b: return sublime.Region(start_tag.a, start_tag.a + 1) else: return sublime.Region(start_tag.a, end_tag.b)
def get_text_object_region(view, s, text_object, inclusive=False, count=1): try: delims, type_ = PAIRS[text_object] except KeyError: return s if type_ == TAG: return find_tag_text_object(view, s, inclusive) if type_ == BRACKET: opening = find_prev_lone_bracket(view, s.b, delims) closing = find_next_lone_bracket(view, s.b, delims) if not (opening and closing): return s if inclusive: return sublime.Region(opening.a, closing.b) return sublime.Region(opening.a + 1, closing.b - 1) if type_ == QUOTE: # FIXME: Escape sequences like \" are probably syntax-dependant. prev_quote = reverse_search_by_pt(view, '(?<!\\\\)' + delims[0], start=0, end=s.b) next_quote = find_in_range(view, '(?<!\\\\)' + delims[0], start=s.b, end=view.size()) if not (prev_quote and next_quote): return s if inclusive: return sublime.Region(prev_quote.a, next_quote.b) return sublime.Region(prev_quote.a + 1, next_quote.b - 1) if type_ == WORD: w = a_word(view, s.b, inclusive=inclusive, count=count) if not w: return s return w if type_ == BIG_WORD: w = a_big_word(view, s.b, inclusive=inclusive, count=count) if not w: return s return w if type_ == SENTENCE: # FIXME: This doesn't work well. # TODO: Improve this. sentence_start = view.find_by_class(s.b, forward=False, classes=sublime.CLASS_EMPTY_LINE) sentence_start_2 = reverse_search_by_pt(view, "[.?!:]\s+|[.?!:]$", start=0, end=s.b) if sentence_start_2: sentence_start = sentence_start + 1 if sentence_start > sentence_start_2.b else sentence_start_2.b else: sentence_start = sentence_start + 1 sentence_end = find_in_range(view, "[.?!:)](?=\s)|[.?!:)]$", start=s.b, end=view.size()) if not (sentence_end): return s if inclusive: return sublime.Region(sentence_start, sentence_end.b) else: return sublime.Region(sentence_start, sentence_end.b) return s
def find_tag_text_object(view, s, inclusive=False): if (view.score_selector(s.b, 'text.html') == 0 and view.score_selector(s.b, 'text.xml') == 0): # TODO: What happens with other xml formats? return s # TODO: Receive the actual mode in the parameter list? current_pt = (s.b - 1) if view.has_non_empty_selection_region() else s.b start_pt = utils.previous_white_space_char( view, current_pt, white_space=' \t\n') + 1 if view.substr(sublime.Region(start_pt, start_pt + 2)) == '</': closing_tag = view.find(RX_ANY_END_TAG, start_pt, sublime.IGNORECASE) name = get_tag_name(view.substr(closing_tag)) start_tag_pattern = r'<({0}).*?>'.format(name) start_tag = search.reverse_search_by_pt(view, start_tag_pattern, 0, start_pt) elif view.substr(start_pt) == '<': start_tag = view.find(RX_ANY_START_TAG, start_pt, sublime.IGNORECASE) if start_tag.a != start_pt: return s else: start_tag = search.reverse_search_by_pt(view, RX_ANY_START_TAG, 0, start_pt) if not start_tag: return s tag_name = get_tag_name(view.substr(start_tag)) literal_end_tag = r'</{0}>'.format(tag_name) end_tag = None current_pt = start_tag.b while True: temp_end_tag = view.find(literal_end_tag, current_pt, sublime.IGNORECASE) if not end_tag and not temp_end_tag: return s elif not temp_end_tag: break end_tag = temp_end_tag current_pt = end_tag.b where = view.substr(sublime.Region(start_pt, end_tag.end())) opening_tags = re.findall(r'<{0}.*?>'.format(tag_name), where, re.IGNORECASE) closing_tags = re.findall(literal_end_tag, where, sublime.IGNORECASE) if len(opening_tags) == len(closing_tags): break if not end_tag: return s # Perhaps this should be handled further up by the command itself? was_visual = view.has_non_empty_selection_region() if not inclusive: if not was_visual: return sublime.Region(start_tag.b, end_tag.a) else: if start_tag.b == end_tag.a: return sublime.Region(start_tag.b, start_tag.b + 1) else: return sublime.Region(start_tag.b, end_tag.a) if not was_visual: return sublime.Region(start_tag.a, end_tag.b) else: if start_tag.a == end_tag.b: return sublime.Region(start_tag.a, start_tag.a + 1) else: return sublime.Region(start_tag.a, end_tag.b)