llm-shell-hint/wrapping_delegate.go
2025-08-26 21:20:50 +03:00

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
}