From eefd5565f8829b6428ab436eae7a9e9a193aec17 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 14 Apr 2026 12:46:12 +0300 Subject: [PATCH] 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 --- internal/prompts/template.go | 6 ++++++ internal/prompts/template_test.go | 28 ++++++++++++++++++++++++++++ internal/ui/commands/commands.go | 1 + internal/ui/input.go | 15 ++++++++++++--- internal/ui/model.go | 2 ++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/internal/prompts/template.go b/internal/prompts/template.go index 188f3e2d..c2846f88 100644 --- a/internal/prompts/template.go +++ b/internal/prompts/template.go @@ -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 { diff --git a/internal/prompts/template_test.go b/internal/prompts/template_test.go index d2493632..097487b5 100644 --- a/internal/prompts/template_test.go +++ b/internal/prompts/template_test.go @@ -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) + } + }) + } +} diff --git a/internal/ui/commands/commands.go b/internal/ui/commands/commands.go index fee9a4ec..54da241d 100644 --- a/internal/ui/commands/commands.go +++ b/internal/ui/commands/commands.go @@ -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 diff --git a/internal/ui/input.go b/internal/ui/input.go index 0f86ab26..9f3e2754 100644 --- a/internal/ui/input.go +++ b/internal/ui/input.go @@ -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 diff --git a/internal/ui/model.go b/internal/ui/model.go index 20727373..b8c7fa80 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -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