diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..93ecaf3b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +[*] + +#Our EditorConfig + + +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.cs] +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true + diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs b/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs index 12b59e34..5661d75c 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsOptionPage.cs @@ -1,5 +1,6 @@ -using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell; using SmartCmdArgs.Helper; +using System; using System.ComponentModel; using System.Linq; using System.Reflection; @@ -45,6 +46,19 @@ public enum EnableBehaviour [Description("Enable by default (old behaviour)")] EnableByDefault, } + [Flags] + [TypeConverter(typeof(EnumDescriptionTypeConverter))] + public enum SetActiveProfileBehavior + { + [Description("Never, due to a bug in virtual profiles you will have to reselect Smart CLI Args every time you open a project")] + Never = 1 << 0, + [Description("On Smart CLI Arg tree changed (ie option checked)")] + OnTreeChanged = 1 << 1, + [Description("On first run/debug")] + OnRun = 1 << 2, + [Description("On Smart CLI Arg option checked or first run/debug")] + OnCheckedOrRun = OnTreeChanged | OnRun + } public class CmdArgsOptionPage : DialogPage, INotifyPropertyChanged { @@ -64,6 +78,7 @@ public CmdArgsOptionPage() : base() } private EnableBehaviour _enableBehaviour; + private SetActiveProfileBehavior _setActiveProfileBehavior; private RelativePathRootOption _relativePathRoot; private bool _useMonospaceFont; @@ -78,6 +93,7 @@ public CmdArgsOptionPage() : base() private bool _manageWorkingDirectories; private bool _manageLaunchApplication; private bool _vcsSupportEnabled; + private bool _useCpsVirtualProfile; private bool _useSolutionDir; private bool _macroEvaluationEnabled; @@ -91,6 +107,16 @@ public EnableBehaviour EnableBehaviour set => SetAndNotify(value, ref _enableBehaviour); } + [Category("Settings Defaults")] + [DisplayName("Set Active Profile Behavior")] + [Description("When we should automatically make ourselves the active profile")] + [DefaultValue(SetActiveProfileBehavior.OnTreeChanged)] + public SetActiveProfileBehavior SetActiveProfileBehavior + { + get => _setActiveProfileBehavior; + set => SetAndNotify(value, ref _setActiveProfileBehavior); + } + [Category("General")] [DisplayName("Relative path root")] [Description("Sets the base path that is used to resolve relative paths for the open/reveal file/folder context menu option.")] @@ -111,6 +137,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 +228,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 d37d7b74..f78d4091 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsPackage.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/CmdArgsPackage.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // Copyright (c) Company. All rights reserved. // @@ -69,7 +69,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; @@ -105,7 +106,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) @@ -170,7 +172,8 @@ private ServiceProvider ConfigureServices() services.AddLazySingleton(x => GetDialogPage()); services.AddLazySingleton(); services.AddLazySingleton(); - services.AddLazySingleton(); + services.AddLazySingleton(); + services.AddLazySingleton(); services.AddLazySingleton(); services.AddSingleton(); services.AddSingleton(); @@ -262,9 +265,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/CpsProjectConfigService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectConfigService.cs new file mode 100644 index 00000000..1de94293 --- /dev/null +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectConfigService.cs @@ -0,0 +1,365 @@ +using EnvDTE; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Debug; +using Microsoft.VisualStudio.ProjectSystem.Properties; +using SmartCmdArgs.DataSerialization; +using System; +using System.Collections.Generic; +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 +// include the ManagedProjectSystem extension by default. For instance, installing Visual Studio with only +// the C++ workload does not install this extension, whereas it's included with the .NET workload at +// "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Microsoft\ManagedProjectSystem". +// One might consider adding the Microsoft.VisualStudio.ProjectSystem assembly to the VSIX. However, this approach fails due to +// Visual Studio's configuration file (located at "%userprofile%\AppData\Local\Microsoft\VisualStudio\17.0_xxxxxxxx\devenv.exe.config") +// specifying a binding redirect for Microsoft.VisualStudio.ProjectSystem.Managed to the latest version. +// As such, ensuring compatibility requires knowledge of the version Visual Studio redirects to, which varies +// by Visual Studio installation version. + +namespace SmartCmdArgs.Services +{ + public interface ICpsProjectConfigService + { + 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, UpdateProjectConfigReason reason); + } + + 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) + { + // VC implements this on their DTE.Project.Object + context = project.Object as IVsBrowseObjectContext; + } + + if (context == null) + { + unconfiguredProjectServices = null; + projectServices = null; + + return false; + } + else + { + UnconfiguredProject unconfiguredProject = context.UnconfiguredProject; + + // VS2017 returns the interface types of the services classes but VS2019 returns the classes directly. + // Hence, we need to obtain the object via reflection to avoid MissingMethodExceptions. + object services = typeof(UnconfiguredProject).GetProperty("Services").GetValue(unconfiguredProject); + object prjServices = typeof(IProjectService).GetProperty("Services").GetValue(unconfiguredProject.ProjectService); + + unconfiguredProjectServices = services as IUnconfiguredProjectServices; + projectServices = prjServices as IProjectServices; + + return unconfiguredProjectServices != null && project != null; + } + } + + public string GetActiveLaunchProfileName(EnvDTE.Project project) + { + if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) + { + var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); + return launchSettingsProvider?.ActiveProfile?.Name; + } + return null; + } + + 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)) + { + var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); + return launchSettingsProvider?.CurrentSnapshot?.Profiles?.Select(p => p.Name); + } + return null; + } + + 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) + return null; + + return launchSettingsProvider.SourceBlock.LinkTo( + new ActionBlock(_ => listener()), + new DataflowLinkOptions { PropagateCompletion = true }); + } + + return null; + } + + public void SetConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp, UpdateProjectConfigReason reason) + { + IUnconfiguredProjectServices unconfiguredProjectServices; + IProjectServices projectServices; + + if (TryGetProjectServices(project, out unconfiguredProjectServices, out projectServices)) + { + var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); + ILaunchProfile baseLaunchProfile = null; + var applyProfileFix=true; + if (optionsSettingsService.UseCpsVirtualProfile) + { + baseLaunchProfile = launchSettingsProvider.CurrentSnapshot.Profiles.FirstOrDefault(x => x.Name == VirtualProfileName); + } + + if (baseLaunchProfile == null) + { + baseLaunchProfile = launchSettingsProvider?.ActiveProfile; + } + else + applyProfileFix = false; //we already existed + + var dbgsnapshot = launchSettingsProvider.CurrentSnapshot; + var dbgactiveProfile = launchSettingsProvider.ActiveProfile; + //OurLogger.Info(LogCat.Other, $"dbgsnapshot {dbgsnapshot?.ActiveProfile?.Name}, dbgactiveProfile {dbgactiveProfile?.Name}, baseLaunchProfile: {baseLaunchProfile?.Name} forceOurProfileActive: {applyProfileFix}"); + if (baseLaunchProfile == null) + return; + + var writableLaunchProfile = WritableLaunchProfile.GetWritableLaunchProfile(baseLaunchProfile); + + if (arguments != null) + writableLaunchProfile.CommandLineArgs = arguments; + else + applyProfileFix = false;//incase not used on a solution + + if (envVars != null) + writableLaunchProfile.EnvironmentVariables = envVars.ToImmutableDictionary(); + + if (workDir != null) + writableLaunchProfile.WorkingDirectory = workDir; + + if (launchApp != null) + writableLaunchProfile.CommandName = launchApp; + + if (optionsSettingsService.UseCpsVirtualProfile) + { + writableLaunchProfile.Name = VirtualProfileName; + writableLaunchProfile.DoNotPersist = true; + } + var activeProfileBehavior = optionsSettingsService.SetActiveProfileBehavior; + var setActiveProfile = (activeProfileBehavior.HasFlag(SetActiveProfileBehavior.OnRun) && (reason == UpdateProjectConfigReason.RunDebugLaunch && applyProfileFix)) || (activeProfileBehavior.HasFlag(SetActiveProfileBehavior.OnTreeChanged) && reason == UpdateProjectConfigReason.TreeChange); //applyprofilefix is set on first run only when we are added, if this happens due to a tree change but the behavior is OnRun we want to set the active profile. Note as this only happens on first run it is not the same as setting it active on every tree change + + + projectServices.ThreadingPolicy.ExecuteSynchronously(async () => + { + + if (applyProfileFix){ + var activeProfile = launchSettingsProvider.ActiveProfile?.Name; + if (! String.IsNullOrWhiteSpace( activeProfile)) + await launchSettingsProvider.SetActiveProfileAsync(activeProfile); + } + + await launchSettingsProvider.AddOrUpdateProfileAsync(writableLaunchProfile, addToFront: false); + if (setActiveProfile) + await launchSettingsProvider.SetActiveProfileAsync(writableLaunchProfile.Name); + + }); + } + } + + public void GetItemsFromConfig(EnvDTE.Project project, List allArgs, bool includeArgs, bool includeEnvVars, bool includeWorkDir, bool includeLaunchApp) + { + IUnconfiguredProjectServices unconfiguredProjectServices; + IProjectServices projectServices; + + if (TryGetProjectServices(project, out unconfiguredProjectServices, out projectServices)) + { + var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); + var launchProfiles = launchSettingsProvider?.CurrentSnapshot?.Profiles; + + if (launchProfiles == null) + return; + + foreach (var profile in launchProfiles) + { + var profileGrp = new CmdItemJson { Command = profile.Name, LaunchProfile = profile.Name, Items = new List() }; + + if (includeArgs && !string.IsNullOrEmpty(profile.CommandLineArgs)) + { + profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.CmdArg, Command = profile.CommandLineArgs, Enabled = true }); + } + + if (includeEnvVars) + { + foreach (var envVarPair in profile.EnvironmentVariables) + { + profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.EnvVar, Command = $"{envVarPair.Key}={envVarPair.Value}", Enabled = true }); + } + } + + if (includeWorkDir && !string.IsNullOrEmpty(profile.WorkingDirectory)) + { + profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.WorkDir, Command = profile.WorkingDirectory, Enabled = true }); + } + + if (includeLaunchApp && !string.IsNullOrEmpty(profile.CommandName)) + { + profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.LaunchApp, Command = profile.CommandName, Enabled = true }); + } + + if (profileGrp.Items.Count > 0) + { + allArgs.Add(profileGrp); + } + } + } + } + } + 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; } + public string CommandLineArgs { set; get; } + public string WorkingDirectory { set; get; } + public bool LaunchBrowser { set; get; } + public string LaunchUrl { set; get; } + 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; + CommandLineArgs = launchProfile.CommandLineArgs; + WorkingDirectory = launchProfile.WorkingDirectory; + LaunchBrowser = launchProfile.LaunchBrowser; + 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/Helper/CpsProjectSupport.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectSupport.cs deleted file mode 100644 index 5b364858..00000000 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Helper/CpsProjectSupport.cs +++ /dev/null @@ -1,210 +0,0 @@ -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Debug; -using Microsoft.VisualStudio.ProjectSystem.Properties; -using SmartCmdArgs.DataSerialization; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks.Dataflow; - -// 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 -// include the ManagedProjectSystem extension by default. For instance, installing Visual Studio with only -// the C++ workload does not install this extension, whereas it's included with the .NET workload at -// "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Microsoft\ManagedProjectSystem". -// One might consider adding the Microsoft.VisualStudio.ProjectSystem assembly to the VSIX. However, this approach fails due to -// Visual Studio's configuration file (located at "%userprofile%\AppData\Local\Microsoft\VisualStudio\17.0_xxxxxxxx\devenv.exe.config") -// specifying a binding redirect for Microsoft.VisualStudio.ProjectSystem.Managed to the latest version. -// As such, ensuring compatibility requires knowledge of the version Visual Studio redirects to, which varies -// by Visual Studio installation version. - -namespace SmartCmdArgs.Helper -{ - public static class CpsProjectSupport - { - private static bool TryGetProjectServices(EnvDTE.Project project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices) - { - IVsBrowseObjectContext context = project as IVsBrowseObjectContext; - if (context == null && project != null) - { - // VC implements this on their DTE.Project.Object - context = project.Object as IVsBrowseObjectContext; - } - - if (context == null) - { - unconfiguredProjectServices = null; - projectServices = null; - - return false; - } - else - { - UnconfiguredProject unconfiguredProject = context.UnconfiguredProject; - - // VS2017 returns the interface types of the services classes but VS2019 returns the classes directly. - // Hence, we need to obtain the object via reflection to avoid MissingMethodExceptions. - object services = typeof(UnconfiguredProject).GetProperty("Services").GetValue(unconfiguredProject); - object prjServices = typeof(IProjectService).GetProperty("Services").GetValue(unconfiguredProject.ProjectService); - - unconfiguredProjectServices = services as IUnconfiguredProjectServices; - projectServices = prjServices as IProjectServices; - - return unconfiguredProjectServices != null && project != null; - } - } - - public static string GetActiveLaunchProfileName(EnvDTE.Project project) - { - if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) - { - var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); - return launchSettingsProvider?.ActiveProfile?.Name; - } - return null; - } - - public static IEnumerable GetLaunchProfileNames(EnvDTE.Project project) - { - if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) - { - var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); - return launchSettingsProvider?.CurrentSnapshot?.Profiles?.Select(p => p.Name); - } - return null; - } - - public static IDisposable ListenToLaunchProfileChanges(EnvDTE.Project project, Action listener) - { - if (TryGetProjectServices(project, out IUnconfiguredProjectServices unconfiguredProjectServices, out IProjectServices projectServices)) - { - var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); - - if (launchSettingsProvider == null) - return null; - - return launchSettingsProvider.SourceBlock.LinkTo( - new ActionBlock(_ => listener()), - new DataflowLinkOptions { PropagateCompletion = true }); - } - - return null; - } - - public static void SetCpsProjectConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp) - { - IUnconfiguredProjectServices unconfiguredProjectServices; - IProjectServices projectServices; - - if (TryGetProjectServices(project, out unconfiguredProjectServices, out projectServices)) - { - var launchSettingsProvider = unconfiguredProjectServices.ExportProvider.GetExportedValue(); - var activeLaunchProfile = launchSettingsProvider?.ActiveProfile; - - if (activeLaunchProfile == null) - return; - - WritableLaunchProfile writableLaunchProfile = new WritableLaunchProfile(activeLaunchProfile); - - if (arguments != null) - writableLaunchProfile.CommandLineArgs = arguments; - - if (envVars != null) - writableLaunchProfile.EnvironmentVariables = envVars.ToImmutableDictionary(); - - if (workDir != null) - writableLaunchProfile.WorkingDirectory = workDir; - - 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(() => - { - return launchSettingsProvider.AddOrUpdateProfileAsync(writableLaunchProfile, addToFront: false); - }); - } - } - - public static List GetCpsProjectAllArguments(EnvDTE.Project project, 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; - - foreach (var profile in launchProfiles) - { - var profileGrp = new CmdItemJson { Command = profile.Name, LaunchProfile = profile.Name, Items = new List() }; - - if (includeArgs && !string.IsNullOrEmpty(profile.CommandLineArgs)) - { - profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.CmdArg, Command = profile.CommandLineArgs, Enabled = true }); - } - - if (includeEnvVars) - { - foreach (var envVarPair in profile.EnvironmentVariables) - { - profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.EnvVar, Command = $"{envVarPair.Key}={envVarPair.Value}", Enabled = true }); - } - } - - if (includeWorkDir && !string.IsNullOrEmpty(profile.WorkingDirectory)) - { - profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.WorkDir, Command = profile.WorkingDirectory, Enabled = true }); - } - - if (includeLaunchApp && !string.IsNullOrEmpty(profile.CommandName)) - { - profileGrp.Items.Add(new CmdItemJson { Type = ViewModel.CmdParamType.LaunchApp, Command = profile.CommandName, Enabled = true }); - } - - if (profileGrp.Items.Count > 0) - { - result.Add(profileGrp); - } - } - } - - return result; - } - } - - class WritableLaunchProfile : ILaunchProfile - { - public string Name { set; get; } - public string CommandName { set; get; } - public string ExecutablePath { set; get; } - public string CommandLineArgs { set; get; } - public string WorkingDirectory { set; get; } - public bool LaunchBrowser { set; get; } - public string LaunchUrl { set; get; } - public ImmutableDictionary EnvironmentVariables { set; get; } - public ImmutableDictionary OtherSettings { set; get; } - - public WritableLaunchProfile(ILaunchProfile launchProfile) - { - Name = launchProfile.Name; - ExecutablePath = launchProfile.ExecutablePath; - CommandName = launchProfile.CommandName; - CommandLineArgs = launchProfile.CommandLineArgs; - WorkingDirectory = launchProfile.WorkingDirectory; - LaunchBrowser = launchProfile.LaunchBrowser; - LaunchUrl = launchProfile.LaunchUrl; - EnvironmentVariables = launchProfile.EnvironmentVariables; - OtherSettings = launchProfile.OtherSettings; - } - } -} 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 b8ed9fd3..5d2ec0f5 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; @@ -116,5 +124,13 @@ private void UseSolutionDirChanged() fileStorage.DeleteAllUnusedArgFiles(); fileStorage.SaveAllProjects(); } + private void UseCpsVirtualProfileChanged() + { + foreach (var project in vsHelper.GetSupportedProjects().Where(x => x.SupportsLaunchProfiles())) + { + projectConfigService.UpdateProjectConfig(project, UpdateProjectConfigReason.UseVirtualProfileChanged); + cpsProjectConfigService.SetActiveLaunchProfileToVirtualProfile(project.GetProject()); + } + } } } diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/OptionsSettingsService.cs index 5f1e1aa4..1911f967 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; @@ -24,11 +24,13 @@ public interface IOptionsSettingsService bool? GatherArgsIgnoreCpp { get; } // Options + bool UseCpsVirtualProfile { get; } bool UseMonospaceFont { get; } bool DisplayTagForCla { get; } bool DeleteEmptyFilesAutomatically { get; } bool DeleteUnnecessaryFilesAutomatically { get; } EnableBehaviour EnableBehaviour { get; } + SetActiveProfileBehavior SetActiveProfileBehavior { get; } InactiveDisableMode DisableInactiveItems { get; } RelativePathRootOption RelativePathRoot { get; } @@ -69,8 +71,14 @@ public OptionsSettingsService( public bool? GatherArgsIgnoreCpp => settingsViewModel.GatherArgsIgnoreCpp; // 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 SetActiveProfileBehavior SetActiveProfileBehavior => OptionsPage.SetActiveProfileBehavior; public bool DeleteEmptyFilesAutomatically => OptionsPage.DeleteEmptyFilesAutomatically; public bool DeleteUnnecessaryFilesAutomatically => OptionsPage.DeleteUnnecessaryFilesAutomatically; public EnableBehaviour EnableBehaviour => OptionsPage.EnableBehaviour; diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs index e144f24f..89ea8ddc 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/ProjectConfigService.cs @@ -8,13 +8,16 @@ namespace SmartCmdArgs.Services { + public enum UpdateProjectConfigReason{ RunDebugLaunch, TreeChange,UseVirtualProfileChanged } public interface IProjectConfigService { bool IsSupportedProject(IVsHierarchyWrapper project); List GetItemsFromProjectConfig(IVsHierarchyWrapper project); - void UpdateProjectConfig(IVsHierarchyWrapper project); + void UpdateProjectConfig(IVsHierarchyWrapper project, UpdateProjectConfigReason reason); + + } public class ProjectConfigService : IProjectConfigService @@ -22,24 +25,110 @@ 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 { - public delegate void SetConfigDelegate(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp); + public delegate void SetConfigDelegate(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp, UpdateProjectConfigReason reason); public delegate void GetAllArgumentsDelegate(EnvDTE.Project project, List allArgs, bool includeArgs, bool includeEnvVars, bool includeWorkDir, bool includeLaunchApp); public SetConfigDelegate SetConfig; public GetAllArgumentsDelegate GetItemsFromConfig; } + private Dictionary InitConfigHandlerForSupportedProjects() + { + return new Dictionary() + { + // C# + {ProjectKinds.CS, new ProjectConfigHandlers() { + SetConfig = (project, arguments, envVars, workDir, launchApp, reason) => { + 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, reason) => SetMultiConfigProperty(project, arguments, "UAPDebug.CommandLineArguments"), + GetItemsFromConfig = GetItemsFromMultiConfig("UAPDebug.CommandLineArguments") + } }, + + // VB.NET + {ProjectKinds.VB, new ProjectConfigHandlers() { + SetConfig = (project, arguments, _, workDir, launchApp, reason) => { + 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, _, reason) => { + 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, _, reason) => { + 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, reason) => { + 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}")); @@ -291,7 +380,7 @@ private static ProjectConfigHandlers.GetAllArgumentsDelegate GetItemsFromMultiCo ("AppHostLocalDebugger", "CommandLineArgs", null, null, null), }; - private static void SetVCProjEngineConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp) + private static void SetVCProjEngineConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp, UpdateProjectConfigReason reason) { ThreadHelper.ThrowIfNotOnUIThread(); @@ -505,7 +594,7 @@ private static string VFFormatConfigName(EnvDTE.Configuration vcCfg) // to optain the right configurations object from `Project.Object.Configurations` // this object is simmilar to the VCProjEngine configuration and has `DebugSettings` // which contain the CommandArguments which we can use to set the args. - private static void SetVFProjEngineConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp) + private static void SetVFProjEngineConfig(EnvDTE.Project project, string arguments, IDictionary envVars, string workDir, string launchApp, UpdateProjectConfigReason reason) { ThreadHelper.ThrowIfNotOnUIThread(); @@ -623,100 +712,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 +731,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 +748,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) @@ -781,7 +776,7 @@ public List GetItemsFromProjectConfig(IVsHierarchyWrapper project) return result; } - public void UpdateProjectConfig(IVsHierarchyWrapper project) + public void UpdateProjectConfig(IVsHierarchyWrapper project, UpdateProjectConfigReason reason) { if (!lifeCycleService.Value.IsEnabled || project == null) return; @@ -796,10 +791,11 @@ public void UpdateProjectConfig(IVsHierarchyWrapper project) if (TryGetProjectConfigHandlers(project, out ProjectConfigHandlers handler)) { - handler.SetConfig(project.GetProject(), commandLineArgs, envVars, workDir, launchApp); + handler.SetConfig(project.GetProject(), commandLineArgs, envVars, workDir, launchApp, reason); + } - Logger.Info($"Updated Configuration for Project: {project.GetName()}"); + Logger.Info($"Updated Configuration for Project: {project.GetName()} command line: {commandLineArgs}"); } } @@ -825,4 +821,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/TreeViewEventHandlingService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/TreeViewEventHandlingService.cs index acedeeb1..3ab878b5 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/TreeViewEventHandlingService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/TreeViewEventHandlingService.cs @@ -86,7 +86,7 @@ private void OnTreeChangedThrottled(object sender, TreeViewModel.TreeChangedEven { var projectGuid = e.AffectedProject.Id; var project = vsHelper.HierarchyForProjectGuid(projectGuid); - projectConfigService.UpdateProjectConfig(project); + projectConfigService.UpdateProjectConfig(project, UpdateProjectConfigReason.TreeChange); } private void OnTreeChanged(object sender, TreeViewModel.TreeChangedEventArgs e) 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/Services/VsEventHandlingService.cs b/SmartCmdArgs/SmartCmdArgs.Shared/Services/VsEventHandlingService.cs index 23afe764..70613744 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/Services/VsEventHandlingService.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/Services/VsEventHandlingService.cs @@ -140,7 +140,7 @@ private void VsHelper_ProjectWillRun(object sender, EventArgs e) foreach (var startupProject in treeViewModel.StartupProjects) { var project = vsHelper.HierarchyForProjectGuid(startupProject.Id); - projectConfigService.UpdateProjectConfig(project); + projectConfigService.UpdateProjectConfig(project, UpdateProjectConfigReason.RunDebugLaunch); fileStorage.SaveProject(project); } } diff --git a/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems b/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems index 75f1a27e..afed7876 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems +++ b/SmartCmdArgs/SmartCmdArgs.Shared/SmartCmdArgs.Shared.projitems @@ -1,144 +1,144 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 676c8571-fc9d-43dd-948e-897817a07039 - - - SmartCmdArgs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ArgumentItemView.xaml - - - - - - - - - - - - - - - - - - - - - - GatherArgsQuestionControl.xaml - - - - - - - SettingsControl.xaml - - - - - ToolWindowControl.xaml - - - - - SettingsCheckBox.xaml - - - - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - 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 + + + + + + + + + + + + + + + + + + + + + + GatherArgsQuestionControl.xaml + + + + + + + SettingsControl.xaml + + + + + ToolWindowControl.xaml + + + + + SettingsCheckBox.xaml + + + + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + 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/ViewModel/ToolWindowViewModel.cs b/SmartCmdArgs/SmartCmdArgs.Shared/ViewModel/ToolWindowViewModel.cs index 7e657d93..7ce9d92d 100644 --- a/SmartCmdArgs/SmartCmdArgs.Shared/ViewModel/ToolWindowViewModel.cs +++ b/SmartCmdArgs/SmartCmdArgs.Shared/ViewModel/ToolWindowViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -36,6 +36,13 @@ public bool DisplayTagForCla set => SetAndNotify(value, ref _displayTagForCla); } + private SetActiveProfileBehavior _setActiveProfileBehavior; + public SetActiveProfileBehavior SetActiveProfileBehavior + { + get => _setActiveProfileBehavior; + set => SetAndNotify(value, ref _setActiveProfileBehavior); + } + private bool _showDisabledScreen; public bool ShowDisabledScreen { 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 70e0ee5e..9cda4c0a 100644 --- a/SmartCmdArgs/SmartCmdArgs17/source.extension.cs +++ b/SmartCmdArgs/SmartCmdArgs17/source.extension.cs @@ -1,18 +1,18 @@ -// ------------------------------------------------------------------------------ -// -// This file was generated by VSIX Synchronizer -// -// ------------------------------------------------------------------------------ -namespace SmartCmdArgs -{ - internal sealed partial class Vsix - { - public const string Id = "SmartCmdArgs17.6edd462d-d4d6-40b9-a7b4-451a6ce6c527"; - 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"; +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace SmartCmdArgs +{ + internal sealed partial class Vsix + { + public const string Id = "SmartCmdArgs17.6edd462d-d4d6-40b9-a7b4-451a6ce6c527"; + 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.2.0.1"; - public const string Author = "Irame & MBulli"; - public const string Tags = "Commandline Command Line Arguments cmd project"; - } -} + 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 80d0e0e0..7597ca05 100644 --- a/SmartCmdArgs/SmartCmdArgs17/source.extension.vsixmanifest +++ b/SmartCmdArgs/SmartCmdArgs17/source.extension.vsixmanifest @@ -9,6 +9,7 @@ Resources\BigIcon.png Resources\vsix_preview_image.png Commandline Command Line Arguments cmd project + true 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; }