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 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=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 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=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=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 _get_text_object_sentence(view, s: Region, inclusive: bool, count: int) -> Region: sentence_start = view.find_by_class(s.b, forward=False, classes=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 Region(sentence_start, sentence_end.b) else: return Region(sentence_start, sentence_end.b)
def _get_text_object_quote(view, s: Region, inclusive: bool, count: int, delims) -> Region: line = view.line(s) delim_open = delims[0] # FIXME: Escape sequences like \" are probably syntax-dependant. prev_quote = reverse_search_by_pt(view, r'(?<!\\\\)' + delim_open, start=line.a, end=s.b) next_quote = find_in_range(view, r'(?<!\\\\)' + delim_open, start=s.b, end=line.b) if next_quote and not prev_quote: prev_quote = next_quote next_quote = find_in_range(view, r'(?<!\\\\)' + delim_open, start=prev_quote.b, end=line.b) if not (prev_quote and next_quote): return s if inclusive: return Region(prev_quote.a, next_quote.b) return Region(prev_quote.a + 1, next_quote.b - 1)
def _resolve_line_number(view, token, current: int) -> int: # Args: # view (View): The view where the calculation is made. # token (Token): # current (int): Line number where we are now. if isinstance(token, TokenDot): return row_at(view, view.text_point(current, 0)) if isinstance(token, TokenDigits): return max(int(token.content) - 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): match = view.find(token.content, view.text_point(current, 0)) if not match: raise ValueError('E385: Search hit BOTTOM without match for: ' + token.content) return row_at(view, match.a) if isinstance(token, TokenSearchBackward): match = reverse_search_by_pt(view, token.content, 0, view.text_point(current, 0)) if not match: raise ValueError('E384: Search hit TOP without match for: ' + token.content) return row_at(view, match.a) if isinstance(token, TokenMark): if token.content == '<': sel = list(view.sel())[0] view.sel().clear() view.sel().add(sel) if sel.a < sel.b: return row_at(view, sel.a) else: return row_at(view, sel.a - 1) elif token.content == '>': sel = list(view.sel())[0] view.sel().clear() view.sel().add(sel) if sel.a < sel.b: return row_at(view, sel.b - 1) else: return row_at(view, sel.b) elif token.content in tuple('abcdefghijklmnopqrstuvwxyz'): mark = get_mark(view, token.content) if not isinstance(mark, Region): raise ValueError('E20: mark not set') return view.rowcol(mark.b)[0] raise NotImplementedError()
def _resolve_line_number(view, token, current): # type: (...) -> int # Args: # view (View): The view where the calculation is made. # token (Token): # current (int): Line number where we are now. if isinstance(token, TokenDot): return row_at(view, view.text_point(current, 0)) if isinstance(token, TokenDigits): return max(int(token.content) - 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): match = view.find(token.content, view.text_point(current, 0)) if not match: raise ValueError('pattern not found') return row_at(view, match.a) if isinstance(token, TokenSearchBackward): match = reverse_search_by_pt(view, token.content, 0, view.text_point(current, 0)) if not match: raise ValueError('pattern not found') return row_at(view, match.a) if isinstance(token, TokenMark): if token.content == '<': sel = list(view.sel())[0] view.sel().clear() view.sel().add(sel) if sel.a < sel.b: return row_at(view, sel.a) else: return row_at(view, sel.a - 1) elif token.content == '>': sel = list(view.sel())[0] view.sel().clear() view.sel().add(sel) if sel.a < sel.b: return row_at(view, sel.b - 1) else: return row_at(view, sel.b) elif token.content in tuple('abcdefghijklmnopqrstuvwxyz'): return view.rowcol( get_mark_as_encoded_address(view, token.content).b)[0] raise NotImplementedError()
def previous_begin_tag(view, pattern, start=0, end=0): assert pattern, 'bad call' region = reverse_search_by_pt(view, pattern, start, end, IGNORECASE) if not region: return None, None, None match = re.search(pattern, view.substr(region)) return (region, match.group(1), match.group(0)[1] != '/')
def resolve_notation(self, view, token, current): """Return 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: 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: 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, count=1): # type: (...) -> Region try: delims, type_ = PAIRS[text_object] except KeyError: return s if type_ == TAG: begin_tag, end_tag, _ = find_containing_tag(view, s.b) if not (begin_tag and end_tag): return s if inclusive: return Region(begin_tag.a, end_tag.b) else: return Region(begin_tag.b, end_tag.a) if type_ == PARAGRAPH: return find_paragraph_text_object(view, s, inclusive=inclusive, count=count) if type_ == BRACKET: b = resolve_insertion_point_at_b(s) opening = find_prev_lone_bracket(view, b, delims) closing = find_next_lone_bracket(view, b, delims) if not (opening and closing): return s if inclusive: return Region(opening.a, closing.b) a = opening.a + 1 if view.substr(a) == '\n': a += 1 return Region(a, 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 Region(prev_quote.a, next_quote.b) return 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 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 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=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 Region(sentence_start, sentence_end.b) else: return Region(sentence_start, sentence_end.b) if type_ == INDENT: start, end = find_indent_text_object(view, s, inclusive) return Region(start, end) if type_ == LINE: start, end = find_line_text_object(view, s) return Region(start, end) return s
def _resolve_line_number(view, token, current): # type: (...) -> int # Args: # view (View): The view where the calculation is made. # token (Token): # current (int): Line number where we are now. if isinstance(token, TokenDot): return row_at(view, view.text_point(current, 0)) if isinstance(token, TokenDigits): return max(int(token.content) - 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): match = view.find(token.content, view.text_point(current, 0)) if not match: raise ValueError('pattern not found') return row_at(view, match.a) if isinstance(token, TokenSearchBackward): match = reverse_search_by_pt(view, token.content, 0, view.text_point(current, 0)) if not match: raise ValueError('pattern not found') return row_at(view, match.a) if isinstance(token, TokenMark): if token.content == '<': sel = list(view.sel())[0] view.sel().clear() view.sel().add(sel) if sel.a < sel.b: return row_at(view, sel.a) else: return row_at(view, sel.a - 1) elif token.content == '>': sel = list(view.sel())[0] view.sel().clear() view.sel().add(sel) if sel.a < sel.b: return row_at(view, sel.b - 1) else: return row_at(view, sel.b) elif token.content in tuple('abcdefghijklmnopqrstuvwxyz'): # The state class is intentionally imported here instead of at the # begining of the file to avoid circular imports errors. The State # needs to refactored and replaced with some less god-like from NeoVintageous.nv.state import State address = State(view).marks.get_as_encoded_address(token.content) return view.rowcol(address.b)[0] raise NotImplementedError()
def get_text_object_region(view, s, text_object, inclusive=False, count=1): # type: (...) -> Region try: delims, type_ = PAIRS[text_object] except KeyError: return s if type_ == TAG: begin_tag, end_tag, _ = find_containing_tag(view, s.begin()) if not (begin_tag and end_tag): return s if inclusive: return Region(begin_tag.a, end_tag.b) else: return 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, max(0, s.begin() - 1), delims) closing = find_next_lone_bracket(view, s.end(), delims) if not (opening and closing): return s if inclusive: return Region(opening.a, closing.b) a = opening.a + 1 if view.substr(a) == '\n': a += 1 b = closing.b - 1 if b > a: line = view.line(b) if next_non_blank(view, line.a) + 1 == line.b: row_a, col_a = view.rowcol(a) row_b, col_b = view.rowcol(b) if (row_b - 1) > row_a: line = view.full_line(view.text_point((row_b - 1), 0)) return Region(a, line.b) return Region(a, b) 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 Region(prev_quote.a, next_quote.b) return Region(prev_quote.a + 1, next_quote.b - 1) if type_ == WORD: w = a_word(view, s.b, inclusive=inclusive, count=count) if s.size() <= 1: return w return Region(s.a, w.b) if type_ == BIG_WORD: w = a_big_word(view, s.b, inclusive=inclusive, count=count) if s.size() <= 1: return w return 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=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 Region(sentence_start, sentence_end.b) else: return Region(sentence_start, sentence_end.b) # Support for a port of the Indent Object plugin: # https://github.com/michaeljsmith/vim-indent-object if type_ == INDENT: start, end = find_indent_text_object(view, s, inclusive) return Region(start, end) if type_ == LINE: start, end = find_line_text_object(view, s) return Region(start, end) return s