Branches view initial version

This commit is contained in:
Vaclav Tvrdik
2026-10-13 13:54:51 +02:00
parent cf120991bc
commit dc11d4e688
20 changed files with 2807 additions and 221 deletions

View File

@@ -4,6 +4,7 @@ import pytest
import sublime # the stub, via conftest
from conftest import put_caret # noqa: E402
from vdiff import commands, git, state, utils
@@ -163,6 +164,58 @@ def test_on_cancel_goes_back_to_commit_list(monkeypatch):
assert back == [1] and st.sha is None
def test_esc_from_the_reopened_commit_list_returns_to_the_branches_view(monkeypatch):
loads = []
monkeypatch.setattr(commands.branches, "load", lambda view, st, keep_caret=False: loads.append(st.mode))
window = sublime.Window()
view = sublime.View()
st = state.ViewState("/repo")
st.mode = state.MODE_BRANCHES
commit = git.Commit("a" * 40, "a" * 7, "me", "today", "subject", 1, 1, 0)
# Esc on the list opened straight over the branches view: nothing to put back
st.drawn, st.rendered_token = st.describe(), st.token
commands.VdiffCommitsCommand(window).show_commits(view, st, [commit], 0)
window.panel[1](-1)
assert loads == []
# reopened after a commit's files were cancelled: the state says branches, the screen still shows the commit
st.drawn = (state.MODE_DIFF, "a" * 40, False, "x.py")
commands.VdiffCommitsCommand(window).show_commits(view, st, [commit], 0)
window.panel[1](-1)
assert loads == [state.MODE_BRANCHES]
# the same holds for a diff the list was reopened over: the kept working-tree file is shown again
shown = []
monkeypatch.setattr(commands, "show_file", lambda view, st, c: shown.append(c.path))
st.mode, st.change = state.MODE_DIFF, change("kept.py")
commands.VdiffCommitsCommand(window).show_commits(view, st, [commit], 0)
window.panel[1](-1)
assert shown == ["kept.py"]
def test_files_on_a_clean_tree_leave_the_branches_view_in_place(monkeypatch):
view, st = sublime.View(), state.ViewState("/repo")
messages, notices = [], []
monkeypatch.setattr(commands, "open_review", lambda window: (view, st))
monkeypatch.setattr(commands, "status", messages.append)
monkeypatch.setattr(commands.render, "render_message", lambda view, st, text: notices.append(text))
monkeypatch.setattr(commands.git, "changed_files", lambda repo: [])
window = sublime.Window()
st.mode = state.MODE_BRANCHES
commands.VdiffFilesCommand(window).run()
assert messages == ["no uncommitted changes"] and notices == [] and st.in_branches and window.panel is None
# with changes the panel comes up as usual; in a diff view the notice is shown
monkeypatch.setattr(commands.git, "changed_files", lambda repo: [change("a.py")])
commands.VdiffFilesCommand(window).run()
assert window.panel is not None
# nor does a commit under review give way to it
st.mode, st.sha = state.MODE_DIFF, "a" * 40
monkeypatch.setattr(commands.git, "changed_files", lambda repo: [])
commands.VdiffFilesCommand(window).run()
assert notices == [] and st.sha == "a" * 40
st.sha = None
commands.VdiffFilesCommand(window).run()
assert notices == ["No uncommitted changes in /repo"]
def test_step_change_uses_blocks(rings):
view, st = at_change([(2, 4), (7, 8)], 0)
commands.ACTIONS["next_change"](view, None, st)
@@ -476,14 +529,6 @@ def test_esc_rewinds_the_file_navigation_counts_from(loading):
# --- cycling past the ends --------------------------------------------------
@pytest.fixture
def rings(monkeypatch):
"""Record visual bells instead of drawing them"""
bells = []
monkeypatch.setattr(commands.render, "flash", lambda view: bells.append(view))
return bells
def at_file(files, path):
"""A loaded working-tree view sitting on `path`"""
view, window = sublime.View(), sublime.Window()
@@ -600,6 +645,9 @@ def test_next_unstaged_scans_forward_and_wraps():
assert commands._next_unstaged(files, 2) == 2 # the slot we count from counts itself
assert commands._next_unstaged(files, 3) == 0 # wraps past the end
assert commands._next_unstaged(flagged(True, True), 0) == -1
# a partially staged file still has hunks to stage
partial = git.FileChange("p.py", "M", True, 1, 1, None, unstaged=True)
assert commands._next_unstaged(flagged(True, True) + [partial], 0) == 2
def test_staging_moves_to_the_next_unstaged_file(monkeypatch):
@@ -653,6 +701,10 @@ def test_stage_all_and_unstage_all_need_something_to_do(monkeypatch):
assert press(monkeypatch, "stage_all", flagged(True, True), "f0.py", []) == []
assert press(monkeypatch, "unstage_all", flagged(False, False), "f0.py", []) == []
assert messages == ["nothing to stage", "nothing to unstage"]
# a partially staged file is something to stage
partial = git.FileChange("p.py", "M", True, 1, 1, None, unstaged=True)
assert press(monkeypatch, "stage_all", flagged(True) + [partial], "f0.py", flagged(True, True)) == ["f0.py"]
assert messages[-1] == "staged 1 file"
view, window, st = at_file(flagged(False), "f0.py")
st.enter("abc", git.CommitInfo("abc", "abc1234", "Ann", "2026-09-23 10:00", "Fix things", ""))
commands.ACTIONS["stage_all"](view, window, st)
@@ -744,9 +796,31 @@ def heading_row(st, path):
return next(i for i, (c, _, section) in enumerate(st.all_rows) if section and c.path == path)
def put_caret(view, row):
view.sel().clear()
view.sel().add(sublime.Region(commands.render.index_point(view, row)))
def test_the_header_follows_the_caret_from_file_to_file():
from vdiff import listeners
view, st = all_files_view()
st.files[1] = git.FileChange("b.py", "M", False, 0, 4, None)
commands.render.render_all(view, st)
st.rendered_token = st.token
commands.render.init_view(view)
state.put(view, st)
title = lambda: view.phantoms[commands.render.PHANTOM_KEY][0].content.split("</div>", 1)[1] # noqa: E731
listener = listeners.VdiffEventListener()
put_caret(view, heading_row(st, "b.py") + 1)
listener.on_selection_modified(view)
assert st.change.path == "b.py" and ">b.py<" in title() and '<span class="del">-4</span> <span class="ins">+0</span>' in title()
# within the same file nothing is redrawn; back in a.py the header is a.py's again
shown = view.phantoms[commands.render.PHANTOM_KEY]
listener.on_selection_modified(view)
assert view.phantoms[commands.render.PHANTOM_KEY] is shown
put_caret(view, heading_row(st, "a.py"))
listener.on_selection_modified(view)
assert ">a.py<" in title() and '<span class="del">-1</span> <span class="ins">+1</span>' in title()
# not while a render is pending: the rows describe the old text
st.token += 1
put_caret(view, heading_row(st, "b.py"))
listener.on_selection_modified(view)
assert st.change.path == "a.py"
def test_all_files_step_change_from_a_heading_lands_on_its_first_hunk():