Skip to content

Commit

Permalink
Implement consistent markdown handling
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-c-martin committed Dec 8, 2023
1 parent 8727bc2 commit e3e501a
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 104 deletions.
28 changes: 28 additions & 0 deletions src/Bicep.LangServer.IntegrationTests/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4544,5 +4544,33 @@ public async Task Compile_time_imports_offer_imported_symbol_property_completion
completions.Should().NotContain(c => c.Label == "bar");
completions.Should().NotContain(c => c.Label == "baz");
}

[TestMethod]
public async Task Description_markdown_is_correctly_formatted()
{
// https://github.com/Azure/bicep/issues/12412
var (text, cursor) = ParserHelper.GetFileWithSingleCursor("""
type foo = {
@description('''Source port ranges.
Can be a single valid port number, a range in the form of \<start\>-\<end\>, or a * for any ports.
When a wildcard is used, that needs to be the only value.''')
sourcePortRanges: string[]
}

param foo1 foo = {
so|
}
""");

var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text);

var completions = await file.RequestCompletion(cursor);
completions.Single(x => x.Label == "sourcePortRanges").Documentation!.MarkupContent!.Value
.Should().BeEquivalentToIgnoringNewlines(
"Type: `string[]` \n" +
"Source port ranges.\n" +
" Can be a single valid port number, a range in the form of \\<start\\>-\\<end\\>, or a * for any ports.\n" +
" When a wildcard is used, that needs to be the only value. \n");
}
}
}
30 changes: 30 additions & 0 deletions src/Bicep.LangServer.IntegrationTests/HoverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,36 @@ public async Task Hovers_are_displayed_on_imported_types()
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```bicep\nfoo: Type<string>\n```\nThe foo type\n"));
}

[TestMethod]
public async Task Description_markdown_is_correctly_formatted()
{
// https://github.com/Azure/bicep/issues/12412
var (text, cursor) = ParserHelper.GetFileWithSingleCursor("""
type foo = {
@description('''Source port ranges.
Can be a single valid port number, a range in the form of \<start\>-\<end\>, or a * for any ports.
When a wildcard is used, that needs to be the only value.''')
sourcePortRanges: string[]
}

param foo1 foo = {
sourceP|ortRanges:
}
""");

var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text);

var hover = await file.RequestHover(cursor);
hover!.Contents!.MarkupContent!.Value
.Should().BeEquivalentToIgnoringNewlines(
"```bicep\n" +
"sourcePortRanges: string[]\n" +
"``` \n" +
"Source port ranges.\n" +
" Can be a single valid port number, a range in the form of \\<start\\>-\\<end\\>, or a * for any ports.\n" +
" When a wildcard is used, that needs to be the only value. \n");
}


private string GetManifestFileContents(string? documentationUri, string? description)
{
Expand Down
50 changes: 27 additions & 23 deletions src/Bicep.LangServer/Completions/BicepCompletionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ namespace Bicep.LanguageServer.Completions
{
public class BicepCompletionProvider : ICompletionProvider
{
private const string MarkdownNewLine = " \n";

private static readonly Container<string> ResourceSymbolCommitChars = new(":");

private static readonly Container<string> PropertyAccessCommitChars = new(".");
Expand Down Expand Up @@ -111,8 +109,8 @@ private IEnumerable<CompletionItem> GetParamIdentifierCompletions(SemanticModel
GetCompletionItemKind(SymbolKind.ParameterAssignment),
metadata.Name)
.WithDocumentation(
$"Type: {metadata.TypeReference.Type}" +
(metadata.Description is null ? "" : $"{MarkdownNewLine}{metadata.Description}"))
MarkdownHelper.AppendNewline($"Type: `{metadata.TypeReference.Type}`") +
MarkdownHelper.AppendNewline(metadata.Description))
.WithDetail(metadata.Name)
.WithPlainTextEdit(paramsCompletionContext.ReplacementRange, metadata.Name)
.Build();
Expand Down Expand Up @@ -1793,7 +1791,9 @@ private static CompletionItem CreateResourceTypeCompletion(ResourceTypeReference
// Lower-case all resource types in filter text otherwise editor may prefer those with casing that match what the user has already typed (#9168)
.WithFilterText(insertText.ToLowerInvariant())
.WithPlainTextEdit(replacementRange, insertText)
.WithDocumentation($"Type: `{resourceType.Type}`{MarkdownNewLine}API Version: `{resourceType.ApiVersion}`")
.WithDocumentation(
MarkdownHelper.AppendNewline($"Type: `{resourceType.Type}`") +
MarkdownHelper.AppendNewline($"API Version: `{resourceType.ApiVersion}`"))
.WithSortText(index.ToString("x8"))
.Build();
}
Expand All @@ -1802,7 +1802,8 @@ private static CompletionItem CreateResourceTypeCompletion(ResourceTypeReference
var insertText = StringUtils.EscapeBicepString(resourceType.Type);
return CompletionItemBuilder.Create(CompletionItemKind.Class, insertText)
.WithSnippetEdit(replacementRange, $"{insertText[..^1]}@$0'")
.WithDocumentation($"Type: `{resourceType.Type}`{MarkdownNewLine}`")
.WithDocumentation(
MarkdownHelper.AppendNewline($"Type: `{resourceType.Type}`"))
.WithFollowupCompletion("resource type completion")
.WithSortText(index.ToString("x8"))
.Build();
Expand All @@ -1816,11 +1817,11 @@ private static CompletionItem CreateResourceTypeSegmentCompletion(ResourceTypeRe
StringUtils.EscapeBicepString($"{resourceType.TypeSegments[^1]}@{resourceType.ApiVersion}") :
StringUtils.EscapeBicepString($"{resourceType.TypeSegments[^1]}");

var apiVersionMarkdown = displayApiVersion is not null ? $"{MarkdownNewLine}API Version: `{displayApiVersion}`" : "";

return CompletionItemBuilder.Create(CompletionItemKind.Class, insertText)
.WithPlainTextEdit(replacementRange, insertText)
.WithDocumentation($"Type: `{resourceType.FormatType()}`{apiVersionMarkdown}")
.WithDocumentation(
MarkdownHelper.AppendNewline($"Type: `{resourceType.Type}`") +
MarkdownHelper.AppendNewline(displayApiVersion is not null ? $"API Version: `{displayApiVersion}`" : null))
// 8 hex digits is probably overkill :)
.WithSortText(index.ToString("x8"))
.Build();
Expand Down Expand Up @@ -1853,7 +1854,7 @@ private static CompletionItemBuilder CreateFilePathCompletionBuilder(string name
CompletionItemBuilder.Create(CompletionItemKind.Snippet, label)
.WithSnippetEdit(replacementRange, snippet)
.WithDetail(detail)
.WithDocumentation($"```bicep\n{new Snippet(snippet).FormatDocumentation()}\n```")
.WithDocumentation(MarkdownHelper.CodeBlock(new Snippet(snippet).FormatDocumentation()))
.WithSortText(GetSortText(label, priority))
.Preselect(preselect)
.Build();
Expand All @@ -1866,7 +1867,7 @@ private static CompletionItemBuilder CreateFilePathCompletionBuilder(string name
.WithSnippetEdit(replacementRange, snippet)
.WithCommand(command)
.WithDetail(detail)
.WithDocumentation($"```bicep\n{new Snippet(snippet).FormatDocumentation()}\n```")
.WithDocumentation(MarkdownHelper.CodeBlock(new Snippet(snippet).FormatDocumentation()))
.WithSortText(GetSortText(label, priority))
.Preselect(preselect)
.Build();
Expand All @@ -1879,7 +1880,7 @@ private static CompletionItemBuilder CreateFilePathCompletionBuilder(string name
.WithSnippetEdit(replacementRange, snippet)
.WithAdditionalEdits(additionalTextEdits)
.WithDetail(detail)
.WithDocumentation($"```bicep\n{new Snippet(snippet).FormatDocumentation()}\n```")
.WithDocumentation(MarkdownHelper.CodeBlock(new Snippet(snippet).FormatDocumentation()))
.WithSortText(GetSortText(label, priority))
.Build();

Expand Down Expand Up @@ -2109,29 +2110,26 @@ private static string FormatPropertyDocumentation(TypeProperty property)
{
var buffer = new StringBuilder();

buffer.Append($"Type: `{property.TypeReference.Type}`{MarkdownNewLine}");
buffer.Append(MarkdownHelper.AppendNewline($"Type: `{property.TypeReference.Type}`"));

if (property.Flags.HasFlag(TypePropertyFlags.ReadOnly))
{
// this case will be used for dot property access completions
// this flag is not possible in property name completions
buffer.Append($"Read-only property{MarkdownNewLine}");
buffer.Append(MarkdownHelper.AppendNewline($"Read-only property"));
}

if (property.Flags.HasFlag(TypePropertyFlags.WriteOnly))
{
buffer.Append($"Write-only property{MarkdownNewLine}");
buffer.Append(MarkdownHelper.AppendNewline($"Write-only property"));
}

if (property.Flags.HasFlag(TypePropertyFlags.Constant))
{
buffer.Append($"Requires a compile-time constant value.{MarkdownNewLine}");
buffer.Append(MarkdownHelper.AppendNewline($"Requires a compile-time constant value."));
}

if (property.Description is not null)
{
buffer.Append($"{property.Description}{MarkdownNewLine}");
}
buffer.Append(MarkdownHelper.AppendNewline(property.Description));

return buffer.ToString();
}
Expand All @@ -2140,13 +2138,19 @@ private static string FormatPropertyDocumentation(TypeProperty property)
{
if (symbol is DeclaredSymbol declaredSymbol && declaredSymbol.DeclaringSyntax is DecorableSyntax decorableSyntax)
{
var documentation = DescriptionHelper.TryGetFromDecorator(model, decorableSyntax);
var buffer = new StringBuilder();

if (declaredSymbol is ParameterSymbol)
{
documentation = $"Type: {declaredSymbol.Type}" + (documentation is null ? "" : $"{MarkdownNewLine}{documentation}");
buffer.Append(MarkdownHelper.AppendNewline($"Type: `{declaredSymbol.Type}`"));
}
return documentation;

var documentation = DescriptionHelper.TryGetFromDecorator(model, decorableSyntax);
buffer.Append(MarkdownHelper.AppendNewline(documentation));

return buffer.ToString();
}

return null;
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/Bicep.LangServer/Completions/CompletionItemBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class CompletionItemBuilder
private readonly CompletionItemKind kind;
private readonly string label;

private CompletionItemLabelDetails? labelDetails;
private TextEditContainer? additionalTextEdits;
private Container<string>? commitCharacters;
private string? detail;
Expand Down Expand Up @@ -41,6 +42,7 @@ public CompletionItem Build()
return new()
{
Label = this.label,
LabelDetails = this.labelDetails,
Kind = kind,

AdditionalTextEdits = this.additionalTextEdits,
Expand Down Expand Up @@ -141,6 +143,17 @@ public CompletionItemBuilder WithSnippet(string snippet)
return this;
}

public CompletionItemBuilder WithLabelDetails(string detail, string description)
{
this.labelDetails = new CompletionItemLabelDetails
{
Detail = detail,
Description = description,
};

return this;
}

public CompletionItemBuilder WithSnippetEdit(Range range, string snippet)
{
this.AssertNoInsertText();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Bicep.LanguageServer.Providers;
using Bicep.LanguageServer.Settings;
using Bicep.LanguageServer.Telemetry;
using Bicep.LanguageServer.Utils;
using Microsoft.Win32;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;

Expand Down Expand Up @@ -226,7 +227,7 @@ private async Task<IEnumerable<CompletionItem>> GetMCRModuleRegistryVersionCompl
.WithFilterText(insertText)
.WithSortText(GetSortText(version, i))
.WithDetail(description)
.WithDocumentation(GetDocumentationLink(documentationUri))
.WithDocumentation(MarkdownHelper.GetDocumentationLink(documentationUri))
.Build();

completions.Add(completionItem);
Expand Down Expand Up @@ -374,7 +375,7 @@ private async Task<IEnumerable<CompletionItem>> GetMCRPathCompletionFromBicepCon
.WithFilterText(insertText)
.WithSortText(GetSortText(label, ModuleCompletionPriority.Alias))
.WithDetail(description)
.WithDocumentation(GetDocumentationLink(documentationUri))
.WithDocumentation(MarkdownHelper.GetDocumentationLink(documentationUri))
.WithFollowupCompletion("module version completion")
.Build();

Expand Down Expand Up @@ -423,7 +424,7 @@ private async Task<IEnumerable<CompletionItem>> GetMCRPathCompletionFromBicepCon
.WithFilterText(insertText)
.WithSortText(GetSortText(label, ModuleCompletionPriority.Alias))
.WithDetail(module.Description)
.WithDocumentation(GetDocumentationLink(module.DocumentationUri))
.WithDocumentation(MarkdownHelper.GetDocumentationLink(module.DocumentationUri))
.WithFollowupCompletion("module version completion")
.Build();
completions.Add(completionItem);
Expand Down Expand Up @@ -520,7 +521,7 @@ private async Task<IEnumerable<CompletionItem>> GetPublicMCRPathCompletions(stri
.WithFilterText(insertText)
.WithSortText(GetSortText(moduleName))
.WithDetail(description)
.WithDocumentation(GetDocumentationLink(documentationUri))
.WithDocumentation(MarkdownHelper.GetDocumentationLink(documentationUri))
.WithFollowupCompletion("module version completion")
.Build();

Expand Down Expand Up @@ -662,11 +663,6 @@ private IEnumerable<CompletionItem> GetACRModuleRegistriesCompletionsFromBicepCo
return completions;
}

private static string? GetDocumentationLink(string? documentationUri)
{
return Bicep.LanguageServer.Utils.MarkdownHelper.GetDocumentationLink(documentationUri);
}

private static string GetSortText(string label, int priority) => $"{priority}_{label}";

private static string GetSortText(string label, ModuleCompletionPriority priority = ModuleCompletionPriority.Default)
Expand Down
Loading

0 comments on commit e3e501a

Please sign in to comment.