Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

viewport line wrapping bug #644

Open
Kyren223 opened this issue Oct 21, 2024 · 3 comments · May be fixed by #578
Open

viewport line wrapping bug #644

Kyren223 opened this issue Oct 21, 2024 · 3 comments · May be fixed by #578

Comments

@Kyren223
Copy link

Describe the bug
Multi-line (wrapped) text causes the viewport to not behave correctly, when using
viewport.Model#GotoBottom() and updating the height of it (by resizing the terminal),
Instead of hiding the top messages, it behaves weirdly and hides the bottom message.

Setup
Please complete the following information along with version numbers, if applicable.

  • OS Tumbleweed (through WSL2)
  • Shell zsh
  • Terminal Emulator Windows Terminal
  • Terminal Multiplexer tmux 3.4

To Reproduce
Steps to reproduce the behavior:

  1. Run the provided code
  2. Type a long message that doesn't fit in 1 line
  3. Type a few short messages that do fit in 1 line
  4. Resize the terminal height so the textarea + all the messages will fill the entire space
  5. Shrink the height by 1 char each time

Source Code
A slightly modified version of the example "chat" code

package main

import (
	"fmt"
	"log"
	"strings"

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

func main() {
	p := tea.NewProgram(initialModel(), tea.WithAltScreen())

	if _, err := p.Run(); err != nil {
		log.Fatal(err)
	}
}

type (
	errMsg error
)

type model struct {
	viewport    viewport.Model
	messages    []string
	textarea    textarea.Model
	senderStyle lipgloss.Style
	err         error
}

func initialModel() model {
	ta := textarea.New()
	ta.Placeholder = "Send a message..."
	ta.Focus()

	ta.Prompt = "┃ "
	ta.CharLimit = 280

	ta.SetWidth(30)
	ta.SetHeight(3)

	// Remove cursor line styling
	ta.FocusedStyle.CursorLine = lipgloss.NewStyle()

	ta.ShowLineNumbers = false

	vp := viewport.New(30, 5)
	vp.SetContent(`Welcome to the chat room!
Type a message and press Enter to send.`)

	ta.KeyMap.InsertNewline.SetEnabled(false)

	return model{
		textarea:    ta,
		messages:    []string{},
		viewport:    vp,
		senderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("5")),
		err:         nil,
	}
}

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

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var (
		tiCmd tea.Cmd
		vpCmd tea.Cmd
	)

	m.textarea, tiCmd = m.textarea.Update(msg)
	m.viewport, vpCmd = m.viewport.Update(msg)

	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.viewport.Width = msg.Width
		m.viewport.Height = msg.Height - m.textarea.Height()
		m.viewport.GotoBottom()

	case tea.KeyMsg:
		switch msg.Type {
		case tea.KeyCtrlC, tea.KeyEsc:
			fmt.Println(m.textarea.Value())
			return m, tea.Quit
		case tea.KeyEnter:
			m.messages = append(m.messages, m.senderStyle.Render("You: ")+m.textarea.Value())
			m.viewport.SetContent(strings.Join(m.messages, "\n"))
			m.textarea.Reset()
			m.viewport.GotoBottom()
		}

	// We handle errors just like any other message
	case errMsg:
		m.err = msg
		return m, nil
	}

	return m, tea.Batch(tiCmd, vpCmd)
}

func (m model) View() string {
	return fmt.Sprintf(
		"%s\n%s",
		m.viewport.View(),
		m.textarea.View(),
	) + ""
}

Expected behavior

  • First shrink hides the top line (first line of the long message)
  • Second shrink hides the top line (second line of the long message)
  • Third shrink hides the top line (of whatever the next line is)

Actual behavior

  • First shrink hides the bottom line
  • Second shrink hides the top line (first line of the long message)
  • Third shrink hides the top line (second line of the long message) AND re-shows the missing bottom line

Screenshots
Normal
image

After shrinking once (bottom line missing, both 1st and 2nd line of the top message exist)
The expected behaviour would be to either

  1. Hide the first line of the top message (preferred)
  2. Hide both the first and second line of the top message
    image

After shrinking again (bottom line reappears, both the 1st and 2nd line of the top message disappear)
image

@seeseemelk
Copy link

What a coincindence. I discovered this exact same issue while trying to diagnose a line wrapping issue in the Select widget from huh. (charmbracelet/huh#429)

@meowgorithm meowgorithm transferred this issue from charmbracelet/bubbletea Oct 22, 2024
@Kyren223
Copy link
Author

Glad to know this is actually an issue and not just a skill issue on my part, I discovered it while working on a TUI for messaging, so this is a core functionality of what I am making.
Hopefully this gets fixed fast

@meowgorithm
Copy link
Member

Hi! This is not entirely a bug: historically we've left wrapping as an exercise for the user. If you need to solve this now you can simply:

wrapped := lipgloss.NewStyle().Width(yourWidth).Render(yourContent)
viewport.SetContent(wrapped)

That said, we're tracking this in #578.

@meowgorithm meowgorithm linked a pull request Nov 4, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants