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 #223

Closed
Vinschni opened this issue Aug 21, 2022 · 5 comments
Closed

Viewport: line wrapping #223

Vinschni opened this issue Aug 21, 2022 · 5 comments

Comments

@Vinschni
Copy link

Vinschni commented Aug 21, 2022

I tried to track that down but I'm clueless what causes that.

On a terminal with not as wide as the printed string content the lines are not wrapped if useHighPerformanceRenderer = false.
When useHighPerformanceRenderer = true lines are wrapped.

How can i achieve wrapping with useHighPerformanceRenderer = false ?
(Background: I use viewport not for the entire screen but with bubbleboxer)

The following example allows reproduction and shows how lines are not wrapped. (Rebuild with useHighPerformanceRenderer = true to see wrapped lines)

package main

import (
	"fmt"
	"os"

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

const useHighPerformanceRenderer = false

type model struct {
	content  string
	viewport viewport.Model
}

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

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var (
		cmd  tea.Cmd
		cmds []tea.Cmd
	)

	switch msg := msg.(type) {
	case tea.KeyMsg:
		if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
			return m, tea.Quit
		}

	case tea.WindowSizeMsg:
		m.viewport = viewport.New(msg.Width, msg.Height)
		m.viewport.HighPerformanceRendering = useHighPerformanceRenderer
		m.viewport.SetContent(m.content)

		if useHighPerformanceRenderer {
			cmds = append(cmds, viewport.Sync(m.viewport))
		}
	}

	// Handle keyboard and mouse events in the viewport
	m.viewport, cmd = m.viewport.Update(msg)
	cmds = append(cmds, cmd)

	return m, tea.Batch(cmds...)
}

func (m model) View() string {
	return m.viewport.View()
}

func main() {
	// Load some text for our viewport
	content := `ImATextThatDoesNotGetWrappedWhenNoHighPerformanceRenderIsDoneImATextThatDoesNotGetWrappedWhenNoHighPerformanceRenderIsDoneImATextThatDoesNotGetWrappedWhenNoHighPerformanceRenderIsDone`
	p := tea.NewProgram(
		model{content: string(content)},
		tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
	)

	if err := p.Start(); err != nil {
		fmt.Println("could not run program:", err)
		os.Exit(1)
	}
}
@meowgorithm
Copy link
Member

meowgorithm commented Aug 22, 2022

Hi! While automatic line wrapping is a feature we're planning on adding, for now you should always perform line wrapping manually:

width := 80
wrapped := lipgloss.NewStyle().Width(width).Render(unwrapped)
viewport.SetContent(wrapped)

Remember that you can get the terminal dimensions with a tea.WindowSizeMsg.

Wrapping very, very long lines like the one in your example will need to be done totally manually until muesli/reflow#43 is fixed upstream.

@Vinschni
Copy link
Author

Hi,
this wrapping works great for text, did not know about that feature of lipgloss 👍

But as I'm using it with glamour and bubbleboxer when links are wrapped its broken.
image

I build a minimal example that shows it. Any tipp on how i could conter such behaviour?

package main

import (
	"github.com/charmbracelet/bubbles/viewport"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/glamour"
	"github.com/charmbracelet/lipgloss"
	boxer "github.com/treilik/bubbleboxer"
)

const (
	leftAddr  = "left"
	rightAddr = "right"
)

type model struct {
	tui   boxer.Boxer
	left  stringer
	right viewportModel
}

func (m model) Init() tea.Cmd { return nil }
func (m model) View() string  { return m.tui.View() }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var (
		cmd  tea.Cmd
		cmds []tea.Cmd
	)

	switch msg := msg.(type) {
	case tea.KeyMsg:
		if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
			return m, tea.Quit
		}

	case tea.WindowSizeMsg:
		m.tui.UpdateSize(msg)

	}

	// Handle keyboard and mouse events in the viewport
	//m.viewport, cmd = m.viewport.Update(msg)
	cmds = append(cmds, cmd)

	return m, tea.Batch(cmds...)
}

func main() {
	m := model{tui: boxer.Boxer{},
		left:  stringer(leftAddr),
		right: initViewport()}
	m.tui.LayoutTree = boxer.Node{

		Children: []boxer.Node{
			m.tui.CreateLeaf(leftAddr, &m.left),
			m.tui.CreateLeaf(rightAddr, &m.right),
		},
	}
	p := tea.NewProgram(m, tea.WithAltScreen())
	if err := p.Start(); err != nil {
		panic(err)
	}

}

// -- stringer Model
type stringer string

func (s stringer) String() string { return string(s) }

// satisfy the tea.Model interface
func (s *stringer) Init() tea.Cmd                           { return nil }
func (s *stringer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return s, nil }
func (s *stringer) View() string                            { return s.String() }

// -- viewportModel Model
type viewportModel struct {
	content  string
	viewport viewport.Model
}

func initViewport() viewportModel {
	// Load some text for our viewport
	content := `[mylink](https://ImALinkThatIsWrappedButFormattingIsInvalid.com/ImALinkThatIsWrappedButFormattingIsInvalid/ImALinkThatIsWrappedButFormattingIsInvalid)`
	return viewportModel{content: string(content)}
}

func (m *viewportModel) Init() tea.Cmd { return nil }
func (m *viewportModel) View() string  { return m.viewport.View() }
func (m *viewportModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var (
		cmd  tea.Cmd
		cmds []tea.Cmd
	)

	switch msg := msg.(type) {

	case tea.WindowSizeMsg:
		m.viewport = viewport.New(msg.Width, msg.Height)
		r, _ := glamour.NewTermRenderer(
			glamour.WithStandardStyle("dark"),
			glamour.WithWordWrap(m.viewport.Width),
		)

		out, _ := r.Render(m.content)
		m.viewport.SetContent(lipgloss.NewStyle().Width(msg.Width).Render(out))
	}

	m.viewport, cmd = m.viewport.Update(msg)
	cmds = append(cmds, cmd)

	return m, tea.Batch(cmds...)
}

@Vinschni
Copy link
Author

Vinschni commented Aug 22, 2022

@Vinschni Vinschni closed this as not planned Won't fix, can't repro, duplicate, stale Aug 26, 2022
@meowgorithm
Copy link
Member

This is a different issue, actually. Reopening so we can track it.

@meowgorithm meowgorithm reopened this Aug 26, 2022
@meowgorithm meowgorithm changed the title Viewport wrapping only works when HighPerformanceRenderer is used Viewport: automatic line wrapping Aug 26, 2022
@meowgorithm meowgorithm added the enhancement New feature or request label Aug 26, 2022
@meowgorithm
Copy link
Member

Actually, this is indeed a Glamour issue as you mentioned. Closing accordingly—thank you!

@meowgorithm meowgorithm changed the title Viewport: automatic line wrapping Viewport: line wrapping Aug 31, 2022
@meowgorithm meowgorithm removed the enhancement New feature or request label Aug 31, 2022
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

No branches or pull requests

2 participants