-
Notifications
You must be signed in to change notification settings - Fork 419
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 a /blockstructure endpoint that uses Roslyn's outlining implemention #1209
Conversation
using System.Composition; | ||
using System.Reflection; | ||
using System.Threading; | ||
using System.Threading.Tasks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort with System namespaces on top.
_getBlockStructure = _blockStructureService.LazyGetMethod("GetBlockStructure"); | ||
_getSpans = _blockStructure.Value.GetProperty("Spans"); | ||
_getIsCollpasible = _blockSpan.Value.GetProperty("IsCollapsible"); | ||
_getTextSpan = _blockSpan.Value.GetProperty("TextSpan"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you file an issue on https://github.com/dotnet/roslyn/issues to add a public API here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var service = _blockStructureService.LazyGetMethod("GetService").InvokeStatic(new[] { document }); | ||
|
||
var structure = _getBlockStructure.Invoke<object>(service, new object[] { document, CancellationToken.None }); | ||
IEnumerable spans = _getSpans.GetMethod.Invoke<IEnumerable>(structure, Array.Empty<object>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var?
var structure = _getBlockStructure.Invoke<object>(service, new object[] { document, CancellationToken.None }); | ||
IEnumerable spans = _getSpans.GetMethod.Invoke<IEnumerable>(structure, Array.Empty<object>()); | ||
|
||
List<QuickFix> outliningSpans = new List<QuickFix>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var?
{ | ||
Line = lines.GetLineFromPosition(textSpan.Start).LineNumber, | ||
EndLine = lines.GetLineFromPosition(textSpan.End).LineNumber | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there are reason not to add Column
and EndColumn
too? I know that VS Code's FoldingRange
only handles lines but other editors might find this useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm very glad you're adding this!
namespace OmniSharp.Roslyn.CSharp.Services.Structure | ||
{ | ||
[OmniSharpHandler(OmniSharpEndpoints.BlockStructure, LanguageNames.CSharp)] | ||
public class BlockStructureService : IRequestHandler<Request, IEnumerable<QuickFix>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is QuickFix
the right thing? I have two thoughts:
- VS Code's
FoldingRange
also has aFoldingRangeKind
that we might want to expose, so we might want a custom model. QuickFix
is a "kitchen sink" model with lots of baggage that even unnecessarily allocates aList<string>
. I've never been a fan of overusing it in the protocol. If most of the properties onQuickFix
aren't interesting, it's probably cleaner to just define a newBlockStructure
model.
This aligns well with the |
@@ -25,6 +25,7 @@ public static class OmniSharpEndpoints | |||
public const string Rename = "/rename"; | |||
public const string SignatureHelp = "/signatureHelp"; | |||
public const string MembersTree = "/currentfilemembersastree"; | |||
public const string BlockStructure = "/blockstructure"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a V2
endpoint like other new endpoints that have been added?
var lineSpans = (await GetResponseAsync(testFile1)). | ||
Select(l => (l.Line, l.EndLine)).ToArray(); | ||
|
||
var expected = new [] { (0, 8), (2, 7), (4, 6) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a markup feature for tests in OmniSharp that is similar to Roslyn's that you can use instead of raw numbers. You can see it used in places like this: https://github.com/OmniSharp/omnisharp-roslyn/blob/master/tests/OmniSharp.Roslyn.CSharp.Tests/FormattingFacts.cs#L141
@DustinCampbell I incorporated your feedback. Want to sign off? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for all of the additional feedback @rchande. I gave this PR another serious look. Most of my feedback are little nits or minor details. However, I'd like to consider whether the protocol endpoint that's being introduced has the right shape. After all, this is essentially a public OmniSharp API. Just passing the Roslyn types through is possibly not the right thing. Could you take a look at that feedback?
/// <summary> | ||
/// The span of text to collapse. | ||
/// </summary> | ||
public Range TextSpan { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might consider using "Range" terminology here. I don't think "Span" appears anywhere in OmniSharp's protocol.
/// <summary> | ||
/// The span of text to display in the hint on mouse hover. | ||
/// </summary> | ||
public Range HintSpan { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here.
@@ -0,0 +1,30 @@ | |||
namespace OmniSharp.Models.V2 | |||
{ | |||
public class BlockSpan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be more specific to the purpose its indeed? E.g. CodeFoldingBlock
or something like that?
/// </summary> | ||
public string BannerText { get; } | ||
|
||
public string Type { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this? What values are legal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is property on the internal Roslyn types that's also stringly-typed. I wasn't sure if we would need to use it at some point so I left it in, but I can also remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be good to think through the protocol API that makes sense for OmniSharp, rather than serving up internal API structures from Roslyn which haven't gone through a review process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, we should provide the possible values here so that OmniSharp consumers understand what it's used for.
Column == point.Column; | ||
} | ||
|
||
public override int GetHashCode() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will conflict with my PR. Sorry about that!
|
||
|
||
var expected = testFile.Content.GetSpans() | ||
.Select(t => t.ToRange(text.Lines)).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that there's already a TestContent.GetRangeFromSpan(...)
helper that you can use. Ultimately that calls an existing extension method in TestUtility on SourceText
: https://github.com/OmniSharp/omnisharp-roslyn/blob/master/tests/TestUtility/SourceTextExtensions.cs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That extension returns TestUtiltity.TextRange
, but my product code returns OmniSharp.Models.V2.Range
. These types have essentially the same shape and there are even duplicated test/product Point
classes. I don't really want to write code that knows how to compare test and product Range/Points. Thoughts on the best way to resolve this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TestUtility.TextRange
has a ToRange
method that returns an OmniSharp.Models.V2.Range
. Maybe just use that and not write any extra code? 👍
{ | ||
public static class TextSpanExtensions | ||
{ | ||
public static Range ToRange(this TextSpan textSpan, TextLineCollection lines) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need to add this extension method. See my comment below.
{ | ||
void M()[| | ||
{ | ||
if (false)[| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this isn't collapsible in Visual Studio. I think we might be conflating the vertical indicators in VS with code folding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh. I thought we hadn't done that. I stand happily corrected. 😄
|
||
private async Task<BlockStructureResponse> GetResponseAsync(TestFile testFile) | ||
{ | ||
SharedOmniSharpTestHost.AddFilesToWorkspace(new[] { testFile }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you don't need to construct an array here. It's a params array parameter.
}; | ||
|
||
var requestHandler = GetRequestHandler(SharedOmniSharpTestHost); | ||
return await requestHandler.Handle(request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to make this method async and await here. Just return the Task.
namespace OmniSharp.Models.v2 | ||
{ | ||
[OmniSharpEndpoint(OmniSharpEndpoints.V2.BlockStructure, typeof(BlockStructureRequest), typeof(BlockStructureResponse))] | ||
public class BlockStructureRequest : Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably just inherit from SimpleFileRequest
. It doesn't need to support all of the properties from Request
.
|
||
public class CodeFoldingBlockKind | ||
{ | ||
public static readonly string Comment = nameof(Comment).ToLowerInvariant(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose to include these values because the VS Code folding API has special handling for these types of blocks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! The type should probably be plural -- CodeFoldingBlockKinds
.
@DustinCampbell I think I got everything, care to sign off now? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really, really want to sign off. 😄 However, it looks like there's a mismerge in this PR that causes C# 7.3 support to be disabled. Also, could you take a look at the minor renames to the protocol that I suggested?
|
||
public class CodeFoldingBlockKind | ||
{ | ||
public static readonly string Comment = nameof(Comment).ToLowerInvariant(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! The type should probably be plural -- CodeFoldingBlockKinds
.
/// If the block is one of the types specified in <see cref="CodeFoldingBlockKind"/>, that type. | ||
/// Otherwise, null. | ||
/// </summary> | ||
public string Type { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind
?
public string Type { get; } | ||
} | ||
|
||
public class CodeFoldingBlockKind |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be plural: CodeFoldingBlockKinds
.
@@ -47,7 +47,6 @@ public static LanguageVersion ToLanguageVersion(string propertyValue) | |||
case "7": return LanguageVersion.CSharp7; | |||
case "7.1": return LanguageVersion.CSharp7_1; | |||
case "7.2": return LanguageVersion.CSharp7_2; | |||
case "7.3": return LanguageVersion.CSharp7_3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you meant to break C# 7.3 did you? Mis-merge?
@@ -16,4 +16,8 @@ | |||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisCSharpWorkspacesVersion)" /> | |||
</ItemGroup> | |||
|
|||
<ItemGroup> | |||
<Folder Include="Services\Blocks\" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks unnecessary.
.Select(b => b.Range) | ||
.ToArray(); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extra blank line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lovely 😄
Forgot to do this in #1209 :)
We'll use this to implement language-specific folding in the C# extension