08123aa3c5
Bypass Execute() preprocessing for __complete/__completeNoDesc so Cobra's built-in completion handles shell TAB without creating tasks. Add root ValidArgsFunction for flexible syntax (e.g. "opal 1 de<TAB>" → delete), attribute value completions (status:pending, priority:H, date synonyms), and NoSpace directive on key: completions to avoid trailing space. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
133 lines
4.3 KiB
Go
133 lines
4.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.jnss.me/joakim/opal/internal/engine"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// taskFilterCompletion provides dynamic completions for task filter arguments.
|
|
// Suggests +tag, project:name, and attribute value completions from the database.
|
|
func taskFilterCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
// If typing a key:value, complete the value part
|
|
if idx := strings.IndexByte(toComplete, ':'); idx >= 0 {
|
|
key := toComplete[:idx]
|
|
if engine.ValidAttributeKeys[key] {
|
|
return attributeValueCompletions(key, toComplete), cobra.ShellCompDirectiveNoFileComp
|
|
}
|
|
}
|
|
|
|
// If toComplete is a prefix of an attribute key, return only key:
|
|
// completions with NoSpace so the cursor stays after the colon.
|
|
if toComplete != "" && !strings.HasPrefix(toComplete, "+") && !strings.HasPrefix(toComplete, "-") {
|
|
var keyCompletions []string
|
|
for key := range engine.ValidAttributeKeys {
|
|
if strings.HasPrefix(key, toComplete) {
|
|
keyCompletions = append(keyCompletions, fmt.Sprintf("%s:", key))
|
|
}
|
|
}
|
|
if len(keyCompletions) > 0 {
|
|
return keyCompletions, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
|
|
}
|
|
}
|
|
|
|
var completions []string
|
|
|
|
tags, err := engine.GetAllTags()
|
|
if err == nil {
|
|
for _, tag := range tags {
|
|
completions = append(completions, fmt.Sprintf("+%s", tag))
|
|
}
|
|
}
|
|
|
|
projects, err := engine.GetAllProjects()
|
|
if err == nil {
|
|
for _, proj := range projects {
|
|
completions = append(completions, fmt.Sprintf("project:%s", proj))
|
|
}
|
|
}
|
|
|
|
// Add known attribute keys
|
|
for key := range engine.ValidAttributeKeys {
|
|
completions = append(completions, fmt.Sprintf("%s:", key))
|
|
}
|
|
|
|
return completions, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
|
|
}
|
|
|
|
// attributeValueCompletions returns key:value completions for a known attribute key.
|
|
// Cobra filters by prefix automatically, so we return all values prefixed with "key:".
|
|
func attributeValueCompletions(key, toComplete string) []string {
|
|
var values []string
|
|
|
|
switch key {
|
|
case "status":
|
|
values = []string{"pending", "completed", "deleted", "recurring"}
|
|
case "priority":
|
|
values = []string{"H", "M", "L"}
|
|
case "project":
|
|
projects, err := engine.GetAllProjects()
|
|
if err == nil {
|
|
values = projects
|
|
}
|
|
case "due", "wait", "scheduled", "until":
|
|
values = []string{
|
|
"today", "tomorrow", "yesterday", "now",
|
|
"eod", "sow", "eow", "som", "eom",
|
|
"mon", "tue", "wed", "thu", "fri", "sat", "sun",
|
|
}
|
|
case "recur":
|
|
values = []string{"daily", "weekly", "monthly", "yearly", "1d", "1w", "2w", "1m", "1y"}
|
|
}
|
|
|
|
completions := make([]string, 0, len(values))
|
|
for _, v := range values {
|
|
completions = append(completions, fmt.Sprintf("%s:%s", key, v))
|
|
}
|
|
return completions
|
|
}
|
|
|
|
// rootValidArgsFunction provides completions for root-level arguments,
|
|
// enabling flexible syntax like "opal 1 de<TAB>" to complete to "delete".
|
|
func rootValidArgsFunction(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
// Delegate to taskFilterCompletion first — if toComplete is a partial
|
|
// attribute key, it returns early with NoSpace and we should honour that.
|
|
filterCompletions, directive := taskFilterCompletion(cmd, args, toComplete)
|
|
|
|
var completions []string
|
|
|
|
// Suggest command names
|
|
for _, name := range commandNames {
|
|
completions = append(completions, name)
|
|
}
|
|
|
|
// Suggest report names
|
|
for _, name := range reportNames {
|
|
completions = append(completions, name)
|
|
}
|
|
|
|
completions = append(completions, filterCompletions...)
|
|
|
|
return completions, directive
|
|
}
|
|
|
|
func init() {
|
|
// Root command completions for flexible syntax (e.g., "opal 1 de<TAB>")
|
|
rootCmd.ValidArgsFunction = rootValidArgsFunction
|
|
|
|
// Register dynamic completions for commands that accept filters
|
|
addCmd.ValidArgsFunction = taskFilterCompletion
|
|
doneCmd.ValidArgsFunction = taskFilterCompletion
|
|
deleteCmd.ValidArgsFunction = taskFilterCompletion
|
|
modifyCmd.ValidArgsFunction = taskFilterCompletion
|
|
startCmd.ValidArgsFunction = taskFilterCompletion
|
|
stopCmd.ValidArgsFunction = taskFilterCompletion
|
|
editCmd.ValidArgsFunction = taskFilterCompletion
|
|
infoCmd.ValidArgsFunction = taskFilterCompletion
|
|
annotateCmd.ValidArgsFunction = taskFilterCompletion
|
|
denotateCmd.ValidArgsFunction = taskFilterCompletion
|
|
logCmd.ValidArgsFunction = taskFilterCompletion
|
|
}
|