Skip to content

Commit

Permalink
[closes gh-58] Added tests for Roslyn 2.0 and fixed the logic accordi…
Browse files Browse the repository at this point in the history
…ngly.
  • Loading branch information
ashmind committed Mar 4, 2017
1 parent 2adae7d commit af88998
Show file tree
Hide file tree
Showing 35 changed files with 320 additions and 41 deletions.
2 changes: 1 addition & 1 deletion MirrorSharp.Benchmarks/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"BenchmarkDotNet": "0.9.9",
"Newtonsoft.Json": "9.0.1",
"MirrorSharp.Common": "*",
"MirrorSharp.Tests": "*",
"MirrorSharp.Testing": "*",
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
Expand Down
17 changes: 17 additions & 0 deletions MirrorSharp.Common/Internal/FastJsonWriterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,22 @@ public static void WriteSymbolDisplayPart(this IFastJsonWriter writer, SymbolDis
writer.WriteProperty("selected", true);
writer.WriteEndObject();
}

public static void WriteTaggedTexts<TCollection>(this IFastJsonWriter writer, TCollection texts, bool selected = false)
where TCollection : IEnumerable<TaggedText>
{
foreach (var text in texts) {
writer.WriteTaggedText(text, selected);
}
}

public static void WriteTaggedText(this IFastJsonWriter writer, TaggedText text, bool selected) {
writer.WriteStartObject();
writer.WriteProperty("text", text.Text);
writer.WriteProperty("kind", text.Tag);
if (selected)
writer.WriteProperty("selected", true);
writer.WriteEndObject();
}
}
}
15 changes: 9 additions & 6 deletions MirrorSharp.Common/Internal/Handlers/Shared/CompletionSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Text;
using MirrorSharp.Internal.Reflection;
using MirrorSharp.Internal.Results;

namespace MirrorSharp.Internal.Handlers.Shared {
Expand Down Expand Up @@ -42,7 +43,7 @@ public async Task SelectCompletionAsync(int selectedIndex, WorkSession session,
var change = await session.Completion.Service.GetChangeAsync(session.Document, item, cancellationToken: cancellationToken).ConfigureAwait(false);
session.Completion.CurrentList = null;

var textChanges = ReplaceIncompleteText(session, completion, change.TextChanges);
var textChanges = ReplaceIncompleteText(session, completion, RoslynReflectionFast.GetTextChanges(change));
session.Completion.ChangeEchoPending = true;

var writer = sender.StartJsonMessage("changes");
Expand All @@ -56,18 +57,19 @@ public async Task SelectCompletionAsync(int selectedIndex, WorkSession session,
}

private static ImmutableArray<TextChange> ReplaceIncompleteText(WorkSession session, CompletionList completion, ImmutableArray<TextChange> textChanges) {
if (session.CursorPosition <= completion.DefaultSpan.Start)
var completionSpan = RoslynReflectionFast.GetSpan(completion);
if (session.CursorPosition <= completionSpan.Start)
return textChanges;

if (textChanges.Length == 1) {
// optimization
var span = textChanges[0].Span;
var newStart = Math.Min(span.Start, completion.DefaultSpan.Start);
var newStart = Math.Min(span.Start, completionSpan.Start);
var newLength = Math.Max(span.End, session.CursorPosition) - newStart;
textChanges = ImmutableArray.Create(new TextChange(new TextSpan(newStart, newLength), textChanges[0].NewText));
}
else {
textChanges = textChanges.Insert(0, new TextChange(new TextSpan(completion.DefaultSpan.Start, session.CursorPosition - completion.DefaultSpan.Start), ""));
textChanges = textChanges.Insert(0, new TextChange(new TextSpan(completionSpan.Start, session.CursorPosition - completionSpan.Start), ""));
}
return textChanges;
}
Expand Down Expand Up @@ -100,11 +102,12 @@ private async Task TriggerCompletionAsync(WorkSession session, ICommandResultSen
}

private Task SendCompletionListAsync(CompletionList completionList, ICommandResultSender sender, CancellationToken cancellationToken) {
var completionSpan = RoslynReflectionFast.GetSpan(completionList);
var writer = sender.StartJsonMessage("completions");

writer.WriteProperty("commitChars", new CharListString(completionList.Rules.DefaultCommitCharacters));
writer.WritePropertyName("span");
writer.WriteSpan(completionList.DefaultSpan);
writer.WriteSpan(completionSpan);

var suggestionItem = completionList.SuggestionModeItem;
if (suggestionItem != null) {
Expand All @@ -123,7 +126,7 @@ private Task SendCompletionListAsync(CompletionList completionList, ICommandResu
writer.WriteValue(tag.ToLowerInvariant());
}
writer.WriteEndArray();
if (item.Span != completionList.DefaultSpan) {
if (item.Span != completionSpan) {
writer.WritePropertyName("span");
writer.WriteSpan(item.Span);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,18 @@ private Task SendSignatureHelpAsync([CanBeNull] SignatureHelpItemsData items, IC
if (itemIndex == selectedItemIndex)
writer.WriteProperty("selected", true);
writer.WritePropertyStartArray("parts");
writer.WriteSymbolDisplayParts(item.PrefixDisplayParts);
writer.WriteTaggedTexts(item.PrefixDisplayParts);
var parameterIndex = 0;
foreach (var parameter in item.Parameters) {
if (parameterIndex > 0)
writer.WriteSymbolDisplayParts(item.SeparatorDisplayParts);
writer.WriteTaggedTexts(item.SeparatorDisplayParts);
var selected = items.ArgumentIndex == parameterIndex;
writer.WriteSymbolDisplayParts(parameter.PrefixDisplayParts, selected);
writer.WriteSymbolDisplayParts(parameter.DisplayParts, selected);
writer.WriteSymbolDisplayParts(parameter.SuffixDisplayParts, selected);
writer.WriteTaggedTexts(parameter.PrefixDisplayParts, selected);
writer.WriteTaggedTexts(parameter.DisplayParts, selected);
writer.WriteTaggedTexts(parameter.SuffixDisplayParts, selected);
parameterIndex += 1;
}
writer.WriteSymbolDisplayParts(item.SuffixDisplayParts);
writer.WriteTaggedTexts(item.SuffixDisplayParts);
writer.WriteEndArray();
writer.WriteEndObject();
itemIndex += 1;
Expand Down
4 changes: 2 additions & 2 deletions MirrorSharp.Common/Internal/Handlers/SlowUpdateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ private static void WriteActions(IFastJsonWriterInternal writer, IReadOnlyCollec
if (action is CodeActionWithOptions)
continue;

if (!RoslynInternalCalls.GetIsInvokable(action)) {
WriteActions(writer, RoslynInternalCalls.GetCodeActions(action), session);
if (RoslynReflectionFast.IsInlinable(action)) {
WriteActions(writer, RoslynReflectionFast.GetNestedCodeActions(action), session);
continue;
}
var id = session.CurrentCodeActions.Count;
Expand Down
2 changes: 1 addition & 1 deletion MirrorSharp.Common/Internal/Languages/LanguageBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ [NotNull] CompilationOptions defaultCompilationOptions

private ImmutableArray<ISignatureHelpProviderWrapper> CreateDefaultSignatureHelpProviders() {
return ImmutableArray.CreateRange(
RoslynInternalCalls.GetSignatureHelpProvidersSlow(HostServices)
RoslynReflectionFast.GetSignatureHelpProvidersSlow(HostServices)
.Where(l => l.Metadata.Language == Name)
.Select(l => l.Value)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,71 @@
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Host.Mef;
using AshMind.Extensions;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Text;
using TypeInfo = System.Reflection.TypeInfo;

namespace MirrorSharp.Internal.Reflection {
internal static class RoslynInternalCalls {
internal static class RoslynReflectionFast {
// Roslyn v2
private static readonly Func<CodeAction, bool> _getIsInlinable =
RoslynTypes.CodeAction
.GetProperty("IsInlinable", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
?.GetMethod.CreateDelegate<Func<CodeAction, bool>>();

private static readonly Func<CodeAction, ImmutableArray<CodeAction>> _getNestedCodeActions =
RoslynTypes.CodeAction
.GetProperty("NestedCodeActions", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
?.GetMethod.CreateDelegate<Func<CodeAction, ImmutableArray<CodeAction>>>();

private static readonly Func<CompletionChange, TextChange> _getTextChange =
RoslynTypes.CompletionChange
.GetProperty("TextChange", BindingFlags.Public | BindingFlags.Instance)
?.GetMethod.CreateDelegate<Func<CompletionChange, TextChange>>();

private static readonly Func<CompletionList, TextSpan> _getSpan =
RoslynTypes.CompletionList
.GetProperty("Span", BindingFlags.Public | BindingFlags.Instance)
?.GetMethod.CreateDelegate<Func<CompletionList, TextSpan>>();

// Roslyn v1
private static readonly Func<CodeAction, bool> _getIsInvokable =
RoslynTypes.CodeAction
.GetProperty("IsInvokable", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.GetMethod.CreateDelegate<Func<CodeAction, bool>>();
?.GetMethod.CreateDelegate<Func<CodeAction, bool>>();

private static readonly Func<CodeAction, ImmutableArray<CodeAction>> _getCodeActions =
RoslynTypes.CodeAction
.GetMethod("GetCodeActions", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.CreateDelegate<Func<CodeAction, ImmutableArray<CodeAction>>>();
?.CreateDelegate<Func<CodeAction, ImmutableArray<CodeAction>>>();

public static bool IsInlinable(CodeAction action) {
if (_getIsInlinable == null) // Roslyn v1
return !_getIsInvokable(action);

return _getIsInlinable(action);
}

public static bool GetIsInvokable(CodeAction action) => _getIsInvokable(action);
public static ImmutableArray<CodeAction> GetCodeActions(CodeAction action) => _getCodeActions(action);
public static ImmutableArray<CodeAction> GetNestedCodeActions(CodeAction action) {
if (_getNestedCodeActions == null) // Roslyn v1
return _getCodeActions(action);

return _getNestedCodeActions(action);
}

public static ImmutableArray<TextChange> GetTextChanges(CompletionChange change) {
if (_getTextChange != null) // Roslyn v2, does not populate TextChanges array
return ImmutableArray.Create(_getTextChange(change));

return change.TextChanges;
}

public static TextSpan GetSpan(CompletionList completion) {
if (_getSpan != null) // Roslyn v2, does not populate DefaultSpan array
return _getSpan(completion);

return completion.DefaultSpan;
}

public static IEnumerable<Lazy<ISignatureHelpProviderWrapper, OrderableLanguageMetadataData>> GetSignatureHelpProvidersSlow(MefHostServices hostServices) {
var mefHostServicesType = typeof(MefHostServices).GetTypeInfo();
Expand Down
3 changes: 3 additions & 0 deletions MirrorSharp.Common/Internal/Reflection/RoslynTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ internal static class RoslynTypes {
private static readonly Assembly MicrosoftCodeAnalysisFeatures = typeof(CompletionProvider).GetTypeInfo().Assembly;

public static readonly TypeInfo CodeAction = typeof(CodeAction).GetTypeInfo();
public static readonly TypeInfo CompletionChange = typeof(CompletionChange).GetTypeInfo();
public static readonly TypeInfo CompletionList = typeof(CompletionList).GetTypeInfo();
public static readonly TypeInfo SymbolDisplayPartKindTags = MicrosoftCodeAnalysisFeatures.GetType("Microsoft.CodeAnalysis.SymbolDisplayPartKindTags", true).GetTypeInfo();
// ReSharper disable once InconsistentNaming
public static readonly TypeInfo ISignatureHelpProvider = MicrosoftCodeAnalysisFeatures.GetType("Microsoft.CodeAnalysis.SignatureHelp.ISignatureHelpProvider", true).GetTypeInfo();
public static readonly TypeInfo SignatureHelpTriggerInfo = MicrosoftCodeAnalysisFeatures.GetType("Microsoft.CodeAnalysis.SignatureHelp.SignatureHelpTriggerInfo", true).GetTypeInfo();
Expand Down
44 changes: 35 additions & 9 deletions MirrorSharp.Common/Internal/Reflection/SignatureHelpItemData.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
Expand All @@ -9,6 +10,24 @@

namespace MirrorSharp.Internal.Reflection {
internal struct SignatureHelpItemData {
[UsedImplicitly] // see FromInternalTypeExpressionSlow
public SignatureHelpItemData(
Func<CancellationToken, IEnumerable<TaggedText>> documentationFactory,
ImmutableArray<TaggedText> prefixParts,
ImmutableArray<TaggedText> separatorParts,
ImmutableArray<TaggedText> suffixParts,
IEnumerable<SignatureHelpParameterData> parameters,
int parameterCount
) {
DocumentationFactory = documentationFactory;
PrefixDisplayParts = prefixParts;
SeparatorDisplayParts = separatorParts;
SuffixDisplayParts = suffixParts;
Parameters = parameters;
ParameterCount = parameterCount;
}

// Roslyn v1
[UsedImplicitly] // see FromInternalTypeExpressionSlow
public SignatureHelpItemData(
Func<CancellationToken, IEnumerable<SymbolDisplayPart>> documentationFactory,
Expand All @@ -18,24 +37,31 @@ public SignatureHelpItemData(
IEnumerable<SignatureHelpParameterData> parameters,
int parameterCount
) {
DocumentationFactory = documentationFactory;
PrefixDisplayParts = prefixParts;
SeparatorDisplayParts = separatorParts;
SuffixDisplayParts = suffixParts;
DocumentationFactory = documentationFactory != null
? (Func<CancellationToken, IEnumerable<TaggedText>>)(t => documentationFactory(t).ToTaggedText())
: null;
PrefixDisplayParts = prefixParts.ToTaggedText();
SeparatorDisplayParts = separatorParts.ToTaggedText();
SuffixDisplayParts = suffixParts.ToTaggedText();
Parameters = parameters;
ParameterCount = parameterCount;
}

public Func<CancellationToken, IEnumerable<SymbolDisplayPart>> DocumentationFactory { get; }
public ImmutableArray<SymbolDisplayPart> PrefixDisplayParts { get; }
public ImmutableArray<SymbolDisplayPart> SeparatorDisplayParts { get; }
public ImmutableArray<SymbolDisplayPart> SuffixDisplayParts { get; }
public Func<CancellationToken, IEnumerable<TaggedText>> DocumentationFactory { get; }
public ImmutableArray<TaggedText> PrefixDisplayParts { get; }
public ImmutableArray<TaggedText> SeparatorDisplayParts { get; }
public ImmutableArray<TaggedText> SuffixDisplayParts { get; }
public IEnumerable<SignatureHelpParameterData> Parameters { get; }
public int ParameterCount { get; }

public static Expression FromInternalTypeExpressionSlow(Expression expression) {
var displayPartsType = expression.Property("PrefixDisplayParts").Type;
var constructor = typeof(SignatureHelpItemData).GetTypeInfo()
.GetConstructors()
.Single(c => c.GetParameters()[1].ParameterType == displayPartsType);

return Expression.New(
typeof(SignatureHelpItemData).GetTypeInfo().GetConstructors()[0],
constructor,
expression.Property("DocumentationFactory"),
expression.Property("PrefixDisplayParts"),
expression.Property("SeparatorDisplayParts"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;

namespace MirrorSharp.Internal.Reflection {
internal class SignatureHelpParameterData {
public SignatureHelpParameterData(IList<SymbolDisplayPart> displayParts, IList<SymbolDisplayPart> prefixDisplayParts, IList<SymbolDisplayPart> suffixDisplayParts) {
[UsedImplicitly] // see FromInternalTypeExpressionSlow
public SignatureHelpParameterData(IList<TaggedText> displayParts, IList<TaggedText> prefixDisplayParts, IList<TaggedText> suffixDisplayParts) {
DisplayParts = displayParts;
PrefixDisplayParts = prefixDisplayParts;
SuffixDisplayParts = suffixDisplayParts;
}

public IList<SymbolDisplayPart> DisplayParts { get; }
public IList<SymbolDisplayPart> PrefixDisplayParts { get; }
public IList<SymbolDisplayPart> SuffixDisplayParts { get; }
// Roslyn v1
[UsedImplicitly] // see FromInternalTypeExpressionSlow
public SignatureHelpParameterData(IList<SymbolDisplayPart> displayParts, IList<SymbolDisplayPart> prefixDisplayParts, IList<SymbolDisplayPart> suffixDisplayParts) {
DisplayParts = displayParts.ToTaggedText();
PrefixDisplayParts = prefixDisplayParts.ToTaggedText();
SuffixDisplayParts = suffixDisplayParts.ToTaggedText();
}

public IList<TaggedText> DisplayParts { get; }
public IList<TaggedText> PrefixDisplayParts { get; }
public IList<TaggedText> SuffixDisplayParts { get; }

public static Expression FromInternalTypeExpressionSlow(Expression expression) {
var displayPartsType = expression.Property("DisplayParts").Type;
var constructor = typeof(SignatureHelpParameterData).GetTypeInfo()
.GetConstructors()
.Single(c => c.GetParameters()[0].ParameterType == displayPartsType);

return Expression.New(
typeof(SignatureHelpParameterData).GetTypeInfo().GetConstructors()[0],
constructor,
expression.Property("DisplayParts"),
expression.Property("PrefixDisplayParts"),
expression.Property("SuffixDisplayParts")
Expand Down
25 changes: 25 additions & 0 deletions MirrorSharp.Common/Internal/Reflection/TaggedTextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using AshMind.Extensions;
using Microsoft.CodeAnalysis;

namespace MirrorSharp.Internal.Reflection {
internal static class TaggedTextExtensions {
private static readonly Lazy<Func<SymbolDisplayPartKind, string>> _getTag = new Lazy<Func<SymbolDisplayPartKind, string>>(
() => RoslynTypes.SymbolDisplayPartKindTags
.GetMethod("GetTag", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.CreateDelegate<Func<SymbolDisplayPartKind, string>>()
);

public static ImmutableArray<TaggedText> ToTaggedText(this IEnumerable<SymbolDisplayPart> displayParts) {
if (displayParts == null)
return ImmutableArray<TaggedText>.Empty;

return displayParts.Select(d => new TaggedText(_getTag.Value(d.Kind), d.ToString())).ToImmutableArray();
}
}
}
Loading

0 comments on commit af88998

Please sign in to comment.