Skip to content

Commit

Permalink
add: Exec, ReleaseTerminal and RestoreTerminal to re-use input and te…
Browse files Browse the repository at this point in the history
…rminal (#237)

* add: program.ReleaseTerminal and RestoreTerminal to re-use input & terminal

* chore(examples): add altscreen toggling to exec demo

* chore: put low-level altscreen stuff alongside other screen funcs

* docs: edit GoDocs for ReleaseTerminal and RestoreTerminal

* feat(renderer): add internal Msg renderMsg to immediately repaint

* fix: repaint instantly on RestoreTerminal

* fix: restore the altscreen state when restoring the terminal

* feat: implement Cmd-based API for blocking *exec.Cmds

* feat: allow Exec to return custom messages

* feat: allow Exec to be run without a callback

* fix: separate parameters for exec.Command examples

* fix: error message would get printed over by prompt in exec example

* fix: ignore signals while child process is running

* feat: allow to execute other things besides exec.Commands (#280)

* feat: allow to execute other things besides exec.Commands.

* fix: lint issues

* fix: renames, examples

* fix: callback type should be exported

* docs(exce): tiny ExecCommand doc comment correction

* chore(exec): break out Cmd for clarity's sake in example

* fix(exec): give the terminal a moment to catch up if exiting altscreen

* docs(exec): tidy up doc comments

* chore(exec): disambiguate methods for restoring the terminal state vs input

Co-authored-by: Christian Rocha <christian@rocha.is>
Co-authored-by: Carlos A Becker <caarlos0@gmail.com>
  • Loading branch information
3 people committed Apr 12, 2022
1 parent ecba57e commit 3795c03
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 62 deletions.
67 changes: 67 additions & 0 deletions examples/exec/main.go
@@ -0,0 +1,67 @@
package main

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

tea "github.com/charmbracelet/bubbletea"
)

type editorFinishedMsg struct{ err error }

func openEditor() tea.Cmd {
c := exec.Command(os.Getenv("EDITOR")) //nolint:gosec
return tea.Exec(tea.WrapExecCommand(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, 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).Start(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
8 changes: 7 additions & 1 deletion renderer.go
Expand Up @@ -15,7 +15,10 @@ type renderer interface {
// output at its discretion.
write(string)

// Request a full re-render.
// Request a full re-render. Note that this will not trigger a render
// immediately. Rather, this method causes the next render to be a full
// repaint. Because of this, it's safe to call this method multiple times
// in succession.
repaint()

// Whether or not the alternate screen buffer is enabled.
Expand All @@ -25,3 +28,6 @@ type renderer interface {
// does not actually toggle the alternate screen buffer.
setAltScreen(bool)
}

// repaintMsg forces a full repaint.
type repaintMsg struct{}
9 changes: 9 additions & 0 deletions screen.go
Expand Up @@ -42,3 +42,12 @@ func changeScrollingRegion(w io.Writer, top, bottom int) {
func cursorBack(w io.Writer, n int) {
fmt.Fprintf(w, te.CSI+te.CursorBackSeq, n)
}

func enterAltScreen(w io.Writer) {
fmt.Fprintf(w, te.CSI+te.AltScreenSeq)
moveCursor(w, 0, 0)
}

func exitAltScreen(w io.Writer) {
fmt.Fprintf(w, te.CSI+te.ExitAltScreenSeq)
}
5 changes: 5 additions & 0 deletions standard_renderer.go
Expand Up @@ -345,6 +345,11 @@ func (r *standardRenderer) insertBottom(lines []string, topBoundary, bottomBound
// handleMessages handles internal messages for the renderer.
func (r *standardRenderer) handleMessages(msg Msg) {
switch msg := msg.(type) {
case repaintMsg:
// Force a repaint by clearing the render cache as we slide into a
// render.
r.repaint()

case WindowSizeMsg:
r.mtx.Lock()
r.width = msg.Width
Expand Down

0 comments on commit 3795c03

Please sign in to comment.