-
Notifications
You must be signed in to change notification settings - Fork 427
Support inlay hints #2357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Support inlay hints #2357
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ba3b918
Support inlay hints
333fred 2fef4e0
Merge branch 'master' into inlay-hints
333fred 3579cdd
Merge branch 'master' into inlay-hints
filipw dc67109
Update src/OmniSharp.Roslyn.CSharp/Services/InlayHints/InlayHintServi…
filipw 3636ecd
Update src/OmniSharp.Abstractions/Models/v1/InlayHints/InlayHintReque…
filipw 460f14e
Merge branch 'master' into inlay-hints
JoeRobich 9ed057d
Ensure tests are runtime-independent.
333fred File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
src/OmniSharp.Abstractions/LoggingExtensions/LoggingExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| using System.Diagnostics; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Text; | ||
|
|
||
| #nullable enable | ||
|
|
||
| namespace Microsoft.Extensions.Logging | ||
| { | ||
| public static class LoggingExtensions | ||
| { | ||
| public static void Log(this ILogger logger, LogLevel logLevel, [InterpolatedStringHandlerArgument("logger", "logLevel")] LoggerInterpolatedStringHandler handler) | ||
| { | ||
| logger.Log(logLevel, handler.ToString()); | ||
| } | ||
| } | ||
|
|
||
| [InterpolatedStringHandler] | ||
| public struct LoggerInterpolatedStringHandler | ||
| { | ||
| private readonly StringBuilder? _builder; | ||
| public LoggerInterpolatedStringHandler(int literalLength, int formattedCount, ILogger logger, LogLevel level, out bool shouldAppend) | ||
| { | ||
| if (logger.IsEnabled(level)) | ||
| { | ||
| shouldAppend = true; | ||
| _builder = new(literalLength); | ||
| } | ||
| else | ||
| { | ||
| shouldAppend = false; | ||
| _builder = null; | ||
| } | ||
| } | ||
|
|
||
| public void AppendLiteral(string literal) | ||
| { | ||
| Debug.Assert(_builder != null); | ||
| _builder!.Append(literal); | ||
| } | ||
|
|
||
| public void AppendFormatted<T>(T t) | ||
| { | ||
| Debug.Assert(_builder != null); | ||
| _builder!.Append(t?.ToString()); | ||
| } | ||
|
|
||
| public void AppendFormatted<T>(T t, int alignment, string format) | ||
| { | ||
| Debug.Assert(_builder != null); | ||
| _builder!.Append(string.Format($"{{0,{alignment}:{format}}}", t)); | ||
| } | ||
|
|
||
| public override string ToString() | ||
| { | ||
| return _builder?.ToString() ?? string.Empty; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #if !NET6_0_OR_GREATER | ||
| namespace System.Runtime.CompilerServices | ||
| { | ||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] | ||
| internal sealed class InterpolatedStringHandlerAttribute : Attribute | ||
| { | ||
| } | ||
| [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] | ||
| internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute | ||
| { | ||
| public InterpolatedStringHandlerArgumentAttribute(string argument) => Arguments = new string[] { argument }; | ||
|
|
||
| public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) => Arguments = arguments; | ||
|
|
||
| public string[] Arguments { get; } | ||
| } | ||
| } | ||
| #endif |
35 changes: 35 additions & 0 deletions
35
src/OmniSharp.Abstractions/Models/v1/InlayHints/InlayHint.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| using OmniSharp.Models.V2; | ||
|
|
||
| #nullable enable annotations | ||
|
|
||
| namespace OmniSharp.Models.v1.InlayHints; | ||
|
|
||
| public sealed record InlayHint | ||
| { | ||
| public Point Position { get; set; } | ||
| public string Label { get; set; } | ||
| public string? Tooltip { get; set; } | ||
| public (string SolutionVersion, int Position) Data { get; set; } | ||
|
|
||
| #nullable enable | ||
| public override string ToString() | ||
| { | ||
| return $"InlineHint {{ {nameof(Position)} = {Position}, {nameof(Label)} = {Label}, {nameof(Tooltip)} = {Tooltip} }}"; | ||
| } | ||
|
|
||
| public bool Equals(InlayHint? other) | ||
| { | ||
| if (ReferenceEquals(this, other)) return true; | ||
| if (other is null) return false; | ||
|
|
||
| return Position == other.Position && Label == other.Label && Tooltip == other.Tooltip; | ||
| } | ||
|
|
||
| public override int GetHashCode() => (Position, Label, Tooltip).GetHashCode(); | ||
| } | ||
|
|
||
| public enum InlayHintKind | ||
| { | ||
| Type = 1, | ||
| Parameter = 2, | ||
| } |
13 changes: 13 additions & 0 deletions
13
src/OmniSharp.Abstractions/Models/v1/InlayHints/InlayHintRequest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using OmniSharp.Mef; | ||
| using OmniSharp.Models.V2; | ||
|
|
||
| #nullable enable annotations | ||
|
|
||
| namespace OmniSharp.Models.v1.InlayHints; | ||
|
|
||
|
|
||
| [OmniSharpEndpoint(OmniSharpEndpoints.InlayHint, typeof(InlayHintRequest), typeof(InlayHintResponse))] | ||
| public record InlayHintRequest : IRequest | ||
| { | ||
| public Location Location { get; set; } | ||
| } | ||
9 changes: 9 additions & 0 deletions
9
src/OmniSharp.Abstractions/Models/v1/InlayHints/InlayHintResolveRequest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using OmniSharp.Mef; | ||
|
|
||
| namespace OmniSharp.Models.v1.InlayHints; | ||
|
|
||
| [OmniSharpEndpoint(OmniSharpEndpoints.InlayHintResolve, typeof(InlayHintResolveRequest), typeof(InlayHint))] | ||
| public record InlayHintResolveRequest : IRequest | ||
| { | ||
| public InlayHint Hint { get; set; } | ||
| } |
11 changes: 11 additions & 0 deletions
11
src/OmniSharp.Abstractions/Models/v1/InlayHints/InlayHintResponse.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #nullable enable annotations | ||
|
|
||
| using System.Collections.Generic; | ||
|
|
||
| namespace OmniSharp.Models.v1.InlayHints; | ||
|
|
||
| public record InlayHintResponse | ||
| { | ||
| public static readonly InlayHintResponse None = new() { InlayHints = new() }; | ||
| public List<InlayHint> InlayHints { get; set; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
src/OmniSharp.Roslyn.CSharp/Services/InlayHints/InlayHintService.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Composition; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.InlineHints; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Options; | ||
| using OmniSharp.Extensions; | ||
| using OmniSharp.Mef; | ||
| using OmniSharp.Models.v1.InlayHints; | ||
| using OmniSharp.Options; | ||
| using OmniSharp.Roslyn.CSharp.Helpers; | ||
|
|
||
| #nullable enable | ||
|
|
||
| namespace OmniSharp.Roslyn.CSharp.Services.InlayHints; | ||
|
|
||
| [Shared] | ||
| [OmniSharpHandler(OmniSharpEndpoints.InlayHint, LanguageNames.CSharp)] | ||
| [OmniSharpHandler(OmniSharpEndpoints.InlayHintResolve, LanguageNames.CSharp)] | ||
| internal class InlayHintService : | ||
| IRequestHandler<InlayHintRequest, InlayHintResponse>, | ||
| IRequestHandler<InlayHintResolveRequest, InlayHint> | ||
| { | ||
| private readonly OmniSharpWorkspace _workspace; | ||
| private readonly IOptionsMonitor<OmniSharpOptions> _omniSharpOptions; | ||
| private readonly ILogger _logger; | ||
| private readonly InlineHintCache _cache; | ||
| private readonly FormattingOptions _formattingOptions; | ||
|
|
||
| [ImportingConstructor] | ||
| public InlayHintService(OmniSharpWorkspace workspace, FormattingOptions formattingOptions, ILoggerFactory loggerFactory, IOptionsMonitor<OmniSharpOptions> omniSharpOptions) | ||
| { | ||
| _workspace = workspace; | ||
| _formattingOptions = formattingOptions; | ||
| _logger = loggerFactory.CreateLogger<InlayHintService>(); | ||
| _omniSharpOptions = omniSharpOptions; | ||
| _cache = new(_logger); | ||
| } | ||
| public async Task<InlayHintResponse> Handle(InlayHintRequest request) | ||
|
filipw marked this conversation as resolved.
|
||
| { | ||
| var document = _workspace.GetDocument(request.Location.FileName); | ||
| if (document == null) | ||
| { | ||
| _logger.Log(LogLevel.Warning, $"Inlay hints requested for document not in workspace {request.Location}"); | ||
| return InlayHintResponse.None; | ||
| } | ||
|
|
||
| var sourceText = await document.GetTextAsync(); | ||
| var mappedSpan = sourceText.GetSpanFromRange(request.Location.Range); | ||
|
|
||
| var inlayHintsOptions = _omniSharpOptions.CurrentValue.RoslynExtensionsOptions.InlayHintsOptions; | ||
| var options = new OmniSharpInlineHintsOptions | ||
| { | ||
| ParameterOptions = new() | ||
| { | ||
| EnabledForParameters = inlayHintsOptions.EnableForParameters, | ||
| ForIndexerParameters = inlayHintsOptions.ForIndexerParameters, | ||
| ForLiteralParameters = inlayHintsOptions.ForLiteralParameters, | ||
| ForObjectCreationParameters = inlayHintsOptions.ForObjectCreationParameters, | ||
| ForOtherParameters = inlayHintsOptions.ForOtherParameters, | ||
| SuppressForParametersThatDifferOnlyBySuffix = inlayHintsOptions.SuppressForParametersThatDifferOnlyBySuffix, | ||
| SuppressForParametersThatMatchArgumentName = inlayHintsOptions.SuppressForParametersThatMatchArgumentName, | ||
| SuppressForParametersThatMatchMethodIntent = inlayHintsOptions.SuppressForParametersThatMatchMethodIntent, | ||
| }, | ||
| TypeOptions = new() | ||
| { | ||
| EnabledForTypes = inlayHintsOptions.EnableForTypes, | ||
| ForImplicitObjectCreation = inlayHintsOptions.ForImplicitObjectCreation, | ||
| ForImplicitVariableTypes = inlayHintsOptions.ForImplicitVariableTypes, | ||
| ForLambdaParameterTypes = inlayHintsOptions.ForLambdaParameterTypes, | ||
| } | ||
| }; | ||
|
|
||
| var hints = await OmniSharpInlineHintsService.GetInlineHintsAsync(document, mappedSpan, options, CancellationToken.None); | ||
|
|
||
| var solutionVersion = _workspace.CurrentSolution.Version; | ||
|
|
||
| return new() | ||
| { | ||
| InlayHints = _cache.MapAndCacheHints(hints, document, solutionVersion, sourceText) | ||
| }; | ||
| } | ||
|
|
||
| public async Task<InlayHint> Handle(InlayHintResolveRequest request) | ||
| { | ||
| if (!_cache.TryGetFromCache(request.Hint, out var roslynHint, out var document)) | ||
| { | ||
| return request.Hint; | ||
| } | ||
|
|
||
| var descriptionTags = await roslynHint.GetDescrptionAsync(document, CancellationToken.None); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opened dotnet/roslyn#60217 for this typo |
||
| StringBuilder stringBuilder = new StringBuilder(); | ||
| MarkdownHelpers.TaggedTextToMarkdown( | ||
| descriptionTags, | ||
| stringBuilder, | ||
| _formattingOptions, | ||
| MarkdownFormat.FirstLineAsCSharp, | ||
| out _); | ||
|
|
||
| return request.Hint with | ||
| { | ||
| Tooltip = stringBuilder.ToString(), | ||
| }; | ||
| } | ||
|
|
||
| private class InlineHintCache | ||
| { | ||
| private readonly object _lock = new(); | ||
| private string? _currentVersionString; | ||
| private List<(OmniSharpInlineHint Hint, Document Document)>? _hints; | ||
| private readonly ILogger _logger; | ||
|
|
||
| public InlineHintCache(ILogger logger) | ||
| { | ||
| _logger = logger; | ||
| } | ||
|
|
||
| public List<InlayHint> MapAndCacheHints(ImmutableArray<OmniSharpInlineHint> roslynHints, Document document, VersionStamp solutionVersion, SourceText text) | ||
| { | ||
| var resultList = new List<InlayHint>(); | ||
| var solutionVersionString = solutionVersion.ToString(); | ||
| lock (_lock) | ||
| { | ||
| var hintsList = _currentVersionString == solutionVersionString | ||
| ? _hints | ||
| : new(); | ||
|
|
||
| foreach (var hint in roslynHints) | ||
| { | ||
| var position = hintsList!.Count; | ||
| resultList.Add(new InlayHint() | ||
| { | ||
| Label = string.Concat(hint.DisplayParts), | ||
| Position = text.GetPointFromPosition(hint.Span.End), | ||
| Data = (solutionVersionString, position) | ||
| }); | ||
|
|
||
| hintsList.Add((hint, document)); | ||
| } | ||
|
|
||
| _currentVersionString = solutionVersionString; | ||
| _hints = hintsList; | ||
| } | ||
|
|
||
| return resultList; | ||
| } | ||
|
|
||
| public bool TryGetFromCache(InlayHint hint, out OmniSharpInlineHint roslynHint, [NotNullWhen(true)] out Document? document) | ||
| { | ||
| (roslynHint, document) = (default, null); | ||
| lock (_lock) | ||
| { | ||
| if (_hints is null) | ||
| { | ||
| _logger.LogWarning("Attempted to resolve hint before hints were requested"); | ||
| return false; | ||
| } | ||
|
|
||
| if (_currentVersionString == hint.Data.SolutionVersion) | ||
| { | ||
| if (hint.Data.Position >= _hints.Count) | ||
| { | ||
| _logger.LogWarning("Hint position is not found in the list"); | ||
| roslynHint = default; | ||
| return false; | ||
| } | ||
|
|
||
| (roslynHint, document) = _hints[hint.Data.Position]; | ||
| return true; | ||
| } | ||
| else | ||
| { | ||
| _logger.LogInformation("Requested hint for outdated solution version"); | ||
| roslynHint = default; | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| namespace OmniSharp.Options | ||
| { | ||
| public struct InlayHintsOptions | ||
| { | ||
| public static readonly InlayHintsOptions AllOn = new() | ||
| { | ||
| EnableForParameters = true, | ||
| ForLiteralParameters = true, | ||
| ForIndexerParameters = true, | ||
| ForObjectCreationParameters = true, | ||
| ForOtherParameters = true, | ||
| SuppressForParametersThatDifferOnlyBySuffix = true, | ||
| SuppressForParametersThatMatchMethodIntent = true, | ||
| SuppressForParametersThatMatchArgumentName = true, | ||
| EnableForTypes = true, | ||
| ForImplicitVariableTypes = true, | ||
| ForLambdaParameterTypes = true, | ||
| ForImplicitObjectCreation = true | ||
| }; | ||
|
|
||
| public bool EnableForParameters { get; set; } | ||
| public bool ForLiteralParameters { get; set; } | ||
| public bool ForIndexerParameters { get; set; } | ||
| public bool ForObjectCreationParameters { get; set; } | ||
| public bool ForOtherParameters { get; set; } | ||
| public bool SuppressForParametersThatDifferOnlyBySuffix { get; set; } | ||
| public bool SuppressForParametersThatMatchMethodIntent { get; set; } | ||
| public bool SuppressForParametersThatMatchArgumentName { get; set; } | ||
|
|
||
| public bool EnableForTypes { get; set; } | ||
| public bool ForImplicitVariableTypes { get; set; } | ||
| public bool ForLambdaParameterTypes { get; set; } | ||
| public bool ForImplicitObjectCreation { get; set; } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.