on main help sign in
feat(tui): chat: support double-click to select and copy text after
8566940Ayman Bagabas
internal/tui/components/chat/chat.go+36−9
@@ -29,6 +29,12 @@ type SessionSelectedMsg = session.Session
29
29
30type SessionClearedMsg struct{}
30type SessionClearedMsg struct{}
31
31
32+type SelectionCopyMsg struct {
33+ clickCount int
34+ endSelection bool
35+ x, y int
36+}
37+
32const (
38const (
33 NotFound = -1
39 NotFound = -1
34)
40)
@@ -134,14 +140,34 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
134 x := msg.X - 1 // Adjust for padding
140 x := msg.X - 1 // Adjust for padding
135 y := msg.Y - 1 // Adjust for padding
141 y := msg.Y - 1 // Adjust for padding
136 if msg.Button == tea.MouseLeft {
142 if msg.Button == tea.MouseLeft {
143+ clickCount := m.clickCount
137 if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
144 if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
138− m.listCmp.SelectionStop()
145+ return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
139− } else {
146+ return SelectionCopyMsg{
140− m.listCmp.EndSelection(x, y)
147+ clickCount: clickCount,
141− m.listCmp.SelectionStop()
148+ endSelection: false,
149+ }
150+ })
142 }
151 }
152+ return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
153+ return SelectionCopyMsg{
154+ clickCount: clickCount,
155+ endSelection: true,
156+ x: x,
157+ y: y,
158+ }
159+ })
143 }
160 }
144 return m, nil
161 return m, nil
162+ case SelectionCopyMsg:
163+ if msg.clickCount == m.clickCount && time.Since(m.lastClickTime) >= doubleClickThreshold {
164+ // If the click count matches and within threshold, copy selected text
165+ if msg.endSelection {
166+ m.listCmp.EndSelection(msg.x, msg.y)
167+ }
168+ m.listCmp.SelectionStop()
169+ return m, m.CopySelectedText(true)
170+ }
145 case pubsub.Event[permission.PermissionNotification]:
171 case pubsub.Event[permission.PermissionNotification]:
146 return m, m.handlePermissionRequest(msg.Payload)
172 return m, m.handlePermissionRequest(msg.Payload)
147 case SessionSelectedMsg:
173 case SessionSelectedMsg:
@@ -621,13 +647,13 @@ func (m *messageListCmp) GoToBottom() tea.Cmd {
621 return m.listCmp.GoToBottom()
647 return m.listCmp.GoToBottom()
622}
648}
623
649
650+const (
651+ doubleClickThreshold = 500 * time.Millisecond
652+ clickTolerance = 2 // pixels
653+)
654+
624// handleMouseClick handles mouse click events and detects double/triple clicks.
655// handleMouseClick handles mouse click events and detects double/triple clicks.
625func (m *messageListCmp) handleMouseClick(x, y int) tea.Cmd {
656func (m *messageListCmp) handleMouseClick(x, y int) tea.Cmd {
626− const (
627− doubleClickThreshold = 500 * time.Millisecond
628− clickTolerance = 2 // pixels
629− )
630−
631 now := time.Now()
657 now := time.Now()
632
658
633 // Check if this is a potential multi-click
659 // Check if this is a potential multi-click
@@ -664,6 +690,7 @@ func (m *messageListCmp) SelectionClear() tea.Cmd {
664 m.listCmp.SelectionClear()
690 m.listCmp.SelectionClear()
665 m.previousSelected = ""
691 m.previousSelected = ""
666 m.lastClickX, m.lastClickY = 0, 0
692 m.lastClickX, m.lastClickY = 0, 0
693+ m.lastClickTime = time.Time{}
667 m.clickCount = 0
694 m.clickCount = 0
668 return nil
695 return nil
669}
696}
internal/tui/page/chat/chat.go+4
@@ -211,6 +211,10 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
211 return p, cmd
211 return p, cmd
212 }
212 }
213 return p, nil
213 return p, nil
214+ case chat.SelectionCopyMsg:
215+ u, cmd := p.chat.Update(msg)
216+ p.chat = u.(chat.MessageListCmp)
217+ return p, cmd
214 case tea.WindowSizeMsg:
218 case tea.WindowSizeMsg:
215 u, cmd := p.editor.Update(msg)
219 u, cmd := p.editor.Update(msg)
216 p.editor = u.(editor.Editor)
220 p.editor = u.(editor.Editor)