summaryrefslogtreecommitdiffhomepage
path: root/internal/tui/components/dialog
diff options
context:
space:
mode:
authorKujtim Hoxha <[email protected]>2025-04-18 20:17:38 +0200
committerKujtim Hoxha <[email protected]>2025-04-21 13:42:27 +0200
commit333ea6ec4b2abfc2c1a9c3f6b0918ca5d296347f (patch)
treee0d456417368e8716c81ee43b82be3d6ed39c59e /internal/tui/components/dialog
parent05d0e86f10369fd0e51a924ac88029fb92591499 (diff)
downloadopencode-333ea6ec4b2abfc2c1a9c3f6b0918ca5d296347f.tar.gz
opencode-333ea6ec4b2abfc2c1a9c3f6b0918ca5d296347f.zip
implement patch, update ui, improve rendering
Diffstat (limited to 'internal/tui/components/dialog')
-rw-r--r--internal/tui/components/dialog/permission.go16
-rw-r--r--internal/tui/components/dialog/session.go224
2 files changed, 233 insertions, 7 deletions
diff --git a/internal/tui/components/dialog/permission.go b/internal/tui/components/dialog/permission.go
index 295884432..f83472e68 100644
--- a/internal/tui/components/dialog/permission.go
+++ b/internal/tui/components/dialog/permission.go
@@ -36,7 +36,7 @@ type PermissionResponseMsg struct {
type PermissionDialogCmp interface {
tea.Model
layout.Bindings
- SetPermissions(permission permission.PermissionRequest)
+ SetPermissions(permission permission.PermissionRequest) tea.Cmd
}
type permissionsMapping struct {
@@ -98,7 +98,8 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
p.windowSize = msg
- p.SetSize()
+ cmd := p.SetSize()
+ cmds = append(cmds, cmd)
p.markdownCache = make(map[string]string)
p.diffCache = make(map[string]string)
case tea.KeyMsg:
@@ -267,7 +268,7 @@ func (p *permissionDialogCmp) renderEditContent() string {
}
func (p *permissionDialogCmp) renderPatchContent() string {
- if pr, ok := p.permission.Params.(tools.PatchPermissionsParams); ok {
+ if pr, ok := p.permission.Params.(tools.EditPermissionsParams); ok {
diff := p.GetOrSetDiff(p.permission.ID, func() (string, error) {
return diff.FormatDiff(pr.Diff, diff.WithTotalWidth(p.contentViewPort.Width))
})
@@ -401,9 +402,9 @@ func (p *permissionDialogCmp) BindingKeys() []key.Binding {
return layout.KeyMapToSlice(helpKeys)
}
-func (p *permissionDialogCmp) SetSize() {
+func (p *permissionDialogCmp) SetSize() tea.Cmd {
if p.permission.ID == "" {
- return
+ return nil
}
switch p.permission.ToolName {
case tools.BashToolName:
@@ -422,11 +423,12 @@ func (p *permissionDialogCmp) SetSize() {
p.width = int(float64(p.windowSize.Width) * 0.7)
p.height = int(float64(p.windowSize.Height) * 0.5)
}
+ return nil
}
-func (p *permissionDialogCmp) SetPermissions(permission permission.PermissionRequest) {
+func (p *permissionDialogCmp) SetPermissions(permission permission.PermissionRequest) tea.Cmd {
p.permission = permission
- p.SetSize()
+ return p.SetSize()
}
// Helper to get or set cached diff content
diff --git a/internal/tui/components/dialog/session.go b/internal/tui/components/dialog/session.go
new file mode 100644
index 000000000..d8c859c49
--- /dev/null
+++ b/internal/tui/components/dialog/session.go
@@ -0,0 +1,224 @@
+package dialog
+
+import (
+ "github.com/charmbracelet/bubbles/key"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/kujtimiihoxha/opencode/internal/session"
+ "github.com/kujtimiihoxha/opencode/internal/tui/layout"
+ "github.com/kujtimiihoxha/opencode/internal/tui/styles"
+ "github.com/kujtimiihoxha/opencode/internal/tui/util"
+)
+
+// SessionSelectedMsg is sent when a session is selected
+type SessionSelectedMsg struct {
+ Session session.Session
+}
+
+// CloseSessionDialogMsg is sent when the session dialog is closed
+type CloseSessionDialogMsg struct{}
+
+// SessionDialog interface for the session switching dialog
+type SessionDialog interface {
+ tea.Model
+ layout.Bindings
+ SetSessions(sessions []session.Session)
+ SetSelectedSession(sessionID string)
+}
+
+type sessionDialogCmp struct {
+ sessions []session.Session
+ selectedIdx int
+ width int
+ height int
+ selectedSessionID string
+}
+
+type sessionKeyMap struct {
+ Up key.Binding
+ Down key.Binding
+ Enter key.Binding
+ Escape key.Binding
+ J key.Binding
+ K key.Binding
+}
+
+var sessionKeys = sessionKeyMap{
+ Up: key.NewBinding(
+ key.WithKeys("up"),
+ key.WithHelp("↑", "previous session"),
+ ),
+ Down: key.NewBinding(
+ key.WithKeys("down"),
+ key.WithHelp("↓", "next session"),
+ ),
+ Enter: key.NewBinding(
+ key.WithKeys("enter"),
+ key.WithHelp("enter", "select session"),
+ ),
+ Escape: key.NewBinding(
+ key.WithKeys("esc"),
+ key.WithHelp("esc", "close"),
+ ),
+ J: key.NewBinding(
+ key.WithKeys("j"),
+ key.WithHelp("j", "next session"),
+ ),
+ K: key.NewBinding(
+ key.WithKeys("k"),
+ key.WithHelp("k", "previous session"),
+ ),
+}
+
+func (s *sessionDialogCmp) Init() tea.Cmd {
+ return nil
+}
+
+func (s *sessionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ switch {
+ case key.Matches(msg, sessionKeys.Up) || key.Matches(msg, sessionKeys.K):
+ if s.selectedIdx > 0 {
+ s.selectedIdx--
+ }
+ return s, nil
+ case key.Matches(msg, sessionKeys.Down) || key.Matches(msg, sessionKeys.J):
+ if s.selectedIdx < len(s.sessions)-1 {
+ s.selectedIdx++
+ }
+ return s, nil
+ case key.Matches(msg, sessionKeys.Enter):
+ if len(s.sessions) > 0 {
+ return s, util.CmdHandler(SessionSelectedMsg{
+ Session: s.sessions[s.selectedIdx],
+ })
+ }
+ case key.Matches(msg, sessionKeys.Escape):
+ return s, util.CmdHandler(CloseSessionDialogMsg{})
+ }
+ case tea.WindowSizeMsg:
+ s.width = msg.Width
+ s.height = msg.Height
+ }
+ return s, nil
+}
+
+func (s *sessionDialogCmp) View() string {
+ if len(s.sessions) == 0 {
+ return styles.BaseStyle.Padding(1, 2).
+ Border(lipgloss.RoundedBorder()).
+ BorderBackground(styles.Background).
+ BorderForeground(styles.ForgroundDim).
+ Width(40).
+ Render("No sessions available")
+ }
+
+ // Calculate max width needed for session titles
+ maxWidth := 40 // Minimum width
+ for _, sess := range s.sessions {
+ if len(sess.Title) > maxWidth-4 { // Account for padding
+ maxWidth = len(sess.Title) + 4
+ }
+ }
+
+ // Limit height to avoid taking up too much screen space
+ maxVisibleSessions := min(10, len(s.sessions))
+
+ // Build the session list
+ sessionItems := make([]string, 0, maxVisibleSessions)
+ startIdx := 0
+
+ // If we have more sessions than can be displayed, adjust the start index
+ if len(s.sessions) > maxVisibleSessions {
+ // Center the selected item when possible
+ halfVisible := maxVisibleSessions / 2
+ if s.selectedIdx >= halfVisible && s.selectedIdx < len(s.sessions)-halfVisible {
+ startIdx = s.selectedIdx - halfVisible
+ } else if s.selectedIdx >= len(s.sessions)-halfVisible {
+ startIdx = len(s.sessions) - maxVisibleSessions
+ }
+ }
+
+ endIdx := min(startIdx+maxVisibleSessions, len(s.sessions))
+
+ for i := startIdx; i < endIdx; i++ {
+ sess := s.sessions[i]
+ itemStyle := styles.BaseStyle.Width(maxWidth)
+
+ if i == s.selectedIdx {
+ itemStyle = itemStyle.
+ Background(styles.PrimaryColor).
+ Foreground(styles.Background).
+ Bold(true)
+ }
+
+ sessionItems = append(sessionItems, itemStyle.Padding(0, 1).Render(sess.Title))
+ }
+
+ title := styles.BaseStyle.
+ Foreground(styles.PrimaryColor).
+ Bold(true).
+ Padding(0, 1).
+ Render("Switch Session")
+
+ content := lipgloss.JoinVertical(
+ lipgloss.Left,
+ title,
+ styles.BaseStyle.Render(""),
+ lipgloss.JoinVertical(lipgloss.Left, sessionItems...),
+ styles.BaseStyle.Render(""),
+ styles.BaseStyle.Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"),
+ )
+
+ return styles.BaseStyle.Padding(1, 2).
+ Border(lipgloss.RoundedBorder()).
+ BorderBackground(styles.Background).
+ BorderForeground(styles.ForgroundDim).
+ Width(lipgloss.Width(content) + 4).
+ Render(content)
+}
+
+func (s *sessionDialogCmp) BindingKeys() []key.Binding {
+ return layout.KeyMapToSlice(sessionKeys)
+}
+
+func (s *sessionDialogCmp) SetSessions(sessions []session.Session) {
+ s.sessions = sessions
+
+ // If we have a selected session ID, find its index
+ if s.selectedSessionID != "" {
+ for i, sess := range sessions {
+ if sess.ID == s.selectedSessionID {
+ s.selectedIdx = i
+ return
+ }
+ }
+ }
+
+ // Default to first session if selected not found
+ s.selectedIdx = 0
+}
+
+func (s *sessionDialogCmp) SetSelectedSession(sessionID string) {
+ s.selectedSessionID = sessionID
+
+ // Update the selected index if sessions are already loaded
+ if len(s.sessions) > 0 {
+ for i, sess := range s.sessions {
+ if sess.ID == sessionID {
+ s.selectedIdx = i
+ return
+ }
+ }
+ }
+}
+
+// NewSessionDialogCmp creates a new session switching dialog
+func NewSessionDialogCmp() SessionDialog {
+ return &sessionDialogCmp{
+ sessions: []session.Session{},
+ selectedIdx: 0,
+ selectedSessionID: "",
+ }
+} \ No newline at end of file