def setup(self, override_thresh=False, count_lines=False, adj_only=None, ignore={}, plugin={}): """ Initialize class settings from settings file and inputs. """ # Init view params self.last_id_view = None self.last_id_sel = None self.view_tracker = (None, None) self.ignore_threshold = override_thresh or bool(self.settings.get("ignore_threshold", False)) self.adj_only = adj_only if adj_only is not None else bool(self.settings.get("match_only_adjacent", False)) self.auto_selection_threshold = int(self.settings.get("auto_selection_threshold", 10)) self.no_multi_select_icons = bool(self.settings.get("no_multi_select_icons", False)) self.count_lines = count_lines self.default_string_escape_mode = str(self.settings.get('bracket_string_escape_mode', "string")) self.show_unmatched = bool(self.settings.get("show_unmatched", True)) # Init bracket objects self.bracket_types = self.settings.get("brackets", []) self.scope_types = self.settings.get("scope_brackets", []) # Init selection params self.use_selection_threshold = True self.selection_threshold = int(self.settings.get("search_threshold", 5000)) self.new_select = False self.loaded_modules = set([]) # High Visibility options self.hv_style = select_bracket_style(self.settings.get("high_visibility_style", "outline")) self.hv_underline = self.hv_style & sublime.DRAW_EMPTY_AS_OVERWRITE self.hv_color = self.settings.get("high_visibility_color", HV_RSVD_VALUES[1]) # Init plugin self.plugin = None self.transform = set([]) if 'command' in plugin: self.plugin = BracketPlugin(plugin, self.loaded_modules) self.new_select = True if 'type' in plugin: for t in plugin["type"]: self.transform.add(t)
def setup(self, override_thresh=False, count_lines=False, adj_only=None, ignore={}, plugin={}): """ Initialize class settings from settings file and inputs. """ # Init view params self.last_id_view = None self.last_id_sel = None self.view_tracker = (None, None) self.ignore_threshold = override_thresh self.adj_only = adj_only if adj_only is not None else bool(self.settings.get("match_only_adjacent", False)) self.auto_selection_threshold = int(self.settings.get("auto_selection_threshold", 10)) self.no_multi_select_icons = bool(self.settings.get("no_multi_select_icons", False)) self.count_lines = count_lines self.default_string_escape_mode = str(self.settings.get('bracket_string_escape_mode', "string")) self.show_unmatched = bool(self.settings.get("show_unmatched", True)) # Init bracket objects self.bracket_types = self.settings.get("brackets", []) self.scope_types = self.settings.get("scope_brackets", []) # Init selection params self.use_selection_threshold = True self.selection_threshold = int(self.settings.get("search_threshold", 5000)) self.new_select = False self.loaded_modules = set([]) # High Visibility options self.hv_style = select_bracket_style(self.settings.get("high_visibility_style", "outline")) self.hv_underline = self.hv_style & sublime.DRAW_EMPTY_AS_OVERWRITE self.hv_color = self.settings.get("high_visibility_color", HV_RSVD_VALUES[1]) # Init plugin self.plugin = None self.transform = set([]) if 'command' in plugin: self.plugin = BracketPlugin(plugin, self.loaded_modules) self.new_select = True if 'type' in plugin: for t in plugin["type"]: self.transform.add(t)
class BhCore(object): """ Bracket matching class. """ plugin_reload = False def __init__(self, override_thresh=False, count_lines=False, adj_only=None, ignore={}, plugin={}, keycommand=False): """ Load settings and setup reload events if settings changes. """ self.settings = sublime.load_settings("bh_core.sublime-settings") self.keycommand = keycommand if not keycommand: self.settings.clear_on_change('reload') self.settings.add_on_change('reload', self.setup) self.setup(override_thresh, count_lines, adj_only, ignore, plugin) def setup(self, override_thresh=False, count_lines=False, adj_only=None, ignore={}, plugin={}): """ Initialize class settings from settings file and inputs. """ # Init view params self.last_id_view = None self.last_id_sel = None self.view_tracker = (None, None) self.ignore_threshold = override_thresh self.adj_only = adj_only if adj_only is not None else bool(self.settings.get("match_only_adjacent", False)) self.auto_selection_threshold = int(self.settings.get("auto_selection_threshold", 10)) self.no_multi_select_icons = bool(self.settings.get("no_multi_select_icons", False)) self.count_lines = count_lines self.default_string_escape_mode = str(self.settings.get('bracket_string_escape_mode', "string")) self.show_unmatched = bool(self.settings.get("show_unmatched", True)) # Init bracket objects self.bracket_types = self.settings.get("brackets", []) self.scope_types = self.settings.get("scope_brackets", []) # Init selection params self.use_selection_threshold = True self.selection_threshold = int(self.settings.get("search_threshold", 5000)) self.new_select = False self.loaded_modules = set([]) # High Visibility options self.hv_style = select_bracket_style(self.settings.get("high_visibility_style", "outline")) self.hv_underline = self.hv_style & sublime.DRAW_EMPTY_AS_OVERWRITE self.hv_color = self.settings.get("high_visibility_color", HV_RSVD_VALUES[1]) # Init plugin self.plugin = None self.transform = set([]) if 'command' in plugin: self.plugin = BracketPlugin(plugin, self.loaded_modules) self.new_select = True if 'type' in plugin: for t in plugin["type"]: self.transform.add(t) def init_bracket_regions(self): """ Load up styled regions for brackets to use. """ self.bracket_regions = {} styles = self.settings.get("bracket_styles", DEFAULT_STYLES) icon_path = "Packages/BracketHighlighter/icons" # icon_path = "Theme - BracketHighlighter/icons" # Make sure default and unmatched styles in styles for key, value in DEFAULT_STYLES.items(): if key not in styles: styles[key] = value continue for k, v in value.items(): if k not in styles[key]: styles[key][k] = v # Initialize styles default_settings = styles["default"] for k, v in styles.items(): self.bracket_regions[k] = StyleDefinition(k, v, default_settings, icon_path) def is_valid_definition(self, params, language): """ Ensure bracket definition should be and can be loaded. """ return ( not exclude_bracket( params.get("enabled", True), params.get("language_filter", "blacklist"), params.get("language_list", []), language ) and params["open"] is not None and params["close"] is not None ) def init_brackets(self, language): """ Initialize bracket match definition objects from settings file. """ self.find_regex = [] self.sub_find_regex = [] self.index_open = {} self.index_close = {} self.brackets = [] self.scopes = [] self.view_tracker = (language, self.view.id()) self.enabled = False self.sels = [] self.multi_select = False scopes = {} loaded_modules = self.loaded_modules.copy() for params in self.bracket_types: if self.is_valid_definition(params, language): try: load_modules(params, loaded_modules) entry = BracketDefinition(params) self.brackets.append(entry) if not entry.find_in_sub_search_only: self.find_regex.append(params["open"]) self.find_regex.append(params["close"]) else: self.find_regex.append(r"([^\s\S])") self.find_regex.append(r"([^\s\S])") if entry.find_in_sub_search: self.sub_find_regex.append(params["open"]) self.sub_find_regex.append(params["close"]) else: self.sub_find_regex.append(r"([^\s\S])") self.sub_find_regex.append(r"([^\s\S])") except Exception as e: bh_logging(e) scope_count = 0 for params in self.scope_types: if self.is_valid_definition(params, language): try: load_modules(params, loaded_modules) entry = ScopeDefinition(params) for x in entry.scopes: if x not in scopes: scopes[x] = scope_count scope_count += 1 self.scopes.append({"name": x, "brackets": [entry]}) else: self.scopes[scopes[x]]["brackets"].append(entry) except Exception as e: bh_logging(e) if len(self.brackets): bh_debug( "Search patterns:\n" + "(?:%s)\n" % '|'.join(self.find_regex) + "(?:%s)" % '|'.join(self.sub_find_regex) ) self.sub_pattern = re.compile("(?:%s)" % '|'.join(self.sub_find_regex), re.MULTILINE | re.IGNORECASE) self.pattern = re.compile("(?:%s)" % '|'.join(self.find_regex), re.MULTILINE | re.IGNORECASE) self.enabled = True def init_match(self): """ Initialize matching for the current view's syntax. """ self.chars = 0 self.lines = 0 syntax = self.view.settings().get('syntax') language = basename(syntax).replace('.tmLanguage', '').lower() if syntax != None else "plain text" if language != self.view_tracker[0] or self.view.id() != self.view_tracker[1]: self.init_bracket_regions() self.init_brackets(language) else: for r in self.bracket_regions.values(): r.selections = [] r.open_selections = [] r.close_selections = [] r.center_selections = [] def unique(self): """ Check if the current selection(s) is different from the last. """ id_view = self.view.id() id_sel = "".join([str(sel.a) for sel in self.view.sel()]) is_unique = False if id_view != self.last_id_view or id_sel != self.last_id_sel: self.last_id_view = id_view self.last_id_sel = id_sel is_unique = True return is_unique def store_sel(self, regions): """ Store the current selection selection to be set at the end. """ if self.new_select: for region in regions: self.sels.append(region) def change_sel(self): """ Change the view's selections. """ if self.new_select and len(self.sels) > 0: if self.multi_select == False: self.view.show(self.sels[0]) self.view.sel().clear() self.view.sel().add_all(self.sels) def hv_highlight_color(self, b_value): """ High visibility highlight decesions. """ color = self.hv_color if self.hv_color == HV_RSVD_VALUES[0]: color = self.bracket_regions["default"].color elif self.hv_color == HV_RSVD_VALUES[1]: color = b_value return color def highlight_regions(self, name, icon_type, selections, bracket, regions): """ Apply the highlightes for the highlight region. """ if len(selections): self.view.add_regions( name, getattr(bracket, selections), self.hv_highlight_color(bracket.color) if HIGH_VISIBILITY else bracket.color, getattr(bracket, icon_type), self.hv_style if HIGH_VISIBILITY else bracket.style ) regions.append(name) def highlight(self, view): """ Highlight all bracket regions. """ for region_key in self.view.settings().get("bh_regions", []): self.view.erase_regions(region_key) regions = [] icon_type = "no_icon" open_icon_type = "no_icon" close_icon_type = "no_icon" if not self.no_multi_select_icons or not self.multi_select: icon_type = "small_icon" if self.view.line_height() < 16 else "icon" open_icon_type = "small_open_icon" if self.view.line_height() < 16 else "open_icon" close_icon_type = "small_close_icon" if self.view.line_height() < 16 else "close_icon" for name, r in self.bracket_regions.items(): self.highlight_regions("bh_" + name, icon_type, "selections", r, regions) self.highlight_regions("bh_" + name + "_center", "no_icon", "center_selections", r, regions) self.highlight_regions("bh_" + name + "_open", open_icon_type, "open_selections", r, regions) self.highlight_regions("bh_" + name + "_close", close_icon_type, "close_selections", r, regions) # Track which regions were set in the view so that they can be cleaned up later. self.view.settings().set("bh_regions", regions) def get_search_bfr(self, sel): """ Read in the view's buffer for scanning for brackets etc. """ # Determine how much of the buffer to search view_min = 0 view_max = self.view.size() if not self.ignore_threshold: left_delta = sel.a - view_min right_delta = view_max - sel.a limit = self.selection_threshold / 2 rpad = limit - left_delta if left_delta < limit else 0 lpad = limit - right_delta if right_delta < limit else 0 llimit = limit + lpad rlimit = limit + rpad self.search_window = ( sel.a - llimit if left_delta >= llimit else view_min, sel.a + rlimit if right_delta >= rlimit else view_max ) else: self.search_window = (0, view_max) # Search Buffer return self.view.substr(sublime.Region(0, view_max)) def match(self, view, force_match=True): """ Preform matching brackets surround the selection(s) """ if view == None: return if not GLOBAL_ENABLE: for region_key in view.settings().get("bh_regions", []): view.erase_regions(region_key) return if self.keycommand: BhCore.plugin_reload = True if not self.keycommand and BhCore.plugin_reload: self.setup() BhCore.plugin_reload = False # Setup views self.view = view self.last_view = view num_sels = len(view.sel()) self.multi_select = (num_sels > 1) if self.unique() or force_match: # Initialize self.init_match() # Nothing to search for if not self.enabled: return # Abort if selections are beyond the threshold if self.use_selection_threshold and num_sels >= self.selection_threshold: self.highlight(view) return multi_select_count = 0 # Process selections. for sel in view.sel(): bfr = self.get_search_bfr(sel) if not self.ignore_threshold and multi_select_count >= self.auto_selection_threshold: self.store_sel([sel]) multi_select_count += 1 continue if not self.find_scopes(bfr, sel): self.sub_search_mode = False self.find_matches(bfr, sel) multi_select_count += 1 # Highlight, focus, and display lines etc. self.change_sel() self.highlight(view) if self.count_lines: sublime.status_message('In Block: Lines ' + str(self.lines) + ', Chars ' + str(self.chars)) def save_incomplete_regions(self, left, right, regions): """ Store single incomplete brackets for highlighting. """ found = left if left is not None else right bracket = self.bracket_regions["unmatched"] if bracket.underline: bracket.selections += underline((found.toregion(),)) else: bracket.selections += [found.toregion()] self.store_sel(regions) def save_regions(self, left, right, regions): """ Saved matched regions. Perform any special considerations for region formatting. """ bracket = self.bracket_regions.get(self.bracket_style, self.bracket_regions["default"]) lines = abs(self.view.rowcol(right.begin)[0] - self.view.rowcol(left.end)[0] + 1) if self.count_lines: self.chars += abs(right.begin - left.end) self.lines += lines if HIGH_VISIBILITY: if lines <= 1: if self.hv_underline: bracket.selections += underline((sublime.Region(left.begin, right.end),)) else: bracket.selections += [sublime.Region(left.begin, right.end)] else: bracket.open_selections += [sublime.Region(left.begin)] if self.hv_underline: bracket.center_selections += underline((sublime.Region(left.begin + 1, right.end - 1),)) else: bracket.center_selections += [sublime.Region(left.begin, right.end)] bracket.close_selections += [sublime.Region(right.begin)] elif bracket.underline: if lines <= 1: bracket.selections += underline((left.toregion(), right.toregion())) else: bracket.open_selections += [sublime.Region(left.begin)] bracket.close_selections += [sublime.Region(right.begin)] if left.size(): bracket.center_selections += underline((sublime.Region(left.begin + 1, left.end),)) if right.size(): bracket.center_selections += underline((sublime.Region(right.begin + 1, right.end),)) else: if lines <= 1: bracket.selections += [left.toregion(), right.toregion()] else: bracket.open_selections += [left.toregion()] bracket.close_selections += [right.toregion()] self.store_sel(regions) def sub_search(self, sel, search_window, bfr, scope=None): """ Search a scope bracket match for bracekts within. """ bracket = None left, right = self.match_brackets(bfr, search_window, sel, scope) regions = [sublime.Region(sel.a, sel.b)] if left is not None and right is not None: bracket = self.brackets[left.type] left, right, regions = self.run_plugin(bracket.name, left, right, regions) # Matched brackets if left is not None and right is not None and bracket is not None: self.save_regions(left, right, regions) return True return False def find_scopes(self, bfr, sel): """ Find brackets by scope definition. """ # Search buffer left, right, bracket, sub_matched = self.match_scope_brackets(bfr, sel) if sub_matched: return True regions = [sublime.Region(sel.a, sel.b)] if left is not None and right is not None: left, right, regions = self.run_plugin(bracket.name, left, right, regions) if left is None and right is None: self.store_sel(regions) return True if left is not None and right is not None: self.save_regions(left, right, regions) return True elif (left is not None or right is not None) and self.show_invalid: self.save_incomplete_regions(left, right, regions) return True return False def find_matches(self, bfr, sel): """ Find bracket matches """ bracket = None left, right = self.match_brackets(bfr, self.search_window, sel) regions = [sublime.Region(sel.a, sel.b)] if left is not None and right is not None: bracket = self.brackets[left.type] left, right, regions = self.run_plugin(bracket.name, left, right, regions) # Matched brackets if left is not None and right is not None and bracket is not None: self.save_regions(left, right, regions) # Unmatched brackets elif (left is not None or right is not None) and self.show_unmatched: self.save_incomplete_regions(left, right, regions) else: self.store_sel(regions) def escaped(self, pt, ignore_string_escape, scope): """ Check if sub bracket in string scope is escaped. """ if not ignore_string_escape: return False if scope and scope.startswith("string"): return self.string_escaped(pt) return False def string_escaped(self, pt): """ Check if bracket is follows escaping characters. Account for if in string or regex string scope. """ escaped = False start = pt - 1 first = False if self.view.settings().get("bracket_string_escape_mode", self.default_string_escape_mode) == "string": first = True while self.view.substr(start) == "\\": if first: first = False else: escaped = False if escaped else True start -= 1 return escaped def is_illegal_scope(self, pt, bracket_id, scope=None): """ Check if scope at pt X should be ignored. """ bracket = self.brackets[bracket_id] if self.sub_search_mode and not bracket.find_in_sub_search: return True illegal_scope = False # Scope sent in, so we must be scanning whatever this scope is if scope != None: if self.escaped(pt, bracket.ignore_string_escape, scope): illegal_scope = True return illegal_scope # for exception in bracket.scope_exclude_exceptions: elif len(bracket.scope_exclude_exceptions) and self.view.match_selector(pt, ", ".join(bracket.scope_exclude_exceptions)): pass elif len(bracket.scope_exclude) and self.view.match_selector(pt, ", ".join(bracket.scope_exclude)): illegal_scope = True return illegal_scope def compare(self, first, second, bfr, scope_bracket=False): """ Compare brackets. This function allows bracket plugins to add aditional logic. """ if scope_bracket: match = first is not None and second is not None else: match = first.type == second.type if match: bracket = self.scopes[first.scope]["brackets"][first.type] if scope_bracket else self.brackets[first.type] try: if bracket.compare is not None and match: match = bracket.compare( bracket.name, BracketRegion(first.begin, first.end), BracketRegion(second.begin, second.end), bfr ) except: bh_logging("Plugin Compare Error:\n%s" % str(traceback.format_exc())) return match def post_match(self, left, right, center, bfr, scope_bracket=False): """ Peform special logic after a match has been made. This function allows bracket plugins to add aditional logic. """ if left is not None: if scope_bracket: bracket = self.scopes[left.scope]["brackets"][left.type] bracket_scope = left.scope else: bracket = self.brackets[left.type] bracket_type = left.type elif right is not None: if scope_bracket: bracket = self.scopes[right.scope]["brackets"][right.type] bracket_scope = right.scope else: bracket = self.brackets[right.type] bracket_type = right.type else: return left, right self.bracket_style = bracket.style if bracket.post_match is not None: try: lbracket, rbracket, self.bracket_style = bracket.post_match( self.view, bracket.name, bracket.style, BracketRegion(left.begin, left.end) if left is not None else None, BracketRegion(right.begin, right.end) if right is not None else None, center, bfr, self.search_window ) if scope_bracket: left = ScopeEntry(lbracket.begin, lbracket.end, bracket_scope, bracket_type) if lbracket is not None else None right = ScopeEntry(rbracket.begin, rbracket.end, bracket_scope, bracket_type) if rbracket is not None else None else: left = BracketEntry(lbracket.begin, lbracket.end, bracket_type) if lbracket is not None else None right = BracketEntry(rbracket.begin, rbracket.end, bracket_type) if rbracket is not None else None except: bh_logging("Plugin Post Match Error:\n%s" % str(traceback.format_exc())) return left, right def run_plugin(self, name, left, right, regions): """ Run a bracket plugin. """ lbracket = BracketRegion(left.begin, left.end) rbracket = BracketRegion(right.begin, right.end) if ( ("__all__" in self.transform or name in self.transform) and self.plugin != None and self.plugin.is_enabled() ): lbracket, rbracket, regions = self.plugin.run_command(self.view, name, lbracket, rbracket, regions) left = left.move(lbracket.begin, lbracket.end) if lbracket is not None else None right = right.move(rbracket.begin, rbracket.end) if rbracket is not None else None return left, right, regions def match_scope_brackets(self, bfr, sel): """ See if scope should be searched, and then check endcaps to determine if valid scope bracket. """ center = sel.a left = None right = None scope_count = 0 before_center = center - 1 bracket_count = 0 partial_find = None max_size = self.view.size() - 1 selected_scope = None bracket = None # Cannot be inside a bracket pair if cursor is at zero if center == 0: return left, right, selected_scope, False # Identify if the cursor is in a scope with bracket definitions for s in self.scopes: scope = s["name"] extent = None exceed_limit = False if self.view.match_selector(center, scope) and self.view.match_selector(before_center, scope): extent = self.view.extract_scope(center) while not exceed_limit and extent.begin() != 0: if self.view.match_selector(extent.begin() - 1, scope): extent = extent.cover(self.view.extract_scope(extent.begin() - 1)) if extent.begin() < self.search_window[0] or extent.end() > self.search_window[1]: extent = None exceed_limit = True else: break while not exceed_limit and extent.end() != max_size: if self.view.match_selector(extent.end(), scope): extent = extent.cover(self.view.extract_scope(extent.end())) if extent.begin() < self.search_window[0] or extent.end() > self.search_window[1]: extent = None exceed_limit = True else: break if extent is None: scope_count += 1 continue # Search the bracket patterns of this scope # to determine if this scope matches the rules. bracket_count = 0 scope_bfr = bfr[extent.begin():extent.end()] for b in s["brackets"]: m = b.open.search(scope_bfr) if m and m.group(1): left = ScopeEntry(extent.begin() + m.start(1), extent.begin() + m.end(1), scope_count, bracket_count) m = b.close.search(scope_bfr) if m and m.group(1): right = ScopeEntry(extent.begin() + m.start(1), extent.begin() + m.end(1), scope_count, bracket_count) if not self.compare(left, right, bfr, scope_bracket=True): left, right = None, None # Track partial matches. If a full match isn't found, # return the first partial match at the end. if partial_find is None and bool(left) != bool(right): partial_find = (left, right) left = None right = None if left and right: break bracket_count += 1 if left and right: break scope_count += 1 # Full match not found. Return partial match (if any). if (left is None or right is None) and partial_find is not None: left, right = partial_find[0], partial_find[1] # Make sure cursor in highlighted sub group if (left and center <= left.begin) or (right and center >= right.end): left, right = None, None if left is not None: selected_scope = self.scopes[left.scope]["name"] elif right is not None: selected_scope = self.scopes[right.scope]["name"] if left is not None and right is not None: bracket = self.scopes[left.scope]["brackets"][left.type] if bracket.sub_search: self.sub_search_mode = True if self.sub_search(sel, (left.begin, right.end), bfr, scope): return left, right, self.brackets[left.type], True elif bracket.sub_search_only: left, right, bracket = None, None, None if self.adj_only: left, right = self.adjacent_check(left, right, center) left, right = self.post_match(left, right, center, bfr, scope_bracket=True) return left, right, bracket, False def match_brackets(self, bfr, window, sel, scope=None): """ Regex bracket matching. """ center = sel.a left = None right = None stack = [] pattern = self.pattern if not self.sub_search_mode else self.sub_pattern bsearch = BracketSearch(bfr, window, center, pattern, self.is_illegal_scope, scope) for o in bsearch.get_open(BracketSearchSide.left): if len(stack) and bsearch.is_done(BracektSearchType.closing): if self.compare(o, stack[-1], bfr): stack.pop() continue for c in bsearch.get_close(BracketSearchSide.left): if o.end <= c.begin: stack.append(c) continue elif len(stack): bsearch.remember(BracektSearchType.closing) break if len(stack): b = stack.pop() if self.compare(o, b, bfr): continue else: left = o break bsearch.reset_end_state() stack = [] # Grab each closest closing right side bracket and attempt to match it. # If the closing bracket cannot be matched, select it. for c in bsearch.get_close(BracketSearchSide.right): if len(stack) and bsearch.is_done(BracektSearchType.opening): if self.compare(stack[-1], c, bfr): stack.pop() continue for o in bsearch.get_open(BracketSearchSide.right): if o.end <= c.begin: stack.append(o) continue else: bsearch.remember(BracektSearchType.opening) break if len(stack): b = stack.pop() if self.compare(b, c, bfr): continue else: if left is None or self.compare(left, c, bfr): right = c break if self.adj_only: left, right = self.adjacent_check(left, right, center) return self.post_match(left, right, center, bfr) def adjacent_check(self, left, right, center): if left and right: if left.end < center < right.begin: left, right = None, None elif (left and left.end < center) or (right and center < right.begin): left, right = None, None return left, right
class BhCore(object): """ Bracket matching class. """ plugin_reload = False def __init__(self, override_thresh=False, count_lines=False, adj_only=None, ignore={}, plugin={}, keycommand=False): """ Load settings and setup reload events if settings changes. """ self.settings = sublime.load_settings("bh_core.sublime-settings") self.keycommand = keycommand if not keycommand: self.settings.clear_on_change('reload') self.settings.add_on_change('reload', self.setup) self.setup(override_thresh, count_lines, adj_only, ignore, plugin) def setup(self, override_thresh=False, count_lines=False, adj_only=None, ignore={}, plugin={}): """ Initialize class settings from settings file and inputs. """ # Init view params self.last_id_view = None self.last_id_sel = None self.view_tracker = (None, None) self.ignore_threshold = override_thresh or bool(self.settings.get("ignore_threshold", False)) self.adj_only = adj_only if adj_only is not None else bool(self.settings.get("match_only_adjacent", False)) self.auto_selection_threshold = int(self.settings.get("auto_selection_threshold", 10)) self.no_multi_select_icons = bool(self.settings.get("no_multi_select_icons", False)) self.count_lines = count_lines self.default_string_escape_mode = str(self.settings.get('bracket_string_escape_mode', "string")) self.show_unmatched = bool(self.settings.get("show_unmatched", True)) # Init bracket objects self.bracket_types = self.settings.get("brackets", []) self.scope_types = self.settings.get("scope_brackets", []) # Init selection params self.use_selection_threshold = True self.selection_threshold = int(self.settings.get("search_threshold", 5000)) self.new_select = False self.loaded_modules = set([]) # High Visibility options self.hv_style = select_bracket_style(self.settings.get("high_visibility_style", "outline")) self.hv_underline = self.hv_style & sublime.DRAW_EMPTY_AS_OVERWRITE self.hv_color = self.settings.get("high_visibility_color", HV_RSVD_VALUES[1]) # Init plugin self.plugin = None self.transform = set([]) if 'command' in plugin: self.plugin = BracketPlugin(plugin, self.loaded_modules) self.new_select = True if 'type' in plugin: for t in plugin["type"]: self.transform.add(t) def init_bracket_regions(self): """ Load up styled regions for brackets to use. """ self.bracket_regions = {} styles = self.settings.get("bracket_styles", DEFAULT_STYLES) icon_path = "Packages/BracketHighlighter/icons" # icon_path = "Theme - BracketHighlighter/icons" # Make sure default and unmatched styles in styles for key, value in DEFAULT_STYLES.items(): if key not in styles: styles[key] = value continue for k, v in value.items(): if k not in styles[key]: styles[key][k] = v # Initialize styles default_settings = styles["default"] for k, v in styles.items(): self.bracket_regions[k] = StyleDefinition(k, v, default_settings, icon_path) def is_valid_definition(self, params, language): """ Ensure bracket definition should be and can be loaded. """ return ( not exclude_bracket( params.get("enabled", True), params.get("language_filter", "blacklist"), params.get("language_list", []), language ) and params["open"] is not None and params["close"] is not None ) def init_brackets(self, language): """ Initialize bracket match definition objects from settings file. """ self.find_regex = [] self.sub_find_regex = [] self.index_open = {} self.index_close = {} self.brackets = [] self.scopes = [] self.view_tracker = (language, self.view.id()) self.enabled = False self.sels = [] self.multi_select = False scopes = {} loaded_modules = self.loaded_modules.copy() for params in self.bracket_types: if self.is_valid_definition(params, language): try: load_modules(params, loaded_modules) entry = BracketDefinition(params) self.brackets.append(entry) if not entry.find_in_sub_search_only: self.find_regex.append(params["open"]) self.find_regex.append(params["close"]) else: self.find_regex.append(r"([^\s\S])") self.find_regex.append(r"([^\s\S])") if entry.find_in_sub_search: self.sub_find_regex.append(params["open"]) self.sub_find_regex.append(params["close"]) else: self.sub_find_regex.append(r"([^\s\S])") self.sub_find_regex.append(r"([^\s\S])") except Exception as e: bh_logging(e) scope_count = 0 for params in self.scope_types: if self.is_valid_definition(params, language): try: load_modules(params, loaded_modules) entry = ScopeDefinition(params) for x in entry.scopes: if x not in scopes: scopes[x] = scope_count scope_count += 1 self.scopes.append({"name": x, "brackets": [entry]}) else: self.scopes[scopes[x]]["brackets"].append(entry) except Exception as e: bh_logging(e) if len(self.brackets): bh_debug( "Search patterns:\n" + "(?:%s)\n" % '|'.join(self.find_regex) + "(?:%s)" % '|'.join(self.sub_find_regex) ) self.sub_pattern = ure.compile("(?:%s)" % '|'.join(self.sub_find_regex), ure.MULTILINE | ure.IGNORECASE) self.pattern = ure.compile("(?:%s)" % '|'.join(self.find_regex), ure.MULTILINE | ure.IGNORECASE) self.enabled = True def init_match(self): """ Initialize matching for the current view's syntax. """ self.chars = 0 self.lines = 0 syntax = self.view.settings().get('syntax') language = basename(syntax).replace('.tmLanguage', '').lower() if syntax != None else "plain text" if language != self.view_tracker[0] or self.view.id() != self.view_tracker[1]: self.init_bracket_regions() self.init_brackets(language) else: for r in self.bracket_regions.values(): r.selections = [] r.open_selections = [] r.close_selections = [] r.center_selections = [] def unique(self): """ Check if the current selection(s) is different from the last. """ id_view = self.view.id() id_sel = "".join([str(sel.a) for sel in self.view.sel()]) is_unique = False if id_view != self.last_id_view or id_sel != self.last_id_sel: self.last_id_view = id_view self.last_id_sel = id_sel is_unique = True return is_unique def store_sel(self, regions): """ Store the current selection selection to be set at the end. """ if self.new_select: for region in regions: self.sels.append(region) def change_sel(self): """ Change the view's selections. """ if self.new_select and len(self.sels) > 0: if self.multi_select == False: self.view.show(self.sels[0]) self.view.sel().clear() self.view.sel().add_all(self.sels) def hv_highlight_color(self, b_value): """ High visibility highlight decesions. """ color = self.hv_color if self.hv_color == HV_RSVD_VALUES[0]: color = self.bracket_regions["default"].color elif self.hv_color == HV_RSVD_VALUES[1]: color = b_value return color def highlight_regions(self, name, icon_type, selections, bracket, regions): """ Apply the highlightes for the highlight region. """ if len(selections): self.view.add_regions( name, getattr(bracket, selections), self.hv_highlight_color(bracket.color) if HIGH_VISIBILITY else bracket.color, getattr(bracket, icon_type), self.hv_style if HIGH_VISIBILITY else bracket.style ) regions.append(name) def highlight(self, view): """ Highlight all bracket regions. """ for region_key in self.view.settings().get("bh_regions", []): self.view.erase_regions(region_key) regions = [] icon_type = "no_icon" open_icon_type = "no_icon" close_icon_type = "no_icon" if not self.no_multi_select_icons or not self.multi_select: icon_type = "small_icon" if self.view.line_height() < 16 else "icon" open_icon_type = "small_open_icon" if self.view.line_height() < 16 else "open_icon" close_icon_type = "small_close_icon" if self.view.line_height() < 16 else "close_icon" for name, r in self.bracket_regions.items(): self.highlight_regions("bh_" + name, icon_type, "selections", r, regions) self.highlight_regions("bh_" + name + "_center", "no_icon", "center_selections", r, regions) self.highlight_regions("bh_" + name + "_open", open_icon_type, "open_selections", r, regions) self.highlight_regions("bh_" + name + "_close", close_icon_type, "close_selections", r, regions) # Track which regions were set in the view so that they can be cleaned up later. self.view.settings().set("bh_regions", regions) def get_search_bfr(self, sel): """ Read in the view's buffer for scanning for brackets etc. """ # Determine how much of the buffer to search view_min = 0 view_max = self.view.size() if not self.ignore_threshold: left_delta = sel.a - view_min right_delta = view_max - sel.a limit = self.selection_threshold / 2 rpad = limit - left_delta if left_delta < limit else 0 lpad = limit - right_delta if right_delta < limit else 0 llimit = limit + lpad rlimit = limit + rpad self.search_window = ( sel.a - llimit if left_delta >= llimit else view_min, sel.a + rlimit if right_delta >= rlimit else view_max ) else: self.search_window = (0, view_max) # Search Buffer return self.view.substr(sublime.Region(0, view_max)) def match(self, view, force_match=True): """ Preform matching brackets surround the selection(s) """ if view == None: return view.settings().set("BracketHighlighterBusy", True) if not GLOBAL_ENABLE: for region_key in view.settings().get("bh_regions", []): view.erase_regions(region_key) view.settings().set("BracketHighlighterBusy", False) return if self.keycommand: BhCore.plugin_reload = True if not self.keycommand and BhCore.plugin_reload: self.setup() BhCore.plugin_reload = False # Setup views self.view = view self.last_view = view num_sels = len(view.sel()) self.multi_select = (num_sels > 1) if self.unique() or force_match: # Initialize self.init_match() # Nothing to search for if not self.enabled: view.settings().set("BracketHighlighterBusy", False) return # Abort if selections are beyond the threshold if self.use_selection_threshold and num_sels >= self.selection_threshold: self.highlight(view) view.settings().set("BracketHighlighterBusy", False) return multi_select_count = 0 # Process selections. for sel in view.sel(): bfr = self.get_search_bfr(sel) if not self.ignore_threshold and multi_select_count >= self.auto_selection_threshold: self.store_sel([sel]) multi_select_count += 1 continue if not self.find_scopes(bfr, sel): self.sub_search_mode = False self.find_matches(bfr, sel) multi_select_count += 1 # Highlight, focus, and display lines etc. self.change_sel() self.highlight(view) if self.count_lines: sublime.status_message('In Block: Lines ' + str(self.lines) + ', Chars ' + str(self.chars)) view.settings().set("BracketHighlighterBusy", False) def save_incomplete_regions(self, left, right, regions): """ Store single incomplete brackets for highlighting. """ found = left if left is not None else right bracket = self.bracket_regions["unmatched"] if bracket.underline: bracket.selections += underline((found.toregion(),)) else: bracket.selections += [found.toregion()] self.store_sel(regions) def save_regions(self, left, right, regions): """ Saved matched regions. Perform any special considerations for region formatting. """ bracket = self.bracket_regions.get(self.bracket_style, self.bracket_regions["default"]) lines = abs(self.view.rowcol(right.begin)[0] - self.view.rowcol(left.end)[0] + 1) if self.count_lines: self.chars += abs(right.begin - left.end) self.lines += lines if HIGH_VISIBILITY: if lines <= 1: if self.hv_underline: bracket.selections += underline((sublime.Region(left.begin, right.end),)) else: bracket.selections += [sublime.Region(left.begin, right.end)] else: bracket.open_selections += [sublime.Region(left.begin)] if self.hv_underline: bracket.center_selections += underline((sublime.Region(left.begin + 1, right.end - 1),)) else: bracket.center_selections += [sublime.Region(left.begin, right.end)] bracket.close_selections += [sublime.Region(right.begin)] elif bracket.underline: if lines <= 1: bracket.selections += underline((left.toregion(), right.toregion())) else: bracket.open_selections += [sublime.Region(left.begin)] bracket.close_selections += [sublime.Region(right.begin)] if left.size(): bracket.center_selections += underline((sublime.Region(left.begin + 1, left.end),)) if right.size(): bracket.center_selections += underline((sublime.Region(right.begin + 1, right.end),)) else: if lines <= 1: bracket.selections += [left.toregion(), right.toregion()] else: bracket.open_selections += [left.toregion()] bracket.close_selections += [right.toregion()] self.store_sel(regions) def sub_search(self, sel, search_window, bfr, scope=None): """ Search a scope bracket match for bracekts within. """ bracket = None left, right = self.match_brackets(bfr, search_window, sel, scope) regions = [sublime.Region(sel.a, sel.b)] if left is not None and right is not None: bracket = self.brackets[left.type] left, right, regions, nobracket = self.run_plugin(bracket.name, left, right, regions) if nobracket: return True # Matched brackets if left is not None and right is not None and bracket is not None: self.save_regions(left, right, regions) return True return False def find_scopes(self, bfr, sel): """ Find brackets by scope definition. """ # Search buffer left, right, bracket, sub_matched = self.match_scope_brackets(bfr, sel) if sub_matched: return True regions = [sublime.Region(sel.a, sel.b)] if left is not None and right is not None: left, right, regions, _ = self.run_plugin(bracket.name, left, right, regions) if left is None and right is None: self.store_sel(regions) return True if left is not None and right is not None: self.save_regions(left, right, regions) return True elif (left is not None or right is not None) and self.show_invalid: self.save_incomplete_regions(left, right, regions) return True return False def find_matches(self, bfr, sel): """ Find bracket matches """ bracket = None left, right = self.match_brackets(bfr, self.search_window, sel) regions = [sublime.Region(sel.a, sel.b)] if left is not None and right is not None: bracket = self.brackets[left.type] left, right, regions, _ = self.run_plugin(bracket.name, left, right, regions) # Matched brackets if left is not None and right is not None and bracket is not None: self.save_regions(left, right, regions) # Unmatched brackets elif (left is not None or right is not None) and self.show_unmatched: self.save_incomplete_regions(left, right, regions) else: self.store_sel(regions) def escaped(self, pt, ignore_string_escape, scope): """ Check if sub bracket in string scope is escaped. """ if not ignore_string_escape: return False if scope and scope.startswith("string"): return self.string_escaped(pt) return False def string_escaped(self, pt): """ Check if bracket is follows escaping characters. Account for if in string or regex string scope. """ escaped = False start = pt - 1 first = False if self.view.settings().get("bracket_string_escape_mode", self.default_string_escape_mode) == "string": first = True while self.view.substr(start) == "\\": if first: first = False else: escaped = False if escaped else True start -= 1 return escaped def is_illegal_scope(self, pt, bracket_id, scope=None): """ Check if scope at pt X should be ignored. """ bracket = self.brackets[bracket_id] if self.sub_search_mode and not bracket.find_in_sub_search: return True illegal_scope = False # Scope sent in, so we must be scanning whatever this scope is if scope != None: if self.escaped(pt, bracket.ignore_string_escape, scope): illegal_scope = True return illegal_scope # for exception in bracket.scope_exclude_exceptions: elif len(bracket.scope_exclude_exceptions) and self.view.match_selector(pt, ", ".join(bracket.scope_exclude_exceptions)): pass elif len(bracket.scope_exclude) and self.view.match_selector(pt, ", ".join(bracket.scope_exclude)): illegal_scope = True return illegal_scope def compare(self, first, second, bfr, scope_bracket=False): """ Compare brackets. This function allows bracket plugins to add aditional logic. """ if scope_bracket: match = first is not None and second is not None else: match = first.type == second.type if match: bracket = self.scopes[first.scope]["brackets"][first.type] if scope_bracket else self.brackets[first.type] try: if bracket.compare is not None and match: match = bracket.compare( bracket.name, BracketRegion(first.begin, first.end), BracketRegion(second.begin, second.end), bfr ) except: bh_logging("Plugin Compare Error:\n%s" % str(traceback.format_exc())) return match def post_match(self, left, right, center, bfr, scope_bracket=False): """ Peform special logic after a match has been made. This function allows bracket plugins to add aditional logic. """ if left is not None: if scope_bracket: bracket = self.scopes[left.scope]["brackets"][left.type] bracket_scope = left.scope else: bracket = self.brackets[left.type] bracket_type = left.type elif right is not None: if scope_bracket: bracket = self.scopes[right.scope]["brackets"][right.type] bracket_scope = right.scope else: bracket = self.brackets[right.type] bracket_type = right.type else: return left, right self.bracket_style = bracket.style if bracket.post_match is not None: try: lbracket, rbracket, self.bracket_style = bracket.post_match( self.view, bracket.name, bracket.style, BracketRegion(left.begin, left.end) if left is not None else None, BracketRegion(right.begin, right.end) if right is not None else None, center, bfr, self.search_window ) if scope_bracket: left = ScopeEntry(lbracket.begin, lbracket.end, bracket_scope, bracket_type) if lbracket is not None else None right = ScopeEntry(rbracket.begin, rbracket.end, bracket_scope, bracket_type) if rbracket is not None else None else: left = BracketEntry(lbracket.begin, lbracket.end, bracket_type) if lbracket is not None else None right = BracketEntry(rbracket.begin, rbracket.end, bracket_type) if rbracket is not None else None except: bh_logging("Plugin Post Match Error:\n%s" % str(traceback.format_exc())) return left, right def run_plugin(self, name, left, right, regions): """ Run a bracket plugin. """ lbracket = BracketRegion(left.begin, left.end) rbracket = BracketRegion(right.begin, right.end) nobracket = False if ( ("__all__" in self.transform or name in self.transform) and self.plugin != None and self.plugin.is_enabled() ): lbracket, rbracket, regions, nobracket = self.plugin.run_command(self.view, name, lbracket, rbracket, regions) left = left.move(lbracket.begin, lbracket.end) if lbracket is not None else None right = right.move(rbracket.begin, rbracket.end) if rbracket is not None else None return left, right, regions, nobracket def match_scope_brackets(self, bfr, sel): """ See if scope should be searched, and then check endcaps to determine if valid scope bracket. """ center = sel.a left = None right = None scope_count = 0 before_center = center - 1 bracket_count = 0 partial_find = None max_size = self.view.size() - 1 selected_scope = None bracket = None # Cannot be inside a bracket pair if cursor is at zero if center == 0: return left, right, selected_scope, False # Identify if the cursor is in a scope with bracket definitions for s in self.scopes: scope = s["name"] extent = None exceed_limit = False if self.view.match_selector(center, scope) and self.view.match_selector(before_center, scope): extent = self.view.extract_scope(center) while not exceed_limit and extent.begin() != 0: if self.view.match_selector(extent.begin() - 1, scope): extent = extent.cover(self.view.extract_scope(extent.begin() - 1)) if extent.begin() < self.search_window[0] or extent.end() > self.search_window[1]: extent = None exceed_limit = True else: break while not exceed_limit and extent.end() != max_size: if self.view.match_selector(extent.end(), scope): extent = extent.cover(self.view.extract_scope(extent.end())) if extent.begin() < self.search_window[0] or extent.end() > self.search_window[1]: extent = None exceed_limit = True else: break if extent is None: scope_count += 1 continue # Search the bracket patterns of this scope # to determine if this scope matches the rules. bracket_count = 0 scope_bfr = bfr[extent.begin():extent.end()] for b in s["brackets"]: m = b.open.search(scope_bfr) if m and m.group(1): left = ScopeEntry(extent.begin() + m.start(1), extent.begin() + m.end(1), scope_count, bracket_count) m = b.close.search(scope_bfr) if m and m.group(1): right = ScopeEntry(extent.begin() + m.start(1), extent.begin() + m.end(1), scope_count, bracket_count) if not self.compare(left, right, bfr, scope_bracket=True): left, right = None, None # Track partial matches. If a full match isn't found, # return the first partial match at the end. if partial_find is None and bool(left) != bool(right): partial_find = (left, right) left = None right = None if left and right: break bracket_count += 1 if left and right: break scope_count += 1 # Full match not found. Return partial match (if any). if (left is None or right is None) and partial_find is not None: left, right = partial_find[0], partial_find[1] # Make sure cursor in highlighted sub group if (left and center <= left.begin) or (right and center >= right.end): left, right = None, None if left is not None: selected_scope = self.scopes[left.scope]["name"] elif right is not None: selected_scope = self.scopes[right.scope]["name"] if left is not None and right is not None: bracket = self.scopes[left.scope]["brackets"][left.type] if bracket.sub_search: self.sub_search_mode = True if self.sub_search(sel, (left.begin, right.end), bfr, scope): return left, right, self.brackets[left.type], True elif bracket.sub_search_only: left, right, bracket = None, None, None if self.adj_only: left, right = self.adjacent_check(left, right, center) left, right = self.post_match(left, right, center, bfr, scope_bracket=True) return left, right, bracket, False def match_brackets(self, bfr, window, sel, scope=None): """ Regex bracket matching. """ center = sel.a left = None right = None stack = [] pattern = self.pattern if not self.sub_search_mode else self.sub_pattern bsearch = BracketSearch(bfr, window, center, pattern, self.is_illegal_scope, scope) for o in bsearch.get_open(BracketSearchSide.left): if len(stack) and bsearch.is_done(BracektSearchType.closing): if self.compare(o, stack[-1], bfr): stack.pop() continue for c in bsearch.get_close(BracketSearchSide.left): if o.end <= c.begin: stack.append(c) continue elif len(stack): bsearch.remember(BracektSearchType.closing) break if len(stack): b = stack.pop() if self.compare(o, b, bfr): continue else: left = o break bsearch.reset_end_state() stack = [] # Grab each closest closing right side bracket and attempt to match it. # If the closing bracket cannot be matched, select it. for c in bsearch.get_close(BracketSearchSide.right): if len(stack) and bsearch.is_done(BracektSearchType.opening): if self.compare(stack[-1], c, bfr): stack.pop() continue for o in bsearch.get_open(BracketSearchSide.right): if o.end <= c.begin: stack.append(o) continue else: bsearch.remember(BracektSearchType.opening) break if len(stack): b = stack.pop() if self.compare(b, c, bfr): continue else: if left is None or self.compare(left, c, bfr): right = c break if self.adj_only: left, right = self.adjacent_check(left, right, center) return self.post_match(left, right, center, bfr) def adjacent_check(self, left, right, center): if left and right: if left.end < center < right.begin: left, right = None, None elif (left and left.end < center) or (right and center < right.begin): left, right = None, None return left, right