on main help sign in
feat: add keybinding to permissions dialog to navigate the diffview
commit7677483e71ca4f6d0026f4f0dba38fdf4254ae07 authorAndrey Nering <andreynering@users.noreply.github.com> dateTue 17 Jun 2025, 15:51 (1 year ago) parenta4a9297
2 files changed+78−10
internal/tui/components/dialogs/permissions/keys.go+41−6 internal/tui/components/dialogs/permissions/permissions.go+37−4
internal/tui/components/dialogs/permissions/keys.go+41−6
@@ -11,7 +11,12 @@ type KeyMap struct {
1111 Select,
1212 Allow,
1313 AllowSession,
14− Deny key.Binding
14+ Deny,
15+ ToggleDiffMode,
16+ ScrollDown,
17+ ScrollUp key.Binding
18+ ScrollLeft,
19+ ScrollRight key.Binding
1520}
1621
1722func DefaultKeyMap() KeyMap {
@@ -41,9 +46,29 @@ func DefaultKeyMap() KeyMap {
4146 key.WithHelp("d", "deny"),
4247 ),
4348 Select: key.NewBinding(
44− key.WithKeys("enter", "tab", "ctrl+y"),
49+ key.WithKeys("enter", "ctrl+y"),
4550 key.WithHelp("enter", "confirm"),
4651 ),
52+ ToggleDiffMode: key.NewBinding(
53+ key.WithKeys("t"),
54+ key.WithHelp("t", "toggle diff mode"),
55+ ),
56+ ScrollDown: key.NewBinding(
57+ key.WithKeys("shift+down", "J"),
58+ key.WithHelp("shift+↓", "scroll down"),
59+ ),
60+ ScrollUp: key.NewBinding(
61+ key.WithKeys("shift+up", "K"),
62+ key.WithHelp("shift+↑", "scroll up"),
63+ ),
64+ ScrollLeft: key.NewBinding(
65+ key.WithKeys("shift+left", "H"),
66+ key.WithHelp("shift+←", "scroll left"),
67+ ),
68+ ScrollRight: key.NewBinding(
69+ key.WithKeys("shift+right", "L"),
70+ key.WithHelp("shift+→", "scroll right"),
71+ ),
4772 }
4873}
4974
@@ -57,6 +82,11 @@ func (k KeyMap) KeyBindings() []key.Binding {
5782 k.Allow,
5883 k.AllowSession,
5984 k.Deny,
85+ k.ToggleDiffMode,
86+ k.ScrollDown,
87+ k.ScrollUp,
88+ k.ScrollLeft,
89+ k.ScrollRight,
6090 }
6191}
6292
@@ -74,9 +104,14 @@ func (k KeyMap) FullHelp() [][]key.Binding {
74104// ShortHelp implements help.KeyMap.
75105func (k KeyMap) ShortHelp() []key.Binding {
76106 return []key.Binding{
77− k.Allow,
78− k.AllowSession,
79− k.Deny,
80− k.Select,
107+ k.ToggleDiffMode,
108+ key.NewBinding(
109+ key.WithKeys("shift+left", "shift+down", "shift+up", "shift+right"),
110+ key.WithHelp("shift+←↓↑→", "scroll"),
111+ ),
112+ key.NewBinding(
113+ key.WithKeys("shift+h", "shift+j", "shift+k", "shift+l"),
114+ key.WithHelp("shift+hjkl", "scroll"),
115+ ),
81116 }
82117}
internal/tui/components/dialogs/permissions/permissions.go+37−4
@@ -4,6 +4,7 @@ import (
44 "fmt"
55 "strings"
66
7+ "github.com/charmbracelet/bubbles/v2/help"
78 "github.com/charmbracelet/bubbles/v2/key"
89 "github.com/charmbracelet/bubbles/v2/viewport"
910 tea "github.com/charmbracelet/bubbletea/v2"
@@ -50,6 +51,11 @@ type permissionDialogCmp struct {
5051 contentViewPort viewport.Model
5152 selectedOption int // 0: Allow, 1: Allow for session, 2: Deny
5253
54+ // Diff view state
55+ diffSplitMode bool // true for split, false for unified
56+ diffXOffset int // horizontal scroll offset
57+ diffYOffset int // vertical scroll offset
58+
5359 keyMap KeyMap
5460}
5561
@@ -101,6 +107,21 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
101107 util.CmdHandler(dialogs.CloseDialogMsg{}),
102108 util.CmdHandler(PermissionResponseMsg{Action: PermissionDeny, Permission: p.permission}),
103109 )
110+ case key.Matches(msg, p.keyMap.ToggleDiffMode):
111+ p.diffSplitMode = !p.diffSplitMode
112+ return p, nil
113+ case key.Matches(msg, p.keyMap.ScrollDown):
114+ p.diffYOffset += 1
115+ return p, nil
116+ case key.Matches(msg, p.keyMap.ScrollUp):
117+ p.diffYOffset = max(0, p.diffYOffset-1)
118+ return p, nil
119+ case key.Matches(msg, p.keyMap.ScrollLeft):
120+ p.diffXOffset = max(0, p.diffXOffset-5)
121+ return p, nil
122+ case key.Matches(msg, p.keyMap.ScrollRight):
123+ p.diffXOffset += 5
124+ return p, nil
104125 default:
105126 // Pass other keys to viewport
106127 viewPort, cmd := p.contentViewPort.Update(msg)
@@ -269,8 +290,15 @@ func (p *permissionDialogCmp) renderEditContent() string {
269290 formatter := core.DiffFormatter().
270291 Before(fsext.PrettyPath(pr.FilePath), pr.OldContent).
271292 After(fsext.PrettyPath(pr.FilePath), pr.NewContent).
293+ Height(p.contentViewPort.Height()).
272294 Width(p.contentViewPort.Width()).
273− Split()
295+ XOffset(p.diffXOffset).
296+ YOffset(p.diffYOffset)
297+ if p.diffSplitMode {
298+ formatter = formatter.Split()
299+ } else {
300+ formatter = formatter.Unified()
301+ }
274302
275303 diff := formatter.String()
276304 contentHeight := min(p.height-9, lipgloss.Height(diff))
@@ -367,11 +395,13 @@ func (p *permissionDialogCmp) render() string {
367395
368396 // Render content based on tool type
369397 var contentFinal string
398+ var contentHelp string
370399 switch p.permission.ToolName {
371400 case tools.BashToolName:
372401 contentFinal = p.renderBashContent()
373402 case tools.EditToolName:
374403 contentFinal = p.renderEditContent()
404+ contentHelp = help.New().View(p.keyMap)
375405 case tools.WriteToolName:
376406 contentFinal = p.renderWriteContent()
377407 case tools.FetchToolName:
@@ -381,8 +411,7 @@ func (p *permissionDialogCmp) render() string {
381411 }
382412 // Calculate content height dynamically based on window size
383413
384− content := lipgloss.JoinVertical(
385− lipgloss.Top,
414+ strs := []string{
386415 title,
387416 "",
388417 headerContent,
@@ -390,7 +419,11 @@ func (p *permissionDialogCmp) render() string {
390419 "",
391420 buttons,
392421 "",
393− )
422+ }
423+ if contentHelp != "" {
424+ strs = append(strs, "", contentHelp)
425+ }
426+ content := lipgloss.JoinVertical(lipgloss.Top, strs...)
394427
395428 return baseStyle.
396429 Padding(0, 1).