mirror of
https://github.com/sst/opencode.git
synced 2025-07-07 16:14:59 +00:00
210 lines
5 KiB
Go
210 lines
5 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea/v2"
|
|
"github.com/charmbracelet/lipgloss/v2"
|
|
"github.com/charmbracelet/lipgloss/v2/compat"
|
|
"github.com/sst/opencode/internal/app"
|
|
"github.com/sst/opencode/internal/commands"
|
|
"github.com/sst/opencode/internal/styles"
|
|
"github.com/sst/opencode/internal/theme"
|
|
)
|
|
|
|
type CommandsComponent interface {
|
|
tea.ViewModel
|
|
SetSize(width, height int) tea.Cmd
|
|
SetBackgroundColor(color compat.AdaptiveColor)
|
|
}
|
|
|
|
type commandsComponent struct {
|
|
app *app.App
|
|
width, height int
|
|
showKeybinds bool
|
|
showAll bool
|
|
background *compat.AdaptiveColor
|
|
limit *int
|
|
}
|
|
|
|
func (c *commandsComponent) SetSize(width, height int) tea.Cmd {
|
|
c.width = width
|
|
c.height = height
|
|
return nil
|
|
}
|
|
|
|
func (c *commandsComponent) SetBackgroundColor(color compat.AdaptiveColor) {
|
|
c.background = &color
|
|
}
|
|
|
|
func (c *commandsComponent) View() string {
|
|
t := theme.CurrentTheme()
|
|
|
|
triggerStyle := styles.NewStyle().Foreground(t.Primary()).Bold(true)
|
|
descriptionStyle := styles.NewStyle().Foreground(t.Text())
|
|
keybindStyle := styles.NewStyle().Foreground(t.TextMuted())
|
|
|
|
if c.background != nil {
|
|
triggerStyle = triggerStyle.Background(*c.background)
|
|
descriptionStyle = descriptionStyle.Background(*c.background)
|
|
keybindStyle = keybindStyle.Background(*c.background)
|
|
}
|
|
|
|
var commandsToShow []commands.Command
|
|
var triggeredCommands []commands.Command
|
|
var untriggeredCommands []commands.Command
|
|
|
|
for _, cmd := range c.app.Commands.Sorted() {
|
|
if c.showAll || cmd.Trigger != "" {
|
|
if cmd.Trigger != "" {
|
|
triggeredCommands = append(triggeredCommands, cmd)
|
|
} else if c.showAll {
|
|
untriggeredCommands = append(untriggeredCommands, cmd)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Combine triggered commands first, then untriggered
|
|
commandsToShow = append(commandsToShow, triggeredCommands...)
|
|
commandsToShow = append(commandsToShow, untriggeredCommands...)
|
|
|
|
if c.limit != nil && len(commandsToShow) > *c.limit {
|
|
commandsToShow = commandsToShow[:*c.limit]
|
|
}
|
|
|
|
if len(commandsToShow) == 0 {
|
|
muted := styles.NewStyle().Foreground(theme.CurrentTheme().TextMuted())
|
|
if c.showAll {
|
|
return muted.Render("No commands available")
|
|
}
|
|
return muted.Render("No commands with triggers available")
|
|
}
|
|
|
|
// Calculate column widths
|
|
maxTriggerWidth := 0
|
|
maxDescriptionWidth := 0
|
|
maxKeybindWidth := 0
|
|
|
|
// Prepare command data
|
|
type commandRow struct {
|
|
trigger string
|
|
description string
|
|
keybinds string
|
|
}
|
|
|
|
rows := make([]commandRow, 0, len(commandsToShow))
|
|
|
|
for _, cmd := range commandsToShow {
|
|
trigger := ""
|
|
if cmd.Trigger != "" {
|
|
trigger = "/" + cmd.Trigger
|
|
} else {
|
|
trigger = string(cmd.Name)
|
|
}
|
|
description := cmd.Description
|
|
|
|
// Format keybindings
|
|
var keybindStrs []string
|
|
if c.showKeybinds {
|
|
for _, kb := range cmd.Keybindings {
|
|
if kb.RequiresLeader {
|
|
keybindStrs = append(keybindStrs, c.app.Config.Keybinds.Leader+" "+kb.Key)
|
|
} else {
|
|
keybindStrs = append(keybindStrs, kb.Key)
|
|
}
|
|
}
|
|
}
|
|
keybinds := strings.Join(keybindStrs, ", ")
|
|
|
|
rows = append(rows, commandRow{
|
|
trigger: trigger,
|
|
description: description,
|
|
keybinds: keybinds,
|
|
})
|
|
|
|
// Update max widths
|
|
if len(trigger) > maxTriggerWidth {
|
|
maxTriggerWidth = len(trigger)
|
|
}
|
|
if len(description) > maxDescriptionWidth {
|
|
maxDescriptionWidth = len(description)
|
|
}
|
|
if len(keybinds) > maxKeybindWidth {
|
|
maxKeybindWidth = len(keybinds)
|
|
}
|
|
}
|
|
|
|
// Add padding between columns
|
|
columnPadding := 3
|
|
|
|
// Build the output
|
|
var output strings.Builder
|
|
|
|
maxWidth := 0
|
|
for _, row := range rows {
|
|
// Pad each column to align properly
|
|
trigger := fmt.Sprintf("%-*s", maxTriggerWidth, row.trigger)
|
|
description := fmt.Sprintf("%-*s", maxDescriptionWidth, row.description)
|
|
|
|
// Apply styles and combine
|
|
line := triggerStyle.Render(trigger) +
|
|
triggerStyle.Render(strings.Repeat(" ", columnPadding)) +
|
|
descriptionStyle.Render(description)
|
|
|
|
if c.showKeybinds && row.keybinds != "" {
|
|
line += keybindStyle.Render(strings.Repeat(" ", columnPadding)) +
|
|
keybindStyle.Render(row.keybinds)
|
|
}
|
|
|
|
output.WriteString(line + "\n")
|
|
maxWidth = max(maxWidth, lipgloss.Width(line))
|
|
}
|
|
|
|
// Remove trailing newline
|
|
result := strings.TrimSuffix(output.String(), "\n")
|
|
if c.background != nil {
|
|
result = styles.NewStyle().Background(*c.background).Width(maxWidth).Render(result)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
type Option func(*commandsComponent)
|
|
|
|
func WithKeybinds(show bool) Option {
|
|
return func(c *commandsComponent) {
|
|
c.showKeybinds = show
|
|
}
|
|
}
|
|
|
|
func WithBackground(background compat.AdaptiveColor) Option {
|
|
return func(c *commandsComponent) {
|
|
c.background = &background
|
|
}
|
|
}
|
|
|
|
func WithLimit(limit int) Option {
|
|
return func(c *commandsComponent) {
|
|
c.limit = &limit
|
|
}
|
|
}
|
|
|
|
func WithShowAll(showAll bool) Option {
|
|
return func(c *commandsComponent) {
|
|
c.showAll = showAll
|
|
}
|
|
}
|
|
|
|
func New(app *app.App, opts ...Option) CommandsComponent {
|
|
c := &commandsComponent{
|
|
app: app,
|
|
background: nil,
|
|
showKeybinds: true,
|
|
showAll: false,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(c)
|
|
}
|
|
return c
|
|
}
|