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

Spike/vs extensibility #1136

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 1 addition & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<NoWarn>$(NoWarn);1998;NU5100</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<NoWarn>$(NoWarn);1998;NU5100;NU1900;NU1901;NU1902;NU1903;NU1904;VSEXTPREVIEW_SETTINGS</NoWarn>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<LangVersion>10.0</LangVersion>
<AssemblyVersion>9.2.6.0</AssemblyVersion>
Expand Down
38 changes: 17 additions & 21 deletions Vsix/CodeConversion.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using CodeConv.Shared.Util;
using EnvDTE;
using ICSharpCode.CodeConverter.Common;
using ICSharpCode.CodeConverter.VsExtension.ICSharpCode.CodeConverter.VsExtension;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Settings;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
Expand All @@ -20,8 +17,7 @@ namespace ICSharpCode.CodeConverter.VsExtension;

internal class CodeConversion
{
public Func<Task<ConverterOptionsPage>> GetOptions { get; }
private readonly IAsyncServiceProvider _serviceProvider;
private readonly VisualStudioExtensibility _serviceProvider;
private readonly JoinableTaskFactory _joinableTaskFactory;
private readonly VisualStudioWorkspace _visualStudioWorkspace;
private static readonly string Intro = Environment.NewLine + Environment.NewLine + new string(Enumerable.Repeat('-', 80).ToArray()) + Environment.NewLine;
Expand All @@ -30,21 +26,17 @@ internal class CodeConversion

private string SolutionDir => Path.GetDirectoryName(_visualStudioWorkspace.CurrentSolution.FilePath);

public static async Task<CodeConversion> CreateAsync(CodeConverterPackage serviceProvider, VisualStudioWorkspace visualStudioWorkspace, Func<Task<ConverterOptionsPage>> getOptions)
public static async Task<CodeConversion> CreateAsync(VisualStudioExtensibility serviceProvider, VisualStudioWorkspace visualStudioWorkspace)
{
var options = await getOptions();
AppDomain.CurrentDomain.UseVersionAgnosticAssemblyResolution(options.BypassAssemblyLoadingErrors);

return new CodeConversion(serviceProvider, serviceProvider.JoinableTaskFactory, serviceProvider.PackageCancellation, visualStudioWorkspace,
getOptions, await OutputWindow.CreateAsync());
return new CodeConversion(serviceProvider, ThreadHelper.JoinableTaskFactory, serviceProvider, visualStudioWorkspace,
await OutputWindow.CreateAsync());
}

public CodeConversion(IAsyncServiceProvider serviceProvider,
public CodeConversion(VisualStudioExtensibility serviceProvider,
JoinableTaskFactory joinableTaskFactory, Cancellation packageCancellation, VisualStudioWorkspace visualStudioWorkspace,
Func<Task<ConverterOptionsPage>> getOptions, OutputWindow outputWindow)
Func<Task<ConverterSettingsAccessor>> getOptions, OutputWindow outputWindow)
{
JoinableTaskFactorySingleton.Instance = joinableTaskFactory;
GetOptions = getOptions;
_serviceProvider = serviceProvider;
_joinableTaskFactory = joinableTaskFactory;
_visualStudioWorkspace = visualStudioWorkspace;
Expand Down Expand Up @@ -94,7 +86,8 @@ await _joinableTaskFactory.RunAsync(async () => {
return result;
});

if ((await GetOptions()).CopyResultToClipboardForSingleDocument) {
var readEffectiveValueAsync = await ReadSettingValueAsync(ConverterSettings.CopyResultToClipboardForSingleDocument, cancellationToken);
if (readEffectiveValueAsync.Value) {
await SetClipboardTextOnUiThreadAsync(conversionResult.ConvertedCode ?? conversionResult.GetExceptionsAsString());
await _outputWindow.WriteToOutputWindowAsync(Environment.NewLine + "Conversion result copied to clipboard.");
await VisualStudioInteraction.ShowMessageBoxAsync("Conversion result copied to clipboard.", $"Conversion result copied to clipboard. {conversionResult.GetExceptionsAsString()}", false);
Expand All @@ -106,6 +99,11 @@ await _joinableTaskFactory.RunAsync(async () => {
}
}

private async Task<SettingValue<T>> ReadSettingValueAsync<T>(Setting<T> setting, CancellationToken cancellationToken) where T: Setting<T>
{
return await _serviceProvider.Settings().ReadEffectiveValueAsync(setting.FullId, cancellationToken);
}

/// <remarks>
/// https://github.com/icsharpcode/CodeConverter/issues/592
/// https://github.com/dotnet/roslyn/issues/6615
Expand Down Expand Up @@ -161,8 +159,6 @@ private async Task WriteConvertedFilesAndShowSummaryAsync(IAsyncEnumerable<Conve

private async Task FinalizeConversionAsync(List<string> files, List<string> errors, string longestFilePath, List<ConversionResult> filesToOverwrite)
{
var options = await GetOptions();

var pathsToOverwrite = filesToOverwrite.Select(f => PathRelativeToSolutionDir(f.SourcePathOrNull));
var shouldOverwriteSolutionAndProjectFiles =
filesToOverwrite.Any() &&
Expand Down
136 changes: 36 additions & 100 deletions Vsix/CodeConverterPackage.cs
Original file line number Diff line number Diff line change
@@ -1,131 +1,67 @@
using System;
using System.ComponentModel.Design;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.Shell;
using System.Diagnostics.CodeAnalysis;
using ICSharpCode.CodeConverter.VsExtension.ICSharpCode.CodeConverter.VsExtension;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
#pragma warning disable CEE0012 // TODO: I can't find any information on what this means

namespace ICSharpCode.CodeConverter.VsExtension;

/// <summary>
/// Implements the VS package exposed by this assembly.
///
/// This package will load when:
/// This package should load when:
/// * Visual Studio has been configured not to support UIContextRules and has a solution with a csproj or vbproj
/// * Someone clicks one of the menu items
/// * Someone opens the options page (it doesn't need to load in this case, but it seems to anyway)
/// </summary>
/// <remarks>
/// Until the package is loaded, converting a multiple selection of projects won't work because there's no way to set a ProvideUIContextRule that covers that case
/// </remarks>
[ProvideBindingPath] // Resolve assemblies in our folder i.e. System.Threading.Tasks.Dataflow
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[InstalledProductRegistration("#110", "#112", "1.0")] // Info on this package for Help/About
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideOptionPage(typeof(ConverterOptionsPage),
"Code Converter", "General", 0, 0, true)]
[Guid(PackageGuidString)]
//See https://docs.microsoft.com/en-us/visualstudio/extensibility/how-to-use-rule-based-ui-context-for-visual-studio-extensions?view=vs-2019#term-types
[ProvideUIContextRule(ConvertableSolutionMenuVisibilityGuid, name: nameof(ConvertableSolutionMenuVisibilityGuid),
expression: "HasVbproj | HasCsproj", termNames: new[] { "HasVbproj", "HasCsproj" },
termValues: new[] { "SolutionHasProjectCapability:VB", "SolutionHasProjectCapability:CSharp" })]
[ProvideUIContextRule(CsEditorMenuVisibilityGuid, name: nameof(CsEditorMenuVisibilityGuid),
expression: "Cs", termNames: new[] { "Cs" },
termValues: new[] { "ActiveEditorContentType:CSharp" })]
[ProvideUIContextRule(VbEditorMenuVisibilityGuid, name: nameof(VbEditorMenuVisibilityGuid),
expression: "Vb", termNames: new[] { "Vb" },
termValues: new[] { "ActiveEditorContentType:Basic" })]
[ProvideUIContextRule(CsFileMenuVisibilityGuid, name: nameof(CsFileMenuVisibilityGuid),
expression: "Csproj", termNames: new[] { "Csproj" },
termValues: new[] { "ActiveProjectCapability:CSharp"})]
[ProvideUIContextRule(VbFileMenuVisibilityGuid, name: nameof(VbFileMenuVisibilityGuid),
expression: "Vbproj", termNames: new[] { "Vbproj" },
termValues: new[] { "ActiveProjectCapability:VB"})]
[ProvideUIContextRule(CsNodeMenuVisibilityGuid, name: nameof(CsNodeMenuVisibilityGuid),
expression: "Csproj", termNames: new[] { "Csproj" },
termValues: new[] { "ActiveProjectCapability:CSharp"})]
[ProvideUIContextRule(VbNodeMenuVisibilityGuid, name: nameof(VbNodeMenuVisibilityGuid),
expression: "Vbproj", termNames: new[] { "Vbproj" },
termValues: new[] { "ActiveProjectCapability:VB"})]
[ProvideUIContextRule(CsProjMenuVisibilityGuid, name: nameof(CsProjMenuVisibilityGuid),
expression: "Csproj", termNames: new[] { "Csproj" },
termValues: new[] { "ActiveProjectCapability:CSharp" })]
[ProvideUIContextRule(VbProjMenuVisibilityGuid, name: nameof(VbProjMenuVisibilityGuid),
expression: "Vbproj", termNames: new[] { "Vbproj" },
termValues: new[] { "ActiveProjectCapability:VB" })]
[ProvideUIContextRule(CsSolutionMenuVisibilityGuid, name: nameof(CsSolutionMenuVisibilityGuid),
expression: "HasCsproj", termNames: new[] { "HasCsproj" },
termValues: new[] { "SolutionHasProjectCapability:CSharp" })]
[ProvideUIContextRule(VbSolutionMenuVisibilityGuid, name: nameof(VbSolutionMenuVisibilityGuid),
expression: "HasVbproj", termNames: new[] { "HasVbproj" },
termValues: new[] { "SolutionHasProjectCapability:VB" })]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
public sealed class CodeConverterPackage : AsyncPackage
[VisualStudioContribution]
public sealed class CodeConverterPackage : Extension
{
/// <summary>
/// ConvertCSToVBCommandPackage GUID string.
/// </summary>
public const string PackageGuidString = "60378c8b-d75c-4fb2-aa2b-58609d67f886";

public const string CsEditorMenuVisibilityGuid = "64448be3-dcfe-467c-8659-408d672a9909";
public const string VbEditorMenuVisibilityGuid = "8eb86734-0b20-4986-9f20-9ed22824d0e2";
public const string CsFileMenuVisibilityGuid = "e32d529f-034b-4fe8-8e27-33a8ecf8f9ca";
public const string VbFileMenuVisibilityGuid = "207ed41c-1bf3-4e92-ad4f-f910b461acfc";
public const string CsNodeMenuVisibilityGuid = "1c92cd03-4bcc-4426-8c66-d34b081df30c";
public const string VbNodeMenuVisibilityGuid = "3151fbb3-0dd3-4804-8371-3340f65fd05d";
public const string CsProjMenuVisibilityGuid = "045a3ed1-4cb2-4c47-95be-0d99948e854f";
public const string VbProjMenuVisibilityGuid = "11700acc-38d7-4fc1-88dd-9e316aa5d6d5";
public const string CsSolutionMenuVisibilityGuid = "cbe34396-af03-49ab-8945-3611a641abf6";
public const string VbSolutionMenuVisibilityGuid = "3332e9e5-019c-4e93-b75a-2499f6f1cec6";
public const string ConvertableSolutionMenuVisibilityGuid = "8e7192d0-28b7-4fe7-8d84-82c1db98d459";
private readonly VisualStudioExtensibility _extensibility;

internal Cancellation PackageCancellation { get; } = new();

/// <summary>
/// Initializes a new instance of package class.
/// </summary>
public CodeConverterPackage()
public CodeConverterPackage(VisualStudioExtensibility extensibility)
{
// Inside this method you can place any initialization code that does not require
// any Visual Studio service because at this point the package object is created but
// not sited yet inside Visual Studio environment. The place to do all the other
// initialization is the Initialize method.
_extensibility = extensibility;
}

/// <summary>
/// Initialization of the package; this method is called right after the package is sited, so this is the place
/// where you can put all the initialization code that rely on services provided by VisualStudio.
/// </summary>
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
protected override void Dispose(bool disposing)
{
var oleMenuCommandService = await this.GetServiceAsync<IMenuCommandService, OleMenuCommandService>();
var componentModel = await this.GetServiceAsync<SComponentModel, IComponentModel>();
PackageCancellation.Dispose();
base.Dispose(disposing);
}

public override ExtensionConfiguration ExtensionConfiguration {
get {
var activationConstraints = new[] {ProjectCapability.CSharp, ProjectCapability.VB}
.Select(ActivationConstraint.ActiveProjectCapability);
return new ExtensionConfiguration() {
LoadedWhen = ActivationConstraint.Or(activationConstraints.ToArray()),
RequiresInProcessHosting = false,
Metadata = new ExtensionMetadata("ICSharpCode.CodeConverter", ExtensionAssemblyVersion, "ICSharpCode", "Code Converter", "Convert projects/files between VB.NET and C#")
};

await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var visualStudioWorkspace = componentModel.GetService<VisualStudioWorkspace>();
var codeConversion = await CodeConversion.CreateAsync(this, visualStudioWorkspace, this.GetDialogPageAsync<ConverterOptionsPage>);
ConvertCSToVBCommand.Initialize(this, oleMenuCommandService, codeConversion);
ConvertVBToCSCommand.Initialize(this, oleMenuCommandService, codeConversion);
PasteAsVB.Initialize(this, oleMenuCommandService, codeConversion);
PasteAsCS.Initialize(this, oleMenuCommandService, codeConversion);
VisualStudioInteraction.Initialize(PackageCancellation);
await TaskScheduler.Default;
await base.InitializeAsync(cancellationToken, progress);
}
}

internal OleMenuCommandWithBlockingStatus CreateCommand(Func<CancellationToken, Task> callbackAsync, CommandID menuCommandId)
protected override void InitializeServices(IServiceCollection serviceCollection)
{
return new OleMenuCommandWithBlockingStatus(JoinableTaskFactory, PackageCancellation, callbackAsync, menuCommandId);
base.InitializeServices(serviceCollection);
this.InitializeSettingsAsync(_extensibility).Forget();
}

protected override void Dispose(bool disposing)
private async Task InitializeSettingsAsync(VisualStudioExtensibility extensibility)
{
PackageCancellation.Dispose();
base.Dispose(disposing);
await extensibility.Settings().SubscribeAsync(
[ConverterSettings.ConverterSettingsCategory],
CancellationToken.None, values => {
var settingsAccessor = new ConverterSettingsAccessor(values);
//TODO: Bind/pass this somewhere
});
}
}
Loading
Loading