-
Notifications
You must be signed in to change notification settings - Fork 420
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
Changes from all commits
ab2a160
8055a70
163336d
2b675c3
e57dbc4
1b76656
53828f6
27d5eb3
a1c7841
6d94006
be90f02
af6ee65
473500b
d8dbd6b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
{ | ||
} | ||
} |
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; } | ||
} | ||
} |
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); | ||
} | ||
} |
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; | ||
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>()); | ||
|
||
|
||
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. 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; | ||
} | ||
} | ||
} |
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)[| | ||
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. 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 commentThe 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 commentThe 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); | ||
} | ||
} | ||
} |
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.