From 8566940e36c2f3aa4b2b184838eebd4583081fcd Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 6 Aug 2025 15:30:20 -0400 Subject: [PATCH] feat(tui): chat: support double-click to select and copy text after timeout --- internal/tui/components/chat/chat.go | 45 ++++++++++++++++++++++------ internal/tui/page/chat/chat.go | 4 +++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/internal/tui/components/chat/chat.go b/internal/tui/components/chat/chat.go index 00e18135b5d498c08c7db52135691f5db61ce220..74f7af06c09e2d13ccd3dd707df84bbba8c0e89f 100644 --- a/internal/tui/components/chat/chat.go +++ b/internal/tui/components/chat/chat.go @@ -29,6 +29,12 @@ type SessionSelectedMsg = session.Session type SessionClearedMsg struct{} +type SelectionCopyMsg struct { + clickCount int + endSelection bool + x, y int +} + const ( NotFound = -1 ) @@ -134,14 +140,34 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { x := msg.X - 1 // Adjust for padding y := msg.Y - 1 // Adjust for padding if msg.Button == tea.MouseLeft { + clickCount := m.clickCount if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 { - m.listCmp.SelectionStop() - } else { - m.listCmp.EndSelection(x, y) - m.listCmp.SelectionStop() + return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg { + return SelectionCopyMsg{ + clickCount: clickCount, + endSelection: false, + } + }) } + return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg { + return SelectionCopyMsg{ + clickCount: clickCount, + endSelection: true, + x: x, + y: y, + } + }) } return m, nil + case SelectionCopyMsg: + if msg.clickCount == m.clickCount && time.Since(m.lastClickTime) >= doubleClickThreshold { + // If the click count matches and within threshold, copy selected text + if msg.endSelection { + m.listCmp.EndSelection(msg.x, msg.y) + } + m.listCmp.SelectionStop() + return m, m.CopySelectedText(true) + } case pubsub.Event[permission.PermissionNotification]: return m, m.handlePermissionRequest(msg.Payload) case SessionSelectedMsg: @@ -621,13 +647,13 @@ func (m *messageListCmp) GoToBottom() tea.Cmd { return m.listCmp.GoToBottom() } +const ( + doubleClickThreshold = 500 * time.Millisecond + clickTolerance = 2 // pixels +) + // handleMouseClick handles mouse click events and detects double/triple clicks. func (m *messageListCmp) handleMouseClick(x, y int) tea.Cmd { - const ( - doubleClickThreshold = 500 * time.Millisecond - clickTolerance = 2 // pixels - ) - now := time.Now() // Check if this is a potential multi-click @@ -664,6 +690,7 @@ func (m *messageListCmp) SelectionClear() tea.Cmd { m.listCmp.SelectionClear() m.previousSelected = "" m.lastClickX, m.lastClickY = 0, 0 + m.lastClickTime = time.Time{} m.clickCount = 0 return nil } diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index 381ad91d429ffd10ac5461b869ca33db1e68fb33..b418abc4989f33433c2c4d12d0aa5aab7069254d 100644 --- a/internal/tui/page/chat/chat.go +++ b/internal/tui/page/chat/chat.go @@ -211,6 +211,10 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return p, cmd } return p, nil + case chat.SelectionCopyMsg: + u, cmd := p.chat.Update(msg) + p.chat = u.(chat.MessageListCmp) + return p, cmd case tea.WindowSizeMsg: u, cmd := p.editor.Update(msg) p.editor = u.(editor.Editor)