mukan-ignite/ignite/pkg/cliui/model/events.go
Mukan Erkin Törük 26b204bd04
Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

255 lines
6 KiB
Go

package cliuimodel
import (
"container/list"
"fmt"
"strings"
"time"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/ignite/cli/v29/ignite/pkg/cliui/colors"
"github.com/ignite/cli/v29/ignite/pkg/cliui/icons"
"github.com/ignite/cli/v29/ignite/pkg/events"
)
// EventMsg defines a message for events.
type EventMsg struct {
events.Event
Start time.Time
Duration time.Duration
}
// NewStatusEvents returns a new events model.
func NewStatusEvents(bus events.Provider, maxHistory int) StatusEvents {
return StatusEvents{
events: list.New(),
spinner: NewSpinner(),
maxHistory: maxHistory,
bus: bus,
}
}
// StatusEvents defines a model for status events.
// The model renders a view that can be divided in three sections.
// The first one displays the "static" events which are the ones
// that are not status events. The second section displays a spinner
// with the status event that is in progress, and the third one
// displays a list with the past status events.
type StatusEvents struct {
static []events.Event
events *list.List
spinner spinner.Model
maxHistory int
bus events.Provider
}
func (m *StatusEvents) ClearEvents() {
m.static = nil
m.events.Init()
}
func (m StatusEvents) Wait() tea.Cmd {
return tea.Batch(m.spinner.Tick, m.WaitEvent)
}
func (m StatusEvents) WaitEvent() tea.Msg {
e := <-m.bus.Events()
return EventMsg{
Start: time.Now(),
Event: e,
}
}
func (m StatusEvents) Update(msg tea.Msg) (StatusEvents, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case EventMsg:
if msg.InProgress() {
// Save the duration of the current ongoing event before setting a new one
if e := m.events.Front(); e != nil {
evt := e.Value.(EventMsg)
evt.Duration = time.Since(evt.Start)
e.Value = evt
}
// Add the event to the queue
m.events.PushFront(msg)
// Only show a reduced history of events
if m.events.Len() > m.maxHistory {
m.events.Remove(m.events.Back())
}
} else {
// Events that have no progress status are considered static,
// so they will be printed without the spinner and won't be
// removed from the output until the view is removed.
m.static = append(m.static, msg.Event)
}
// Return a command to wait for the next event
cmd = m.Wait()
default:
// Update the spinner state and get a new tick command
m.spinner, cmd = m.spinner.Update(msg)
}
return m, cmd
}
func (m StatusEvents) View() string {
var view strings.Builder
// Display static events first
for _, evt := range m.static {
view.WriteString(evt.String())
if !strings.HasSuffix(evt.Message, "\n") {
view.WriteRune('\n')
}
}
// Make sure there is a line between the static and status events
if m.static != nil && m.events.Len() > 0 {
view.WriteRune('\n')
}
// Display status events
if m.events.Len() > 0 {
for e := m.events.Front(); e != nil; e = e.Next() {
evt := e.Value.(EventMsg)
// The first event is displayed using a spinner
if e.Prev() == nil {
fmt.Fprintf(&view, "%s%s\n", m.spinner.View(), evt)
if e.Next() != nil {
view.WriteRune('\n')
}
continue
}
// Display finished status event
d := evt.Duration.Round(time.Second)
s := strings.TrimSuffix(evt.String(), "...")
fmt.Fprintf(&view, "%s %s %s\n", icons.OK, s, colors.Faint(d.String()))
}
}
return view.String()
}
// NewEvents returns a new events model.
func NewEvents(bus events.Provider) Events {
return Events{
events: list.New(),
bus: bus,
spinner: NewSpinner(),
}
}
// Events defines a model for events.
// The model renders a view that prints all received events one after
// the other. Status events are displayed with a spinner and removed
// from the list once they finish.
type Events struct {
events *list.List
bus events.Provider
spinner spinner.Model
}
func (m *Events) ClearEvents() {
m.events.Init()
}
func (m Events) Wait() tea.Cmd {
// Check if the last added event is a status event
// and if so make sure that the spinner is updated.
if e := m.events.Back(); e != nil {
if evt := e.Value.(events.Event); evt.InProgress() {
return tea.Batch(m.spinner.Tick, m.WaitEvent)
}
}
// By default, just wait until the next event is received
return m.WaitEvent
}
func (m Events) WaitEvent() tea.Msg {
e := <-m.bus.Events()
return EventMsg{
Event: e,
Start: time.Now(),
}
}
func (m Events) Update(msg tea.Msg) (Events, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case EventMsg:
// Remove the last event if is a status one.
// Status events must always be the last event in the list so the
// spinner is displayed at the bottom and not in between events.
// They are removed when another status event is received.
if e := m.events.Back(); e != nil {
if evt := e.Value.(events.Event); evt.InProgress() {
m.events.Remove(e)
}
}
// Append event at the end of the list
m.events.PushBack(msg.Event)
// Return a command to wait for the next event
cmd = m.Wait()
default:
// Update the spinner state and get a new tick command
m.spinner, cmd = m.spinner.Update(msg)
}
return m, cmd
}
func (m Events) View() string {
var (
view strings.Builder
group string
)
// Display the list of events
for e := m.events.Front(); e != nil; e = e.Next() {
evt := e.Value.(events.Event)
// Add an empty line when the event group changes but omit it
// for the first event to avoid adding an initial empty line.
if group != evt.Group && e.Prev() != nil {
// Update the group being displayed
group = evt.Group
view.WriteRune('\n')
}
if e.Next() == nil && evt.InProgress() {
// When the event is the last one and is a status event display a spinner...
fmt.Fprintf(&view, "\n%s%s", m.spinner.View(), evt)
} else {
// Otherwise display the event without the spinner
view.WriteString(evt.String())
}
// Make sure that events have an EOL, so they are displayed right below each other
if !strings.HasSuffix(evt.Message, "\n") {
view.WriteRune('\n')
}
}
return view.String()
}