From 13f72a0b56b8a0b70c98c6940ecf2e94d7cb8801 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Thu, 20 Aug 2026 16:51:59 +0200 Subject: [PATCH] fix(completion): always leave a trailing space after a task name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The protocol carries one directive per response, so a single truncated wildcard prefix cost every complete name its trailing space: a Taskfile holding `deploy-*` anywhere made `task buil` insert `build` with the cursor stuck against it. Completing a task name is the common case and the wildcard prefix the rare one, so the polarity was backwards — and the legacy wrappers, which had no notion of an incomplete candidate, always left the space. A wildcard prefix now gets a space it does not want, which is the accepted trade until suggestions can carry a directive of their own. Required variables keep NoSpace: there a free-form `VAR=` is the common case, not the exception. --- internal/complete/complete_test.go | 4 +++- internal/complete/engine.go | 15 +++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/internal/complete/complete_test.go b/internal/complete/complete_test.go index d047c8f0..390978bc 100644 --- a/internal/complete/complete_test.go +++ b/internal/complete/complete_test.go @@ -166,7 +166,9 @@ func TestComplete_WildcardTaskNames(t *testing.T) { // Patterns are cut at their first `*`: `wildcard-*` and `wildcard-*-*` // collapse into one candidate, and `*-wildcard-*` leaves nothing to insert. require.Equal(t, []string{"build", "matches-exactly-", "release-", "start-", "s-", "wildcard-"}, values(suggs)) - require.Equal(t, complete.DirectiveNoSpace|complete.DirectiveNoFileComp, dir) + // A truncated prefix costs the whole response its trailing space, so task + // names keep theirs and the wildcard prefix gets one it does not want. + require.Equal(t, complete.DirectiveNoFileComp, dir) // Without a desc, the pattern says what the prefix stands for. require.Contains(t, descriptions(suggs), "wildcard-*") diff --git a/internal/complete/engine.go b/internal/complete/engine.go index 99afea88..eda451b5 100644 --- a/internal/complete/engine.go +++ b/internal/complete/engine.go @@ -67,7 +67,6 @@ func completeTaskNames(e *task.Executor, opts Options) ([]Suggestion, Directive) out := make([]Suggestion, 0, len(tasks)) seen := make(map[string]bool, len(tasks)) - anyPartial := false add := func(name, desc string) { value, partial := suggestedName(name) // `*-wildcard-*` has no prefix, and `wildcard-*` / `wildcard-*-*` share one. @@ -75,11 +74,9 @@ func completeTaskNames(e *task.Executor, opts Options) ([]Suggestion, Directive) return } seen[value] = true - if partial { - anyPartial = true - if desc == "" && !opts.NoDescriptions { - desc = name - } + // Without a desc, a truncated pattern says what its prefix stands for. + if partial && desc == "" && !opts.NoDescriptions { + desc = name } out = append(out, Suggestion{Value: value, Description: desc}) } @@ -94,10 +91,8 @@ func completeTaskNames(e *task.Executor, opts Options) ([]Suggestion, Directive) } } - // A truncated pattern is half a name: the cursor must stay against it. - if anyPartial { - return out, DirectiveNoSpace | DirectiveNoFileComp - } + // A single truncated wildcard prefix would otherwise cost every complete + // name its trailing space: the directive covers the whole response. return out, DirectiveNoFileComp }