def run(self, edit): v = self.view if v.sel()[0].size() == 0: v.run_command("expand_selection", {"to": "word"}) cur_start = v.sel()[0].begin() cur_end = v.sel()[0].end() for sel in v.sel(): text = v.substr(sel) res = self.matcher(text) if not res: #first check one character to the left to see if its a symbol sel = Region(sel.begin() - 1, sel.end()) text = v.substr(sel) res = self.matcher(text) if not res: #now expand selection one character to the right to see if its a string sel = Region(sel.begin(), sel.end() + 1) text = v.substr(sel) res = self.matcher(text) if not res: #this is a mute point continue self.replacer(v, edit, sel, text, res) v.sel().clear() v.sel().add(Region(cur_start, cur_end))
def find_sentences_backward(view, start_pt: int, count: int = 1) -> Region: if isinstance(start_pt, Region): start_pt = start_pt.a pt = prev_non_ws(view, start_pt) sen = Region(pt) prev = sen while True: sen = view.expand_by_class(sen, CLASS_LINE_END | CLASS_PUNCTUATION_END) if sen.a <= 0 or view.substr(sen.begin() - 1) in ('.', '\n', '?', '!'): if view.substr(sen.begin() - 1) == '.' and not view.substr(sen.begin()) == ' ': continue if prev == sen: break prev = sen if sen: pt = next_non_blank(view, sen.a) if pt < sen.b and pt != start_pt: if view.substr(pt) == '\n': if pt + 1 != start_pt: pt += 1 return Region(pt) if pt > 0: continue return sen return sen
def remove_comments(view: sublime.View, edit: sublime.Edit, region: sublime.Region, tokens: dict): "Removes comment markers from given region. Returns amount of characters removed" text = view.substr(region) if text.startswith(tokens['start']) and text.endswith(tokens['end']): start_offset = region.begin() + len(tokens['start']) end_offset = region.end() - len(tokens['end']) # Narrow down offsets for whitespace if view.substr(start_offset).isspace(): start_offset += 1 if view.substr(end_offset - 1).isspace(): end_offset -= 1 start_region = sublime.Region(region.begin(), start_offset) end_region = sublime.Region(end_offset, region.end()) # It's faster to erase the start region first # See comment in Default/comment.py plugin view.erase(edit, start_region) end_region = sublime.Region(end_region.begin() - start_region.size(), end_region.end() - start_region.size()) view.erase(edit, end_region) return start_region.size() + end_region.size() return 0
def get_line_indent(view: sublime.View, line: sublime.Region) -> str: "Returns indentation for given line" pos = line.begin() end = line.end() while pos < end and view.substr(pos).isspace(): pos += 1 return view.substr(sublime.Region(line.begin(), pos))
def replace_with_snippet(view: sublime.View, edit: sublime.Edit, region: sublime.Region, snippet: str): "Replaces given region view with snippet contents" sel = view.sel() sel.clear() sel.add(sublime.Region(region.begin(), region.begin())) view.replace(edit, region, '') view.run_command('insert_snippet', {'contents': snippet})
def get_line_indent(view: sublime.View, line: sublime.Region) -> str: "Returns indentation for given line or line found from given character location" if isinstance(line, int): line = view.line(line) pos = line.begin() end = line.end() while pos < end and view.substr(pos).isspace(): pos += 1 return view.substr(sublime.Region(line.begin(), pos))
def find_sentences_backward(view, start_pt, count=1): if isinstance(start_pt, Region): start_pt = start_pt.a pt = utils.previous_non_white_space_char(view, start_pt, white_space='\n \t') sen = Region(pt) while True: sen = view.expand_by_class(sen, CLASS_LINE_END | CLASS_PUNCTUATION_END) if sen.a <= 0 or view.substr(sen.begin() - 1) in ('.', '\n', '?', '!'): if view.substr(sen.begin() - 1) == '.' and not view.substr(sen.begin()) == ' ': continue return sen
def update_region(region: sublime.Region, delta: int, last_pos: int) -> sublime.Region: if delta < 0: # Content removed if last_pos == region.begin(): # Updated content at the abbreviation edge region.a += delta region.b += delta elif region.begin() < last_pos <= region.end(): region.b += delta elif delta > 0 and region.begin() <= last_pos <= region.end(): # Content inserted region.b += delta return region
def run(self, edit): v = self.view if v.sel()[0].size() == 0: v.run_command("expand_selection", {"to": "scope"}) for sel in v.sel(): text = v.substr(sel) res = re_quotes.match(text) if not res: # the current selection doesn't begin and end with a quote. # let's expand the selection one character each direction and try again sel = Region(sel.begin() - 1, sel.end() + 1) text = v.substr(sel) res = re_quotes.match(text) if not res: # still no match... skip it! continue prevSpace = res.group(1) oldQuotes = res.group(2) newQuotes = "'" if oldQuotes == '"' else '"' text = res.group(3) text = string.replace(text, newQuotes, "\\" + newQuotes) text = string.replace(text, "\\" + oldQuotes, oldQuotes) text = prevSpace + newQuotes + text + newQuotes v.replace(edit, sel, text)
def run(self, edit, **kwargs): v = self.view if v.sel()[0].size() == 0: v.run_command('expand_selection', {'to': 'scope'}) for sel in v.sel(): text = v.substr(sel) res = re_quotes.match(text) if not res: # the current selection doesn't begin and end with a quote. # let's expand the selection one character each direction and try again sel = Region(sel.begin() - 1, sel.end() + 1) text = v.substr(sel) res = re_quotes.match(text) if not res: # still no match... skip it! continue oldQuotes = res.group(1) if 'key' in kwargs: newQuotes = kwargs['key'] else: newQuotes = quoteList[(quoteList.index(oldQuotes) + 1) % len(quoteList)] text = res.group(2) text = text.replace(newQuotes, "\\" + newQuotes) text = text.replace("\\" + oldQuotes, oldQuotes) text = newQuotes + text + newQuotes v.replace(edit, sel, text)
def _get_text_object_bracket(view, s: Region, inclusive: bool, count: int, delims: tuple) -> Region: 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 - 1) row_b, col_b = view.rowcol(b + 1) 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)
def get_wrap_region(view: sublime.View, sel: sublime.Region, config: Config) -> sublime.Region: "Returns region to wrap with abbreviation" if sel.empty(): # No selection means user wants to wrap current tag container pt = sel.begin() ctx = emmet.get_tag_context(view, pt) if ctx: # Check how given point relates to matched tag: # if it's in either open or close tag, we should wrap tag itself, # otherwise we should wrap its contents open_tag = ctx.get('open') close_tag = ctx.get('close') if in_range(open_tag, pt) or (close_tag and in_range(close_tag, pt)): return sublime.Region( open_tag.begin(), close_tag and close_tag.end() or open_tag.end()) if close_tag: r = sublime.Region(open_tag.end(), close_tag.begin()) return utils.narrow_to_non_space(view, r) return utils.narrow_to_non_space(view, sel)
def get_block(view: sublime.View, s: sublime.Region) -> (str, sublime.Region): """Get the code block under the cursor. The code block is the lines satisfying the following conditions: - Includes the line under the cursor. - Includes no blank line. - More indented than that of the line under the cursor. If `s` is a selected region, the code block is it. """ if not s.empty(): return (view.substr(s), s) view_end_row = view.rowcol(view.size())[0] current_row = view.rowcol(s.begin())[0] current_indent = get_indent(view, current_row) start_point = 0 for first_row in range(current_row, -1, -1): indent = get_indent(view, first_row) if (not indent.startswith(current_indent) or get_line(view, first_row).strip() == ''): start_point = view.text_point(first_row + 1, 0) break end_point = view.size() for last_row in range(current_row, view_end_row + 1): indent = get_indent(view, last_row) if (not indent.startswith(current_indent) or get_line(view, last_row).strip() == ''): end_point = view.text_point(last_row, 0) - 1 break block_region = sublime.Region(start_point, end_point) return (view.substr(block_region), block_region)
def select_item(view: sublime.View, sel: sublime.Region, is_css=False, is_previous=False): "Selects next/previous item for CSS source" buffer_id = view.buffer_id() pos = sel.begin() # Check if we are still in calculated model model = models_for_buffer.get(buffer_id) if model: region = find_region(sel, model.ranges, is_previous) if region: select(view, region) return # Out of available selection range, move to next tag pos = model.start if is_previous else model.end # Calculate new model from current editor content content = utils.get_content(view) model = emmet.select_item(content, pos, is_css, is_previous) if model: models_for_buffer[buffer_id] = model region = find_region(sel, model.ranges, is_previous) if region: select(view, region) return
def is_valid_tracker(tracker: AbbreviationTracker, region: sublime.Region, pos: int) -> bool: "Check if given tracker is in valid state for keeping it marked" if isinstance(tracker, AbbreviationTrackerError): if region.end() == pos: # Last entered character is invalid return False abbreviation = tracker.abbreviation start = region.begin() target_pos = region.end() if '</' in abbreviation: # XXX Silly check for auto-consed tag in JSX (see Naomi syntax) # Find better solution return False if syntax.is_jsx(tracker.config.syntax): start += 1 while target_pos > start: ch = abbreviation[target_pos - start - 1] if ch in pairs_end: target_pos -= 1 else: break return target_pos != pos return True
def get_wrap_region(view: sublime.View, sel: sublime.Region, config: Config) -> sublime.Region: "Returns region to wrap with abbreviation" if sel.empty(): # No selection means user wants to wrap current tag container pt = sel.begin() ctx = emmet.get_tag_context(view, pt) if ctx: # Check how given point relates to matched tag: # if it's in either open or close tag, we should wrap tag itself, # otherwise we should wrap its contents open_tag = ctx.get('open') close_tag = ctx.get('close') if in_range(open_tag, pt) or (close_tag and in_range(close_tag, pt)): return sublime.Region( open_tag.begin(), close_tag and close_tag.end() or open_tag.end()) if close_tag: r = sublime.Region(open_tag.end(), close_tag.begin()) next_region = utils.narrow_to_non_space(view, r) # On the right side of tag contents, we should skip new lines only # and trim spaces at the end of line padding = view.substr( sublime.Region(next_region.end(), r.end())) ix = padding.find('\n') end = next_region.end() + ix if ix != -1 else r.end() next_region = sublime.Region(next_region.begin(), end) return next_region return utils.narrow_to_non_space(view, sel, utils.NON_SPACE_LEFT)
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 new_char_phantom(view: sublime.View, char_region: sublime.Region) -> sublime.Phantom: char = view.substr(char_region)[0] return sublime.Phantom( # the "begin" position has a better visual selection result than the "end" position sublime.Region(char_region.begin()), generate_phantom_html(view, char), layout=sublime.LAYOUT_INLINE, )
def get_cell(view: sublime.View, region: sublime.Region, *, logger=HERMES_LOGGER) -> (str, sublime.Region): """Get the code cell under the cursor. Cells are separated by markers defined in `cell_delimiter_pattern` in the config file. If `s` is a selected region, the code cell is it. """ if not region.empty(): return (view.substr(region), region) cell_delimiter_pattern = (sublime.load_settings( "Hermes.sublime-settings").get("cell_delimiter_pattern")) separators = view.find_all(cell_delimiter_pattern) r = sublime.Region(region.begin() + 1, region.begin() + 1) start_point = separators[bisect.bisect(separators, r) - 1].end() + 1 end_point = separators[bisect.bisect(separators, r)].begin() - 1 cell_region = sublime.Region(start_point, end_point) return (view.substr(cell_region), cell_region)
def run(self, edit): sel = self.view.sel() current = sel[0].begin() line = self.view.line(current) length = line.end() - line.begin() upper = '/*==' + ('-' * (length - 1)) bottom = '/' + ('-' * (length - 1)) + '==*/' added = self.view.insert(edit, line.begin(), upper + '\n/ ') line = Region(line.begin() + added, line.end() + added) self.view.insert(edit, line.end(), '\n' + bottom)
def is_regions_intersected(region_1: sublime.Region, region_2: sublime.Region, allow_boundary: bool = False) -> bool: """ @brief Check whether two regions are intersected. @param region_1 The 1st region @param region_2 The 2nd region @param allow_boundary Treat boundary contact as intersected @return True if intersected, False otherwise. """ # treat boundary contact as intersected if allow_boundary: # left/right begin/end = l/r b/e lb_, le_ = region_1.begin(), region_1.end() rb_, re_ = region_2.begin(), region_2.end() if lb_ == rb_ or lb_ == re_ or le_ == rb_ or le_ == re_: return True return region_1.intersects(region_2)
def wrap_bracket(view, edit, open_bracket): close_bracket = sexp.OPEN[open_bracket] for region, sel in iterate(view): if not region.empty(): view.insert(edit, region.end(), close_bracket) view.insert(edit, region.begin(), open_bracket) else: point = region.begin() form = forms.find_adjacent(view, point) # cursor is in between the dispatch macro and an open paren if (form and view.match_selector(point - 1, "keyword.operator.macro") and view.match_selector(point, selectors.SEXP_BEGIN)): form = Region(form.begin() + 1, form.end()) if not form: form = Region(point, point) sel.append(Region(point + 1, point + 1)) view.insert(edit, form.end(), close_bracket) view.insert(edit, form.begin(), open_bracket)
def get_content(view: sublime.View, region: sublime.Region, lines=False): "Returns contents of given region, properly de-indented" base_line = view.substr(view.line(region.begin())) m = re_indent.match(base_line) indent = m.group(0) if m else '' src_lines = view.substr(region).splitlines() dest_lines = [] for line in src_lines: if dest_lines and line.startswith(indent): line = line[len(indent):] dest_lines.append(line) return dest_lines if lines else '\n'.join(dest_lines)
def narrow_to_non_space(view: sublime.View, region: sublime.Region) -> sublime.Region: "Returns copy of region which starts and ends at non-space character" begin = region.begin() end = region.end() while begin < end: if not view.substr(begin).isspace(): break begin += 1 while end > begin: if not view.substr(end - 1).isspace(): break end -= 1 return sublime.Region(begin, end)
def indent_region(view, edit, region, prune=False): if region and not view.match_selector(region.begin(), IGNORE_SELECTORS): new_lines = [] for line in view.lines(region): replacee = line if new_lines: if previous := new_lines.pop(): begin = previous.end() end = begin + line.size() replacee = Region(begin, end) if replacer := get_indented_string(view, replacee, prune=prune): view.replace(edit, replacee, replacer) new_lines.append(view.full_line(replacee.begin())) restore_cursors(view)
def convert_from_data_url(view: sublime.View, region: sublime.Region, dest: str): src = view.substr(region) m = re.match(r'^data\:.+?;base64,(.+)', src) if m: base_dir = os.path.dirname(view.file_name()) abs_dest = utils.create_path(base_dir, dest) file_url = os.path.relpath(abs_dest, base_dir).replace('\\', '/') dest_dir = os.path.dirname(abs_dest) if not os.path.exists(dest_dir): os.makedirs(dest_dir) with open(abs_dest, 'wb') as fd: fd.write(base64.urlsafe_b64decode(m.group(1))) view.run_command('convert_data_url_replace', { 'region': [region.begin(), region.end()], 'text': file_url })
def narrow_to_non_space( view: sublime.View, region: sublime.Region, direction=NON_SPACE_LEFT | NON_SPACE_RIGHT) -> sublime.Region: "Returns copy of region which starts and ends at non-space character" begin = region.begin() end = region.end() if direction & NON_SPACE_LEFT: while begin < end: if not view.substr(begin).isspace(): break begin += 1 if (direction & NON_SPACE_RIGHT): while end > begin: if not view.substr(end - 1).isspace(): break end -= 1 return sublime.Region(begin, end)
def get_comment_regions(view: sublime.View, region: sublime.Region, tokens: dict): "Finds comments inside given region and returns their regions" result = [] text = view.substr(region) start = region.begin() offset = 0 while True: c_start = text.find(tokens['start'], offset) if c_start != -1: offset = c_start + len(tokens['start']) # Find comment end c_end = text.find(tokens['end'], offset) if c_end != -1: offset = c_end + len(tokens['end']) result.append(sublime.Region(start + c_start, start + offset)) else: break return result
def get_wrap_region(view: sublime.View, sel: sublime.Region, options: dict) -> sublime.Region: "Returns region to wrap with abbreviation" if sel.empty() and options.get('context'): # If there’s no selection than user wants to wrap current tag container ctx = options['context'] pt = sel.begin() # Check how given point relates to matched tag: # if it's in either open or close tag, we should wrap tag itself, # otherwise we should wrap its contents open_tag = ctx.get('open') close_tag = ctx.get('close') if in_range(open_tag, pt) or (close_tag and in_range(close_tag, pt)): return sublime.Region( open_tag.begin(), close_tag and close_tag.end() or open_tag.end()) if close_tag: r = sublime.Region(open_tag.end(), close_tag.begin()) return utils.narrow_to_non_space(view, r) return sel
def run(self, edit): v = self.view if v.sel()[0].size() == 0: v.run_command("expand_selection", {"to": "scope"}) for sel in v.sel(): text = v.substr(sel) res = re_quotes.match(text) if not res: # the current selection doesn't begin and end with a quote. # let's expand the selection one character each direction and try again sel = Region(sel.begin() - 1, sel.end() + 1) text = v.substr(sel) res = re_quotes.match(text) if not res: # still no match... skip it! continue oldQuotes = res.group(1) newQuotes = "'" if oldQuotes == '"' else '"' text = res.group(2) text = string.replace(text, newQuotes, "\\" + newQuotes) text = string.replace(text, "\\" + oldQuotes, oldQuotes) text = newQuotes + text + newQuotes v.replace(edit, sel, text)
def _f(view, s): if mode == INTERNAL_NORMAL: if len(target) != 1: return s # The *target* letters w, W, s, and p correspond to a |word|, a # |WORD|, a |sentence|, and a |paragraph| respectively. These are # special in that they have nothing to delete, and used with |ds| they # are a no-op. With |cs|, one could consider them a slight shortcut for # ysi (cswb == ysiwb, more or less). noop = 'wWsp' if target in noop: return s valid_targets = '\'"`b()B{}r[]a<>t.,-_;:@#~*\\/' if target not in valid_targets: return s # All marks, except punctuation marks, are only searched for on the # current line. # If opening punctuation mark is used, contained whitespace is also trimmed. trim_contained_whitespace = True if target in '({[<' else False search_current_line_only = False if target in 'b()B{}r[]a<>' else True # Expand targets into begin and end variables because punctuation marks # and their aliases represent themselves and their counterparts e.g. (), # []. Target is the same for begin and end for all other valid marks # e.g. ', ", `, -, _, etc. t_char_begin, t_char_end = _get_punctuation_marks(target) s_rowcol_begin = view.rowcol(s.begin()) s_rowcol_end = view.rowcol(s.end()) # A t is a pair of HTML or XML tags. if target == 't': # TODO test dst works when cursor position is inside tag begin <a|bc>x</abc> -> dst -> |x # TODO test dst works when cursor position is inside tag end <abc>x</a|bc> -> dst -> |x t_region_end = view.find('<\\/.*?>', s.b) t_region_begin = reverse_search(view, '<.*?>', start=0, end=s.b) else: current = view.substr(s.begin()) # TODO test ds{char} works when cursor position is on target begin |"x" -> ds" -> |x # TODO test ds{char} works when cursor position is on target end "x|" -> ds" -> |x if current == t_char_begin: t_region_begin = Region(s.begin(), s.begin() + 1) else: t_region_begin = _rfind(view, t_char_begin, start=0, end=s.begin(), flags=LITERAL) t_region_begin_rowcol = view.rowcol(t_region_begin.begin()) t_region_end = _find(view, t_char_end, start=t_region_begin.end(), flags=LITERAL) t_region_end_rowcol = view.rowcol(t_region_end.end()) if search_current_line_only: if t_region_begin_rowcol[0] != s_rowcol_begin[0]: return s if t_region_end_rowcol[0] != s_rowcol_end[0]: return s if trim_contained_whitespace: t_region_begin_ws = _find(view, '\\s*.', start=t_region_begin.end()) t_region_end_ws = _rfind(view, '.\\s*', start=t_region_begin.end(), end=t_region_end.begin()) if t_region_begin_ws.size() > 1: t_region_begin = Region(t_region_begin.begin(), t_region_begin_ws.end() - 1) if t_region_end_ws.size() > 1: t_region_end = Region(t_region_end_ws.begin() + 1, t_region_end.end()) # Note: Be careful using boolean evaluation on a Region because an empty # Region evaluates to False. It evaluates to False because Region # invokes `__len__()` which will be zero if the Region is empty e.g. # `Region(3).size()` is `0`, whereas `Region(3, 4).size()` is `1`. # `sublime.View.find(sub)` returns `Region(-1)` if *sub* not found. This # is similar to how the python `str.find(sub)` function works i.e. it # returns `-1` if *sub* not found, because *sub* could be found at # position `0`. To check if a Region was found use `Region(3) >= 0`. To # check if a Region is non empty you can use boolean evaluation i.e. `if # Region(3): ...`. In the following case boolean evaluation is # intentional. if not (t_region_end and t_region_begin): return s # It's important that the end is replaced first. If we replaced the # begin region first then the end replacement would be off-by-one # because the begin is reducing the size of the internal buffer by one # i.e. it's deleting a character. view.replace(edit, t_region_end, '') view.replace(edit, t_region_begin, '') return Region(t_region_begin.begin()) return s
def key_function(self, region: sublime.Region) -> Callable[[int], int]: return lambda i: -abs(self.matches[i][0] - region.begin())
def run_each(self, edit, region, braces="{}", pressed=None, unindent=False, select=False, replace=False): """ Options: braces a list of matching braces or a string containing the pair pressed the key pressed; used for closing vs opening logic unindent removes one "tab" from a newline. true for braces that handle indent, like {} select whether to select the region inside the braces replace whether to insert new braces where the old braces were """ if self.view.settings().get("translate_tabs_to_spaces"): tab = " " * self.view.settings().get("tab_size") else: tab = "\t" row, col = self.view.rowcol(region.begin()) indent_point = self.view.text_point(row, 0) if indent_point < region.begin(): indent = self.view.substr(Region(indent_point, region.begin())) indent = re.match("[ \t]*", indent).group(0) else: indent = "" line = self.view.substr(self.view.line(region.a)) selection = self.view.substr(region) # for braces that have newlines ("""), insert the current line's indent if isinstance(braces, list): l_brace = braces[0] r_brace = braces[1] braces = "".join(braces) braces = braces.replace("\n", "\n" + indent) length = len(l_brace) else: braces = braces.replace("\n", "\n" + indent) length = len(braces) // 2 l_brace = braces[:length] r_brace = braces[length:] if region.empty(): after = self.view.substr(Region(region.a, region.a + length)) insert_braces = braces complicated_check = self.complicated_quote_checker(insert_braces, region, pressed, after, r_brace) if complicated_check: insert_braces = complicated_check elif ( pressed and after == r_brace and r_brace[-1] == pressed ): # and (pressed not in QUOTING_BRACKETS or in_string_scope): # in this case we pressed the closing character, and that's the character that is to the right # so do nothing except advance cursor position insert_braces = False elif unindent and row > 0 and indent and line == indent: # indent has the current line's indent # get previous line's indent: prev_point = self.view.text_point(row - 1, 0) prev_line = self.view.line(prev_point) prev_indent = self.view.substr(prev_line) prev_indent = re.match("[ \t]*", prev_indent).group(0) if ( (not pressed or pressed == l_brace) and len(indent) > len(prev_indent) and indent[len(prev_indent) :] == tab ): # move region.a back by 'indent' amount region = Region(region.a - len(tab), region.b - len(tab)) # and remove the tab self.view.replace(edit, Region(region.a, region.a + len(tab) - 1), "") elif pressed and pressed == r_brace: if len(indent) == len(prev_indent): # move region.a back by 'indent' amount region = Region(region.a - len(tab), region.b - len(tab)) # and remove the tab self.view.replace(edit, Region(region.a, region.a + len(tab) - 1), "") insert_braces = r_brace elif pressed and pressed != l_brace: # we pressed the closing bracket or quote. This *never* insert_braces = r_brace if insert_braces: self.view.insert(edit, region.a, insert_braces) self.view.sel().add(Region(region.a + length, region.a + length)) elif selection in QUOTING_BRACKETS and pressed in QUOTING_BRACKETS and selection != pressed: # changing a quote from single <=> double, just insert the quote. self.view.replace(edit, region, pressed) self.view.sel().add(Region(region.end(), region.end())) elif pressed and pressed != l_brace: b = region.begin() + len(r_brace) self.view.replace(edit, region, r_brace) self.view.sel().add(Region(b, b)) else: substitute = self.view.substr(region) replacement = l_brace + substitute + r_brace # if we're inserting "real" brackets, not quotes: real_brackets = l_brace in OPENING_BRACKETS and r_brace in CLOSING_BRACKETS # check to see if entire lines are selected, and if so do some smart indenting # bol_is_nl => allman style {} # bol_at_nl => kernigan&ritchie if region.begin() == 0: bol_is_nl = True bol_at_nl = False elif len(self.view) == region.begin() + 1: bol_is_nl = False bol_at_nl = False else: bol_is_nl = self.view.substr(region.begin() - 1) == "\n" bol_at_nl = ( l_brace == "{" and self.view.substr(region.begin()) == "\n" and self.view.substr(region.begin() - 1) != "\n" ) eol_is_nl = region.end() == self.view.size() or self.view.substr(region.end()) == "\n" eol_at_nl = self.view.substr(region.end() - 1) == "\n" if eol_is_nl: eol_is_nl = self.view.line(region.begin()) != self.view.line(region.end()) if real_brackets and (bol_is_nl or bol_at_nl) and (eol_is_nl or eol_at_nl): indent = "" if bol_at_nl and substitute: substitute = substitute[1:] m = re.match("([ \t]*)" + tab, substitute) if m: indent = m.group(1) else: substitute = tab + substitute b = region.begin() - len("\n" + indent + r_brace) if bol_at_nl: replacement = l_brace + "\n" + substitute if eol_at_nl: replacement += indent + r_brace + "\n" b -= 1 else: replacement += r_brace + "\n" b += len(indent) if not self.view.substr(region.begin() - 1) == " ": replacement = " " + replacement else: replacement = indent + l_brace + "\n" + substitute + indent + r_brace + "\n" b -= 1 b += len(replacement) else: b = region.begin() + len(replacement) if ( replace and self.view.substr(region.begin() - 1) in OPENING_BRACKET_LIKE and self.view.substr(region.end()) in CLOSING_BRACKET_LIKE ): b -= 1 self.view.replace(edit, Region(region.begin() - 1, region.end() + 1), replacement) elif ( replace and self.view.substr(region.begin()) in OPENING_BRACKET_LIKE and self.view.substr(region.end() - 1) in CLOSING_BRACKET_LIKE ): replacement = l_brace + replacement[2:-2] + r_brace b -= 2 self.view.replace(edit, region, replacement) l_brace = r_brace = "" else: self.view.replace(edit, region, replacement) if select: self.view.sel().add(Region(b - len(replacement) + len(l_brace), b - len(r_brace))) else: self.view.sel().add(Region(b, b))
def run_each(self, edit, region, braces='{}', pressed=None, unindent=False, select=False, replace=False): self.view.sel().subtract(region) if self.view.settings().get('translate_tabs_to_spaces'): tab = ' ' * self.view.settings().get('tab_size') else: tab = "\t" row, col = self.view.rowcol(region.begin()) indent_point = self.view.text_point(row, 0) if indent_point < region.begin(): indent = self.view.substr(Region(indent_point, region.begin())) indent = re.match('[ \t]*', indent).group(0) else: indent = '' line = self.view.substr(self.view.line(region.a)) selection = self.view.substr(region) # for braces that have newlines ("""), insert the current line's indent braces = braces.replace("\n", "\n" + indent) length = len(braces) / 2 l_brace = braces[:length] r_brace = braces[length:] if region.empty(): after = self.view.substr(Region(region.a, region.a + length)) insert_braces = braces complicated_check = self.complicated_quote_checker(insert_braces, region, pressed, after, r_brace) if complicated_check: insert_braces = complicated_check elif pressed and after == r_brace and r_brace[-1] == pressed: # and (pressed not in QUOTING_BRACKETS or in_string_scope): # in this case we pressed the closing character, and that's the character that is to the right # so do nothing except advance cursor position insert_braces = False elif unindent and row > 0 and indent and line == indent: # indent has the current line's indent # get previous line's indent: prev_point = self.view.text_point(row - 1, 0) prev_line = self.view.line(prev_point) prev_indent = self.view.substr(prev_line) prev_indent = re.match('[ \t]*', prev_indent).group(0) if (not pressed or pressed == l_brace) and len(indent) > len(prev_indent) and indent[len(prev_indent):] == tab: # move region.a back by 'indent' amount region = Region(region.a - len(tab), region.b - len(tab)) # and remove the tab self.view.replace(edit, Region(region.a, region.a + len(tab) - 1), '') elif pressed and pressed == r_brace: if len(indent) == len(prev_indent): # move region.a back by 'indent' amount region = Region(region.a - len(tab), region.b - len(tab)) # and remove the tab self.view.replace(edit, Region(region.a, region.a + len(tab) - 1), '') insert_braces = r_brace elif pressed and pressed != l_brace: # we pressed the closing bracket or quote. This *never* insert_braces = r_brace if insert_braces: self.view.insert(edit, region.a, insert_braces) self.view.sel().add(Region(region.a + length, region.a + length)) elif selection in QUOTING_BRACKETS and pressed in QUOTING_BRACKETS and selection != pressed: # changing a quote from single <=> double, just insert the quote. self.view.replace(edit, region, pressed) self.view.sel().add(Region(region.end(), region.end())) elif pressed and pressed != l_brace: b = region.begin() + len(r_brace) self.view.replace(edit, region, r_brace) self.view.sel().add(Region(b, b)) else: substitute = self.view.substr(region) replacement = l_brace + substitute + r_brace # if we're inserting "real" brackets, not quotes: real_brackets = l_brace in OPENING_BRACKETS and r_brace in CLOSING_BRACKETS # check to see if entire lines are selected, and if so do some smart indenting bol_is_nl = region.begin() == 0 or self.view.substr(region.begin() - 1) == "\n" eol_is_nl = region.end() == self.view.size() - 1 or self.view.substr(region.end() - 1) == "\n" if real_brackets and bol_is_nl and eol_is_nl: indent = '' final = '' m = re.match('([ \t]*)' + tab, self.view.substr(region)) if m: indent = m.group(1) final = "\n" else: substitute = tab + substitute replacement = indent + l_brace + "\n" + substitute + indent + r_brace + final b = region.begin() + len(replacement) - len("\n" + indent + r_brace + final) else: b = region.begin() + len(replacement) if replace and self.view.substr(region.begin() - 1) in OPENING_BRACKET_LIKE and self.view.substr(region.end()) in CLOSING_BRACKET_LIKE: b -= 1 self.view.replace(edit, Region(region.begin() - 1, region.end() + 1), replacement) elif replace and self.view.substr(region.begin()) in OPENING_BRACKET_LIKE and self.view.substr(region.end() - 1) in CLOSING_BRACKET_LIKE: replacement = l_brace + replacement[2:-2] + r_brace b -= 2 self.view.replace(edit, region, replacement) l_brace = r_brace = '' else: self.view.replace(edit, region, replacement) if select: self.view.sel().add(Region(b - len(replacement) + len(l_brace), b - len(r_brace))) else: self.view.sel().add(Region(b, b))
def region_to_range(view: sublime.View, region: sublime.Region) -> 'Range': return Range(offset_to_point(view, region.begin()), offset_to_point(view, region.end()))
def uncomment_region(view, edit, region): begin = region.begin() end = region.end() - 1 # We will loop backwards, this means that it will hit the closing # punctuation for block comments first. i = end + 1 while i > begin: i -= 1 scopes = view.scope_name(i) # Not a punctuation, ignore it. if 'punctuation.definition.comment' not in scopes: continue # Found the second forward slash for the “// ” comment. if 'comment.line' in scopes: punctuation_region = generate_comment_punctuation_region(view, i) view.erase(edit, punctuation_region) i = punctuation_region.begin() continue # We found the beginning of the block comment first, this means that # there’s no end to it and we can easily remove it. It can be “/* ”, # “/** ”, “{/* ” or “{/** ”. if 'punctuation.definition.comment.begin' in scopes: punctuation_region = generate_jsjsx_comment_punctuation_region( view, i ) view.erase(edit, punctuation_region) i = punctuation_region.begin() continue # We are looping backwards, so it is expected to find the closing # punctuation first which can be “ */” or “ */}”. possible_jsx_comment = False if i < view.size() and is_jsx_close_brace(view, i + 1): possible_jsx_comment = True closing_punctuation_region = generate_comment_punctuation_region( view, i ) # Move the cursor 1 character after the beginning punctuation. i = scan_reverse(view, i, not_predicate(has_scope_predicate( 'punctuation.definition.comment.begin' ))) open_punctuation_region = generate_comment_punctuation_region( view, i - 1 ) # Correct the regions to include the JSX braces if necessary. if possible_jsx_comment: if is_jsx_open_brace(view, open_punctuation_region.begin() - 1): open_punctuation_region = Region( open_punctuation_region.begin() - 1, open_punctuation_region.end() ) closing_punctuation_region = Region( closing_punctuation_region.begin(), closing_punctuation_region.end() + 1 ) view.erase(edit, closing_punctuation_region) view.erase(edit, open_punctuation_region) # Move the cursor to the beginning of the block to “consume” it. i = open_punctuation_region.begin()