on main help sign in
small fixes
72afeb9Kujtim Hoxha
internal/diff/patch.go+17−16
@@ -91,11 +91,9 @@ func (p *Parser) isDone(prefixes []string) bool {
91 if p.index >= len(p.lines) {
91 if p.index >= len(p.lines) {
92 return true
92 return true
93 }
93 }
94− if prefixes != nil {
95− for _, prefix := range prefixes {
94+ for _, prefix := range prefixes {
96− if strings.HasPrefix(p.lines[p.index], prefix) {
95+ if strings.HasPrefix(p.lines[p.index], prefix) {
97− return true
96+ return true
98− }
99 }
97 }
100 }
98 }
101 return false
99 return false
@@ -219,7 +217,7 @@ func (p *Parser) parseUpdateFile(text string) (PatchAction, error) {
219 sectionStr = p.lines[p.index]
217 sectionStr = p.lines[p.index]
220 p.index++
218 p.index++
221 }
219 }
222− if !(defStr != "" || sectionStr != "" || index == 0) {
220+ if defStr == "" && sectionStr == "" && index != 0 {
223 return action, NewDiffError(fmt.Sprintf("Invalid Line:\n%s", p.lines[p.index]))
221 return action, NewDiffError(fmt.Sprintf("Invalid Line:\n%s", p.lines[p.index]))
224 }
222 }
225 if strings.TrimSpace(defStr) != "" {
223 if strings.TrimSpace(defStr) != "" {
@@ -433,12 +431,13 @@ func peekNextSection(lines []string, initialIndex int) ([]string, []Chunk, int,
433 delLines = make([]string, 0, 8)
431 delLines = make([]string, 0, 8)
434 insLines = make([]string, 0, 8)
432 insLines = make([]string, 0, 8)
435 }
433 }
436− if mode == "delete" {
434+ switch mode {
435+ case "delete":
437 delLines = append(delLines, line)
436 delLines = append(delLines, line)
438 old = append(old, line)
437 old = append(old, line)
439− } else if mode == "add" {
438+ case "add":
440 insLines = append(insLines, line)
439 insLines = append(insLines, line)
441− } else {
440+ default:
442 old = append(old, line)
441 old = append(old, line)
443 }
442 }
444 }
443 }
@@ -513,7 +512,7 @@ func IdentifyFilesAdded(text string) []string {
513
512
514func getUpdatedFile(text string, action PatchAction, path string) (string, error) {
513func getUpdatedFile(text string, action PatchAction, path string) (string, error) {
515 if action.Type != ActionUpdate {
514 if action.Type != ActionUpdate {
516− return "", errors.New("Expected UPDATE action")
515+ return "", errors.New("expected UPDATE action")
517 }
516 }
518 origLines := strings.Split(text, "\n")
517 origLines := strings.Split(text, "\n")
519 destLines := make([]string, 0, len(origLines)) // Preallocate with capacity
518 destLines := make([]string, 0, len(origLines)) // Preallocate with capacity
@@ -543,18 +542,19 @@ func getUpdatedFile(text string, action PatchAction, path string) (string, error
543func PatchToCommit(patch Patch, orig map[string]string) (Commit, error) {
542func PatchToCommit(patch Patch, orig map[string]string) (Commit, error) {
544 commit := Commit{Changes: make(map[string]FileChange, len(patch.Actions))}
543 commit := Commit{Changes: make(map[string]FileChange, len(patch.Actions))}
545 for pathKey, action := range patch.Actions {
544 for pathKey, action := range patch.Actions {
546− if action.Type == ActionDelete {
545+ switch action.Type {
546+ case ActionDelete:
547 oldContent := orig[pathKey]
547 oldContent := orig[pathKey]
548 commit.Changes[pathKey] = FileChange{
548 commit.Changes[pathKey] = FileChange{
549 Type: ActionDelete,
549 Type: ActionDelete,
550 OldContent: &oldContent,
550 OldContent: &oldContent,
551 }
551 }
552− } else if action.Type == ActionAdd {
552+ case ActionAdd:
553 commit.Changes[pathKey] = FileChange{
553 commit.Changes[pathKey] = FileChange{
554 Type: ActionAdd,
554 Type: ActionAdd,
555 NewContent: action.NewFile,
555 NewContent: action.NewFile,
556 }
556 }
557− } else if action.Type == ActionUpdate {
557+ case ActionUpdate:
558 newContent, err := getUpdatedFile(orig[pathKey], action, pathKey)
558 newContent, err := getUpdatedFile(orig[pathKey], action, pathKey)
559 if err != nil {
559 if err != nil {
560 return Commit{}, err
560 return Commit{}, err
@@ -619,18 +619,19 @@ func LoadFiles(paths []string, openFn func(string) (string, error)) (map[string]
619
619
620func ApplyCommit(commit Commit, writeFn func(string, string) error, removeFn func(string) error) error {
620func ApplyCommit(commit Commit, writeFn func(string, string) error, removeFn func(string) error) error {
621 for p, change := range commit.Changes {
621 for p, change := range commit.Changes {
622− if change.Type == ActionDelete {
622+ switch change.Type {
623+ case ActionDelete:
623 if err := removeFn(p); err != nil {
624 if err := removeFn(p); err != nil {
624 return err
625 return err
625 }
626 }
626− } else if change.Type == ActionAdd {
627+ case ActionAdd:
627 if change.NewContent == nil {
628 if change.NewContent == nil {
628 return NewDiffError(fmt.Sprintf("Add action for %s has nil new_content", p))
629 return NewDiffError(fmt.Sprintf("Add action for %s has nil new_content", p))
629 }
630 }
630 if err := writeFn(p, *change.NewContent); err != nil {
631 if err := writeFn(p, *change.NewContent); err != nil {
631 return err
632 return err
632 }
633 }
633− } else if change.Type == ActionUpdate {
634+ case ActionUpdate:
634 if change.NewContent == nil {
635 if change.NewContent == nil {
635 return NewDiffError(fmt.Sprintf("Update action for %s has nil new_content", p))
636 return NewDiffError(fmt.Sprintf("Update action for %s has nil new_content", p))
636 }
637 }
internal/llm/agent/agent.go+2
@@ -221,6 +221,8 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
221 agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, msgHistory)
221 agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, msgHistory)
222 if err != nil {
222 if err != nil {
223 if errors.Is(err, context.Canceled) {
223 if errors.Is(err, context.Canceled) {
224+ agentMessage.AddFinish(message.FinishReasonCanceled)
225+ a.messages.Update(context.Background(), agentMessage)
224 return a.err(ErrRequestCancelled)
226 return a.err(ErrRequestCancelled)
225 }
227 }
226 return a.err(fmt.Errorf("failed to process events: %w", err))
228 return a.err(fmt.Errorf("failed to process events: %w", err))
internal/llm/tools/edit.go+3−3
@@ -141,20 +141,20 @@ func (e *editTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
141 if params.OldString == "" {
141 if params.OldString == "" {
142 response, err = e.createNewFile(ctx, params.FilePath, params.NewString)
142 response, err = e.createNewFile(ctx, params.FilePath, params.NewString)
143 if err != nil {
143 if err != nil {
144− return response, nil
144+ return response, err
145 }
145 }
146 }
146 }
147
147
148 if params.NewString == "" {
148 if params.NewString == "" {
149 response, err = e.deleteContent(ctx, params.FilePath, params.OldString)
149 response, err = e.deleteContent(ctx, params.FilePath, params.OldString)
150 if err != nil {
150 if err != nil {
151− return response, nil
151+ return response, err
152 }
152 }
153 }
153 }
154
154
155 response, err = e.replaceContent(ctx, params.FilePath, params.OldString, params.NewString)
155 response, err = e.replaceContent(ctx, params.FilePath, params.OldString, params.NewString)
156 if err != nil {
156 if err != nil {
157− return response, nil
157+ return response, err
158 }
158 }
159 if response.IsError {
159 if response.IsError {
160 // Return early if there was an error during content replacement
160 // Return early if there was an error during content replacement
internal/tui/components/chat/editor.go+7−2
@@ -21,6 +21,8 @@ type editorCmp struct {
21 textarea textarea.Model
21 textarea textarea.Model
22}
22}
23
23
24+type FocusEditorMsg bool
25+
24type focusedEditorKeyMaps struct {
26type focusedEditorKeyMaps struct {
25 Send key.Binding
27 Send key.Binding
26 OpenEditor key.Binding
28 OpenEditor key.Binding
@@ -112,7 +114,6 @@ func (m *editorCmp) send() tea.Cmd {
112 util.CmdHandler(SendMsg{
114 util.CmdHandler(SendMsg{
113 Text: value,
115 Text: value,
114 }),
116 }),
115− util.CmdHandler(EditorFocusMsg(false)),
116 )
117 )
117}
118}
118
119
@@ -124,9 +125,13 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
124 m.session = msg
125 m.session = msg
125 }
126 }
126 return m, nil
127 return m, nil
128+ case FocusEditorMsg:
129+ if msg {
130+ m.textarea.Focus()
131+ return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
132+ }
127 case tea.KeyMsg:
133 case tea.KeyMsg:
128 if key.Matches(msg, focusedKeyMaps.OpenEditor) {
134 if key.Matches(msg, focusedKeyMaps.OpenEditor) {
129− m.textarea.Blur()
130 return m, openEditor()
135 return m, openEditor()
131 }
136 }
132 // if the key does not match any binding, return
137 // if the key does not match any binding, return
internal/tui/components/chat/list.go+36−13
@@ -22,6 +22,10 @@ import (
22 "github.com/kujtimiihoxha/opencode/internal/tui/util"
22 "github.com/kujtimiihoxha/opencode/internal/tui/util"
23)
23)
24
24
25+type cacheItem struct {
26+ width int
27+ content []uiMessage
28+}
25type messagesCmp struct {
29type messagesCmp struct {
26 app *app.App
30 app *app.App
27 width, height int
31 width, height int
@@ -32,8 +36,9 @@ type messagesCmp struct {
32 uiMessages []uiMessage
36 uiMessages []uiMessage
33 currentMsgID string
37 currentMsgID string
34 mutex sync.Mutex
38 mutex sync.Mutex
35− cachedContent map[string][]uiMessage
39+ cachedContent map[string]cacheItem
36 spinner spinner.Model
40 spinner spinner.Model
41+ lastUpdate time.Time
37 rendering bool
42 rendering bool
38}
43}
39type renderFinishedMsg struct{}
44type renderFinishedMsg struct{}
@@ -44,6 +49,8 @@ func (m *messagesCmp) Init() tea.Cmd {
44
49
45func (m *messagesCmp) preloadSessions() tea.Cmd {
50func (m *messagesCmp) preloadSessions() tea.Cmd {
46 return func() tea.Msg {
51 return func() tea.Msg {
52+ m.mutex.Lock()
53+ defer m.mutex.Unlock()
47 sessions, err := m.app.Sessions.List(context.Background())
54 sessions, err := m.app.Sessions.List(context.Background())
48 if err != nil {
55 if err != nil {
49 return util.ReportError(err)()
56 return util.ReportError(err)()
@@ -67,13 +74,13 @@ func (m *messagesCmp) preloadSessions() tea.Cmd {
67 }
74 }
68 logging.Debug("preloaded sessions")
75 logging.Debug("preloaded sessions")
69
76
70− return nil
77+ return func() tea.Msg {
78+ return renderFinishedMsg{}
79+ }
71 }
80 }
72}
81}
73
82
74func (m *messagesCmp) cacheSessionMessages(messages []message.Message, width int) {
83func (m *messagesCmp) cacheSessionMessages(messages []message.Message, width int) {
75− m.mutex.Lock()
76− defer m.mutex.Unlock()
77 pos := 0
84 pos := 0
78 if m.width == 0 {
85 if m.width == 0 {
79 return
86 return
@@ -87,7 +94,10 @@ func (m *messagesCmp) cacheSessionMessages(messages []message.Message, width int
87 width,
94 width,
88 pos,
95 pos,
89 )
96 )
90− m.cachedContent[msg.ID] = []uiMessage{userMsg}
97+ m.cachedContent[msg.ID] = cacheItem{
98+ width: width,
99+ content: []uiMessage{userMsg},
100+ }
91 pos += userMsg.height + 1 // + 1 for spacing
101 pos += userMsg.height + 1 // + 1 for spacing
92 case message.Assistant:
102 case message.Assistant:
93 assistantMessages := renderAssistantMessage(
103 assistantMessages := renderAssistantMessage(
@@ -102,7 +112,10 @@ func (m *messagesCmp) cacheSessionMessages(messages []message.Message, width int
102 for _, msg := range assistantMessages {
112 for _, msg := range assistantMessages {
103 pos += msg.height + 1 // + 1 for spacing
113 pos += msg.height + 1 // + 1 for spacing
104 }
114 }
105− m.cachedContent[msg.ID] = assistantMessages
115+ m.cachedContent[msg.ID] = cacheItem{
116+ width: width,
117+ content: assistantMessages,
118+ }
106 }
119 }
107 }
120 }
108}
121}
@@ -223,8 +236,8 @@ func (m *messagesCmp) renderView() {
223 for inx, msg := range m.messages {
236 for inx, msg := range m.messages {
224 switch msg.Role {
237 switch msg.Role {
225 case message.User:
238 case message.User:
226− if messages, ok := m.cachedContent[msg.ID]; ok {
239+ if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width {
227− m.uiMessages = append(m.uiMessages, messages...)
240+ m.uiMessages = append(m.uiMessages, cache.content...)
228 continue
241 continue
229 }
242 }
230 userMsg := renderUserMessage(
243 userMsg := renderUserMessage(
@@ -234,11 +247,14 @@ func (m *messagesCmp) renderView() {
234 pos,
247 pos,
235 )
248 )
236 m.uiMessages = append(m.uiMessages, userMsg)
249 m.uiMessages = append(m.uiMessages, userMsg)
237− m.cachedContent[msg.ID] = []uiMessage{userMsg}
250+ m.cachedContent[msg.ID] = cacheItem{
251+ width: m.width,
252+ content: []uiMessage{userMsg},
253+ }
238 pos += userMsg.height + 1 // + 1 for spacing
254 pos += userMsg.height + 1 // + 1 for spacing
239 case message.Assistant:
255 case message.Assistant:
240− if messages, ok := m.cachedContent[msg.ID]; ok {
256+ if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width {
241− m.uiMessages = append(m.uiMessages, messages...)
257+ m.uiMessages = append(m.uiMessages, cache.content...)
242 continue
258 continue
243 }
259 }
244 assistantMessages := renderAssistantMessage(
260 assistantMessages := renderAssistantMessage(
@@ -254,7 +270,10 @@ func (m *messagesCmp) renderView() {
254 m.uiMessages = append(m.uiMessages, msg)
270 m.uiMessages = append(m.uiMessages, msg)
255 pos += msg.height + 1 // + 1 for spacing
271 pos += msg.height + 1 // + 1 for spacing
256 }
272 }
257− m.cachedContent[msg.ID] = assistantMessages
273+ m.cachedContent[msg.ID] = cacheItem{
274+ width: m.width,
275+ content: assistantMessages,
276+ }
258 }
277 }
259 }
278 }
260
279
@@ -418,6 +437,10 @@ func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
418 m.height = height
437 m.height = height
419 m.viewport.Width = width
438 m.viewport.Width = width
420 m.viewport.Height = height - 2
439 m.viewport.Height = height - 2
440+ for _, msg := range m.messages {
441+ delete(m.cachedContent, msg.ID)
442+ }
443+ m.uiMessages = make([]uiMessage, 0)
421 m.renderView()
444 m.renderView()
422 return m.preloadSessions()
445 return m.preloadSessions()
423}
446}
@@ -456,7 +479,7 @@ func NewMessagesCmp(app *app.App) tea.Model {
456 return &messagesCmp{
479 return &messagesCmp{
457 app: app,
480 app: app,
458 writingMode: true,
481 writingMode: true,
459− cachedContent: make(map[string][]uiMessage),
482+ cachedContent: make(map[string]cacheItem),
460 viewport: viewport.New(0, 0),
483 viewport: viewport.New(0, 0),
461 spinner: s,
484 spinner: s,
462 }
485 }
internal/tui/components/chat/message.go+1
@@ -389,6 +389,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
389 errContent := fmt.Sprintf("Error: %s", strings.ReplaceAll(response.Content, "\n", " "))
389 errContent := fmt.Sprintf("Error: %s", strings.ReplaceAll(response.Content, "\n", " "))
390 errContent = ansi.Truncate(errContent, width-1, "...")
390 errContent = ansi.Truncate(errContent, width-1, "...")
391 return styles.BaseStyle.
391 return styles.BaseStyle.
392+ Width(width).
392 Foreground(styles.Error).
393 Foreground(styles.Error).
393 Render(errContent)
394 Render(errContent)
394 }
395 }
internal/tui/components/dialog/permission.go+12−10
@@ -40,7 +40,8 @@ type PermissionDialogCmp interface {
40}
40}
41
41
42type permissionsMapping struct {
42type permissionsMapping struct {
43+ Left key.Binding
43− LeftRight key.Binding
44+ Right key.Binding
44 EnterSpace key.Binding
45 EnterSpace key.Binding
45 Allow key.Binding
46 Allow key.Binding
46 AllowSession key.Binding
47 AllowSession key.Binding
@@ -49,9 +50,13 @@ type permissionsMapping struct {
49}
50}
50
51
51var permissionsKeys = permissionsMapping{
52var permissionsKeys = permissionsMapping{
52− LeftRight: key.NewBinding(
53+ Left: key.NewBinding(
53− key.WithKeys("left", "right"),
54+ key.WithKeys("left"),
55+ key.WithHelp("←", "switch options"),
56+ ),
57+ Right: key.NewBinding(
58+ key.WithKeys("right"),
54− key.WithHelp("←/→", "switch options"),
59+ key.WithHelp("→", "switch options"),
55 ),
60 ),
56 EnterSpace: key.NewBinding(
61 EnterSpace: key.NewBinding(
57 key.WithKeys("enter", " "),
62 key.WithKeys("enter", " "),
@@ -104,21 +109,18 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
104 p.diffCache = make(map[string]string)
109 p.diffCache = make(map[string]string)
105 case tea.KeyMsg:
110 case tea.KeyMsg:
106 switch {
111 switch {
107− case key.Matches(msg, permissionsKeys.LeftRight) || key.Matches(msg, permissionsKeys.Tab):
112+ case key.Matches(msg, permissionsKeys.Right) || key.Matches(msg, permissionsKeys.Tab):
108− // Change selected option
109 p.selectedOption = (p.selectedOption + 1) % 3
113 p.selectedOption = (p.selectedOption + 1) % 3
110 return p, nil
114 return p, nil
115+ case key.Matches(msg, permissionsKeys.Left):
116+ p.selectedOption = (p.selectedOption + 2) % 3
111 case key.Matches(msg, permissionsKeys.EnterSpace):
117 case key.Matches(msg, permissionsKeys.EnterSpace):
112− // Select current option
113 return p, p.selectCurrentOption()
118 return p, p.selectCurrentOption()
114 case key.Matches(msg, permissionsKeys.Allow):
119 case key.Matches(msg, permissionsKeys.Allow):
115− // Select Allow
116 return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionAllow, Permission: p.permission})
120 return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionAllow, Permission: p.permission})
117 case key.Matches(msg, permissionsKeys.AllowSession):
121 case key.Matches(msg, permissionsKeys.AllowSession):
118− // Select Allow for session
119 return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionAllowForSession, Permission: p.permission})
122 return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionAllowForSession, Permission: p.permission})
120 case key.Matches(msg, permissionsKeys.Deny):
123 case key.Matches(msg, permissionsKeys.Deny):
121− // Select Deny
122 return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionDeny, Permission: p.permission})
124 return p, util.CmdHandler(PermissionResponseMsg{Action: PermissionDeny, Permission: p.permission})
123 default:
125 default:
124 // Pass other keys to viewport
126 // Pass other keys to viewport
internal/tui/components/dialog/session.go+25−23
@@ -27,20 +27,20 @@ type SessionDialog interface {
27}
27}
28
28
29type sessionDialogCmp struct {
29type sessionDialogCmp struct {
30− sessions []session.Session
30+ sessions []session.Session
31− selectedIdx int
31+ selectedIdx int
32− width int
32+ width int
33− height int
33+ height int
34 selectedSessionID string
34 selectedSessionID string
35}
35}
36
36
37type sessionKeyMap struct {
37type sessionKeyMap struct {
38− Up key.Binding
38+ Up key.Binding
39− Down key.Binding
39+ Down key.Binding
40− Enter key.Binding
40+ Enter key.Binding
41− Escape key.Binding
41+ Escape key.Binding
42− J key.Binding
42+ J key.Binding
43− K key.Binding
43+ K key.Binding
44}
44}
45
45
46var sessionKeys = sessionKeyMap{
46var sessionKeys = sessionKeyMap{
@@ -128,7 +128,7 @@ func (s *sessionDialogCmp) View() string {
128 // Build the session list
128 // Build the session list
129 sessionItems := make([]string, 0, maxVisibleSessions)
129 sessionItems := make([]string, 0, maxVisibleSessions)
130 startIdx := 0
130 startIdx := 0
131−
131+
132 // If we have more sessions than can be displayed, adjust the start index
132 // If we have more sessions than can be displayed, adjust the start index
133 if len(s.sessions) > maxVisibleSessions {
133 if len(s.sessions) > maxVisibleSessions {
134 // Center the selected item when possible
134 // Center the selected item when possible
@@ -145,30 +145,31 @@ func (s *sessionDialogCmp) View() string {
145 for i := startIdx; i < endIdx; i++ {
145 for i := startIdx; i < endIdx; i++ {
146 sess := s.sessions[i]
146 sess := s.sessions[i]
147 itemStyle := styles.BaseStyle.Width(maxWidth)
147 itemStyle := styles.BaseStyle.Width(maxWidth)
148−
148+
149 if i == s.selectedIdx {
149 if i == s.selectedIdx {
150 itemStyle = itemStyle.
150 itemStyle = itemStyle.
151 Background(styles.PrimaryColor).
151 Background(styles.PrimaryColor).
152 Foreground(styles.Background).
152 Foreground(styles.Background).
153 Bold(true)
153 Bold(true)
154 }
154 }
155−
155+
156 sessionItems = append(sessionItems, itemStyle.Padding(0, 1).Render(sess.Title))
156 sessionItems = append(sessionItems, itemStyle.Padding(0, 1).Render(sess.Title))
157 }
157 }
158
158
159 title := styles.BaseStyle.
159 title := styles.BaseStyle.
160 Foreground(styles.PrimaryColor).
160 Foreground(styles.PrimaryColor).
161 Bold(true).
161 Bold(true).
162+ Width(maxWidth).
162 Padding(0, 1).
163 Padding(0, 1).
163 Render("Switch Session")
164 Render("Switch Session")
164
165
165 content := lipgloss.JoinVertical(
166 content := lipgloss.JoinVertical(
166 lipgloss.Left,
167 lipgloss.Left,
167 title,
168 title,
168− styles.BaseStyle.Render(""),
169+ styles.BaseStyle.Width(maxWidth).Render(""),
169− lipgloss.JoinVertical(lipgloss.Left, sessionItems...),
170+ styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)),
170− styles.BaseStyle.Render(""),
171+ styles.BaseStyle.Width(maxWidth).Render(""),
171− styles.BaseStyle.Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"),
172+ styles.BaseStyle.Width(maxWidth).Padding(0, 1).Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"),
172 )
173 )
173
174
174 return styles.BaseStyle.Padding(1, 2).
175 return styles.BaseStyle.Padding(1, 2).
@@ -185,7 +186,7 @@ func (s *sessionDialogCmp) BindingKeys() []key.Binding {
185
186
186func (s *sessionDialogCmp) SetSessions(sessions []session.Session) {
187func (s *sessionDialogCmp) SetSessions(sessions []session.Session) {
187 s.sessions = sessions
188 s.sessions = sessions
188−
189+
189 // If we have a selected session ID, find its index
190 // If we have a selected session ID, find its index
190 if s.selectedSessionID != "" {
191 if s.selectedSessionID != "" {
191 for i, sess := range sessions {
192 for i, sess := range sessions {
@@ -195,14 +196,14 @@ func (s *sessionDialogCmp) SetSessions(sessions []session.Session) {
195 }
196 }
196 }
197 }
197 }
198 }
198−
199+
199 // Default to first session if selected not found
200 // Default to first session if selected not found
200 s.selectedIdx = 0
201 s.selectedIdx = 0
201}
202}
202
203
203func (s *sessionDialogCmp) SetSelectedSession(sessionID string) {
204func (s *sessionDialogCmp) SetSelectedSession(sessionID string) {
204 s.selectedSessionID = sessionID
205 s.selectedSessionID = sessionID
205−
206+
206 // Update the selected index if sessions are already loaded
207 // Update the selected index if sessions are already loaded
207 if len(s.sessions) > 0 {
208 if len(s.sessions) > 0 {
208 for i, sess := range s.sessions {
209 for i, sess := range s.sessions {
@@ -217,8 +218,9 @@ func (s *sessionDialogCmp) SetSelectedSession(sessionID string) {
217// NewSessionDialogCmp creates a new session switching dialog
218// NewSessionDialogCmp creates a new session switching dialog
218func NewSessionDialogCmp() SessionDialog {
219func NewSessionDialogCmp() SessionDialog {
219 return &sessionDialogCmp{
220 return &sessionDialogCmp{
220− sessions: []session.Session{},
221+ sessions: []session.Session{},
221− selectedIdx: 0,
222+ selectedIdx: 0,
222 selectedSessionID: "",
223 selectedSessionID: "",
223 }
224 }
224−}
225+}
226+
internal/tui/page/chat.go−7
@@ -43,13 +43,6 @@ func (p *chatPage) Init() tea.Cmd {
43 cmds := []tea.Cmd{
43 cmds := []tea.Cmd{
44 p.layout.Init(),
44 p.layout.Init(),
45 }
45 }
46−
47− sessions, _ := p.app.Sessions.List(context.Background())
48− if len(sessions) > 0 {
49− p.session = sessions[0]
50− cmd := p.setSidebar()
51− cmds = append(cmds, util.CmdHandler(chat.SessionSelectedMsg(p.session)), cmd)
52− }
53 return tea.Batch(cmds...)
46 return tea.Batch(cmds...)
54}
47}
55
48
internal/tui/tui.go+3−1
@@ -163,6 +163,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
163 a.showPermissions = true
163 a.showPermissions = true
164 return a, a.permissions.SetPermissions(msg.Payload)
164 return a, a.permissions.SetPermissions(msg.Payload)
165 case dialog.PermissionResponseMsg:
165 case dialog.PermissionResponseMsg:
166+ var cmd tea.Cmd
166 switch msg.Action {
167 switch msg.Action {
167 case dialog.PermissionAllow:
168 case dialog.PermissionAllow:
168 a.app.Permissions.Grant(msg.Permission)
169 a.app.Permissions.Grant(msg.Permission)
@@ -170,9 +171,10 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
170 a.app.Permissions.GrantPersistant(msg.Permission)
171 a.app.Permissions.GrantPersistant(msg.Permission)
171 case dialog.PermissionDeny:
172 case dialog.PermissionDeny:
172 a.app.Permissions.Deny(msg.Permission)
173 a.app.Permissions.Deny(msg.Permission)
174+ cmd = util.CmdHandler(chat.FocusEditorMsg(true))
173 }
175 }
174 a.showPermissions = false
176 a.showPermissions = false
175− return a, nil
177+ return a, cmd
176
178
177 case page.PageChangeMsg:
179 case page.PageChangeMsg:
178 return a, a.moveToPage(msg.ID)
180 return a, a.moveToPage(msg.ID)