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