diff --git a/internal/tui/components/dialogs/permissions/keys.go b/internal/tui/components/dialogs/permissions/keys.go index d626eecf9a819cfb209823f922f96dfb58ea3ca4..c77810f15be294e5a71f911d61da93b324bd7f17 100644 --- a/internal/tui/components/dialogs/permissions/keys.go +++ b/internal/tui/components/dialogs/permissions/keys.go @@ -11,7 +11,12 @@ type KeyMap struct { Select, Allow, AllowSession, - Deny key.Binding + Deny, + ToggleDiffMode, + ScrollDown, + ScrollUp key.Binding + ScrollLeft, + ScrollRight key.Binding } func DefaultKeyMap() KeyMap { @@ -41,9 +46,29 @@ func DefaultKeyMap() KeyMap { key.WithHelp("d", "deny"), ), Select: key.NewBinding( - key.WithKeys("enter", "tab", "ctrl+y"), + key.WithKeys("enter", "ctrl+y"), key.WithHelp("enter", "confirm"), ), + ToggleDiffMode: key.NewBinding( + key.WithKeys("t"), + key.WithHelp("t", "toggle diff mode"), + ), + ScrollDown: key.NewBinding( + key.WithKeys("shift+down", "J"), + key.WithHelp("shift+↓", "scroll down"), + ), + ScrollUp: key.NewBinding( + key.WithKeys("shift+up", "K"), + key.WithHelp("shift+↑", "scroll up"), + ), + ScrollLeft: key.NewBinding( + key.WithKeys("shift+left", "H"), + key.WithHelp("shift+←", "scroll left"), + ), + ScrollRight: key.NewBinding( + key.WithKeys("shift+right", "L"), + key.WithHelp("shift+→", "scroll right"), + ), } } @@ -57,6 +82,11 @@ func (k KeyMap) KeyBindings() []key.Binding { k.Allow, k.AllowSession, k.Deny, + k.ToggleDiffMode, + k.ScrollDown, + k.ScrollUp, + k.ScrollLeft, + k.ScrollRight, } } @@ -74,9 +104,14 @@ func (k KeyMap) FullHelp() [][]key.Binding { // ShortHelp implements help.KeyMap. func (k KeyMap) ShortHelp() []key.Binding { return []key.Binding{ - k.Allow, - k.AllowSession, - k.Deny, - k.Select, + k.ToggleDiffMode, + key.NewBinding( + key.WithKeys("shift+left", "shift+down", "shift+up", "shift+right"), + key.WithHelp("shift+←↓↑→", "scroll"), + ), + key.NewBinding( + key.WithKeys("shift+h", "shift+j", "shift+k", "shift+l"), + key.WithHelp("shift+hjkl", "scroll"), + ), } } diff --git a/internal/tui/components/dialogs/permissions/permissions.go b/internal/tui/components/dialogs/permissions/permissions.go index 4d0563b244af45d8640a741bb79baa9007a0ff3c..1fc6398ce24205537806d0aebd4cb82abcb0b122 100644 --- a/internal/tui/components/dialogs/permissions/permissions.go +++ b/internal/tui/components/dialogs/permissions/permissions.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/charmbracelet/bubbles/v2/help" "github.com/charmbracelet/bubbles/v2/key" "github.com/charmbracelet/bubbles/v2/viewport" tea "github.com/charmbracelet/bubbletea/v2" @@ -50,6 +51,11 @@ type permissionDialogCmp struct { contentViewPort viewport.Model selectedOption int // 0: Allow, 1: Allow for session, 2: Deny + // Diff view state + diffSplitMode bool // true for split, false for unified + diffXOffset int // horizontal scroll offset + diffYOffset int // vertical scroll offset + keyMap KeyMap } @@ -101,6 +107,21 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { util.CmdHandler(dialogs.CloseDialogMsg{}), util.CmdHandler(PermissionResponseMsg{Action: PermissionDeny, Permission: p.permission}), ) + case key.Matches(msg, p.keyMap.ToggleDiffMode): + p.diffSplitMode = !p.diffSplitMode + return p, nil + case key.Matches(msg, p.keyMap.ScrollDown): + p.diffYOffset += 1 + return p, nil + case key.Matches(msg, p.keyMap.ScrollUp): + p.diffYOffset = max(0, p.diffYOffset-1) + return p, nil + case key.Matches(msg, p.keyMap.ScrollLeft): + p.diffXOffset = max(0, p.diffXOffset-5) + return p, nil + case key.Matches(msg, p.keyMap.ScrollRight): + p.diffXOffset += 5 + return p, nil default: // Pass other keys to viewport viewPort, cmd := p.contentViewPort.Update(msg) @@ -269,8 +290,15 @@ func (p *permissionDialogCmp) renderEditContent() string { formatter := core.DiffFormatter(). Before(fsext.PrettyPath(pr.FilePath), pr.OldContent). After(fsext.PrettyPath(pr.FilePath), pr.NewContent). + Height(p.contentViewPort.Height()). Width(p.contentViewPort.Width()). - Split() + XOffset(p.diffXOffset). + YOffset(p.diffYOffset) + if p.diffSplitMode { + formatter = formatter.Split() + } else { + formatter = formatter.Unified() + } diff := formatter.String() contentHeight := min(p.height-9, lipgloss.Height(diff)) @@ -367,11 +395,13 @@ func (p *permissionDialogCmp) render() string { // Render content based on tool type var contentFinal string + var contentHelp string switch p.permission.ToolName { case tools.BashToolName: contentFinal = p.renderBashContent() case tools.EditToolName: contentFinal = p.renderEditContent() + contentHelp = help.New().View(p.keyMap) case tools.WriteToolName: contentFinal = p.renderWriteContent() case tools.FetchToolName: @@ -381,8 +411,7 @@ func (p *permissionDialogCmp) render() string { } // Calculate content height dynamically based on window size - content := lipgloss.JoinVertical( - lipgloss.Top, + strs := []string{ title, "", headerContent, @@ -390,7 +419,11 @@ func (p *permissionDialogCmp) render() string { "", buttons, "", - ) + } + if contentHelp != "" { + strs = append(strs, "", contentHelp) + } + content := lipgloss.JoinVertical(lipgloss.Top, strs...) return baseStyle. Padding(0, 1).