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 a /blockstructure endpoint that uses Roslyn's outlining implemention #1209

Merged
merged 14 commits into from
Jun 18, 2018

Conversation

rchande
Copy link

@rchande rchande commented May 31, 2018

We'll use this to implement language-specific folding in the C# extension

using System.Composition;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
Copy link
Contributor

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");
Copy link
Contributor

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?

Copy link
Author

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>());
Copy link
Contributor

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>();
Copy link
Contributor

@DustinCampbell DustinCampbell Jun 1, 2018

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
});
Copy link
Contributor

@DustinCampbell DustinCampbell Jun 1, 2018

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.

Copy link
Contributor

@DustinCampbell DustinCampbell left a 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>>
Copy link
Contributor

@DustinCampbell DustinCampbell Jun 1, 2018

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:

  1. VS Code's FoldingRange also has a FoldingRangeKind that we might want to expose, so we might want a custom model.
  2. QuickFix is a "kitchen sink" model with lots of baggage that even unnecessarily allocates a List<string>. I've never been a fan of overusing it in the protocol. If most of the properties on QuickFix aren't interesting, it's probably cleaner to just define a new BlockStructure model.

@DustinCampbell DustinCampbell changed the title Add a /blockstructure endpoint that uses Roslyn's outlining implement… Add a /blockstructure endpoint that uses Roslyn's outlining implemention Jun 1, 2018
@DustinCampbell
Copy link
Contributor

This aligns well with the /codestructure end point that I'm working on that'll serve as a replacement for /currentfilemembersastree and /currentfilemembersasflat

@@ -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";
Copy link
Contributor

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) };
Copy link
Contributor

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

@rchande
Copy link
Author

rchande commented Jun 1, 2018

@DustinCampbell I incorporated your feedback. Want to sign off?

Copy link
Member

@david-driscoll david-driscoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@DustinCampbell DustinCampbell left a 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; }
Copy link
Contributor

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; }
Copy link
Contributor

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
Copy link
Contributor

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; }
Copy link
Contributor

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?

Copy link
Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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()
Copy link
Contributor

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();
Copy link
Contributor

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.

Copy link
Author

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?

Copy link
Contributor

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)
Copy link
Contributor

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)[|
Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, actually 😄. :
image

Copy link
Contributor

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 });
Copy link
Contributor

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);
Copy link
Contributor

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
Copy link
Contributor

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();
Copy link
Author

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

Copy link
Contributor

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.

@rchande
Copy link
Author

rchande commented Jun 6, 2018

@DustinCampbell I think I got everything, care to sign off now?

Copy link
Contributor

@DustinCampbell DustinCampbell left a 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();
Copy link
Contributor

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; }
Copy link
Contributor

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
Copy link
Contributor

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;
Copy link
Contributor

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\" />
Copy link
Contributor

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();


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra blank line.

Copy link
Contributor

@DustinCampbell DustinCampbell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely 😄

@rchande rchande merged commit c7b6f80 into OmniSharp:master Jun 18, 2018
rchande pushed a commit that referenced this pull request Jun 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants