charmbracelet / bubbletea

A powerful little TUI framework 🏗
MIT License
26.57k stars 767 forks source link

Resizing truncates on terminal window's original size #658

Closed Zebbeni closed 1 year ago

Zebbeni commented 1 year ago

I'm doing a simple experiment to poll the terminal for size changes and update a bordered viewport to span the full window. (It's not an ideal solution but I can't rely on WindowSizeMsg events on Windows.) In some cases think I'm experiencing https://github.com/charmbracelet/bubbletea/issues/573 but it tends to correct itself with subsequent resizes. The more consistent problem I'm finding is that the viewport gets truncated on the width the terminal window had at the time the program launched, and its y offset seems to get messed up if I enlarge the terminal window beyond its original height. I don't see the style MaxWidth or MaxHeight being set to anything unexpected so I wonder if this is a bug? Or is there something else I'm missing?

Viewport_Resize

package main

import (
    "fmt"
    "os"
    "time"

    "golang.org/x/term"

    "github.com/charmbracelet/bubbles/viewport"
    tea "github.com/charmbracelet/bubbletea"
    "github.com/charmbracelet/lipgloss"
)

var borderStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder())
var textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("45"))

type tickMsg int

type model struct {
    w, h int
    vp   viewport.Model
}

func tick() tea.Msg {
    time.Sleep(time.Second / 4)
    return tickMsg(1)
}

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

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg.(type) {
    case tickMsg:
        w, h, _ := term.GetSize(int(os.Stdout.Fd()))
        if w != m.w || h != m.h {
            m.updateSize(w, h)
        }
        return m, tick
    }
    return m, nil
}

func (m *model) View() string {
    return m.vp.View()
}

func (m *model) updateSize(w, h int) {
    m.w = w
    m.h = h

    m.vp = viewport.New(m.w, m.h)
    m.vp.Style = borderStyle

    text := fmt.Sprintf("Size: %d, %d", m.w, m.h)
    rendered := textStyle.Copy().Width(m.w).Height(m.h).Render(text)
    m.vp.SetContent(rendered)
}

func main() {
    m := &model{w: 1, h: 1, vp: viewport.New(1, 1)}
    p := tea.NewProgram(m)
    if _, err := p.Run(); err != nil {
        fmt.Print(err)
    }
}
Zebbeni commented 1 year ago

Okay, it looks like batching a WindowSizeMsg Cmd with my tick Cmd solves my problem. It'd be great if bubbletea comes up with a way to avoid polling for window resizes on Windows machines in the future but I'm happy with this for now.

previous:

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg.(type) {
    case tickMsg:
        w, h, _ := term.GetSize(int(os.Stdout.Fd()))
        if w != m.w || h != m.h {
            m.updateSize(w, h)
        }
        return m, tick
    }
    return m, nil
}

solution:

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg.(type) {
    case tickMsg:
        w, h, _ := term.GetSize(int(os.Stdout.Fd()))
        if w != m.w || h != m.h {
            m.updateSize(w, h)
        }
        return m, tea.Batch(tick, func() tea.Msg { return tea.WindowSizeMsg{Width: w, Height: h} })
    }
    return m, nil
}
bashbunni commented 1 year ago

What issues are you having with the tea.WindowSizeMsg on Windows? It should work cross platform. The resizes also trigger the tea.WindowSizeMsg, you shouldn't be having to trigger it manually