<commit_before>from collections import OrderedDict

import sublime
from sublime_plugin import TextCommand, EventListener

from . import util


interfaces = {}
subclasses = []


class Interface():

    interface_type = ""
    read_only = True
    syntax_file = ""
    word_wrap = False

    dedent = 0
    skip_first_line = False

    regions = []
    template = ""

    def __init__(self, view_attrs=None, view=None):
        self.view_attrs = view_attrs or {}
        subclass_attrs = (getattr(self, attr) for attr in vars(self.__class__).keys())

        self.partials = {
            attr.key: attr
            for attr in subclass_attrs
            if callable(attr) and hasattr(attr, "key")
            }

        if self.skip_first_line:
            self.template = self.template[self.template.find("\n") + 1:]
        if self.dedent:
            for attr in vars(self.__class__).keys():
                if attr.startswith("template"):
                    setattr(self, attr, "\n".join(
                        line[self.dedent:] if len(line) >= self.dedent else line
                        for line in getattr(self, attr).split("\n")
                        ))

        if view:
            self.view = view
        else:
            self.view = self.create_view()

        interfaces[self.view.id()] = self

    def create_view(self):
        window = sublime.active_window()
        self.view = window.new_file()

        for k, v in self.view_attrs.items():
            self.view.settings().set(k, v)

        self.view.set_name(self.title())
        self.view.settings().set("git_savvy.{}_view".format(self.interface_type), True)
        self.view.settings().set("git_savvy.interface", self.interface_type)
        self.view.settings().set("word_wrap", self.word_wrap)
        self.view.set_syntax_file(self.syntax_file)
        self.view.set_scratch(True)
        self.view.set_read_only(self.read_only)
        util.view.disable_other_plugins(self.view)

        self.render()
        window.focus_view(self.view)

        return self.view

    def render(self):
        if self.regions:
            self.clear_regions()
        if hasattr(self, "pre_render"):
            self.pre_render()

        rendered = self.template

        self.regions = []
        keyed_content = self.get_keyed_content()
        for key, new_content in keyed_content.items():
            interpol = "{" + key + "}"
            interpol_len = len(interpol)
            cursor = 0
            match = rendered.find(interpol)
            while match >= 0:
                self.regions.append((key, (match, match+len(new_content))))
                rendered = rendered[:match] + new_content + rendered[match+interpol_len:]

                match = rendered.find(interpol, cursor)

        self.view.run_command("gs_new_content_and_regions", {
            "content": rendered,
            "regions": self.regions,
            "nuke_cursors": True
            })

    def get_keyed_content(self):
        keyed_content = OrderedDict(
            (key, render_fn())
            for key, render_fn in self.partials.items()
            )

        for key in keyed_content:
            output = keyed_content[key]
            if isinstance(output, tuple):
                sub_template, complex_partials = output
                keyed_content[key] = sub_template

                for render_fn in complex_partials:
                    keyed_content[render_fn.key] = render_fn()

        return keyed_content

    def update(self, key, content):<commit_msg>Fix: Adjust region ranges for template interpolations temporally following but physically preceding.<commit_after>from collections import OrderedDict

import sublime
from sublime_plugin import TextCommand, EventListener

from . import util


interfaces = {}
subclasses = []


class Interface():

    interface_type = ""
    read_only = True
    syntax_file = ""
    word_wrap = False

    dedent = 0
    skip_first_line = False

    regions = []
    template = ""

    def __init__(self, view_attrs=None, view=None):
        self.view_attrs = view_attrs or {}
        subclass_attrs = (getattr(self, attr) for attr in vars(self.__class__).keys())

        self.partials = {
            attr.key: attr
            for attr in subclass_attrs
            if callable(attr) and hasattr(attr, "key")
            }

        if self.skip_first_line:
            self.template = self.template[self.template.find("\n") + 1:]
        if self.dedent:
            for attr in vars(self.__class__).keys():
                if attr.startswith("template"):
                    setattr(self, attr, "\n".join(
                        line[self.dedent:] if len(line) >= self.dedent else line
                        for line in getattr(self, attr).split("\n")
                        ))

        if view:
            self.view = view
        else:
            self.view = self.create_view()

        interfaces[self.view.id()] = self

    def create_view(self):
        window = sublime.active_window()
        self.view = window.new_file()

        for k, v in self.view_attrs.items():
            self.view.settings().set(k, v)

        self.view.set_name(self.title())
        self.view.settings().set("git_savvy.{}_view".format(self.interface_type), True)
        self.view.settings().set("git_savvy.interface", self.interface_type)
        self.view.settings().set("word_wrap", self.word_wrap)
        self.view.set_syntax_file(self.syntax_file)
        self.view.set_scratch(True)
        self.view.set_read_only(self.read_only)
        util.view.disable_other_plugins(self.view)

        self.render()
        window.focus_view(self.view)

        return self.view

    def render(self):
        if self.regions:
            self.clear_regions()
        if hasattr(self, "pre_render"):
            self.pre_render()

        rendered = self.template

        self.regions = []
        keyed_content = self.get_keyed_content()
        for key, new_content in keyed_content.items():
            interpol = "{" + key + "}"
            interpol_len = len(interpol)
            cursor = 0
            match = rendered.find(interpol)
            while match >= 0:
                self.adjust(self.regions, match, interpol_len, len(new_content))
                self.regions.append((key, [match, match+len(new_content)]))
                rendered = rendered[:match] + new_content + rendered[match+interpol_len:]

                match = rendered.find(interpol, cursor)

        self.view.run_command("gs_new_content_and_regions", {
            "content": rendered,
            "regions": self.regions,
            "nuke_cursors": True
            })

    @staticmethod
    def adjust(regions, idx, orig_len, new_len):
        """
        When interpolating template variables, update region ranges for previously-evaluated
        variables, but which occur later on in the output/template string.
        """
        diff = new_len - orig_len
        for region in regions:
            if region[1][0] > idx:
                region[1][0] += diff
                region[1][1] += diff

    def get_keyed_content(self):
        keyed_content = OrderedDict(
            (key, render_fn())
            for key, render_fn in self.partials.items()
            )

        for key in keyed_content:
            output = keyed_content[key]
            if isinstance(output, tuple):
                sub_template, complex_partials = output
                keyed_content[key] = sub_template

                for render_fn in complex_partials:
                    keyed_content[render_fn.key] = render_fn()

        return keyed_content

    def update(self, key, content):
{'fraction_non_alphanumeric': 0.10625, 'fraction_numerical': 0.0018229166666666667, 'mean_word_length': 1.9565050038491147, 'pattern_counts': {'":': 6, '<': 3, '<?xml version=': 0, '>': 8, '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': '4839223', 'n_tokens_mistral': 2277, 'n_tokens_neox': 2149, 'n_words': 461}