Added command list
This commit is contained in:
+167
@@ -0,0 +1,167 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user