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

Added an option to configure a code actions folder #848

Merged
merged 10 commits into from
May 17, 2017
9 changes: 2 additions & 7 deletions src/OmniSharp.Abstractions/Options/OmniSharpOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ namespace OmniSharp.Options
{
public class OmniSharpOptions
{
public FormattingOptions FormattingOptions { get; }
public RoslynExtensionsOptions RoslynExtensionsOptions { get; } = new RoslynExtensionsOptions();

public OmniSharpOptions() : this(new FormattingOptions()) { }

public OmniSharpOptions(FormattingOptions options)
{
FormattingOptions = options ?? throw new ArgumentNullException(nameof(options));
}
public FormattingOptions FormattingOptions { get; } = new FormattingOptions();
}
}
32 changes: 32 additions & 0 deletions src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace OmniSharp.Options
{
public class RoslynExtensionsOptions
{
public string[] LocationPaths { get; set; }

public IEnumerable<string> GetNormalizedLocationPaths(IOmniSharpEnvironment env)
{
if (LocationPaths == null || LocationPaths.Length == 0) return Enumerable.Empty<string>();

var normalizePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var locationPath in LocationPaths)
{
if (Path.IsPathRooted(locationPath))
{
normalizePaths.Add(locationPath);
}
else
{
normalizePaths.Add(Path.Combine(env.TargetDirectory, locationPath));
}
}

return normalizePaths;
}
}
}
2 changes: 2 additions & 0 deletions src/OmniSharp.Abstractions/Services/IAssemblyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace OmniSharp.Services
public interface IAssemblyLoader
{
Assembly Load(AssemblyName name);

IReadOnlyList<Assembly> LoadAllFrom(string folderPath);
}

public static class IAssemblyLoaderExtensions
Expand Down
43 changes: 42 additions & 1 deletion src/OmniSharp.Host/Services/AssemblyLoader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using Microsoft.Extensions.Logging;
using OmniSharp.Services;
Expand All @@ -11,7 +14,7 @@ internal class AssemblyLoader : IAssemblyLoader

public AssemblyLoader(ILoggerFactory loggerFactory)
{
this._logger = loggerFactory.CreateLogger<AssemblyLoader>();
_logger = loggerFactory.CreateLogger<AssemblyLoader>();
}

public Assembly Load(AssemblyName name)
Expand All @@ -30,5 +33,43 @@ public Assembly Load(AssemblyName name)
_logger.LogTrace($"Assembly loaded: {name}");
return result;
}

public IReadOnlyList<Assembly> LoadAllFrom(string folderPath)
{
if (string.IsNullOrWhiteSpace(folderPath)) return new Assembly[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

You could use Array.Empty<Assembly>() and avoid allocating here if you like.


var assemblies = new List<Assembly>();
foreach (var filePath in Directory.EnumerateFiles(folderPath, "*.dll"))
{
var assembly = LoadFromPath(filePath);
if (assembly != null)
{
assemblies.Add(assembly);
}
}

return assemblies;
}

private Assembly LoadFromPath(string assemblyPath)
{
Assembly assembly = null;

try
{
#if NET46
assembly = Assembly.LoadFrom(assemblyPath);
#else
assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
#endif
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to load assembly from path: {assemblyPath}");
}

_logger.LogTrace($"Assembly loaded from path: {assemblyPath}");
return assembly;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Composition;
using OmniSharp.Services;

namespace OmniSharp.Roslyn.CSharp.Services.CodeActions
{
[Export(typeof(ICodeActionProvider))]
public class ExternalCodeActionProvider : AbstractCodeActionProvider
{
[ImportingConstructor]
public ExternalCodeActionProvider(ExternalFeaturesHostServicesProvider featuresHostServicesProvider)
: base("ExternalCodeActions", featuresHostServicesProvider.Assemblies)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Immutable;
using System.Composition;
using System.Reflection;
using OmniSharp.Options;
using OmniSharp.Services;
using System.Linq;
Copy link
Contributor

Choose a reason for hiding this comment

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

Sort usings? It looks like System.Linq wound up on the bottom.


namespace OmniSharp.Roslyn.CSharp.Services
{
[Export(typeof(IHostServicesProvider))]
[Export(typeof(ExternalFeaturesHostServicesProvider))]
[Shared]
public class ExternalFeaturesHostServicesProvider : IHostServicesProvider
{
public ImmutableArray<Assembly> Assemblies { get; }

[ImportingConstructor]
public ExternalFeaturesHostServicesProvider(IAssemblyLoader loader, OmniSharpOptions options, IOmniSharpEnvironment env)
{
var builder = ImmutableArray.CreateBuilder<Assembly>();

var roslynExtensionsLocations = options.RoslynExtensionsOptions.GetNormalizedLocationPaths(env);
if (roslynExtensionsLocations?.Any() == true)
{
foreach (var roslynExtensionsLocation in roslynExtensionsLocations)
{
builder.AddRange(loader.LoadAllFrom(roslynExtensionsLocation));
}
}

Assemblies = builder.ToImmutable();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System.Collections.Immutable;
using System.Composition;
using System.Reflection;
using OmniSharp.Options;
using OmniSharp.Services;
using System.Linq;
Copy link
Contributor

Choose a reason for hiding this comment

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

Sort usings? It looks like System.Linq wound up on the bottom.


namespace OmniSharp.Roslyn.CSharp.Services
{
[Export(typeof(IHostServicesProvider))]
[Export(typeof(RoslynFeaturesHostServicesProvider))]
[Shared]
public class RoslynFeaturesHostServicesProvider : IHostServicesProvider
{
public ImmutableArray<Assembly> Assemblies { get; }
Expand All @@ -21,7 +24,8 @@ public RoslynFeaturesHostServicesProvider(IAssemblyLoader loader)

builder.AddRange(loader.Load(Features, CSharpFeatures));

this.Assemblies = builder.ToImmutable();

Assemblies = builder.ToImmutable();
}
}
}
16 changes: 16 additions & 0 deletions src/OmniSharp/app.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,20 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
</startup>
<runtime>
Copy link
Member Author

Choose a reason for hiding this comment

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

this was added so that a refactoring written against Roslyn 1.0.0 (which is often the case) can load too

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 need to be updated everytime we update Roslyn to a new version (or, at least, everytime the assembly version changes). Correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

In the current format, yes, correct. While this is technically not necessary, most of the code actions out there (i.e. packaged as VSIX-es) reference Roslyn assemblies from the host (VS). So it has to be low version if you want to avoid having a dependency on something like let's say Update 3 of Visual Studio to be installed by the user.
I believe even the official Roslyn SDK templates default to Roslyn 1.0.0.

Otherwise, we'd only be able to load into the current process code actions built specifically against the version of Roslyn OmniSharp is built against.

I only added the three most common ones, maybe there are other DLLs that would be worthy of redirecting?

Copy link
Contributor

Choose a reason for hiding this comment

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

There are others that will come up eventually, but this is good for now.

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis.CSharp" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis.Workspaces" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>