From 7cac56ca63531f81001aef73652dc9c2de9a2299 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 11 Jul 2024 10:11:10 -0300 Subject: [PATCH] feat: allow to suspend bubbletea programs closes #497 closes #1053 --- exec.go | 11 +++++++++++ tea.go | 17 +++++++++++++++++ tty_unix.go | 8 ++++++++ tty_windows.go | 4 ++++ 4 files changed, 40 insertions(+) diff --git a/exec.go b/exec.go index 7a14d2a778..a67aa90d82 100644 --- a/exec.go +++ b/exec.go @@ -98,6 +98,17 @@ func (c *osExecCommand) SetStderr(w io.Writer) { } } +func (p *Program) suspend() { + if err := p.ReleaseTerminal(); err != nil { + // If we can't release input, abort. + return + } + + suspendProcess() + + _ = p.RestoreTerminal() +} + // exec runs an ExecCommand and delivers the results to the program as a Msg. func (p *Program) exec(c ExecCommand, fn ExecCallback) { if err := p.ReleaseTerminal(); err != nil { diff --git a/tea.go b/tea.go index 7ded48b63d..93ab87c7dc 100644 --- a/tea.go +++ b/tea.go @@ -177,10 +177,22 @@ func Quit() Msg { return QuitMsg{} } +// Quit is a special command that tells the Bubble Tea program to suspend. +func Suspend() Msg { + return SuspendMsg{} +} + // QuitMsg signals that the program should quit. You can send a QuitMsg with // Quit. type QuitMsg struct{} +// SuspendMsg signals the program should suspend. +// This usually happens when ctrl+z is pressed on common programs, but since +// bubbletea puts the terminal in raw mode, we need to handle it in a +// per-program basis. +// You can send this message with Suspend. +type SuspendMsg struct{} + // NewProgram creates a new Program. func NewProgram(model Model, opts ...ProgramOption) *Program { p := &Program{ @@ -327,6 +339,11 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { case QuitMsg: return model, nil + case SuspendMsg: + if suspendSupported { + p.suspend() + } + case clearScreenMsg: p.renderer.clearScreen() diff --git a/tty_unix.go b/tty_unix.go index 362e585095..6f76e9732c 100644 --- a/tty_unix.go +++ b/tty_unix.go @@ -6,6 +6,7 @@ package tea import ( "fmt" "os" + "syscall" "github.com/charmbracelet/x/term" ) @@ -34,3 +35,10 @@ func openInputTTY() (*os.File, error) { } return f, nil } + +var suspendSupported = true + +// Send SIGTSTP to the entire process group. +func suspendProcess() { + _ = syscall.Kill(0, syscall.SIGTSTP) +} diff --git a/tty_windows.go b/tty_windows.go index de6a82f9c4..93505ebc8d 100644 --- a/tty_windows.go +++ b/tty_windows.go @@ -62,3 +62,7 @@ func openInputTTY() (*os.File, error) { } return f, nil } + +var suspendSupported = false + +func suspendProcess() {}