@@ -23,6 +23,7 @@ import (
2323 "charm.land/bubbles/v2/textarea"
2424 tea "charm.land/bubbletea/v2"
2525 "charm.land/lipgloss/v2"
26+ "github.com/atotto/clipboard"
2627 "github.com/charmbracelet/catwalk/pkg/catwalk"
2728 "github.com/charmbracelet/crush/internal/agent/tools/mcp"
2829 "github.com/charmbracelet/crush/internal/app"
@@ -103,6 +104,9 @@ type (
103104
104105 // closeDialogMsg is sent to close the current dialog.
105106 closeDialogMsg struct{}
107+
108+ // copyChatHighlightMsg is sent to copy the current chat highlight to clipboard.
109+ copyChatHighlightMsg struct{}
106110)
107111
108112// UI represents the main user interface model.
@@ -200,6 +204,9 @@ type UI struct {
200204 // Todo spinner
201205 todoSpinner spinner.Model
202206 todoIsSpinning bool
207+
208+ // mouse highlighting related state
209+ lastClickTime time.Time
203210}
204211
205212// New creates a new instance of the [UI] model.
@@ -481,6 +488,8 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
481488 m.keyMap.Models.SetHelp("ctrl+m", "models")
482489 m.keyMap.Editor.Newline.SetHelp("shift+enter", "newline")
483490 }
491+ case copyChatHighlightMsg:
492+ cmds = append(cmds, m.copyChatHighlight())
484493 case tea.MouseClickMsg:
485494 switch m.state {
486495 case uiChat:
@@ -488,7 +497,9 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
488497 // Adjust for chat area position
489498 x -= m.layout.main.Min.X
490499 y -= m.layout.main.Min.Y
491− m.chat.HandleMouseDown(x, y)
500+ if m.chat.HandleMouseDown(x, y) {
501+ m.lastClickTime = time.Now()
502+ }
492503 }
493504
494505 case tea.MouseMotionMsg:
@@ -524,13 +535,22 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
524535 }
525536
526537 case tea.MouseReleaseMsg:
538+ const doubleClickThreshold = 500 * time.Millisecond
539+
527540 switch m.state {
528541 case uiChat:
529542 x, y := msg.X, msg.Y
530543 // Adjust for chat area position
531544 x -= m.layout.main.Min.X
532545 y -= m.layout.main.Min.Y
533− m.chat.HandleMouseUp(x, y)
546+ if m.chat.HandleMouseUp(x, y) {
547+ cmds = append(cmds, tea.Tick(doubleClickThreshold, func(t time.Time) tea.Msg {
548+ if time.Since(m.lastClickTime) >= doubleClickThreshold {
549+ return copyChatHighlightMsg{}
550+ }
551+ return nil
552+ }))
553+ }
534554 }
535555 case tea.MouseWheelMsg:
536556 // Pass mouse events to dialogs first if any are open.
@@ -2842,6 +2862,22 @@ func (m *UI) runMCPPrompt(clientID, promptID string, arguments map[string]string
28422862 return tea.Sequence(cmds...)
28432863}
28442864
2865+func (m *UI) copyChatHighlight() tea.Cmd {
2866+ text := m.chat.HighlighContent()
2867+ return tea.Sequence(
2868+ tea.SetClipboard(text),
2869+ func() tea.Msg {
2870+ _ = clipboard.WriteAll(text)
2871+ return nil
2872+ },
2873+ func() tea.Msg {
2874+ m.chat.ClearMouse()
2875+ return nil
2876+ },
2877+ uiutil.ReportInfo("Selected text copied to clipboard"),
2878+ )
2879+}
2880+
28452881// renderLogo renders the Crush logo with the given styles and dimensions.
28462882func renderLogo(t *styles.Styles, compact bool, width int) string {
28472883 return logo.Render(version.Version, compact, logo.Opts{