mirror of
https://github.com/taigrr/bubbletea.git
synced 2026-04-02 02:59:09 -07:00
feat: print unmanaged output above the application (#249)
* merge Adjective-Object/tea_log_renderer into standard renderer * rename queuedMessages -> queuedMessageLines & break apart strings during message processing * delete cursorDownBy * += 1 -> ++ to make the linter happy * add skipLines[] tracking back to standard renderer, and add rename skippedLines local to jumpedLines to clarify they are separate comments * request repaint when a message is recieved * Convert Println and Printf to commands * Add package manager example demonstrating tea.Printf * Use Unix instead of UnixMicro for Go 1.13 support in CI * fix off by one in std renderer * add Printf/Println to tea.go * revert attempt at sequence compression + cursorUpBy Co-authored-by: Maxwell Huang-Hobbs <mahuangh@microsoft.com> Co-authored-by: Christian Rocha <christian@rocha.is>
This commit is contained in:
139
examples/package-manager/main.go
Normal file
139
examples/package-manager/main.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/progress"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type model struct {
|
||||
packages []string
|
||||
index int
|
||||
width int
|
||||
height int
|
||||
spinner spinner.Model
|
||||
progress progress.Model
|
||||
done bool
|
||||
}
|
||||
|
||||
var (
|
||||
currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
|
||||
subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("239"))
|
||||
doneStyle = lipgloss.NewStyle().Margin(1, 2)
|
||||
checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓")
|
||||
)
|
||||
|
||||
func newModel() model {
|
||||
p := progress.New(
|
||||
progress.WithDefaultGradient(),
|
||||
progress.WithWidth(40),
|
||||
progress.WithoutPercentage(),
|
||||
)
|
||||
s := spinner.New()
|
||||
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
|
||||
return model{
|
||||
packages: getPackages(),
|
||||
spinner: s,
|
||||
progress: p,
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return tea.Batch(downloadAndInstall(m.packages[m.index]), m.spinner.Tick)
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width, m.height = msg.Width, msg.Height
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "esc", "q":
|
||||
return m, tea.Quit
|
||||
}
|
||||
case installedPkgMsg:
|
||||
if m.index >= len(m.packages)-1 {
|
||||
// Everything's been installed. We're done!
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
progressCmd := m.progress.SetPercent(float64(m.index) / float64(len(m.packages)-1))
|
||||
|
||||
m.index++
|
||||
return m, tea.Batch(
|
||||
progressCmd,
|
||||
tea.Printf("%s %s", checkMark, m.packages[m.index]), // print success message above our program
|
||||
downloadAndInstall(m.packages[m.index]), // download the next package
|
||||
)
|
||||
case spinner.TickMsg:
|
||||
var cmd tea.Cmd
|
||||
m.spinner, cmd = m.spinner.Update(msg)
|
||||
return m, cmd
|
||||
case progress.FrameMsg:
|
||||
newModel, cmd := m.progress.Update(msg)
|
||||
if newModel, ok := newModel.(progress.Model); ok {
|
||||
m.progress = newModel
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
n := len(m.packages)
|
||||
w := lipgloss.Width(fmt.Sprintf("%d", n))
|
||||
|
||||
if m.done {
|
||||
return doneStyle.Render(fmt.Sprintf("Done! Installed %d packages.\n", n))
|
||||
}
|
||||
|
||||
pkgCount := fmt.Sprintf(" %*d/%*d", w, m.index, w, n-1)
|
||||
|
||||
spin := m.spinner.View() + " "
|
||||
prog := m.progress.View()
|
||||
cellsAvail := max(0, m.width-lipgloss.Width(spin+prog+pkgCount))
|
||||
|
||||
pkgName := currentPkgNameStyle.Render(m.packages[m.index])
|
||||
info := lipgloss.NewStyle().MaxWidth(cellsAvail).Render("Installing " + pkgName)
|
||||
|
||||
cellsRemaining := max(0, m.width-lipgloss.Width(spin+info+prog+pkgCount))
|
||||
gap := strings.Repeat(" ", cellsRemaining)
|
||||
|
||||
return spin + info + gap + prog + pkgCount
|
||||
}
|
||||
|
||||
type installedPkgMsg string
|
||||
|
||||
func downloadAndInstall(pkg string) tea.Cmd {
|
||||
// This is where you'd do i/o stuff to download and install packages. In
|
||||
// our case we're just pausing for a moment to simulate the process.
|
||||
d := time.Millisecond * time.Duration(rand.Intn(500))
|
||||
return tea.Tick(d, func(t time.Time) tea.Msg {
|
||||
return installedPkgMsg(pkg)
|
||||
})
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
if err := tea.NewProgram(newModel()).Start(); err != nil {
|
||||
fmt.Println("Error running program:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
52
examples/package-manager/packages.go
Normal file
52
examples/package-manager/packages.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
var packages = []string{
|
||||
"vegeutils",
|
||||
"libgardening",
|
||||
"currykit",
|
||||
"spicerack",
|
||||
"fullenglish",
|
||||
"eggy",
|
||||
"bad-kitty",
|
||||
"chai",
|
||||
"hojicha",
|
||||
"libtacos",
|
||||
"babys-monads",
|
||||
"libpurring",
|
||||
"currywurst-devel",
|
||||
"xmodmeow",
|
||||
"licorice-utils",
|
||||
"cashew-apple",
|
||||
"rock-lobster",
|
||||
"standmixer",
|
||||
"coffee-CUPS",
|
||||
"libesszet",
|
||||
"zeichenorientierte-benutzerschnittstellen",
|
||||
"schnurrkit",
|
||||
"old-socks-devel",
|
||||
"jalapeño",
|
||||
"molasses-utils",
|
||||
"xkohlrabi",
|
||||
"party-gherkin",
|
||||
"snow-peas",
|
||||
"libyuzu",
|
||||
}
|
||||
|
||||
func getPackages() []string {
|
||||
pkgs := packages
|
||||
copy(pkgs, packages)
|
||||
|
||||
rand.Shuffle(len(pkgs), func(i, j int) {
|
||||
pkgs[i], pkgs[j] = pkgs[j], pkgs[i]
|
||||
})
|
||||
|
||||
for k := range pkgs {
|
||||
pkgs[k] += fmt.Sprintf("-%d.%d.%d", rand.Intn(10), rand.Intn(10), rand.Intn(10))
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
Reference in New Issue
Block a user