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

Order code actions #1078

Merged
merged 15 commits into from
Jan 10, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ protected async Task<IEnumerable<AvailableCodeAction>> GetAvailableCodeActions(I
await CollectCodeFixesActions(document, span, codeActions);
await CollectRefactoringActions(document, span, codeActions);

// TODO: Determine good way to order code actions.
codeActions.Reverse();

// Be sure to filter out any code actions that inherit from CodeActionWithOptions.
// This isn't a great solution and might need changing later, but every Roslyn code action
// derived from this type tries to display a dialog. For now, this is a reasonable solution.
Expand Down Expand Up @@ -128,26 +125,54 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis

private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerable<Diagnostic> diagnostics, List<CodeAction> codeActions)
{
foreach (var provider in this.Providers)
IEnumerable<CodeFixProvider> sortedProviders = GetSortedCodeFixProviders();
foreach (var codeFixProvider in sortedProviders)
{
foreach (var codeFixProvider in provider.CodeFixProviders)
var fixableDiagnostics = diagnostics.Where(d => HasFix(codeFixProvider, d.Id)).ToImmutableArray();
if (fixableDiagnostics.Length > 0)
{
var fixableDiagnostics = diagnostics.Where(d => HasFix(codeFixProvider, d.Id)).ToImmutableArray();
if (fixableDiagnostics.Length > 0)
var context = new CodeFixContext(document, span, fixableDiagnostics, (a, _) => codeActions.Add(a), CancellationToken.None);

try
{
var context = new CodeFixContext(document, span, fixableDiagnostics, (a, _) => codeActions.Add(a), CancellationToken.None);

try
{
await codeFixProvider.RegisterCodeFixesAsync(context);
}
catch (Exception ex)
{
this.Logger.LogError(ex, $"Error registering code fixes for {codeFixProvider.GetType().FullName}");
}
await codeFixProvider.RegisterCodeFixesAsync(context);
}
catch (Exception ex)
{
this.Logger.LogError(ex, $"Error registering code fixes for {codeFixProvider.GetType().FullName}");
}
}
}
}

private IEnumerable<CodeFixProvider> GetSortedCodeFixProviders()
{
List<ProviderNode<CodeFixProvider>> nodesList = new List<ProviderNode<CodeFixProvider>>();
foreach (var provider in this.Providers)
{
foreach (var codeFixProvider in provider.CodeFixProviders)
{
nodesList.Add(ProviderNode<CodeFixProvider>.From(codeFixProvider));
}
}

var graph = Graph<CodeFixProvider>.GetGraph(nodesList);
return graph.TopologicalSort();
Copy link

Choose a reason for hiding this comment

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

Can we cache this? (Eg, does the total set of CodeFixProviders change)

}

private IEnumerable<CodeRefactoringProvider> GetSortedCodeRefactoringProviders()
{
List<ProviderNode<CodeRefactoringProvider>> nodesList = new List<ProviderNode<CodeRefactoringProvider>>();
foreach (var provider in this.Providers)
{
foreach (var codeRefactoringProvider in provider.CodeRefactoringProviders)
{
var node = ProviderNode<CodeRefactoringProvider>.From(codeRefactoringProvider);
}
}

var graph = Graph<CodeRefactoringProvider>.GetGraph(nodesList);
return graph.TopologicalSort();
}

private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId)
Expand Down Expand Up @@ -179,25 +204,23 @@ private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId)

private async Task CollectRefactoringActions(Document document, TextSpan span, List<CodeAction> codeActions)
{
foreach (var provider in this.Providers)
IEnumerable<CodeRefactoringProvider> sortedProviders = GetSortedCodeRefactoringProviders();
foreach (var codeRefactoringProvider in sortedProviders)
{
foreach (var codeRefactoringProvider in provider.CodeRefactoringProviders)
if (_helper.IsDisallowed(codeRefactoringProvider))
{
if (_helper.IsDisallowed(codeRefactoringProvider))
{
continue;
}
continue;
}

var context = new CodeRefactoringContext(document, span, a => codeActions.Add(a), CancellationToken.None);
var context = new CodeRefactoringContext(document, span, a => codeActions.Add(a), CancellationToken.None);

try
{
await codeRefactoringProvider.ComputeRefactoringsAsync(context);
}
catch (Exception ex)
{
this.Logger.LogError(ex, $"Error computing refactorings for {codeRefactoringProvider.GetType().FullName}");
}
try
{
await codeRefactoringProvider.ComputeRefactoringsAsync(context);
}
catch (Exception ex)
{
this.Logger.LogError(ex, $"Error computing refactorings for {codeRefactoringProvider.GetType().FullName}");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;

namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2
{
internal class Graph<T>
Copy link

Choose a reason for hiding this comment

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

Is this adapted from Roslyn?

Copy link
Contributor Author

@akshita31 akshita31 Jan 2, 2018

Choose a reason for hiding this comment

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

Yes, this is taken from Extension Orderer used by Roslyn and modified a bit for the current case.

Copy link

Choose a reason for hiding this comment

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

Ok, we might want to add a comment stating as such.

Copy link
Member

Choose a reason for hiding this comment

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

@DustinCampbell can we get that API opened up publicly? 😀

{
//Dictionary to map between nodes and the names
private Dictionary<string, ProviderNode<T>> Nodes { get; }
private List<ProviderNode<T>> AllNodes { get; }
private Graph(List<ProviderNode<T>> nodesList)
{
Nodes = new Dictionary<string, ProviderNode<T>>();
AllNodes = nodesList;
}
internal static Graph<T> GetGraph(List<ProviderNode<T>> nodesList)
{
var graph = new Graph<T>(nodesList);

foreach (ProviderNode<T> node in graph.AllNodes)
{
graph.Nodes[node.ProviderName] = node;
}

foreach (ProviderNode<T> node in graph.AllNodes)
{
foreach (var before in node.Before)
{
if (graph.Nodes.ContainsKey(before))
{
var beforeNode = graph.Nodes[before];
beforeNode.NodesBeforeMeSet.Add(node);
}
}

foreach (var after in node.After)
{
if (graph.Nodes.ContainsKey(after))
{
var afterNode = graph.Nodes[after];
node.NodesBeforeMeSet.Add(afterNode);
}
}
}

return graph;
}

public List<T> TopologicalSort()
{
List<T> result = new List<T>();
var seenNodes = new HashSet<ProviderNode<T>>();

foreach (var node in AllNodes)
{
Visit(node, result, seenNodes);
}

return result;
}

private void Visit(ProviderNode<T> node, List<T> result, HashSet<ProviderNode<T>> seenNodes)
{
if (seenNodes.Add(node))
{
foreach (var before in node.NodesBeforeMeSet)
{
Visit(before, result, seenNodes);
}

result.Add(node.Provider);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;

namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2
{
internal class ProviderNode<T>
{
public string ProviderName { get; set; }
public List<string> Before { get; set; }
public List<string> After { get; set; }
public T Provider { get; set; }
public HashSet<ProviderNode<T>> NodesBeforeMeSet { get; set; }

public static ProviderNode<T> From(T provider)
{
var exportAttribute = provider.GetType().GetCustomAttribute(typeof(ExportCodeFixProviderAttribute));
string providerName = "";
if (exportAttribute is ExportCodeFixProviderAttribute && ((ExportCodeFixProviderAttribute)exportAttribute).Name != null)
{
providerName = ((ExportCodeFixProviderAttribute)exportAttribute).Name;
}

var orderAttributes = provider.GetType().GetCustomAttributes(typeof(ExtensionOrderAttribute), true).Select(attr => (ExtensionOrderAttribute)attr).ToList();
return new ProviderNode<T>(provider, providerName, orderAttributes);
}

private ProviderNode(T provider, string providerName, List<ExtensionOrderAttribute> orderAttributes)
{
Provider = provider;
ProviderName = providerName;
Before = new List<string>();
After = new List<string>();
NodesBeforeMeSet = new HashSet<ProviderNode<T>>();
orderAttributes.ForEach(attr => AddAttribute(attr));
}

private void AddAttribute(ExtensionOrderAttribute attribute)
{
if (attribute.Before != null)
Before.Add(attribute.Before);
if (attribute.After != null)
After.Add(attribute.After);
}
}
}