diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs b/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs index 12b59e34..d279c559 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell; using SmartCmdArgs.Helper; using System.ComponentModel; using System.Linq; @@ -78,6 +78,7 @@ public CmdArgsOptionPage() : base() private bool _manageWorkingDirectories; private bool _manageLaunchApplication; private bool _vcsSupportEnabled; + private bool _useCpsVirtualProfile; private bool _useSolutionDir; private bool _macroEvaluationEnabled; @@ -111,6 +112,7 @@ public bool UseMonospaceFont set => SetAndNotify(value, ref _useMonospaceFont); } + [Category("Appearance")] [DisplayName("Display Tags for CLAs")] [Description("If enabled the item tag 'CLA' is displayed for Command Line Arguments. Normally the tag 'ENV' is only displayed for environment varibales.")] @@ -201,6 +203,16 @@ public bool VcsSupportEnabled set => SetAndNotify(value, ref _vcsSupportEnabled); } + [Category("Settings Defaults")] + [DisplayName("Use CPS Virtual Profile")] + [Description("If enabled a virtual profile is created for CPS projects and only this profile is changed by the extension.")] + [DefaultValue(false)] + public bool UseCpsVirtualProfile + { + get => _useCpsVirtualProfile; + set => SetAndNotify(value, ref _useCpsVirtualProfile); + } + [Category("Settings Defaults")] [DisplayName("Use Solution Directory")] [Description("If enabled all arguments of every project will be stored in a single file next to the *.sln file. (Only if version control support is enabled)")] diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsPackage.cs b/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsPackage.cs index 51481ec1..981ffe8b 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsPackage.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsPackage.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // Copyright (c) Company. All rights reserved. // @@ -68,7 +68,8 @@ public sealed class CmdArgsPackage : AsyncPackage private ISuoDataService suoDataService; private ILifeCycleService lifeCycleService; private IVsEventHandlingService vsEventHandling; - private IFileStorageEventHandlingService fileStorageEventHandling; + private IFileStorageEventHandlingService fileStorageEventHandling; + private ICpsProjectConfigService cpsProjectConfigService; private ToolWindowViewModel toolWindowViewModel; private TreeViewModel treeViewModel; @@ -104,7 +105,8 @@ public CmdArgsPackage() suoDataService = ServiceProvider.GetRequiredService(); lifeCycleService = ServiceProvider.GetRequiredService(); vsEventHandling = ServiceProvider.GetRequiredService(); - fileStorageEventHandling = ServiceProvider.GetRequiredService(); + fileStorageEventHandling = ServiceProvider.GetRequiredService(); + cpsProjectConfigService = ServiceProvider.GetRequiredService(); } protected override void Dispose(bool disposing) @@ -169,7 +171,8 @@ private ServiceProvider ConfigureServices() services.AddLazySingleton(x => GetDialogPage()); services.AddLazySingleton(); services.AddLazySingleton(); - services.AddLazySingleton(); + services.AddLazySingleton(); + services.AddLazySingleton(); services.AddLazySingleton(); services.AddSingleton(); services.AddSingleton(); @@ -260,9 +263,9 @@ public List GetLaunchProfiles(Guid projGuid) var project = vsHelper.HierarchyForProjectGuid(projGuid); List launchProfiles = null; - if (project?.IsCpsProject() == true) + if (project?.SupportsLaunchProfiles() == true) { - launchProfiles = CpsProjectSupport.GetLaunchProfileNames(project.GetProject())?.ToList(); + launchProfiles = cpsProjectConfigService.GetLaunchProfileNames(project.GetProject())?.ToList(); } return launchProfiles ?? new List(); diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectSupport.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectConfigService.cs similarity index 50% rename from SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectSupport.cs rename to SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectConfigService.cs index 5b364858..ca7e2fca 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectSupport.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectConfigService.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.ProjectSystem; +using EnvDTE; +using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.Debug; using Microsoft.VisualStudio.ProjectSystem.Properties; using SmartCmdArgs.DataSerialization; @@ -7,6 +8,11 @@ using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks.Dataflow; +#if DYNAMIC_VSProjectManaged +using System.Reflection; +using System.Reflection.Emit; +using Expression = System.Linq.Expressions.Expression; +#endif // This isolation of Microsoft.VisualStudio.ProjectSystem dependencies into one file ensures compatibility // across various Visual Studio installations. This is crucial because not all Visual Studio workloads @@ -19,11 +25,32 @@ // As such, ensuring compatibility requires knowledge of the version Visual Studio redirects to, which varies // by Visual Studio installation version. -namespace SmartCmdArgs.Helper +namespace SmartCmdArgs.Services { - public static class CpsProjectSupport + public interface ICpsProjectConfigService { - private static bool TryGetProjectServices(EnvDTE.Project project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices) + string GetActiveLaunchProfileName(Project project); + void GetItemsFromConfig(Project project, List allArgs, bool includeArgs, bool includeEnvVars, bool includeWorkDir, bool includeLaunchApp); + IEnumerable GetLaunchProfileNames(Project project); + IDisposable ListenToLaunchProfileChanges(Project project, Action listener); + void SetActiveLaunchProfileByName(Project project, string profileName); + void SetActiveLaunchProfileToVirtualProfile(Project project); + void SetConfig(Project project, string arguments, IDictionary envVars, string workDir, string launchApp); + } + + public class CpsProjectConfigService : ICpsProjectConfigService + { + public static string VirtualProfileName = "Smart CLI Args"; + + private readonly IOptionsSettingsService optionsSettingsService; + + public CpsProjectConfigService( + IOptionsSettingsService optionsSettingsService) + { + this.optionsSettingsService = optionsSettingsService; + } + + private bool TryGetProjectServices(EnvDTE.Project project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices) { IVsBrowseObjectContext context = project as IVsBrowseObjectContext; if (context == null && project != null) @@ -55,7 +82,7 @@ private static bool TryGetProjectServices(EnvDTE.Project project, out IUnconfigu } } - public static string GetActiveLaunchProfileName(EnvDTE.Project project) + public string GetActiveLaunchProfileName(EnvDTE.Project project) { if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) { @@ -65,7 +92,21 @@ public static string GetActiveLaunchProfileName(EnvDTE.Project project) return null; } - public static IEnumerable GetLaunchProfileNames(EnvDTE.Project project) + public void SetActiveLaunchProfileByName(EnvDTE.Project project, string profileName) + { + if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) + { + var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); + projectServices.ThreadingPolicy.ExecuteSynchronously(async () => + { + await launchSettingsProvider.SetActiveProfileAsync(profileName); + }); + } + } + + public void SetActiveLaunchProfileToVirtualProfile(EnvDTE.Project project) => SetActiveLaunchProfileByName(project, VirtualProfileName); + + public IEnumerable GetLaunchProfileNames(EnvDTE.Project project) { if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) { @@ -75,10 +116,11 @@ public static IEnumerable GetLaunchProfileNames(EnvDTE.Project project) return null; } - public static IDisposable ListenToLaunchProfileChanges(EnvDTE.Project project, Action listener) + public IDisposable ListenToLaunchProfileChanges(EnvDTE.Project project, Action listener) { if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) { + var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); if (launchSettingsProvider == null) @@ -92,7 +134,7 @@ public static IDisposable ListenToLaunchProfileChanges(EnvDTE.Project project, A return null; } - public static void SetCpsProjectConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp) + public void SetConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp) { IUnconfiguredProjectServices unconfiguredProjectServices; IProjectServices projectServices; @@ -100,12 +142,21 @@ public static void SetCpsProjectConfig(EnvDTE.Project project, string arguments, if (TryGetProjectServices(project, out unconfiguredProjectServices, out projectServices)) { var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); - var activeLaunchProfile = launchSettingsProvider?.ActiveProfile; + ILaunchProfile baseLaunchProfile = null; + if (optionsSettingsService.UseCpsVirtualProfile) + { + baseLaunchProfile = launchSettingsProvider.CurrentSnapshot.Profiles.FirstOrDefault(x => x.Name == VirtualProfileName); + } + + if (baseLaunchProfile == null) + { + baseLaunchProfile = launchSettingsProvider?.ActiveProfile; + } - if (activeLaunchProfile == null) + if (baseLaunchProfile == null) return; - WritableLaunchProfile writableLaunchProfile = new WritableLaunchProfile(activeLaunchProfile); + var writableLaunchProfile = WritableLaunchProfile.GetWritableLaunchProfile(baseLaunchProfile); if (arguments != null) writableLaunchProfile.CommandLineArgs = arguments; @@ -119,30 +170,31 @@ public static void SetCpsProjectConfig(EnvDTE.Project project, string arguments, if (launchApp != null) writableLaunchProfile.CommandName = launchApp; - // Does not work on VS2015, which should be okay ... - // We don't hold references for VS2015, where the interface is called IThreadHandling - IProjectThreadingService projectThreadingService = projectServices.ThreadingPolicy; - projectThreadingService.ExecuteSynchronously(() => + if (optionsSettingsService.UseCpsVirtualProfile) + { + writableLaunchProfile.Name = VirtualProfileName; + writableLaunchProfile.DoNotPersist = true; + } + + projectServices.ThreadingPolicy.ExecuteSynchronously(() => { return launchSettingsProvider.AddOrUpdateProfileAsync(writableLaunchProfile, addToFront: false); }); } } - public static List GetCpsProjectAllArguments(EnvDTE.Project project, bool includeArgs, bool includeEnvVars, bool includeWorkDir, bool includeLaunchApp) + public void GetItemsFromConfig(EnvDTE.Project project, List allArgs, bool includeArgs, bool includeEnvVars, bool includeWorkDir, bool includeLaunchApp) { IUnconfiguredProjectServices unconfiguredProjectServices; IProjectServices projectServices; - var result = new List(); - if (TryGetProjectServices(project, out unconfiguredProjectServices, out projectServices)) { var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); var launchProfiles = launchSettingsProvider?.CurrentSnapshot?.Profiles; if (launchProfiles == null) - return result; + return; foreach (var profile in launchProfiles) { @@ -173,17 +225,18 @@ public static List GetCpsProjectAllArguments(EnvDTE.Project project if (profileGrp.Items.Count > 0) { - result.Add(profileGrp); + allArgs.Add(profileGrp); } } } - - return result; } } - - class WritableLaunchProfile : ILaunchProfile + public class WritableLaunchProfile : ILaunchProfile //must be public to avoid having to declare our dynamic assembly a friend +#if VS17 && ! DYNAMIC_VSProjectManaged + , IPersistOption +#endif { + // ILaunchProfile public string Name { set; get; } public string CommandName { set; get; } public string ExecutablePath { set; get; } @@ -194,8 +247,15 @@ class WritableLaunchProfile : ILaunchProfile public ImmutableDictionary EnvironmentVariables { set; get; } public ImmutableDictionary OtherSettings { set; get; } + // IPersistOption + public bool DoNotPersist { get; set; } +#if DYNAMIC_VSProjectManaged + private static Func LaunchProfileIsDoNotPersistFunc; +#endif + private static Lazy IPersistOptionType = new Lazy(() => typeof(ILaunchProfile).Assembly.GetType("Microsoft.VisualStudio.ProjectSystem.Debug.IPersistOption")); public WritableLaunchProfile(ILaunchProfile launchProfile) { + // ILaunchProfile Name = launchProfile.Name; ExecutablePath = launchProfile.ExecutablePath; CommandName = launchProfile.CommandName; @@ -205,6 +265,79 @@ public WritableLaunchProfile(ILaunchProfile launchProfile) LaunchUrl = launchProfile.LaunchUrl; EnvironmentVariables = launchProfile.EnvironmentVariables; OtherSettings = launchProfile.OtherSettings; +#if VS17 +#if DYNAMIC_VSProjectManaged + if (LaunchProfileIsDoNotPersistFunc == null) + { + if (IPersistOptionType.Value == null) + LaunchProfileIsDoNotPersistFunc = (_) => false; + else + { + var instanceParam = Expression.Parameter(typeof(ILaunchProfile)); + var asIPersist = Expression.TypeAs(instanceParam, IPersistOptionType.Value); + var expr = Expression.Condition(Expression.Equal(asIPersist, Expression.Constant(null)), Expression.Constant(false), Expression.Property(asIPersist, nameof(DoNotPersist))); + LaunchProfileIsDoNotPersistFunc = Expression.Lambda>(expr, instanceParam).Compile(); + } + } + DoNotPersist = LaunchProfileIsDoNotPersistFunc(launchProfile); + +#else + if (launchProfile is IPersistOption persistOptionLaunchProfile) + { + // IPersistOption + DoNotPersist = persistOptionLaunchProfile.DoNotPersist; + } +#endif +#endif } + + private static Func getWritableProfileFunc; + internal static WritableLaunchProfile GetWritableLaunchProfile(ILaunchProfile profile) + { +#if DYNAMIC_VSProjectManaged + if (getWritableProfileFunc == null && IPersistOptionType.Value != null) + { + var ourType = typeof(WritableLaunchProfile); + var asmName = new AssemblyName() { Name = "SmartCLIArgsDynamicAsm" }; + asmName.SetPublicKey(ourType.Assembly.GetName().GetPublicKey()); + var assemBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); + + var classBuilder = assemBuilder.DefineDynamicModule("SmartCLIArgsDynamicMod").DefineType("DynamicWritableLaunchProfile", TypeAttributes.NotPublic | TypeAttributes.Class, ourType); + classBuilder.AddInterfaceImplementation(IPersistOptionType.Value); + // not sure why AssemblyBuilder is a baby true IL code doesn't define interface impelmentations that are just inherited + var persist_get = classBuilder.DefineMethod("get_" + nameof(DoNotPersist), MethodAttributes.Virtual | MethodAttributes.Public, typeof(bool), Type.EmptyTypes); + var il = persist_get.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.EmitCall(OpCodes.Callvirt, ourType.GetMethod(persist_get.Name), null); + il.Emit(OpCodes.Ret); + + + classBuilder.DefineMethodOverride(persist_get, IPersistOptionType.Value.GetMethod(persist_get.Name)); + + var constructorArgTypes = new[] { typeof(ILaunchProfile) }; + var constructor = classBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, constructorArgTypes); + var baseConstructor = ourType.GetConstructor(constructorArgTypes); + il = constructor.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Call, baseConstructor); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ret); + var DynamicWritableLaunchProfileType = classBuilder.CreateType(); + var constructorInfo = DynamicWritableLaunchProfileType.GetConstructor(constructorArgTypes); + var instanceParam = Expression.Parameter(typeof(ILaunchProfile)); + var expr = Expression.TypeAs(Expression.New(constructorInfo, instanceParam), ourType); + getWritableProfileFunc = Expression.Lambda>(expr, instanceParam).Compile(); + + } + if (IPersistOptionType.Value != null) + return getWritableProfileFunc(profile); +#endif + return new WritableLaunchProfile(profile); + + + } + } } diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Services/ItemAggregationService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/ItemAggregationService.cs index 45e435eb..d275177b 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/ItemAggregationService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/ItemAggregationService.cs @@ -1,4 +1,4 @@ -using SmartCmdArgs.Helper; +using SmartCmdArgs.Helper; using SmartCmdArgs.ViewModel; using SmartCmdArgs.Wrapper; using System; @@ -23,15 +23,19 @@ internal class ItemAggregationService : IItemAggregationService private readonly IItemEvaluationService itemEvaluation; private readonly IVisualStudioHelperService vsHelper; private readonly TreeViewModel treeViewModel; + private readonly ICpsProjectConfigService cpsProjectConfigService; + public ItemAggregationService( IItemEvaluationService itemEvaluation, IVisualStudioHelperService vsHelper, - TreeViewModel treeViewModel) + TreeViewModel treeViewModel, + ICpsProjectConfigService cpsProjectConfigService) { this.itemEvaluation = itemEvaluation; this.vsHelper = vsHelper; this.treeViewModel = treeViewModel; + this.cpsProjectConfigService = cpsProjectConfigService; } private TResult AggregateComamndLineItemsForProject(IVsHierarchyWrapper project, Func, Func, CmdContainer, TResult> joinItems) @@ -49,8 +53,8 @@ private TResult AggregateComamndLineItemsForProject(IVsHierarchyWrapper string projPlatform = projectObj?.ConfigurationManager?.ActiveConfiguration?.PlatformName; string activeLaunchProfile = null; - if (project.IsCpsProject()) - activeLaunchProfile = CpsProjectSupport.GetActiveLaunchProfileName(projectObj); + if (project.SupportsLaunchProfiles()) + activeLaunchProfile = cpsProjectConfigService.GetActiveLaunchProfileName(projectObj); TResult JoinContainer(CmdContainer con) { diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsEventHandlingService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsEventHandlingService.cs index 2f41d767..e6079536 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsEventHandlingService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsEventHandlingService.cs @@ -1,5 +1,6 @@ -using SmartCmdArgs.ViewModel; +using SmartCmdArgs.ViewModel; using System; +using System.Linq; namespace SmartCmdArgs.Services { @@ -18,6 +19,8 @@ internal class OptionsSettingsEventHandlingService : IOptionsSettingsEventHandli private readonly IViewModelUpdateService viewModelUpdateService; private readonly ToolWindowViewModel toolWindowViewModel; private readonly IToolWindowHistory toolWindowHistory; + private readonly IProjectConfigService projectConfigService; + private readonly ICpsProjectConfigService cpsProjectConfigService; public OptionsSettingsEventHandlingService( IOptionsSettingsService optionsSettings, @@ -26,7 +29,9 @@ public OptionsSettingsEventHandlingService( IVisualStudioHelperService vsHelper, IViewModelUpdateService viewModelUpdateService, ToolWindowViewModel toolWindowViewModel, - IToolWindowHistory toolWindowHistory) + IToolWindowHistory toolWindowHistory, + IProjectConfigService projectConfigService, + ICpsProjectConfigService cpsProjectConfigService) { this.optionsSettings = optionsSettings; this.settingsService = settingsService; @@ -35,6 +40,8 @@ public OptionsSettingsEventHandlingService( this.viewModelUpdateService = viewModelUpdateService; this.toolWindowViewModel = toolWindowViewModel; this.toolWindowHistory = toolWindowHistory; + this.projectConfigService = projectConfigService; + this.cpsProjectConfigService = cpsProjectConfigService; } public void Dispose() @@ -64,6 +71,7 @@ private void OptionsSettings_PropertyChanged(object sender, System.ComponentMode case nameof(IOptionsSettingsService.JsonRootPath): JsonRootPathChanged(); break; case nameof(IOptionsSettingsService.VcsSupportEnabled): VcsSupportChanged(); break; case nameof(IOptionsSettingsService.UseSolutionDir): UseSolutionDirChanged(); break; + case nameof(IOptionsSettingsService.UseCpsVirtualProfile): UseCpsVirtualProfileChanged(); break; case nameof(IOptionsSettingsService.ManageCommandLineArgs): viewModelUpdateService.UpdateIsActiveForParamsDebounced(); break; case nameof(IOptionsSettingsService.ManageEnvironmentVars): viewModelUpdateService.UpdateIsActiveForParamsDebounced(); break; case nameof(IOptionsSettingsService.ManageWorkingDirectories): viewModelUpdateService.UpdateIsActiveForParamsDebounced(); break; @@ -118,5 +126,13 @@ private void UseSolutionDirChanged() fileStorage.DeleteAllUnusedArgFiles(); fileStorage.SaveAllProjects(); } + private void UseCpsVirtualProfileChanged() + { + foreach (var project in vsHelper.GetSupportedProjects().Where(x => x.SupportsLaunchProfiles())) + { + projectConfigService.UpdateProjectConfig(project); + cpsProjectConfigService.SetActiveLaunchProfileToVirtualProfile(project.GetProject()); + } + } } } diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsService.cs index 688413ac..58405dd5 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsService.cs @@ -1,4 +1,4 @@ -using SmartCmdArgs.Helper; +using SmartCmdArgs.Helper; using SmartCmdArgs.ViewModel; using System; using System.ComponentModel; @@ -23,6 +23,7 @@ public interface IOptionsSettingsService bool UseSolutionDir { get; } // Options + bool UseCpsVirtualProfile { get; } bool UseMonospaceFont { get; } bool DisplayTagForCla { get; } bool DeleteEmptyFilesAutomatically { get; } @@ -67,6 +68,11 @@ public OptionsSettingsService( public bool UseSolutionDir => _vsHelperService?.GetSolutionFilename() != null && (settingsViewModel.UseSolutionDir ?? OptionsPage.UseSolutionDir); // Options +#if VS17 + public bool UseCpsVirtualProfile => OptionsPage.UseCpsVirtualProfile; +#else + public bool UseCpsVirtualProfile => false; +#endif public bool UseMonospaceFont => OptionsPage.UseMonospaceFont; public bool DisplayTagForCla => OptionsPage.DisplayTagForCla; public bool DeleteEmptyFilesAutomatically => OptionsPage.DeleteEmptyFilesAutomatically; diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs index e144f24f..44f545dc 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs @@ -22,15 +22,28 @@ public class ProjectConfigService : IProjectConfigService private readonly Lazy lifeCycleService; private readonly IOptionsSettingsService optionsSettings; private readonly IItemAggregationService itemAggregationService; + private readonly ICpsProjectConfigService cpsProjectConfigService; + + private readonly Dictionary configHandlerForSupportedProjects; + private readonly ProjectConfigHandlers cpsProjectConfigHandlers; public ProjectConfigService( Lazy lifeCycleService, IOptionsSettingsService optionsSettings, - IItemAggregationService itemAggregationService) + IItemAggregationService itemAggregationService, + ICpsProjectConfigService cpsProjectConfigService) { this.lifeCycleService = lifeCycleService; this.optionsSettings = optionsSettings; this.itemAggregationService = itemAggregationService; + this.cpsProjectConfigService = cpsProjectConfigService; + + configHandlerForSupportedProjects = InitConfigHandlerForSupportedProjects(); + cpsProjectConfigHandlers = new ProjectConfigHandlers + { + GetItemsFromConfig = cpsProjectConfigService.GetItemsFromConfig, + SetConfig = cpsProjectConfigService.SetConfig, + }; } private class ProjectConfigHandlers @@ -40,6 +53,79 @@ private class ProjectConfigHandlers public SetConfigDelegate SetConfig; public GetAllArgumentsDelegate GetItemsFromConfig; } + private Dictionary InitConfigHandlerForSupportedProjects() + { + return new Dictionary() + { + // C# + {ProjectKinds.CS, new ProjectConfigHandlers() { + SetConfig = (project, arguments, envVars, workDir, launchApp) => { + SetMultiConfigProperty(project, arguments, "StartArguments"); + SetMultiConfigProperty(project, workDir, "StartWorkingDirectory"); + SetMultiConfigProperty(project, launchApp, "StartProgram"); + }, + GetItemsFromConfig = GetItemsFromMultiConfig("StartArguments", "StartWorkingDirectory", "StartProgram") + } }, + // C# UWP + {ProjectKinds.CS_UWP, new ProjectConfigHandlers() { + SetConfig = (project, arguments, envVars, workDir, launchApp) => SetMultiConfigProperty(project, arguments, "UAPDebug.CommandLineArguments"), + GetItemsFromConfig = GetItemsFromMultiConfig("UAPDebug.CommandLineArguments") + } }, + + // VB.NET + {ProjectKinds.VB, new ProjectConfigHandlers() { + SetConfig = (project, arguments, _, workDir, launchApp) => { + SetMultiConfigProperty(project, arguments, "StartArguments"); + SetMultiConfigProperty(project, workDir, "StartWorkingDirectory"); + SetMultiConfigProperty(project, launchApp, "StartProgram"); + }, + GetItemsFromConfig = GetItemsFromMultiConfig("StartArguments", "StartWorkingDirectory", "StartProgram") + } }, + // C/C++ + {ProjectKinds.CPP, new ProjectConfigHandlers() { + SetConfig = SetVCProjEngineConfig, + GetItemsFromConfig = GetItemsFromVCProjEngineConfig + } }, + // Python + {ProjectKinds.Py, new ProjectConfigHandlers() { + SetConfig = (project, arguments, envVars, workDir, _) => { + SetSingleConfigProperty(project, arguments, "CommandLineArguments"); + SetSingleConfigEnvVars(project, envVars, "Environment"); + SetSingleConfigProperty(project, workDir, "WorkingDirectory"); + }, + GetItemsFromConfig = GetItemsFromSingleConfig("CommandLineArguments", "Environment", "WorkingDirectory", null), + } }, + // Node.js + {ProjectKinds.Node, new ProjectConfigHandlers() { + SetConfig = (project, arguments, envVars, workDir, _) => { + SetSingleConfigProperty(project, arguments, "ScriptArguments"); + SetSingleConfigEnvVars(project, envVars, "Environment"); + SetSingleConfigProperty(project, workDir, "WorkingDirectory"); + }, + GetItemsFromConfig = GetItemsFromSingleConfig("ScriptArguments", "Environment", "WorkingDirectory", null), + } }, + // C# - Lagacy DotNetCore + {ProjectKinds.CSCore, new ProjectConfigHandlers() { + SetConfig = cpsProjectConfigService.SetConfig, + GetItemsFromConfig = cpsProjectConfigService.GetItemsFromConfig, + } }, + // F# + {ProjectKinds.FS, new ProjectConfigHandlers() { + SetConfig = (project, arguments, _, workDir, launchApp) => { + SetMultiConfigProperty(project, arguments, "StartArguments"); + SetMultiConfigProperty(project, workDir, "StartWorkingDirectory"); + SetMultiConfigProperty(project, launchApp, "StartProgram"); + }, + GetItemsFromConfig = GetItemsFromMultiConfig("StartArguments", "StartWorkingDirectory", "StartProgram") + } }, + // Fortran + {ProjectKinds.Fortran, new ProjectConfigHandlers() { + SetConfig = SetVFProjEngineConfig, + GetItemsFromConfig = GetItemsFromVFProjEngineConfig + } }, + }; + } + private static string GetEnvVarStringFromDict(IDictionary envVars) => string.Join(Environment.NewLine, envVars.Select(x => $"{x.Key}={x.Value}")); @@ -623,100 +709,6 @@ private static void GetItemsFromVFProjEngineConfig(EnvDTE.Project project, List< #endregion VFProjEngine (Fortran) - #region Common Project System (CPS) - - private static void SetCpsProjectConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp) - { - // Should only be called in VS 2017 or higher - // .Net Core 2 is not supported by VS 2015, so this should not cause problems - CpsProjectSupport.SetCpsProjectConfig(project, arguments, envVars, workDir, launchApp); - } - - private static void GetItemsFromCpsProjectConfig(EnvDTE.Project project, List allArgs, bool includeArgs, bool includeEnvVars, bool includeWorkDir, bool includeLaunchApp) - { - // Should only be called in VS 2017 or higher - // see SetCpsProjectArguments - allArgs.AddRange(CpsProjectSupport.GetCpsProjectAllArguments(project, includeArgs, includeEnvVars, includeWorkDir, includeLaunchApp)); - } - - #endregion Common Project System (CPS) - - private static Dictionary supportedProjects = new Dictionary() - { - // C# - {ProjectKinds.CS, new ProjectConfigHandlers() { - SetConfig = (project, arguments, envVars, workDir, launchApp) => { - SetMultiConfigProperty(project, arguments, "StartArguments"); - SetMultiConfigProperty(project, workDir, "StartWorkingDirectory"); - SetMultiConfigProperty(project, launchApp, "StartProgram"); - }, - GetItemsFromConfig = GetItemsFromMultiConfig("StartArguments", "StartWorkingDirectory", "StartProgram") - } }, - // C# UWP - {ProjectKinds.CS_UWP, new ProjectConfigHandlers() { - SetConfig = (project, arguments, envVars, workDir, launchApp) => SetMultiConfigProperty(project, arguments, "UAPDebug.CommandLineArguments"), - GetItemsFromConfig = GetItemsFromMultiConfig("UAPDebug.CommandLineArguments") - } }, - - // VB.NET - {ProjectKinds.VB, new ProjectConfigHandlers() { - SetConfig = (project, arguments, _, workDir, launchApp) => { - SetMultiConfigProperty(project, arguments, "StartArguments"); - SetMultiConfigProperty(project, workDir, "StartWorkingDirectory"); - SetMultiConfigProperty(project, launchApp, "StartProgram"); - }, - GetItemsFromConfig = GetItemsFromMultiConfig("StartArguments", "StartWorkingDirectory", "StartProgram") - } }, - // C/C++ - {ProjectKinds.CPP, new ProjectConfigHandlers() { - SetConfig = SetVCProjEngineConfig, - GetItemsFromConfig = GetItemsFromVCProjEngineConfig - } }, - // Python - {ProjectKinds.Py, new ProjectConfigHandlers() { - SetConfig = (project, arguments, envVars, workDir, _) => { - SetSingleConfigProperty(project, arguments, "CommandLineArguments"); - SetSingleConfigEnvVars(project, envVars, "Environment"); - SetSingleConfigProperty(project, workDir, "WorkingDirectory"); - }, - GetItemsFromConfig = GetItemsFromSingleConfig("CommandLineArguments", "Environment", "WorkingDirectory", null), - } }, - // Node.js - {ProjectKinds.Node, new ProjectConfigHandlers() { - SetConfig = (project, arguments, envVars, workDir, _) => { - SetSingleConfigProperty(project, arguments, "ScriptArguments"); - SetSingleConfigEnvVars(project, envVars, "Environment"); - SetSingleConfigProperty(project, workDir, "WorkingDirectory"); - }, - GetItemsFromConfig = GetItemsFromSingleConfig("ScriptArguments", "Environment", "WorkingDirectory", null), - } }, - // C# - Lagacy DotNetCore - {ProjectKinds.CSCore, new ProjectConfigHandlers() { - SetConfig = SetCpsProjectConfig, - GetItemsFromConfig = GetItemsFromCpsProjectConfig - } }, - // F# - {ProjectKinds.FS, new ProjectConfigHandlers() { - SetConfig = (project, arguments, _, workDir, launchApp) => { - SetMultiConfigProperty(project, arguments, "StartArguments"); - SetMultiConfigProperty(project, workDir, "StartWorkingDirectory"); - SetMultiConfigProperty(project, launchApp, "StartProgram"); - }, - GetItemsFromConfig = GetItemsFromMultiConfig("StartArguments", "StartWorkingDirectory", "StartProgram") - } }, - // Fortran - {ProjectKinds.Fortran, new ProjectConfigHandlers() { - SetConfig = SetVFProjEngineConfig, - GetItemsFromConfig = GetItemsFromVFProjEngineConfig - } }, - }; - - private static ProjectConfigHandlers CpsProjectConfigHandlers = new ProjectConfigHandlers - { - GetItemsFromConfig = GetItemsFromCpsProjectConfig, - SetConfig = SetCpsProjectConfig, - }; - public bool IsSupportedProject(IVsHierarchyWrapper project) { if (project == null) @@ -736,14 +728,14 @@ public bool IsSupportedProject(IVsHierarchyWrapper project) if (project.IsSharedAssetsProject()) return false; - return supportedProjects.ContainsKey(project.GetKind()); + return configHandlerForSupportedProjects.ContainsKey(project.GetKind()); } - private static bool TryGetProjectConfigHandlers(IVsHierarchyWrapper project, out ProjectConfigHandlers handler) + private bool TryGetProjectConfigHandlers(IVsHierarchyWrapper project, out ProjectConfigHandlers handler) { - if (project.IsCpsProject()) + if (project.SupportsLaunchProfiles()) { - handler = CpsProjectConfigHandlers; + handler = cpsProjectConfigHandlers; return true; } @@ -753,14 +745,14 @@ private static bool TryGetProjectConfigHandlers(IVsHierarchyWrapper project, out { foreach (var kind in project.GetAllTypeGuidsFromFile()) { - if (kind != projectKind && supportedProjects.TryGetValue(kind, out handler)) + if (kind != projectKind && configHandlerForSupportedProjects.TryGetValue(kind, out handler)) { return true; } } } - return supportedProjects.TryGetValue(projectKind, out handler); + return configHandlerForSupportedProjects.TryGetValue(projectKind, out handler); } public List GetItemsFromProjectConfig(IVsHierarchyWrapper project) @@ -825,4 +817,4 @@ static class ProjectKinds public static readonly Guid CSCore = Guid.Parse("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"); } -} \ No newline at end of file +} diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Services/VisualStudioHelperService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/VisualStudioHelperService.cs index c4bf1cf0..b3e1ae2a 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/VisualStudioHelperService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/VisualStudioHelperService.cs @@ -1,4 +1,4 @@ -using EnvDTE; +using EnvDTE; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -113,6 +113,7 @@ class VisualStudioHelperService : IVisualStudioHelperService, IAsyncInitializabl public event EventHandler ProjectAfterRename; private readonly Lazy _projectConfigService; + private readonly Lazy cpsProjectConfigService; class ProjectState : IDisposable { @@ -122,14 +123,14 @@ class ProjectState : IDisposable private IDisposable _launchSettingsChangeListenerDisposable; - public ProjectState(IVsHierarchyWrapper pHierarchy, Action launchProfileChangeAction) + public ProjectState(IVsHierarchyWrapper pHierarchy, ICpsProjectConfigService cpsProjectConfigService, Action launchProfileChangeAction) { ProjectDir = pHierarchy.GetProjectDir(); ProjectName = pHierarchy.GetName(); IsLoaded = pHierarchy.IsLoaded(); - if (pHierarchy.IsCpsProject()) - _launchSettingsChangeListenerDisposable = CpsProjectSupport.ListenToLaunchProfileChanges(pHierarchy.GetProject(), launchProfileChangeAction); + if (pHierarchy.SupportsLaunchProfiles()) + _launchSettingsChangeListenerDisposable = cpsProjectConfigService.ListenToLaunchProfileChanges(pHierarchy.GetProject(), launchProfileChangeAction); } public void Dispose() @@ -140,10 +141,14 @@ public void Dispose() private Dictionary ProjectStateMap = new Dictionary(); - public VisualStudioHelperService(Lazy projectConfigService) + public VisualStudioHelperService( + Lazy projectConfigService, + Lazy cpsProjectConfigService) { this.package = CmdArgsPackage.Instance; _projectConfigService = projectConfigService; + this.cpsProjectConfigService = cpsProjectConfigService; + _VSConstants_VSStd97CmdID_GUID = typeof(VSConstants.VSStd97CmdID).GUID.ToString("B").ToUpper(); _VSConstants_VSStd2KCmdID_GUID = typeof(VSConstants.VSStd2KCmdID).GUID.ToString("B").ToUpper(); @@ -187,7 +192,7 @@ private void AddProjectState(IVsHierarchyWrapper pHierarchy) { Guid projectGuid = pHierarchy.GetGuid(); ProjectStateMap.GetValueOrDefault(projectGuid)?.Dispose(); - ProjectStateMap[projectGuid] = new ProjectState(pHierarchy, () => + ProjectStateMap[projectGuid] = new ProjectState(pHierarchy,cpsProjectConfigService.Value, () => { ThreadHelper.JoinableTaskFactory.Run(async () => { diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems b/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems index 58cfce08..f0b5190f 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems +++ b/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems @@ -1,136 +1,136 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 676c8571-fc9d-43dd-948e-897817a07039 - - - SmartCmdArgs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ArgumentItemView.xaml - - - - - - - - - - - - - - - - - - - - - - - - - SettingsControl.xaml - - - - - ToolWindowControl.xaml - - - - - SettingsCheckBox.xaml - - - - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 676c8571-fc9d-43dd-948e-897817a07039 + + + SmartCmdArgs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ArgumentItemView.xaml + + + + + + + + + + + + + + + + + + + + + + + + + SettingsControl.xaml + + + + + ToolWindowControl.xaml + + + + + SettingsCheckBox.xaml + + + + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + \ No newline at end of file diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Wrapper/IVsHierarchyWrapper.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Wrapper/IVsHierarchyWrapper.cs index 438fffe5..3279abef 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Wrapper/IVsHierarchyWrapper.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Wrapper/IVsHierarchyWrapper.cs @@ -23,7 +23,7 @@ public interface IVsHierarchyWrapper string GetName(); Project GetProject(); string GetProjectDir(); - bool IsCpsProject(); + bool SupportsLaunchProfiles(); bool IsSharedAssetsProject(); bool IsLoaded(); bool TryGetIconMoniker(out ImageMoniker iconMoniker); @@ -81,9 +81,9 @@ public Project GetProject() /// see: https://github.com/dotnet/project-system /// see: https://github.com/Microsoft/VSProjectSystem/blob/master/doc/automation/detect_whether_a_project_is_a_CPS_project.md /// - public bool IsCpsProject() + public bool SupportsLaunchProfiles() { - return hierarchy.IsCapabilityMatch("CPS"); + return hierarchy.IsCapabilityMatch("LaunchProfiles"); // https://github.com/Microsoft/VSProjectSystem/blob/master/doc/overview/project_capabilities.md not all cps projects support profiles https://github.com/microsoft/service-fabric-issues/issues/1095 } public bool IsSharedAssetsProject() diff --git a/SmartCmdArgs/SmartCmdArgs17/SmartCmdArgs17.csproj b/SmartCmdArgs/SmartCmdArgs17/SmartCmdArgs17.csproj index 5c6cba2e..afbcee96 100644 --- a/SmartCmdArgs/SmartCmdArgs17/SmartCmdArgs17.csproj +++ b/SmartCmdArgs/SmartCmdArgs17/SmartCmdArgs17.csproj @@ -1,4 +1,4 @@ - + 17.0 @@ -32,7 +32,7 @@ full false bin\Debug\ - TRACE;DEBUG;VS17 + TRACE;DEBUG;VS17;DYNAMIC-DISABLED_VSProjectManaged prompt 4 @@ -99,18 +99,31 @@ 7.0.0 - - 15.8.243 - - - 2.0.6142705 - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all + + + + + 17.9.380 + + + 2.0.6142705 + + + + + + + 17.7.37.99 + + + + CmdArgsPackage.vsct @@ -163,4 +176,4 @@ --> - \ No newline at end of file + diff --git a/SmartCmdArgs/SmartCmdArgs17/source.extension.cs b/SmartCmdArgs/SmartCmdArgs17/source.extension.cs index 3812e107..17b700ff 100644 --- a/SmartCmdArgs/SmartCmdArgs17/source.extension.cs +++ b/SmartCmdArgs/SmartCmdArgs17/source.extension.cs @@ -11,7 +11,7 @@ internal sealed partial class Vsix public const string Name = "Smart Command Line Arguments VS2022"; public const string Description = @"A Visual Studio 2022 Extension which aims to provide a better UI to manage your command line arguments."; public const string Language = "en-US"; - public const string Version = "3.0.3"; + public const string Version = "3.6"; public const string Author = "Irame & MBulli"; public const string Tags = "Commandline Command Line Arguments cmd project"; } diff --git a/SmartCmdArgs/SmartCmdArgs17/source.extension.vsixmanifest b/SmartCmdArgs/SmartCmdArgs17/source.extension.vsixmanifest index f635a1d7..a11e15c8 100644 --- a/SmartCmdArgs/SmartCmdArgs17/source.extension.vsixmanifest +++ b/SmartCmdArgs/SmartCmdArgs17/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Smart Command Line Arguments VS2022 A Visual Studio 2022 Extension which aims to provide a better UI to manage your command line arguments. https://github.com/MBulli/SmartCommandlineArgs diff --git a/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Services/ItemAggregationServiceTests.cs b/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Services/ItemAggregationServiceTests.cs index 99fcbc4a..aafa2c3d 100644 --- a/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Services/ItemAggregationServiceTests.cs +++ b/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Services/ItemAggregationServiceTests.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using System; using System.Collections.Generic; using SmartCmdArgs.ViewModel; @@ -19,6 +19,7 @@ public class ItemAggregationServiceTests private readonly Mock itemEvaluationServiceMock; private readonly Mock vsHelperServiceMock; private readonly Mock toolWindowHistoryMock; + private readonly Mock cpsProjectConfigService; private readonly TreeViewModel treeViewModel; private readonly ItemAggregationService itemAggregationService; @@ -29,6 +30,7 @@ public ItemAggregationServiceTests(GlobalServiceProvider sp) itemEvaluationServiceMock = new Mock(); vsHelperServiceMock = new Mock(); toolWindowHistoryMock = new Mock(); + cpsProjectConfigService = new Mock(); treeViewModel = new TreeViewModel(toolWindowHistoryMock.LazyObject()); itemEvaluationServiceMock.Setup(x => x.EvaluateMacros(It.IsAny(), It.IsAny())).Returns((arg, _) => arg); @@ -36,7 +38,8 @@ public ItemAggregationServiceTests(GlobalServiceProvider sp) itemAggregationService = new ItemAggregationService( itemEvaluationServiceMock.Object, vsHelperServiceMock.Object, - treeViewModel + treeViewModel, + cpsProjectConfigService.Object ); } diff --git a/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Utils/Extensions.cs b/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Utils/Extensions.cs index ad819059..af0d4f9c 100644 --- a/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Utils/Extensions.cs +++ b/SmartCmdArgs/Testing/SmartCmdArgs.Tests/Utils/Extensions.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio; +using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; using Moq; using SmartCmdArgs.Services; @@ -35,7 +35,7 @@ public static Mock WithGuid(this Mock public static Mock AsCpsProject(this Mock mock) { - mock.Setup(x => x.IsCpsProject()).Returns(true); + mock.Setup(x => x.SupportsLaunchProfiles()).Returns(true); return mock; }