1// Package uiutil provides utility functions for UI message handling.
2// TODO: Move to internal/ui/<appropriate_location> once the new UI migration
3// is finalized.
4package uiutil
5
6import (
7 "context"
8 "errors"
9 "log/slog"
10 "os/exec"
11 "time"
12
13 tea "charm.land/bubbletea/v2"
14 "mvdan.cc/sh/v3/shell"
15)
16
17type Cursor interface {
18 Cursor() *tea.Cursor
19}
20
21func CmdHandler(msg tea.Msg) tea.Cmd {
22 return func() tea.Msg {
23 return msg
24 }
25}
26
27func ReportError(err error) tea.Cmd {
28 slog.Error("Error reported", "error", err)
29 return CmdHandler(InfoMsg{
30 Type: InfoTypeError,
31 Msg: err.Error(),
32 })
33}
34
35type InfoType int
36
37const (
38 InfoTypeInfo InfoType = iota
39 InfoTypeSuccess
40 InfoTypeWarn
41 InfoTypeError
42 InfoTypeUpdate
43)
44
45func ReportInfo(info string) tea.Cmd {
46 return CmdHandler(InfoMsg{
47 Type: InfoTypeInfo,
48 Msg: info,
49 })
50}
51
52func ReportWarn(warn string) tea.Cmd {
53 return CmdHandler(InfoMsg{
54 Type: InfoTypeWarn,
55 Msg: warn,
56 })
57}
58
59type (
60 InfoMsg struct {
61 Type InfoType
62 Msg string
63 TTL time.Duration
64 }
65 ClearStatusMsg struct{}
66)
67
68// ExecShell parses a shell command string and executes it with exec.Command.
69// Uses shell.Fields for proper handling of shell syntax like quotes and
70// arguments while preserving TTY handling for terminal editors.
71func ExecShell(ctx context.Context, cmdStr string, callback tea.ExecCallback) tea.Cmd {
72 fields, err := shell.Fields(cmdStr, nil)
73 if err != nil {
74 return ReportError(err)
75 }
76 if len(fields) == 0 {
77 return ReportError(errors.New("empty command"))
78 }
79
80 cmd := exec.CommandContext(ctx, fields[0], fields[1:]...)
81 return tea.ExecProcess(cmd, callback)
82}