util.go

 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}