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(NewErrorMsg(err))
30}
31
32type InfoType int
33
34const (
35 InfoTypeInfo InfoType = iota
36 InfoTypeSuccess
37 InfoTypeWarn
38 InfoTypeError
39 InfoTypeUpdate
40)
41
42func NewInfoMsg(info string) InfoMsg {
43 return InfoMsg{
44 Type: InfoTypeInfo,
45 Msg: info,
46 }
47}
48
49func NewWarnMsg(warn string) InfoMsg {
50 return InfoMsg{
51 Type: InfoTypeWarn,
52 Msg: warn,
53 }
54}
55
56func NewErrorMsg(err error) InfoMsg {
57 return InfoMsg{
58 Type: InfoTypeError,
59 Msg: err.Error(),
60 }
61}
62
63func ReportInfo(info string) tea.Cmd {
64 return CmdHandler(NewInfoMsg(info))
65}
66
67func ReportWarn(warn string) tea.Cmd {
68 return CmdHandler(NewWarnMsg(warn))
69}
70
71type (
72 InfoMsg struct {
73 Type InfoType
74 Msg string
75 TTL time.Duration
76 }
77 ClearStatusMsg struct{}
78)
79
80// IsEmpty checks if the [InfoMsg] is empty.
81func (m InfoMsg) IsEmpty() bool {
82 var zero InfoMsg
83 return m == zero
84}
85
86// ExecShell parses a shell command string and executes it with exec.Command.
87// Uses shell.Fields for proper handling of shell syntax like quotes and
88// arguments while preserving TTY handling for terminal editors.
89func ExecShell(ctx context.Context, cmdStr string, callback tea.ExecCallback) tea.Cmd {
90 fields, err := shell.Fields(cmdStr, nil)
91 if err != nil {
92 return ReportError(err)
93 }
94 if len(fields) == 0 {
95 return ReportError(errors.New("empty command"))
96 }
97
98 cmd := exec.CommandContext(ctx, fields[0], fields[1:]...)
99 return tea.ExecProcess(cmd, callback)
100}