def _get_text_object_tag(view, s: Region, inclusive: bool, count: int) -> Region: # When the active cursor position is on leading whitespace before a tag on # the same line then the start point of the text object is the tag. line = view.line(get_insertion_point_at_b(s)) tag_in_line = view_find_in_range(view, '^\\s*<[^>]+>', line.begin(), line.end()) if tag_in_line: if s.b >= s.a and s.b < tag_in_line.end(): if s.empty(): s.a = s.b = tag_in_line.end() else: s.a = tag_in_line.end() s.b = tag_in_line.end() + 1 begin_tag, end_tag, _ = find_containing_tag(view, s.begin()) if not (begin_tag and end_tag): return s # The normal method is to select a <tag> until the matching </tag>. For "at" # the tags are included, for "it" they are excluded. But when "it" is # repeated the tags will be included (otherwise nothing would change). if not inclusive: if s == Region(begin_tag.end(), end_tag.begin()): inclusive = True if inclusive: return Region(begin_tag.a, end_tag.b) else: return Region(begin_tag.b, end_tag.a)
def run(self, edit, mode, count, search=None, forward=True, save=True): if len(self.view.sel()) != 1: ui_bell('sneak does not support multiple cursors') return if not search: search = _get_last_sneak_search(self.view) if not search: ui_bell('no previous sneak search') return clear_search_highlighting(self.view) flags = _get_search_flags(self.view, search) s = self.view.sel()[0] start_pt = get_insertion_point_at_b(s) if forward: occurrences = view_find_all_in_range(self.view, search, start_pt + 1, self.view.size(), flags) else: occurrences = list( view_rfind_all(self.view, search, start_pt, flags)) occurrences = occurrences[count - 1:] if not occurrences: ui_bell('not found: %s' % search) return target = occurrences[0].a if mode == NORMAL: s.a = s.b = target elif mode == VISUAL: resolve_visual_target(s, target) elif mode == VISUAL_LINE: resolve_visual_line_target(self.view, s, target) elif mode == INTERNAL_NORMAL: s.b = target else: return set_selection(self.view, s) add_search_highlighting(self.view, occurrences) if save: set_last_char_search_command( self.view, 'sneak_s' if forward else 'sneak_big_s') _set_last_sneak_search(self.view, search)
def _get_text_object_word(view, s: Region, inclusive: bool, count: int) -> Region: if s.size() == 1: pt = get_insertion_point_at_b(s) else: if s.b < s.a: pt = max(0, s.b - 1) else: pt = s.b w = _a_word(view, pt, inclusive=inclusive, count=count) if s.size() <= 1: return w if s.b >= s.a: return Region(s.a, w.b) else: return Region(s.a, w.a) return s
def set_mark(view, name: str) -> None: pt = get_insertion_point_at_b(view.sel()[0]) view.add_regions(_get_key(name), [Region(pt)])
def find_next_item_match_pt(view, s: Region): pt = get_insertion_point_at_b(s) # Note that some item targets are added later in relevant contexts, for # example the item targets <> are added only when in a HTML/XML context. # TODO Default targets should be configurable, see :h 'matchpairs'. targets = '(){}[]' # If in a HTML/XML context, check if the cursor position is within a valid # tag e.g. <name>, and find the matching tag e.g. if inside an opening tag # then find the closing tag, if in a closing tag find the opening one. if view.match_selector(0, 'text.(html|xml)'): # Add valid HTML/XML item targets. targets += '<>' # Find matching HTML/XML items, but only if cursor is within a tag. if view.substr(pt) not in targets: closest_tag = get_closest_tag(view, pt) if closest_tag: if closest_tag.contains(pt): begin_tag, end_tag, _ = find_containing_tag(view, pt) if begin_tag: return begin_tag.a if end_tag.contains(pt) else end_tag.a # Find the next item after or under the cursor. bracket = view_find(view, '|'.join(map(re_escape, targets)), pt) if not bracket: return # Only accept items found within the current cursor line. if bracket.b > view.line(pt).b: return target = view.substr(bracket) target_index = targets.index(target) targets_open = targets[::2] forward = True if target in targets_open else False if target in targets_open: target_pair = (targets[target_index], targets[target_index + 1]) else: target_pair = (targets[target_index - 1], targets[target_index]) accepted_selector = 'punctuation|text.plain' if forward: counter = 0 start = bracket.b while True: bracket = view_find(view, '|'.join(map(re_escape, target_pair)), start) if not bracket: return if view.match_selector(bracket.a, accepted_selector): if view.substr(bracket) == target: counter += 1 else: if counter == 0: return bracket.a counter -= 1 start = bracket.b else: # Note the use of view_rfind_all(), because Sublime doesn't have any # APIs to do reverse searches and finding *all* matches before a point # is easier and more effiecient, at least in a userland implemention. counter = 0 start = bracket.a for bracket in view_rfind_all(view, '|'.join(map(re_escape, target_pair)), start): if view.match_selector(bracket.a, accepted_selector): if view.substr(bracket) == target: counter += 1 else: if counter == 0: return bracket.a counter -= 1 start = bracket.a
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 = get_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 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