Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions internal/keymap/keymap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@ func TestCRUDBindingsExistInAllSchemes(t *testing.T) {
{"F2", "item_edit", "gitinfo"},
{"o", "item_open", "gitinfo"},
{"y", "item_copy", "gitinfo"},
{"n", "item_create", "github"},
{"x", "item_delete", "github"},
{"e", "item_edit", "github"},
{"F2", "item_edit", "github"},
{"o", "item_open", "github"},
{"y", "item_copy", "github"},
{"n", "item_create", "branches"},
{"x", "item_delete", "branches"},
{"e", "item_edit", "branches"},
Expand Down Expand Up @@ -513,6 +519,11 @@ func TestCRUDContextOverridesVimSearchNext(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, "item_create", action)

// In github context, "n" should be item_create.
action, ok = km.Dispatch("n", "github")
assert.True(t, ok)
assert.Equal(t, "item_create", action)

// In branches context, "n" should be item_create.
action, ok = km.Dispatch("n", "branches")
assert.True(t, ok)
Expand All @@ -534,11 +545,36 @@ func TestCRUDOpenKeyAvailableEverywhere(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, "item_open", action)

// "o" should resolve to item_open in github context.
action, ok = km.Dispatch("o", "github")
assert.True(t, ok)
assert.Equal(t, "item_open", action)

// "o" should not be bound in panels without CRUD context.
_, ok = km.Dispatch("o", "preview")
assert.False(t, ok)
}

func TestGitHubPanelDeleteDispatch(t *testing.T) {
for _, scheme := range []string{"default", "vim", "classic"} {
t.Run(scheme, func(t *testing.T) {
km, err := NewKeymap(scheme)
require.NoError(t, err)
km.SetMode(ModePanel)

// "x" in github context should dispatch to item_delete.
action, ok := km.Dispatch("x", "github")
assert.True(t, ok, "scheme %q: 'x' should be bound in github context", scheme)
assert.Equal(t, "item_delete", action, "scheme %q: 'x' in github should be item_delete", scheme)

// "x" in gitinfo context should also dispatch to item_delete.
action, ok = km.Dispatch("x", "gitinfo")
assert.True(t, ok, "scheme %q: 'x' should be bound in gitinfo context", scheme)
assert.Equal(t, "item_delete", action)
})
}
}

// ---------------------------------------------------------------------------
// helpers
// ---------------------------------------------------------------------------
Expand Down
43 changes: 43 additions & 0 deletions internal/keymap/schemes/classic.toml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,49 @@ mode = "panel"
context = "gitinfo"
description = "Copy identifier to clipboard"

# CRUD shortcuts — github panel
[[bindings]]
key = "n"
action = "item_create"
mode = "panel"
context = "github"
description = "Create new item"

[[bindings]]
key = "x"
action = "item_delete"
mode = "panel"
context = "github"
description = "Delete selected item"

[[bindings]]
key = "e"
action = "item_edit"
mode = "panel"
context = "github"
description = "Edit/rename selected item"

[[bindings]]
key = "F2"
action = "item_edit"
mode = "panel"
context = "github"
description = "Edit/rename selected item"

[[bindings]]
key = "o"
action = "item_open"
mode = "panel"
context = "github"
description = "Open in browser"

[[bindings]]
key = "y"
action = "item_copy"
mode = "panel"
context = "github"
description = "Copy identifier to clipboard"

# CRUD shortcuts — branches panel
[[bindings]]
key = "n"
Expand Down
28 changes: 28 additions & 0 deletions internal/keymap/schemes/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,34 @@ context = "gitinfo"
description = "Copy identifier to clipboard"

# CRUD shortcuts — github panel
[[bindings]]
key = "n"
action = "item_create"
mode = "panel"
context = "github"
description = "Create new item"

[[bindings]]
key = "x"
action = "item_delete"
mode = "panel"
context = "github"
description = "Delete selected item"

[[bindings]]
key = "e"
action = "item_edit"
mode = "panel"
context = "github"
description = "Edit/rename selected item"

[[bindings]]
key = "F2"
action = "item_edit"
mode = "panel"
context = "github"
description = "Edit/rename selected item"

[[bindings]]
key = "o"
action = "item_open"
Expand Down
43 changes: 43 additions & 0 deletions internal/keymap/schemes/vim.toml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,49 @@ mode = "panel"
context = "gitinfo"
description = "Copy identifier to clipboard"

# CRUD shortcuts — github panel
[[bindings]]
key = "n"
action = "item_create"
mode = "panel"
context = "github"
description = "Create new item"

[[bindings]]
key = "x"
action = "item_delete"
mode = "panel"
context = "github"
description = "Delete selected item"

[[bindings]]
key = "e"
action = "item_edit"
mode = "panel"
context = "github"
description = "Edit/rename selected item"

[[bindings]]
key = "F2"
action = "item_edit"
mode = "panel"
context = "github"
description = "Edit/rename selected item"

[[bindings]]
key = "o"
action = "item_open"
mode = "panel"
context = "github"
description = "Open in browser"

[[bindings]]
key = "y"
action = "item_copy"
mode = "panel"
context = "github"
description = "Copy identifier to clipboard"

# CRUD shortcuts — branches panel
[[bindings]]
key = "n"
Expand Down
4 changes: 2 additions & 2 deletions internal/panels/branches/branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ func (p *Panel) KeyBindings() []panels.KeyBinding {
{Key: "k/↑", Description: "Move cursor up", Action: "cursor_up"},
{Key: "enter", Description: "Checkout branch", Action: "checkout"},
{Key: "n", Description: "Create new branch", Action: "item_create"},
{Key: "d", Description: "Delete branch", Action: "item_delete"},
{Key: "x", Description: "Delete branch", Action: "item_delete"},
{Key: "e/F2", Description: "Rename branch", Action: "item_edit"},
{Key: "o", Description: "Open in browser", Action: "item_open"},
{Key: "y", Description: "Copy branch name", Action: "item_copy"},
Expand Down Expand Up @@ -333,7 +333,7 @@ func (p *Panel) handleKey(msg tea.KeyPressMsg) (panels.Panel, tea.Cmd) {
return p.requestCheckout()
case "n":
return p.requestCreate()
case "d":
case "d", "x":
return p.requestDelete()
case "e", "F2":
return p.requestRename()
Expand Down
38 changes: 38 additions & 0 deletions internal/panels/branches/branches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,44 @@ func TestDeleteBranch(t *testing.T) {
assert.False(t, mock.lastDeleteForce)
}

func TestDeleteBranch_XKeyTriggersDelete(t *testing.T) {
mock := &mockGitOps{branches: sampleBranches()}
p := newTestPanel(t, mock, defaultCfg())
p.Focus()

// Move to "feature/auth" (non-current local branch).
p.Update(keyMsg('j'))

// Press "x" directly — should trigger delete confirmation.
_, cmd := p.Update(keyMsg('x'))
require.NotNil(t, cmd)

msg := cmd()
modal, ok := msg.(notify.ShowModalMsg)
require.True(t, ok)
assert.Equal(t, "Delete Branch", modal.Title)
assert.Contains(t, modal.Message, "feature/auth")
}

func TestDeleteBranch_DKeyTriggersDelete(t *testing.T) {
mock := &mockGitOps{branches: sampleBranches()}
p := newTestPanel(t, mock, defaultCfg())
p.Focus()

// Move to "feature/auth" (non-current local branch).
p.Update(keyMsg('j'))

// Press "d" directly — should also trigger delete confirmation (legacy key).
_, cmd := p.Update(keyMsg('d'))
require.NotNil(t, cmd)

msg := cmd()
modal, ok := msg.(notify.ShowModalMsg)
require.True(t, ok)
assert.Equal(t, "Delete Branch", modal.Title)
assert.Contains(t, modal.Message, "feature/auth")
}

func TestDeleteBranch_CurrentBlocked(t *testing.T) {
mock := &mockGitOps{branches: sampleBranches()}
p := newTestPanel(t, mock, defaultCfg())
Expand Down
Loading