util.go 1.8 KB

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