| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- package commands
- import (
- "fmt"
- "runtime"
- "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"
- "github.com/sst/opencode/internal/util"
- )
- 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
- showVscode 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.HasTrigger() {
- if cmd.HasTrigger() {
- 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 c.showVscode {
- ctrlKey := "ctrl"
- if runtime.GOOS == "darwin" {
- ctrlKey = "cmd"
- }
- commandsToShow = append(commandsToShow,
- // empty line
- // commands.Command{
- // Name: "",
- // Description: "",
- // },
- commands.Command{
- Name: commands.CommandName(util.Ide()),
- Description: "open opencode",
- Keybindings: []commands.Keybinding{
- {Key: ctrlKey + "+esc", RequiresLeader: false},
- },
- },
- commands.Command{
- Name: commands.CommandName(util.Ide()),
- Description: "reference file",
- Keybindings: []commands.Keybinding{
- {Key: ctrlKey + "+opt+k", RequiresLeader: false},
- },
- },
- )
- }
- 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.HasTrigger() {
- trigger = "/" + cmd.PrimaryTrigger()
- } 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 WithVscode(showVscode bool) Option {
- return func(c *commandsComponent) {
- c.showVscode = showVscode
- }
- }
- 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
- }
|