diff --git a/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs b/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs index b8889615c11..b0e7d13ed9b 100644 --- a/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs +++ b/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs @@ -96,7 +96,7 @@ private async Task Test(Options options) // compile and publish modules using throwaway file system var clientFactory = await RegistryHelper.CreateMockRegistryClientWithPublishedModulesAsync( new MockFileSystem(), - options.PublishedModules.Select(x => (x, "", true)).ToArray()); + [.. options.PublishedModules.Select(x => (x, "", true))]); // create files var mainFile = FileHelper.SaveResultFile(TestContext, "main.bicep", options.Bicep, testOutputPath); diff --git a/src/Bicep.Core/Registry/PublicRegistry/PublicRegistryModuleMetadataProvider.cs b/src/Bicep.Core/Registry/PublicRegistry/PublicRegistryModuleMetadataProvider.cs index 620de999594..550e0a82164 100644 --- a/src/Bicep.Core/Registry/PublicRegistry/PublicRegistryModuleMetadataProvider.cs +++ b/src/Bicep.Core/Registry/PublicRegistry/PublicRegistryModuleMetadataProvider.cs @@ -87,9 +87,7 @@ public ImmutableArray GetModulesMetadata() { StartCacheUpdateInBackgroundIfNeeded(); - return this.cachedIndex - .Select(x => new PublicRegistryModuleMetadata(x.ModulePath, x.GetDescription(), x.GetDocumentationUri())) - .ToImmutableArray(); + return [.. this.cachedIndex.Select(x => new PublicRegistryModuleMetadata(x.ModulePath, x.GetDescription(), x.GetDocumentationUri()))]; } public ImmutableArray GetModuleVersionsMetadata(string modulePath) @@ -103,9 +101,7 @@ public ImmutableArray GetModuleVersionsMeta return []; } - return entry.Versions - .Select(version => new PublicRegistryModuleVersionMetadata(version, entry.GetDescription(version), entry.GetDocumentationUri(version))) - .ToImmutableArray(); + return [.. entry.Versions.Select(version => new PublicRegistryModuleVersionMetadata(version, entry.GetDescription(version), entry.GetDocumentationUri(version)))]; } private void StartCacheUpdateInBackgroundIfNeeded(bool initialDelay = false) diff --git a/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs b/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs index 7454a064042..f1e5c443c95 100644 --- a/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs +++ b/src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs @@ -211,6 +211,14 @@ public async Task GetFilteredCompletions_WithInvalidTextInCompletionContext_Retu [DataRow("module test 'br/public:app/dapr-containerapp:1.0.1|'")] [DataRow("module test |'br/public:app/dapr-containerapp:1.0.1'")] [DataRow("module test 'br/public:app/dapr-containerapp:1.0.1'|")] + [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1|")] + [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1|'")] + [DataRow("module test |'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1'")] + [DataRow("module test 'br:mcr.microsoft.com/bicep/app/dapr-containerapp:1.0.1'|")] + [DataRow("module test 'br:contoso.com/app/dapr-containerapp:1.0.1|")] + [DataRow("module test 'br:contoso.com/app/dapr-containerapp:1.0.1|'")] + [DataRow("module test |'br:contoso.com/app/dapr-containerapp:1.0.1'")] + [DataRow("module test 'br:contoso.com/app/dapr-containerapp:1.0.1'|")] public async Task GetFilteredCompletions_WithInvalidCompletionContext_ReturnsEmptyList(string inputWithCursors) { var publicRegistryModuleMetadataProvider = StrictMock.Of(); diff --git a/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs b/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs index e351237e1aa..f472947e0a0 100644 --- a/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.RegularExpressions; using Bicep.Core; @@ -20,7 +21,7 @@ namespace Bicep.LanguageServer.Completions { /// - /// Provides completions for remote (public or private) module references, e.g. br/public:modulePath:version + /// Provides completions for OCI (public or private) module references, e.g. br/public:modulePath:version /// public class ModuleReferenceCompletionProvider : IModuleReferenceCompletionProvider { @@ -39,13 +40,16 @@ private enum ModuleCompletionPriority } // Direct reference to a full registry login server URI via br: - private static readonly Regex ModuleRegistryWithoutAlias = new(@"'br:(?(.*?))/'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + private static readonly Regex ModulePrefixWithFullPath = new(@"^br:(?(.*?))/", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + // Aliased reference to a registry via br/alias:path - private static readonly Regex ModuleRegistryWithAliasAndPath = new(@"'br/(.*):(?(.*?)):'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); - // Direct reference to the MCR registry via br:mcr.microsoft.com/bicep/path - private static readonly Regex MCRModuleRegistryWithoutAlias = new($"br:{PublicMCRRegistry}/bicep/(?(.*?)):'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); - // Aliased reference to the MCR registry via br/public: - private static readonly Regex MCRModuleRegistryWithAlias = new(@"br/public:(?(.*?)):'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + private static readonly Regex ModulePrefixWithAlias = new(@"^br/(.*):(?(.*?)):", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + + // Direct reference to the MCR (public) registry via br:mcr.microsoft.com/bicep/path + private static readonly Regex PublicModuleWithFullPathAndVersionSeparator = new($"^br:{PublicMCRRegistry}/bicep/(?(.*?)):'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + + // Aliased reference to the MCR (public) registry via br/public: + private static readonly Regex PublicModuleWithAliasAndVersionSeparator = new(@"^br/public:(?(.*?)):'?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); private const string PublicMCRRegistry = LanguageConstants.BicepPublicMcrRegistry; // "mcr.microsoft.com" @@ -72,15 +76,30 @@ public async Task> GetFilteredCompletions(Uri source replacementText = token.Text; } - return GetTopLevelCompletions(context, replacementText, sourceFileUri) - .Concat(GetOciModulePathCompletions(context, replacementText, sourceFileUri)) - .Concat(GetMCRModuleRegistryVersionCompletions(context, replacementText, sourceFileUri)) - .Concat(await GetAllRegistryNameAndAliasCompletions(context, replacementText, sourceFileUri, cancellationToken)); + var completions = GetTopLevelCompletions(context, replacementText, sourceFileUri); + + var startsWithSingleQuote = replacementText.StartsWith('\''); + if (startsWithSingleQuote) + { + var trimmedReplacementText = replacementText.Trim('\''); + + var replacementsRequiringStartingQuote = + GetOciModulePathCompletions(context, trimmedReplacementText, sourceFileUri) + .Concat(GetPublicModuleVersionCompletions(context, trimmedReplacementText, sourceFileUri)) + .Concat(await GetAllRegistryNameAndAliasCompletions(context, trimmedReplacementText, sourceFileUri, cancellationToken)); + + completions = [ + ..completions, + ..replacementsRequiringStartingQuote, + ]; + } + + return completions; } // Handles bicep registry and template spec top-level schema completions. // I.e. typing with an empty path: module m1 - private IEnumerable GetTopLevelCompletions(BicepCompletionContext context, string replacementText, Uri sourceFileUri) + private IEnumerable GetTopLevelCompletions(BicepCompletionContext context, string untrimmedReplacementText, Uri sourceFileUri) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ModulePath) && !context.Kind.HasFlag(BicepCompletionContextKind.UsingFilePath)) @@ -88,7 +107,7 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex return []; } - if (!string.IsNullOrWhiteSpace(replacementText.Trim('\''))) + if (!string.IsNullOrWhiteSpace(untrimmedReplacementText.Trim('\''))) { return []; } @@ -97,7 +116,7 @@ private IEnumerable GetTopLevelCompletions(BicepCompletionContex var rootConfiguration = configurationManager.GetConfiguration(sourceFileUri); var templateSpecModuleAliases = rootConfiguration.ModuleAliases.GetTemplateSpecModuleAliases(); - var bicepModuleAliases = GetOciArtifactModuleAliases(sourceFileUri); + var bicepModuleAliases = GetModuleAliases(sourceFileUri); // Top-level TemplateSpec completions AddCompletionItem("ts:", null, "Template spec", ModuleCompletionPriority.FullPath, "template spec completion"); @@ -161,9 +180,9 @@ void AddCompletionItem(string schemePrefix, string? alias, string detailLabel, M /// /// True if is an OCI module reference (i.e., it starts with br: or br/) /// - private bool IsOciArtifactRegistryReference(string replacementText) + private bool IsOciArtifactRegistryReference(string trimmedText) { - if (replacementText.StartsWith("'br/") || replacementText.StartsWith("'br:")) + if (trimmedText.StartsWith("br/") || trimmedText.StartsWith("br:")) { return true; } @@ -177,28 +196,28 @@ private bool IsOciArtifactRegistryReference(string replacementText) // br:mcr.microsoft/bicep/module/name: // // etc - private IEnumerable GetMCRModuleRegistryVersionCompletions(BicepCompletionContext context, string replacementText, Uri sourceFileUri) + private IEnumerable GetPublicModuleVersionCompletions(BicepCompletionContext context, string trimmedText, Uri sourceFileUri) { - if (!IsOciArtifactRegistryReference(replacementText)) + if (!IsOciArtifactRegistryReference(trimmedText)) { return []; } string? modulePath; - if (MCRModuleRegistryWithAlias.IsMatch(replacementText)) + if (PublicModuleWithAliasAndVersionSeparator.IsMatch(trimmedText)) { - var matches = MCRModuleRegistryWithAlias.Matches(replacementText); + var matches = PublicModuleWithAliasAndVersionSeparator.Matches(trimmedText); modulePath = matches[0].Groups["filePath"].Value; } - else if (MCRModuleRegistryWithoutAlias.IsMatch(replacementText)) + else if (PublicModuleWithFullPathAndVersionSeparator.IsMatch(trimmedText)) { - var matches = MCRModuleRegistryWithoutAlias.Matches(replacementText); + var matches = PublicModuleWithFullPathAndVersionSeparator.Matches(trimmedText); modulePath = matches[0].Groups["filePath"].Value; } else { - modulePath = GetNonPublicMCRFilePathForVersionCompletion(replacementText, sourceFileUri); + modulePath = GetAliasedMCRFilePath(trimmedText, sourceFileUri); } if (modulePath is null) @@ -207,7 +226,6 @@ private IEnumerable GetMCRModuleRegistryVersionCompletions(Bicep } List completions = new(); - replacementText = replacementText.TrimEnd('\''); var versionsMetadata = publicRegistryModuleMetadataProvider.GetModuleVersionsMetadata(modulePath); @@ -215,7 +233,7 @@ private IEnumerable GetMCRModuleRegistryVersionCompletions(Bicep { var (version, description, documentationUri) = versionsMetadata[i]; - var insertText = $"{replacementText}{version}'$0"; + var insertText = $"'{trimmedText}{version}'$0"; // Module version is last completion, no follow-up completions triggered var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, version) @@ -230,100 +248,97 @@ private IEnumerable GetMCRModuleRegistryVersionCompletions(Bicep } return completions; - } - // Handles scenario where the user has configured an alias for MCR in bicepconfig.json. - private string? GetNonPublicMCRFilePathForVersionCompletion(string replacementText, Uri sourceFileUri) - { - foreach (var kvp in GetOciArtifactModuleAliases(sourceFileUri)) + // Handles scenario where the user has configured an alias for MCR in bicepconfig.json. + string? GetAliasedMCRFilePath(string trimmedText, Uri sourceFileUri) { - if (kvp.Value.Registry is string registry && - registry.Equals(PublicMCRRegistry, StringComparison.Ordinal)) + foreach (var kvp in GetModuleAliases(sourceFileUri)) { - var aliasFromBicepConfig = $"'br/{kvp.Key}:"; - var replacementTextWithTrimmedEnd = replacementText.TrimEnd('\''); - - if (replacementTextWithTrimmedEnd.StartsWith(aliasFromBicepConfig, StringComparison.Ordinal)) + if (kvp.Value.Registry is string registry && + registry.Equals(PublicMCRRegistry, StringComparison.Ordinal)) { - var matches = ModuleRegistryWithAliasAndPath.Matches(replacementTextWithTrimmedEnd); + var aliasFromBicepConfig = $"br/{kvp.Key}:"; - if (!matches.Any()) + if (trimmedText.StartsWith(aliasFromBicepConfig, StringComparison.Ordinal)) { - continue; - } + var matches = ModulePrefixWithAlias.Matches(trimmedText); - string filePath = matches[0].Groups["filePath"].Value; + if (!matches.Any()) + { + continue; + } - if (filePath is null) - { - continue; - } + string filePath = matches[0].Groups["filePath"].Value; - var modulePath = kvp.Value.ModulePath; + if (filePath is null) + { + continue; + } - if (modulePath is not null) - { - if (modulePath.StartsWith("bicep/")) + var modulePath = kvp.Value.ModulePath; + + if (modulePath is not null) { - modulePath = modulePath.Substring("bicep/".Length); - return $"{modulePath}/{filePath}"; + if (modulePath.StartsWith("bicep/")) + { + modulePath = modulePath.Substring("bicep/".Length); + return $"{modulePath}/{filePath}"; + } } - } - else - { - if (filePath.StartsWith("bicep/")) + else { - return filePath.Substring("bicep/".Length); + if (filePath.StartsWith("bicep/")) + { + return filePath.Substring("bicep/".Length); + } } } } } - } - return null; + return null; + } } - private ImmutableSortedDictionary GetOciArtifactModuleAliases(Uri sourceFileUri) + private ImmutableSortedDictionary GetModuleAliases(Uri sourceFileUri) { var rootConfiguration = configurationManager.GetConfiguration(sourceFileUri); return rootConfiguration.ModuleAliases.GetOciArtifactModuleAliases(); } // Handles remote (OCI) path completions, e.g. br: and br/ - private IEnumerable GetOciModulePathCompletions(BicepCompletionContext context, string replacementText, Uri sourceFileUri) + private IEnumerable GetOciModulePathCompletions(BicepCompletionContext context, string trimmedText, Uri sourceFileUri) { - if (!IsOciArtifactRegistryReference(replacementText)) + if (!IsOciArtifactRegistryReference(trimmedText)) { return []; } return [ - .. GetPublicModuleCompletions(replacementText, context), - .. GetACRPartialPathCompletionsFromBicepConfig(replacementText, context, sourceFileUri), - .. GetMCRPathCompletionFromBicepConfig(replacementText, context, sourceFileUri), + .. GetPublicModuleCompletions(trimmedText, context), + .. GetPartialPrivatePathCompletionsFromAliases(trimmedText, context, sourceFileUri), + .. GetPublicPathCompletionFromAliases(trimmedText, context, sourceFileUri), ]; } // Handles path completions for case where user has specified an alias in bicepconfig.json with registry set to "mcr.microsoft.com". - private IEnumerable GetMCRPathCompletionFromBicepConfig(string replacementText, BicepCompletionContext context, Uri sourceFileUri) + private IEnumerable GetPublicPathCompletionFromAliases(string trimmedText, BicepCompletionContext context, Uri sourceFileUri) { List completions = new(); - var replacementTextWithTrimmedEnd = replacementText.TrimEnd('\''); - - if (IsPrivateAcrRegistryReference(replacementTextWithTrimmedEnd, out _)) + if (IsPrivateRegistryReference(trimmedText, out _)) { return completions; } - foreach (var kvp in GetOciArtifactModuleAliases(sourceFileUri)) + foreach (var kvp in GetModuleAliases(sourceFileUri)) { if (kvp.Value.Registry is string registry) { - // We currently don't support path completion for ACR, but we'll go ahead and log telemetry to track usage. + // We currently don't support path completion for private modules, but we'll go ahead and log telemetry to track usage. if (!registry.Equals(PublicMCRRegistry, StringComparison.Ordinal) && - replacementTextWithTrimmedEnd.Equals($"'br/{kvp.Key}:")) + trimmedText.Equals($"br/{kvp.Key}:")) { telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.ACR)); break; @@ -331,7 +346,7 @@ private IEnumerable GetMCRPathCompletionFromBicepConfig(string r // br/[alias-that-points-to-mcr.microsoft.com]: if (registry.Equals(PublicMCRRegistry, StringComparison.Ordinal) && - replacementTextWithTrimmedEnd.Equals($"'br/{kvp.Key}:")) + trimmedText.Equals($"br/{kvp.Key}:")) { var modulePath = kvp.Value.ModulePath; @@ -348,13 +363,13 @@ private IEnumerable GetMCRPathCompletionFromBicepConfig(string r // } // } - if (replacementTextWithTrimmedEnd.Equals($"'br/{kvp.Key}:", StringComparison.Ordinal)) + if (trimmedText.Equals($"br/{kvp.Key}:", StringComparison.Ordinal)) { var modules = publicRegistryModuleMetadataProvider.GetModulesMetadata(); foreach (var (moduleName, description, documentationUri) in modules) { var label = $"bicep/{moduleName}"; - var insertText = $"{replacementTextWithTrimmedEnd}bicep/{moduleName}:$0'"; + var insertText = $"'{trimmedText}bicep/{moduleName}:$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, label) .WithSnippetEdit(context.ReplacementRange, insertText) .WithFilterText(insertText) @@ -396,14 +411,15 @@ private IEnumerable GetMCRPathCompletionFromBicepConfig(string r foreach (var module in matchingModules) { var label = module.Name.Substring($"{modulePathWithoutBicepKeyword}/".Length); - StringBuilder sb = new(replacementTextWithTrimmedEnd); - if (!replacementTextWithTrimmedEnd.EndsWith(':')) + StringBuilder sb = new($"'{trimmedText}"); + if (!trimmedText.EndsWith(':')) { sb.Append(":"); } sb.Append($"{label}:$0'"); var insertText = sb.ToString(); + var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, label) .WithSnippetEdit(context.ReplacementRange, insertText) .WithFilterText(insertText) @@ -431,15 +447,17 @@ private IEnumerable GetMCRPathCompletionFromBicepConfig(string r /// /// True if a direct reference to a private ACR registry (i.e. not pointing to the Microsoft public bicep registry) + /// Example: + /// "br:privateacr.azurecr.io/" => true /// - /// - /// + /// + /// Won't be null with true return value, but could be empty /// - private bool IsPrivateAcrRegistryReference(string replacementTextWithTrimmedEnd, out string? registry) + private bool IsPrivateRegistryReference(string text, [NotNullWhen(true)] out string? registry) { registry = null; - var matches = ModuleRegistryWithoutAlias.Matches(replacementTextWithTrimmedEnd); + var matches = ModulePrefixWithFullPath.Matches(text); if (!matches.Any()) { return false; @@ -451,19 +469,32 @@ private bool IsPrivateAcrRegistryReference(string replacementTextWithTrimmedEnd, } // We only support partial path completions for ACR using module paths listed in bicepconfig.json - private IEnumerable GetACRPartialPathCompletionsFromBicepConfig(string replacementText, BicepCompletionContext context, Uri sourceFileUri) + + // Handles ACR path completions for full paths, but only for the case where the user has configured an alias in bicepconfig.json. + // Example: + // bicepconfig.json: + // { + // "moduleAliases": { + // "br": { + // "whatever": { + // "registry": "privateacr.azurecr.io", + // "modulePath": "bicep/app" + // ... + // + // br:privateacr.azurecr.io/ + // => + // br:privateacr.azurecr.io/bicep/app: + private IEnumerable GetPartialPrivatePathCompletionsFromAliases(string trimmedText, BicepCompletionContext context, Uri sourceFileUri) { List completions = new(); - var replacementTextWithTrimmedEnd = replacementText.TrimEnd('\''); - if (!IsPrivateAcrRegistryReference(replacementTextWithTrimmedEnd, out string? registry) || string.IsNullOrWhiteSpace(registry)) + if (!IsPrivateRegistryReference(trimmedText, out string? registry) || string.IsNullOrWhiteSpace(registry)) { return completions; } telemetryProvider.PostEvent(BicepTelemetryEvent.ModuleRegistryPathCompletion(ModuleRegistryType.ACR)); - - foreach (var kvp in GetOciArtifactModuleAliases(sourceFileUri)) + foreach (var kvp in GetModuleAliases(sourceFileUri)) { if (registry.Equals(kvp.Value.Registry, StringComparison.Ordinal)) { @@ -474,7 +505,7 @@ private IEnumerable GetACRPartialPathCompletionsFromBicepConfig( continue; } - var insertText = $"{replacementTextWithTrimmedEnd}{modulePath}:$0'"; + var insertText = $"'{trimmedText}{modulePath}:$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Reference, modulePath) .WithSnippetEdit(context.ReplacementRange, insertText) .WithFilterText(insertText) @@ -492,12 +523,12 @@ private IEnumerable GetACRPartialPathCompletionsFromBicepConfig( // br/public: // or // br:mcr.microsoft.com/bicep/: - private IEnumerable GetPublicModuleCompletions(string replacementText, BicepCompletionContext context) + private IEnumerable GetPublicModuleCompletions(string trimmedText, BicepCompletionContext context) { - var (prefix, suffix) = replacementText switch + var (prefix, suffix) = trimmedText switch { - { } x when x.StartsWith("'br/public:", StringComparison.Ordinal) => ("'br/public:", x["'br/public:".Length..].TrimEnd('\'')), - { } x when x.StartsWith($"'br:{PublicMCRRegistry}/bicep/", StringComparison.Ordinal) => ($"'br:{PublicMCRRegistry}/bicep/", x[$"'br:{PublicMCRRegistry}/bicep/".Length..].TrimEnd('\'')), + { } x when x.StartsWith("br/public:", StringComparison.Ordinal) => ("br/public:", x["br/public:".Length..]), + { } x when x.StartsWith($"br:{PublicMCRRegistry}/bicep/", StringComparison.Ordinal) => ($"br:{PublicMCRRegistry}/bicep/", x[$"br:{PublicMCRRegistry}/bicep/".Length..]), _ => (null, null), }; @@ -516,7 +547,7 @@ private IEnumerable GetPublicModuleCompletions(string replacemen continue; } - var insertText = $"{prefix}{moduleName}:$0'"; + var insertText = $"'{prefix}{moduleName}:$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, moduleName) .WithSnippetEdit(context.ReplacementRange, insertText) @@ -539,23 +570,21 @@ private IEnumerable GetPublicModuleCompletions(string replacemen } // Handles top-level completions of registry names/aliases after br: and br/ - private async Task> GetAllRegistryNameAndAliasCompletions(BicepCompletionContext context, string replacementText, Uri sourceFileUri, CancellationToken cancellationToken) + private async Task> GetAllRegistryNameAndAliasCompletions(BicepCompletionContext context, string trimmedText, Uri sourceFileUri, CancellationToken cancellationToken) { var completions = new List(); - if (!IsOciArtifactRegistryReference(replacementText)) + if (!IsOciArtifactRegistryReference(trimmedText)) { return completions; } - var replacementTextWithTrimmedEnd = replacementText.TrimEnd('\''); - - if (replacementTextWithTrimmedEnd == "'br/") + if (trimmedText == "br/") { - foreach (var kvp in GetOciArtifactModuleAliases(sourceFileUri)) + foreach (var kvp in GetModuleAliases(sourceFileUri)) { var alias = kvp.Key; - var insertText = $"{replacementTextWithTrimmedEnd}{alias}:$0'"; + var insertText = $"'{trimmedText}{alias}:$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, alias) .WithFilterText(insertText) .WithSnippetEdit(context.ReplacementRange, insertText) @@ -565,10 +594,10 @@ private async Task> GetAllRegistryNameAndAliasComple completions.Add(completionItem); } } - else if (replacementTextWithTrimmedEnd == "'br:") + else if (trimmedText == "br:") { var label = $"{PublicMCRRegistry}/bicep"; - var insertText = $"{replacementTextWithTrimmedEnd}{label}/$0'"; + var insertText = $"'{trimmedText}{label}/$0'"; var mcrCompletionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, label) .WithFilterText(insertText) .WithSnippetEdit(context.ReplacementRange, insertText) @@ -578,32 +607,31 @@ private async Task> GetAllRegistryNameAndAliasComple completions.Add(mcrCompletionItem); - IEnumerable acrCompletions = await GetACRModuleRegistriesCompletions(replacementText, context, sourceFileUri, cancellationToken); - completions.AddRange(acrCompletions); + completions.AddRange(await GetPrivateModuleCompletions(trimmedText, context, sourceFileUri, cancellationToken)); } return completions; } // Handles registry name completions for private modules possibly available in ACR registries - private async Task> GetACRModuleRegistriesCompletions(string replacementText, BicepCompletionContext context, Uri sourceFileUri, CancellationToken cancellationToken) + private async Task> GetPrivateModuleCompletions(string trimmedText, BicepCompletionContext context, Uri sourceFileUri, CancellationToken cancellationToken) { if (settingsProvider.GetSetting(LangServerConstants.GetAllAzureContainerRegistriesForCompletionsSetting)) { - return await GetACRModuleRegistriesCompletionsFromAzure(replacementText, context, sourceFileUri, cancellationToken); + return await GetACRModuleRegistriesCompletionsFromAzure(trimmedText, context, sourceFileUri, cancellationToken); } else { // CONSIDER: Somehow indicate in the completion list that users can get more completions by setting GetAllAzureContainerRegistriesForCompletionsSetting - return GetACRModuleRegistriesCompletionsFromBicepConfig(replacementText, context, sourceFileUri); + return GetACRModuleRegistriesCompletionsFromBicepConfig(trimmedText, context, sourceFileUri); } } // Handles private registry name completions for modules available in ACR registries using ResourceGraphClient query. // This returns all registries that the user has access to via Azure (whether or not they contain bicep modules, and whether // or not they're registered in the bicepconfig.json file) - // This is for completions after typing "'br:" - private async Task> GetACRModuleRegistriesCompletionsFromAzure(string replacementText, BicepCompletionContext context, Uri sourceFileUri, CancellationToken cancellationToken) + // This is for completions after typing "br:" + private async Task> GetACRModuleRegistriesCompletionsFromAzure(string trimmedText, BicepCompletionContext context, Uri sourceFileUri, CancellationToken cancellationToken) { List completions = new(); @@ -612,8 +640,7 @@ private async Task> GetACRModuleRegistriesCompletion await foreach (string registryName in azureContainerRegistriesProvider.GetRegistryUrisAccessibleFromAzure(sourceFileUri, cancellationToken) .WithCancellation(cancellationToken)) { - var replacementTextWithTrimmedEnd = replacementText.Trim('\''); - var insertText = $"'{replacementTextWithTrimmedEnd}{registryName}/$0'"; + var insertText = $"'{trimmedText}{registryName}/$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, registryName) .WithFilterText(insertText) @@ -634,12 +661,12 @@ private async Task> GetACRModuleRegistriesCompletion } // Handles private ACR registry name completions only for registries that are configured via aliases in the bicepconfig.json file - private IEnumerable GetACRModuleRegistriesCompletionsFromBicepConfig(string replacementText, BicepCompletionContext context, Uri sourceFileUri) + private IEnumerable GetACRModuleRegistriesCompletionsFromBicepConfig(string trimmedText, BicepCompletionContext context, Uri sourceFileUri) { List completions = new(); HashSet aliases = new(); - foreach (var kvp in GetOciArtifactModuleAliases(sourceFileUri)) + foreach (var kvp in GetModuleAliases(sourceFileUri)) { var label = kvp.Value.Registry; @@ -647,8 +674,7 @@ private IEnumerable GetACRModuleRegistriesCompletionsFromBicepCo { if (!aliases.TryGetValue(label, out _)) { - var replacementTextWithTrimmedEnd = replacementText.Trim('\''); - var insertText = $"'{replacementTextWithTrimmedEnd}{label}/$0'"; + var insertText = $"'{trimmedText}{label}/$0'"; var completionItem = CompletionItemBuilder.Create(CompletionItemKind.Snippet, label) .WithFilterText(insertText) .WithSnippetEdit(context.ReplacementRange, insertText)