charmbracelet / bubbletea

A powerful little TUI framework 🏗
MIT License
27.14k stars 784 forks source link

when start a exec, the first key press always be ignored, it seems that I need one press to "active" the io #1116

Open rickywei opened 1 month ago

rickywei commented 1 month ago
package main

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

    tea "github.com/charmbracelet/bubbletea"
)

type editorFinishedMsg struct{ err error }

func openEditor() tea.Cmd {
    return tea.Exec(&echo{}, func(err error) tea.Msg { return err })

    editor := os.Getenv("EDITOR")
    if editor == "" {
        editor = "vim"
    }
    c := exec.Command(editor) //nolint:gosec
    return tea.ExecProcess(c, func(err error) tea.Msg {
        return editorFinishedMsg{err}
    })
}

type model struct {
    altscreenActive bool
    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 "a":
            m.altscreenActive = !m.altscreenActive
            cmd := tea.EnterAltScreen
            if !m.altscreenActive {
                cmd = tea.ExitAltScreen
            }
            return m, cmd
        case "e":
            // return m, tea.Exec(&echo{}, func(err error) tea.Msg { return err })
            return m, tea.Sequence(openEditor())
        case "ctrl+c", "q":
            return m, tea.Quit
        }
    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 "Press 'e' to open your EDITOR.\nPress 'a' to toggle the altscreen\nPress 'q' to quit.\n"
}

func main() {
    m := model{}
    if _, err := tea.NewProgram(m).Run(); err != nil {
        fmt.Println("Error running program:", err)
        os.Exit(1)
    }
}

type echo struct {
    stdin          io.Reader
    stdout, stderr io.Writer
}

func (e *echo) Run() error {
    fmt.Println(io.Copy(e.stdout, e.stdin))
    return nil
}
func (e *echo) SetStdin(r io.Reader) {
    if e.stdin == nil {
        e.stdin = r
    }
}
func (e *echo) SetStdout(w io.Writer) {
    if e.stdin == nil {

        e.stdout = w
    }
}
func (e *echo) SetStderr(w io.Writer) {
    if e.stdin == nil {
        e.stdout = w
    }

}
rickywei commented 1 month ago

also, it needs an extra key press after exec run exit to input

meowgorithm commented 1 month ago

Hey there! What are you trying to do in the above code? Typically you're going to want to use tea.ExecProcess as tea.Exec is for lower level work (like working with SSH).

Here's a diff that fixes the above code:

diff --git a/main.go b/main.go
index 5b6b2f5..1e4a82f 100644
--- a/main.go
+++ b/main.go
@@ -2,7 +2,6 @@ package main

 import (
    "fmt"
-   "io"
    "os"
    "os/exec"

@@ -12,8 +11,6 @@ import (
 type editorFinishedMsg struct{ err error }

 func openEditor() tea.Cmd {
-   return tea.Exec(&echo{}, func(err error) tea.Msg { return err })
-
    editor := os.Getenv("EDITOR")
    if editor == "" {
        editor = "vim"
@@ -73,30 +70,3 @@ func main() {
        os.Exit(1)
    }
 }
-
-type echo struct {
-   stdin          io.Reader
-   stdout, stderr io.Writer
-}
-
-func (e *echo) Run() error {
-   fmt.Println(io.Copy(e.stdout, e.stdin))
-   return nil
-}
-func (e *echo) SetStdin(r io.Reader) {
-   if e.stdin == nil {
-       e.stdin = r
-   }
-}
-func (e *echo) SetStdout(w io.Writer) {
-   if e.stdin == nil {
-
-       e.stdout = w
-   }
-}
-func (e *echo) SetStderr(w io.Writer) {
-   if e.stdin == nil {
-       e.stdout = w
-   }
-
-}
rickywei commented 1 month ago

@meowgorithm Thanks for your reply. And as you guessed, I am going to interactive with SSH. Since my code is a little long, so I post the code with some modification of example code. (The echo is replaced with SSH in my code). Let me simplify the code here. this example code can reproduce the problem too.

package main

import (
    "fmt"
    "io"
    "os"

    tea "github.com/charmbracelet/bubbletea"
)

type editorFinishedMsg struct{ err error }

type model struct {
    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, tea.Exec(&echo{}, func(err error) tea.Msg { return err })
            return m, tea.Exec(&echo{}, func(err error) tea.Msg { return err })
        case "ctrl+c", "q":
            return m, tea.Quit
        }
    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 "Press 'e' to echo"
}

func main() {
    m := model{}
    if _, err := tea.NewProgram(m).Run(); err != nil {
        fmt.Println("Error running program:", err)
        os.Exit(1)
    }
}

type echo struct {
    stdin          io.Reader
    stdout, stderr io.Writer
}

func (e *echo) Run() error {
    fmt.Println(io.Copy(e.stdout, e.stdin))

    return nil
}
func (e *echo) SetStdin(r io.Reader) {
    e.stdin = r
}
func (e *echo) SetStdout(w io.Writer) {
    e.stdout = w
}
func (e *echo) SetStderr(w io.Writer) {
    e.stdout = w
}

When you run it and press "e", your input should be echoed on screen. But The thing is, the first key you input is not echoed. For example, you only get bc when you input abc.

BenediktGerlach commented 5 days ago

Hi! On Windows I noticed a similar behaviour for reading user input after a tea.Program was run: The first key input is somehow ignored.

package main

import (
    "bufio"
    "fmt"
    tea "github.com/charmbracelet/bubbletea"
    "log"
    "os"
)

func main() {
    p := tea.NewProgram(model{})
    if _, err := p.Run(); err != nil {
        log.Fatal(err)
    }

    fmt.Println("input something")
    if _, err := bufio.NewReader(os.Stdin).ReadString('\n'); err != nil {
        log.Fatal(err)
    }
}

type model struct{}

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

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    var cmd tea.Cmd
    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.Type {
        case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
            return m, tea.Quit
        }
    }
    return m, cmd
}

func (m model) View() string {
    return fmt.Sprint("hi, press enter to quit")
}