164 lines
4.2 KiB
Go
164 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/charmbracelet/bubbles/key"
|
|
"github.com/charmbracelet/bubbles/list"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// WrappingDelegate is a delegate that wraps text instead of truncating it.
|
|
// It's based on the DefaultDelegate but modified to use text wrapping.
|
|
type WrappingDelegate struct {
|
|
ShowDescription bool
|
|
Styles list.DefaultItemStyles
|
|
UpdateFunc func(tea.Msg, *list.Model) tea.Cmd
|
|
ShortHelpFunc func() []key.Binding
|
|
FullHelpFunc func() [][]key.Binding
|
|
height int
|
|
spacing int
|
|
}
|
|
|
|
// NewWrappingDelegate creates a new delegate with wrapping behavior and default styles.
|
|
func NewWrappingDelegate() WrappingDelegate {
|
|
const defaultHeight = 2
|
|
const defaultSpacing = 1
|
|
return WrappingDelegate{
|
|
ShowDescription: true,
|
|
Styles: list.NewDefaultItemStyles(),
|
|
height: defaultHeight,
|
|
spacing: defaultSpacing,
|
|
}
|
|
}
|
|
|
|
// SetHeight sets delegate's preferred height.
|
|
func (d *WrappingDelegate) SetHeight(i int) {
|
|
d.height = i
|
|
}
|
|
|
|
// Height returns the delegate's preferred height.
|
|
// This has effect only if ShowDescription is true,
|
|
// otherwise height is always 1.
|
|
func (d WrappingDelegate) Height() int {
|
|
if d.ShowDescription {
|
|
return d.height
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// SetSpacing sets the delegate's spacing.
|
|
func (d *WrappingDelegate) SetSpacing(i int) {
|
|
d.spacing = i
|
|
}
|
|
|
|
// Spacing returns the delegate's spacing.
|
|
func (d WrappingDelegate) Spacing() int {
|
|
return d.spacing
|
|
}
|
|
|
|
// Update checks whether the delegate's UpdateFunc is set and calls it.
|
|
func (d WrappingDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
|
|
if d.UpdateFunc == nil {
|
|
return nil
|
|
}
|
|
return d.UpdateFunc(msg, m)
|
|
}
|
|
|
|
// wrapText wraps text to the specified width using lipgloss
|
|
func wrapText(text string, width int, style lipgloss.Style) string {
|
|
if width <= 0 {
|
|
return text
|
|
}
|
|
return style.Width(width).Render(text)
|
|
}
|
|
|
|
// Render prints an item with text wrapping instead of truncation.
|
|
func (d WrappingDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
|
|
var (
|
|
title, desc string
|
|
matchedRunes []int
|
|
s = &d.Styles
|
|
)
|
|
|
|
if i, ok := item.(list.DefaultItem); ok {
|
|
title = i.Title()
|
|
desc = i.Description()
|
|
} else {
|
|
return
|
|
}
|
|
|
|
if m.Width() <= 0 {
|
|
// short-circuit
|
|
return
|
|
}
|
|
|
|
// Calculate available text width
|
|
textwidth := m.Width() - s.NormalTitle.GetPaddingLeft() - s.NormalTitle.GetPaddingRight()
|
|
|
|
// Conditions
|
|
var (
|
|
isSelected = index == m.Index()
|
|
emptyFilter = m.FilterState() == list.Filtering && m.FilterValue() == ""
|
|
isFiltered = m.FilterState() == list.Filtering || m.FilterState() == list.FilterApplied
|
|
)
|
|
|
|
if isFiltered && index < len(m.VisibleItems()) {
|
|
// Get indices of matched characters
|
|
matchedRunes = m.MatchesForItem(index)
|
|
}
|
|
|
|
// Apply styling and wrapping based on state
|
|
if emptyFilter {
|
|
title = wrapText(title, textwidth, s.DimmedTitle)
|
|
if d.ShowDescription {
|
|
desc = wrapText(desc, textwidth, s.DimmedDesc)
|
|
}
|
|
} else if isSelected && m.FilterState() != list.Filtering {
|
|
if isFiltered {
|
|
// Highlight matches
|
|
unmatched := s.SelectedTitle.Inline(true)
|
|
matched := unmatched.Inherit(s.FilterMatch)
|
|
title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
|
|
}
|
|
title = wrapText(title, textwidth, s.SelectedTitle)
|
|
if d.ShowDescription {
|
|
desc = wrapText(desc, textwidth, s.SelectedDesc)
|
|
}
|
|
} else {
|
|
if isFiltered {
|
|
// Highlight matches
|
|
unmatched := s.NormalTitle.Inline(true)
|
|
matched := unmatched.Inherit(s.FilterMatch)
|
|
title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
|
|
}
|
|
title = wrapText(title, textwidth, s.NormalTitle)
|
|
if d.ShowDescription {
|
|
desc = wrapText(desc, textwidth, s.NormalDesc)
|
|
}
|
|
}
|
|
|
|
if d.ShowDescription {
|
|
fmt.Fprintf(w, "%s\n%s", title, desc) //nolint: errcheck
|
|
return
|
|
}
|
|
fmt.Fprintf(w, "%s", title) //nolint: errcheck
|
|
}
|
|
|
|
// ShortHelp returns the delegate's short help.
|
|
func (d WrappingDelegate) ShortHelp() []key.Binding {
|
|
if d.ShortHelpFunc != nil {
|
|
return d.ShortHelpFunc()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FullHelp returns the delegate's full help.
|
|
func (d WrappingDelegate) FullHelp() [][]key.Binding {
|
|
if d.FullHelpFunc != nil {
|
|
return d.FullHelpFunc()
|
|
}
|
|
return nil
|
|
}
|