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
9 changes: 9 additions & 0 deletions src/OmniSharp.Abstractions/Models/v2/BlockStructureRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using OmniSharp.Mef;

namespace OmniSharp.Models.v2
{
[OmniSharpEndpoint(OmniSharpEndpoints.V2.BlockStructure, typeof(BlockStructureRequest), typeof(BlockStructureResponse))]
public class BlockStructureRequest : SimpleFileRequest
{
}
}
10 changes: 10 additions & 0 deletions src/OmniSharp.Abstractions/Models/v2/BlockStructureResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using OmniSharp.Models.V2;

namespace OmniSharp.Models.v2
{
public class BlockStructureResponse
{
public IEnumerable<CodeFoldingBlock> Spans { get; set; }
}
}
29 changes: 29 additions & 0 deletions src/OmniSharp.Abstractions/Models/v2/CodeFoldingBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace OmniSharp.Models.V2
{
public class CodeFoldingBlock
{
public CodeFoldingBlock(Range textSpan, string type)
{
Range = textSpan;
Kind = type;
}

/// <summary>
/// The span of text to collapse.
/// </summary>
public Range Range { get; }

/// <summary>
/// If the block is one of the types specified in <see cref="CodeFoldingBlockKinds"/>, that type.
/// Otherwise, null.
/// </summary>
public string Kind { get; }
}

public class CodeFoldingBlockKinds
{
public static readonly string Comment = nameof(Comment);
public static readonly string Imports = nameof(Imports);
public static readonly string Region = nameof(Region);
}
}
1 change: 1 addition & 0 deletions src/OmniSharp.Abstractions/OmniSharpEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public static class V2
public const string DebugTestStop = "/v2/debugtest/stop";
public const string DebugTestsInClassGetStartInfo = "/v2/debugtestsinclass/getstartinfo";

public const string BlockStructure = "/v2/blockstructure";
public const string CodeStructure = "/v2/codestructure";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public static object InvokeStatic(this MethodInfo methodInfo, object[] args)

public static object InvokeStatic(this Lazy<MethodInfo> lazyMethodInfo, object[] args)
{
return lazyMethodInfo.InvokeStatic(args);
return lazyMethodInfo.Value.InvokeStatic(args);
}

public static Lazy<FieldInfo> LazyGetField(this Lazy<Type> lazyType, string fieldName, BindingFlags bindingFlags)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections;
using System.Collections.Generic;
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.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using OmniSharp.Extensions;
using OmniSharp.Mef;
using OmniSharp.Models.v2;
using OmniSharp.Models.V2;
using OmniSharp.Services;
using OmniSharp.Utilities;

namespace OmniSharp.Roslyn.CSharp.Services.Structure
{
[OmniSharpHandler(OmniSharpEndpoints.V2.BlockStructure, LanguageNames.CSharp)]
public class BlockStructureService : IRequestHandler<BlockStructureRequest, BlockStructureResponse>
{
private readonly IAssemblyLoader _loader;
private readonly Lazy<Assembly> _featureAssembly;
private readonly Lazy<Type> _blockStructureService;
private readonly Lazy<Type> _blockStructure;
private readonly Lazy<Type> _blockSpan;
private readonly Lazy<MethodInfo> _getBlockStructure;
private readonly MethodInfo _getSpans;
private readonly MethodInfo _getIsCollpasible;
private readonly MethodInfo _getTextSpan;
private readonly MethodInfo _getType;
private readonly OmniSharpWorkspace _workspace;

[ImportingConstructor]
public BlockStructureService(IAssemblyLoader loader, OmniSharpWorkspace workspace)
{
_workspace = workspace;
_loader = loader;
_featureAssembly = _loader.LazyLoad(Configuration.RoslynFeatures);

_blockStructureService = _featureAssembly.LazyGetType("Microsoft.CodeAnalysis.Structure.BlockStructureService");
_blockStructure = _featureAssembly.LazyGetType("Microsoft.CodeAnalysis.Structure.BlockStructure");
_blockSpan = _featureAssembly.LazyGetType("Microsoft.CodeAnalysis.Structure.BlockSpan");

_getBlockStructure = _blockStructureService.LazyGetMethod("GetBlockStructure");
_getSpans = _blockStructure.Value.GetProperty("Spans").GetMethod;
_getIsCollpasible = _blockSpan.Value.GetProperty("IsCollapsible").GetMethod;
_getTextSpan = _blockSpan.Value.GetProperty("TextSpan").GetMethod;
_getType = _blockSpan.Value.GetProperty("Type").GetMethod;
}

public async Task<BlockStructureResponse> Handle(BlockStructureRequest request)
{
var document = _workspace.GetDocument(request.FileName);
var text = await document.GetTextAsync();

var service = _blockStructureService.LazyGetMethod("GetService").InvokeStatic(new[] { document });

var structure = _getBlockStructure.Invoke<object>(service, new object[] { document, CancellationToken.None });
var spans = _getSpans.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.

nit: extra blank line.

var outliningSpans = new List<CodeFoldingBlock>();
foreach (var span in spans)
{
if (_getIsCollpasible.Invoke<bool>(span, Array.Empty<object>()))
{
var textSpan = _getTextSpan.Invoke<TextSpan>(span, Array.Empty<object>());

outliningSpans.Add(new CodeFoldingBlock(
text.GetRangeFromSpan(textSpan),
type: ConvertToWellKnownBlockType(_getType.Invoke<string>(span, Array.Empty<object>()))));
}
}

return new BlockStructureResponse() { Spans = outliningSpans };
}

private string ConvertToWellKnownBlockType(string kind)
{
return kind == CodeFoldingBlockKinds.Comment ||
kind == CodeFoldingBlockKinds.Imports ||
kind == CodeFoldingBlockKinds.Region
? kind
: null;
}
}
}
56 changes: 56 additions & 0 deletions tests/OmniSharp.Roslyn.CSharp.Tests/BlockStructureFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Linq;
using System.Threading.Tasks;
using OmniSharp.Models.v2;
using OmniSharp.Roslyn.CSharp.Services.Structure;
using TestUtility;
using Xunit;
using Xunit.Abstractions;

namespace OmniSharp.Roslyn.CSharp.Tests
{
public class BlockStructureFacts : AbstractSingleRequestHandlerTestFixture<BlockStructureService>
{
public BlockStructureFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture)
: base(output, sharedOmniSharpHostFixture)
{
}

protected override string EndpointName => OmniSharpEndpoints.V2.BlockStructure;

[Fact]
public async Task UsesRoslynBlockStructureService()
{
var testFile = new TestFile("foo.cs", @"class Foo[|
{
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. 😄

{
}|]
}|]
}|]");
var text = testFile.Content.Text;

var lineSpans = (await GetResponseAsync(testFile)).Spans
.Select(b => b.Range)
.ToArray();

var expected = testFile.Content.GetSpans()
.Select(span => testFile.Content.GetRangeFromSpan(span).ToRange()).ToArray();

Assert.Equal(expected, lineSpans);
}

private Task<BlockStructureResponse> GetResponseAsync(TestFile testFile)
{
SharedOmniSharpTestHost.AddFilesToWorkspace(testFile);
var request = new BlockStructureRequest
{
FileName = testFile.FileName,
};

var requestHandler = GetRequestHandler(SharedOmniSharpTestHost);
return requestHandler.Handle(request);
}
}
}