<commit_before>import os
from functools import partial

import sublime
from sublime_plugin import WindowCommand, TextCommand, EventListener

from .base_command import BaseCommand
from ..common import util

STATUS_TITLE = "STATUS: {}"

STAGED_TEMPLATE = """
  STAGED:
{}
"""

UNSTAGED_TEMPLATE = """
  UNSTAGED:
{}
"""

UNTRACKED_TEMPLATE = """
  UNTRACKED:
{}
"""

MERGE_CONFLICTS_TEMPLATE = """
  MERGE CONFLICTS:
{}
"""

STASHES_TEMPLATE = """

  STASHES:
{}
"""

STATUS_HEADER_TEMPLATE = """
  REMOTE:    {remote_info}
  LOCAL:     {local_info}
  INFO:      Your branch is {branch_info}.
"""

KEY_BINDINGS_MENU = """
  ###################                   ###############
  ## SELECTED FILE ##                   ## ALL FILES ##
  ###################                   ###############

  [o] open file                         [a] stage all unstaged files
  [s] stage file                        [A] stage all unstaged and untracked files
  [u] unstage file                      [U] unstage all staged files
  [d] discard file                      [D] discard all unstaged changes
  [h] open file on remote
  [R] reset file to HEAD
  [M] resolve conflict with Sublimerge

  [f] diff file                         [F] diff all files
  [l] diff file inline

  #############                         #############
  ## ACTIONS ##                         ## STASHES ##
  #############                         #############

  [c] commit                            [t][a] apply stash
  [C] commit, including unstaged        [t][p] pop stash
  [m] amend previous commit             [t][c] create stash
                                        [t][C] create stash including untracked files
  [i] ignore file                       [t][d] discard stash
  [I] ignore pattern

  ###########
  ## OTHER ##
  ###########

  [r] refresh status

-
"""

MERGE_CONFLICT_PORCELAIN_STATUSES = (
    ("D", "D"),  # unmerged, both deleted
    ("A", "U"),  # unmerged, added by us
    ("U", "D"),  # unmerged, deleted by them
    ("U", "A"),  # unmerged, added by them
    ("D", "U"),  # unmerged, deleted by us
    ("A", "A"),  # unmerged, both added
    ("U", "U")  # unmerged, both modified
)

status_view_section_ranges = {}


class GgShowStatusCommand(WindowCommand, BaseCommand):

    """
    Open a status view for the active git repository.
    """

    def run(self):
        repo_path = self.repo_path
        title = STATUS_TITLE.format(os.path.basename(repo_path))
        status_view = self.get_read_only_view("status")
        status_view.set_name(title)
        status_view.set_syntax_file("Packages/GitGadget/syntax/status.tmLanguage")
        status_view.settings().set("git_gadget.repo_path", repo_path)
        self.window.focus_view(status_view)
        status_view.sel().clear()

        status_view.run_command("gg_status_refresh")


class GgStatusRefreshCommand(TextCommand, BaseCommand):

    """
    Get the current state of the git repo and display file status
    and command menu to the user.
    """

    def run(self, edit, cursor=None):
        status_contents, ranges = self.get_contents()
        status_view_section_ranges[self.view.id()] = ranges

        self.view.set_read_only(False)
        self.view.replace(edit, sublime.Region(0, self.view.size()), status_contents)
        self.view.set_read_only(True)

        selections = self.view.sel()
        if cursor is not None:
            selections.clear()
            pt = sublime.Region(cursor, cursor)
            selections.add(pt)
        elif not len(selections):
            pt = sublime.Region(0, 0)
            selections.add(pt)

    def get_contents(self):
        header = STATUS_HEADER_TEMPLATE.format(
            remote_info="unimplemented",
            local_info="unimplemented",
            branch_info="unimplemented",
        )

        cursor = len(header)
        staged, unstaged, untracked, conflicts = self.sort_status_entries(self.get_status())
        unstaged_region, conflicts_region, untracked_region, staged_region = (sublime.Region(0, 0), ) * 4

        def get_region(new_text):
            nonlocal cursor
            start = cursor
            cursor += len(new_text)
            end = cursor
            return sublime.Region(start, end)

        status_text = ""

        if unstaged:
            unstaged_lines = "\n".join("    " + f.path for f in unstaged)
            unstaged_text = UNSTAGED_TEMPLATE.format(unstaged_lines)
            unstaged_region = get_region(unstaged_text)
            status_text += unstaged_text
        if conflicts:
            conflicts_lines = "\n".join("    " + f.path for f in conflicts)
            conflicts_text = MERGE_CONFLICTS_TEMPLATE.format(conflicts_lines)
            conflicts_region = get_region(conflicts_text)
            status_text += conflicts_text
        if untracked:
            untracked_lines = "\n".join("    " + f.path for f in untracked)
            untracked_text = UNTRACKED_TEMPLATE.format(untracked_lines)
            untracked_region = get_region(untracked_text)
            status_text += untracked_text
        if staged:
            staged_lines = "\n".join("    " + f.path for f in staged)
            staged_text = STAGED_TEMPLATE.format(staged_lines)
            staged_region = get_region(staged_text)
            status_text += staged_text

        contents = header + status_text + KEY_BINDINGS_MENU

        return contents, (unstaged_region, conflicts_region, untracked_region, staged_region)

    @staticmethod
    def sort_status_entries(file_status_list):
        staged, unstaged, untracked, conflicts = [], [], [], []

        for f in file_status_list:
            if f.index_status == "?":
                untracked.append(f)
            elif (f.index_status, f.working_status) in MERGE_CONFLICT_PORCELAIN_STATUSES:
                conflicts.append(f)
            elif f.working_status in ("M", "D"):
                unstaged.append(f)
            else:
                staged.append(f)<commit_msg>more accurate listing of staged/unstaged files if parts of files are staged<commit_after>import os
from functools import partial

import sublime
from sublime_plugin import WindowCommand, TextCommand, EventListener

from .base_command import BaseCommand
from ..common import util

STATUS_TITLE = "STATUS: {}"

STAGED_TEMPLATE = """
  STAGED:
{}
"""

UNSTAGED_TEMPLATE = """
  UNSTAGED:
{}
"""

UNTRACKED_TEMPLATE = """
  UNTRACKED:
{}
"""

MERGE_CONFLICTS_TEMPLATE = """
  MERGE CONFLICTS:
{}
"""

STASHES_TEMPLATE = """

  STASHES:
{}
"""

STATUS_HEADER_TEMPLATE = """
  REMOTE:    {remote_info}
  LOCAL:     {local_info}
  INFO:      Your branch is {branch_info}.
"""

KEY_BINDINGS_MENU = """
  ###################                   ###############
  ## SELECTED FILE ##                   ## ALL FILES ##
  ###################                   ###############

  [o] open file                         [a] stage all unstaged files
  [s] stage file                        [A] stage all unstaged and untracked files
  [u] unstage file                      [U] unstage all staged files
  [d] discard file                      [D] discard all unstaged changes
  [h] open file on remote
  [R] reset file to HEAD
  [M] resolve conflict with Sublimerge

  [f] diff file                         [F] diff all files
  [l] diff file inline

  #############                         #############
  ## ACTIONS ##                         ## STASHES ##
  #############                         #############

  [c] commit                            [t][a] apply stash
  [C] commit, including unstaged        [t][p] pop stash
  [m] amend previous commit             [t][c] create stash
                                        [t][C] create stash including untracked files
  [i] ignore file                       [t][d] discard stash
  [I] ignore pattern

  ###########
  ## OTHER ##
  ###########

  [r] refresh status

-
"""

MERGE_CONFLICT_PORCELAIN_STATUSES = (
    ("D", "D"),  # unmerged, both deleted
    ("A", "U"),  # unmerged, added by us
    ("U", "D"),  # unmerged, deleted by them
    ("U", "A"),  # unmerged, added by them
    ("D", "U"),  # unmerged, deleted by us
    ("A", "A"),  # unmerged, both added
    ("U", "U")  # unmerged, both modified
)

status_view_section_ranges = {}


class GgShowStatusCommand(WindowCommand, BaseCommand):

    """
    Open a status view for the active git repository.
    """

    def run(self):
        repo_path = self.repo_path
        title = STATUS_TITLE.format(os.path.basename(repo_path))
        status_view = self.get_read_only_view("status")
        status_view.set_name(title)
        status_view.set_syntax_file("Packages/GitGadget/syntax/status.tmLanguage")
        status_view.settings().set("git_gadget.repo_path", repo_path)
        self.window.focus_view(status_view)
        status_view.sel().clear()

        status_view.run_command("gg_status_refresh")


class GgStatusRefreshCommand(TextCommand, BaseCommand):

    """
    Get the current state of the git repo and display file status
    and command menu to the user.
    """

    def run(self, edit, cursor=None):
        status_contents, ranges = self.get_contents()
        status_view_section_ranges[self.view.id()] = ranges

        self.view.set_read_only(False)
        self.view.replace(edit, sublime.Region(0, self.view.size()), status_contents)
        self.view.set_read_only(True)

        selections = self.view.sel()
        if cursor is not None:
            selections.clear()
            pt = sublime.Region(cursor, cursor)
            selections.add(pt)
        elif not len(selections):
            pt = sublime.Region(0, 0)
            selections.add(pt)

    def get_contents(self):
        header = STATUS_HEADER_TEMPLATE.format(
            remote_info="unimplemented",
            local_info="unimplemented",
            branch_info="unimplemented",
        )

        cursor = len(header)
        staged, unstaged, untracked, conflicts = self.sort_status_entries(self.get_status())
        unstaged_region, conflicts_region, untracked_region, staged_region = (sublime.Region(0, 0), ) * 4

        def get_region(new_text):
            nonlocal cursor
            start = cursor
            cursor += len(new_text)
            end = cursor
            return sublime.Region(start, end)

        status_text = ""

        if unstaged:
            unstaged_lines = "\n".join("    " + f.path for f in unstaged)
            unstaged_text = UNSTAGED_TEMPLATE.format(unstaged_lines)
            unstaged_region = get_region(unstaged_text)
            status_text += unstaged_text
        if conflicts:
            conflicts_lines = "\n".join("    " + f.path for f in conflicts)
            conflicts_text = MERGE_CONFLICTS_TEMPLATE.format(conflicts_lines)
            conflicts_region = get_region(conflicts_text)
            status_text += conflicts_text
        if untracked:
            untracked_lines = "\n".join("    " + f.path for f in untracked)
            untracked_text = UNTRACKED_TEMPLATE.format(untracked_lines)
            untracked_region = get_region(untracked_text)
            status_text += untracked_text
        if staged:
            staged_lines = "\n".join("    " + f.path for f in staged)
            staged_text = STAGED_TEMPLATE.format(staged_lines)
            staged_region = get_region(staged_text)
            status_text += staged_text

        contents = header + status_text + KEY_BINDINGS_MENU

        return contents, (unstaged_region, conflicts_region, untracked_region, staged_region)

    @staticmethod
    def sort_status_entries(file_status_list):
        staged, unstaged, untracked, conflicts = [], [], [], []

        for f in file_status_list:
            if (f.index_status, f.working_status) in MERGE_CONFLICT_PORCELAIN_STATUSES:
                conflicts.append(f)
                continue
            if f.index_status == "?":
                untracked.append(f)
                continue
            elif f.working_status in ("M", "D"):
                unstaged.append(f)
            if f.index_status != " ":
                staged.append(f)
{'fraction_non_alphanumeric': 0.12297520661157024, 'fraction_numerical': 0.0009917355371900827, 'mean_word_length': 2.1878292939936776, 'pattern_counts': {'":': 3, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1411421', 'n_tokens_mistral': 3812, 'n_tokens_neox': 3641, 'n_words': 825}