diff options
| author | Kujtim Hoxha <[email protected]> | 2025-04-18 21:24:35 +0200 |
|---|---|---|
| committer | Kujtim Hoxha <[email protected]> | 2025-04-21 13:42:29 +0200 |
| commit | 72afeb9f54cee8e248093a52ac0779441c79aea3 (patch) | |
| tree | 70f5966bcf2cb8a6186780ea5b22a3515bc8ee22 /internal/tui/components | |
| parent | 333ea6ec4b2abfc2c1a9c3f6b0918ca5d296347f (diff) | |
| download | opencode-72afeb9f54cee8e248093a52ac0779441c79aea3.tar.gz opencode-72afeb9f54cee8e248093a52ac0779441c79aea3.zip | |
small fixes
Diffstat (limited to 'internal/tui/components')
| -rw-r--r-- | internal/tui/components/chat/editor.go | 9 | ||||
| -rw-r--r-- | internal/tui/components/chat/list.go | 49 | ||||
| -rw-r--r-- | internal/tui/components/chat/message.go | 1 | ||||
| -rw-r--r-- | internal/tui/components/dialog/permission.go | 22 | ||||
| -rw-r--r-- | internal/tui/components/dialog/session.go | 48 |
5 files changed, 81 insertions, 48 deletions
diff --git a/internal/tui/components/chat/editor.go b/internal/tui/components/chat/editor.go index 537ef392c..963fbbdbf 100644 --- a/internal/tui/components/chat/editor.go +++ b/internal/tui/components/chat/editor.go @@ -21,6 +21,8 @@ type editorCmp struct { textarea textarea.Model } +type FocusEditorMsg bool + type focusedEditorKeyMaps struct { Send key.Binding OpenEditor key.Binding @@ -112,7 +114,6 @@ func (m *editorCmp) send() tea.Cmd { util.CmdHandler(SendMsg{ Text: value, }), - util.CmdHandler(EditorFocusMsg(false)), ) } @@ -124,9 +125,13 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.session = msg } return m, nil + case FocusEditorMsg: + if msg { + m.textarea.Focus() + return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true))) + } case tea.KeyMsg: if key.Matches(msg, focusedKeyMaps.OpenEditor) { - m.textarea.Blur() return m, openEditor() } // if the key does not match any binding, return diff --git a/internal/tui/components/chat/list.go b/internal/tui/components/chat/list.go index f95b53731..b7703e2cc 100644 --- a/internal/tui/components/chat/list.go +++ b/internal/tui/components/chat/list.go @@ -22,6 +22,10 @@ import ( "github.com/kujtimiihoxha/opencode/internal/tui/util" ) +type cacheItem struct { + width int + content []uiMessage +} type messagesCmp struct { app *app.App width, height int @@ -32,8 +36,9 @@ type messagesCmp struct { uiMessages []uiMessage currentMsgID string mutex sync.Mutex - cachedContent map[string][]uiMessage + cachedContent map[string]cacheItem spinner spinner.Model + lastUpdate time.Time rendering bool } type renderFinishedMsg struct{} @@ -44,6 +49,8 @@ func (m *messagesCmp) Init() tea.Cmd { func (m *messagesCmp) preloadSessions() tea.Cmd { return func() tea.Msg { + m.mutex.Lock() + defer m.mutex.Unlock() sessions, err := m.app.Sessions.List(context.Background()) if err != nil { return util.ReportError(err)() @@ -67,13 +74,13 @@ func (m *messagesCmp) preloadSessions() tea.Cmd { } logging.Debug("preloaded sessions") - return nil + return func() tea.Msg { + return renderFinishedMsg{} + } } } func (m *messagesCmp) cacheSessionMessages(messages []message.Message, width int) { - m.mutex.Lock() - defer m.mutex.Unlock() pos := 0 if m.width == 0 { return @@ -87,7 +94,10 @@ func (m *messagesCmp) cacheSessionMessages(messages []message.Message, width int width, pos, ) - m.cachedContent[msg.ID] = []uiMessage{userMsg} + m.cachedContent[msg.ID] = cacheItem{ + width: width, + content: []uiMessage{userMsg}, + } pos += userMsg.height + 1 // + 1 for spacing case message.Assistant: assistantMessages := renderAssistantMessage( @@ -102,7 +112,10 @@ func (m *messagesCmp) cacheSessionMessages(messages []message.Message, width int for _, msg := range assistantMessages { pos += msg.height + 1 // + 1 for spacing } - m.cachedContent[msg.ID] = assistantMessages + m.cachedContent[msg.ID] = cacheItem{ + width: width, + content: assistantMessages, + } } } } @@ -223,8 +236,8 @@ func (m *messagesCmp) renderView() { for inx, msg := range m.messages { switch msg.Role { case message.User: - if messages, ok := m.cachedContent[msg.ID]; ok { - m.uiMessages = append(m.uiMessages, messages...) + if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width { + m.uiMessages = append(m.uiMessages, cache.content...) continue } userMsg := renderUserMessage( @@ -234,11 +247,14 @@ func (m *messagesCmp) renderView() { pos, ) m.uiMessages = append(m.uiMessages, userMsg) - m.cachedContent[msg.ID] = []uiMessage{userMsg} + m.cachedContent[msg.ID] = cacheItem{ + width: m.width, + content: []uiMessage{userMsg}, + } pos += userMsg.height + 1 // + 1 for spacing case message.Assistant: - if messages, ok := m.cachedContent[msg.ID]; ok { - m.uiMessages = append(m.uiMessages, messages...) + if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width { + m.uiMessages = append(m.uiMessages, cache.content...) continue } assistantMessages := renderAssistantMessage( @@ -254,7 +270,10 @@ func (m *messagesCmp) renderView() { m.uiMessages = append(m.uiMessages, msg) pos += msg.height + 1 // + 1 for spacing } - m.cachedContent[msg.ID] = assistantMessages + m.cachedContent[msg.ID] = cacheItem{ + width: m.width, + content: assistantMessages, + } } } @@ -418,6 +437,10 @@ func (m *messagesCmp) SetSize(width, height int) tea.Cmd { m.height = height m.viewport.Width = width m.viewport.Height = height - 2 + for _, msg := range m.messages { + delete(m.cachedContent, msg.ID) + } + m.uiMessages = make([]uiMessage, 0) m.renderView() return m.preloadSessions() } @@ -456,7 +479,7 @@ func NewMessagesCmp(app *app.App) tea.Model { return &messagesCmp{ app: app, writingMode: true, - cachedContent: make(map[string][]uiMessage), + cachedContent: make(map[string]cacheItem), viewport: viewport.New(0, 0), spinner: s, } diff --git a/internal/tui/components/chat/message.go b/internal/tui/components/chat/message.go index be6c7ce50..7a840b4ec 100644 --- a/internal/tui/components/chat/message.go +++ b/internal/tui/components/chat/message.go @@ -389,6 +389,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult, errContent := fmt.Sprintf("Error: %s", strings.ReplaceAll(response.Content, "\n", " ")) errContent = ansi.Truncate(errContent, width-1, "...") return styles.BaseStyle. + Width(width). Foreground(styles.Error). Render(errContent) } diff --git a/internal/tui/components/dialog/permission.go b/internal/tui/components/dialog/permission.go index f83472e68..1f8df21a0 100644 --- a/internal/tui/components/dialog/permission.go +++ b/internal/tui/components/dialog/permission.go @@ -40,7 +40,8 @@ type PermissionDialogCmp interface { } type permissionsMapping struct { - LeftRight key.Binding + Left key.Binding + Right key.Binding EnterSpace key.Binding Allow key.Binding AllowSession key.Binding @@ -49,9 +50,13 @@ type permissionsMapping struct { } var permissionsKeys = permissionsMapping{ - LeftRight: key.NewBinding( - key.WithKeys("left", "right"), - key.WithHelp("←/→", "switch options"), + Left: key.NewBinding( + key.WithKeys("left"), + key.WithHelp("←", "switch options"), + ), + Right: key.NewBinding( + key.WithKeys("right"), + key.WithHelp("→", "switch options"), ), EnterSpace: key.NewBinding( key.WithKeys("enter", " "), @@ -104,21 +109,18 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { p.diffCache = make(map[string]string) case tea.KeyMsg: switch { - case key.Matches(msg, permissionsKeys.LeftRight) || key.Matches(msg, permissionsKeys.Tab): - // Change selected option + case key.Matches(msg, permissionsKeys.Right) || key.Matches(msg, permissionsKeys.Tab): p.selectedOption = (p.selectedOption + 1) % 3 return p, nil + case key.Matches(msg, permissionsKeys.Left): + p.selectedOption = (p.selectedOption + 2) % 3 case key.Matches(msg, permissionsKeys.EnterSpace): - // Select current option return p, p.selectCurrentOption() case key.Matches(msg, permissionsKeys.Allow): - // Select Allow return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionAllow, Permission: p.permission}) case key.Matches(msg, permissionsKeys.AllowSession): - // Select Allow for session return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionAllowForSession, Permission: p.permission}) case key.Matches(msg, permissionsKeys.Deny): - // Select Deny return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionDeny, Permission: p.permission}) default: // Pass other keys to viewport diff --git a/internal/tui/components/dialog/session.go b/internal/tui/components/dialog/session.go index d8c859c49..060875f91 100644 --- a/internal/tui/components/dialog/session.go +++ b/internal/tui/components/dialog/session.go @@ -27,20 +27,20 @@ type SessionDialog interface { } type sessionDialogCmp struct { - sessions []session.Session - selectedIdx int - width int - height int + 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 + Up key.Binding + Down key.Binding + Enter key.Binding + Escape key.Binding + J key.Binding + K key.Binding } var sessionKeys = sessionKeyMap{ @@ -128,7 +128,7 @@ func (s *sessionDialogCmp) View() string { // 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 @@ -145,30 +145,31 @@ func (s *sessionDialogCmp) View() string { 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). + Width(maxWidth). 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"), + styles.BaseStyle.Width(maxWidth).Render(""), + styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)), + styles.BaseStyle.Width(maxWidth).Render(""), + styles.BaseStyle.Width(maxWidth).Padding(0, 1).Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"), ) return styles.BaseStyle.Padding(1, 2). @@ -185,7 +186,7 @@ func (s *sessionDialogCmp) BindingKeys() []key.Binding { 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 { @@ -195,14 +196,14 @@ func (s *sessionDialogCmp) SetSessions(sessions []session.Session) { } } } - + // 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 { @@ -217,8 +218,9 @@ func (s *sessionDialogCmp) SetSelectedSession(sessionID string) { // NewSessionDialogCmp creates a new session switching dialog func NewSessionDialogCmp() SessionDialog { return &sessionDialogCmp{ - sessions: []session.Session{}, - selectedIdx: 0, + sessions: []session.Session{}, + selectedIdx: 0, selectedSessionID: "", } -}
\ No newline at end of file +} + |
