<commit_before>import sublime_plugin, sublime
import re

sentinel = object()

# view.find sadly can't handle naming groups
NO_NAMING_GROUPS_PATTERN = r"(?s)<{7}[^\n]*\n(.*?)(?:\|{7}[^\n]*\n(.*?))?={7}\n(.*?)>{7}[^\n]*\n"
CONFLICT_REGEX = re.compile(r"(?s)<{7}[^\n]*\n(?P<ours>.*?)(?:\|{7}[^\n]*\n(?P<ancestor>.*?))?={7}\n(?P<theirs>.*?)>{7}[^\n]*\n")

# group patterns; this patterns always match the seperating lines too, so we have to remove then later from the matched regions
CONFLICT_GROUP_REGEX = {
    "ours": r"(?s)<{7}[^\n]*\n.*?\|{7}|>{7}",
    "ancestor": r"(?s)\|{7}[^\n]*\n.*?={7}",
    "theirs": r"(?s)={7}[^\n]*\n.*?>{7}"
}

icons = {
    "ours": "Packages/Git Conflict Resolver/gutter/ours.png",
    "ancestor": "Packages/Git Conflict Resolver/gutter/ancestor.png",
    "theirs": "Packages/Git Conflict Resolver/gutter/theirs.png"
}

messages = {
    "no_conflict_found": "No conflict found",
    "no_such_group": "No such conflict group found"
}

# Global settings
settings_file = "GitConflictResolver.sublime-settings"
settings = {}
default_settings = {
    "live_matching": True,
    "matching_scope": 'invalid',
    "fill_conflict_area": False,
    "outline_conflict_area": True,
    "ours_gutter": True,
    "ancestor_gutter": True,
    "theirs_gutter": True
}


def plugin_loaded():
    global subl_settings, settings

    subl_settings = sublime.load_settings(settings_file)

    # Live matching
    settings['live_matching'] = subl_settings.get(
        'live_matching',
        default_settings['live_matching']
    )
    # Matching scope
    settings['matching_scope'] = subl_settings.get(
        'matching_scope',
        default_settings['matching_scope']
    )
    # Fill conflict area
    settings['fill_conflict_area'] = subl_settings.get(
        'fill_conflict_area',
        default_settings['fill_conflict_area']
    )
    # Outline conflict area
    settings['outline_conflict_area'] = subl_settings.get(
        'outline_conflict_area',
        default_settings['outline_conflict_area']
    )
    # Conflict group highlighting
    settings['ours_gutter'] = subl_settings.get(
        'ours_gutter',
        default_settings['ours_gutter']
    )
    settings['ancestor_gutter'] = subl_settings.get(
        'ancestor_gutter',
        default_settings['ancestor_gutter']
    )
    settings['theirs_gutter'] = subl_settings.get(
        'theirs_gutter',
        default_settings['theirs_gutter']
    )


def findConflict(view, begin=sentinel):
    if begin is sentinel:
        if len(view.sel()) == 0:
            begin = 0
        else:
            begin = view.sel()[0].begin()

    conflict_region = view.find(NO_NAMING_GROUPS_PATTERN, begin)

    if not conflict_region:
        conflict_region = view.find(NO_NAMING_GROUPS_PATTERN, 0)
        if not conflict_region:
            sublime.status_message(messages['no_conflict_found'])
            return None

    return conflict_region


def joinRegions(regions):
    if not regions:
        return sublime.Region(-1, -1)

    begin = min([region.begin() for region in regions if region.begin() >= 0])
    end = max([region.end() for region in regions])

    return sublime.Region(begin, end)


def highlightConflictGroup(view, group):
    # Always remove the regions
    view.erase_regions("GitConflictRegions_"+group)

    if settings[group+'_gutter']:
        conflict_regions = view.find_all(CONFLICT_GROUP_REGEX[group])

        if not conflict_regions:
            return

        # Remove the first and last line since they just contain the seperators
        highlight_regions = []
        for region in conflict_regions:
            region = view.split_by_newlines(region)[1:-1]
            # Ignore empty subregions
            if not region:
                continue

            for subregion in region:
                highlight_regions.append(subregion)

        view.add_regions(
            "GitConflictRegions_"+group,
            highlight_regions,
            "warning",
            icons[group],
            sublime.DRAW_NO_FILL |
            sublime.DRAW_NO_OUTLINE |
            sublime.HIDE_ON_MINIMAP
        )


def highlightConflicts(view):
    conflict_regions = view.find_all(NO_NAMING_GROUPS_PATTERN)

    view.erase_regions("GitConflictRegions")
    view.add_regions(
        "GitConflictRegions",
        conflict_regions,
        settings['matching_scope'],
        "",
        (0 if settings['fill_conflict_area'] else sublime.DRAW_NO_FILL)
        |
        (0 if settings['outline_conflict_area'] else sublime.DRAW_NO_OUTLINE)
    )

    highlightConflictGroup(view, 'ours')
    highlightConflictGroup(view, 'ancestor')
    highlightConflictGroup(view, 'theirs')


def extract(view, region, keep):
    conflict_text = view.substr(region)
    match = CONFLICT_REGEX.search(conflict_text)

    # If we didn't matched the group return None
    if not match.group(keep):
        sublime.status_message(messages['no_such_group'])
        return None

    return CONFLICT_REGEX.sub(r'\g<'+keep+'>', conflict_text)


class FindNextConflict(sublime_plugin.TextCommand):
    def run(self, edit):
        conflict_region = findConflict(self.view)
        if conflict_region is None:
            return

        # Add the region to the selection
        self.view.show_at_center(conflict_region)
        self.view.sel().clear()
        self.view.sel().add(conflict_region)


class Keep(sublime_plugin.TextCommand):
    def run(self, edit, keep):
        conflict_region = findConflict(self.view)

        if conflict_region is None:
            return

        replace_text = extract(self.view, conflict_region, keep)

        if not replace_text:
            return

        self.view.replace(edit, conflict_region, replace_text)


class ScanForConflicts(sublime_plugin.EventListener):
    def on_activated(self, view):
        if(settings['live_matching']):
            highlightConflicts(view)

    def on_pre_save(self, view):
        if(settings['live_matching']):
            highlightConflicts(view)
<commit_msg>[Plugin] Replaces also if the area is empty; maybe add configuration for this?<commit_after>import sublime_plugin, sublime
import re

sentinel = object()

# view.find sadly can't handle naming groups
NO_NAMING_GROUPS_PATTERN = r"(?s)<{7}[^\n]*\n(.*?)(?:\|{7}[^\n]*\n(.*?))?={7}\n(.*?)>{7}[^\n]*\n"
CONFLICT_REGEX = re.compile(r"(?s)<{7}[^\n]*\n(?P<ours>.*?)(?:\|{7}[^\n]*\n(?P<ancestor>.*?))?={7}\n(?P<theirs>.*?)>{7}[^\n]*\n")

# group patterns; this patterns always match the seperating lines too, so we have to remove then later from the matched regions
CONFLICT_GROUP_REGEX = {
    "ours": r"(?s)<{7}[^\n]*\n.*?\|{7}|>{7}",
    "ancestor": r"(?s)\|{7}[^\n]*\n.*?={7}",
    "theirs": r"(?s)={7}[^\n]*\n.*?>{7}"
}

icons = {
    "ours": "Packages/Git Conflict Resolver/gutter/ours.png",
    "ancestor": "Packages/Git Conflict Resolver/gutter/ancestor.png",
    "theirs": "Packages/Git Conflict Resolver/gutter/theirs.png"
}

messages = {
    "no_conflict_found": "No conflict found",
    "no_such_group": "No such conflict group found"
}

# Global settings
settings_file = "GitConflictResolver.sublime-settings"
settings = {}
default_settings = {
    "live_matching": True,
    "matching_scope": 'invalid',
    "fill_conflict_area": False,
    "outline_conflict_area": True,
    "ours_gutter": True,
    "ancestor_gutter": True,
    "theirs_gutter": True
}


def plugin_loaded():
    global subl_settings, settings

    subl_settings = sublime.load_settings(settings_file)

    # Live matching
    settings['live_matching'] = subl_settings.get(
        'live_matching',
        default_settings['live_matching']
    )
    # Matching scope
    settings['matching_scope'] = subl_settings.get(
        'matching_scope',
        default_settings['matching_scope']
    )
    # Fill conflict area
    settings['fill_conflict_area'] = subl_settings.get(
        'fill_conflict_area',
        default_settings['fill_conflict_area']
    )
    # Outline conflict area
    settings['outline_conflict_area'] = subl_settings.get(
        'outline_conflict_area',
        default_settings['outline_conflict_area']
    )
    # Conflict group highlighting
    settings['ours_gutter'] = subl_settings.get(
        'ours_gutter',
        default_settings['ours_gutter']
    )
    settings['ancestor_gutter'] = subl_settings.get(
        'ancestor_gutter',
        default_settings['ancestor_gutter']
    )
    settings['theirs_gutter'] = subl_settings.get(
        'theirs_gutter',
        default_settings['theirs_gutter']
    )


def findConflict(view, begin=sentinel):
    if begin is sentinel:
        if len(view.sel()) == 0:
            begin = 0
        else:
            begin = view.sel()[0].begin()

    conflict_region = view.find(NO_NAMING_GROUPS_PATTERN, begin)

    if not conflict_region:
        conflict_region = view.find(NO_NAMING_GROUPS_PATTERN, 0)
        if not conflict_region:
            sublime.status_message(messages['no_conflict_found'])
            return None

    return conflict_region


def joinRegions(regions):
    if not regions:
        return sublime.Region(-1, -1)

    begin = min([region.begin() for region in regions if region.begin() >= 0])
    end = max([region.end() for region in regions])

    return sublime.Region(begin, end)


def highlightConflictGroup(view, group):
    scope = group+'_gutter'
    if settings[scope]:
        conflict_regions = view.find_all(CONFLICT_GROUP_REGEX[group])

        if not conflict_regions:
            return

        # Remove the first and last line since they just contain the seperators
        highlight_regions = []
        for region in conflict_regions:
            region = view.split_by_newlines(region)[1:-1]
            # Ignore empty subregions
            if not region:
                continue

            for subregion in region:
                highlight_regions.append(subregion)

        view.erase_regions("GitConflictRegion_"+group)
        view.add_regions(
            "GitConflictRegions_"+group,
            highlight_regions,
            "warning",
            icons[group],
            sublime.DRAW_NO_FILL |
            sublime.DRAW_NO_OUTLINE |
            sublime.HIDE_ON_MINIMAP
        )


def highlightConflicts(view):
    conflict_regions = view.find_all(NO_NAMING_GROUPS_PATTERN)

    view.erase_regions("GitConflictRegions")
    view.add_regions(
        "GitConflictRegions",
        conflict_regions,
        settings['matching_scope'],
        "",
        (0 if settings['fill_conflict_area'] else sublime.DRAW_NO_FILL)
        |
        (0 if settings['outline_conflict_area'] else sublime.DRAW_NO_OUTLINE)
    )

    highlightConflictGroup(view, 'ours')
    highlightConflictGroup(view, 'ancestor')
    highlightConflictGroup(view, 'theirs')


def extract(view, region, keep):
    conflict_text = view.substr(region)
    match = CONFLICT_REGEX.search(conflict_text)

    # If we didn't matched the group return None
    if not match.group(keep):
        sublime.status_message(messages['no_such_group'])
        return None

    return CONFLICT_REGEX.sub(r'\g<'+keep+'>', conflict_text)


class FindNextConflict(sublime_plugin.TextCommand):
    def run(self, edit):
        conflict_region = findConflict(self.view)
        if conflict_region is None:
            return

        # Add the region to the selection
        self.view.show_at_center(conflict_region)
        self.view.sel().clear()
        self.view.sel().add(conflict_region)


class Keep(sublime_plugin.TextCommand):
    def run(self, edit, keep):
        conflict_region = findConflict(self.view)

        if conflict_region is None:
            return

        replace_text = extract(self.view, conflict_region, keep)

        if not replace_text:
            replace_text = ""

        self.view.replace(edit, conflict_region, replace_text)


class ScanForConflicts(sublime_plugin.EventListener):
    def on_activated(self, view):
        if(settings['live_matching']):
            highlightConflicts(view)

    def on_pre_save(self, view):
        if(settings['live_matching']):
            highlightConflicts(view)

{'fraction_non_alphanumeric': 0.12456718878812861, 'fraction_numerical': 0.004286892003297609, 'mean_word_length': 3.0248838752488387, 'pattern_counts': {'":': 30, '<': 17, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1421072', 'n_tokens_mistral': 4071, 'n_tokens_neox': 3827, 'n_words': 724}