Skip to content

Commit

Permalink
added support for asynchronous package loading (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
csoltenborn committed Dec 16, 2018
1 parent 52900c8 commit bcd3aa1
Show file tree
Hide file tree
Showing 7 changed files with 493 additions and 67 deletions.
7 changes: 3 additions & 4 deletions GoogleTestAdapter/Packaging.GTA/source.extension.vsixmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
<Tags>Unit Test, C++, Google Test, GoogleTest, GTest, Test Explorer, Testing</Tags>
</Metadata>
<Installation InstalledByMsi="false">
<InstallationTarget Version="11.0" Id="Microsoft.VisualStudio.Premium" />
<InstallationTarget Version="[11.0,16.0]" Id="Microsoft.VisualStudio.Pro" />
<InstallationTarget Version="[11.0,16.0]" Id="Microsoft.VisualStudio.Enterprise" />
<InstallationTarget Version="[11.0,16.0]" Id="Microsoft.VisualStudio.Ultimate" />
<InstallationTarget Version="[12.0,16.0]" Id="Microsoft.VisualStudio.Pro" />
<InstallationTarget Version="[12.0,16.0]" Id="Microsoft.VisualStudio.Enterprise" />
<InstallationTarget Version="[12.0,16.0]" Id="Microsoft.VisualStudio.Ultimate" />
<InstallationTarget Version="[12.0,16.0]" Id="Microsoft.VisualStudio.Community" />
</Installation>
<Dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//------------------------------------------------------------------------------
// <copyright file="PackageRegistrationAttribute.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.VisualStudio.AsyncPackageHelpers {

using System;
using System.Globalization;
using System.IO;
using System.ComponentModel;
using System.ComponentModel.Design;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

/// <devdoc>
/// This attribute is defined on a package to get it to be registered. It
/// is internal because packages are meant to be registered, so it is
/// implicit just by having a package in the assembly.
/// </devdoc>
[AttributeUsage(AttributeTargets.Class, Inherited=true, AllowMultiple=false)]
public sealed class AsyncPackageRegistrationAttribute : RegistrationAttribute
{
private RegistrationMethod registrationMethod = RegistrationMethod.Default;
private bool useManagedResources = false;
private bool allowsBackgroundLoad = false;
private string satellitePath = null;

/// <devdoc>
/// Select between specifying the Codebase entry or the Assembly entry in the registry.
/// This can be overriden during registration
/// </devdoc>
public RegistrationMethod RegisterUsing
{
get
{
return registrationMethod;
}
set
{
registrationMethod = value;
}
}

/// <summary>
/// For managed resources, there should not be a native ui dll registered.
/// </summary>
public bool UseManagedResourcesOnly
{
get { return useManagedResources; }
set { useManagedResources = value; }
}

/// <summary>
/// Package is safe to load on a background thread.
/// </summary>
public bool AllowsBackgroundLoading
{
get { return allowsBackgroundLoad; }
set { allowsBackgroundLoad = value; }
}

/// <summary>
/// To specify a resource dll located in a different location then the default,
/// set this property. This can be useful if your package is installed in the GAC.
/// If this is not set, the directory where the package is located will be use.
///
/// Note that the dll should be located at the following path:
/// SatellitePath\lcid\PackageDllNameUI.dll
/// </summary>
public string SatellitePath
{
get { return satellitePath; }
set { satellitePath = value; }
}

private string RegKeyName(RegistrationContext context)
{
return String.Format(CultureInfo.InvariantCulture, "Packages\\{0}", context.ComponentType.GUID.ToString("B"));
}

/// <devdoc>
/// Called to register this attribute with the given context. The context
/// contains the location where the registration information should be placed.
/// it also contains such as the type being registered, and path information.
///
/// This method is called both for registration and unregistration. The difference is
/// that unregistering just uses a hive that reverses the changes applied to it.
/// </devdoc>
/// <param name="context">
/// Contains the location where the registration information should be placed.
/// It also contains other information such as the type being registered
/// and path of the assembly.
/// </param>
public override void Register(RegistrationContext context) {
Type t = context.ComponentType;

Key packageKey = null;
try
{
packageKey = context.CreateKey(RegKeyName(context));

//use a friendly description if it exists.
DescriptionAttribute attr = TypeDescriptor.GetAttributes(t)[typeof(DescriptionAttribute)] as DescriptionAttribute;
if (attr != null && !String.IsNullOrEmpty(attr.Description)) {
packageKey.SetValue(string.Empty, attr.Description);
}
else {
packageKey.SetValue(string.Empty, t.Name);
}

packageKey.SetValue("InprocServer32", context.InprocServerPath);
packageKey.SetValue("Class", t.FullName);

// If specified on the command line, let the command line option override
if (context.RegistrationMethod != RegistrationMethod.Default)
{
registrationMethod = context.RegistrationMethod;
}

// Select registration method
switch (registrationMethod)
{
case RegistrationMethod.Assembly:
case RegistrationMethod.Default:
packageKey.SetValue("Assembly", t.Assembly.FullName);
break;

case RegistrationMethod.CodeBase:
packageKey.SetValue("CodeBase", context.CodeBase);
break;
}

Key childKey = null;
if (!useManagedResources)
{
try
{
childKey = packageKey.CreateSubkey("SatelliteDll");

// Register the satellite dll
string satelliteDllPath;
if (SatellitePath != null)
{
// Use provided path
satelliteDllPath = context.EscapePath(SatellitePath);
}
else
{
// Default to package path
satelliteDllPath = context.ComponentPath;
}
childKey.SetValue("Path", satelliteDllPath);
childKey.SetValue("DllName", String.Format(CultureInfo.InvariantCulture, "{0}UI.dll", Path.GetFileNameWithoutExtension(t.Assembly.ManifestModule.Name)));
}
finally
{
if (childKey != null)
childKey.Close();
}
}

if (allowsBackgroundLoad)
{
packageKey.SetValue("AllowsBackgroundLoad", true);
}

if (typeof(IVsPackageDynamicToolOwner).IsAssignableFrom(context.ComponentType) ||
typeof(IVsPackageDynamicToolOwnerEx).IsAssignableFrom(context.ComponentType))
{
packageKey.SetValue("SupportsDynamicToolOwner", Microsoft.VisualStudio.PlatformUI.Boxes.BooleanTrue);
}
}
finally
{
if (packageKey != null)
packageKey.Close();
}
}

/// <devdoc>
/// Unregister this package.
/// </devdoc>
/// <param name="context"></param>
public override void Unregister(RegistrationContext context)
{
context.RemoveKey(RegKeyName(context));
}

}
}
48 changes: 48 additions & 0 deletions GoogleTestAdapter/VsPackage.GTA/AsyncPackage/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell.Interop;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;

namespace Microsoft.VisualStudio.AsyncPackageHelpers
{
public static class ExtensionMethods
{
/// <summary>
/// Helper method to use async/await with IAsyncServiceProvider implementation
/// </summary>
/// <param name="asyncServiceProvider">IAsyncServciceProvider instance</param>
/// <param name="serviceType">Type of the Visual Studio service requested</param>
/// <returns>Service object as type of T</returns>
public static async Task<T> GetServiceAsync<T>(this IAsyncServiceProvider asyncServiceProvider, Type serviceType) where T : class
{
T returnValue = null;

await ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
{
object serviceInstance = null;
Guid serviceTypeGuid = serviceType.GUID;
serviceInstance = await asyncServiceProvider.QueryServiceAsync(ref serviceTypeGuid);
// We have to make sure we are on main UI thread before trying to cast as underlying implementation
// can be an STA COM object and doing a cast would require calling QueryInterface/AddRef marshaling
// to main thread via COM.
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
returnValue = serviceInstance as T;
});

return returnValue;
}

/// <summary>
/// Gets if async package is supported in the current instance of Visual Studio
/// </summary>
/// <param name="serviceProvider">an IServiceProvider instance, usually a Package instance</param>
/// <returns>true if async packages are supported</returns>
public static bool IsAsyncPackageSupported(this IServiceProvider serviceProvider)
{
IAsyncServiceProvider asyncServiceProvider = serviceProvider.GetService(typeof(SAsyncServiceProvider)) as IAsyncServiceProvider;
return asyncServiceProvider != null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//------------------------------------------------------------------------------
// <copyright from='2003' to='2004' company='Microsoft Corporation'>
// Copyright (c) Microsoft Corporation, All rights reserved.
// This code sample is provided "AS IS" without warranty of any kind,
// it is not recommended for use in a production environment.
// </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.VisualStudio.AsyncPackageHelpers
{

using System;
using System.Globalization;
using Microsoft.VisualStudio.Shell;

[Flags]
public enum PackageAutoLoadFlags
{
/// <summary>
/// Indicates no special auto-load behavior. This is the default flag value if not specified.
/// </summary>
None = 0x0,

/// <summary>
/// When set package will not auto load in newer Visual Studio versions with rule based UI contexts
/// </summary>
SkipWhenUIContextRulesActive = 0x1,

/// <summary>
/// When set, if the associated package is marked as allowing background loads (via the <see cref="AsyncPackageRegistrationAttribute"/>),
/// then the package will be loaded asynchronously, in the background, when the associated UI context is triggered.
/// </summary>
BackgroundLoad = 0x2
}

/// <summary>
/// This attribute registers the package as an extender. The GUID passed in determines
/// what is being extended. The attributes on a package do not control the behavior of
/// the package, but they can be used by registration tools to register the proper
/// information with Visual Studio.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class ProvideAutoLoadAttribute : RegistrationAttribute
{

private Guid loadGuid = Guid.Empty;

/// <summary>
/// Specify that the package should get loaded when this context is active.
/// </summary>
/// <param name="cmdUiContextGuid">Context which should trigger the loading of your package.</param>
public ProvideAutoLoadAttribute(string cmdUiContextGuid) : this(cmdUiContextGuid, PackageAutoLoadFlags.None)
{
}

/// <summary>
/// Specify that the package should get loaded when this context is active.
/// </summary>
/// <param name="cmdUiContextGuid">Context which should trigger the loading of your package.</param>
public ProvideAutoLoadAttribute(string cmdUiContextGuid, PackageAutoLoadFlags flags = PackageAutoLoadFlags.None)
{
loadGuid = new Guid(cmdUiContextGuid);
Flags = flags;
}

/// <summary>
/// Context Guid which triggers the loading of the package.
/// </summary>
public Guid LoadGuid
{
get
{
return loadGuid;
}
}

/// <summary>
/// Specifies the options for package auto load entry
/// </summary>
public PackageAutoLoadFlags Flags
{
get;
private set;
}

/// <summary>
/// The reg key name of this AutoLoad.
/// </summary>
private string RegKeyName
{
get
{
return string.Format(CultureInfo.InvariantCulture, "AutoLoadPackages\\{0}", loadGuid.ToString("B"));
}
}

/// <summary>
/// Called to register this attribute with the given context. The context
/// contains the location where the registration information should be placed.
/// it also contains such as the type being registered, and path information.
/// </summary>
public override void Register(RegistrationContext context)
{
using (Key childKey = context.CreateKey(RegKeyName))
{
childKey.SetValue(context.ComponentType.GUID.ToString("B"), (int)Flags);
}
}

/// <summary>
/// Unregister this AutoLoad specification.
/// </summary>
public override void Unregister(RegistrationContext context)
{
context.RemoveValue(RegKeyName, context.ComponentType.GUID.ToString("B"));
}
}
}
Loading

0 comments on commit bcd3aa1

Please sign in to comment.