Skip to content
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

Add TextEdits support to InlayHints #2385

Merged
merged 10 commits into from
Apr 18, 2022
Prev Previous commit
Next Next commit
Add TextEdits to InlayHints
  • Loading branch information
JoeRobich committed Apr 16, 2022
commit 4224a2bcf9934af538e0279607eb7583a4bdc395
64 changes: 61 additions & 3 deletions src/OmniSharp.Abstractions/Models/v1/InlayHints/InlayHint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,88 @@ namespace OmniSharp.Models.v1.InlayHints;

public sealed record InlayHint
{
/// <summary>
/// The position of this hint.
/// </summary>
public Point Position { get; set; }

/// <summary>
/// The label of this hint. A human readable string.
/// </summary>
public string Label { get; set; }

/// <summary>
/// The tooltip text when you hover over this item.
/// </summary>
public string? Tooltip { get; set; }

/// <summary>
/// Optional text edits that are performed when accepting this inlay hint.
/// </summary>
public LinePositionSpanTextChange[]? TextEdits { get; set; }

/// <summary>
/// A data entry field that is preserved on a inlay hint between a <see cref="InlayHintRequest" /> and a <see cref="InlayHintResolveRequest" />.
/// </summary>
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} }}";
var textEdits = TextEdits is null ? "null" : $"[ {string.Join<LinePositionSpanTextChange>(", ", TextEdits)} ]";
return $"InlayHint {{ {nameof(Position)} = {Position}, {nameof(Label)} = {Label}, {nameof(Tooltip)} = {Tooltip ?? "null"}, {nameof(TextEdits)} = {textEdits} }}";
}

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;
return Position == other.Position
&& Label == other.Label
&& Tooltip == other.Tooltip
&& TextEditsEqual(TextEdits, other.TextEdits);
}

private static bool TextEditsEqual(LinePositionSpanTextChange[]? a, LinePositionSpanTextChange[]? b)
{
if (a is null)
{
return b is null;
}

if (b is null)
{
return false;
}

if (a.Length != b.Length)
{
return false;
}

for (int index = 0; index < a.Length; index++)
{
if (!a[index].Equals(b[index]))
{
return false;
}
}

return true;
}

public override int GetHashCode() => (Position, Label, Tooltip).GetHashCode();
public override int GetHashCode() => (Position, Label, Tooltip, TextEdits?.GetHashCode() ?? 0).GetHashCode();
}

public enum InlayHintKind
{
/// <summary>
/// An inlay hint that for a type annotation.
JoeRobich marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
Type = 1,
/// <summary>
/// An inlay hint that is for a parameter.
/// </summary>
Parameter = 2,
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public override string ToString()
{
var displayText = NewText != null
? NewText.Replace("\r", @"\r").Replace("\n", @"\n").Replace("\t", @"\t")
: string.Empty;
: "null";

return $"StartLine={StartLine}, StartColumn={StartColumn}, EndLine={EndLine}, EndColumn={EndColumn}, NewText='{displayText}'";
return $"LinePositionSpanTextChange {{ StartLine={StartLine}, StartColumn={StartColumn}, EndLine={EndLine}, EndColumn={EndColumn}, NewText={displayText} }}";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -12,6 +13,7 @@
using Microsoft.Extensions.Options;
using OmniSharp.Extensions;
using OmniSharp.Mef;
using OmniSharp.Models;
using OmniSharp.Models.v1.InlayHints;
using OmniSharp.Options;
using OmniSharp.Roslyn.CSharp.Helpers;
Expand Down Expand Up @@ -139,6 +141,7 @@ public List<InlayHint> MapAndCacheHints(ImmutableArray<OmniSharpInlineHint> rosl
{
Label = string.Concat(hint.DisplayParts),
Position = text.GetPointFromPosition(hint.Span.End),
TextEdits = ConvertToTextChanges(hint.ReplacementTextChange, text),
Data = (solutionVersionString, position)
});

Expand All @@ -152,6 +155,27 @@ public List<InlayHint> MapAndCacheHints(ImmutableArray<OmniSharpInlineHint> rosl
return resultList;
}

internal static LinePositionSpanTextChange[]? ConvertToTextChanges(TextChange? textChange, SourceText sourceText)
{
if (textChange.HasValue == false)
{
return Array.Empty<LinePositionSpanTextChange>();
}

var changeLinePositionSpan = sourceText.Lines.GetLinePositionSpan(textChange.Value.Span);
return new[]
{
new LinePositionSpanTextChange()
JoeRobich marked this conversation as resolved.
Show resolved Hide resolved
{
NewText = textChange.Value.NewText ?? "",
StartLine = changeLinePositionSpan.Start.Line,
StartColumn = changeLinePositionSpan.Start.Character,
EndLine = changeLinePositionSpan.End.Line,
EndColumn = changeLinePositionSpan.End.Character
}
};
}

public bool TryGetFromCache(InlayHint hint, out OmniSharpInlineHint roslynHint, [NotNullWhen(true)] out Document? document)
{
(roslynHint, document) = (default, null);
Expand Down
45 changes: 23 additions & 22 deletions tests/OmniSharp.Roslyn.CSharp.Tests/InlayHintsFacts.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OmniSharp.Models;
using OmniSharp.Models.v1.InlayHints;
using OmniSharp.Models.V2;
using OmniSharp.Options;
Expand Down Expand Up @@ -36,10 +37,10 @@ class C { }
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 3, Column = 2 }, Label = "param1: ", Tooltip = null },
new InlayHint { Position = new Point { Line = 3, Column = 9 }, Label = "paramB: ", Tooltip = null },
new InlayHint { Position = new Point { Line = 1, Column = 4 }, Label = "C ", Tooltip = null },
new InlayHint { Position = new Point { Line = 2, Column = 4 }, Label = "C ", Tooltip = null }
new InlayHint { Position = new Point { Line = 3, Column = 2 }, Label = "param1: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 3, StartColumn = 2, EndLine = 3, EndColumn = 2, NewText = "param1: " } } },
new InlayHint { Position = new Point { Line = 3, Column = 9 }, Label = "paramB: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 3, StartColumn = 9, EndLine = 3, EndColumn = 9, NewText = "paramB: " } } },
new InlayHint { Position = new Point { Line = 1, Column = 4 }, Label = "C ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 0, EndLine = 1, EndColumn = 3, NewText = "C" } } },
new InlayHint { Position = new Point { Line = 2, Column = 4 }, Label = "C ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 2, StartColumn = 0, EndLine = 2, EndColumn = 3, NewText = "C" } } },
},
response.InlayHints);

Expand Down Expand Up @@ -87,8 +88,8 @@ public async Task InlayHintsRetrievedForOnlyTypes(string fileName)
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 1, Column = 4 }, Label = "int ", Tooltip = null },
new InlayHint { Position = new Point { Line = 2, Column = 4 }, Label = "int ", Tooltip = null }
new InlayHint { Position = new Point { Line = 1, Column = 4 }, Label = "int ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 0, EndLine = 1, EndColumn = 3, NewText = "int" } } },
new InlayHint { Position = new Point { Line = 2, Column = 4 }, Label = "int ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 2, StartColumn = 0, EndLine = 2, EndColumn = 3, NewText = "int" } } },
},
response.InlayHints);
}
Expand All @@ -112,8 +113,8 @@ public async Task InlayHintsRetrievedForOnlyParameters(string fileName)
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 3, Column = 2 }, Label = "param1: ", Tooltip = null },
new InlayHint { Position = new Point { Line = 3, Column = 9 }, Label = "paramB: ", Tooltip = null },
new InlayHint { Position = new Point { Line = 3, Column = 2 }, Label = "param1: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 3, StartColumn = 2, EndLine = 3, EndColumn = 2, NewText = "param1: " } } },
new InlayHint { Position = new Point { Line = 3, Column = 9 }, Label = "paramB: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 3, StartColumn = 9, EndLine = 3, EndColumn = 9, NewText = "paramB: " } } },
},
response.InlayHints);
}
Expand Down Expand Up @@ -141,7 +142,7 @@ public async Task InlayHintsForVarTypes(string fileName)
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 1, Column = 4 }, Label = "int ", Tooltip = null }
new InlayHint { Position = new Point { Line = 1, Column = 4 }, Label = "int ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 0, EndLine = 1, EndColumn = 3, NewText = "int" } } },
},
response.InlayHints);
}
Expand Down Expand Up @@ -170,8 +171,8 @@ public async Task InlayHintsForLambdaParameterTypes(string fileName)
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 2, Column = 34 }, Label = "int ", Tooltip = null },
new InlayHint { Position = new Point { Line = 2, Column = 37 }, Label = "string ", Tooltip = null }
new InlayHint { Position = new Point { Line = 2, Column = 34 }, Label = "int ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 2, StartColumn = 34, EndLine = 2, EndColumn = 34, NewText = "int " } } },
new InlayHint { Position = new Point { Line = 2, Column = 37 }, Label = "string ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 2, StartColumn = 37, EndLine = 2, EndColumn = 37, NewText = "string " } } }
},
response.InlayHints);
}
Expand Down Expand Up @@ -199,7 +200,7 @@ public async Task InlayHintsForImplicitObjectCreation(string fileName)
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 1, Column = 14 }, Label = " string", Tooltip = null }
new InlayHint { Position = new Point { Line = 1, Column = 14 }, Label = " string", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 14, EndLine = 1, EndColumn = 14, NewText = " string" } } }
},
response.InlayHints);
}
Expand Down Expand Up @@ -228,7 +229,7 @@ public async Task InlayHintsForLiteralParameters(string fileName)
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 1, Column = 2 }, Label = "i: ", Tooltip = null }
new InlayHint { Position = new Point { Line = 1, Column = 2 }, Label = "i: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 2, EndLine = 1, EndColumn = 2, NewText = "i: " } } }
},
response.InlayHints);
}
Expand Down Expand Up @@ -263,8 +264,8 @@ class C
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 3, Column = 2 }, Label = "test: ", Tooltip = null },
new InlayHint { Position = new Point { Line = 3, Column = 9 }, Label = "test: ", Tooltip = null }
new InlayHint { Position = new Point { Line = 3, Column = 2 }, Label = "test: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 3, StartColumn = 2, EndLine = 3, EndColumn = 2, NewText = "test: " } } },
new InlayHint { Position = new Point { Line = 3, Column = 9 }, Label = "test: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 3, StartColumn = 9, EndLine = 3, EndColumn = 9, NewText = "test: " } } }
},
response.InlayHints);
}
Expand Down Expand Up @@ -299,7 +300,7 @@ class C
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 2, Column = 2 }, Label = "c: ", Tooltip = null }
new InlayHint { Position = new Point { Line = 2, Column = 2 }, Label = "c: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 2, StartColumn = 2, EndLine = 2, EndColumn = 2, NewText = "c: " } } }
},
response.InlayHints);
}
Expand Down Expand Up @@ -332,7 +333,7 @@ public async Task InlayHintsForOtherParameters(string fileName)
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 2, Column = 2 }, Label = "test: ", Tooltip = null }
new InlayHint { Position = new Point { Line = 2, Column = 2 }, Label = "test: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 2, StartColumn = 2, EndLine = 2, EndColumn = 2, NewText = "test: " } } }
},
response.InlayHints);
}
Expand Down Expand Up @@ -363,8 +364,8 @@ public async Task InlayHintsSuppressForParametersThatDifferOnlyBySuffix(string f
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 1, Column = 2 }, Label = "test1: ", Tooltip = null },
new InlayHint { Position = new Point { Line = 1, Column = 5 }, Label = "test2: ", Tooltip = null }
new InlayHint { Position = new Point { Line = 1, Column = 2 }, Label = "test1: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 2, EndLine = 1, EndColumn = 2, NewText = "test1: " } } },
new InlayHint { Position = new Point { Line = 1, Column = 5 }, Label = "test2: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 5, EndLine = 1, EndColumn = 5, NewText = "test2: " } } }
},
response.InlayHints);
}
Expand Down Expand Up @@ -397,8 +398,8 @@ class C
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 1, Column = 18 }, Label = "enabled: ", Tooltip = null }
},
new InlayHint { Position = new Point { Line = 1, Column = 18 }, Label = "enabled: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 1, StartColumn = 18, EndLine = 1, EndColumn = 18, NewText = "enabled: " } } }
},
response.InlayHints);
}
}
Expand Down Expand Up @@ -432,7 +433,7 @@ class C
var response = await GetInlayHints(fileName, code, testHost);
AssertEx.Equal(new[]
{
new InlayHint { Position = new Point { Line = 2, Column = 4 }, Label = "i: ", Tooltip = null }
new InlayHint { Position = new Point { Line = 2, Column = 4 }, Label = "i: ", Tooltip = null, TextEdits = new[] { new LinePositionSpanTextChange { StartLine = 2, StartColumn = 4, EndLine = 2, EndColumn = 4, NewText = "i: " } } }
},
response.InlayHints);
}
Expand Down