go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/cli/components/list.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package components
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/charmbracelet/bubbles/list"
    11  	tea "github.com/charmbracelet/bubbletea"
    12  	"github.com/charmbracelet/lipgloss"
    13  )
    14  
    15  const listHeight = 14
    16  
    17  var (
    18  	titleStyle        = lipgloss.NewStyle().MarginLeft(2)
    19  	itemStyle         = lipgloss.NewStyle().PaddingLeft(4)
    20  	selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
    21  	paginationStyle   = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
    22  	helpStyle         = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
    23  	quitTextStyle     = lipgloss.NewStyle().PaddingLeft(4)
    24  )
    25  
    26  type item string
    27  
    28  func (i item) FilterValue() string { return "" }
    29  
    30  type itemDelegate struct{}
    31  
    32  func (d itemDelegate) Height() int                               { return 1 }
    33  func (d itemDelegate) Spacing() int                              { return 0 }
    34  func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
    35  func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
    36  	i, ok := listItem.(item)
    37  	if !ok {
    38  		return
    39  	}
    40  
    41  	str := fmt.Sprintf("%d. %s", index+1, i)
    42  
    43  	fn := itemStyle.Render
    44  	if index == m.Index() {
    45  		fn = func(strs ...string) string {
    46  			strs = append([]string{">"}, strs...)
    47  			return selectedItemStyle.Render(strs...)
    48  		}
    49  	}
    50  
    51  	fmt.Fprintf(w, fn(str))
    52  }
    53  
    54  type listModel struct {
    55  	list            list.Model
    56  	items           []item
    57  	choice          string
    58  	quitting        bool
    59  	selectedHandler func(s int)
    60  }
    61  
    62  func (m listModel) Init() tea.Cmd {
    63  	return nil
    64  }
    65  
    66  func (m listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    67  	switch msg := msg.(type) {
    68  	case tea.WindowSizeMsg:
    69  		m.list.SetWidth(msg.Width)
    70  		return m, nil
    71  
    72  	case tea.KeyMsg:
    73  		switch keypress := msg.String(); keypress {
    74  		case "ctrl+c":
    75  			m.quitting = true
    76  			m.selectedHandler(-1)
    77  			return m, tea.Quit
    78  
    79  		case "enter":
    80  			i, ok := m.list.SelectedItem().(item)
    81  			if ok {
    82  				m.selectedHandler(m.list.Index())
    83  				m.choice = string(i)
    84  			}
    85  			return m, tea.Quit
    86  		}
    87  	}
    88  
    89  	var cmd tea.Cmd
    90  	m.list, cmd = m.list.Update(msg)
    91  	return m, cmd
    92  }
    93  
    94  func (m listModel) View() string {
    95  	if m.choice != "" {
    96  		return quitTextStyle.Render(fmt.Sprintf("selected: %s", m.choice))
    97  	}
    98  	if m.quitting {
    99  		return quitTextStyle.Render("aborted selection.")
   100  	}
   101  	return "\n" + m.list.View()
   102  }
   103  
   104  func (m listModel) Selection() string {
   105  	return m.choice
   106  }
   107  
   108  func NewListModel(title string, entries []string, h func(idx int)) *listModel {
   109  	items := []list.Item{}
   110  
   111  	for i := range entries {
   112  		items = append(items, item(entries[i]))
   113  	}
   114  
   115  	const defaultWidth = 20
   116  
   117  	l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
   118  	l.Title = title
   119  	l.SetShowStatusBar(false)
   120  	l.SetFilteringEnabled(false)
   121  	l.Styles.Title = titleStyle
   122  	l.Styles.PaginationStyle = paginationStyle
   123  	l.Styles.HelpStyle = helpStyle
   124  
   125  	return &listModel{
   126  		list:            l,
   127  		selectedHandler: h,
   128  	}
   129  }