diff --git a/main.go b/main.go index 4bba17c..cbd5e62 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,8 @@ package main import ( - "bufio" "flag" "fmt" - "io" "io/ioutil" "os" "os/user" @@ -103,16 +101,11 @@ func SetStatus(args []string) error { outputPath := path.Join(curUser.HomeDir, ".checkin") // Prompt user for input - fmt.Printf("What's ~%v been up to?\n~%v", curUser.Username, curUser.Username) - reader := bufio.NewReader(os.Stdin) - input, err := reader.ReadString('\n') - if err != nil && err != io.EOF { + input, err := PromptInput() + if err != nil { return err } - // Strip whitespace from right - input = strings.TrimRight(input, " \n\t\r") - // Remove file on blank input if input == "" { err = os.Remove(outputPath) @@ -125,9 +118,6 @@ func SetStatus(args []string) error { return nil } - // Prepend status with user's name - input = fmt.Sprintf("~%s%s", curUser.Username, input) - if *includeWd { wd, err := os.Getwd() if err != nil { diff --git a/prompt.go b/prompt.go new file mode 100644 index 0000000..9230c20 --- /dev/null +++ b/prompt.go @@ -0,0 +1,95 @@ +package main + +import ( + "errors" + "fmt" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/muesli/termenv" + "os/user" + "strings" +) + +// PromptInput prompts the user for input and returns either a well-formed status (leading ~user) or an empty string. +func PromptInput() (string, error) { + model, err := newInputModel() + if err != nil { + return "", err + } + p := tea.NewProgram(model) + err = p.Start() + if err != nil { + return "", err + } + return model.String(), nil +} + +type inputModel struct { + // Contains the model of the child text entry widget + input textinput.Model + // The prompt presented to the user + prompt string + // Any error that occurs while processing. + Err error +} + +// newInputModel creates a new inputModel ready for use, populated with a random prompt. +func newInputModel() (*inputModel, error) { + user, err := user.Current() + if err != nil { + return &inputModel{}, err + } + model := inputModel{} + model.prompt = fmt.Sprintf("What's ~%s up to?", user.Username) + + model.input = textinput.NewModel() + + // The input's Prompt is used to prepend an unchangeable prefix to the input. + model.input.Prompt = fmt.Sprintf("~%s", user.Username) + model.input.SetValue(" ") + model.input.CursorEnd() + model.input.Focus() + return &model, nil +} + +// String will return either a valid status with all whitespace removed or a blank string. +func (m *inputModel) String() string { + output := strings.TrimRight(m.input.Value(), " \n\t\r") + if output == "" { + return output + } + return fmt.Sprintf("%s%s", m.input.Prompt, output) +} + +func (m *inputModel) Init() tea.Cmd { + return textinput.Blink +} + +func (m *inputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlC, tea.KeyEsc: + m.Err = errors.New("user cancelled input") + fallthrough + case tea.KeyCtrlD, tea.KeyEnter: + return m, tea.Quit + } + + case error: + m.Err = msg + return m, tea.Quit + } + + var cmd tea.Cmd + m.input, cmd = m.input.Update(msg) + return m, cmd +} + +func (m *inputModel) View() string { + if termenv.ColorProfile() == termenv.Ascii { + return fmt.Sprintf("%s %s", m.prompt, m.input.View()) + } + return fmt.Sprintf("%s %s", termenv.String(m.prompt).Bold(), m.input.View()) +}