From cf120991bce4164335622b94968e40d725dca852 Mon Sep 17 00:00:00 2001 From: Vaclav Tvrdik Date: Sat, 10 Oct 2026 15:02:39 +0200 Subject: [PATCH] in all in one view mode show file status (modified/staged/added/deleted/undecided/?) --- README.md | 2 +- tests/test_commands.py | 18 ++++++++++++++++++ tests/test_palette.py | 10 ++++++++++ vDiff.sublime-settings | 3 ++- vdiff/palette.py | 9 ++++++--- vdiff/render.py | 15 +++++++++++---- 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2e536d9..ce057c3 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Use one method or the other, not both: an unpacked `Packages/vDiff` directory ov | `?` | show this key list in a popup | | Esc | close the view | -The header links do the same with the mouse. The all-files view shows five context lines around each change by default; configure this with `all_files_context` in `vDiff.sublime-settings`. The same file sets the background of deleted and inserted lines (`deleted_line_background`, `inserted_line_background`) and the colours of the all-files line-number column (`line_numbers_foreground`, `line_numbers_background`); left empty, they follow the colour scheme. The all-files file heading takes its colours from `file_header_background`, `file_header_deleted_foreground` and `file_header_inserted_foreground`. +The header links do the same with the mouse. The all-files view shows five context lines around each change by default; configure this with `all_files_context` in `vDiff.sublime-settings`. The same file sets the background of deleted and inserted lines (`deleted_line_background`, `inserted_line_background`) and the colours of the all-files line-number column (`line_numbers_foreground`, `line_numbers_background`); left empty, they follow the colour scheme. Each all-files file heading shows the file's status letter (`s` when anything is staged, else `m`, `d`, `a`, `?` ...) before its -/+ counts, and takes its colours from `file_header_background`, `file_header_status_foreground`, `file_header_deleted_foreground` and `file_header_inserted_foreground`. ## The channel diff --git a/tests/test_commands.py b/tests/test_commands.py index 4db4aba..2f87c6c 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -722,6 +722,24 @@ def all_files_view(): return view, st +def test_all_files_headings_show_the_status_letter_before_the_counts(): + from vdiff import parse + view = sublime.View() + st = state.ViewState("/repo") + st.all_mode = True + st.files = [git.FileChange("a - b.py", "M", True, 1, 0, None), change("m.py"), change("gone.py", "D")] + lines = [parse.Line("+", None, 1, "x")] + st.all_diffs = {c.path: (lines, parse.change_blocks(lines), False) for c in st.files} + commands.render.render_all(view, st) + + headings = [row for row in view.text.split("\n") if row.startswith("▼")] + # staged wins over the git letter; a path containing " -" must not confuse the letter's position + assert headings == ["▼ a - b.py s -0 +1", "▼ m.py m -0 +1", "▼ gone.py d -0 +1"] + regions, scope, _, _ = view.regions[commands.render.KEY_FILE_STATUS] + assert scope == commands.render.palette.SCOPE_FILE_STATUS + assert [view.text[r.a:r.b] for r in regions] == ["s", "m", "d"] + + def heading_row(st, path): return next(i for i, (c, _, section) in enumerate(st.all_rows) if section and c.path == path) diff --git a/tests/test_palette.py b/tests/test_palette.py index 55e32e8..86ee344 100644 --- a/tests/test_palette.py +++ b/tests/test_palette.py @@ -147,3 +147,13 @@ def test_file_header_colours_come_from_settings(tmp_path, monkeypatch): assert rules[palette.SCOPE_FILE_INS_COUNT]["background"] == "#202040" assert rules[palette.SCOPE_FILE_INS_COUNT]["foreground"] == "#00ff00" assert rules[palette.SCOPE_FILE_DEL_COUNT]["foreground"] == palette.FILE_DEL_FOREGROUND + + +def test_the_status_letter_is_white_on_the_heading_unless_set(tmp_path, monkeypatch): + palette.ensure(sublime.View()) + rule = by_scope(tmp_path)[palette.SCOPE_FILE_STATUS] + assert rule["foreground"] == palette.FILE_STATUS_FOREGROUND == "#ffffff" + assert rule["background"] == by_scope(tmp_path)[palette.SCOPE_FILE_HEADER]["background"] + colour_option(monkeypatch, "file_header_status_foreground", "#ffff00") + palette.ensure(sublime.View()) + assert by_scope(tmp_path)[palette.SCOPE_FILE_STATUS]["foreground"] == "#ffff00" diff --git a/vDiff.sublime-settings b/vDiff.sublime-settings index 5242602..fcf15d8 100644 --- a/vDiff.sublime-settings +++ b/vDiff.sublime-settings @@ -25,8 +25,9 @@ "line_numbers_foreground": "", "line_numbers_background": "", - // file heading of the all-files view: its background and the colours of its -/+ counts + // file heading of the all-files view: its background and the colours of its status letter and -/+ counts "file_header_background": "#0000EE", + "file_header_status_foreground": "#ffffff", "file_header_deleted_foreground": "#d64646", "file_header_inserted_foreground": "#46d646", diff --git a/vdiff/palette.py b/vdiff/palette.py index ede8f8f..3d35793 100644 --- a/vdiff/palette.py +++ b/vdiff/palette.py @@ -32,11 +32,13 @@ KINDS: Tuple[str, ...] = TINT_KINDS + ("ctx",) KIND_TINT: Dict[str, str] = {"del": SCOPE_DEL, "ins": SCOPE_INS, "del.char": SCOPE_DEL_CHAR, "ins.char": SCOPE_INS_CHAR, "ctx": SCOPE_CTX} -# all-files file heading and its -/+ counts; their colours come from settings, these when one is empty +# all-files file heading, its status letter and its -/+ counts; their colours come from settings, these when one is empty SCOPE_FILE_HEADER = "vdiff.file.header" SCOPE_FILE_DEL_COUNT = "vdiff.file.del.count" SCOPE_FILE_INS_COUNT = "vdiff.file.ins.count" +SCOPE_FILE_STATUS = "vdiff.file.status" FILE_HEADER_BACKGROUND = "#0000FF" +FILE_STATUS_FOREGROUND = "#ffffff" # sublime's former markup.deleted / markup.inserted colours FILE_DEL_FOREGROUND = "#d64646" FILE_INS_FOREGROUND = "#46d646" @@ -45,7 +47,7 @@ FILE_INS_FOREGROUND = "#46d646" COLOUR_SETTINGS: Tuple[str, ...] = ("deleted_line_background", "inserted_line_background", "line_numbers_foreground", "line_numbers_background", "file_header_background", "file_header_deleted_foreground", - "file_header_inserted_foreground") + "file_header_inserted_foreground", "file_header_status_foreground") # the scopes those colours go into: the changed-line tints and the all-files line-number column SCOPE_LINE_DEL = "vdiff.line.del" SCOPE_LINE_INS = "vdiff.line.ins" @@ -311,7 +313,8 @@ def _merge_rules(rules: List[Dict[str, str]], tints: Dict[str, str], colours: Di # the all-files file heading and its deletion and insertion counts header = user["file_header_background"] or FILE_HEADER_BACKGROUND wanted = [{"scope": SCOPE_FILE_HEADER, "background": header}] - for scope, colour in ((SCOPE_FILE_DEL_COUNT, user["file_header_deleted_foreground"] or FILE_DEL_FOREGROUND), + for scope, colour in ((SCOPE_FILE_STATUS, user["file_header_status_foreground"] or FILE_STATUS_FOREGROUND), + (SCOPE_FILE_DEL_COUNT, user["file_header_deleted_foreground"] or FILE_DEL_FOREGROUND), (SCOPE_FILE_INS_COUNT, user["file_header_inserted_foreground"] or FILE_INS_FOREGROUND)): wanted.append({"scope": scope, "background": header, "foreground": colour, "font_style": "bold"}) # the user's line colours; an unset one keeps any old rule, which render then ignores diff --git a/vdiff/render.py b/vdiff/render.py index 7af956f..6d3c69b 100644 --- a/vdiff/render.py +++ b/vdiff/render.py @@ -25,6 +25,7 @@ KEY_INS_CHAR = "vdiff.ins.char" KEY_FILE_HEADER = "vdiff.file.header" KEY_FILE_DEL_COUNT = palette.SCOPE_FILE_DEL_COUNT KEY_FILE_INS_COUNT = palette.SCOPE_FILE_INS_COUNT +KEY_FILE_STATUS = palette.SCOPE_FILE_STATUS KEY_FILE_PREFIX = "vdiff.file.prefix" REGION_KEYS = ( KEY_DEL, @@ -36,6 +37,7 @@ REGION_KEYS = ( KEY_FILE_HEADER, KEY_FILE_DEL_COUNT, KEY_FILE_INS_COUNT, + KEY_FILE_STATUS, KEY_FILE_PREFIX, ) @@ -318,12 +320,13 @@ def render_all(view: sublime.View, st: ViewState, focus: Union[None, str, Tuple[ st.all_change_targets.append((change.path, hunk_index)) hunk_index += 1 in_change = changed - # the heading: fold marker, path and the file's -/+ counts + # the heading: fold marker, path, lowercase status letter (s when anything is staged) and the file's -/+ counts ins, dele = parse.counts(lines) marker = "▶" if change.path in st.collapsed else "▼" if change.path == focus: heading = len(rows) - rows.append(f"{marker} {change.path} -{dele} +{ins}") + letter = "s" if change.staged else change.status.lower() + rows.append(f"{marker} {change.path} {letter} -{dele} +{ins}") metadata.append((change, None, True)) # the diff rows, recording the row where each visible change starts if change.path not in st.collapsed: @@ -351,7 +354,7 @@ def render_all(view: sublime.View, st: ViewState, focus: Union[None, str, Tuple[ # per-row regions: heading counts, number prefix, tint, token colours starts = _row_starts_for_text(rows) del_regions, ins_regions, file_regions, prefix_regions = [], [], [], [] - file_del_counts, file_ins_counts = [], [] + file_del_counts, file_ins_counts, file_statuses = [], [], [] token_groups: Dict[str, List[sublime.Region]] = {} default_colour = palette.foreground(view, "text.plain") resolved = palette.tokens_resolved(view) @@ -360,9 +363,12 @@ def render_all(view: sublime.View, st: ViewState, focus: Union[None, str, Tuple[ for i, (change, line, section) in enumerate(metadata): if section: file_regions.append(sublime.Region(starts[i], starts[i] + len(rows[i]))) - # colour just the counts over the heading tint; rfind, as a path may contain " -" + # colour just the status letter and counts over the heading tint; rfind, as a path may contain " -" minus = rows[i].rfind(" -") + 1 plus = rows[i].rfind(" +") + 1 + if minus >= 2: + # the letter sits just before the space before the deletion count + file_statuses.append(sublime.Region(starts[i] + minus - 2, starts[i] + minus - 1)) if minus and plus > minus: file_del_counts.append(sublime.Region(starts[i] + minus, starts[i] + plus - 1)) if plus: @@ -393,6 +399,7 @@ def render_all(view: sublime.View, st: ViewState, focus: Union[None, str, Tuple[ view.add_regions(KEY_FILE_HEADER, file_regions, _file_header_scope(view), flags=sublime.DRAW_NO_OUTLINE) view.add_regions(KEY_FILE_DEL_COUNT, file_del_counts, KEY_FILE_DEL_COUNT, flags=sublime.DRAW_NO_OUTLINE) view.add_regions(KEY_FILE_INS_COUNT, file_ins_counts, KEY_FILE_INS_COUNT, flags=sublime.DRAW_NO_OUTLINE) + view.add_regions(KEY_FILE_STATUS, file_statuses, KEY_FILE_STATUS, flags=sublime.DRAW_NO_OUTLINE) view.add_regions(KEY_FILE_PREFIX, prefix_regions, palette.line_numbers_scope(view), flags=sublime.DRAW_NO_OUTLINE) for scope, regions in token_groups.items(): view.add_regions(palette.token_key(scope), regions, scope, flags=sublime.DRAW_NO_OUTLINE)