commands.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package completions
  2. import (
  3. "sort"
  4. "strings"
  5. "github.com/charmbracelet/lipgloss/v2"
  6. "github.com/lithammer/fuzzysearch/fuzzy"
  7. "github.com/sst/opencode/internal/app"
  8. "github.com/sst/opencode/internal/commands"
  9. "github.com/sst/opencode/internal/styles"
  10. "github.com/sst/opencode/internal/theme"
  11. )
  12. type CommandCompletionProvider struct {
  13. app *app.App
  14. }
  15. func NewCommandCompletionProvider(app *app.App) CompletionProvider {
  16. return &CommandCompletionProvider{app: app}
  17. }
  18. func (c *CommandCompletionProvider) GetId() string {
  19. return "commands"
  20. }
  21. func (c *CommandCompletionProvider) GetEmptyMessage() string {
  22. return "no matching commands"
  23. }
  24. func (c *CommandCompletionProvider) getCommandCompletionItem(
  25. cmd commands.Command,
  26. space int,
  27. ) CompletionSuggestion {
  28. displayFunc := func(s styles.Style) string {
  29. t := theme.CurrentTheme()
  30. spacer := strings.Repeat(" ", space)
  31. display := " /" + cmd.PrimaryTrigger() + s.
  32. Foreground(t.TextMuted()).
  33. Render(spacer+cmd.Description)
  34. return display
  35. }
  36. value := string(cmd.Name)
  37. return CompletionSuggestion{
  38. Display: displayFunc,
  39. Value: value,
  40. ProviderID: c.GetId(),
  41. RawData: cmd,
  42. }
  43. }
  44. func (c *CommandCompletionProvider) GetChildEntries(
  45. query string,
  46. ) ([]CompletionSuggestion, error) {
  47. commands := c.app.Commands
  48. space := 1
  49. for _, cmd := range c.app.Commands {
  50. if cmd.HasTrigger() && lipgloss.Width(cmd.PrimaryTrigger()) > space {
  51. space = lipgloss.Width(cmd.PrimaryTrigger())
  52. }
  53. }
  54. space += 2
  55. sorted := commands.Sorted()
  56. if query == "" {
  57. // If no query, return all commands
  58. items := []CompletionSuggestion{}
  59. for _, cmd := range sorted {
  60. if !cmd.HasTrigger() {
  61. continue
  62. }
  63. space := space - lipgloss.Width(cmd.PrimaryTrigger())
  64. items = append(items, c.getCommandCompletionItem(cmd, space))
  65. }
  66. return items, nil
  67. }
  68. var commandNames []string
  69. commandMap := make(map[string]CompletionSuggestion)
  70. for _, cmd := range sorted {
  71. if !cmd.HasTrigger() {
  72. continue
  73. }
  74. space := space - lipgloss.Width(cmd.PrimaryTrigger())
  75. for _, trigger := range cmd.Trigger {
  76. commandNames = append(commandNames, trigger)
  77. commandMap[trigger] = c.getCommandCompletionItem(cmd, space)
  78. }
  79. }
  80. matches := fuzzy.RankFindFold(query, commandNames)
  81. sort.Sort(matches)
  82. // Convert matches to completion items, deduplicating by command name
  83. items := []CompletionSuggestion{}
  84. seen := make(map[string]bool)
  85. for _, match := range matches {
  86. if item, ok := commandMap[match.Target]; ok {
  87. // Use the command's value (name) as the deduplication key
  88. if !seen[item.Value] {
  89. seen[item.Value] = true
  90. items = append(items, item)
  91. }
  92. }
  93. }
  94. return items, nil
  95. }