Files
Radu Macocian (admac) 9537237a85 Added command list
2026-06-26 10:31:02 +02:00

168 lines
4.4 KiB
Go

package ui
import (
"fmt"
"io"
"strings"
"charm.land/bubbles/v2/list"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/sahilm/fuzzy"
)
const commandDropdownHeight = 8
var (
dropdownStyle = lipgloss.NewStyle().
Background(lipgloss.Color("236")).
Padding(0, 1)
inlineTitleStyle = lipgloss.NewStyle().Background(lipgloss.Color("236")).Foreground(lipgloss.Color("86")).Bold(true)
inlineTitleSelStyle = lipgloss.NewStyle().Background(lipgloss.Color("60")).Foreground(lipgloss.Color("86")).Bold(true)
inlineDescStyle = lipgloss.NewStyle().Background(lipgloss.Color("236")).Foreground(lipgloss.Color("245"))
inlineDescSelStyle = lipgloss.NewStyle().Background(lipgloss.Color("60")).Foreground(lipgloss.Color("255"))
inlinePadStyle = lipgloss.NewStyle().Background(lipgloss.Color("236"))
inlinePadSelStyle = lipgloss.NewStyle().Background(lipgloss.Color("60"))
)
type inlineDelegate struct{}
func (inlineDelegate) Height() int { return 1 }
func (inlineDelegate) Spacing() int { return 0 }
func (inlineDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (inlineDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
c, ok := item.(command)
if !ok {
return
}
if m.Width() <= 0 {
return
}
title := "/" + c.name
desc := c.description
titleStyle := inlineTitleStyle
descStyle := inlineDescStyle
padStyle := inlinePadStyle
if index == m.Index() {
titleStyle = inlineTitleSelStyle
descStyle = inlineDescSelStyle
padStyle = inlinePadSelStyle
}
renderedTitle := titleStyle.Render(title)
renderedDesc := descStyle.Render(desc)
pad := max(m.Width()-1-lipgloss.Width(renderedTitle)-lipgloss.Width(renderedDesc), 1)
fmt.Fprintf(w, "%s%s%s%s", renderedTitle, padStyle.Render(" "), renderedDesc, padStyle.Render(strings.Repeat(" ", pad)))
}
type command struct {
name string
description string
handler func(m uiModel) (uiModel, tea.Cmd)
}
func (c command) FilterValue() string { return c.name }
func (c command) Title() string { return "/" + c.name }
func (c command) Description() string { return c.description }
type commandList struct {
list list.Model
open bool
available []command
}
func newCommandList(width int) commandList {
l := list.New(nil, inlineDelegate{}, width, 1)
l.Title = "Commands"
l.SetShowFilter(false)
l.SetFilteringEnabled(false)
l.SetShowStatusBar(false)
l.SetShowPagination(false)
l.SetShowHelp(false)
l.DisableQuitKeybindings()
return commandList{list: l}
}
func (m uiModel) buildCommands() []command {
return []command{
{
name: "exit",
description: "Quit the application",
handler: func(m uiModel) (uiModel, tea.Cmd) {
m.logger.Debug("Received exit sequence. Quiting")
return m, tea.Quit
},
},
{
name: "history",
description: "Open conversation history",
handler: func(m uiModel) (uiModel, tea.Cmd) {
m.logger.Debug("Received history call")
m.focus = focusHistory
return m, m.getHistory()
},
},
{
name: "models",
description: "Choose an LLM model",
handler: func(m uiModel) (uiModel, tea.Cmd) {
m.logger.Debug("Received models sequence")
m.focus = focusModels
return m, m.getModels()
},
},
{
name: "rename",
description: "Rename the current conversation",
handler: func(m uiModel) (uiModel, tea.Cmd) {
m.logger.Debug("Received rename request")
m.waiting = true
return m, m.renameConversation()
},
},
}
}
func (cl *commandList) setAvailable(cmds []command) {
cl.available = cmds
items := make([]list.Item, len(cmds))
for i, c := range cmds {
items[i] = c
}
cl.list.SetItems(items)
cl.list.SetHeight(len(items) + 1)
}
func (cl *commandList) filter(query string) {
var matches []list.Item
if query == "" {
matches = make([]list.Item, len(cl.available))
for i, c := range cl.available {
matches[i] = c
}
} else {
names := make([]string, len(cl.available))
for i, c := range cl.available {
names[i] = c.name
}
result := fuzzy.Find(query, names)
matches = make([]list.Item, len(result))
for i, r := range result {
matches[i] = cl.available[r.Index]
}
}
cl.list.SetItems(matches)
cl.list.ResetSelected()
}
func (cl commandList) view() string {
return dropdownStyle.Render(cl.list.View())
}
func (cl commandList) selectedItem() (command, bool) {
c, ok := cl.list.SelectedItem().(command)
return c, ok
}