Skip to content

Commit 417b98d

Browse files
authored
Merge pull request #1124 from rumpl/fix-copy
Fix copying utf8
2 parents 4d54245 + 76fe014 commit 417b98d

5 files changed

Lines changed: 48 additions & 34 deletions

File tree

pkg/tui/components/messages/messages.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -841,11 +841,14 @@ func (m *model) copySelectionToClipboard() tea.Cmd {
841841
return nil
842842
}
843843

844-
if err := clipboard.WriteAll(selectedText); err != nil {
845-
return core.CmdHandler(notification.ShowMsg{Text: "Failed to copy: " + err.Error(), Type: notification.TypeError})
846-
}
847-
848-
return core.CmdHandler(notification.ShowMsg{Text: "Text copied to clipboard"})
844+
return tea.Sequence(
845+
tea.SetClipboard(selectedText),
846+
func() tea.Msg {
847+
_ = clipboard.WriteAll(selectedText)
848+
return nil
849+
},
850+
notification.SuccessCmd("Text copied to clipboard."),
851+
)
849852
}
850853

851854
func (m *model) clearSelection() {

pkg/tui/components/notification/notification.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
tea "charm.land/bubbletea/v2"
99
"charm.land/lipgloss/v2"
1010

11+
"github.com/docker/cagent/pkg/tui/core"
1112
"github.com/docker/cagent/pkg/tui/styles"
1213
)
1314

@@ -38,6 +39,27 @@ type HideMsg struct {
3839
ID uint64 // If 0, hides all notifications (backward compatibility)
3940
}
4041

42+
func SuccessCmd(text string) tea.Cmd {
43+
return core.CmdHandler(ShowMsg{
44+
Text: text,
45+
Type: TypeSuccess,
46+
})
47+
}
48+
49+
func WarningCmd(text string) tea.Cmd {
50+
return core.CmdHandler(ShowMsg{
51+
Text: text,
52+
Type: TypeWarning,
53+
})
54+
}
55+
56+
func ErrorCmd(text string) tea.Cmd {
57+
return core.CmdHandler(ShowMsg{
58+
Text: text,
59+
Type: TypeError,
60+
})
61+
}
62+
4163
// notificationItem represents a single notification
4264
type notificationItem struct {
4365
ID uint64

pkg/tui/page/chat/chat.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,7 @@ func (p *chatPage) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
305305
cmd := p.messages.AddShellOutputMessage(msg.Output)
306306
return p, cmd
307307
case *runtime.WarningEvent:
308-
cmd := core.CmdHandler(notification.ShowMsg{Text: msg.Message, Type: notification.TypeWarning})
309-
return p, cmd
308+
return p, notification.WarningCmd(msg.Message)
310309
case *runtime.RAGIndexingStartedEvent, *runtime.RAGIndexingProgressEvent, *runtime.RAGIndexingCompletedEvent:
311310
// Forward RAG events to sidebar
312311
slog.Debug("Chat page forwarding RAG event to sidebar", "event_type", fmt.Sprintf("%T", msg))

pkg/tui/page/chat/editor.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
tea "charm.land/bubbletea/v2"
1111

1212
"github.com/docker/cagent/pkg/tui/components/notification"
13-
"github.com/docker/cagent/pkg/tui/core"
1413
)
1514

1615
// editorDoneMsg is sent when the external editor finishes to trigger a TUI refresh.
@@ -24,20 +23,14 @@ func (p *chatPage) openExternalEditor() tea.Cmd {
2423
// Create a temporary file with the current content
2524
tmpFile, err := os.CreateTemp("", "cagent-*.md")
2625
if err != nil {
27-
return core.CmdHandler(notification.ShowMsg{
28-
Text: fmt.Sprintf("Failed to create temp file: %v", err),
29-
Type: notification.TypeError,
30-
})
26+
return notification.ErrorCmd(fmt.Sprintf("Failed to create temp file: %v", err))
3127
}
3228
tmpPath := tmpFile.Name()
3329

3430
if _, err := tmpFile.WriteString(content); err != nil {
3531
tmpFile.Close()
3632
os.Remove(tmpPath)
37-
return core.CmdHandler(notification.ShowMsg{
38-
Text: fmt.Sprintf("Failed to write temp file: %v", err),
39-
Type: notification.TypeError,
40-
})
33+
return notification.ErrorCmd(fmt.Sprintf("Failed to write temp file: %v", err))
4134
}
4235
tmpFile.Close()
4336

@@ -63,20 +56,14 @@ func (p *chatPage) openExternalEditor() tea.Cmd {
6356
return tea.ExecProcess(cmd, func(err error) tea.Msg {
6457
if err != nil {
6558
os.Remove(tmpPath)
66-
return core.CmdHandler(notification.ShowMsg{
67-
Type: notification.TypeError,
68-
Text: fmt.Sprintf("Editor error: %v", err),
69-
})
59+
return notification.ErrorCmd(fmt.Sprintf("Editor error: %v", err))
7060
}
7161

7262
updatedContent, readErr := os.ReadFile(tmpPath)
7363
os.Remove(tmpPath)
7464

7565
if readErr != nil {
76-
return core.CmdHandler(notification.ShowMsg{
77-
Text: fmt.Sprintf("Failed to read edited file: %v", readErr),
78-
Type: notification.TypeError,
79-
})
66+
return notification.ErrorCmd(fmt.Sprintf("Failed to read edited file: %v", readErr))
8067
}
8168

8269
// Trim trailing newline that editors often add

pkg/tui/tui.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -210,22 +210,25 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
210210

211211
case messages.EvalSessionMsg:
212212
evalFile, _ := evaluation.Save(a.application.Session(), msg.Filename)
213-
return a, core.CmdHandler(notification.ShowMsg{Text: fmt.Sprintf("Eval saved to file %s", evalFile)})
213+
return a, notification.SuccessCmd(fmt.Sprintf("Eval saved to file %s", evalFile))
214214

215215
case messages.CompactSessionMsg:
216216
return a, a.chatPage.CompactSession()
217217

218218
case messages.CopySessionToClipboardMsg:
219219
transcript := a.application.PlainTextTranscript()
220220
if transcript == "" {
221-
return a, core.CmdHandler(notification.ShowMsg{Text: "Conversation is empty; nothing copied."})
221+
return a, notification.SuccessCmd("Conversation is empty; nothing copied.")
222222
}
223223

224-
if err := clipboard.WriteAll(transcript); err != nil {
225-
return a, core.CmdHandler(notification.ShowMsg{Text: "Failed to copy conversation: " + err.Error(), Type: notification.TypeError})
226-
}
227-
228-
return a, core.CmdHandler(notification.ShowMsg{Text: "Conversation copied to clipboard."})
224+
return a, tea.Sequence(
225+
tea.SetClipboard(transcript),
226+
func() tea.Msg {
227+
_ = clipboard.WriteAll(transcript)
228+
return nil
229+
},
230+
notification.SuccessCmd("Conversation copied to clipboard."),
231+
)
229232

230233
case messages.ToggleYoloMsg:
231234
sess := a.application.Session()
@@ -236,7 +239,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
236239
} else {
237240
statusText = "Yolo mode disabled: tools will require confirmation"
238241
}
239-
return a, core.CmdHandler(notification.ShowMsg{Text: statusText})
242+
return a, notification.SuccessCmd(statusText)
240243

241244
case messages.AgentCommandMsg:
242245
resolvedCommand := a.application.ResolveCommand(context.Background(), msg.Command)
@@ -246,7 +249,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
246249
// Convert the interface{} back to mcptools.PromptInfo
247250
promptInfo, ok := msg.PromptInfo.(mcptools.PromptInfo)
248251
if !ok {
249-
return a, core.CmdHandler(notification.ShowMsg{Text: "Invalid prompt info"})
252+
return a, notification.ErrorCmd("Invalid prompt info")
250253
}
251254
// Show the MCP prompt input dialog
252255
return a, core.CmdHandler(dialog.OpenDialogMsg{
@@ -258,7 +261,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
258261
promptContent, err := a.application.ExecuteMCPPrompt(context.Background(), msg.PromptName, msg.Arguments)
259262
if err != nil {
260263
errorMsg := fmt.Sprintf("Error executing MCP prompt '%s': %v", msg.PromptName, err)
261-
return a, core.CmdHandler(notification.ShowMsg{Text: errorMsg})
264+
return a, notification.ErrorCmd(errorMsg)
262265
}
263266
return a, core.CmdHandler(editor.SendMsg{Content: promptContent})
264267

0 commit comments

Comments
 (0)