diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index 909ef31f451e8eb5bfabd61e3cb1750e738f838d..e79eb35e27cb5805c000e774b209c66c96b01ebd 100644 --- a/internal/tui/page/chat/chat.go +++ b/internal/tui/page/chat/chat.go @@ -3,6 +3,7 @@ package chat import ( "context" "strings" + "time" "github.com/charmbracelet/bubbles/v2/key" tea "github.com/charmbracelet/bubbletea/v2" @@ -33,6 +34,7 @@ type ( ChatFocusedMsg struct { Focused bool // True if the chat input is focused, false otherwise } + CancelTimerExpiredMsg struct{} ) type ChatPage interface { @@ -57,6 +59,8 @@ type chatPage struct { showDetails bool // Show details in the header header header.Header compactSidebar layout.Container + + cancelPending bool // True if ESC was pressed once and waiting for second press } func (p *chatPage) Init() tea.Cmd { @@ -67,9 +71,19 @@ func (p *chatPage) Init() tea.Cmd { ) } +// cancelTimerCmd creates a command that expires the cancel timer after 2 seconds +func (p *chatPage) cancelTimerCmd() tea.Cmd { + return tea.Tick(2*time.Second, func(time.Time) tea.Msg { + return CancelTimerExpiredMsg{} + }) +} + func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { + case CancelTimerExpiredMsg: + p.cancelPending = false + return p, nil case tea.WindowSizeMsg: h, cmd := p.header.Update(msg) cmds = append(cmds, cmd) @@ -181,10 +195,16 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return p, tea.Batch(cmds...) case key.Matches(msg, p.keyMap.Cancel): if p.session.ID != "" { - // Cancel the current session's generation process - // This allows users to interrupt long-running operations - p.app.CoderAgent.Cancel(p.session.ID) - return p, nil + if p.cancelPending { + // Second ESC press - actually cancel the session + p.cancelPending = false + p.app.CoderAgent.Cancel(p.session.ID) + return p, nil + } else { + // First ESC press - start the timer + p.cancelPending = true + return p, p.cancelTimerCmd() + } } case key.Matches(msg, p.keyMap.Details): if p.session.ID == "" || !p.compactMode { @@ -336,7 +356,14 @@ func (p *chatPage) Bindings() []key.Binding { p.keyMap.AddAttachment, } if p.app.CoderAgent.IsBusy() { - bindings = append([]key.Binding{p.keyMap.Cancel}, bindings...) + cancelBinding := p.keyMap.Cancel + if p.cancelPending { + cancelBinding = key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "press again to cancel"), + ) + } + bindings = append([]key.Binding{cancelBinding}, bindings...) } if p.chatFocused {