diff --git a/CHANGELOG.md b/CHANGELOG.md index 70edd1f281..edb6383b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ @vmaerten). - Improved shell completion scripts (Zsh, Fish, PowerShell) by adding missing flags and dynamic experimental feature detection (#2532 by @vmaerten). +- Improved performance of fuzzy task name matching by implementing lazy + initialization. Added `--disable-fuzzy` flag and `disable-fuzzy` taskrc option + to allow disabling fuzzy matching entirely (#2521, #2523 by @vmaerten). - Added LLM-optimized documentation via VitePress plugin, generating `llms.txt` and `llms-full.txt` for AI-powered development tools (#2513 by @vmaerten). - Fixed Zsh and Fish completions to stop suggesting task names after `--` diff --git a/completion/fish/task.fish b/completion/fish/task.fish index 76e797202e..b2b9b045cb 100644 --- a/completion/fish/task.fish +++ b/completion/fish/task.fish @@ -71,6 +71,7 @@ complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored outp complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks' complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell" complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution' +complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names' complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing' complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command' complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments' diff --git a/completion/ps/task.ps1 b/completion/ps/task.ps1 index b25124ab41..941c1d48c4 100644 --- a/completion/ps/task.ps1 +++ b/completion/ps/task.ps1 @@ -15,6 +15,7 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock { [CompletionResult]::new('--completion', '--completion', [CompletionResultType]::ParameterName, 'generate shell completion'), [CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'set directory'), [CompletionResult]::new('--dir', '--dir', [CompletionResultType]::ParameterName, 'set directory'), + [CompletionResult]::new('--disable-fuzzy', '--disable-fuzzy', [CompletionResultType]::ParameterName, 'disable fuzzy matching'), [CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'dry run'), [CompletionResult]::new('--dry', '--dry', [CompletionResultType]::ParameterName, 'dry run'), [CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'), diff --git a/completion/zsh/_task b/completion/zsh/_task index 0738ed2c24..85a5dd1e67 100755 --- a/completion/zsh/_task +++ b/completion/zsh/_task @@ -60,6 +60,7 @@ _task() { '(-c --color)'{-c,--color}'[colored output]' '(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)' '(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' + '(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]' '(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]' '(--dry)--dry[dry-run mode, compile and print tasks only]' '(-x --exit-code)'{-x,--exit-code}'[pass-through exit code of task command]' diff --git a/executor.go b/executor.go index ca106f2ac2..3ca5fbcf66 100644 --- a/executor.go +++ b/executor.go @@ -40,6 +40,7 @@ type ( Watch bool Verbose bool Silent bool + DisableFuzzy bool AssumeYes bool AssumeTerm bool // Used for testing Dry bool @@ -65,7 +66,8 @@ type ( UserWorkingDir string EnableVersionCheck bool - fuzzyModel *fuzzy.Model + fuzzyModel *fuzzy.Model + fuzzyModelOnce sync.Once concurrencySemaphore chan struct{} taskCallCount map[string]*int32 @@ -312,6 +314,19 @@ func (o *silentOption) ApplyToExecutor(e *Executor) { e.Silent = o.silent } +// WithDisableFuzzy tells the [Executor] to disable fuzzy matching for task names. +func WithDisableFuzzy(disableFuzzy bool) ExecutorOption { + return &disableFuzzyOption{disableFuzzy} +} + +type disableFuzzyOption struct { + disableFuzzy bool +} + +func (o *disableFuzzyOption) ApplyToExecutor(e *Executor) { + e.DisableFuzzy = o.disableFuzzy +} + // WithAssumeYes tells the [Executor] to assume "yes" for all prompts. func WithAssumeYes(assumeYes bool) ExecutorOption { return &assumeYesOption{assumeYes} diff --git a/internal/flags/flags.go b/internal/flags/flags.go index 1e8bb5a3e3..eb5930dc10 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -58,6 +58,7 @@ var ( Watch bool Verbose bool Silent bool + DisableFuzzy bool AssumeYes bool Dry bool Summary bool @@ -125,6 +126,7 @@ func init() { pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.") pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, func() *bool { return config.Verbose }, false), "Enables verbose mode.") pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.") + pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.") pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.") pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.") pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.") @@ -248,6 +250,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) { task.WithWatch(Watch), task.WithVerbose(Verbose), task.WithSilent(Silent), + task.WithDisableFuzzy(DisableFuzzy), task.WithAssumeYes(AssumeYes), task.WithDry(Dry || Status), task.WithSummary(Summary), diff --git a/setup.go b/setup.go index c903864750..2fc3a6bf1e 100644 --- a/setup.go +++ b/setup.go @@ -36,7 +36,6 @@ func (e *Executor) Setup() error { if err := e.readTaskfile(node); err != nil { return err } - e.setupFuzzyModel() e.setupStdFiles() if err := e.setupOutput(); err != nil { return err diff --git a/task.go b/task.go index 2a21644086..603d4e908a 100644 --- a/task.go +++ b/task.go @@ -456,8 +456,11 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) { // If we found no tasks if len(aliasedTasks) == 0 { didYouMean := "" - if e.fuzzyModel != nil { - didYouMean = e.fuzzyModel.SpellCheck(call.Task) + if !e.DisableFuzzy { + e.fuzzyModelOnce.Do(e.setupFuzzyModel) + if e.fuzzyModel != nil { + didYouMean = e.fuzzyModel.SpellCheck(call.Task) + } } return nil, &errors.TaskNotFoundError{ TaskName: call.Task, diff --git a/taskrc/ast/taskrc.go b/taskrc/ast/taskrc.go index dd4de9cc77..8952315d62 100644 --- a/taskrc/ast/taskrc.go +++ b/taskrc/ast/taskrc.go @@ -10,12 +10,13 @@ import ( ) type TaskRC struct { - Version *semver.Version `yaml:"version"` - Verbose *bool `yaml:"verbose"` - Concurrency *int `yaml:"concurrency"` - Remote Remote `yaml:"remote"` - Experiments map[string]int `yaml:"experiments"` - Failfast bool `yaml:"failfast"` + Version *semver.Version `yaml:"version"` + Verbose *bool `yaml:"verbose"` + DisableFuzzy *bool `yaml:"disable-fuzzy"` + Concurrency *int `yaml:"concurrency"` + Remote Remote `yaml:"remote"` + Failfast bool `yaml:"failfast"` + Experiments map[string]int `yaml:"experiments"` } type Remote struct { @@ -53,6 +54,7 @@ func (t *TaskRC) Merge(other *TaskRC) { } t.Verbose = cmp.Or(other.Verbose, t.Verbose) + t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy) t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency) t.Failfast = cmp.Or(other.Failfast, t.Failfast) } diff --git a/website/src/docs/reference/cli.md b/website/src/docs/reference/cli.md index d95bb85981..c434e5b854 100644 --- a/website/src/docs/reference/cli.md +++ b/website/src/docs/reference/cli.md @@ -108,6 +108,16 @@ Disable command echoing. task deploy --silent ``` +#### `--disable-fuzzy` + +Disable fuzzy matching for task names. When enabled, Task will not suggest similar task names when you mistype a task name. + +```bash +task buidl --disable-fuzzy +# Output: Task "buidl" does not exist +# (without "Did you mean 'build'?" suggestion) +``` + ### Execution Control #### `-F, --failfast` diff --git a/website/src/docs/reference/config.md b/website/src/docs/reference/config.md index 478c334df1..a0129e8f7a 100644 --- a/website/src/docs/reference/config.md +++ b/website/src/docs/reference/config.md @@ -91,6 +91,17 @@ experiments: verbose: true ``` +### `disable-fuzzy` + +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Disable fuzzy matching for task names. When enabled, Task will not suggest similar task names when you mistype a task name. +- **CLI equivalent**: [`--disable-fuzzy`](./cli.md#--disable-fuzzy) + +```yaml +disable-fuzzy: true +``` + ### `concurrency` - **Type**: `integer` @@ -120,6 +131,7 @@ Here's a complete example of a `.taskrc.yml` file with all available options: ```yaml # Global settings verbose: true +disable-fuzzy: false concurrency: 2 # Enable experimental features diff --git a/website/src/public/schema-taskrc.json b/website/src/public/schema-taskrc.json index d4ac369637..34c6ed5336 100644 --- a/website/src/public/schema-taskrc.json +++ b/website/src/public/schema-taskrc.json @@ -57,6 +57,10 @@ "type": "boolean", "description": "Enable verbose output" }, + "disable-fuzzy": { + "type": "boolean", + "description": "Disable fuzzy matching for task names" + }, "concurrency": { "type": "integer", "description": "Number of concurrent tasks to run",