charmbracelet/bubbletea

External process input errors when using `cmd.exe`

grafviktor opened this issue · 3 comments

Describe the bug
When use bubbletea app to run an external process on Windows OS, the process looses first character of the user input.

Setup
Please complete the following information along with version numbers, if applicable.

  • Windows 10
  • cmd.exe
  • Tested on github.com/charmbracelet/bubbletea v0.24.2 and v0.25.0

To Reproduce
Steps to reproduce the behavior:

  1. Create a project using the source code below. Place echo_input.cmd in the project's folder.
  2. Open cmd.exe, go to the project folder, and run the app: go run main.go
  3. Once the app started press up or down arrow keys
  4. Press e to run external process (the process will launch echo_input.cmd)
  5. Press 1 2 3 sequence on your keyboard
  6. Notice that only 23 was printed on your screen. Press enter key.
  7. Press any key to exit from the external process
  8. Notice that your lost character 1 was intercepted by the model's update method

Source Code

// main.go
package main

import (
	"fmt"
	"os"
	"os/exec"

	tea "github.com/charmbracelet/bubbletea"
)

type editorFinishedMsg struct{ err error }

func runExternalProcess() tea.Cmd {
	c := exec.Command("cmd", "/c", "echo_input.cmd")
	return tea.ExecProcess(c, func(err error) tea.Msg {
		return editorFinishedMsg{err}
	})
}

type model struct {
	input string
	err   error
}

func (m model) Init() tea.Cmd { return nil }

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "e":
			return m, runExternalProcess()
		case "ctrl+c", "q":
			return m, tea.Quit
		default:
			{
				if msg.Type != tea.KeyUp && msg.Type != tea.KeyDown {
					m.input = string(msg.Runes)
				}
			}
		}
	case editorFinishedMsg:
		if msg.err != nil {
			m.err = msg.err
			return m, tea.Quit
		}
	}
	return m, nil
}

func (m model) View() string {
	if m.err != nil {
		return "Error: " + m.err.Error() + "\n"
	}
	return fmt.Sprintf("Press 'e' to test input.\nPress 'q' to quit.\nLost character: %s", m.input)
}

func main() {
	if _, err := tea.NewProgram(model{}, tea.WithAltScreen()).Run(); err != nil {
		fmt.Println("Error running program:", err)
		os.Exit(1)
	}
}
rem echo_input.cmd
@echo off
set /p user_input=Enter 123:
echo You entered: %user_input%
pause

Expected behavior
All user input should be redirected to external process

It's readInputs function who steals the first char from the external process.

Okay cool: so we have an PR open (#878) that overhauls input on Windows which will likely fix this. Would you mind checking on your end @grafviktor?

Okay cool: so we have an PR open (#878) that overhauls input on Windows which will likely fix this. Would you mind checking on your end @grafviktor?

Unfortunately, the patch makes things even worse - with every N process which I run from my example app, readInputs steals N+1 symbols. Also it forwards the ending Enter('\r') key which I send to the running process back to the model.

I put a comment beneath Ayman's PR to keep him in the loop.