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

The visible area in viewport may seem incorrect when content exceed the viewport.Width #1017

Open
MTTMTTMTT opened this issue May 13, 2024 · 10 comments · May be fixed by charmbracelet/bubbles#530

Comments

@MTTMTTMTT
Copy link

Describe the bug

When I am using a viewport (fixed width and height) and set content with a very long text, the visible area may be incorrect because of multilines.
The viewport.GotoBottom() seems ineffective.

Setup

  • OS: macOS
  • Shell: zsh
  • Terminal Emulator: none
  • Terminal Multiplexer: none

To Reproduce

  1. go run main.go
  2. setting the viewarea viewport.New(30, 5)
  3. sending very long text longer than the viewport area, it will cause changing into a new line when hit the end.
  4. sending long text utill the viewport area is full
  5. See error

Source Code

Using the example https://github.com/charmbracelet/bubbletea/blob/master/examples/chat/main.go

package main

// A simple program demonstrating the text area component from the Bubbles
// component library.

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())

	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.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\n%s",
		m.viewport.View(),
		m.textarea.View(),
	) + "\n\n"
}

Expected behavior

When inputting short text.

this is a short msg1
this is a short msg2
this is a short msg3
this is a short msg4
this is a short msg5
this is a short msg6
this is a short msg7
image

When inputting long text.

this is a 35 characters long msg111
this is a 35 characters long msg222
this is a 35 characters long msg333
this is a 35 characters long msg444
this is a 35 characters long msg555
this is a 35 characters long msg666
this is a 35 characters long msg777
image
@MTTMTTMTT
Copy link
Author

I am using this lib to create a terminal application.
This is the most elegant lib I have ever used in golang and I LOVE it.

But there seems to be some problem when rendering a long text in a small viewport area.
I am receiving messages from a websocket server.

Everytime when server informs me with a new message, I expect to append the message to the new line.
And the viewport will always display the most recent message (last line).
So I expect this will work:

	m.viewport.SetContent(strings.Join(m.messages, "\n"))
	m.viewport.GotoBottom()

When receiving short messages, this will work as the example shows.
But when it comes to long messages, now I have two choices.

  1. Cut my messages into 30 width characters slices, ensuring every message doesn't hit the end.
  2. Retain only last 3 messages (not very ideal solution)

I'm wondering if bubbletea provides a decent method to handle with the newline, or is this a bug related to the viewport?

@MTTMTTMTT
Copy link
Author

Any updates on this issue?
Happy to provide my code if needed.

@pikomonde
Copy link

Hi @MTTMTTMTT , I would love to work on this, let me reproduce it in my local.

@MTTMTTMTT
Copy link
Author

Thanks for your reply! I will wait for your feedback.

@pikomonde
Copy link

Hi @MTTMTTMTT , I think it is expected to wrap around, because the message is more than 30 characters. But I'm agree that it's weird, that the message ends with the msg444. Menawhile the msg555, msg666, and msg777` are deep buried under.

Btw, your solution are these to options right?:

  1. Cut my messages into 30 width characters slices, ensuring every message doesn't hit the end.
You: this is a 35 characters  
You: this is a 35 characters  
You: this is a 35 characters  
You: this is a 35 characters  
You: this is a 35 characters  
  1. Retain only last 3 messages (not very ideal solution)
long msg555                   
You: this is a 35 characters  
long msg666                   
You: this is a 35 characters  
long msg777                   

If so, then I like solution no 2 more, because in solution no 1, we cannot read the end of the sentence (get trimmed because it's the 31st character and more)

Wdyt?
cc: @meowgorithm

@pikomonde
Copy link

Btw, take a look at this: charmbracelet/bubbles#509

@MTTMTTMTT
Copy link
Author

MTTMTTMTT commented May 29, 2024

Yes, the msg555, msg666, and msg777 is buried under. This is the problem I encounted. @pikomonde

For my solution 1, I mean to cut the string into []string slices.
Every string in the slice is 30 characters in length and ensuring not to cause soft wrap.
This is my code.
image
image

package main

import (
	"fmt"
)

func main() {
	fmt.Println(SoftWrap(5, "aaaaabbbbbcccccddddd"))
}

func SoftWrap(width int, msg string) []string {
	if len(msg) <= width {
		return []string{msg}
	}
	result := make([]string, 0)
	result = append(result, msg[:width])
	result = append(result, SoftWrap(width, msg[width:])...)
	return result
}

This could help but make me headache with another problem, the Chinese characters.
The Chinese characters take more than 1 place in the string array, it will casue the wrong result in spliting.

But this doesn't matter, let's just ignore the Chinese characters problem now.

@MTTMTTMTT
Copy link
Author

I took a look at the charmbracelet/bubbles#509.
It solve the problem of soft wrap by scorlling the view screen, but I don't like the solution.
It will bring much more user inputs.

I also find my issue same to charmbracelet/bubbles#479
In charmbracelet/bubbles#479, I tried to use the high performance rendering, but this doesn't work.

@pikomonde
Copy link

Oh, got it, so you want to make the software to new line.

To summarize:

The input:

this is a 35 characters long msg111
this is a 35 characters long msg222
this is a 35 characters long msg333
this is a 35 characters long msg444
this is a 35 characters long msg555
this is a 35 characters long msg666
this is a 35 characters long msg777

Current Implementation:

[]string{
  "this is a 35 characters long msg111",
  "this is a 35 characters long msg222",
  "this is a 35 characters long msg333",
  "this is a 35 characters long msg444",
  "this is a 35 characters long msg555",
  "this is a 35 characters long msg666",
  "this is a 35 characters long msg777",
}

Expected Implementation:

[]string{
  "this is a 35 characters ",
  "long msg111",
  "this is a 35 characters ",
  "long msg222",
  "this is a 35 characters ",
  "long msg333",
  "this is a 35 characters ",
  "long msg444",
  "this is a 35 characters ",
  "long msg555",
  "this is a 35 characters ",
  "long msg666",
  "this is a 35 characters ",
  "long msg777",
}

CMIIW

@pikomonde
Copy link

@MTTMTTMTT please check, I've created the PR, I think it should solve the Chinese characters problem. I haven't try it for that case

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants