(msg tea.KeyMsg)
| 424 | } |
| 425 | |
| 426 | func (m harness) updateInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { |
| 427 | switch msg.Type { |
| 428 | case tea.KeyEnter: |
| 429 | if m.state != stateIdle { |
| 430 | return m, nil |
| 431 | } |
| 432 | text := strings.TrimSpace(m.input.Value()) |
| 433 | if text == "" { |
| 434 | return m, nil |
| 435 | } |
| 436 | // Echo the prompt into scrollback so the user can scan back through |
| 437 | // the conversation. A timestamped divider above each turn gives an |
| 438 | // obvious "this is a new prompt" marker — without it, tool log |
| 439 | // lines and assistant text all run together visually. |
| 440 | m.output.WriteString("\n" + renderTurnHeader(m.viewport.Width) + "\n") |
| 441 | m.output.WriteString(promptMarkerStyle.Render("❯ ") + promptTextStyle.Render(text) + "\n\n") |
| 442 | m.setConversationContent() |
| 443 | m.viewport.GotoBottom() |
| 444 | m.followBottom = true |
| 445 | m.input.SetValue("") |
| 446 | m.state = stateRunning |
| 447 | // Kick the spinner alongside the agent run so it animates while |
| 448 | // we wait. The TickMsg handler self-perpetuates while running. |
| 449 | return m, tea.Batch(m.runOnce(text), m.spinner.Tick) |
| 450 | |
| 451 | case tea.KeyCtrlD, tea.KeyCtrlC: |
| 452 | return m, tea.Quit |
| 453 | |
| 454 | case tea.KeyPgUp, tea.KeyPgDown, tea.KeyHome, tea.KeyEnd: |
| 455 | var cmd tea.Cmd |
| 456 | m.viewport, cmd = m.viewport.Update(msg) |
| 457 | m.followBottom = m.viewport.AtBottom() |
| 458 | return m, cmd |
| 459 | } |
| 460 | |
| 461 | var cmd tea.Cmd |
| 462 | m.input, cmd = m.input.Update(msg) |
| 463 | return m, cmd |
| 464 | } |
| 465 | |
| 466 | func (m harness) updateApproval(msg tea.KeyMsg) (tea.Model, tea.Cmd) { |
| 467 | // When the approval includes a diff/detail, forward scroll keys to the |
no test coverage detected