Example #1
0
    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)
Example #2
0
    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)
Example #3
0
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
Example #4
0
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