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

bug: Console freezes with certain lipgloss.place usage #17

Closed
2 tasks done
BigJk opened this issue Apr 10, 2023 · 3 comments
Closed
2 tasks done

bug: Console freezes with certain lipgloss.place usage #17

BigJk opened this issue Apr 10, 2023 · 3 comments
Labels
bug Something isn't working

Comments

@BigJk
Copy link

BigJk commented Apr 10, 2023

🌧 Describe the problem

I'm currently using bubbletea and bubblezone to develop a small console game pet-project. While developing I encountered a strange bug.

When I use lipgloss.place in a certain way with zone.mark the whole program and console freezes after responding correctly a few times. Even when I kill the process the console will be stuck without further input being possible. This happens with iterm2 and the default macos terminal. I have to create a new console session to get the console working again.

⛅ Expected behavior

I expected the program to keep processing mouse inputs and not freeze.

🔄 Minimal reproduction

I threw together a minimal example without any of my game logic that keeps freezing on my system:

package main

import (
	"fmt"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	zone "github.com/lrstanley/bubblezone"
	"github.com/muesli/reflow/wordwrap"
	"os"
	"strings"
)

const TriggerBug = true

type TestModel struct {
	size         tea.WindowSizeMsg
	selectedCard int
	numberCards  int
}

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

func (m TestModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		if msg.String() == "ctrl+c" {
			return m, tea.Quit
		}
	case tea.WindowSizeMsg:
		m.size = msg
	case tea.MouseMsg:
		if msg.Type == tea.MouseLeft || msg.Type == tea.MouseMotion {
			// Check zones to set selected card
			for i := 0; i < m.numberCards; i++ {
				if zone.Get(fmt.Sprintf("%s%d", "card_", i)).InBounds(msg) {
					m.selectedCard = i
				}
			}
		}
	}

	return m, nil
}

func (m TestModel) View() string {
	cardStyle := lipgloss.NewStyle().Width(30).Padding(1, 2).Margin(0, 2)

	var cardBoxes []string
	for i := 0; i < m.numberCards; i++ {
		selected := i == m.selectedCard

		style := cardStyle.
			Border(lipgloss.NormalBorder(), selected, false, false, false).
			BorderBackground(lipgloss.Color("#cccccc")).
			Background(lipgloss.Color("#cccccc")).
			BorderForeground(lipgloss.Color("#ffffff")).
			Foreground(lipgloss.Color("#ffffff"))

		// If the card is selected we give it a bit more height
		if selected {
			cardBoxes = append(cardBoxes,
				style.
					Height(min(m.size.Height-1, m.size.Height/2+5)).
					Render(wordwrap.String(fmt.Sprintf("%s\n\n%s\n\n%s", strings.Repeat("•", 3), "Example Card", "Hello World... Hello World... Hello World.."), 20)),
			)
			continue
		}

		// Non-selected card style
		cardBoxes = append(cardBoxes,
			style.
				Height(m.size.Height/2).
				Render(wordwrap.String(fmt.Sprintf("%s\n\n%s\n\n%s", strings.Repeat("•", 3), "Example Card", "Hello World... Hello World... Hello World.."), 20)),
		)
	}

	for i := range cardBoxes {
		cardBoxes[i] = zone.Mark(fmt.Sprintf("%s%d", "card_", i), cardBoxes[i])
	}

	// EDIT: also breaks after a while
	if !TriggerBug {
		return zone.Scan(lipgloss.JoinHorizontal(lipgloss.Bottom, cardBoxes...))
	}

	// Freeze:
	return zone.Scan(lipgloss.Place(m.size.Width, m.size.Height, lipgloss.Center, lipgloss.Bottom, lipgloss.JoinHorizontal(lipgloss.Bottom, cardBoxes...)))
}

func main() {
	zone.NewGlobal()

	p := tea.NewProgram(TestModel{
		numberCards: 3,
	}, tea.WithAltScreen(), tea.WithMouseAllMotion())
	if _, err := p.Run(); err != nil {
		fmt.Printf("Alas, there's been an error: %v", err)
		os.Exit(1)
	}
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

💠 Version: bubblezone

v0.0.0-20230303230241-08f906ff62a9

🖥 Version: Operating system

macos

⚙ Additional context

Video showing the working lipgloss.JoinHorizontal vs the freezing lipgloss.Place. When the second terminal tab stops responding it's completely frozen. ctrl+c does nothing and I manually need to restart it.

https://streamable.com/65dklc

EDIT: lipgloss.JoinHorizontal also breaks for me after a while

🤝 Requirements

@BigJk BigJk added the bug Something isn't working label Apr 10, 2023
@lrstanley
Copy link
Owner

From what I've found so far, I'm unable to replicate using powershell (windows) or WSL2 with Windows Terminal (linux) -- both work fine for at least a minute or two. I did a test with Alacritty (via WSL), and I do get an error:

The app opens like normal, however, after moving the mouse for a second or two, it exits with the above output.

The above may be the same issue you are receiving, but iTerm2 might not be handling the exit correctly. The above issue also doesn't look related to bubblezone, but hard to say for sure.

I also see something similar here: charmbracelet/bubbletea#664

I will keep poking the above error, though not 100% sure it's the same issue you're having. It's unlikely an issue with BubbleZone, however, as we don't inject any special characters that aren't already used by bubbletea/lipgloss itself.

Would you be able to provide your go.mod file? Would like to get the same versions of the dependencies to see if I didn't just pull in newer versions.

@lrstanley
Copy link
Owner

Confirmed my above thoughts. I removed BubbleZone completely from the example, and I am still experiencing the issue. Furthermore, Alacritty does seem to have the same issue sometimes as you are seeing in iTerm2, though it's not consistent. Sometimes it returns immediately, sometimes it will hang/freeze and do nothing, and sometimes it will work for about 5 seconds then exit.

Code that doesn't include BubbleZone, but sees what I think is the same issue:

package main

import (
	"fmt"
	"os"
	"strings"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/muesli/reflow/wordwrap"
)

type TestModel struct {
	size         tea.WindowSizeMsg
	selectedCard int
	numberCards  int

	mouseEvents int
}

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

func (m TestModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		if msg.String() == "ctrl+c" {
			return m, tea.Quit
		}
	case tea.WindowSizeMsg:
		m.size = msg
	case tea.MouseMsg:
		m.mouseEvents++
	}

	return m, nil
}

func (m TestModel) View() string {
	cardStyle := lipgloss.NewStyle().Width(30).Padding(1, 2).Margin(0, 2)

	var cardBoxes []string
	for i := 0; i < m.numberCards; i++ {
		selected := i == m.selectedCard

		style := cardStyle.
			Border(lipgloss.NormalBorder(), selected, false, false, false).
			BorderBackground(lipgloss.Color("#cccccc")).
			Background(lipgloss.Color("#cccccc")).
			BorderForeground(lipgloss.Color("#ffffff")).
			Foreground(lipgloss.Color("#ffffff"))

		// If the card is selected we give it a bit more height
		if selected {
			cardBoxes = append(cardBoxes,
				style.
					Height(min(m.size.Height-1, m.size.Height/2+5)).
					Render(wordwrap.String(fmt.Sprintf(
						"%s\n\nmouse events: %d\n%s\n\n%s",
						strings.Repeat("•", 3),
						m.mouseEvents,
						"Example Card",
						"Hello World... Hello World... Hello World..",
					), 19)),
			)
			continue
		}

		// Non-selected card style
		cardBoxes = append(cardBoxes,
			style.
				Height(m.size.Height/2).
				Render(wordwrap.String(fmt.Sprintf(
					"%s\n\n%s\n\n%s",
					strings.Repeat("•", 3),
					"Example Card",
					"Hello World... Hello World... Hello World..",
				), 20)),
		)
	}

	return lipgloss.Place(m.size.Width, m.size.Height, lipgloss.Center, lipgloss.Bottom, lipgloss.JoinHorizontal(lipgloss.Bottom, cardBoxes...))
}

func main() {
	p := tea.NewProgram(TestModel{
		numberCards: 3,
	}, tea.WithAltScreen(), tea.WithMouseAllMotion())
	if _, err := p.Run(); err != nil {
		fmt.Printf("Alas, there's been an error: %v", err)
		os.Exit(1)
	}
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

Above code should include the number of mouse events as a counter on one of the cards.

I can still test the same versions of the dependencies if given the go.mod, however.

@BigJk
Copy link
Author

BigJk commented Apr 11, 2023

Thanks for the quick response and investigation!

From your findings I tried to rule out Bubblezone and you are probably right. It doesn't seem to be related to Bubblezone but to the usage of tea.WithMouseAllMotion(). With it enabled iterm freezes if I move my mouse often enough in the window. Alternatively after minimising and re-opening the running program iterm seems to freeze up consistently. Same happens with the basic macos terminal. tea.WithMouseCellMotion() doesn't trigger that problem.

I will head over to the Bubbltea issues now: charmbracelet/bubbletea#668

@lrstanley lrstanley closed this as not planned Won't fix, can't repro, duplicate, stale Apr 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants