Ejemplo n.º 1
0
 def set_status(branch_name):
     _, _, inserted, modified, deleted = contents
     template = (settings.get('status_bar_text')
                 if _HAVE_JINJA2 else None)
     if template:
         # render the template using jinja2 library
         text = jinja2.environment.Template(''.join(template)).render(
             repo=self.git_handler.repository_name,
             compare=self.git_handler.format_compare_against(),
             branch=branch_name,
             state=file_state,
             deleted=len(deleted),
             inserted=len(inserted),
             modified=len(modified))
     else:
         # Render hardcoded text if jinja is not available.
         parts = []
         parts.append('On %s' % branch_name)
         compare = self.git_handler.format_compare_against()
         if compare not in ('HEAD', branch_name):
             parts.append('Comparing against %s' % compare)
         count = len(inserted)
         if count:
             parts.append('%d+' % count)
         count = len(deleted)
         if count:
             parts.append('%d-' % count)
         count = len(modified)
         if count:
             parts.append(u'%d≠' % count)
         text = ', '.join(parts)
     # add text and try to be the left most one
     self.git_handler.view.set_status('00_git_gutter', text)
Ejemplo n.º 2
0
    def jump(self, all_changes, current_row):
        if settings.get("next_prev_change_wrap", True):
            default = all_changes[-1]
        else:
            default = all_changes[0]

        return next((change for change in reversed(all_changes) if change < current_row), default)
Ejemplo n.º 3
0
    def _check_ignored_or_untracked(self, contents):
        """Check diff result and invoke gutter and status message update.

        Arguments:
            contentes - a tuble of ([inserted], [modified], [deleted]) lines
        """
        if self.git_handler.in_repo() is False:
            show_untracked = settings.get(
                'show_markers_on_untracked_file', False)
            # need to check for ignored or untracked file
            if show_untracked:
                def bind_ignored_or_untracked(is_ignored):
                    if is_ignored:
                        self._bind_files('ignored')
                    else:
                        def bind_untracked(is_untracked):
                            if is_untracked:
                                self._bind_files('untracked')
                            else:
                                # file was staged but empty
                                self._bind_files('inserted')
                        self.git_handler.untracked().then(bind_untracked)
                self.git_handler.ignored().then(bind_ignored_or_untracked)

            # show_untracked was set to false recently so clear gutter
            elif self.show_untracked:
                self._clear_all()
                self._update_status(0, 0, 0, "", "")
            self.show_untracked = show_untracked

        # update the if lines changed
        elif self.diff_results is None or self.diff_results != contents:
            self.diff_results = contents
            self._update_ui(contents)
Ejemplo n.º 4
0
    def next_jump(self, all_changes, current_row):
        if settings.get('next_prev_change_wrap', True):
            default = all_changes[0]
        else:
            default = all_changes[-1]

        return next((change for change in all_changes
                    if change > current_row), default)
    def next_jump(self, all_changes, current_row):
        if settings.get('next_prev_change_wrap', True):
            default = all_changes[0]
        else:
            default = all_changes[-1]

        return next((change for change in all_changes if change > current_row),
                    default)
    def prev_jump(self, all_changes, current_row):
        if settings.get('next_prev_change_wrap', True):
            default = all_changes[-1]
        else:
            default = all_changes[0]

        return next((change for change in reversed(all_changes)
                    if change < current_row), default)
Ejemplo n.º 7
0
 def on_hover(self, view, point, hover_zone):
     if hover_zone != sublime.HOVER_GUTTER:
         return
     # don't let the popup flicker / fight with other packages
     if view.is_popup_visible():
         return
     if not settings.get("enable_hover_diff_popup"):
         return
     show_diff_popup(view, point, flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY)
Ejemplo n.º 8
0
 def on_hover(self, view, point, hover_zone):
     if hover_zone != sublime.HOVER_GUTTER:
         return
     # don't let the popup flicker / fight with other packages
     if view.is_popup_visible():
         return
     if not settings.get("enable_hover_diff_popup"):
         return
     view.run_command(
         'git_gutter_diff_popup',
         args={'point': point, 'flags': sublime.HIDE_ON_MOUSE_MOVE_AWAY})
Ejemplo n.º 9
0
    def run(self, force_refresh=False):
        self.view = self.window.active_view()
        if not self.view:
            # View is not ready yet, try again later.
            sublime.set_timeout(self.run, 1)
            return

        self.clear_all()
        self.show_in_minimap = settings.get('show_in_minimap', True)
        show_untracked = settings.get('show_markers_on_untracked_file', False)

        if ViewCollection.untracked(self.view):
            if show_untracked:
                self.bind_files('untracked')
        elif ViewCollection.ignored(self.view):
            if show_untracked:
                self.bind_files('ignored')
        else:
            # If the file is untracked there is no need to execute the diff
            # update
            if force_refresh:
                ViewCollection.clear_git_time(self.view)
            inserted, modified, deleted = ViewCollection.diff(self.view)
            self.lines_removed(deleted)
            self.bind_icons('inserted', inserted)
            self.bind_icons('changed', modified)

            if(ViewCollection.show_status(self.view) != "none"):
                if(ViewCollection.show_status(self.view) == 'all'):
                    branch = ViewCollection.current_branch(
                        self.view).decode("utf-8").strip()
                else:
                    branch = ""

                self.update_status(len(inserted),
                                   len(modified),
                                   len(deleted),
                                   ViewCollection.get_compare(self.view), branch)
            else:
                self.update_status(0, 0, 0, "", "")
Ejemplo n.º 10
0
    def _update_status(self, file_state, contents):
        """Update status message.

        The method joins and renders the lines read from 'status_bar_text'
        setting to the status bar using the jinja2 library to fill in all
        the state information of the open file.

        Arguments:
            file_state (string): The git status of the open file.
            contents (tuble): The result of git_handler.diff(), with the
                information about the modifications of the file.
                Scheme: (first, last, [inserted], [modified], [deleted])
        """
        if not settings.get('show_status_bar_text', False):
            self.git_handler.view.erase_status('00_git_gutter')
            return

        def set_status(branch_name):
            _, _, inserted, modified, deleted = contents
            template = (settings.get('status_bar_text')
                        if _HAVE_JINJA2 else None)
            if template:
                # render the template using jinja2 library
                text = jinja2.environment.Template(''.join(template)).render(
                    repo=self.git_handler.repository_name,
                    compare=self.git_handler.format_compare_against(),
                    branch=branch_name,
                    state=file_state,
                    deleted=len(deleted),
                    inserted=len(inserted),
                    modified=len(modified))
            else:
                # Render hardcoded text if jinja is not available.
                parts = []
                parts.append('On %s' % branch_name)
                compare = self.git_handler.format_compare_against()
                if compare not in ('HEAD', branch_name):
                    parts.append('Comparing against %s' % compare)
                count = len(inserted)
                if count:
                    parts.append('%d+' % count)
                count = len(deleted)
                if count:
                    parts.append('%d-' % count)
                count = len(modified)
                if count:
                    parts.append(u'%d≠' % count)
                text = ', '.join(parts)
            # add text and try to be the left most one
            self.git_handler.view.set_status('00_git_gutter', text)

        self.git_handler.git_current_branch().then(set_status)
Ejemplo n.º 11
0
    def _get_protected_regions(self):
        """Create a list of line start points of all protected lines.

        A protected region describes a line which is occupied by a higher prior
        gutter icon which must not be overwritten by GitGutter.

        Returns:
            frozenset: A list of protected lines' start points.
        """
        view = self.git_handler.view
        keys = settings.get('protected_regions', [])
        return frozenset(
            view.line(reg).a for key in keys for reg in view.get_regions(key))
Ejemplo n.º 12
0
 def on_hover(self, view, point, hover_zone):
     if hover_zone != sublime.HOVER_GUTTER:
         return
     # don't let the popup flicker / fight with other packages
     if view.is_popup_visible():
         return
     if not settings.get("enable_hover_diff_popup"):
         return
     view.run_command('git_gutter_diff_popup',
                      args={
                          'point': point,
                          'flags': sublime.HIDE_ON_MOUSE_MOVE_AWAY
                      })
Ejemplo n.º 13
0
    def _is_region_protected(self, region):
        # Load protected Regions from Settings
        protected_regions = settings.get('protected_regions', [])
        # List of Lists of Regions
        sets = [self.view.get_regions(r) for r in protected_regions]
        # List of Regions
        regions = [r for rs in sets for r in rs]
        # get the line of the region (gutter icon applies to whole line)
        region_line = self.view.line(region)
        for r in regions:
            if r.contains(region) or region_line.contains(r):
                return True

        return False
Ejemplo n.º 14
0
    def is_region_protected(self, region):
        # Load protected Regions from Settings
        protected_regions = settings.get('protected_regions', [])
        # List of Lists of Regions
        sets = [self.view.get_regions(r) for r in protected_regions]
        # List of Regions
        regions = [r for rs in sets for r in rs]
        # get the line of the region (gutter icon applies to whole line)
        region_line = self.view.line(region)
        for r in regions:
            if r.contains(region) or region_line.contains(r):
                return True

        return False
Ejemplo n.º 15
0
    def debounce(self, view, event_type):
        key = (event_type, view.file_name())
        this_keypress = time.time()
        self.latest_keypresses[key] = this_keypress

        def callback():
            latest_keypress = self.latest_keypresses.get(key, None)
            if this_keypress == latest_keypress:
                view.run_command('git_gutter')

        if ST3:
            set_timeout = sublime.set_timeout_async
        else:
            set_timeout = sublime.set_timeout

        set_timeout(callback, settings.get("debounce_delay"))
Ejemplo n.º 16
0
    def debounce(self, view, event_type):
        key = (event_type, view.file_name())
        this_keypress = time.time()
        self._latest_keypresses[key] = this_keypress

        def callback():
            latest_keypress = self._latest_keypresses.get(key, None)
            if this_keypress == latest_keypress:
                view.run_command('git_gutter')

        if ST3:
            set_timeout = sublime.set_timeout_async
        else:
            set_timeout = sublime.set_timeout

        set_timeout(callback, settings.get("debounce_delay"))
Ejemplo n.º 17
0
    def debounce(self, view, event_type, func):
        if self.non_blocking():
            key = (event_type, view.file_name())
            this_keypress = time.time()
            self.latest_keypresses[key] = this_keypress

            def callback():
                latest_keypress = self.latest_keypresses.get(key, None)
                if this_keypress == latest_keypress:
                    func(view)

            if ST3:
                set_timeout = sublime.set_timeout_async
            else:
                set_timeout = sublime.set_timeout

            set_timeout(callback, settings.get("debounce_delay"))
        else:
            func(view)
Ejemplo n.º 18
0
    def on_hover(view, point, hover_zone):
        """Open diff popup if user hovers the mouse over the gutter area.

        Arguments:
            view (View): The view which received the event.
            point (Point): The text position where the mouse hovered
            hover_zone (int): The context the event was triggered in
        """
        if hover_zone != sublime.HOVER_GUTTER:
            return
        # don't let the popup flicker / fight with other packages
        if view.is_popup_visible():
            return
        if not settings.get('enable_hover_diff_popup'):
            return
        view.run_command('git_gutter_diff_popup', {
            'point': point,
            'flags': sublime.HIDE_ON_MOUSE_MOVE_AWAY
        })
Ejemplo n.º 19
0
    def set_compare_against(self, compare_against, refresh=False):
        """Apply a new branch/commit/tag string the view is compared to.

        If one of the settings 'focus_change_mode' or 'live_mode' is true,
        the view, is automatically compared by 'on_activate' event when
        returning from a quick panel and therefore the command 'git_gutter'
        can be omitted. This assumption can be overridden by 'refresh' for
        commands that do not show a quick panel.

        Arguments:
            compare_against (string): The branch, commit or tag as returned
                from 'git show-ref' to compare the view against
            refresh (bool): True to force git diff and update GUI
        """
        settings.set_compare_against(self._git_tree, compare_against)
        self.invalidate_git_file()
        if refresh or not any(
                settings.get(key, True)
                for key in ('focus_change_mode', 'live_mode')):
            self.view.run_command('git_gutter')  # refresh UI
Ejemplo n.º 20
0
    def _check_ignored_or_untracked(self, contents):
        show_untracked = settings.get('show_markers_on_untracked_file', False)
        if show_untracked and self._are_all_lines_added(contents):

            def bind_ignored_or_untracked(is_ignored):
                if is_ignored:
                    self._bind_files('ignored')
                else:

                    def bind_untracked(is_untracked):
                        if is_untracked:
                            self._bind_files('untracked')
                        else:
                            self._lazy_update_ui(contents)

                    self.git_handler.untracked().then(bind_untracked)

            self.git_handler.ignored().then(bind_ignored_or_untracked)
            return
        self._lazy_update_ui(contents)
Ejemplo n.º 21
0
    def debounce(self, view, event_type):
        """Invoke evaluation of changes after some idle time.

        Arguments:
            view (View): The view to perform evaluation for
            event_type (string): The event identifier
        """
        key = view.id()
        this_event = time.time()
        self._latest_events.setdefault(key, {})[event_type] = this_event

        def callback():
            """Run git_gutter command for most recent event."""
            if not self.is_view_visible(view):
                return
            view_events = self._latest_events.get(key, {})
            if this_event == view_events.get(event_type, None):
                view.run_command('git_gutter',
                                 {'event_type': list(view_events.keys())})
                self._latest_events[key] = {}

        # Run command delayed and asynchronous if supported.
        set_timeout(callback, max(300, settings.get('debounce_delay', 1000)))
Ejemplo n.º 22
0
    def _check_ignored_or_untracked(self, contents):
        """Check diff result and invoke gutter and status message update.

        Arguments:
            contents (tuble): The result of git_handler.diff(), with the
                information about the modifications of the file.
                Scheme: (first, last, [inserted], [modified], [deleted])
        """
        # nothing to update
        if contents is None:
            self._busy = False
            return

        if not self.git_handler.in_repo():
            show_untracked = settings.get('show_markers_on_untracked_file',
                                          False)

            def bind_ignored_or_untracked(is_ignored):
                if is_ignored:
                    event = 'ignored'
                    self._update_status(event, (0, 0, [], [], []))
                    if show_untracked:
                        self._bind_files(event)
                else:

                    def bind_untracked(is_untracked):
                        event = 'untracked' if is_untracked else 'inserted'
                        self._update_status(event, (0, 0, [], [], []))
                        if show_untracked:
                            self._bind_files(event)

                    self.git_handler.untracked().then(bind_untracked)

            self.git_handler.ignored().then(bind_ignored_or_untracked)
        else:
            self._update_ui(contents)
Ejemplo n.º 23
0
 def non_blocking(self, default=True):
     return settings.get('non_blocking', default)
Ejemplo n.º 24
0
    def process_diff_line_change(self, line_nr, diff_str):
        hunk_re = '^@@ \-(\d+),?(\d*) \+(\d+),?(\d*) @@'
        hunks = re.finditer(hunk_re, diff_str, re.MULTILINE)

        # we also want to extract the position of the surrounding changes
        first_change = prev_change = next_change = None

        for hunk in hunks:
            start = int(hunk.group(3))
            size = int(hunk.group(4) or 1)
            if first_change is None:
                first_change = start
            # special handling to also match the line below deleted
            # content
            if size == 0 and line_nr == start + 1:
                pass
            # continue if the hunk is before the line
            elif start + size < line_nr:
                prev_change = start
                continue
            # break if the hunk is after the line
            elif line_nr < start:
                break
            # in the following the line is inside the hunk
            try:
                next_hunk = next(hunks)
                hunk_end = next_hunk.start()
                next_change = int(next_hunk.group(3))
            except:
                hunk_end = len(diff_str)

            # if wrap is disable avoid wrapping
            wrap = settings.get('next_prev_change_wrap', True)
            if not wrap:
                if prev_change is None:
                    prev_change = start
                if next_change is None:
                    next_change = start

            # if prev change is None set it to the wrap around the
            # document: prev -> last hunk, next -> first hunk
            if prev_change is None:
                try:
                    remaining_hunks = list(hunks)
                    if remaining_hunks:
                        last_hunk = remaining_hunks[-1]
                        prev_change = int(last_hunk.group(3))
                    elif next_change is not None:
                        prev_change = next_change
                    else:
                        prev_change = start
                except:
                    prev_change = start
            if next_change is None:
                next_change = first_change

            # extract the content of the hunk
            hunk_content = diff_str[hunk.start():hunk_end]
            # store all deleted lines (starting with -)
            hunk_lines = hunk_content.splitlines()[1:]
            deleted_lines = [
                line[1:] for line in hunk_lines if line.startswith("-")
            ]
            added_lines = [
                line[1:] for line in hunk_lines if line.startswith("+")
            ]
            meta = {
                "added_lines": added_lines,
                "first_change": first_change,
                "next_change": next_change,
                "prev_change": prev_change
            }
            return (deleted_lines, start, size, meta)
        return ([], -1, -1, {})
Ejemplo n.º 25
0
def show_diff_popup(view, point, flags=0):
    if not _MDPOPUPS_INSTALLED:
        return

    line = view.rowcol(point)[0] + 1
    lines, start, size, meta = ViewCollection.diff_line_change(view, line)
    if start == -1:
        return

    # extract the type of the hunk: removed, modified, (x)or added
    is_removed = size == 0
    is_modified = not is_removed and bool(lines)
    is_added = not is_removed and not is_modified

    def navigate(href):
        if href == "hide":
            view.hide_popup()
        elif href == "revert":
            new_text = "\n".join(lines)
            # (removed) if there is no text to remove, set the
            # region to the end of the line, where the hunk starts
            # and add a new line to the start of the text
            if is_removed:
                if start != 0:
                    # set the start and the end to the end of the start line
                    start_point = end_point = view.text_point(start, 0) - 1
                    # add a leading newline before inserting the text
                    new_text = "\n" + new_text
                else:
                    # (special handling for deleted at the start of the file)
                    # if we are before the start we need to set the start
                    # to 0 and add the newline behind the text
                    start_point = end_point = 0
                    new_text = new_text + "\n"
            # (modified/added)
            # set the start point to the start of the hunk
            # and the end point to the end of the hunk
            else:
                start_point = view.text_point(start - 1, 0)
                end_point = view.text_point(start + size - 1, 0)
                # (modified) if there is text to insert, we
                # don't want to capture the trailing newline,
                # because we insert lines without a trailing newline
                if is_modified and end_point != view.size():
                    end_point -= 1
            replace_param = {
                "a": start_point,
                "b": end_point,
                "text": new_text
            }
            view.run_command("git_gutter_replace_text", replace_param)
            # hide the popup and update the gutter
            view.hide_popup()
            view.window().run_command("git_gutter")
        elif href == "copy":
            sublime.set_clipboard("\n".join(lines))
            copy_message = "  ".join(l.strip() for l in lines)
            sublime.status_message("Copied: " + copy_message)
        elif href in ["next_change", "prev_change", "first_change"]:
            next_line = meta.get(href, line)
            pt = view.text_point(next_line - 1, 0)

            def show_new_popup():
                if view.visible_region().contains(pt):
                    show_diff_popup(view, pt, flags=flags)
                else:
                    sublime.set_timeout(show_new_popup, 10)
            view.show_at_center(pt)
            show_new_popup()

    # write the symbols/text for each button
    use_icons = settings.get("diff_popup_use_icon_buttons")

    # the buttons as a map from the href to the caption/icon
    button_descriptions = {
        "hide": chr(0x00D7) if use_icons else "(close)",
        "copy": chr(0x2398) if use_icons else "(copy)",
        "revert": chr(0x27F2) if use_icons else "(revert)",
        "first_change": chr(0x2912) if use_icons else "(first)",
        "prev_change": chr(0x2191) if use_icons else "(previous)",
        "next_change": chr(0x2193) if use_icons else "(next)"
    }

    def is_button_enabled(k):
        if k in ["first_change", "next_change", "prev_change"]:
            return meta.get(k, start) != start
        return True
    buttons = {}
    for k, v in button_descriptions.items():
        if is_button_enabled(k):
            buttons[k] = '[{0}]({1})'.format(v, k)
        else:
            buttons[k] = v

    if not is_added:
        # (modified/removed) show the button line above the content,
        # which in git
        lang = mdpopups.get_language_from_view(view) or ""
        # strip the indent to the minimal indentation
        is_tab_indent = any(l.startswith("\t") for l in lines)
        indent_char = "\t" if is_tab_indent else " "
        min_indent = min(len(l) - len(l.lstrip(indent_char))
                         for l in lines)
        source_content = "\n".join(l[min_indent:] for l in lines)
        # replace spaces by non-breakable ones to avoid line wrapping
        source_content = source_content.replace(" ", "\u00A0")
        button_line = (
            '{hide} '
            '{first_change} {prev_change} {next_change} '
            '{copy} {revert}'
            .format(**buttons)
        )
        content = (
            '{button_line}\n'
            '``` {lang}\n'
            '{source_content}\n'
            '```'
            .format(**locals())
        )
    else:
        # (added) only show the button line without the copy button
        # (there is nothing to show or copy)
        button_line = (
            '{hide} '
            '{first_change} {prev_change} {next_change} '
            '{revert}'
            .format(**buttons)
        )
        content = button_line
    css = ''
    if _MD_POPUPS_USE_WRAPPER_CLASS:
        wrapper_class = "git-gutter"
        if use_icons:
            css = 'div.git-gutter a { text-decoration: none; }'
    else:
        wrapper_class = ""
        if use_icons:
            css = 'a { text-decoration: none; }'
    location = view.line(point).a
    window_width = int(view.viewport_extent()[0])
    mdpopups.show_popup(
        view, content, location=location, on_navigate=navigate,
        wrapper_class=wrapper_class, css=css,
        flags=flags, max_width=window_width)
Ejemplo n.º 26
0
 def focus_change_mode(self, default=True):
     return settings.get('focus_change_mode', default)
Ejemplo n.º 27
0
 def live_mode(self, default=True):
     return settings.get('live_mode', default)
Ejemplo n.º 28
0
 def live_mode(self, default=True):
     return settings.get('live_mode', default)
Ejemplo n.º 29
0
 def focus_change_mode(self, default=True):
     return settings.get('focus_change_mode', default)
Ejemplo n.º 30
0
 def focus_change_mode():
     """Evaluate changes every time a view gets the focus."""
     return settings.get('focus_change_mode', True)
Ejemplo n.º 31
0
 def live_mode():
     """Evaluate changes every time the view is modified."""
     return settings.get('live_mode', True)
Ejemplo n.º 32
0
    def diff_line_change(self, row):
        """Use cached diff result to extract the changes of a certain line.

        Arguments:
            row (int): The row to find the changes for

        Returns:
            tuple: The tuple contains 4 items of information about changes
                around the row with (deleted_lines, start, size, meta).
        """
        hunk_re = r'^@@ \-(\d+),?(\d*) \+(\d+),?(\d*) @@'
        hunks = re.finditer(hunk_re, self._git_diff_cache, re.MULTILINE)

        # we also want to extract the position of the surrounding changes
        first_change = prev_change = next_change = None

        for hunk in hunks:
            _, _, start, size = hunk.groups()
            start = int(start)
            size = int(size or 1)
            if first_change is None:
                first_change = start
            # special handling to also match the line below deleted
            # content
            if size == 0 and row == start + 1:
                pass
            # continue if the hunk is before the line
            elif start + size < row:
                prev_change = start
                continue
            # break if the hunk is after the line
            elif row < start:
                break
            # in the following the line is inside the hunk
            try:
                next_hunk = next(hunks)
                hunk_end = next_hunk.start()
                next_change = int(next_hunk.group(3))
            except:
                hunk_end = len(self._git_diff_cache)

            # if wrap is disable avoid wrapping
            wrap = settings.get('next_prev_change_wrap', True)
            if not wrap:
                if prev_change is None:
                    prev_change = start
                if next_change is None:
                    next_change = start

            # if prev change is None set it to the wrap around the
            # document: prev -> last hunk, next -> first hunk
            if prev_change is None:
                try:
                    remaining_hunks = list(hunks)
                    if remaining_hunks:
                        last_hunk = remaining_hunks[-1]
                        prev_change = int(last_hunk.group(3))
                    elif next_change is not None:
                        prev_change = next_change
                    else:
                        prev_change = start
                except:
                    prev_change = start
            if next_change is None:
                next_change = first_change

            # extract the content of the hunk
            hunk_content = self._git_diff_cache[hunk.start():hunk_end]
            # store all deleted lines (starting with -)
            hunk_lines = hunk_content.splitlines()[1:]
            deleted_lines = [
                line[1:] for line in hunk_lines if line.startswith("-")
            ]
            added_lines = [
                line[1:] for line in hunk_lines if line.startswith("+")
            ]
            meta = {
                "added_lines": added_lines,
                "first_change": first_change,
                "next_change": next_change,
                "prev_change": prev_change
            }
            return (deleted_lines, start, size, meta)
        return ([], -1, -1, {})
Ejemplo n.º 33
0
    def process_diff_line_change(self, diff_str, line_nr):
        hunk_re = '^@@ \-(\d+),?(\d*) \+(\d+),?(\d*) @@'
        hunks = re.finditer(hunk_re, diff_str, re.MULTILINE)

        # we also want to extract the position of the surrounding changes
        first_change = prev_change = next_change = None

        for hunk in hunks:
            start = int(hunk.group(3))
            size = int(hunk.group(4) or 1)
            if first_change is None:
                first_change = start
            # special handling to also match the line below deleted
            # content
            if size == 0 and line_nr == start + 1:
                pass
            # continue if the hunk is before the line
            elif start + size < line_nr:
                prev_change = start
                continue
            # break if the hunk is after the line
            elif line_nr < start:
                break
            # in the following the line is inside the hunk
            try:
                next_hunk = next(hunks)
                hunk_end = next_hunk.start()
                next_change = int(next_hunk.group(3))
            except:
                hunk_end = len(diff_str)
            # extract the content of the hunk
            hunk_content = diff_str[hunk.start():hunk_end]
            # store all deleted lines (starting with -)
            lines = [line[1:] for line in hunk_content.split("\n")[1:]
                     if line.startswith("-")]

            # if wrap is disable avoid wrapping
            wrap = settings.get('next_prev_change_wrap', True)
            if not wrap:
                if prev_change is None:
                    prev_change = start
                if next_change is None:
                    next_change = start

            # if prev change is None set it to the wrap around the
            # document: prev -> last hunk, next -> first hunk
            if prev_change is None:
                try:
                    remaining_hunks = list(hunks)
                    if remaining_hunks:
                        last_hunk = remaining_hunks[-1]
                        prev_change = int(last_hunk.group(3))
                    elif next_change is not None:
                        prev_change = next_change
                    else:
                        prev_change = start
                except:
                    prev_change = start
            if next_change is None:
                next_change = first_change
            meta = {
                "first_change": first_change,
                "next_change": next_change,
                "prev_change": prev_change
            }
            return lines, start, size, meta
        return [], -1, -1, {}