feat(ui): populate input instead of auto-submitting prompts with args

- Add HasArgPlaceholders() method to PromptTemplate to detect , $@,
  $ARGUMENTS, etc. placeholders in template content
- Add HasArgs field to SlashCommand struct
- Set HasArgs when registering prompt templates as slash commands
- In fuzzy finder Enter handler, populate input with command + trailing
  space when HasArgs is true, letting the user type arguments naturally
- Fix potential index bug by capturing selectedCmd before resetting index
This commit is contained in:
Ed Zynda
2026-04-14 12:46:12 +03:00
parent 9d1b8a102e
commit eefd5565f8
5 changed files with 49 additions and 3 deletions
+6
View File
@@ -266,6 +266,12 @@ func joinArgsRange(args []string, start, length int) string {
return strings.Join(args[start:end], " ")
}
// HasArgPlaceholders reports whether the template content contains any
// argument placeholders ($1, $@, $ARGUMENTS, ${@:...}, etc.).
func (t *PromptTemplate) HasArgPlaceholders() bool {
return argPlaceholder.MatchString(t.Content)
}
// Expand substitutes arguments into the template content and returns the result.
// It first parses args from the input string, then substitutes them into the template.
func (t *PromptTemplate) Expand(argsInput string) string {
+28
View File
@@ -213,3 +213,31 @@ func TestPromptTemplateExpand(t *testing.T) {
})
}
}
func TestHasArgPlaceholders(t *testing.T) {
tests := []struct {
name string
content string
want bool
}{
{"no placeholders", "Just a plain prompt with no args", false},
{"$1 placeholder", "Create a $1 component", true},
{"$@ placeholder", "Run with args: $@", true},
{"$ARGUMENTS placeholder", "Features: $ARGUMENTS", true},
{"${1} placeholder", "Name: ${1}", true},
{"${ARGUMENTS} placeholder", "All: ${ARGUMENTS}", true},
{"${@:1} placeholder", "Rest: ${@:1}", true},
{"${@:1:2} placeholder", "Slice: ${@:1:2}", true},
{"dollar in text", "Cost is one hundred dollars", false},
{"empty content", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tpl := &PromptTemplate{Content: tt.content}
if got := tpl.HasArgPlaceholders(); got != tt.want {
t.Errorf("HasArgPlaceholders() = %v, want %v", got, tt.want)
}
})
}
}
+1
View File
@@ -20,6 +20,7 @@ type SlashCommand struct {
Aliases []string
Category string // e.g., "Navigation", "System", "Info"
Complete func(prefix string) []string // optional argument tab-completion
HasArgs bool // true when the command expects arguments (e.g. prompt templates with placeholders)
}
// SlashCommands provides the global registry of all available slash commands
+12 -3
View File
@@ -285,16 +285,25 @@ func (s *InputComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
s.textarea.CursorEnd()
return s, nil
}
selectedCmd := s.filtered[s.selected].Command
// Populate textarea with selected item and submit on next tick.
if s.argMode {
s.textarea.SetValue(s.argCommand + " " + s.filtered[s.selected].Command.Name)
s.textarea.SetValue(s.argCommand + " " + selectedCmd.Name)
} else {
s.textarea.SetValue(s.filtered[s.selected].Command.Name)
s.textarea.SetValue(selectedCmd.Name)
}
s.textarea.CursorEnd()
s.showPopup = false
s.selected = 0
s.submitNext = true
// If the selected command expects arguments, populate
// the input with the command + trailing space so the
// user can type args, instead of auto-submitting.
if !s.argMode && selectedCmd.HasArgs {
s.textarea.SetValue(selectedCmd.Name + " ")
s.textarea.CursorEnd()
} else {
s.submitNext = true
}
return s, nil
}
return s, nil
+2
View File
@@ -827,6 +827,7 @@ func NewAppModel(appCtrl AppController, opts AppModelOptions) *AppModel {
Name: "/" + tpl.Name,
Description: tpl.Description,
Category: "Prompts",
HasArgs: tpl.HasArgPlaceholders(),
})
}
}
@@ -2932,6 +2933,7 @@ func (m *AppModel) refreshPromptTemplates() {
Name: "/" + tpl.Name,
Description: tpl.Description,
Category: "Prompts",
HasArgs: tpl.HasArgPlaceholders(),
})
}
ic.commands = kept