diff --git a/README.md b/README.md index e4d6c13..2e536d9 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 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 channel diff --git a/tests/test_commands.py b/tests/test_commands.py index 0e5bcce..4db4aba 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -94,6 +94,64 @@ def test_enter_skips_refetch_when_preview_is_current(monkeypatch): assert shown == ["b.py", "a.py"] +@pytest.mark.parametrize("mode, all_mode", [("file_by_file", False), ("all_in_one", True), (None, False)]) +def test_a_new_review_view_starts_in_the_default_view_mode(monkeypatch, mode, all_mode): + option(monkeypatch, "default_view_mode", mode) + monkeypatch.setattr(commands, "resolve_repo", lambda window: "/repo") + monkeypatch.setattr(commands, "find_review_view", lambda window: sublime.View()) + _, st = commands.open_review(sublime.Window()) + assert st.all_mode is all_mode + + +def test_panel_in_all_in_one_mode_scrolls_instead_of_loading_files(monkeypatch): + loads, focused = [], [] + + def fake_show_all(view, st, focus=None): + # load at once: what show_all's done does once the diffs land + st.token += 1 + st.rendered_token = st.token + loads.append(st.change.path) + + monkeypatch.setattr(commands, "show_all", fake_show_all) + monkeypatch.setattr(commands, "show_file", lambda view, st, c: pytest.fail("loaded a single file")) + monkeypatch.setattr(commands.render, "render_all", lambda view, st, focus=None: focused.append(focus)) + st = state.ViewState("/repo", all_mode=True) + st.change = change("kept.py") + st.files = [st.change] + files = [change("a.py"), change("b.py")] + st.collapsed = {"b.py"} + window = sublime.Window() + commands.show_files_panel(window, sublime.View(), st, files, st.kept()) + _, on_select, _, on_highlight, _ = window.panel + assert loads == ["a.py"] + + # a highlight unfolds and scrolls to its file; Enter on it needs no redraw + on_highlight(1) + assert focused == ["b.py"] and st.change.path == "b.py" and "b.py" not in st.collapsed + on_select(1) + assert focused == ["b.py"] and loads == ["a.py"] + + # Esc goes back to the kept file, still in the all-in-one view + on_select(-1) + assert st.all_mode and st.change.path == "kept.py" and loads == ["a.py", "kept.py"] + + +def test_esc_goes_back_to_the_kept_view_mode(monkeypatch): + calls = [] + monkeypatch.setattr(commands, "show_file", lambda view, st, c: calls.append(("file", c.path))) + monkeypatch.setattr(commands, "show_all", lambda view, st, focus=None: calls.append(("all", st.change.path))) + st = state.ViewState("/repo") + st.change = change("kept.py") + st.files = [st.change] + kept = st.kept() + # the view switched to all-in-one after the snapshot; Esc still restores file-by-file + st.all_mode = True + window = sublime.Window() + commands.show_files_panel(window, sublime.View(), st, [change("a.py")], kept) + window.panel[1](-1) + assert calls[-1] == ("file", "kept.py") and not st.all_mode + + def test_on_cancel_goes_back_to_commit_list(monkeypatch): monkeypatch.setattr(commands, "show_file", lambda view, st, c: None) st = state.ViewState("/repo") diff --git a/tests/test_palette.py b/tests/test_palette.py index 222fb27..55e32e8 100644 --- a/tests/test_palette.py +++ b/tests/test_palette.py @@ -84,3 +84,66 @@ def test_a_hand_edited_override_is_never_clobbered(tmp_path): assert "// mine" in path.read_text() + + +def colour_option(monkeypatch, name, value): + """Set one of the colour settings for this test""" + from vdiff import utils + monkeypatch.setitem(sublime.load_settings(utils.SETTINGS_FILE), name, value) + + +def test_user_line_colours_get_rules_and_tint_the_token_layer(tmp_path, monkeypatch): + colour_option(monkeypatch, "deleted_line_background", "#ff000040") + colour_option(monkeypatch, "line_numbers_foreground", "#888888") + view = sublime.View() + palette.ensure(view) + + rules = by_scope(tmp_path) + assert rules[palette.SCOPE_LINE_DEL] == {"scope": palette.SCOPE_LINE_DEL, "background": "#ff000040"} + assert palette.SCOPE_LINE_INS not in rules + # the token layer repaints deleted text, so it carries the same background + colour = view.styles["comment"]["foreground"] + assert rules[palette.scope_for("del", colour)]["background"] == "#ff000040" + # a foreground alone still gets a background, or sublime would fill the region with the foreground + numbers = rules[palette.SCOPE_LINE_NUMBERS] + assert numbers["foreground"] == "#888888" and numbers["background"] + + +def test_changing_a_colour_setting_rewrites_the_override(tmp_path, monkeypatch): + view = sublime.View() + palette.ensure(view) + assert palette.SCOPE_LINE_INS not in by_scope(tmp_path) + colour_option(monkeypatch, "inserted_line_background", "#00ff0040") + palette.ensure(view) + assert by_scope(tmp_path)[palette.SCOPE_LINE_INS]["background"] == "#00ff0040" + + +def test_render_uses_the_user_scopes_only_once_set_and_live(monkeypatch): + view = sublime.View() + # unset: the scheme's diff tint and the plain-text line numbers, as before + assert palette.line_scope(view, "del") == palette.SCOPE_DEL + assert palette.line_numbers_scope(view) == "text.plain" + colour_option(monkeypatch, "deleted_line_background", "#ff000040") + colour_option(monkeypatch, "line_numbers_background", "#222222") + # set, but the override is not loaded yet: an unknown scope would paint nothing + assert palette.line_scope(view, "del") == palette.SCOPE_DEL + view.override_loaded = True + assert palette.line_scope(view, "del") == palette.SCOPE_LINE_DEL + assert palette.line_scope(view, "ins") == palette.SCOPE_INS + assert palette.line_numbers_scope(view) == palette.SCOPE_LINE_NUMBERS + + +def test_file_header_colours_come_from_settings(tmp_path, monkeypatch): + palette.ensure(sublime.View()) + rules = by_scope(tmp_path) + # unset: the built-in defaults + assert rules[palette.SCOPE_FILE_HEADER]["background"] == palette.FILE_HEADER_BACKGROUND + colour_option(monkeypatch, "file_header_background", "#202040") + colour_option(monkeypatch, "file_header_inserted_foreground", "#00ff00") + palette.ensure(sublime.View()) + rules = by_scope(tmp_path) + assert rules[palette.SCOPE_FILE_HEADER]["background"] == "#202040" + # the counts sit on the heading, so they share its background + 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 diff --git a/vDiff.sublime-settings b/vDiff.sublime-settings index d8f58b8..5242602 100644 --- a/vDiff.sublime-settings +++ b/vDiff.sublime-settings @@ -1,4 +1,7 @@ { + // how a review view first shows its files: "file_by_file" or "all_in_one"; v switches between them + "default_view_mode": "file_by_file", + // number of commits listed by "vDiff: Recent Commits" "commits_count": 30, @@ -14,6 +17,19 @@ // number of unchanged lines shown around each change in the all-files view "all_files_context": 5, + // background of deleted and inserted lines, e.g. "#d6464633"; empty uses the colour scheme's diff colours + "deleted_line_background": "", + "inserted_line_background": "", + + // line-number column of the all-files view, e.g. "#888888" on "#1e1e1e"; empty keeps the plain-text look + "line_numbers_foreground": "", + "line_numbers_background": "", + + // file heading of the all-files view: its background and the colours of its -/+ counts + "file_header_background": "#0000EE", + "file_header_deleted_foreground": "#d64646", + "file_header_inserted_foreground": "#46d646", + // git executable; sublime's PATH may not include a user-installed git "git_binary": "git", diff --git a/vdiff/commands.py b/vdiff/commands.py index f437e80..f7e2053 100644 --- a/vdiff/commands.py +++ b/vdiff/commands.py @@ -86,13 +86,17 @@ def open_review(window: sublime.Window) -> Optional[Tuple[sublime.View, ViewStat st = state.get(view) if st is None or st.repo != repo: # a different repository: browsing state no longer applies - st = state.put(view, state.ViewState(repo)) + all_mode = settings().get("default_view_mode") == "all_in_one" + st = state.put(view, state.ViewState(repo, all_mode=all_mode)) window.focus_view(view) return view, st def show_all(view: sublime.View, st: ViewState, focus: Optional[str] = None) -> None: - """Load all file diffs off the UI thread and switch to the aggregate view, on `focus`'s heading if given""" + """Load all file diffs off the UI thread and switch to the aggregate view, on `focus`'s heading + + Without `focus` the caret goes to the current file's heading, as it is when the diffs land + """ st.all_mode = True # bump the token so earlier loads drop their results st.token += 1 @@ -110,7 +114,8 @@ def show_all(view: sublime.View, st: ViewState, focus: Optional[str] = None) -> st.set_all_diffs(result) if st.change is None and files: st.change = files[0] - render.render_all(view, st, focus=focus) + render.render_all(view, st, focus=focus or (st.change.path if st.change else None)) + st.rendered_token = token run_async(work, done) @@ -183,12 +188,26 @@ def commit_items(commits: Sequence[Commit]) -> List[sublime.QuickPanelItem]: def show_files_panel(window: sublime.Window, view: sublime.View, st: ViewState, files: List[FileChange], kept: Kept, on_cancel: Optional[Callable[[], None]] = None, placeholder: str = "Changed files") -> None: - """Quick panel over `files`: highlight previews, Enter shows, Esc restores `kept` (then runs on_cancel)""" + """Quick panel over `files`: highlight previews, Enter shows, Esc restores `kept` (then runs on_cancel) + + In the aggregate view every diff is loaded once as the panel opens, and a highlight only scrolls to its file + """ # the panel opens on the shown file, the view focused behind it st.files = files selected = max(0, st.index()) window.focus_view(view) + def show(change: FileChange) -> None: + """Show `change`: its own diff, or its heading in the aggregate view""" + if not st.all_mode: + show_file(view, st, change) + return + st.change = change + st.collapsed.discard(change.path) + # still loading: show_all lands on st.change when the diffs arrive + if st.rendered_token == st.token: + render.render_all(view, st, focus=change.path) + def on_select(index: int) -> None: """Enter shows the chosen file; Esc restores `kept`, then runs on_cancel if given""" st.highlight += 1 # cancel a pending preview @@ -197,15 +216,18 @@ def show_files_panel(window: sublime.Window, view: sublime.View, st: ViewState, st.restore(kept) on_cancel() elif kept.change is not None: - # reshow the kept file + # reshow the kept file in the kept mode st.restore(kept) - show_file(view, st, kept.change) + if st.all_mode: + show_all(view, st) + else: + show_file(view, st, kept.change) # nothing was kept before: the last previewed file stays return change = files[index] if st.change is change and st.rendered_token == st.token: return # the preview already shows it - show_file(view, st, change) + show(change) def on_highlight(index: int) -> None: """Schedule a debounced preview of the highlighted file""" @@ -217,10 +239,13 @@ def show_files_panel(window: sublime.Window, view: sublime.View, st: ViewState, def preview() -> None: """Show the file unless another highlight came since""" if ticket == st.highlight: - show_file(view, st, files[index]) + show(files[index]) sublime.set_timeout(preview, PREVIEW_DELAY_MS) + if st.all_mode and files: + st.change = files[selected] + show_all(view, st) window.show_quick_panel(file_items(files), on_select, 0, selected, on_highlight, placeholder) diff --git a/vdiff/palette.py b/vdiff/palette.py index eb5e1c8..ede8f8f 100644 --- a/vdiff/palette.py +++ b/vdiff/palette.py @@ -14,6 +14,8 @@ from typing import Any, Dict, List, Optional, Set, Tuple import sublime +from .utils import settings + LOG = logging.getLogger("vdiff") # tints under the token layer: the scheme's own diff scopes need no restart, and the token layer repaints their text @@ -30,7 +32,7 @@ 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; the colours are user-requested +# all-files file heading 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" @@ -39,6 +41,16 @@ FILE_HEADER_BACKGROUND = "#0000FF" FILE_DEL_FOREGROUND = "#d64646" FILE_INS_FOREGROUND = "#46d646" +# colours the user may set; an empty line colour keeps the colour scheme's tint or the plain-text look +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") +# 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" +SCOPE_LINE_NUMBERS = "vdiff.line.numbers" + # tints to provision when the active scheme defines none of its own (some third-party schemes) FALLBACK_TINTS: Dict[str, str] = { SCOPE_DEL: "hsla(357, 45%, 60%, 0.15)", @@ -84,6 +96,8 @@ _palettes: Dict[str, Dict[str, int]] = {} _active: Optional[str] = None # schemes the user has already been told need a restart _notified: Set[Optional[str]] = set() +# {override file name: the user colours it was provisioned with}, so changing one re-provisions +_provisioned_with: Dict[str, Dict[str, str]] = {} def reset() -> None: @@ -91,6 +105,7 @@ def reset() -> None: global _active _palettes.clear() _notified.clear() + _provisioned_with.clear() _active = None @@ -161,6 +176,26 @@ def token_keys() -> List[str]: return [token_key(_scope_name(kind, i)) for kind in KINDS for i in sorted(colours.values())] +def user_colours() -> Dict[str, str]: + """{setting: colour} for COLOUR_SETTINGS, "" where unset""" + return {name: settings().get(name) or "" for name in COLOUR_SETTINGS} + + +def line_scope(view: sublime.View, kind: str) -> str: + """Tint scope for a changed line, "del" or "ins": the user's colour once its rule is live, else the scheme's""" + own, setting, scheme = ((SCOPE_LINE_DEL, "deleted_line_background", SCOPE_DEL) if kind == "del" else + (SCOPE_LINE_INS, "inserted_line_background", SCOPE_INS)) + return own if settings().get(setting) and scope_background(view, own) else scheme + + +def line_numbers_scope(view: sublime.View) -> str: + """Scope for the all-files line-number column: the user's colours once their rule is live, else plain text""" + if (settings().get("line_numbers_foreground") or settings().get("line_numbers_background")) \ + and scope_background(view, SCOPE_LINE_NUMBERS): + return SCOPE_LINE_NUMBERS + return "text.plain" + + def _scope_name(kind: str, index: int) -> str: """Generated scope name for a background kind and palette index""" return f"vdiff.{kind}.c{index}" @@ -174,8 +209,10 @@ def ensure(view: sublime.View) -> None: global _active name = override_name(view) _active = name - if not name or name in _palettes: + user = user_colours() + if not name or (name in _palettes and _provisioned_with.get(name) == user): return + _provisioned_with[name] = user # merge into the existing override, if any path = os.path.join(sublime.packages_path(), "User", name) try: @@ -186,10 +223,10 @@ def ensure(view: sublime.View) -> None: scheme = sublime.decode_value(f.read()) or {} rules = scheme.setdefault("rules", []) # probe the scheme, record the palette, and write only when a rule changed - tints = _tints(view) + tints = _tints(view, user) colours = {colour: i for i, colour in enumerate(_probe_palette(view))} _palettes[name] = colours - if not _merge_rules(rules, tints, colours, view): + if not _merge_rules(rules, tints, colours, view, user): return os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w", encoding="utf-8") as f: @@ -211,10 +248,11 @@ def _probe_palette(view: sublime.View) -> List[str]: return seen -def _tints(view: sublime.View) -> Dict[str, str]: +def _tints(view: sublime.View, user: Dict[str, str]) -> Dict[str, str]: """{tint scope: background} for the scheme's diff colours, falling back to ours per colour pair - All or nothing per pair: taking only the half a scheme defines would put our hue next to its own + All or nothing per pair: taking only the half a scheme defines would put our hue next to its own. + A line colour the user set wins over both """ tints: Dict[str, str] = {} for line_scope, char_scope in ((SCOPE_DEL, SCOPE_DEL_CHAR), (SCOPE_INS, SCOPE_INS_CHAR)): @@ -222,6 +260,9 @@ def _tints(view: sublime.View) -> Dict[str, str]: char = scope_background(view, char_scope) tints[line_scope] = line if line and char else FALLBACK_TINTS[line_scope] tints[char_scope] = char if line and char else FALLBACK_TINTS[char_scope] + for scope, setting in ((SCOPE_DEL, "deleted_line_background"), (SCOPE_INS, "inserted_line_background")): + if user[setting]: + tints[scope] = user[setting] # a region whose background is missing, transparent or equal to the editor's is filled with its # foreground, so context colours carry the editor background nudged by an invisible step tints[SCOPE_CTX] = _nudge(_global_background(view)) @@ -246,57 +287,55 @@ def _nudge(colour: str) -> str: return f"{colour[:5]}{blue + 1 if blue < 128 else blue - 1:02x}{colour[7:]}" +def _put_rule(rules: List[Dict[str, str]], present: Dict[Optional[str], Dict[str, str]], wanted: Dict[str, str]) -> bool: + """Add `wanted`, or make the rule with its scope equal to it; True when anything changed""" + rule = present.get(wanted["scope"]) + if rule is None: + rules.append(wanted) + elif rule != wanted: + rule.clear() + rule.update(wanted) + else: + return False + return True + + def _merge_rules(rules: List[Dict[str, str]], tints: Dict[str, str], colours: Dict[str, int], - view: sublime.View) -> bool: + view: sublime.View, user: Dict[str, str]) -> bool: """Bring the scheme's rules up to date, in place; True when anything changed Each (kind, colour) rule carries a background and a foreground, as a background alone would flatten the line's colours; plain tints are added only when missing and never overwritten, so a hand-tuned one survives """ present = {rule.get("scope"): rule for rule in rules} + # 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), + (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 + for scope, setting in ((SCOPE_LINE_DEL, "deleted_line_background"), (SCOPE_LINE_INS, "inserted_line_background")): + if user[setting]: + wanted.append({"scope": scope, "background": user[setting]}) + if user["line_numbers_foreground"] or user["line_numbers_background"]: + # both halves, as a region without a background is filled with its foreground + wanted.append({"scope": SCOPE_LINE_NUMBERS, + "foreground": user["line_numbers_foreground"] or foreground(view, "text.plain") or "#ffffff", + "background": user["line_numbers_background"] or _nudge(_global_background(view))}) changed = False - # the all-files file heading - header = present.get(SCOPE_FILE_HEADER) - wanted_header = {"scope": SCOPE_FILE_HEADER, "background": FILE_HEADER_BACKGROUND} - if header is None: - rules.append(wanted_header) - changed = True - elif header != wanted_header: - header.clear() - header.update(wanted_header) - changed = True - # its deletion and insertion counts - for scope, foreground in ((SCOPE_FILE_DEL_COUNT, FILE_DEL_FOREGROUND), - (SCOPE_FILE_INS_COUNT, FILE_INS_FOREGROUND)): - count_rule = present.get(scope) - wanted_count = {"scope": scope, "background": FILE_HEADER_BACKGROUND, - "foreground": foreground, "font_style": "bold"} - if count_rule is None: - rules.append(wanted_count) - changed = True - elif count_rule != wanted_count: - count_rule.clear() - count_rule.update(wanted_count) - changed = True + for rule in wanted: + changed = _put_rule(rules, present, rule) or changed # plain tints the scheme lacks; the context tint gets no rule of its own for scope, background in tints.items(): if scope != SCOPE_CTX and scope not in present and not scope_background(view, scope): rules.append({"scope": scope, "background": background}) changed = True - # one rule per (kind, colour) pair + # one rule per (kind, colour) pair; a palette shifted under an earlier session's rule is ours to correct for colour, index in colours.items(): for kind in KINDS: - wanted = {"scope": _scope_name(kind, index), "background": tints[KIND_TINT[kind]], - "foreground": colour} - rule = present.get(wanted["scope"]) - if rule is None: - rules.append(wanted) - changed = True - elif rule != wanted: - # the palette shifted under a rule an earlier session wrote; ours to keep correct - rule.clear() - rule.update(wanted) - changed = True + pair = {"scope": _scope_name(kind, index), "background": tints[KIND_TINT[kind]], "foreground": colour} + changed = _put_rule(rules, present, pair) or changed return changed diff --git a/vdiff/render.py b/vdiff/render.py index 46abaf3..7af956f 100644 --- a/vdiff/render.py +++ b/vdiff/render.py @@ -388,12 +388,12 @@ def render_all(view: sublime.View, st: ViewState, focus: Union[None, str, Tuple[ elif line.kind == "+": ins_regions.append(region) # paint everything - view.add_regions(KEY_DEL, del_regions, palette.SCOPE_DEL, flags=sublime.DRAW_NO_OUTLINE) - view.add_regions(KEY_INS, ins_regions, palette.SCOPE_INS, flags=sublime.DRAW_NO_OUTLINE) + view.add_regions(KEY_DEL, del_regions, palette.line_scope(view, "del"), flags=sublime.DRAW_NO_OUTLINE) + view.add_regions(KEY_INS, ins_regions, palette.line_scope(view, "ins"), flags=sublime.DRAW_NO_OUTLINE) 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_PREFIX, prefix_regions, "text.plain", 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) insertions, deletions = _totals(st.files) @@ -515,8 +515,8 @@ def _paint_regions(view: sublime.View, st: ViewState) -> None: # full-line tint starts = _row_starts(lines) deleted, inserted = _line_regions(lines, starts) - view.add_regions(KEY_DEL, deleted, palette.SCOPE_DEL, flags=sublime.DRAW_NO_OUTLINE) - view.add_regions(KEY_INS, inserted, palette.SCOPE_INS, flags=sublime.DRAW_NO_OUTLINE) + view.add_regions(KEY_DEL, deleted, palette.line_scope(view, "del"), flags=sublime.DRAW_NO_OUTLINE) + view.add_regions(KEY_INS, inserted, palette.line_scope(view, "ins"), flags=sublime.DRAW_NO_OUTLINE) # gutter dots mark_flags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE del_marks = [sublime.Region(starts[i]) for i, line in enumerate(lines) if line.kind == "-"] diff --git a/vdiff/state.py b/vdiff/state.py index ad5f2f8..f0b810e 100644 --- a/vdiff/state.py +++ b/vdiff/state.py @@ -22,6 +22,7 @@ class Kept(NamedTuple): commit: Optional["CommitInfo"] change: Optional["FileChange"] files: List["FileChange"] + all_mode: bool def find_change(files: Sequence["FileChange"], path: Optional[str]) -> int: @@ -74,7 +75,7 @@ class ViewState: def kept(self) -> Kept: """Snapshot of what to go back to on Esc""" - return Kept(self.sha, self.commit, self.change, list(self.files)) + return Kept(self.sha, self.commit, self.change, list(self.files), self.all_mode) def enter(self, sha: Optional[str] = None, commit: Optional["CommitInfo"] = None) -> Kept: """Switch to browsing a commit (or the working tree); returns what to restore on Esc""" @@ -86,7 +87,7 @@ class ViewState: def restore(self, kept: Kept) -> None: """Go back to a snapshot; its file becomes the requested one""" - self.sha, self.commit, self.change, self.files = kept + self.sha, self.commit, self.change, self.files, self.all_mode = kept self.requested = kept.change def set_all_diffs(self, diffs: Dict[str, Tuple[List["Line"], List["Block"], bool]]) -> None: