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

Conversation

Projects
None yet
3 participants
@filipw
Member

filipw commented May 5, 2017

Use case: I'd like to load my own or 3rd party code actions into Omnisharp.

In this PR I added a possibility to add the following CodeActions node to the omnisharp.json.

{
    "FormattingOptions": {
        "IndentationSize": 2
    },
    "CodeActions": {
        "LocationPaths": [
            "C:/codeactions"
        ]
    }
}

This will be picked up by RoslynFeaturesHostServicesProvider as a source location for loading assemblies containing refactorings and code fixes.
Because omnisharp.json is hierarchical (global/local) we can specify code actions on a global and local level if needed.

No I could download something like https://marketplace.visualstudio.com/items?itemName=josefpihrt.Roslynator2017 and extract its DLLs into C:/codeactions and profit. Relative and absolute paths are supported.

@filipw filipw requested review from DustinCampbell and david-driscoll May 5, 2017

@filipw

This comment has been minimized.

Show comment
Hide comment
@filipw

filipw May 5, 2017

Member

The reason why I thought it might be a good idea is it could give a chance for user to opt-in into features not yet available as internal part of Roslyn, but already built in the community or self-built. (i.e. dotnet/roslyn#8925 which has been requested already in Omnisharp repos and so on).

See: http://recordit.co/dEAKIBAvbl

Member

filipw commented May 5, 2017

The reason why I thought it might be a good idea is it could give a chance for user to opt-in into features not yet available as internal part of Roslyn, but already built in the community or self-built. (i.e. dotnet/roslyn#8925 which has been requested already in Omnisharp repos and so on).

See: http://recordit.co/dEAKIBAvbl

@@ -3,4 +3,20 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
</startup>
<runtime>

This comment has been minimized.

@filipw

filipw May 5, 2017

Member

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

@filipw

filipw May 5, 2017

Member

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

This comment has been minimized.

@DustinCampbell

DustinCampbell May 5, 2017

Contributor

This will need to be updated everytime we update Roslyn to a new version (or, at least, everytime the assembly version changes). Correct?

@DustinCampbell

DustinCampbell May 5, 2017

Contributor

This will need to be updated everytime we update Roslyn to a new version (or, at least, everytime the assembly version changes). Correct?

This comment has been minimized.

@filipw

filipw May 5, 2017

Member

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?

@filipw

filipw May 5, 2017

Member

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?

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

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

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

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

@david-driscoll

I think this is something we can support for the moment, with the knowledge that it is really just a power user feature, use at their own risk.

Show outdated Hide outdated src/OmniSharp.Abstractions/Options/CodeActionOptions.cs
var normalizePaths = new HashSet<string>();
foreach (var locationPath in LocationPaths)
{
if (Path.IsPathRooted(locationPath))

This comment has been minimized.

@david-driscoll

david-driscoll May 5, 2017

Member

If the paths are relative shouldn't they be rooted in the directory where the configuration file lives? However this may be super painful to pull off (would require a custom options provider)

@david-driscoll

david-driscoll May 5, 2017

Member

If the paths are relative shouldn't they be rooted in the directory where the configuration file lives? However this may be super painful to pull off (would require a custom options provider)

This comment has been minimized.

@filipw

filipw May 5, 2017

Member

yeah so at the moment, relative always means relative to the folder in which OmniSharp is running.
I think this is reasonable for most cases, but you are right, when you are editing the global file like c:\users\filip\.omnisharp\omnisharp.json it might appear a bit counter intuitive.

@filipw

filipw May 5, 2017

Member

yeah so at the moment, relative always means relative to the folder in which OmniSharp is running.
I think this is reasonable for most cases, but you are right, when you are editing the global file like c:\users\filip\.omnisharp\omnisharp.json it might appear a bit counter intuitive.

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Yeah, you're right that will be counter intuitive for the global config file case. Maybe a doc comment on LocationPaths to help describe the expected values?

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Yeah, you're right that will be counter intuitive for the global config file case. Maybe a doc comment on LocationPaths to help describe the expected values?

@DustinCampbell

This comment has been minimized.

Show comment
Hide comment
@DustinCampbell

DustinCampbell May 5, 2017

Contributor

Is this change all that interesting if we add a proper diagnostics engine that loads analyzers referenced by a project?

Contributor

DustinCampbell commented May 5, 2017

Is this change all that interesting if we add a proper diagnostics engine that loads analyzers referenced by a project?

@filipw

This comment has been minimized.

Show comment
Hide comment
@filipw

filipw May 5, 2017

Member

Is this change all that interesting if we add a proper diagnostics engine that loads analyzers referenced by a project?

however stand alone refactorings without analyzer (i.e. initalize a field from constructor, LINQ into foreach and so on) are installed as VSIX - they are not referenced by projects. So this PR attempts to cover only that use case.

Setting a code actions folder in a global omnisharp.json would be to me roughly equivalent to installing a VSIX into my Visual Studio.

Member

filipw commented May 5, 2017

Is this change all that interesting if we add a proper diagnostics engine that loads analyzers referenced by a project?

however stand alone refactorings without analyzer (i.e. initalize a field from constructor, LINQ into foreach and so on) are installed as VSIX - they are not referenced by projects. So this PR attempts to cover only that use case.

Setting a code actions folder in a global omnisharp.json would be to me roughly equivalent to installing a VSIX into my Visual Studio.

@DustinCampbell

This comment has been minimized.

Show comment
Hide comment
@DustinCampbell

DustinCampbell May 5, 2017

Contributor

The refactorings you mentioned will very likely be added to Roslyn itself.

Contributor

DustinCampbell commented May 5, 2017

The refactorings you mentioned will very likely be added to Roslyn itself.

@filipw

This comment has been minimized.

Show comment
Hide comment
@filipw

filipw May 5, 2017

Member

yeah absolutely, then when they ship in the box, there is no need to opt into anything anymore 😃
But it's exactly the same case as with installing these types of add ons into Visual Studio - they can help fill some feature gap.

but until then, if you wish, you could tap into anything from here https://github.com/JosefPihrt/Roslynator/tree/master/source/Refactorings/Refactorings or here https://github.com/code-cracker/code-cracker/tree/master/src/CSharp/CodeCracker/Refactoring or few other places that people have been building.

This also has the reverse effect - if I'm an author of a refactoring, I'm loving the fact that I can build it, and it works in VS and I there is a way that I can also load it through OmniSharp into VS Code and other places. In fact, for example, at work, I have some domain specific refactorings for internal purposes only, that developers in our organization install into their VS - I think it would be great to be able to load them into OmniSharp.

I think ultimately the discussion should be what are the potential extensibility point in OmniSharp, and I think this is an excellent candidate one, especially as, in a way, it aligns with Visual Studio so experience-wise should be intuitive.

Member

filipw commented May 5, 2017

yeah absolutely, then when they ship in the box, there is no need to opt into anything anymore 😃
But it's exactly the same case as with installing these types of add ons into Visual Studio - they can help fill some feature gap.

but until then, if you wish, you could tap into anything from here https://github.com/JosefPihrt/Roslynator/tree/master/source/Refactorings/Refactorings or here https://github.com/code-cracker/code-cracker/tree/master/src/CSharp/CodeCracker/Refactoring or few other places that people have been building.

This also has the reverse effect - if I'm an author of a refactoring, I'm loving the fact that I can build it, and it works in VS and I there is a way that I can also load it through OmniSharp into VS Code and other places. In fact, for example, at work, I have some domain specific refactorings for internal purposes only, that developers in our organization install into their VS - I think it would be great to be able to load them into OmniSharp.

I think ultimately the discussion should be what are the potential extensibility point in OmniSharp, and I think this is an excellent candidate one, especially as, in a way, it aligns with Visual Studio so experience-wise should be intuitive.

@DustinCampbell

This comment has been minimized.

Show comment
Hide comment
@DustinCampbell

DustinCampbell May 5, 2017

Contributor

Note: I'm not against this change. I'm just pushing on whether it's the right change, so we don't have to support it later if we decide that we should have done something else.

Contributor

DustinCampbell commented May 5, 2017

Note: I'm not against this change. I'm just pushing on whether it's the right change, so we don't have to support it later if we decide that we should have done something else.

@david-driscoll

This comment has been minimized.

Show comment
Hide comment
@david-driscoll

david-driscoll May 5, 2017

Member

Would anyone complain if we called this a beta / alpha feature? Perhaps annotate the code (and docs when we get there) to indicate that.

Ideally I'd love some sort of nuget based process going forward, but this is a nice segway into that.

Member

david-driscoll commented May 5, 2017

Would anyone complain if we called this a beta / alpha feature? Perhaps annotate the code (and docs when we get there) to indicate that.

Ideally I'd love some sort of nuget based process going forward, but this is a nice segway into that.

@filipw

This comment has been minimized.

Show comment
Hide comment
@filipw

filipw May 5, 2017

Member

An alternative solution to the same problem could be something like this:

  • someone creates a hypothetical ThirdPartyCodeActionProvider implementation of OmniSharp's ICodeActionProvider which subclasses our RoslynCodeActionProvider and introduces the type of functionality contained in this current PR (ability to load code actions from some external paths)
  • package is published into Nuget
  • user installs this Nuget package into, say,`%USERPROFILE%.omnisharp\plugins
  • at startup we scan that plugins folder and pick up the plugin and swap the default RoslynCodeActionProvider with ThirdPartyCodeActionProvider and everything else remains unchanged.

The difference would be omnisharp.json cannot participate anymore, so the plugin would need to have its own config file, but I think that's fine.

This could also have a different format, and the plugin could just bundle the code actions straight up and offer them as direct dependency - the downside here is that you'd need such dedicated loader for everything, rather than a having a more generic solution.

Member

filipw commented May 5, 2017

An alternative solution to the same problem could be something like this:

  • someone creates a hypothetical ThirdPartyCodeActionProvider implementation of OmniSharp's ICodeActionProvider which subclasses our RoslynCodeActionProvider and introduces the type of functionality contained in this current PR (ability to load code actions from some external paths)
  • package is published into Nuget
  • user installs this Nuget package into, say,`%USERPROFILE%.omnisharp\plugins
  • at startup we scan that plugins folder and pick up the plugin and swap the default RoslynCodeActionProvider with ThirdPartyCodeActionProvider and everything else remains unchanged.

The difference would be omnisharp.json cannot participate anymore, so the plugin would need to have its own config file, but I think that's fine.

This could also have a different format, and the plugin could just bundle the code actions straight up and offer them as direct dependency - the downside here is that you'd need such dedicated loader for everything, rather than a having a more generic solution.

@DustinCampbell

Sorry for the delay in giving this a careful review.

Show outdated Hide outdated src/OmniSharp.Abstractions/Options/OmniSharpOptions.cs
@@ -4,6 +4,8 @@ namespace OmniSharp.Options
{
public class OmniSharpOptions
{
public CodeActionOptions CodeActions { get; set; } = new CodeActionOptions();

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Why is this settable and instantiated here? Shouldn't it follow the same pattern as FormattingOptions below?

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Why is this settable and instantiated here? Shouldn't it follow the same pattern as FormattingOptions below?

This comment has been minimized.

@filipw

filipw May 11, 2017

Member

that's a good observation. Let's change it to public CodeActionOptions CodeActions { get; } = new CodeActionOptions(); I think this should still bind fine to the ASP.NET Configuration and be more elegant. The other property (FormattingOptions) should use the same approach, it would be cleaner. Currently it can be set through the constructor too, but that constructor is redundant - I don't believe we use it anywhere, only the default constructor is in use

@filipw

filipw May 11, 2017

Member

that's a good observation. Let's change it to public CodeActionOptions CodeActions { get; } = new CodeActionOptions(); I think this should still bind fine to the ASP.NET Configuration and be more elegant. The other property (FormattingOptions) should use the same approach, it would be cleaner. Currently it can be set through the constructor too, but that constructor is redundant - I don't believe we use it anywhere, only the default constructor is in use

Show outdated Hide outdated src/OmniSharp.Abstractions/Services/IAssemblyLoader.cs
@@ -7,6 +7,8 @@ namespace OmniSharp.Services
public interface IAssemblyLoader
{
Assembly Load(AssemblyName name);
IEnumerable<Assembly> LoadAll(string folderPath);

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

I'd prefer this be called LoadAllFrom to call out that it uses the LoadFrom context rather than the Load context.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

I'd prefer this be called LoadAllFrom to call out that it uses the LoadFrom context rather than the Load context.

Show outdated Hide outdated src/OmniSharp.Host/Services/AssemblyLoader.cs
@@ -30,5 +33,43 @@ public Assembly Load(AssemblyName name)
_logger.LogTrace($"Assembly loaded: {name}");
return result;
}
public IEnumerable<Assembly> LoadAll(string folderPath)

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Seems like we could return a better interface than IEnumerable<Assembly> since the instance is a List<Assembly>. Maybe IReadOnlyList<Assembly> so that callers could index into it if they want.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Seems like we could return a better interface than IEnumerable<Assembly> since the instance is a List<Assembly>. Maybe IReadOnlyList<Assembly> so that callers could index into it if they want.

Show outdated Hide outdated src/OmniSharp.Host/Services/AssemblyLoader.cs
public IEnumerable<Assembly> LoadAll(string folderPath)
{
if (folderPath == null) return Enumerable.Empty<Assembly>();

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

string.IsNullOrWhitespace?

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

string.IsNullOrWhitespace?

Show outdated Hide outdated src/OmniSharp.Roslyn.CSharp/Services/RoslynFeaturesHostServicesProvider.cs
@@ -12,7 +14,7 @@ public class RoslynFeaturesHostServicesProvider : IHostServicesProvider
public ImmutableArray<Assembly> Assemblies { get; }
[ImportingConstructor]
public RoslynFeaturesHostServicesProvider(IAssemblyLoader loader)
public RoslynFeaturesHostServicesProvider(IAssemblyLoader loader, OmniSharpOptions options, IOmniSharpEnvironment env)

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

To separate concerns, this should probably be added to another IHostServicesProvider rather than tacked onto the RoslynFeaturesHostServicesProvider, since this is specifically about loading the Roslyn features assemblies. Note that OmniSharpWorkspace aggregates multiple IHostServicesProviders.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

To separate concerns, this should probably be added to another IHostServicesProvider rather than tacked onto the RoslynFeaturesHostServicesProvider, since this is specifically about loading the Roslyn features assemblies. Note that OmniSharpWorkspace aggregates multiple IHostServicesProviders.

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Also, I just realized that RoslynFeaturesHostServicesProvider is not marked as [Shared]. It probably doesn't matter in practice (since OmniSharpWorkspace is a singleton), but I just noticed.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Also, I just realized that RoslynFeaturesHostServicesProvider is not marked as [Shared]. It probably doesn't matter in practice (since OmniSharpWorkspace is a singleton), but I just noticed.

This comment has been minimized.

@filipw

filipw May 11, 2017

Member

I didn't know there can be many of them - makes sense, thanks!

@filipw

filipw May 11, 2017

Member

I didn't know there can be many of them - makes sense, thanks!

Show outdated Hide outdated src/OmniSharp.Abstractions/Options/CodeActionOptions.cs
{
public string[] LocationPaths { get; set; }
public IEnumerable<string> GetLocations(IOmniSharpEnvironment env)

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Perhaps this should be called something like GetNormalizedLocationPaths(...) or something like that to indicate what it does differently over just accessing the LocationPaths property?

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Perhaps this should be called something like GetNormalizedLocationPaths(...) or something like that to indicate what it does differently over just accessing the LocationPaths property?

This comment has been minimized.

@filipw

filipw May 11, 2017

Member

👍

@filipw
Show outdated Hide outdated src/OmniSharp.Abstractions/Options/CodeActionOptions.cs
{
if (LocationPaths == null) return Enumerable.Empty<string>();
var normalizePaths = new HashSet<string>();

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Do we want this to be case-sensitive? Or, should you add StringComparer.OrdinalIgnoreCase? (We do that in other places)

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Do we want this to be case-sensitive? Or, should you add StringComparer.OrdinalIgnoreCase? (We do that in other places)

Show outdated Hide outdated src/OmniSharp.Roslyn.CSharp/Services/RoslynFeaturesHostServicesProvider.cs
@@ -21,6 +23,15 @@ public RoslynFeaturesHostServicesProvider(IAssemblyLoader loader)
builder.AddRange(loader.Load(Features, CSharpFeatures));
var codeActionLocations = options.CodeActions.GetLocations(env);
if (codeActionLocations != null && codeActionLocations.Any())

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Optional: codeActionLocations?.Any() == true

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Optional: codeActionLocations?.Any() == true

Show outdated Hide outdated src/OmniSharp.Abstractions/Options/CodeActionOptions.cs
normalizePaths.Add(Path.Combine(env.TargetDirectory, locationPath));
}
return normalizePaths;

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Note that the paths will now be returned in a different order than they were specified, which could result in subtle bugs. (e.g. Imagine two assemblies that reference the same dependency but different versions. Their load order could be important.)

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Note that the paths will now be returned in a different order than they were specified, which could result in subtle bugs. (e.g. Imagine two assemblies that reference the same dependency but different versions. Their load order could be important.)

Show outdated Hide outdated src/OmniSharp.Abstractions/Options/CodeActionOptions.cs
var normalizePaths = new HashSet<string>();
foreach (var locationPath in LocationPaths)
{
if (Path.IsPathRooted(locationPath))

This comment has been minimized.

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Yeah, you're right that will be counter intuitive for the global config file case. Maybe a doc comment on LocationPaths to help describe the expected values?

@DustinCampbell

DustinCampbell May 7, 2017

Contributor

Yeah, you're right that will be counter intuitive for the global config file case. Maybe a doc comment on LocationPaths to help describe the expected values?

@DustinCampbell

This comment has been minimized.

Show comment
Hide comment
@DustinCampbell

DustinCampbell May 15, 2017

Contributor

I'm wondering if the folder should have a more general name. Maybe "RoslynExtensions" instead of "CodeActions". After all, there's nothing restricting this to just Code Actions. It would also load, say, CompletionProviders.

Contributor

DustinCampbell commented May 15, 2017

I'm wondering if the folder should have a more general name. Maybe "RoslynExtensions" instead of "CodeActions". After all, there's nothing restricting this to just Code Actions. It would also load, say, CompletionProviders.

filipw added some commits May 5, 2017

@filipw

This comment has been minimized.

Show comment
Hide comment
@filipw

filipw May 15, 2017

Member

I'm wondering if the folder should have a more general name. Maybe "RoslynExtensions" instead of "CodeActions". After all, there's nothing restricting this to just Code Actions. It would also load, say, CompletionProviders.

yes makes sense to me. We still need relevant exports to be there (i.e. ICodeActionProvider) to understand which types should be discovered, but as far as loading assemblies, indeed, it can bring in all kinds of extensions/add-ons.

Member

filipw commented May 15, 2017

I'm wondering if the folder should have a more general name. Maybe "RoslynExtensions" instead of "CodeActions". After all, there's nothing restricting this to just Code Actions. It would also load, say, CompletionProviders.

yes makes sense to me. We still need relevant exports to be there (i.e. ICodeActionProvider) to understand which types should be discovered, but as far as loading assemblies, indeed, it can bring in all kinds of extensions/add-ons.

@DustinCampbell

just nits

Show outdated Hide outdated src/OmniSharp.Host/Services/AssemblyLoader.cs
public IReadOnlyList<Assembly> LoadAllFrom(string folderPath)
{
if (string.IsNullOrWhiteSpace(folderPath)) return new Assembly[0];

This comment has been minimized.

@DustinCampbell

DustinCampbell May 17, 2017

Contributor

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

@DustinCampbell

DustinCampbell May 17, 2017

Contributor

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

Show outdated Hide outdated src/OmniSharp.Roslyn.CSharp/Services/RoslynFeaturesHostServicesProvider.cs
using OmniSharp.Services;
using System.Linq;

This comment has been minimized.

@DustinCampbell

DustinCampbell May 17, 2017

Contributor

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

@DustinCampbell

DustinCampbell May 17, 2017

Contributor

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

Show outdated Hide outdated ...OmniSharp.Roslyn.CSharp/Services/ExternalFeaturesHostServicesProvider.cs
using System.Reflection;
using OmniSharp.Options;
using OmniSharp.Services;
using System.Linq;

This comment has been minimized.

@DustinCampbell

DustinCampbell May 17, 2017

Contributor

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

@DustinCampbell

DustinCampbell May 17, 2017

Contributor

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

@DustinCampbell

This comment has been minimized.

Show comment
Hide comment
@DustinCampbell

DustinCampbell May 17, 2017

Contributor

@filipw: CI passed. I'm good with this change (only found a couple of tiny nits). Are there more changes you wanted to make?

Contributor

DustinCampbell commented May 17, 2017

@filipw: CI passed. I'm good with this change (only found a couple of tiny nits). Are there more changes you wanted to make?

@david-driscoll

Will wait for @filipw to come online after work (It's 5:30 now?)

Can fix the nits, or just let em pass and fix them later if we need to. 👍

@filipw

This comment has been minimized.

Show comment
Hide comment
@filipw

filipw May 17, 2017

Member

Thanks guys, should be good to go.

Member

filipw commented May 17, 2017

Thanks guys, should be good to go.

@DustinCampbell

This comment has been minimized.

Show comment
Hide comment
@DustinCampbell

DustinCampbell May 17, 2017

Contributor

I won the "merge first contest". 😄 Kicking this again.

Contributor

DustinCampbell commented May 17, 2017

I won the "merge first contest". 😄 Kicking this again.

@DustinCampbell DustinCampbell merged commit 4e59671 into OmniSharp:dev May 17, 2017

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

@filipw filipw deleted the filipw:features/code-actions-folder branch May 18, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment