diff --git a/src/System.CommandLine.StaticCompletions/shells/NuShellShellProvider.cs b/src/System.CommandLine.StaticCompletions/shells/NuShellShellProvider.cs index fdd50157d9f2..fa0271f0da5f 100644 --- a/src/System.CommandLine.StaticCompletions/shells/NuShellShellProvider.cs +++ b/src/System.CommandLine.StaticCompletions/shells/NuShellShellProvider.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.CodeDom.Compiler; +using System.CommandLine.Completions; using System.CommandLine.StaticCompletions.Resources; +using System.Text; namespace System.CommandLine.StaticCompletions.Shells; @@ -16,34 +19,540 @@ public class NushellShellProvider : IShellProvider // override the ToString method to return the argument name so that CLI help is cleaner for 'default' values public override string ToString() => ArgumentName; - private static readonly string _dynamicCompletionScript = - """ - # Add the following content to your config.nu file: + public string GenerateCompletions(Command command) + { + var binaryName = command.Name; + using var textWriter = new StringWriter { NewLine = "\n" }; + using var writer = new IndentedTextWriter(textWriter); - let external_completer = { |spans| + // Collect all completers needed + var completers = new Dictionary(); + var hasDynamic = CollectCompletersAndCheckDynamic([], command, completers); + + // Write header + writer.WriteLine($""" + # Static completion definitions for {binaryName}. + # Generated by `dotnet completions script nushell`. + + # Place this file in one of the paths listed in $env.NU_LIB_DIRS + # then `use` it in your config.nu or elsewhere. + + # For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, + # you could place this file as `dotnet-completions.nu` in the above + # directory and then put the following line in your config.nu: + # `use dotnet-completions.nu *` + + # Of course, the above assumes that your $env.NU_LIB_DIRS is + # actually populated; refer to + # if it isn't. + + # Alternatively, you can put this file wherever you want and `use` + # the absolute path to the file: + # `use /path/to/this/file.nu *` + + """); + + // Write dynamic completer if needed + if (hasDynamic) + { + WriteDynamicCompleter(writer, binaryName); + } + + // Write static completers for items with descriptions + WriteStaticCompleters(writer, completers); + + // Write extern definitions + WriteExternDefinitions(writer, binaryName, [], command, completers); + + writer.Flush(); + return textWriter.ToString(); + } + + /// + /// Recursively collects all completers needed for options and arguments, + /// and returns whether any dynamic symbols were found. + /// + private static bool CollectCompletersAndCheckDynamic( + string[] parentPath, + Command command, + Dictionary completers) + { + var hasDynamic = false; + string[] currentPath = parentPath.Length == 0 + ? [command.Name] + : [..parentPath, command.Name]; + + // Process options (use Options, not HierarchicalOptions, to avoid duplicates) + foreach (var option in command.Options.Where(o => !o.Hidden)) + { + if (option.IsDynamic) + { + hasDynamic = true; + continue; + } + + if (option.IsFlag()) + { + continue; + } + + var items = option.GetCompletions(CompletionContext.Empty).ToArray(); + if (items.Length > 0) + { + var name = CompleterName(currentPath, option.Name); + completers.TryAdd(name, items); + } + } + + // Process arguments + foreach (var argument in command.Arguments.Where(a => !a.Hidden)) + { + if (argument.IsDynamic) + { + hasDynamic = true; + continue; + } + + var items = argument.GetCompletions(CompletionContext.Empty).ToArray(); + if (items.Length > 0) + { + var name = CompleterName(currentPath, argument.Name); + completers.TryAdd(name, items); + } + } + + // Recurse into subcommands + foreach (var sub in command.Subcommands.Where(c => !c.Hidden)) + { + hasDynamic |= CollectCompletersAndCheckDynamic(currentPath, sub, completers); + } + + return hasDynamic; + } + + /// + /// Generates a unique completer name based on command path and symbol name. + /// + private static string CompleterName(string[] commandPath, string symbolName) + { + return string.Join(' ', commandPath) + ' ' + symbolName; + } + + /// + /// Generates an inline completer array for completions. + /// Returns simple array like ["value1", "value2"]. + /// Note: Inline completers don't support descriptions in NuShell. + /// + private static string GenerateInlineCompleter(CompletionItem[] items) + { + var values = items.Select(i => $"\"{SanitizeValue(i.InsertText ?? i.Label)}\""); + return $"[{string.Join(' ', values)}]"; + } + + /// + /// Returns true if any completion items have descriptions. + /// + private static bool HasDescriptions(CompletionItem[] items) => + items.Any(i => i.Documentation is not null || i.Detail is not null); + + /// + /// Writes the dynamic completer function that calls back to the binary. + /// + private static void WriteDynamicCompleter(IndentedTextWriter writer, string binaryName) + { + writer.WriteLine("# Dynamic completion function"); + writer.WriteLine($"def \"nu-complete {binaryName}-dynamic\" [context: string] {{"); + writer.Indent++; + writer.WriteLine($"^{binaryName} complete --position ($context | str length) $context | lines"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLine(); + } + + /// + /// Writes static completer functions for completions that have descriptions. + /// Simple completions without descriptions are inlined directly. + /// + private static void WriteStaticCompleters( + IndentedTextWriter writer, + Dictionary completers) + { + foreach (var (name, items) in completers) + { + if (!HasDescriptions(items)) + { + continue; // Simple completions are inlined + } + + writer.WriteLine($"def \"{CompleterDefName(name)}\" [] {{"); + writer.Indent++; + writer.WriteLine("["); + writer.Indent++; + foreach (var item in items) + { + var value = SanitizeValue(item.InsertText ?? item.Label); + var desc = FirstSentence(SanitizeDescription(item.Documentation ?? item.Detail ?? item.Label)); + writer.WriteLine($"{{ value: \"{value}\" description: \"{desc}\" }}"); + } + writer.Indent--; + writer.WriteLine("]"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLine(); + } + } + + /// + /// Generates a completer function name for static completions with descriptions. + /// + private static string CompleterDefName(string completerKey) => + $"nu-complete {completerKey}"; + + /// + /// Recursively writes extern definitions for a command and all its subcommands. + /// + private static void WriteExternDefinitions( + IndentedTextWriter writer, + string binaryName, + string[] parentPath, + Command command, + Dictionary completers) + { + string[] currentPath = parentPath.Length == 0 + ? [command.Name] + : [..parentPath, command.Name]; + + var commandName = string.Join(' ', currentPath); + + // Write description comment if available (first sentence only) + var description = command.Description is not null ? FirstSentence(command.Description) : null; + if (!string.IsNullOrEmpty(description)) + { + writer.WriteLine($"# {description}"); + } + + writer.WriteLine($"export extern \"{commandName}\" ["); + writer.Indent++; + + // Write positional arguments first + foreach (var arg in command.Arguments.Where(a => !a.Hidden)) + { + writer.WriteLine(GenerateArgumentParameter(arg, binaryName, currentPath, completers)); + } + + // Write options (including inherited recursive options) + // Skip HelpOption - users can use `help ` for NuShell-formatted help + // or --help/-? for native help + foreach (var option in command.HierarchicalOptions().Where(o => o is not Help.HelpOption)) + { + writer.WriteLine(GenerateOptionParameter(option, binaryName, currentPath, completers)); + } + + writer.Indent--; + writer.WriteLine("]"); + writer.WriteLine(); + + // Recurse for subcommands + foreach (var sub in command.Subcommands.Where(c => !c.Hidden)) + { + WriteExternDefinitions(writer, binaryName, currentPath, sub, completers); + } + } + + /// + /// Finds the completer key for an option by searching the completers dictionary. + /// For recursive options inherited from parent commands, this finds the key + /// registered at the original command where the option was defined. + /// + private static string? FindCompleterKey( + Option option, + string[] commandPath, + Dictionary completers) + { + // First try the current command path + var directKey = CompleterName(commandPath, option.Name); + if (completers.ContainsKey(directKey)) + { + return directKey; + } + + // For recursive options, search parent paths + // Walk up the command path looking for where this option's completer was registered + for (var i = commandPath.Length - 1; i >= 1; i--) + { + var parentPath = commandPath[..i]; + var parentKey = CompleterName(parentPath, option.Name); + if (completers.ContainsKey(parentKey)) { - dotnet: { || - dotnet complete ( - $spans | skip 1 | str join " " - ) | lines + return parentKey; + } + } + + return null; + } + + /// + /// Generates a NuShell parameter definition for an option. + /// + private static string GenerateOptionParameter( + Option option, + string binaryName, + string[] commandPath, + Dictionary completers) + { + var sb = new StringBuilder(); + + // Get primary name and aliases + var (primary, aliases) = option.PrimaryNameAndAliases(); + var paramName = primary.TrimStart('-'); + + // Build parameter name: --long(-s) or just --long + if (aliases?.Any(a => a.IsShortAlias()) == true) + { + var shortAlias = aliases.First(a => a.IsShortAlias()).TrimStart('-'); + sb.Append($"--{paramName}(-{shortAlias})"); + } + else + { + sb.Append($"--{paramName}"); + } + + // Add type annotation if not a flag + if (!option.IsFlag()) + { + var nushellType = MapToNushellType(option.ValueType) ?? "string"; + sb.Append($": {nushellType}"); + + // Add completer reference if available + if (option.IsDynamic) + { + sb.Append($"@\"nu-complete {binaryName}-dynamic\""); + } + else + { + var completerKey = FindCompleterKey(option, commandPath, completers); + if (completerKey is not null && completers.TryGetValue(completerKey, out var items)) + { + if (HasDescriptions(items)) + { + sb.Append($"@\"{CompleterDefName(completerKey)}\""); + } + else + { + sb.Append($"@{GenerateInlineCompleter(items)}"); + } + } + } + } + + // Add description as comment (first sentence only) + if (!string.IsNullOrEmpty(option.Description)) + { + sb.Append($" # {FirstSentence(option.Description)}"); + } + + return sb.ToString(); + } + + /// + /// Generates a NuShell parameter definition for an argument. + /// + private static string GenerateArgumentParameter( + Argument argument, + string binaryName, + string[] commandPath, + Dictionary completers) + { + var sb = new StringBuilder(); + + // Handle variadic arguments + var isVariadic = argument.Arity.MaximumNumberOfValues > 1; + if (isVariadic) + { + sb.Append("..."); + } + + // Argument name - sanitize to be a valid NuShell identifier + var argName = SanitizeArgumentName(argument.Name); + sb.Append(argName); + + // Optional marker (but not for variadic arguments - spread params can't have ?) + if (argument.Arity.MinimumNumberOfValues == 0 && !isVariadic) + { + sb.Append('?'); + } + + // Type annotation + var nushellType = MapToNushellType(argument.ValueType) ?? "string"; + sb.Append($": {nushellType}"); + + // Add completer reference if available + if (argument.IsDynamic) + { + sb.Append($"@\"nu-complete {binaryName}-dynamic\""); + } + else + { + var completerKey = CompleterName(commandPath, argument.Name); + if (completers.TryGetValue(completerKey, out var items)) + { + if (HasDescriptions(items)) + { + sb.Append($"@\"{CompleterDefName(completerKey)}\""); } - } | get $spans.0 | each { || do $in } + else + { + sb.Append($"@{GenerateInlineCompleter(items)}"); + } + } + } + + // Add description as comment (first sentence only) + if (!string.IsNullOrEmpty(argument.Description)) + { + sb.Append($" # {FirstSentence(argument.Description)}"); + } + + return sb.ToString(); + } + + /// + /// Maps .NET types to NuShell types. + /// + private static string? MapToNushellType(Type type) + { + // Handle nullable types + var underlying = Nullable.GetUnderlyingType(type) ?? type; + + // Handle arrays and collections + if (underlying.IsArray) + { + underlying = underlying.GetElementType() ?? typeof(string); } - # And then in the config record, find the completions section and add the - # external_completer that was defined earlier to external: + return underlying switch + { + _ when underlying == typeof(string) => "string", + _ when underlying == typeof(int) => "int", + _ when underlying == typeof(long) => "int", + _ when underlying == typeof(short) => "int", + _ when underlying == typeof(byte) => "int", + _ when underlying == typeof(float) => "number", + _ when underlying == typeof(double) => "number", + _ when underlying == typeof(decimal) => "number", + _ when underlying == typeof(bool) => null, // flags have no type + _ when underlying == typeof(FileInfo) => "path", + _ when underlying == typeof(DirectoryInfo) => "directory", + _ => "string" + }; + } - let-env config = { - # your options here - completions: { - # your options here - external: { - # your options here - completer: $external_completer # add it here + /// + /// Sanitizes an argument name to be a valid NuShell identifier. + /// Converts complex names like "PROJECT | SOLUTION | FILE" to simple lowercase identifiers. + /// + private static string SanitizeArgumentName(string name) + { + // If name contains pipes or spaces, extract a simple word or use a generic name + if (name.Contains('|') || name.Contains(' ')) + { + // Try to find a simple word in the name (prefer "file", "path", "project", etc.) + var parts = name.Split(['|', ' '], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + foreach (var preferred in new[] { "FILE", "PATH", "PROJECT", "SOLUTION", "PACKAGE", "NAME" }) + { + if (parts.Any(p => p.Equals(preferred, StringComparison.OrdinalIgnoreCase))) + { + return preferred.ToLowerInvariant(); } } + // Fall back to first word if no preferred match + if (parts.Length > 0) + { + return parts[0].ToLowerInvariant().Replace("-", "_"); + } + return "arg"; + } + + // Simple name - just lowercase and replace dashes with underscores + return name.ToLowerInvariant().Replace("-", "_"); + } + + /// + /// Sanitizes a string for use in a NuShell string literal. + /// + private static string SanitizeValue(string? s) => + s? + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + ?? ""; + + /// + /// Sanitizes a description for use in a NuShell string literal. + /// + private static string SanitizeDescription(string? s) => + s? + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .ReplaceLineEndings(" ") + ?? ""; + + /// + /// Sanitizes a description for use in a NuShell comment. + /// + private static string SanitizeComment(string? s) => + s? + .ReplaceLineEndings(" ") + .Replace("\t", " ") + ?? ""; + + /// + /// Returns the first sentence of a string (up to the first period, or first line if no period). + /// Returns empty string if the first line ends with punctuation that indicates an incomplete thought. + /// Strips trailing period for consistency. + /// + private static string FirstSentence(string s) + { + s = s.Trim(); + + var lineEnd = s.IndexOfAny(['\r', '\n']); + + // Find sentence-ending period: ". " followed by uppercase letter (new sentence) + // This avoids matching periods in acronyms like ".NET" + var periodIdx = -1; + for (var i = 0; i < s.Length - 2; i++) + { + if (s[i] == '.' && s[i + 1] == ' ' && char.IsUpper(s[i + 2])) + { + periodIdx = i; + break; + } + } + + // If no sentence-ending period found, check for period at end of string + if (periodIdx < 0 && s.EndsWith('.')) + { + periodIdx = s.Length - 1; + } + + // Take whichever comes first: end of first sentence or end of first line + if (periodIdx >= 0 && (lineEnd < 0 || periodIdx < lineEnd)) + { + return s[..periodIdx]; // Exclude the period + } + + // No sentence found - use first line if it doesn't end with incomplete punctuation + var firstLine = (lineEnd < 0 ? s : s[..lineEnd]).TrimEnd(); + if (firstLine.Length > 0 && firstLine[^1] is ':' or ',' or ';') + { + return ""; + } + + // Strip trailing period from first line for consistency + if (firstLine.EndsWith('.')) + { + return firstLine[..^1]; } - """; - public string GenerateCompletions(System.CommandLine.Command command) => _dynamicCompletionScript; + return firstLine; + } } diff --git a/test/System.CommandLine.StaticCompletions.Tests/NushellShellProviderTests.cs b/test/System.CommandLine.StaticCompletions.Tests/NushellShellProviderTests.cs new file mode 100644 index 000000000000..1985f7306770 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/NushellShellProviderTests.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace System.CommandLine.StaticCompletions.Tests; + +using System.CommandLine.Help; +using System.CommandLine.StaticCompletions.Shells; +using Xunit; +using Xunit.Abstractions; + +public class NushellShellProviderTests(ITestOutputHelper log) +{ + private IShellProvider _provider = new NushellShellProvider(); + + [Fact] + public async Task GenericCompletions() + { + await _provider.Verify(new("mycommand"), log); + } + + [Fact] + public async Task SimpleOptionCompletion() + { + await _provider.Verify(new("mycommand") + { + new Option("--name") + }, log); + } + + [Fact] + public async Task SubcommandAndOptionInTopLevelList() + { + await _provider.Verify(new("mycommand") + { + new Option("--name"), + new Command("subcommand") + }, log); + } + + [Fact] + public async Task NestedSubcommandCompletion() + { + await _provider.Verify(new("mycommand") + { + new Command("subcommand") + { + new Command("nested") + } + }, log); + } + + [Fact] + public async Task FlagOptionsHaveNoType() + { + await _provider.Verify(new("mycommand") + { + new Option("--verbose", "-v") + { + Arity = ArgumentArity.Zero + }, + new Option("--debug") + { + Arity = ArgumentArity.Zero + } + }, log); + } + + [Fact] + public async Task OptionWithAliasGenerated() + { + await _provider.Verify(new("mycommand") + { + new Option("--configuration", "-c"), + new Option("--verbosity", "-v") + }, log); + } + + [Fact] + public async Task RecursiveOptionsInherited() + { + await _provider.Verify(new("mycommand") + { + new Option("--verbose", "-v") + { + Arity = ArgumentArity.Zero, + Recursive = true + }, + new Command("subcommand") + { + new Option("--name") + } + }, log); + } + + [Fact] + public async Task RecursiveOptionsWithCompletions() + { + var verbosityOption = new Option("--verbosity", "-v") + { + Recursive = true + }; + verbosityOption.AcceptOnlyFromAmong("quiet", "minimal", "normal", "detailed", "diagnostic"); + + await _provider.Verify(new("mycommand") + { + verbosityOption, + new Command("subcommand") + { + new Option("--name"), + new Command("nested") + } + }, log); + } + + [Fact] + public async Task DynamicCompletionsGeneration() + { + var dynamicOption = new Option("--project") + { + IsDynamic = true + }; + var dynamicArg = new Argument("file") + { + IsDynamic = true + }; + Command command = new Command("mycommand") + { + dynamicOption, + dynamicArg + }; + await _provider.Verify(command, log); + } + + [Fact] + public async Task StaticCompletionsWithValues() + { + var optionWithCompletions = new Option("--verbosity", "-v"); + optionWithCompletions.AcceptOnlyFromAmong("quiet", "minimal", "normal", "detailed", "diagnostic"); + + await _provider.Verify(new("mycommand") + { + optionWithCompletions + }, log); + } + + [Fact] + public async Task ArgumentWithCompletions() + { + var argWithCompletions = new Argument("framework"); + argWithCompletions.CompletionSources.Add((context) => + { + return [ + new("net6.0"), + new("net7.0"), + new("net8.0") + ]; + }); + + await _provider.Verify(new("mycommand") + { + argWithCompletions + }, log); + } + + [Fact] + public async Task ComplexCommandHierarchy() + { + Command command = new Command("my-app") + { + new Option("-c") + { + Arity = ArgumentArity.Zero, + Recursive = true + }, + new Option("-v") + { + Arity = ArgumentArity.Zero + }, + new HelpOption(), + new Command("test", "Subcommand\nwith a second line") + { + new Option("--debug", "-d") + { + Arity = ArgumentArity.Zero + } + }, + new Command("help", "Print this message or the help of the given subcommand(s)") + { + new Command("test") + } + }; + await _provider.Verify(command, log); + } + + [Fact] + public async Task TypeMappingForArguments() + { + await _provider.Verify(new("mycommand") + { + new Argument("file"), + new Argument("directory"), + new Argument("count"), + new Argument("ratio") + }, log); + } + + [Fact] + public async Task OptionalAndVariadicArguments() + { + await _provider.Verify(new("mycommand") + { + new Argument("required"), + new Argument("optional") + { + Arity = ArgumentArity.ZeroOrOne + }, + new Argument("files") + { + Arity = ArgumentArity.ZeroOrMore + } + }, log); + } +} diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.ArgumentWithCompletions.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.ArgumentWithCompletions.verified.nu new file mode 100644 index 000000000000..d463bca07fa5 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.ArgumentWithCompletions.verified.nu @@ -0,0 +1,23 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + framework: string@["net6.0" "net7.0" "net8.0"] +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.ComplexCommandHierarchy.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.ComplexCommandHierarchy.verified.nu new file mode 100644 index 000000000000..e50eac9c7011 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.ComplexCommandHierarchy.verified.nu @@ -0,0 +1,39 @@ +# Static completion definitions for my-app. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "my-app" [ + --c + --v +] + +# Subcommand +export extern "my-app test" [ + --debug(-d) + --c +] + +# Print this message or the help of the given subcommand(s) +export extern "my-app help" [ + --c +] + +export extern "my-app help test" [ + --c +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.DynamicCompletionsGeneration.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.DynamicCompletionsGeneration.verified.nu new file mode 100644 index 000000000000..f0441c83ff76 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.DynamicCompletionsGeneration.verified.nu @@ -0,0 +1,29 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +# Dynamic completion function +def "nu-complete mycommand-dynamic" [context: string] { + ^mycommand complete --position ($context | str length) $context | lines +} + +export extern "mycommand" [ + file: string@"nu-complete mycommand-dynamic" + --project: string@"nu-complete mycommand-dynamic" +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.FlagOptionsHaveNoType.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.FlagOptionsHaveNoType.verified.nu new file mode 100644 index 000000000000..c27abaa3f05b --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.FlagOptionsHaveNoType.verified.nu @@ -0,0 +1,24 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + --verbose(-v) + --debug +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.GenericCompletions.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.GenericCompletions.verified.nu new file mode 100644 index 000000000000..fbcd6782ab28 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.GenericCompletions.verified.nu @@ -0,0 +1,22 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.NestedSubcommandCompletion.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.NestedSubcommandCompletion.verified.nu new file mode 100644 index 000000000000..61f121d11ebe --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.NestedSubcommandCompletion.verified.nu @@ -0,0 +1,28 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ +] + +export extern "mycommand subcommand" [ +] + +export extern "mycommand subcommand nested" [ +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.OptionWithAliasGenerated.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.OptionWithAliasGenerated.verified.nu new file mode 100644 index 000000000000..1c3f7beb1462 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.OptionWithAliasGenerated.verified.nu @@ -0,0 +1,24 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + --configuration(-c): string + --verbosity(-v): int +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.OptionalAndVariadicArguments.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.OptionalAndVariadicArguments.verified.nu new file mode 100644 index 000000000000..6ad212e4199f --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.OptionalAndVariadicArguments.verified.nu @@ -0,0 +1,25 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + required: string + optional?: string + ...files: string +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.RecursiveOptionsInherited.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.RecursiveOptionsInherited.verified.nu new file mode 100644 index 000000000000..3eb9748134c7 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.RecursiveOptionsInherited.verified.nu @@ -0,0 +1,28 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + --verbose(-v) +] + +export extern "mycommand subcommand" [ + --name: string + --verbose(-v) +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.RecursiveOptionsWithCompletions.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.RecursiveOptionsWithCompletions.verified.nu new file mode 100644 index 000000000000..0cc883336b6b --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.RecursiveOptionsWithCompletions.verified.nu @@ -0,0 +1,32 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + --verbosity(-v): string@["detailed" "diagnostic" "minimal" "normal" "quiet"] +] + +export extern "mycommand subcommand" [ + --name: string + --verbosity(-v): string@["detailed" "diagnostic" "minimal" "normal" "quiet"] +] + +export extern "mycommand subcommand nested" [ + --verbosity(-v): string@["detailed" "diagnostic" "minimal" "normal" "quiet"] +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.SimpleOptionCompletion.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.SimpleOptionCompletion.verified.nu new file mode 100644 index 000000000000..1ef7d5a93edb --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.SimpleOptionCompletion.verified.nu @@ -0,0 +1,23 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + --name: string +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.StaticCompletionsWithValues.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.StaticCompletionsWithValues.verified.nu new file mode 100644 index 000000000000..269fb274773c --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.StaticCompletionsWithValues.verified.nu @@ -0,0 +1,23 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + --verbosity(-v): string@["detailed" "diagnostic" "minimal" "normal" "quiet"] +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.SubcommandAndOptionInTopLevelList.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.SubcommandAndOptionInTopLevelList.verified.nu new file mode 100644 index 000000000000..85d7a5ec267b --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.SubcommandAndOptionInTopLevelList.verified.nu @@ -0,0 +1,26 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + --name: string +] + +export extern "mycommand subcommand" [ +] + diff --git a/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.TypeMappingForArguments.verified.nu b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.TypeMappingForArguments.verified.nu new file mode 100644 index 000000000000..332c95f60bd8 --- /dev/null +++ b/test/System.CommandLine.StaticCompletions.Tests/snapshots/nushell/NushellShellProviderTests.TypeMappingForArguments.verified.nu @@ -0,0 +1,26 @@ +# Static completion definitions for mycommand. +# Generated by `dotnet completions script nushell`. + +# Place this file in one of the paths listed in $env.NU_LIB_DIRS +# then `use` it in your config.nu or elsewhere. + +# For example, if ~/.config/nushell/lib is in your $env.NU_LIB_DIRS, +# you could place this file as `dotnet-completions.nu` in the above +# directory and then put the following line in your config.nu: +# `use dotnet-completions.nu *` + +# Of course, the above assumes that your $env.NU_LIB_DIRS is +# actually populated; refer to +# if it isn't. + +# Alternatively, you can put this file wherever you want and `use` +# the absolute path to the file: +# `use /path/to/this/file.nu *` + +export extern "mycommand" [ + file: path + directory: directory + count: int + ratio: number +] +